* Re: git-log --follow?
From: Linus Torvalds @ 2007-07-12 17:49 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <7vsl7uvx8v.fsf_-_@assigned-by-dhcp.cox.net>
On Thu, 12 Jul 2007, Junio C Hamano wrote:
>
> I think this is just a testament that "following renames" is not
> as useful in a real project as people seem to believe, not a
> real complaint.
Yeah. That said, what you wanted would have actually worked with my
original strange patch to "git blame", and in particular that also would
allow you to get a "log" for certain lines in the file.
So something like
git blame -C -Lx,y --log
may still make sense to give people.
Here's a new version of that patch (I've long since lost the original one,
plus it probably wouldn't apply anyway, but it was easy to re-generate).
It doesn't set up the revs thing nicely, so trying to add "-p" or "--stat"
etc doesn't really get you what you'd want, and "--decorate" doesn't work
since it doesn't call "cmd_log_init".
So this certainly has some room for improvement, but my point is that "git
blame" actually knows all this, and in many ways does a better job than
"git log" (but a very *different* job!!).
So "git log" gives you the log for a (set of) pathname(s), while with this
patch, "git blame --log" gives you the log for the commits that can be
*blamed* for the current state of that pathname!
Two very different things, but both are valid, and interesting things to
do, I think. And I really think it's worth doing, if only because it's so
simple, and fits so well with the whole "git blame" structure!
Linus
---
diff --git a/builtin-blame.c b/builtin-blame.c
index 0519339..8de06d3 100644
--- a/builtin-blame.c
+++ b/builtin-blame.c
@@ -18,6 +18,7 @@
#include "cache-tree.h"
#include "path-list.h"
#include "mailmap.h"
+#include "log-tree.h"
static char blame_usage[] =
"git-blame [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-p] [-w] [-L n,m] [-S <revs-file>] [-M] [-C] [-C] [--contents <filename>] [--incremental] [commit] [--] file\n"
@@ -34,6 +35,7 @@ static char blame_usage[] =
" -L n,m Process only line range n,m, counting from 1\n"
" -M, -C Find line movements within and across files\n"
" --incremental Show blame entries as we find them, incrementally\n"
+" --log Show blame entries as we find them in 'git log' style\n"
" --contents file Use <file>'s contents as the final image\n"
" -S revs-file Use revisions from revs-file instead of calling git-rev-list\n";
@@ -45,6 +47,7 @@ static int max_score_digits;
static int show_root;
static int blank_boundary;
static int incremental;
+static int blame_log;
static int cmd_is_annotate;
static int xdl_opts = XDF_NEED_MINIMAL;
static struct path_list mailmap;
@@ -1431,11 +1434,22 @@ static void write_filename_info(const char *path)
* The blame_entry is found to be guilty for the range. Mark it
* as such, and show it in incremental output.
*/
-static void found_guilty_entry(struct blame_entry *ent)
+static void found_guilty_entry(struct blame_entry *ent, struct rev_info *rev)
{
if (ent->guilty)
return;
ent->guilty = 1;
+ if (blame_log) {
+ struct origin *suspect = ent->suspect;
+ struct commit *commit = suspect->commit;
+
+ if (commit->object.flags & METAINFO_SHOWN)
+ return;
+ commit->object.flags |= METAINFO_SHOWN;
+ log_tree_commit(rev, commit);
+ maybe_flush_or_die(stdout, "stdout");
+ return;
+ }
if (incremental) {
struct origin *suspect = ent->suspect;
@@ -1505,7 +1519,7 @@ static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt)
/* Take responsibility for the remaining entries */
for (ent = sb->ent; ent; ent = ent->next)
if (same_suspect(ent->suspect, suspect))
- found_guilty_entry(ent);
+ found_guilty_entry(ent, revs);
origin_decref(suspect);
if (DEBUG) /* sanity */
@@ -2204,6 +2218,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
}
else if (!strcmp("--incremental", arg))
incremental = 1;
+ else if (!strcmp("--log", arg))
+ save_commit_buffer = incremental = blame_log = 1;
else if (!strcmp("--score-debug", arg))
output_option |= OUTPUT_SHOW_SCORE;
else if (!strcmp("-f", arg) ||
@@ -2224,7 +2240,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
argv[unk++] = arg;
}
- if (!incremental)
+ if (blame_log || !incremental)
setup_pager();
if (!blame_move_score)
@@ -2324,7 +2340,14 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
argv[unk] = NULL;
init_revisions(&revs, NULL);
+
+ /* Maybe we should call "cmd_log_init()" here instead? */
+ revs.always_show_header = 1;
+ revs.commit_format = CMIT_FMT_DEFAULT;
+ revs.verbose_header = 1;
+ revs.abbrev = DEFAULT_ABBREV;
setup_revisions(unk, argv, &revs, NULL);
+
memset(&sb, 0, sizeof(sb));
/*
^ permalink raw reply related
* Re: [PATCH] apply delta depth bias to already deltified objects
From: Nicolas Pitre @ 2007-07-12 18:07 UTC (permalink / raw)
To: Brian Downing; +Cc: Junio C Hamano, git
In-Reply-To: <20070712164458.GC19073@lavos.net>
On Thu, 12 Jul 2007, Brian Downing wrote:
> On Thu, Jul 12, 2007 at 12:27:02PM -0400, Nicolas Pitre wrote:
> > Better yet, the integer truncation error should be compensated for, with
> > this:
> >
> > max_size =
> > (trg_entry->delta_size * max_depth + max_depth - trg_entry->depth) /
> > (max_depth - trg_entry->depth + 1);
>
> Yep, with this, my degenerate case seems to find the optimum solution
> (depth ~ 65) even at crazy maximum depths like 1000.
>
> Looks good to me.
Great.
Now to conclude this, I have a patch with much simpler math which I'll
post rsn.
Nicolas
^ permalink raw reply
* Re: mtimes of working files
From: Eric Wong @ 2007-07-12 18:25 UTC (permalink / raw)
To: Randal L. Schwartz; +Cc: Johannes Schindelin, Git Mailing List
In-Reply-To: <86myy122mm.fsf@blue.stonehenge.com>
"Randal L. Schwartz" <merlyn@stonehenge.com> wrote:
> >>>>> "Eric" == Eric Wong <normalperson@yhbt.net> writes:
>
> Eric> open FH, "git log -r --name-only --no-color --pretty=raw -z @ARGV |" or die $!;
>
> This breaks needlessly on @ARGV names that contain spaces. You want:
>
> open FH, "-|", qw(git log -r --name-only --no-color --pretty=raw -z), @ARGV or die $!;
>
> But that sounds familiar.... I think there's a function somewhere included in
> the git distro that does this. I'm old and senile though. :)
Yep, I added that @ARGV at the last second and didn't care enough to fix
it. I didn't want to link this into the git build system so that it
could find Git.pm, either.
So I'll just go with this 5.8-ism. I didn't really intend for that
script to go anywhere, maybe somebody who wants it badly enough can make
the ls-files call respect any path limiting intended in @ARGV but still
allow revision ranges to be passed (my original intention of supporting
@ARGV was only revision ranges).
--
Eric Wong
^ permalink raw reply
* Re: git-svn+cygwin failed fetch
From: Eric Wong @ 2007-07-12 18:27 UTC (permalink / raw)
To: Russ Dill; +Cc: git
In-Reply-To: <f9d2a5e10707121049o758109c0l4ebeb08250093ba0@mail.gmail.com>
Russ Dill <russ.dill@gmail.com> wrote:
> On 7/11/07, Eric Wong <normalperson@yhbt.net> wrote:
> >Russ Dill <russ.dill@gmail.com> wrote:
> >> On 7/11/07, Eric Wong <normalperson@yhbt.net> wrote:
> >> >Russ Dill <russ.dill@gmail.com> wrote:
> >> >> [...]/src $ mkdir foo
> >> >> [...]/src $ cd foo
> >> >> [...]/src/foo $ git-svn init -t tags -b branches -T trunk
> >> >> https://www.[...].com/svn/foo/bar/bla
> >> >> Initialized empty Git repository in .git/
> >> >> Using higher level of URL: https://www.[...].com/svn/foo/bar/bla =>
> >> >> https://www.[...].com/svn/foo
> >> >>
> >> >> [...]/src/foo $ git-svn fetch
> >> >> config --get svn-remote.svn.url: command returned error: 1
> >> >>
> >> >> [...]/src/foo $ git config --get svn-remote.svn.url
> >> >> https://www.[...].com/svn/foo
> >> >
> >> >Sorry, I can't help here other than recommending a real UNIX with
> >> >fork + pipe + exec and all that fun stuff.
> >> >
> >> >git-svn relies heavily[1] on both input and output pipes of the
> >> >safer-but-made-for-UNIX fork + pipe + exec(@list) variety, so I suspect
> >> >this is just the tip of the iceberg for Windows incompatibilies with
> >> >git-svn...
> >>
> >> Its actually reading and writing quite a bit of stuff from the config
> >> file, so why this one simple command would fail eludes me. Especially
> >> since it wrote it there in the first place. If I comment out the
> >> command_oneline and hardcode the value I know it should return,
> >> git-fetch runs. Its actually been running for several hours now.
> >
> >Wow. That's a pleasant surprise that anything in git-svn works at all
> >on cygwin. I was almost certain git-svn on Windows was a hopeless cause
> >from other chatter I had heard on the mailing list.
> >
> >command_oneline() is used everywhere in that code, so I'm at a total loss
> >as to why it would fail in one place. Can you put a the following lines
> >right before where it was failing?
> >
> > print "GIT_CONFIG: $ENV{GIT_CONFIG} | GIT_DIR: $ENV{GIT_DIR}\n";
> > system('cat', "$ENV{GIT_DIR}/config");
> >
> >And tell me what it outputs?
>
>
> Use of uninitialized value in concatenation (.) or string at
> /usr/bin/git-svn line 1189.
> GIT_CONFIG: | GIT_DIR: .git
> [core]
> repositoryformatversion = 0
> filemode = true
> bare = false
> logallrefupdates = true
> [svn-remote "svn"]
> url = https://www.[...].com/svn/foo
> fetch = bar/bla/trunk:refs/remotes/trunk
> branches = bar/bla/branches/*:refs/remotes/*
> tags = bar/bla/tags/*:refs/remotes/tags/*
> [gui]
> geometry = 864x678+162+162 104 204
>
>
> If I export GIT_CONFIG, then the problem goes away. Much better work
> around then hardcoding svn-remote.svn.url
Very strange. I do set GIT_CONFIG internally in git-svn in a few places
and then unset it.
> The git-svn fetch died overnight due to an http error. Its restarted
> now. There are dozens of branches and tags in the repo, and each one
> seems to take about a half hour to and hour to fully fetch. It takes a
> similar amount of time to checkout trunk with tortise SVN. The repo is
> local, but I don't have direct access to it.
SVN 1.4.4 with a working do_switch() API call should be much faster.
--
Eric Wong
^ permalink raw reply
* [PATCH] apply delta depth bias to already deltified objects
From: Nicolas Pitre @ 2007-07-12 18:33 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Brian Downing, git
In-Reply-To: <alpine.LFD.0.999.0707120049120.32552@xanadu.home>
We already apply a bias on the initial delta attempt with max_size being
a function of the base object depth. This has the effect of favoring
shallower deltas even if deeper deltas could be smaller, and therefore
creating a wider delta tree (see commits 4e8da195 and c3b06a69).
This principle should also be applied to all delta attempts for the same
object and not only the first attempt. With this the criteria for the
best delta is not only its size but also its depth, so that a shallower
delta might be selected even if it is larger than a deeper one. Even if
some deltas get larger, they allow for wider delta trees making the
depth limit less quickly reached and therefore better deltas can be
subsequently found, keeping the resulting pack size even smaller.
Runtime access to the pack should also benefit from shallower deltas.
Testing on different repositories showed slighter faster repacks,
smaller resulting packs, and a much nicer curve for delta depth
distribution with no more peak at the maximum depth level.
Improvements are even more significant with smaller depth limits.
Signed-off-by: Nicolas Pitre <nico@cam.org>
---
No, I wonn't provide the full test results again. Yes, they're
different and even slightly better with this version, but I can't be
bothered to run them all and wait for the results again. You'll have to
take my word or do your own.
diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c
index 54b9d26..b4f3e7c 100644
--- a/builtin-pack-objects.c
+++ b/builtin-pack-objects.c
@@ -1303,6 +1303,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
struct object_entry *trg_entry = trg->entry;
struct object_entry *src_entry = src->entry;
unsigned long trg_size, src_size, delta_size, sizediff, max_size, sz;
+ unsigned ref_depth;
enum object_type type;
void *delta_buf;
@@ -1332,12 +1333,17 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
/* Now some size filtering heuristics. */
trg_size = trg_entry->size;
- max_size = trg_size/2 - 20;
- max_size = max_size * (max_depth - src_entry->depth) / max_depth;
+ if (!trg_entry->delta) {
+ max_size = trg_size/2 - 20;
+ ref_depth = 1;
+ } else {
+ max_size = trg_entry->delta_size;
+ ref_depth = trg_entry->depth;
+ }
+ max_size = max_size * (max_depth - src_entry->depth) /
+ (max_depth - ref_depth + 1);
if (max_size == 0)
return 0;
- if (trg_entry->delta && trg_entry->delta_size <= max_size)
- max_size = trg_entry->delta_size;
src_size = src_entry->size;
sizediff = src_size < trg_size ? trg_size - src_size : 0;
if (sizediff >= max_size)
^ permalink raw reply related
* Perforce support.
From: Govind Salinas @ 2007-07-12 18:34 UTC (permalink / raw)
To: git
Hi,
I am hoping to convince my co-workers to start using a Distributed
SCM, hopefully git, and I wanted to see what people had to say about
the Perforce-git interoperability. To make it more fun we are doing
this on Windows.
I have been playing around with git for a month or so and have started
writing, what I hope will be, a nice GUI over git that works well on
Windows (Cygwin) and offers some feeling of familiarity to our
Perforce users. That however is only half the problem.
We need to be able to go back and forth to our main Perforce depot,
and while I understand that git-svn support is very good, I have only
seen limited support of Perforce. I was wondering if anyone has been
using git with p4 and how well did it work. We have very complex and
somewhat large "clients" that do a lot of mapping of directories
(which strikes me as particularly insane) and I was wondering if any
of the tools support that.
If anyone has any suggestions/guidance on how to do this I would appreciate it.
Thank You,
Govind Salinas.
^ permalink raw reply
* [PATCH] gitweb: new cgi parameter: opt
From: Miklos Vajna @ 2007-07-12 18:39 UTC (permalink / raw)
To: Jakub Narebski; +Cc: git, Junio C Hamano
In-Reply-To: <200707121211.32813.jnareb@gmail.com>
Currently the only supported value is '--no-merges' for the 'rss', 'atom',
'log', 'shortlog' and 'history' actions, but it can be easily extended to allow
other parameters for other actions.
Signed-off-by: Miklos Vajna <vmiklos@frugalware.org>
---
Hello,
Na Thu, Jul 12, 2007 at 12:11:32PM +0200, Jakub Narebski <jnareb@gmail.com> pisal(a):
> 'log', 'shortlog' and 'history' actions, but it can be easily extended to allow
> Micronit: it is unwritten (as of yet) requirement to word wrap commit
> message at 80 columns or less.
OK, though I think 79 is valid for "80 or less" :-)
> By the way, there is t9500-gitweb-standalone-no-errors.sh test script to
> check if gitweb doesn't give any Perl warnings or errors. Please try to
> use it; it should at least find errors about undefined values and such.
> But it has the disadvantage of requiring git to be build (compiled),
> even if theoretically testing gitweb doesn't require it.
>
> You would see something like
>
> Argument "nomerges" isn't numeric in numeric eq (==)
>
> when running this test with --debug, I think.
As far as i see the test passes without any such warning ATM.
> First, you don't need inner () parentheses to delimit/create list, as
> anonymous array reference constructor [] works like it. So it could be
> written simply as
>
> +my %options = (
> + "--no-merges" => ['rss', 'atom', 'log', 'shortlog', 'history'],
> +);
>
> Second, instead of quoting each word by hand, we can use handy Perl
> quoting operator, qw(), i.e. 'word list' operator, like below.
> See perlop(1), "Quote and Quote-like Operators" subsection
>
> +my %options = (
> + "--no-merges" => [ qw(rss atom log shortlog history) ],
> +);
Fixed.
> history view. So I'd use
>
> +our @options = $cgi->param('option');
>
> instead. This would make option validation bit harder, but I think not that
> harder. But it would also make using extra options easier: just @options
> instead of (defined $option ? $option : ()).
Done.
> I'd also use @extra_options, or @act_opts instead of @options as
> a variable name to be more descriptive.
Changed. Also renamed %options to %allowed_options.
> I'm also not sure if invalid option parameter for action should return
> error, or be simply ignored. This allow to hand-edit URL, changing for
> example action from 'commitdiff' to 'commit', not worrying about spurious
> parameters.
I did this way because gitweb also dies for invalid actions but it can
be removed if you don't like it.
> By the way, gitweb uses shortened names for paramaters. Perhaps 'opt'
> or 'op' instead of 'options' here and in href subroutine (below)?
Changed to 'opt'.
gitweb/gitweb.perl | 19 +++++++++++++++++++
1 files changed, 19 insertions(+), 0 deletions(-)
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 27580b5..13134fe 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -386,6 +386,23 @@ if (defined $hash_base) {
}
}
+my %allowed_options = (
+ "--no-merges" => [ qw(rss atom log shortlog history) ],
+);
+
+our @extra_options = $cgi->param('opt');
+if (defined @extra_options) {
+ foreach(@extra_options)
+ {
+ if (not grep(/^$_$/, keys %allowed_options)) {
+ die_error(undef, "Invalid option parameter");
+ }
+ if (not grep(/^$action$/, @{$allowed_options{$_}})) {
+ die_error(undef, "Invalid option parameter for this action");
+ }
+ }
+}
+
our $hash_parent_base = $cgi->param('hpb');
if (defined $hash_parent_base) {
if (!validate_refname($hash_parent_base)) {
@@ -537,6 +554,7 @@ sub href(%) {
action => "a",
file_name => "f",
file_parent => "fp",
+ extra_options => "opt",
hash => "h",
hash_parent => "hp",
hash_base => "hb",
@@ -1773,6 +1791,7 @@ sub parse_commits {
($arg ? ($arg) : ()),
("--max-count=" . $maxcount),
("--skip=" . $skip),
+ @extra_options,
$commit_id,
"--",
($filename ? ($filename) : ())
--
1.5.3.rc0.39.g46f7-dirty
^ permalink raw reply related
* Re: [PATCH] gitweb: new cgi parameter: option
From: Junio C Hamano @ 2007-07-12 18:45 UTC (permalink / raw)
To: Jakub Narebski; +Cc: Miklos Vajna, git
In-Reply-To: <200707121211.32813.jnareb@gmail.com>
Jakub Narebski <jnareb@gmail.com> writes:
> [*1*] At least in gitweb. If I understand correctly, you can use
> "git diff --cc tree1 tree2 tree2 ..." to get combined diff of specified
> tree-ish; I'm not sure if git-diff-tree support this. And I know that
> gitweb does not support this... at least for now. Would this be useful,
> I wonder?
I would say that would only be useful to satisfy curiosity.
Luckily or unluckily I have not had real life use of that
multiple tree comparison feature that is supported by "git
diff" (multiple blob comparison is also available, which is
mildly useful).
>> +our $option = $cgi->param('option');
>> +if (defined $option) {
>> + if (not grep(/^$option$/, keys %options)) {
>> + die_error(undef, "Invalid option parameter");
>> + }
"!exists $options{$option}" ?
> I'd rather make it possible to pass multiple additional options, for
> example both '--remove-empty' (to speed up) and '--no-merges' for the
> history view. So I'd use
>
> +our @options = $cgi->param('option');
>
> instead.
Good point.
> I'm also not sure if invalid option parameter for action should return
> error, or be simply ignored.
I'm mildly against "simply ignoring".
> By the way, gitweb uses shortened names for paramaters. Perhaps 'opt'
> or 'op' instead of 'options' here and in href subroutine (below)?
Or even 'o' ;-).
^ permalink raw reply
* Re: [PATCH 2/4] Move the --decorate option from builtin-log.c to revision.c.
From: Junio C Hamano @ 2007-07-12 18:45 UTC (permalink / raw)
To: Johannes Schindelin; +Cc: git
In-Reply-To: <Pine.LNX.4.64.0707110229120.4047@racer.site>
Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> ---decorate::
> - Print out the ref names of any commits that are shown.
> -
> ...
> +
> +--decorate::
> + When a commit is shown, and it matches a ref, print that ref name
> + in brackets after the commit name.
The character-pair ( and ) are usually called parentheses, not
brackets.
Other than that, these two code movements and refactorings feel
a sane thing to do.
^ permalink raw reply
* Re: git-log --follow?
From: Junio C Hamano @ 2007-07-12 18:45 UTC (permalink / raw)
To: Linus Torvalds; +Cc: git
In-Reply-To: <alpine.LFD.0.999.0707121026080.20061@woody.linux-foundation.org>
Linus Torvalds <torvalds@linux-foundation.org> writes:
> On Thu, 12 Jul 2007, Junio C Hamano wrote:
>>
>> I think this is just a testament that "following renames" is not
>> as useful in a real project as people seem to believe, not a
>> real complaint.
>
> Yeah. That said, what you wanted would have actually worked with my
> original strange patch to "git blame", and in particular that also would
> allow you to get a "log" for certain lines in the file.
Yeah, I just tried the blame from 'pu' (I have been carrying
that original patch from you there). The output is not very
intuitive in that it talks about each commit but it is not
apparent _why_ the command talks about that commit. Maybe
adding "there are the lines in the final image of the blob you
are blaming that came from this commit" to the output would make
the output easier to read.
^ permalink raw reply
* Re: [PATCH 3/4] --decorate now decorates ancestors, too
From: Junio C Hamano @ 2007-07-12 18:45 UTC (permalink / raw)
To: Theodore Tso; +Cc: Johannes Schindelin, git
In-Reply-To: <20070711022714.GI27033@thunk.org>
Theodore Tso <tytso@mit.edu> writes:
> On Wed, Jul 11, 2007 at 02:29:49AM +0100, Johannes Schindelin wrote:
>>
>> The option --decorate changed default behavior: Earlier, it decorated
>> commits pointed to by any ref. The new behavior is this: decorate the
>> with the given refs and its ancestors, i.e.
>>
>> git log --decorate next master
>>
>> will show "next", "next^", "next~2", ..., "master", "master^", ...
>> in parenthesis after the commit name.
>
> I'm wondering how useful the default is. The arguments get used for
> two things; both for git-log to decide what revisions to display, and
> which refs to decorate, right? I'm not sure that overloading is such
> a great idea.
>
> Also, I note that "git log --decorate" does nothing at all. Maybe it
> would be better to keep the default to be "any-ref" instead of "given"?
I think defaulting to "given" is a regression. It could be
argued that "tag-ref" or "tag" might be a better default
(judging from my experience with "name-rev"), but keeping
"any-ref" would probably be the safest.
But in general I do not see ("I haven't realized" might turn out
to be a better expression) much value in this series yet except
for the initial clean-up patches, while I think this option
would be quite expensive in terms of memory footprints on
projects with nontrivial size of history. I dunno.
^ permalink raw reply
* Re: [PATCH] Documentation for git-log --follow
From: Linus Torvalds @ 2007-07-12 18:48 UTC (permalink / raw)
To: Steven Walter; +Cc: git
In-Reply-To: <20070712145230.GA21590@dervierte>
On Thu, 12 Jul 2007, Steven Walter wrote:
>
> It would appear that Linus didn't add write and docs for this feature
> when he wrote it.
I am shocked. *Shocked* I say.
Oh, actually, no I'm not. Par for the course for me. Sorry.
It might be worth documenting that unline all the other log commands,
--follow requires exactly one pathname. Maybe it's obvious.
Linus
^ permalink raw reply
* Re: git-log --follow?
From: Linus Torvalds @ 2007-07-12 19:01 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <7vsl7ttq97.fsf@assigned-by-dhcp.cox.net>
On Thu, 12 Jul 2007, Junio C Hamano wrote:
>
> Yeah, I just tried the blame from 'pu' (I have been carrying
> that original patch from you there). The output is not very
> intuitive in that it talks about each commit but it is not
> apparent _why_ the command talks about that commit. Maybe
> adding "there are the lines in the final image of the blob you
> are blaming that came from this commit" to the output would make
> the output easier to read.
That doesn't necessarily work, at least not incrementally. The same commit
can show up multiple times for *different* 'blame_entry' things, and I
don't think we want to show such a commit multiple times, and I also don't
think we know what all the blame entries are going to be until the end.
So right now I use that METAINFO_SHOWN flag to just show the commit once.
That makes the log a *lot* more readable.
In my original patch (the one you apparently have in 'pu'), I did that
differently (and not very well), but maybe that approach lends itself
better to showing some kind of patch.
I think my newer version is likely better.
Linus
^ permalink raw reply
* git .pack expansion question
From: Ryan Moszynski @ 2007-07-12 19:03 UTC (permalink / raw)
To: git
hopefully this is the right list for this:
am i understanding this right?
if i run the command:
git-repack
this creates an .idx and a .pack file that together hold all the files
and other metadata for my git directory. What i can't figure out
though, is how to send just these two files to another machine over
the network, and then using only these two files, have git create a
replica of the original git directory that the .pack file was made
from. I've tried
mkdir git-test
cd git-test
cp pack-92fadfab20e56acbbf28ed45851d61dc0d35c6ab.idx && .pack ./
git-init-db
git add .
git-unpack-objects < pack-92fadfab20e56acbbf28ed45851d61dc0d35c6ab.pack
now, the objects that were created from my original files are in the
objects directory, but I need them expanded so my git directory is a
copy of the one i originally git-repack'ed. Is there an easy way to
do this? This is for back up and archival purposes.
here are three ways i know to do this already, none of which I like very much.
cloning a repository over a network,
drawback: git has to be installed on both machines
or
scp'ing my whole git directory over the network,
drawback: too many little files, scp is too slow
or
tar'ing up my git directory so i can send one file for everything,
drawback: since git already has a compression/expansion tool built in,
i shouldn't have to use another to be able to do what i need.
any comments appreciated,
ryan
^ permalink raw reply
* [PATCH 0/6] Add git-rewrite-commits v2
From: skimo @ 2007-07-12 19:05 UTC (permalink / raw)
To: git, Junio C Hamano; +Cc: Johannes Schindelin
From: Sven Verdoolaege <skimo@kotnet.org>
[PATCH 1/6] revision: allow selection of commits that do not match a pattern
[PATCH 2/6] export get_short_sha1
[PATCH 3/6] Define ishex(x) in git-compat-util.h
[PATCH 4/6] refs.c: lock cached_refs during for_each_ref
[PATCH 5/6] revision: mark commits that didn't match a pattern for later use
[PATCH 6/6] Add git-rewrite-commits
The first is fairly independent, but it is used in the tests
of the last patch and may be replaced by Johannes' extended patch.
The next three should be fairly uncontroversial.
The fifth may be considered a waste of a precious bit.
If so, any suggestions for other ways of passing on this information
are welcomed.
The sixth contains the actual git-rewrite-commits builtin.
My main motivation was that cg-admin-rewritehist doesn't
change the SHA1's in commit message and I don't like shell
programming.
The main difference with the previous series is that
the options are now called --index-filter and --commit-filter
instead of --index-map and --commit-map.
The commit filter should now produce a list of commit SHA1
instead of the modified raw commit.
The refs are now rewritten at the very end, which should
be safer if anything goes wrong along the way and obviates
the need for the call to add_ref_decoration of the previous
series.
Most of the changes were requested by Johannes Schindelin.
I'm sure he'll let me know if I forgot anything.
In a follow-up patch (series), he will add more helper functions
for the filters.
skimo
^ permalink raw reply
* [PATCH 6/6] Add git-rewrite-commits
From: skimo @ 2007-07-12 19:06 UTC (permalink / raw)
To: git, Junio C Hamano; +Cc: Johannes Schindelin
In-Reply-To: <11842671631744-git-send-email-skimo@liacs.nl>
From: Sven Verdoolaege <skimo@kotnet.org>
This builtin is similar to git-filter-branch (and cg-admin-rewritehist).
The main difference is that git-rewrite-commits will automatically
rewrite any SHA1 in a commit message to the rewritten SHA1 as
well as any reference (in .git/refs/) that points to a rewritten commit.
It's also a lot faster than git-filter-branch if no external command
is called. For example, running either to eliminating a commit specified
as a graft results in the following timings, both being performed
on a freshly cloned copy of a small repo:
bash-3.00$ time git-filter-branch test
Rewrite 274fe3dfb8e8c7d0a6ce05138bdb650de7b459ea (425/425)
Rewritten history saved to the test branch
real 0m30.845s
user 0m13.400s
sys 0m19.640s
bash-3.00$ time git-rewrite-commits
real 0m0.223s
user 0m0.080s
sys 0m0.140s
The command line is more reminiscent of git-log.
For example you can say
git-rewrite-commits --all
to incorporate grafts in all branches, or
git rewrite-commits --author='!Darl McBribe' --all
to remove all commits by Darl McBribe.
Signed-off-by: Sven Verdoolaege <skimo@kotnet.org>
Reviewed-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
.gitignore | 1 +
Documentation/cmd-list.perl | 1 +
Documentation/git-rewrite-commits.txt | 156 +++++++
Makefile | 1 +
builtin-rewrite-commits.c | 712 +++++++++++++++++++++++++++++++++
builtin.h | 1 +
git.c | 1 +
t/t7005-rewrite-commits.sh | 109 +++++
8 files changed, 982 insertions(+), 0 deletions(-)
create mode 100644 Documentation/git-rewrite-commits.txt
create mode 100644 builtin-rewrite-commits.c
create mode 100755 t/t7005-rewrite-commits.sh
diff --git a/.gitignore b/.gitignore
index 20ee642..bcd95a9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -109,6 +109,7 @@ git-reset
git-rev-list
git-rev-parse
git-revert
+git-rewrite-commits
git-rm
git-runstatus
git-send-email
diff --git a/Documentation/cmd-list.perl b/Documentation/cmd-list.perl
index 2143995..63911b7 100755
--- a/Documentation/cmd-list.perl
+++ b/Documentation/cmd-list.perl
@@ -166,6 +166,7 @@ git-reset mainporcelain
git-revert mainporcelain
git-rev-list plumbinginterrogators
git-rev-parse ancillaryinterrogators
+git-rewrite-commits mainporcelain
git-rm mainporcelain
git-runstatus ancillaryinterrogators
git-send-email foreignscminterface
diff --git a/Documentation/git-rewrite-commits.txt b/Documentation/git-rewrite-commits.txt
new file mode 100644
index 0000000..fb4e220
--- /dev/null
+++ b/Documentation/git-rewrite-commits.txt
@@ -0,0 +1,156 @@
+git-rewrite-commits(1)
+======================
+
+NAME
+----
+git-rewrite-commits - Rewrite commits
+
+SYNOPSIS
+--------
+'git-rewrite-commits' [--index-filter <command>] [--commit-filter <command>]
+ [<rev-list options>...]
+
+DESCRIPTION
+-----------
+Lets you rewrite the commits selected by the gitlink:git-rev-list[1]
+options, optionally applying custom filters on each of them.
+These filters either modify the tree associated to a commit
+(through manipulations of the index) or the (raw) commit itself.
+Any commit within the specified range that is filtered out
+through a pattern is removed from its children (assuming they
+are in range) and replaced by the closest ancestors that
+are either not being rewritten or not filtered out.
+
+Any branch pointing to any of the rewritten commits is replaced
+by a pointer to the new commit. The original pointers are saved
+in the refs/original hierarchy. Any abbreviated SHA1 in the commit
+message of any of the rewritten commits that points to (another)
+rewritten commit is replaced by the SHA1 of the corresponding new commit.
+
+Besides the actions specified by the filters, the new commits
+will also reflect any grafts that may apply to any of the selected commits.
+
+*WARNING*! The rewritten history will have different object names for all
+the objects and will not converge with the original branch. You will not
+be able to easily push and distribute the rewritten branch on top of the
+original branch. Please do not use this command if you do not know the
+full implications, and avoid using it anyway, if a simple single commit
+would suffice to fix your problem.
+
+Filters
+~~~~~~~
+
+The filters are applied in the order as listed below. The <command>
+argument is run as "sh -c '<command'>", with the $GIT_COMMIT
+environment variable set to the commit that is being rewritten.
+If any call to a filter fails, then git-rewrite-commits will abort.
+
+
+OPTIONS
+-------
+
+--index-filter <command>::
+ This filter should only modify the index specified by 'GIT_INDEX_FILE'.
+ Prior to running the filter, this temporary index is populated
+ with the tree of the commit that is being rewritten.
+ This tree will be replaced according to the new state of this
+ temporary index after the filter finishes.
+
+--commit-filter <command>::
+ This filter receives the (raw) commit on stdin and should produce
+ zero or more SHA1s of commits that should replace the given commit
+ on stdout.
+ In other words, the filter "git-hash-object -w -t commit --stdin"
+ leaves the current commit intact. This command is also available
+ as the shell function "commit".
+ In the input commit, the tree has already been modified by the
+ index filter (if any) and has all references to older commits
+ (including the parents) changed to the possibly rewritten commits.
+
+--write-sha1-mapping
+ Write mapping of old SHA1s to new SHA1s for use in filters.
+
+<rev-list-options>::
+ Selects the commits to be rewritten, defaulting to the history
+ that lead to HEAD. If commits are filtered using a (negative)
+ pattern then all the commits filtered out will be removed
+ from the history of the selected commits.
+
+
+Examples
+--------
+
+Suppose you want to remove a file (containing confidential information
+or copyright violation) from all commits:
+
+----------------------------------------------------------------------------
+git rewrite-commits --index-filter 'git update-index --remove filename || :'
+----------------------------------------------------------------------------
+
+Now, you will get the rewritten history saved in your current branch
+(the old branch is saved in refs/original).
+
+To set a commit "$graft-id" (which typically is at the tip of another
+history) to be the parent of the current initial commit "$commit-id", in
+order to paste the other history behind the current history:
+
+-----------------------------------------------
+echo "$commit-id $graft-id" >> .git/info/grafts
+git rewrite-commits
+-----------------------------------------------
+
+To remove commits authored by "Darl McBribe" from the history:
+
+--------------------------------------------
+git rewrite-commits --author='!Darl McBribe'
+--------------------------------------------
+
+Note that the changes introduced by the commits, and not reverted by
+subsequent commits, will still be in the rewritten branch. If you want
+to throw out _changes_ together with the commits, you should use the
+interactive mode of gitlink:git-rebase[1].
+
+Consider this history:
+
+------------------
+ D--E--F--G--H
+ / /
+A--B-----C
+------------------
+
+To rewrite only commits D,E,F,G,H, but leave A, B and C alone, use:
+
+--------------------------------
+git rewrite-commits ... C..H
+--------------------------------
+
+To rewrite commits E,F,G,H, use one of these:
+
+----------------------------------------
+git rewrite-commits ... C..H --not D
+git rewrite-commits ... D..H --not C
+----------------------------------------
+
+To move the whole tree into a subdirectory, or remove it from there:
+
+---------------------------------------------------------------
+git rewrite-commits --index-filter \
+ 'git ls-files -s | sed "s-\t-&newsubdir/-" |
+ GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
+ git update-index --index-info &&
+ mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE'
+---------------------------------------------------------------
+
+
+Author
+------
+Written by Sven Verdoolaege.
+Inspired by cg-admin-rewritehist by Petr "Pasky" Baudis <pasky@suse.cz>.
+
+Documentation
+--------------
+Documentation by Petr Baudis, Sven Verdoolaege and the git list.
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Makefile b/Makefile
index d7541b4..252ef8e 100644
--- a/Makefile
+++ b/Makefile
@@ -370,6 +370,7 @@ BUILTIN_OBJS = \
builtin-rev-list.o \
builtin-rev-parse.o \
builtin-revert.o \
+ builtin-rewrite-commits.o \
builtin-rm.o \
builtin-runstatus.o \
builtin-shortlog.o \
diff --git a/builtin-rewrite-commits.c b/builtin-rewrite-commits.c
new file mode 100644
index 0000000..d95a16c
--- /dev/null
+++ b/builtin-rewrite-commits.c
@@ -0,0 +1,712 @@
+#include "cache.h"
+#include "refs.h"
+#include "builtin.h"
+#include "commit.h"
+#include "diff.h"
+#include "revision.h"
+#include "list-objects.h"
+#include "run-command.h"
+#include "cache-tree.h"
+#include "grep.h"
+
+struct decoration rewrite_decoration = { "rewritten as" };
+static const char *original_prefix = "original";
+static const char *commit_filter;
+static const char *index_filter;
+static const char *absolute_git_dir;
+static const char *rewrite_dir;
+static char commit_env[] = "GIT_COMMIT=0000000000000000000000000000000000000000";
+static int write_sha1_mapping;
+
+struct rewrite_decoration {
+ struct rewrite_decoration *next;
+ unsigned char sha1[20];
+};
+
+static void add_rewrite_decoration(struct object *obj, unsigned char *sha1)
+{
+ struct rewrite_decoration *deco = xmalloc(sizeof(struct rewrite_decoration));
+ hashcpy(deco->sha1, sha1);
+ deco->next = add_decoration(&rewrite_decoration, obj, deco);
+}
+
+static int get_rewritten_sha1(unsigned char *sha1)
+{
+ struct rewrite_decoration *deco;
+ struct object *obj = lookup_object(sha1);
+
+ if (!obj)
+ return 1;
+
+ deco = lookup_decoration(&rewrite_decoration, obj);
+ if (!deco)
+ return 1;
+
+ hashcpy(sha1, deco->sha1);
+ return 0;
+}
+
+static char *add_parents(char *dest, struct commit_list *parents)
+{
+ unsigned char sha1[20];
+ struct commit_list *list;
+
+ for (list = parents; list; list = list->next) {
+ hashcpy(sha1, list->item->object.sha1);
+ get_rewritten_sha1(sha1);
+ memcpy(dest, "parent ", 7);
+ dest += 7;
+ memcpy(dest, sha1_to_hex(sha1), 40);
+ dest += 40;
+ *dest++ = '\n';
+ }
+ return dest;
+}
+
+static int skip_one_line(char **buf_p, unsigned long *len_p)
+{
+ int linelen;
+ char *end = memchr(*buf_p, '\n', *len_p);
+
+ if (!end)
+ linelen = *len_p;
+ if (end)
+ linelen = end - *buf_p + 1;
+ *buf_p += linelen;
+ *len_p -= linelen;
+
+ return linelen;
+}
+
+static char *filter_index(char *orig_hex, struct commit *commit)
+{
+ int argc;
+ const char *argv[10];
+ static char index_env[16+PATH_MAX];
+ char *tmp_index = mkpath("%s/rewrite_index", absolute_git_dir);
+ const char *env[] = { index_env, commit_env, NULL };
+ char *hex;
+
+ memcpy(index_env, "GIT_INDEX_FILE=", 15);
+ strcpy(index_env+15, tmp_index);
+ tmp_index = index_env+15;
+
+ memcpy(commit_env+sizeof(commit_env)-41,
+ sha1_to_hex(commit->object.sha1), 40);
+
+ /* First write out tree to temporary index */
+ argc = 0;
+ argv[argc++] = "read-tree";
+ argv[argc++] = orig_hex;
+ argv[argc] = NULL;
+ if (run_command_v_opt_cd_env(argv, RUN_GIT_CMD, NULL, env))
+ die("Cannot write index '%s' for filter", tmp_index);
+
+ /* Then filter the index */
+ argc = 0;
+ argv[argc++] = "sh";
+ argv[argc++] = "-c";
+ argv[argc++] = index_filter;
+ argv[argc] = NULL;
+ if (run_command_v_opt_cd_env(argv, 0, rewrite_dir, env))
+ die("Index filter '%s' failed", index_filter);
+
+ /* Finally read it back in */
+ if (read_cache_from(tmp_index) < 0)
+ die("Error reading index '%s'", tmp_index);
+ active_cache_tree = cache_tree();
+ if (cache_tree_update(active_cache_tree, active_cache, active_nr,
+ 0, 0) < 0)
+ die("Error building trees");
+ hex = sha1_to_hex(active_cache_tree->sha1);
+ discard_cache();
+
+ unlink(tmp_index);
+
+ return hex;
+}
+
+static char *rewrite_header(char *dest, unsigned long *len_p, char **buf_p,
+ struct commit *commit)
+{
+ struct commit_list *parents = commit->parents;
+ int linelen;
+
+ do {
+ char *line = *buf_p;
+ linelen = skip_one_line(buf_p, len_p);
+
+ if (!linelen)
+ return dest;
+
+ if (index_filter && !memcmp(line, "tree ", 5)) {
+ if (linelen != 46)
+ die("bad tree line in commit");
+ memcpy(dest, "tree ", 5);
+ line[45] = '\0';
+ memcpy(dest+5, filter_index(line+5, commit), 40);
+ line[45] = '\n';
+ dest[45] = '\n';
+ dest += 46;
+ continue;
+ }
+
+ /* drop old parents */
+ if (!memcmp(line, "parent ", 7)) {
+ if (linelen != 48)
+ die("bad parent line in commit");
+ continue;
+ }
+
+ /* insert new parents before author */
+ if (!memcmp(line, "author ", 7))
+ dest = add_parents(dest, parents);
+
+ memcpy(dest, line, linelen);
+ dest += linelen;
+ } while (linelen > 1);
+ return dest;
+}
+
+static size_t hex_len(const char *s, size_t n)
+{
+ size_t offset;
+
+ for (offset = 0; offset < n; ++offset)
+ if (!ishex(s[offset]))
+ break;
+ return offset;
+}
+
+static size_t non_hex_len(const char *s, size_t n)
+{
+ size_t offset;
+
+ for (offset = 0; offset < n; ++offset)
+ if (ishex(s[offset]))
+ break;
+ return offset;
+}
+
+/* Replace any (short) sha1 of a rewritten commit by the new (short) sha1 */
+static char *rewrite_body(char *dest, unsigned long len, char *buf)
+{
+ unsigned char sha1[20];
+
+ while (len) {
+ size_t ll = non_hex_len(buf, len);
+ memcpy(dest, buf, ll);
+ dest += ll;
+ buf += ll;
+ len -= ll;
+
+ ll = hex_len(buf, len);
+ if (ll >= 8 && ll <= 40 &&
+ !get_short_sha1(buf, ll, sha1, 1) &&
+ !get_rewritten_sha1(sha1))
+ memcpy(dest, sha1_to_hex(sha1), ll);
+ else
+ memcpy(dest, buf, ll);
+ dest += ll;
+ buf += ll;
+ len -= ll;
+ }
+ return dest;
+}
+
+static void write_ref_sha1_or_die(const char *ref, const unsigned char *old_sha1,
+ const unsigned char *new_sha1,
+ const char *logmsg, int flags)
+{
+ struct ref_lock *lock;
+
+ lock = lock_any_ref_for_update(ref, old_sha1, flags);
+ if (!lock)
+ die("%s: cannot lock the ref", ref);
+ if (write_ref_sha1(lock, new_sha1, logmsg) < 0)
+ die("%s: cannot update the ref", ref);
+}
+
+static int is_ref_to_be_rewritten(const char *ref)
+{
+ unsigned char sha1[20];
+ int flag;
+
+ if (prefixcmp(ref, "refs/"))
+ return 0;
+ if (!prefixcmp(ref, "refs/remotes/"))
+ return 0;
+ if (!prefixcmp(ref+5, original_prefix))
+ return 0;
+
+ resolve_ref(ref, sha1, 0, &flag);
+ if (flag & REF_ISSYMREF)
+ return 0;
+
+ return 1;
+}
+
+static int is_pruned(struct commit *commit, int path_pruning)
+{
+ if (commit->object.flags & PRUNED)
+ return 1;
+ if (path_pruning &&
+ !(commit->object.flags & (TREECHANGE | UNINTERESTING)))
+ return 1;
+ return 0;
+}
+
+static void rewrite_sha1(struct object *obj, unsigned char *new_sha1)
+{
+ if (!hashcmp(obj->sha1, new_sha1))
+ return;
+
+ add_rewrite_decoration(obj, new_sha1);
+}
+
+/*
+ * Replace any parent that has been removed by its parents
+ * and return the number of new parents.
+ * We directly modify the parent list, so any libification
+ * should probably adapt this function.
+ */
+static int rewrite_parents(struct commit *commit, int path_pruning)
+{
+ int n;
+ struct commit_list *list, *parents, **prev;
+ unsigned char sha1[20];
+
+ for (n = 0, prev = &commit->parents; *prev; ++n) {
+ list = *prev;
+
+ rewrite_parents(list->item, path_pruning);
+ if (!is_pruned(list->item, path_pruning)) {
+ prev = &list->next;
+ continue;
+ }
+
+ hashcpy(sha1, list->item->object.sha1);
+ get_rewritten_sha1(sha1);
+ if (!is_null_sha1(sha1)) {
+ hashclr(sha1);
+ rewrite_sha1(&list->item->object, sha1);
+ }
+
+ parents = list->item->parents;
+ if (!parents) {
+ *prev = list->next;
+ free(list);
+ --n;
+ continue;
+ }
+ list->item = parents->item;
+ prev = &list->next;
+ list = list->next;
+ while ((parents = parents->next)) {
+ commit_list_insert(parents->item, prev);
+ prev = &(*prev)->next;
+ ++n;
+ }
+ *prev = list;
+ }
+
+ return n;
+}
+
+static int rewrite_ref(const char *refname, const unsigned char *sha1,
+ int flags, void *cb_data)
+{
+ int prefix_len;
+ int len;
+ char buffer[256], *p;
+ struct object *obj = parse_object(sha1);
+ unsigned char new_sha1[20];
+ struct commit *commit;
+ int pruned;
+
+ if (!obj)
+ return 0;
+ if (obj->type == OBJ_TAG)
+ return 0;
+ if (!is_ref_to_be_rewritten(refname))
+ return 0;
+
+ commit = lookup_commit_reference(sha1);
+ pruned = is_pruned(commit, !!cb_data);
+
+ hashcpy(new_sha1, sha1);
+ if (!pruned && get_rewritten_sha1(new_sha1))
+ return 0;
+
+ prefix_len = strlen(original_prefix);
+ len = strlen(refname);
+
+ if (len + prefix_len + 1 + 1 > sizeof(buffer))
+ die("rewrite ref of '%s' too long", refname);
+ p = buffer;
+ memcpy(p, "refs/", 5);
+ p += 5;
+ memcpy(p, original_prefix, prefix_len);
+ p += prefix_len;
+ memcpy(p, refname+4, len-4 + 1);
+
+ if (safe_create_leading_directories(git_path(buffer)))
+ die("Unable to create leading directories of '%s'", buffer);
+ write_ref_sha1_or_die(buffer, NULL, obj->sha1,
+ "copied during rewrite", 0);
+
+ if (pruned) {
+ int i = 1;
+ delete_ref(refname, obj->sha1);
+ mkdir(git_path(refname), 0777);
+ struct commit_list *parent;
+ rewrite_parents(commit, !!cb_data);
+ for (parent = commit->parents; parent; parent = parent->next) {
+ hashcpy(new_sha1, parent->item->object.sha1);
+ get_rewritten_sha1(new_sha1);
+ snprintf(buffer, sizeof(buffer), "%s/%d", refname, i);
+ write_ref_sha1_or_die(buffer, NULL, new_sha1,
+ "ancestor of pruned", 0);
+ ++i;
+ }
+ return 0;
+ }
+ else
+ write_ref_sha1_or_die(refname, obj->sha1, new_sha1,
+ "rewritten", 0);
+
+ return 0;
+}
+
+static void add_sha1_map(const char *old_sha1, const char *new_sha1)
+{
+ int fd;
+
+ if (!write_sha1_mapping)
+ return;
+ if (new_sha1 && !hashcmp(old_sha1, new_sha1))
+ return;
+
+ fd = open(mkpath("%s/map/%s", rewrite_dir, sha1_to_hex(old_sha1)),
+ O_CREAT | O_WRONLY | O_APPEND, 0777);
+ if (fd < 0)
+ die("unable to open map file '%s/map/%s'", rewrite_dir,
+ sha1_to_hex(old_sha1));
+ if (new_sha1)
+ write_or_die(fd, sha1_to_hex(new_sha1), 41);
+ close(fd);
+}
+
+static void filter_and_write_commit(char *commit_body, size_t len,
+ struct commit *commit, unsigned char *sha1)
+{
+ char commit_path[PATH_MAX];
+ struct child_process cmd;
+ int argc;
+ const char *argv[10];
+ int fd;
+ const char *env[] = { commit_env, NULL };
+ char hex[41];
+ struct commit_list *list = NULL, **end = &list;
+
+ memcpy(commit_env+sizeof(commit_env)-41,
+ sha1_to_hex(commit->object.sha1), 40);
+
+ fd = git_mkstemp(commit_path, sizeof(commit_path), ".commit_XXXXXX");;
+ write_or_die(fd, commit_body, len);
+
+ argc = 0;
+ argv[argc++] = "sh";
+ argv[argc++] = "-c";
+ argv[argc++] = commit_filter;
+ argv[argc] = NULL;
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.in = open(commit_path, O_RDONLY);
+ if (cmd.in < 0)
+ die("Unable to read commit from file '%s'", commit_path);
+ unlink(commit_path);
+ cmd.out = -1;
+ cmd.argv = argv;
+ cmd.dir = rewrite_dir;
+ cmd.env = env;
+ if (start_command(&cmd))
+ die("Commit filter '%s' failed to start", commit_filter);
+ while (xread(cmd.out, hex, 41) == 41) {
+ struct commit *new_commit;
+ hex[40] = '\0';
+ if (get_sha1(hex, sha1))
+ die("Unexpected output from commit filter: '%s'",
+ hex);
+ if (!(new_commit = lookup_commit_reference(sha1)))
+ die("Invalid SHA1 in output from commit filter: '%s'",
+ hex);
+ commit_list_insert(new_commit, end);
+ add_sha1_map(commit->object.sha1, new_commit->object.sha1);
+ end = &(*end)->next;
+ }
+ if (list && !list->next)
+ free_commit_list(list);
+ else {
+ hashclr(sha1);
+ commit->object.flags |= PRUNED;
+ /*
+ * If the filter returns two or more commits,
+ * we consider the original commit to have been
+ * removed and put the list in the old commit's
+ * parent list so that all the old commit's children
+ * will copy them.
+ */
+ if (list) {
+ free_commit_list(commit->parents);
+ commit->parents = list;
+ } else
+ add_sha1_map(commit->object.sha1, NULL);
+ }
+ if (finish_command(&cmd))
+ die("Commit filter '%s' failed", commit_filter);
+ close(cmd.in);
+}
+
+static void rewrite_commit(struct commit *commit, int path_pruning)
+{
+ char *buf, *p;
+ int n;
+ char *orig_buf = commit->buffer;
+ unsigned long orig_len = strlen(orig_buf);
+ unsigned char sha1[20];
+
+ n = rewrite_parents(commit, path_pruning);
+
+ /* Make enough remove for n (possibly extra) parents */
+ p = buf = xmalloc(orig_len + n*48);
+ p = rewrite_header(p, &orig_len, &orig_buf, commit);
+ p = rewrite_body(p, orig_len, orig_buf);
+ if (!commit_filter) {
+ if (write_sha1_file(buf, p-buf, commit_type, sha1))
+ die("Unable to write new commit");
+ add_sha1_map(commit->object.sha1, sha1);
+ } else
+ filter_and_write_commit(buf, p-buf, commit, sha1);
+ free(buf);
+
+ rewrite_sha1(&commit->object, sha1);
+}
+
+static void move_head_forward(char *ref, unsigned char *old_sha1, int detached,
+ int path_limiting)
+{
+ unsigned char new_sha1[20];
+ int rewritten = 0;
+ struct commit *commit;
+
+ commit = lookup_commit_reference(old_sha1);
+ if (is_pruned(commit, path_limiting)) {
+ if (commit->parents) {
+ hashcpy(new_sha1, commit->parents->item->object.sha1);
+ get_rewritten_sha1(new_sha1);
+ rewritten = 1;
+ } else
+ return;
+ } else {
+ hashcpy(new_sha1, old_sha1);
+ rewritten = !get_rewritten_sha1(new_sha1);
+ }
+ if (rewritten && !is_bare_repository()) {
+ int argc;
+ const char *argv[10];
+
+ argc = 0;
+ argv[argc++] = "read-tree";
+ argv[argc++] = "-m";
+ argv[argc++] = "-u";
+ argv[argc++] = sha1_to_hex(old_sha1);
+ argv[argc++] = sha1_to_hex(new_sha1);
+ argv[argc] = NULL;
+ if (run_command_v_opt(argv, RUN_GIT_CMD))
+ die("Cannot move HEAD forward");
+ }
+ if (!detached) {
+ unsigned char sha1[20];
+
+ if (resolve_ref(ref, sha1, 1, NULL))
+ create_symref("HEAD", ref, "reattached after rewrite");
+ else {
+ char buffer[256];
+ snprintf(buffer, sizeof(buffer), "%s/1", ref);
+ if (resolve_ref(buffer, sha1, 1, NULL))
+ create_symref("HEAD", buffer,
+ "attached to ancestor of pruned commit");
+ }
+ }
+ else if (rewritten)
+ write_ref_sha1_or_die("HEAD", NULL, new_sha1,
+ "rewritten", REF_NODEREF);
+}
+
+static char aux_functions[] =
+"export GIT_REWRITE_DIR=`pwd`\n"
+"commit()\n"
+"{\n"
+" git-hash-object -w -t commit --stdin\n"
+"}\n"
+"map()\n"
+"{\n"
+" # if it was not rewritten, take the original\n"
+" if test -r \"$GIT_REWRITE_DIR/map/$1\"\n"
+" then\n"
+" cat \"$GIT_REWRITE_DIR/map/$1\"\n"
+" else\n"
+" echo \"$1\"\n"
+" fi\n"
+"}\n"
+"cd t\n";
+
+static char filter_prefix[] = ". aux;";
+
+static char *create_filter(const char *command)
+{
+ int prefix_len = sizeof(filter_prefix)-1;
+ int command_len = strlen(command);
+ char *filter = xmalloc(prefix_len + command_len + 1);
+
+ memcpy(filter, filter_prefix, prefix_len);
+ memcpy(filter+prefix_len, command, command_len+1);
+ return filter;
+}
+
+static const char *create_absolute_path(const char *path)
+{
+ int path_len;
+ static char cwd[PATH_MAX];
+ static int cwd_len = -1;
+ char *absolute;
+
+ if (path[0] == '/')
+ return path;
+
+ if (cwd_len == -1) {
+ if (!getcwd(cwd, sizeof(cwd)))
+ die("unable to get current working directory");
+ cwd_len = strlen(cwd);
+ }
+
+ path_len = strlen(path);
+ absolute = xmalloc(cwd_len+path_len+2);
+ memcpy(absolute, cwd, cwd_len);
+ absolute[cwd_len] = '/';
+ memcpy(absolute+cwd_len+1, path, path_len+1);
+ return absolute;
+}
+
+static int rm_rf(const char *dir)
+{
+ int argc;
+ const char *argv[10];
+
+ argc = 0;
+ argv[argc++] = "rm";
+ argv[argc++] = "-rf";
+ argv[argc++] = dir;
+ argv[argc] = NULL;
+ return run_command_v_opt(argv, 0);
+}
+
+static void cleanup_temp_dir()
+{
+ if (rm_rf(rewrite_dir))
+ die("Unable to clean up rewrite directory '%s'", rewrite_dir);
+}
+
+static void setup_temp_dir()
+{
+ int aux;
+
+ absolute_git_dir = create_absolute_path(get_git_dir());
+ setenv(GIT_DIR_ENVIRONMENT, absolute_git_dir, 1);
+
+ if (!rewrite_dir) {
+ rewrite_dir = xstrdup(git_path("rewrite"));
+ }
+ if (mkdir(rewrite_dir, 0777)) {
+ if (errno == EEXIST)
+ die("rewrite directory '%s' already exists",
+ rewrite_dir);
+ die("unable to create rewrite directory '%s'", rewrite_dir);
+ }
+ atexit(cleanup_temp_dir);
+
+ if (mkdir(mkpath("%s/map", rewrite_dir), 0777))
+ die("unable to create map directory '%s/map'", rewrite_dir);
+ if (mkdir(mkpath("%s/t", rewrite_dir), 0777))
+ die("unable to create temp directory '%s/t'", rewrite_dir);
+ aux = open(mkpath("%s/aux", rewrite_dir), O_CREAT | O_WRONLY, 0777);
+ if (aux < 0)
+ die("unable to create aux file '%s/aux'", rewrite_dir);
+ write_or_die(aux, aux_functions, sizeof(aux_functions)-1);
+ close(aux);
+}
+
+int cmd_rewrite_commits(int argc, const char **argv, const char *prefix)
+{
+ struct rev_info rev;
+ struct commit *commit;
+ unsigned char HEAD_sha1[20];
+ char *HEAD_ref;
+ int flag;
+ int i, j;
+
+ init_revisions(&rev, prefix);
+
+ for (i = 1, j = 1; i < argc; ++i) {
+ if (!strcmp(argv[i], "--index-filter")) {
+ if (++i == argc)
+ die("Argument required for --index-filter");
+ index_filter = argv[i];
+ } else if (!strcmp(argv[i], "--commit-filter")) {
+ if (++i == argc)
+ die("Argument required for --commit-filter");
+ commit_filter = argv[i];
+ } else if (!strcmp(argv[i], "--write-sha1-mapping")) {
+ write_sha1_mapping = 1;
+ } else
+ argv[j++] = argv[i];
+ }
+ argc = j;
+ argc = setup_revisions(argc, argv, &rev, "HEAD");
+ rev.ignore_merges = 0;
+ rev.topo_order = 1;
+ rev.reverse = 1;
+ rev.parents = 1;
+
+ if (index_filter || commit_filter)
+ setup_temp_dir();
+ else
+ /* They'll never know. BWUHAHA */
+ write_sha1_mapping = 0;
+
+ if (index_filter)
+ index_filter = create_filter(index_filter);
+ if (commit_filter)
+ commit_filter = create_filter(commit_filter);
+
+ prepare_revision_walk(&rev);
+ while ((commit = get_revision(&rev)) != NULL) {
+ rewrite_commit(commit, !!rev.prune_fn);
+ }
+
+ HEAD_ref = xstrdup(resolve_ref("HEAD", HEAD_sha1, 1, &flag));
+ if (flag & REF_ISSYMREF) {
+ /* Detach HEAD at its current position */
+ write_ref_sha1_or_die("HEAD", NULL, HEAD_sha1,
+ "detached for rewrite", REF_NODEREF);
+ }
+
+ rm_rf(git_path("refs/%s", original_prefix));
+ for_each_ref(rewrite_ref, rev.prune_fn);
+
+ move_head_forward(HEAD_ref, HEAD_sha1, !(flag & REF_ISSYMREF),
+ !!rev.prune_fn);
+ free(HEAD_ref);
+
+ return 0;
+}
diff --git a/builtin.h b/builtin.h
index 4cc228d..5a08ec8 100644
--- a/builtin.h
+++ b/builtin.h
@@ -63,6 +63,7 @@ extern int cmd_rerere(int argc, const char **argv, const char *prefix);
extern int cmd_rev_list(int argc, const char **argv, const char *prefix);
extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);
extern int cmd_revert(int argc, const char **argv, const char *prefix);
+extern int cmd_rewrite_commits(int argc, const char **argv, const char *prefix);
extern int cmd_rm(int argc, const char **argv, const char *prefix);
extern int cmd_runstatus(int argc, const char **argv, const char *prefix);
extern int cmd_shortlog(int argc, const char **argv, const char *prefix);
diff --git a/git.c b/git.c
index a647f9c..ce02b0b 100644
--- a/git.c
+++ b/git.c
@@ -356,6 +356,7 @@ static void handle_internal_command(int argc, const char **argv)
{ "rev-list", cmd_rev_list, RUN_SETUP },
{ "rev-parse", cmd_rev_parse, RUN_SETUP },
{ "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE },
+ { "rewrite-commits", cmd_rewrite_commits, RUN_SETUP },
{ "rm", cmd_rm, RUN_SETUP | NEED_WORK_TREE },
{ "runstatus", cmd_runstatus, RUN_SETUP | NEED_WORK_TREE },
{ "shortlog", cmd_shortlog, RUN_SETUP | USE_PAGER },
diff --git a/t/t7005-rewrite-commits.sh b/t/t7005-rewrite-commits.sh
new file mode 100755
index 0000000..eef4129
--- /dev/null
+++ b/t/t7005-rewrite-commits.sh
@@ -0,0 +1,109 @@
+#!/bin/sh
+
+test_description='git-rewrite-commits'
+. ./test-lib.sh
+
+make_commit () {
+ lower=$(echo $1 | tr A-Z a-z)
+ echo $lower > $lower
+ git add $lower
+ test_tick
+ git commit -m $1
+ git tag $1
+}
+
+test_expect_success 'setup' '
+ make_commit A
+ make_commit B
+ git checkout -b branch B
+ make_commit D
+ make_commit E
+ git checkout master
+ make_commit C
+ git checkout branch
+ git merge C
+ git tag F
+ make_commit G
+ make_commit H
+'
+
+orig_H=$(git rev-parse H)
+test_expect_success 'rewrite identically' '
+ git-rewrite-commits
+'
+
+test_expect_success 'result is really identical' '
+ test $orig_H = $(git rev-parse H)
+'
+
+test_expect_success 'rewrite identically using commit filter' '
+ git-rewrite-commits --commit-filter="commit"
+'
+
+test_expect_success 'result is really identical' '
+ test $orig_H = $(git rev-parse H)
+'
+
+# for lack of 'git-mv --cached d doh'
+test_expect_success 'rewrite, renaming a specific file' '
+ git-rewrite-commits --index-filter \
+ "git-ls-files -s | sed \"s-\\td\\\$-\\tdoh-\" |
+ GIT_INDEX_FILE=\$GIT_INDEX_FILE.new \
+ git-update-index --index-info &&
+ mv \$GIT_INDEX_FILE.new \$GIT_INDEX_FILE"
+'
+
+test_expect_success 'test that the file was renamed' '
+ test d = $(git-show H:doh) &&
+ ! git-show H:d --
+'
+
+orig_H=$(git rev-parse H)
+test_expect_success 'use index-filter to move into a subdirectory' '
+ git-rewrite-commits --index-filter \
+ "git ls-files -s | sed \"s-\\t-&newsubdir/-\" |
+ GIT_INDEX_FILE=\$GIT_INDEX_FILE.new \
+ git update-index --index-info &&
+ mv \$GIT_INDEX_FILE.new \$GIT_INDEX_FILE" &&
+ test -z "$(git diff $orig_H H:newsubdir)"'
+
+test_expect_success 'remove merge commit' '
+ git-rewrite-commits --grep="!Merge" &&
+ test 2 = `git-log ^G^@ G --pretty=format:%P | wc -w`'
+
+test_expect_success 'remove new merge commit using commit filter' '
+ git-rewrite-commits --commit-filter \
+ "if test \$GIT_COMMIT = $(git rev-parse G); then \
+ cat > /dev/null; \
+ else \
+ commit; \
+ fi" &&
+ test 2 = `git-log ^H^@ H --pretty=format:%P | wc -w`'
+
+test_expect_success 'remove first commit' '
+ git-rewrite-commits --grep="!A"'
+
+test_expect_success 'stops when index filter fails' '
+ ! git-rewrite-commits --index-filter false &&
+ git-checkout branch
+'
+
+test_expect_success 'author information is preserved' '
+ : > i &&
+ git add i &&
+ test_tick &&
+ GIT_AUTHOR_NAME="B V Uips" git commit -m bvuips &&
+ git-rewrite-commits --commit-filter "(cat; \
+ test \$GIT_COMMIT != $(git rev-parse master) || \
+ echo Hallo) | commit" &&
+ test 1 = $(git rev-list --author="B V Uips" HEAD | wc -l)
+'
+
+test_expect_success 'use index-filter to select a subdirectory' '
+ git-rewrite-commits --index-filter \
+ "git read-tree \$GIT_COMMIT:newsubdir" -- newsubdir &&
+ test 0 = $(git rev-list HEAD -- i | wc -l) &&
+ test 0 = $(git rev-list HEAD -- newsubdir | wc -l)
+'
+
+test_done
--
1.5.3.rc0.100.ge60b4
^ permalink raw reply related
* [PATCH 2/6] export get_short_sha1
From: skimo @ 2007-07-12 19:05 UTC (permalink / raw)
To: git, Junio C Hamano; +Cc: Johannes Schindelin
In-Reply-To: <11842671631744-git-send-email-skimo@liacs.nl>
From: Sven Verdoolaege <skimo@kotnet.org>
Sometimes it is useful to check whether a given string
is exactly a short SHA1.
Signed-off-by: Sven Verdoolaege <skimo@kotnet.org>
---
cache.h | 1 +
sha1_name.c | 3 +--
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/cache.h b/cache.h
index e64071e..8fda8ee 100644
--- a/cache.h
+++ b/cache.h
@@ -391,6 +391,7 @@ static inline unsigned int hexval(unsigned char c)
extern int get_sha1(const char *str, unsigned char *sha1);
extern int get_sha1_with_mode(const char *str, unsigned char *sha1, unsigned *mode);
+extern int get_short_sha1(const char *name, int len, unsigned char *sha1, int quietly);
extern int get_sha1_hex(const char *hex, unsigned char *sha1);
extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */
extern int read_ref(const char *filename, unsigned char *sha1);
diff --git a/sha1_name.c b/sha1_name.c
index 858f08c..0bed79d 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -154,8 +154,7 @@ static int find_unique_short_object(int len, char *canonical,
return 0;
}
-static int get_short_sha1(const char *name, int len, unsigned char *sha1,
- int quietly)
+int get_short_sha1(const char *name, int len, unsigned char *sha1, int quietly)
{
int i, status;
char canonical[40];
--
1.5.3.rc0.100.ge60b4
^ permalink raw reply related
* [PATCH 3/6] Define ishex(x) in git-compat-util.h
From: skimo @ 2007-07-12 19:06 UTC (permalink / raw)
To: git, Junio C Hamano; +Cc: Johannes Schindelin
In-Reply-To: <11842671631744-git-send-email-skimo@liacs.nl>
From: Sven Verdoolaege <skimo@kotnet.org>
Signed-off-by: Sven Verdoolaege <skimo@kotnet.org>
---
builtin-name-rev.c | 1 -
ctype.c | 5 +++--
git-compat-util.h | 5 ++++-
3 files changed, 7 insertions(+), 4 deletions(-)
diff --git a/builtin-name-rev.c b/builtin-name-rev.c
index 61eba34..b2ac40c 100644
--- a/builtin-name-rev.c
+++ b/builtin-name-rev.c
@@ -233,7 +233,6 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
break;
for (p_start = p; *p; p++) {
-#define ishex(x) (isdigit((x)) || ((x) >= 'a' && (x) <= 'f'))
if (!ishex(*p))
forty = 0;
else if (++forty == 40 &&
diff --git a/ctype.c b/ctype.c
index ee06eb7..97b5724 100644
--- a/ctype.c
+++ b/ctype.c
@@ -6,7 +6,8 @@
#include "cache.h"
#define SS GIT_SPACE
-#define AA GIT_ALPHA
+#define HA GIT_HEXAL
+#define AA GIT_OTHAL
#define DD GIT_DIGIT
unsigned char sane_ctype[256] = {
@@ -16,7 +17,7 @@ unsigned char sane_ctype[256] = {
DD, DD, DD, DD, DD, DD, DD, DD, DD, DD, 0, 0, 0, 0, 0, 0, /* 48-15 */
0, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, /* 64-15 */
AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, 0, 0, 0, 0, 0, /* 80-15 */
- 0, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, /* 96-15 */
+ 0, HA, HA, HA, HA, HA, HA, AA, AA, AA, AA, AA, AA, AA, AA, AA, /* 96-15 */
AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, 0, 0, 0, 0, 0, /* 112-15 */
/* Nothing in the 128.. range */
};
diff --git a/git-compat-util.h b/git-compat-util.h
index 362e040..1a36f4c 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -325,12 +325,15 @@ static inline int has_extension(const char *filename, const char *ext)
extern unsigned char sane_ctype[256];
#define GIT_SPACE 0x01
#define GIT_DIGIT 0x02
-#define GIT_ALPHA 0x04
+#define GIT_HEXAL 0x04
+#define GIT_OTHAL 0x08
+#define GIT_ALPHA (GIT_HEXAL | GIT_OTHAL)
#define sane_istest(x,mask) ((sane_ctype[(unsigned char)(x)] & (mask)) != 0)
#define isspace(x) sane_istest(x,GIT_SPACE)
#define isdigit(x) sane_istest(x,GIT_DIGIT)
#define isalpha(x) sane_istest(x,GIT_ALPHA)
#define isalnum(x) sane_istest(x,GIT_ALPHA | GIT_DIGIT)
+#define ishex(x) sane_istest(x,GIT_HEXAL | GIT_DIGIT)
#define tolower(x) sane_case((unsigned char)(x), 0x20)
#define toupper(x) sane_case((unsigned char)(x), 0)
--
1.5.3.rc0.100.ge60b4
^ permalink raw reply related
* [PATCH 4/6] refs.c: lock cached_refs during for_each_ref
From: skimo @ 2007-07-12 19:06 UTC (permalink / raw)
To: git, Junio C Hamano; +Cc: Johannes Schindelin
In-Reply-To: <11842671631744-git-send-email-skimo@liacs.nl>
From: Sven Verdoolaege <skimo@kotnet.org>
If the function called by for_each_ref modifies a ref in any way,
the cached_refs that for_each_ref was looping over would be
removed, resulting in undefined behavior.
This patch prevents the cached_refs from being removed
while for_each_ref is still iterating over them.
Signed-off-by: Sven Verdoolaege <skimo@kotnet.org>
---
refs.c | 37 +++++++++++++++++++++++++++++++++----
1 files changed, 33 insertions(+), 4 deletions(-)
diff --git a/refs.c b/refs.c
index 4dc7e8b..e710903 100644
--- a/refs.c
+++ b/refs.c
@@ -153,6 +153,8 @@ static struct ref_list *sort_ref_list(struct ref_list *list)
static struct cached_refs {
char did_loose;
char did_packed;
+ char is_locked;
+ char is_invalidated;
struct ref_list *loose;
struct ref_list *packed;
} cached_refs;
@@ -170,6 +172,11 @@ static void invalidate_cached_refs(void)
{
struct cached_refs *ca = &cached_refs;
+ if (ca->is_locked) {
+ ca->is_invalidated = 1;
+ return;
+ }
+
if (ca->did_loose && ca->loose)
free_ref_list(ca->loose);
if (ca->did_packed && ca->packed)
@@ -178,6 +185,24 @@ static void invalidate_cached_refs(void)
ca->did_loose = ca->did_packed = 0;
}
+static void lock_cached_refs(void)
+{
+ struct cached_refs *ca = &cached_refs;
+
+ ca->is_locked = 1;
+}
+
+static void unlock_cached_refs(void)
+{
+ struct cached_refs *ca = &cached_refs;
+
+ ca->is_locked = 0;
+ if (ca->is_invalidated) {
+ invalidate_cached_refs();
+ ca->is_invalidated = 0;
+ }
+}
+
static void read_packed_refs(FILE *f, struct cached_refs *cached_refs)
{
struct ref_list *list = NULL;
@@ -518,10 +543,12 @@ int peel_ref(const char *ref, unsigned char *sha1)
static int do_for_each_ref(const char *base, each_ref_fn fn, int trim,
void *cb_data)
{
- int retval;
+ int retval = 0;
struct ref_list *packed = get_packed_refs();
struct ref_list *loose = get_loose_refs();
+ lock_cached_refs();
+
while (packed && loose) {
struct ref_list *entry;
int cmp = strcmp(packed->name, loose->name);
@@ -538,15 +565,17 @@ static int do_for_each_ref(const char *base, each_ref_fn fn, int trim,
}
retval = do_one_ref(base, fn, trim, cb_data, entry);
if (retval)
- return retval;
+ goto out;
}
for (packed = packed ? packed : loose; packed; packed = packed->next) {
retval = do_one_ref(base, fn, trim, cb_data, packed);
if (retval)
- return retval;
+ goto out;
}
- return 0;
+ out:
+ unlock_cached_refs();
+ return retval;
}
int head_ref(each_ref_fn fn, void *cb_data)
--
1.5.3.rc0.100.ge60b4
^ permalink raw reply related
* [PATCH 5/6] revision: mark commits that didn't match a pattern for later use
From: skimo @ 2007-07-12 19:06 UTC (permalink / raw)
To: git, Junio C Hamano; +Cc: Johannes Schindelin
In-Reply-To: <11842671631744-git-send-email-skimo@liacs.nl>
From: Sven Verdoolaege <skimo@kotnet.org>
Signed-off-by: Sven Verdoolaege <skimo@kotnet.org>
---
revision.c | 4 +++-
revision.h | 1 +
2 files changed, 4 insertions(+), 1 deletions(-)
diff --git a/revision.c b/revision.c
index 38061f9..cbeb137 100644
--- a/revision.c
+++ b/revision.c
@@ -1446,8 +1446,10 @@ static struct commit *get_revision_1(struct rev_info *revs)
if (revs->no_merges &&
commit->parents && commit->parents->next)
continue;
- if (!commit_match(commit, revs))
+ if (!commit_match(commit, revs)) {
+ commit->object.flags |= PRUNED;
continue;
+ }
if (revs->prune_fn && revs->dense) {
/* Commit without changes? */
if (!(commit->object.flags & TREECHANGE)) {
diff --git a/revision.h b/revision.h
index 9728d4c..d3609ab 100644
--- a/revision.h
+++ b/revision.h
@@ -10,6 +10,7 @@
#define CHILD_SHOWN (1u<<6)
#define ADDED (1u<<7) /* Parents already parsed and added? */
#define SYMMETRIC_LEFT (1u<<8)
+#define PRUNED (1u<<9)
struct rev_info;
struct log_info;
--
1.5.3.rc0.100.ge60b4
^ permalink raw reply related
* [PATCH 1/6] revision: allow selection of commits that do not match a pattern
From: skimo @ 2007-07-12 19:05 UTC (permalink / raw)
To: git, Junio C Hamano; +Cc: Johannes Schindelin
In-Reply-To: <11842671631744-git-send-email-skimo@liacs.nl>
From: Sven Verdoolaege <skimo@kotnet.org>
We do this by maintaining two lists of patterns, one for
those that should match and one for those that should not match.
A negative pattern is specified by putting a '!' in front.
For example, to show the commits of Jakub Narebski that
are not about gitweb, you'd do a
git log --author='Narebski' --grep='!gitweb' --all-match
As an added bonus, this patch also documents --all-match.
Signed-off-by: Sven Verdoolaege <skimo@kotnet.org>
Reviewed-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
Documentation/git-rev-list.txt | 17 ++++++++++
revision.c | 64 +++++++++++++++++++++++++++++++++------
revision.h | 1 +
3 files changed, 72 insertions(+), 10 deletions(-)
diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt
index 20dcac6..c462f5d 100644
--- a/Documentation/git-rev-list.txt
+++ b/Documentation/git-rev-list.txt
@@ -214,11 +214,19 @@ limiting may be applied.
Limit the commits output to ones with author/committer
header lines that match the specified pattern (regular expression).
+ A pattern starting with a '!' will show only commits that do
+ not match the remainder of the pattern.
+ To match lines starting with '!', escape the initial '!'
+ with a backslash.
--grep='pattern'::
Limit the commits output to ones with log message that
matches the specified pattern (regular expression).
+ A pattern starting with a '!' will show only commits that do
+ not match the remainder of the pattern.
+ To match lines starting with '!', escape the initial '!'
+ with a backslash.
--regexp-ignore-case::
@@ -229,6 +237,15 @@ limiting may be applied.
Consider the limiting patterns to be extended regular expressions
instead of the default basic regular expressions.
+--all-match::
+
+ Without this option, a commit is shown if any of the
+ (positive or negative) patterns matches, i.e., there
+ is at least one positive match or not all of the negative
+ patterns match. With this options, a commit is only
+ shown if all of the patterns match, i.e., all positive
+ patterns match and no negative pattern matches.
+
--remove-empty::
Stop when a given path disappears from the tree.
diff --git a/revision.c b/revision.c
index 27cce09..38061f9 100644
--- a/revision.c
+++ b/revision.c
@@ -826,34 +826,50 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,
return 0;
}
-static void add_grep(struct rev_info *revs, const char *ptn, enum grep_pat_token what)
+static void add_grep(struct rev_info *revs, const char *ptn,
+ enum grep_pat_token what)
{
- if (!revs->grep_filter) {
+ int negated = 0;
+ struct grep_opt **filter;
+
+ if (ptn[0] == '\\' && ptn[1] == '!')
+ ptn++;
+ if (*ptn == '!') {
+ negated = 1;
+ ptn++;
+ }
+ filter = negated ? &revs->grep_neg_filter : &revs->grep_filter;
+ if (!*filter) {
struct grep_opt *opt = xcalloc(1, sizeof(*opt));
opt->status_only = 1;
opt->pattern_tail = &(opt->pattern_list);
opt->regflags = REG_NEWLINE;
- revs->grep_filter = opt;
+ *filter = opt;
}
- append_grep_pattern(revs->grep_filter, ptn,
- "command line", 0, what);
+ append_grep_pattern(*filter, ptn, "command line", 0, what);
}
-static void add_header_grep(struct rev_info *revs, const char *field, const char *pattern)
+static void add_header_grep(struct rev_info *revs, const char *field,
+ const char *pattern)
{
char *pat;
- const char *prefix;
+ const char *prefix, *negated;
int patlen, fldlen;
fldlen = strlen(field);
patlen = strlen(pattern);
pat = xmalloc(patlen + fldlen + 10);
+ negated = "";
+ if (*pattern == '!') {
+ negated = "!";
+ pattern++;
+ }
prefix = ".*";
if (*pattern == '^') {
prefix = "";
pattern++;
}
- sprintf(pat, "^%s %s%s", field, prefix, pattern);
+ sprintf(pat, "%s^%s %s%s", negated, field, prefix, pattern);
add_grep(revs, pat, GREP_PATTERN_HEAD);
}
@@ -1217,6 +1233,9 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
if (revs->grep_filter)
revs->grep_filter->regflags |= regflags;
+ if (revs->grep_neg_filter)
+ revs->grep_neg_filter->regflags |= regflags;
+
if (show_merge)
prepare_show_merge(revs);
if (def && !revs->pending.nr) {
@@ -1254,6 +1273,11 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
compile_grep_patterns(revs->grep_filter);
}
+ if (revs->grep_neg_filter) {
+ revs->grep_neg_filter->all_match = !all_match;
+ compile_grep_patterns(revs->grep_neg_filter);
+ }
+
return left;
}
@@ -1352,11 +1376,31 @@ static int rewrite_parents(struct rev_info *revs, struct commit *commit)
return 0;
}
+/*
+ * If all_match is set, then a commit matches if all the positive
+ * patterns match and not one of the negative patterns matches.
+ * If all_match is not set, then a commit matches if at least one
+ * of the positive patterns matches or not all of the negative
+ * patterns match.
+ */
static int commit_match(struct commit *commit, struct rev_info *opt)
{
- if (!opt->grep_filter)
+ int pos_match, all_match;
+
+ pos_match = !opt->grep_filter ||
+ grep_buffer(opt->grep_filter,
+ NULL, /* we say nothing, not even filename */
+ commit->buffer, strlen(commit->buffer));
+ if (!opt->grep_neg_filter)
+ return pos_match;
+
+ all_match = !opt->grep_neg_filter->all_match;
+ if (!all_match && opt->grep_filter && pos_match)
return 1;
- return grep_buffer(opt->grep_filter,
+ if (all_match && !pos_match)
+ return 0;
+
+ return !grep_buffer(opt->grep_neg_filter,
NULL, /* we say nothing, not even filename */
commit->buffer, strlen(commit->buffer));
}
diff --git a/revision.h b/revision.h
index f46b4d5..9728d4c 100644
--- a/revision.h
+++ b/revision.h
@@ -84,6 +84,7 @@ struct rev_info {
/* Filter by commit log message */
struct grep_opt *grep_filter;
+ struct grep_opt *grep_neg_filter;
/* special limits */
int skip_count;
--
1.5.3.rc0.100.ge60b4
^ permalink raw reply related
* Better handling of local changes in 'gitk'?
From: Linus Torvalds @ 2007-07-12 19:20 UTC (permalink / raw)
To: Paul Mackerras; +Cc: Git Mailing List
I like how gitk shows the local changes as an unnamed commit at the top,
but what I *don't* like is how it just ignored the difference between
stuff that has been added to the index, and stuff that hasn't..
It would be very nice to have *two* such commits (either or both of which
just disappear), where the top-most is the diff to the index, and the
second is the diff from the index to HEAD.
That would not only be useful in general, it would be a wonderful way to
visually introduce people to the notion of what the staging area is all
about.
I think "gitk" was a great way early in git history to show how the git
commit history works and that it made a lot of people understand a lot
more how everything tied together (in a way that would have been much
nastier to visualize with just the SHA1's in "git log"), and I think it
could do the same thing for the staging area, which still seems to
occasionally come up as an issue that confuses some people.
But my inability with tcl/tk precludes me from actually changing the logic
that does
git diff-index HEAD
into two different things that do the two operations
git diff-index --cached HEAD
git diff-files
respectively and ties them together as the two fake commits...
Paul?
Linus
^ permalink raw reply
* Re: Better handling of local changes in 'gitk'?
From: Junio C Hamano @ 2007-07-12 20:43 UTC (permalink / raw)
To: Linus Torvalds; +Cc: Paul Mackerras, Git Mailing List
In-Reply-To: <alpine.LFD.0.999.0707121214420.20061@woody.linux-foundation.org>
Linus Torvalds <torvalds@linux-foundation.org> writes:
> I like how gitk shows the local changes as an unnamed commit at the top,
> but what I *don't* like is how it just ignored the difference between
> stuff that has been added to the index, and stuff that hasn't..
>
> It would be very nice to have *two* such commits (either or both of which
> just disappear), where the top-most is the diff to the index, and the
> second is the diff from the index to HEAD.
>
> That would not only be useful in general, it would be a wonderful way to
> visually introduce people to the notion of what the staging area is all
> about.
Interesting, as I was thinking about something similar when I
typed "git show stash" by mistake. I meant to say "git stash
show", but "git show stash" output actually was even closer to
what I wanted to see.
"git stash" internally creates two commits, based on your HEAD:
.----W
/ /
---H----I
Here, W keeps the state of the working tree, I is the index
state and H is the HEAD. Commit I is direct child of H, Commit
W is an evil merge between H and I, and that is what is kept as
refs/stash, so "git show stash" would end up showing that merge
in --cc format.
^ permalink raw reply
* Re: Better handling of local changes in 'gitk'?
From: Junio C Hamano @ 2007-07-12 20:48 UTC (permalink / raw)
To: Linus Torvalds; +Cc: Paul Mackerras, Git Mailing List
In-Reply-To: <7vir8ptksi.fsf@assigned-by-dhcp.cox.net>
Junio C Hamano <gitster@pobox.com> writes:
> Linus Torvalds <torvalds@linux-foundation.org> writes:
>
>> I like how gitk shows the local changes as an unnamed commit at the top,
>> but what I *don't* like is how it just ignored the difference between
>> stuff that has been added to the index, and stuff that hasn't..
>>
>> It would be very nice to have *two* such commits (either or both of which
>> just disappear), where the top-most is the diff to the index, and the
>> second is the diff from the index to HEAD.
>>
>> That would not only be useful in general, it would be a wonderful way to
>> visually introduce people to the notion of what the staging area is all
>> about.
>
> Interesting, as I was thinking about something similar when I
> typed "git show stash" by mistake. I meant to say "git stash
> show", but "git show stash" output actually was even closer to
> what I wanted to see.
>
> "git stash" internally creates two commits, based on your HEAD:
> ...
Clarification. I do not mean gitk should create such a view
using git-stash (for one thing that would nuke your local
modifications after saving it away). I just meant that I agree
with you that --cc between HEAD, index and the working tree is a
wonderful way to view the current state.
It might make sense to teach "git diff" itself to show this
3-way diff with a new option ("git diff --h-i-w"). The
necessary machinery is already there to handle "git diff maint
master next pu" (four trees!), and "git diff maint:Makefile
master:Makefile next:Makefile" (three blobs).
^ permalink raw reply
* [ANNOUNCE] GIT 1.5.2.4
From: Junio C Hamano @ 2007-07-12 20:57 UTC (permalink / raw)
To: git; +Cc: linux-kernel
In-Reply-To: <7vodjf1gxl.fsf@assigned-by-dhcp.pobox.com>
The latest maintenance release GIT 1.5.2.4 is available at the
usual places:
http://www.kernel.org/pub/software/scm/git/
git-1.5.2.4.tar.{gz,bz2} (tarball)
git-htmldocs-1.5.2.4.tar.{gz,bz2} (preformatted docs)
git-manpages-1.5.2.4.tar.{gz,bz2} (preformatted docs)
RPMS/$arch/git-*-1.5.2.4-1.$arch.rpm (RPM)
----------------------------------------------------------------
GIT v1.5.2.4 Release Notes
==========================
Fixes since v1.5.2.3
--------------------
* Bugfixes
- "git-gui" bugfixes, including a handful fixes to run it
better on Cygwin/MSYS.
- "git checkout" failed to switch back and forth between
branches, one of which has "frotz -> xyzzy" symlink and
file "xyzzy/filfre", while the other one has a file
"frotz/filfre".
- "git prune" used to segfault upon seeing a commit that is
referred to by a tree object (aka "subproject").
- "git diff --name-status --no-index" mishandled an added file.
- "git apply --reverse --whitespace=warn" still complained
about whitespaces that a forward application would have
introduced.
* Documentation Fixes and Updates
- A handful documentation updates.
----------------------------------------------------------------
Changes since v1.5.2.3 are as follows:
Andy Parkins (2):
Make git-prune submodule aware (and fix a SEGFAULT in the process)
user-manual: grammar and style fixes
Gerrit Pape (1):
git-gui: properly popup error if gitk should be started but is not installed
J. Bruce Fields (2):
tutorial: Fix typo
user-manual: more explanation of push and pull usage
Jim Meyering (1):
Don't smash stack when $GIT_ALTERNATE_OBJECT_DIRECTORIES is too long
Johannes Schindelin (3):
diff --no-index: fix --name-status with added files
glossary: add 'reflog'
Fix "apply --reverse" with regard to whitespace
Junio C Hamano (2):
Teach read-tree 2-way merge to ignore intermediate symlinks
GIT 1.5.2.4
Michael Hendricks (1):
Correctly document the name of the global excludes file configuration
Miklos Vajna (1):
Document -<n> for git-format-patch
Shawn O. Pearce (10):
git-gui: Unlock the index when cancelling merge dialog
git-gui: Don't bind F5/M1-R in all windows
git-gui: Bind M1-P to push action
git-gui: Include a Push action on the left toolbar
git-gui: Ensure windows shortcuts always have .bat extension
git-gui: Skip nicknames when selecting author initials
git-gui: Correct ls-tree buffering problem in browser
git-gui: Don't linewrap within console windows
Clarify documentation of fast-import's D subcommand
git-gui: Work around bad interaction between Tcl and cmd.exe on ^{tree}
William Pursell (1):
user-manual: fix directory name in git-archive example
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox