* Re: [PATCH] Add support for author-oriented git-rev-list switches [rev 8]
From: Jon Seymour @ 2005-06-09 15:53 UTC (permalink / raw)
To: git; +Cc: jon.seymour, Junio C Hamano, Linus Torvalds
In-Reply-To: <2cfc403205060902373e5c284f@mail.gmail.com>
Since no-one had pulled the rev-7 patch to review, I have tweaked and
simplified the test cases some more. In particular, I removed the need
for /dev/urandom.
http://blackcubes.dyndns.org/epoch/wrt-patch-latest.patch
Junio: you might be interested in a new bash function I wrote in
t/t6001-rev-list-merge-order.sh
called: test_output_expect_success
If you like, I'll generalize it and move it into testlib.sh
Linus: when you are ready, the URI above contains the latest patch.
jon.
^ permalink raw reply
* Re: [PATCH 3/3] read-tree -m 3-way: handle more trivial merges internally
From: Junio C Hamano @ 2005-06-09 17:26 UTC (permalink / raw)
To: Linus Torvalds; +Cc: git
In-Reply-To: <Pine.LNX.4.58.0506090800580.2286@ppc970.osdl.org>
>>>>> "LT" == Linus Torvalds <torvalds@osdl.org> writes:
LT> No, I think this is quite possibly wrong for several reasons.
I agree with everything you said.
I need to regurgitate other points you raised, but one immediate
comment on the "lost remove" case. The current two-way code has
the same brokenness in that it does not unlink removed files
under "-u". We either need the "list of files to be removed",
or we need to make two-way abort if we see these "remove" cases.
^ permalink raw reply
* Re: [PATCH 3/3] read-tree -m 3-way: handle more trivial merges internally
From: Linus Torvalds @ 2005-06-09 17:37 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <7v7jh3phkk.fsf@assigned-by-dhcp.cox.net>
On Thu, 9 Jun 2005, Junio C Hamano wrote:
>
> I need to regurgitate other points you raised, but one immediate
> comment on the "lost remove" case. The current two-way code has
> the same brokenness in that it does not unlink removed files
> under "-u". We either need the "list of files to be removed",
> or we need to make two-way abort if we see these "remove" cases.
Yes, you're right.
Ho humm. I'll think about it. There's no "next" pointer in a struct
cache-struct, and because we use the on-disk layout (good or bad, I dunno,
but it does remove the need for copying megabytes of data for some cases)
we can't just add one. So to generate a list of "deleted" files we'd have
to make a separate array or something.
Not hard, but it's a bit ugly. I don't see any alternative, though, unless
we really do end up using the same "leave it in the different stages and
force people to run git-merge-cache on the result" thing that the
three-way merge does.
The fact that the three-way merge _might_ also like to remove the entries,
and that the two-way merge already handles the addition of new files, does
kind of argue that we should do it. For symmetry witht he "file add" case,
if nothing else.
Linus
^ permalink raw reply
* RFE: git relink
From: Jeff Garzik @ 2005-06-09 18:35 UTC (permalink / raw)
To: Git Mailing List
It would be nice if somebody were motivated enough to create a command
that functions like:
git relink repoA repoB repoC repoD... repoX
which would examine
repoA/.git
repoB/.git
repoC/.git
repoD/.git
and verify (updating, if necessary) that each of the A/B/C/D repos are
hardlinked to repoX.
Jeff
^ permalink raw reply
* Re: RFE: git relink
From: Ryan Anderson @ 2005-06-09 19:15 UTC (permalink / raw)
To: Jeff Garzik; +Cc: Git Mailing List
In-Reply-To: <42A88C07.5050907@pobox.com>
On Thu, Jun 09, 2005 at 02:35:51PM -0400, Jeff Garzik wrote:
>
> It would be nice if somebody were motivated enough to create a command
> that functions like:
>
> git relink repoA repoB repoC repoD... repoX
>
> which would examine
>
> repoA/.git
> repoB/.git
> repoC/.git
> repoD/.git
>
> and verify (updating, if necessary) that each of the A/B/C/D repos are
> hardlinked to repoX.
Submitted a while ago, dunno what happened with it. This only does 2
repositories, but it's trivial to do
for i in repoA repoB repoC repoD ; do git-relink-script "$i" repoX ; done
Provide a tool to relink two git repositories.
Signed-Off-By: Ryan Anderson <ryan@michonline.com>
---
commit a3bcc763d71bdb91a3b48e9105fbaa5e79abb807
tree 2553e2d8befbe0cda3e413616fd4cc7bf04157ad
parent a31c6d022e2435a514fcc8ca57f9995c4376a986
author Ryan Anderson <ryan@mythryan2.(none)> 1115185675 -0400
committer Ryan Anderson <ryan@michonline.com> 1115185675 -0400
Index: Makefile
===================================================================
--- 51a882a2dc62e0d3cdc79e0badc61559fb723481/Makefile (mode:100644 sha1:99b4753d34879842b972da9b68694c9d0485f216)
+++ 2553e2d8befbe0cda3e413616fd4cc7bf04157ad/Makefile (mode:100644 sha1:a99665e252a2342caa84238e886a80a5f27ac3c8)
@@ -13,7 +13,7 @@
AR=ar
SCRIPTS=git-apply-patch-script git-merge-one-file-script git-prune-script \
- git-pull-script git-tag-script
+ git-pull-script git-tag-script git-relink-script
PROG= git-update-cache git-diff-files git-init-db git-write-tree \
git-read-tree git-commit-tree git-cat-file git-fsck-cache \
Index: git-relink-script
===================================================================
--- /dev/null (tree:51a882a2dc62e0d3cdc79e0badc61559fb723481)
+++ 2553e2d8befbe0cda3e413616fd4cc7bf04157ad/git-relink-script (mode:100644 sha1:78c954edcc370d8be951c856bfbfd38975d08348)
@@ -0,0 +1,115 @@
+#!/usr/bin/env perl
+# Copyright 2005, Ryan Anderson <ryan@michonline.com>
+# Distribution permitted under the GPL v2, as distributed
+# by the Free Software Foundation.
+# Later versions of the GPL at the discretion of Linus Torvalds
+#
+# Scan two git object-trees, and hardlink any common objects between them.
+
+use 5.006;
+use strict;
+use warnings;
+
+sub get_canonical_form($);
+sub do_scan_directory($$$);
+sub compare_two_files($$);
+
+# stats
+my $linked = 0;
+my $already = 0;
+
+my ($dir1, $dir2) = @ARGV;
+
+if (!defined $dir1 || !defined $dir2) {
+ print("Usage: $0 <dir1> <dir2>\nBoth dir1 and dir2 should contain a .git/objects/ subdirectory.\n");
+ exit(1);
+}
+
+$dir1 = get_canonical_form($dir1);
+$dir2 = get_canonical_form($dir2);
+
+printf("Searching '%s' and '%s' for common objects and hardlinking them...\n",$dir1,$dir2);
+
+opendir(D,$dir1 . "objects/")
+ or die "Failed to open $dir1/objects/ : $!";
+
+my @hashdirs = grep !/^\.{1,2}$/, readdir(D);
+foreach my $hashdir (@hashdirs) {
+ do_scan_directory($dir1, $hashdir, $dir2);
+}
+
+printf("Linked %d files, %d were already linked.\n",$linked, $already);
+
+
+sub do_scan_directory($$$) {
+ my ($srcdir, $subdir, $dstdir) = @_;
+
+ my $sfulldir = sprintf("%sobjects/%s/",$srcdir,$subdir);
+ my $dfulldir = sprintf("%sobjects/%s/",$dstdir,$subdir);
+
+ opendir(S,$sfulldir)
+ or die "Failed to opendir $sfulldir: $!";
+
+ foreach my $file (grep(!/\.{1,2}$/, readdir(S))) {
+ my $sfilename = $sfulldir . $file;
+ my $dfilename = $dfulldir . $file;
+
+ compare_two_files($sfilename,$dfilename);
+
+ }
+ closedir(S);
+}
+
+sub compare_two_files($$) {
+ my ($sfilename, $dfilename) = @_;
+
+ # Perl's stat returns relevant information as follows:
+ # 0 = dev number
+ # 1 = inode number
+ # 7 = size
+ my @sstatinfo = stat($sfilename);
+ my @dstatinfo = stat($dfilename);
+
+ if (@sstatinfo == 0 && @dstatinfo == 0) {
+ die sprintf("Stat of both %s and %s failed: %s\n",$sfilename, $dfilename, $!);
+
+ } elsif (@dstatinfo == 0) {
+ return;
+ }
+
+ if ( ($sstatinfo[0] == $dstatinfo[0]) &&
+ ($sstatinfo[1] != $dstatinfo[1])) {
+ if ($sstatinfo[7] == $dstatinfo[7]) {
+ unlink($dfilename)
+ or die "Unlink of $dfilename failed: $!\n";
+
+ link($sfilename,$dfilename)
+ or die "Failed to link $sfilename to $dfilename: $!\n" .
+ "Git Repository containing $dfilename is probably corrupted, please copy '$sfilename' to '$dfilename' to fix.\n";
+
+ $linked++;
+
+ } else {
+ die sprintf("ERROR: File sizes are not the same, cannot relink %s to %s.\n",
+ $sfilename, $dfilename);
+ }
+
+ } elsif ( ($sstatinfo[0] == $dstatinfo[0]) &&
+ ($sstatinfo[1] == $dstatinfo[1])) {
+ $already++;
+ }
+}
+
+sub get_canonical_form($) {
+ my $dir = shift;
+ my $original = $dir;
+
+ die "$dir is not a directory." unless -d $dir;
+
+ $dir .= "/" unless $dir =~ m#/$#;
+ $dir .= ".git/" unless $dir =~ m#\.git/$#;
+
+ die "$original does not have a .git/ subdirectory.\n" unless -d $dir;
+
+ return $dir;
+}
--
Ryan Anderson
sometimes Pug Majere
^ permalink raw reply
* Lost uncommitted changes and cogito
From: Jon Smirl @ 2005-06-09 19:23 UTC (permalink / raw)
To: git
Uning current updated cogito I have three trees:
linus
linux-good
linux-merge
linux-merge had uncommitted changes
>From linus I did cg update which pulled from linus.
From linux-merge I did cg update which pulled from linus.
At this point the uncommitted changes in linux-merge were fine.
Then I wasn't thinking and did cg update linux-good from linux-merge.
linux-merge was already update to date with linux-good since there
were no changes to linux-good pending. Doing that update created an
empty commit and also lost my uncommitted changes. Shouldn't that have
just done nothing?
>From first update in linux-merge:
Applying changes...
drivers/char/drm/Kconfig: locally modified
drivers/char/drm/drm_drv.c: locally modified
drivers/char/drm/radeon_drv.c: locally modified
drivers/char/drm/radeon_drv.h: locally modified
drivers/video/aty/radeon_base.c: locally modified
Merging 35d1bc90546d1f0af198886ae8062a550142d926 ->
cf380ee7308db0f067ceb2ae8b852838788bf453
to 4952d9aaa928e711b0cd40c6a6d48b73544b88f0...
Committed as 941cc5f620f6f4827885bc3f03519416bd9461ab.
[jonsmirl@jonsmirl linux-merge]$ make
>From second - local changes are gone.
Applying changes...
Merging cf380ee7308db0f067ceb2ae8b852838788bf453 ->
a7db5e4f33e72569936af357084e31305fab6618
to 941cc5f620f6f4827885bc3f03519416bd9461ab...
Committed as b0165eea961c7f2809a0aaabae7904a3a766ee05.
[jonsmirl@jonsmirl linux-merge]$ make
--
Jon Smirl
jonsmirl@gmail.com
^ permalink raw reply
* Re: RFE: git relink
From: Ryan Anderson @ 2005-06-09 19:29 UTC (permalink / raw)
To: Ryan Anderson; +Cc: Jeff Garzik, Git Mailing List
In-Reply-To: <20050609191548.GG21076@mythryan2.michonline.com>
On Thu, Jun 09, 2005 at 03:15:48PM -0400, Ryan Anderson wrote:
> On Thu, Jun 09, 2005 at 02:35:51PM -0400, Jeff Garzik wrote:
> >
> > It would be nice if somebody were motivated enough to create a command
> > that functions like:
> >
> > git relink repoA repoB repoC repoD... repoX
> >
> > which would examine
> >
> > repoA/.git
> > repoB/.git
> > repoC/.git
> > repoD/.git
> >
> > and verify (updating, if necessary) that each of the A/B/C/D repos are
> > hardlinked to repoX.
>
> Submitted a while ago, dunno what happened with it. This only does 2
> repositories, but it's trivial to do
> for i in repoA repoB repoC repoD ; do git-relink-script "$i" repoX ; done
And of course, I didn't actually look at my code, what my code really
wants is:
for i in repoA repoB repoC repoD ; do git-relink-script repoX "$i" ; done
It should be relatively trivial to convert it over to the other behavior
if it matters.
--
Ryan Anderson
sometimes Pug Majere
^ permalink raw reply
* Re: [PATCH 3/3] read-tree -m 3-way: handle more trivial merges internally
From: Junio C Hamano @ 2005-06-09 20:35 UTC (permalink / raw)
To: Linus Torvalds; +Cc: git
In-Reply-To: <Pine.LNX.4.58.0506090800580.2286@ppc970.osdl.org>
>>>>> "LT" == Linus Torvalds <torvalds@osdl.org> writes:
LT> Namely that read-tree doesn't have a frigging clue about renames, and
LT> shouldn't have.
LT> But a real merge program _could_ have a frigging clue, and might notice
LT> patterns like
LT> - file got modified in one branch, removed in the other
LT> - a file got added in the other branch
LT> - "Hey, that added file looks like the removed one!"
LT> - Let's merge the modifications from the first branch into the move of
LT> the second branch!
LT> Now, you can (validly) argue that you could still just look at the
LT> original trees ("git-diff-tree -C $O $M") and grep for copies/movement and
LT> do it by hand _there_ instead of looking at the result of the read-tree,
LT> and you may well be right. So again, this is not a _fundamental_ problem,
LT> although it's a bit more fundamental than the first one.
My knee-jerk reaction was "No, I would refuse to make that
argument, because making the merge mechanism to examine trees
itself would take us full-circle back to where we started
[*1*]".
I agree we can, as the zeroth order approximation, run two
"diff-tree -B --find-copies-harder -C" [*3*, *4*] on (O,A) and
(O,B) pairs, and compare their output to cover the rename case
[*2*] you described. I think we also can write a simple program
that reads an unmerged index file and do the equivalent of these
two diff-tree commands.
However, what I suspect to happen in practice is that the lines
of development leading to A and B may have so much modification
to those renamed or copied files since they forked at O that we
may not recognize renames or copies as such by only looking at
(O,A) and (O,B). In order to do a reasonable job while merging,
we may end up needing to run "diff-tree --stdin -B -C" on the
output of "rev-list O A" to fully follow the rename/copy trail
[*5*].
What all this means is that the simple three-stage information
read-tree -m gives us, which is about only three trees, might
not be enough to handle renames and copies intelligently, when
we need to deal with a pair of trees that have diverged for too
long.
Once we go down this path, arguing against making "read-tree -m"
results useless for such an intelligent merge logic (because it
forces the merge logic to look at the trees and commits
involved) ceases to make much sense, because such an intelligent
merge logic needs to look at more than three trees _anyway_.
What "read-tree -m" gives us, while being very efficient,
elegant and effective in "merge small and merge often" use
pattern we recommend, may not be so useful to implement such an
intelligent merge logic, and instead we would do better if we
did it the hard way by inspecting individual commits. I do not
have problem with that approach. It would be a much longer-term
project, though.
So, yes I ended up arguing that the intelligent merge logic
could and probably needs to look at the trees involved ;-).
Among the three-way cases, the only case I think that may make a
practical difference is the case #5ALT, which deals with "a file
added identically in both branches" case. This is what happens
when a widely accepted patch has been applied independently to
both trees recently (eh, "since they forked"). New files tend
to get updated more often, and allowing the file to be locally
modified, instead of failing the merge in read-tree phase, would
help the workflow. If the file were modified in the user's
repository, and checked in, then the current 3-way merge code
cannot help the user that much, because we would be in !O && A
&& B && A!=B situation. I have a suspicion that we could
probably help this case by looking at not just merge base but
the edge commits as well.
I consider #14ALT an improvement, but at the same time I doubt
that particular one would make much practical difference. It is
more or less "while we are at it" kind of change. All others,
including the "remove" cases (I botched -u but as you point out
it is correctable), do not contribute to loosening the index
requirements, but I suspect they might help me later unify
two-way fast forward and three-way merge. Yes, I am still
looking at "read-tree -m H I-mixed-with-H M" that emulates
"read-tree H M".
[Footnotes]
*1* Remember merge-trees Perl script, which I did before you
invented the multi-stage read-tree? Boy it feels like it was so
distant past...
*2* A casual reader may notice that we are arguing about renames
after both of us publicly stated that "renames do not matter".
Here is a clarification. We both consider "recording renames at
commit time" does not matter, but we do take "tracking and
handling the renames" seriously. There is a difference.
*3* Oops. There is not --find-copies-harder yet ;-).
*4* This would be further helped if we had a --show-rename-only
diffcore filter. The operation is similar to the pickaxe, but
it would prune changesets down only to renames and copies. I
actually wrote and threw away such a filter back when I was
trying to find good test cases in linux-2.6 repository.
*5* And the development line leading to A or B may not even be
linear, in which case it may be easier to first decompose the
chain between O and A into individual epochs. Jon Seymour's
"rev-list --merge-order O A" would be very handy for this.
^ permalink raw reply
* Re: [PATCH] Add support for author-oriented git-rev-list switches [rev 8]
From: Junio C Hamano @ 2005-06-09 20:35 UTC (permalink / raw)
To: jon; +Cc: git, jon.seymour, Linus Torvalds
In-Reply-To: <2cfc4032050609085341e46242@mail.gmail.com>
>>>>> "JS" == Jon Seymour <jon.seymour@gmail.com> writes:
JS> test_output_expect_success
JS> If you like, I'll generalize it and move it into testlib.sh
Sounds sensible, and yes please I prefer to have it in
testlib.sh, and you would get bonus points if you converted some
(I do not demand you do all of them ;-)) existing cat/echo users
while you are at it.
Except for some minor points.
I do not think use of "local" to help readability is absolutely
necessary in this small function, and if it is a bashism, I
prefer to see it written in a more portable form. I was
thinking about removing bashism from the test scripts, although
I have not gotten around to actually doing it [*1*, *2*].
[Footnotes]
*1* Auditing all the shell scripts to look for quoting bugs
while eradicating bashism would be another good "Janitor"
sub-project if somebody is interested on the list.
I do not know what happend to other items on the Janitor project
list. Personally I liked the one Sean did to redo the command
line parameters using argp.
*2* Not that I claim what I have already written is bash free.
I do not have a handy reference that lists which is bashism and
which is in POSIX. To stay away from bashism, I just try not to
use certain things that I did not use when I was introduced to
shell programming. That list includes "${parameter#word}",
"${!parameter}", "function" and "local".
^ permalink raw reply
* Re: [ANNOUNCE] Cogito-0.11.3
From: Chris Wright @ 2005-06-09 20:55 UTC (permalink / raw)
To: Dan Holmsand; +Cc: git, Konstantin Antselovich, Petr Baudis
In-Reply-To: <42A83F0C.5020809@gmail.com>
* Dan Holmsand (holmsand@gmail.com) wrote:
> Konstantin Antselovich wrote:
> >I have updated to Cogito-0.11.3, it compiles and runs
> >but make test returns multiple error messages (see below)
>
> There's a typo in rev-list.c. This fixes the tests for me:
This patch is white space damaged. I fixed it, and added it to the
cogito-0.11.3 rpm. Below is the refreshed patch.
thanks,
-chris
--
From: Dan Holmsand <holmsand@gmail.com>
There's a typo in rev-list.c. This fixes the tests for me:
diff --git a/rev-list.c b/rev-list.c
--- a/rev-list.c
+++ b/rev-list.c
@@ -218,7 +218,7 @@ int main(int argc, char **argv)
list = limit_list(list);
show_commit_list(list);
} else {
-#ifdef NO_OPENSSL
+#ifndef NO_OPENSSL
if (sort_list_in_merge_order(list, &process_commit)) {
die("merge order sort failed\n");
}
^ permalink raw reply
* Re: [ANNOUNCE] Cogito-0.11.3
From: Chris Wright @ 2005-06-09 21:06 UTC (permalink / raw)
To: Chris Wright; +Cc: Dan Holmsand, git, Konstantin Antselovich, Petr Baudis
In-Reply-To: <20050609205503.GJ5324@shell0.pdx.osdl.net>
* Chris Wright (chrisw@osdl.org) wrote:
> * Dan Holmsand (holmsand@gmail.com) wrote:
> > Konstantin Antselovich wrote:
> > >I have updated to Cogito-0.11.3, it compiles and runs
> > >but make test returns multiple error messages (see below)
> >
> > There's a typo in rev-list.c. This fixes the tests for me:
>
> This patch is white space damaged. I fixed it, and added it to the
> cogito-0.11.3 rpm. Below is the refreshed patch.
Looks like showdate() is having some minor trouble. A simple cg-log
gave me errors indicating the tz is being interpreted as octal.
There's probably a better way, but bruteforce works ;-) This patch is
in the RPM packages which are now uploading.
thanks,
-chris
--
Strip leading zero from timezone to keep it from being interpreted as
octal causing error such as:
/usr/lib/cogito/cg-Xlib: line 69: 0800: value too great for base (error token is "0800")
Signed-off-by: Chris Wright <chrisw@osdl.org>
--- a/cg-Xlib
+++ b/cg-Xlib
@@ -65,7 +65,9 @@ showdate () {
[ "$format" ] || format=-R
sec=${date[0]}; tz=${date[1]}
if [ "$has_gnudate" ]; then
- dtz=${tz/+/}
+ dtz=${tz/-0/-}
+ dtz=${dtz/+/}
+ dtz=${dtz/#0/}
lsec=$(($dtz / 100 * 3600 + $dtz % 100 * 60 + $sec))
pdate="$(date -ud "1970-01-01 UTC + $lsec sec" "$format" 2>/dev/null)"
else
^ permalink raw reply
* [COGITO PATCH] value too great for base (error token is "-0800")
From: Pavel Roskin @ 2005-06-09 21:40 UTC (permalink / raw)
To: git
Hello!
The current cogito has problems with timezones that are too far from the
Greenwich meridian :-)
Bash interprets numbers beginning with 0 as octals. Therefore, we need
to strip leading zeroes or zeroes following "-". But if we get too
zealous and strip all digits, we'll need to restore one 0. I tried to
write for sed portably, so I avoided some optimizations, such as
s/^-\?$/0/
Signed-off-by: Pavel Roskin <proski@gnu.org>
diff --git a/cg-Xlib b/cg-Xlib
--- a/cg-Xlib
+++ b/cg-Xlib
@@ -65,7 +65,7 @@ showdate () {
[ "$format" ] || format=-R
sec=${date[0]}; tz=${date[1]}
if [ "$has_gnudate" ]; then
- dtz=${tz/+/}
+ dtz=$(echo $tz | sed 's/^+//;s/^0*//;s/^-0*/-/;s/^$/0/;s/^-$/0/')
lsec=$(($dtz / 100 * 3600 + $dtz % 100 * 60 + $sec))
pdate="$(date -ud "1970-01-01 UTC + $lsec sec" "$format" 2>/dev/null)"
else
--
Regards,
Pavel Roskin
^ permalink raw reply
* [PATCH] Add git-diff-stages command.
From: Junio C Hamano @ 2005-06-09 22:13 UTC (permalink / raw)
To: Linus Torvalds; +Cc: git
In-Reply-To: <7vaclzclqd.fsf@assigned-by-dhcp.cox.net>
The diff-* brothers acquired a sibling, git-diff-stages. With
an unmerged index file, you specify two stage numbers and it
shows the differences between them.
Signed-off-by: Junio C Hamano <junkio@cox.net>
---
*** ... I think we also can write a simple program that reads an
*** unmerged index file and do the equivalent of these two
*** diff-tree commands.
***
*** Only lightly tested.
Makefile | 3 +-
diff-stages.c | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 114 insertions(+), 1 deletions(-)
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -33,7 +33,7 @@ PROG= git-update-cache git-diff-files
git-http-pull git-ssh-push git-ssh-pull git-rev-list git-mktag \
git-diff-helper git-tar-tree git-local-pull git-write-blob \
git-get-tar-commit-id git-mkdelta git-apply git-stripspace \
- git-cvs2git
+ git-cvs2git git-diff-stages
all: $(PROG)
@@ -117,6 +117,7 @@ git-write-blob: write-blob.c
git-mkdelta: mkdelta.c
git-stripspace: stripspace.c
git-cvs2git: cvs2git.c
+git-diff-stages: diff-stages.c
git-http-pull: LIBS += -lcurl
git-rev-list: LIBS += -lssl
diff --git a/diff-stages.c b/diff-stages.c
new file mode 100644
--- /dev/null
+++ b/diff-stages.c
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2005 Junio C Hamano
+ */
+
+#include "cache.h"
+#include "diff.h"
+
+static int diff_output_format = DIFF_FORMAT_HUMAN;
+static int detect_rename = 0;
+static int diff_setup_opt = 0;
+static int diff_score_opt = 0;
+static const char *pickaxe = NULL;
+static int pickaxe_opts = 0;
+static int diff_break_opt = -1;
+static const char *orderfile = NULL;
+
+static char *diff_stages_usage =
+"git-diff-stages [-p] [-r] [-z] [-M] [-C] [-R] [-S<string>] [-O<orderfile>] <stage1> <stage2> [<path>...]";
+
+int main(int ac, const char **av)
+{
+ int stage1, stage2, i;
+
+ read_cache();
+ while (1 < ac && av[1][0] == '-') {
+ const char *arg = av[1];
+ if (!strcmp(arg, "-r"))
+ ; /* as usual */
+ else if (!strcmp(arg, "-p"))
+ diff_output_format = DIFF_FORMAT_PATCH;
+ else if (!strncmp(arg, "-B", 2)) {
+ if ((diff_break_opt = diff_scoreopt_parse(arg)) == -1)
+ usage(diff_stages_usage);
+ }
+ else if (!strncmp(arg, "-M", 2)) {
+ detect_rename = DIFF_DETECT_RENAME;
+ if ((diff_score_opt = diff_scoreopt_parse(arg)) == -1)
+ usage(diff_stages_usage);
+ }
+ else if (!strncmp(arg, "-C", 2)) {
+ detect_rename = DIFF_DETECT_COPY;
+ if ((diff_score_opt = diff_scoreopt_parse(arg)) == -1)
+ usage(diff_stages_usage);
+ }
+ else if (!strcmp(arg, "-z"))
+ diff_output_format = DIFF_FORMAT_MACHINE;
+ else if (!strcmp(arg, "-R"))
+ diff_setup_opt |= DIFF_SETUP_REVERSE;
+ else if (!strncmp(arg, "-S", 2))
+ pickaxe = arg + 2;
+ else if (!strncmp(arg, "-O", 2))
+ orderfile = arg + 2;
+ else if (!strcmp(arg, "--pickaxe-all"))
+ pickaxe_opts = DIFF_PICKAXE_ALL;
+ else
+ usage(diff_stages_usage);
+ ac--; av++;
+ }
+
+ if (ac < 3 ||
+ sscanf(av[1], "%d", &stage1) != 1 ||
+ ! (0 <= stage1 && stage1 <= 3) ||
+ sscanf(av[2], "%d", &stage2) != 1 ||
+ ! (0 <= stage2 && stage2 <= 3))
+ usage(diff_stages_usage);
+
+ av += 3; /* The rest from av[0] are for paths restriction. */
+ diff_setup(diff_setup_opt);
+
+ i = 0;
+ while (i < active_nr) {
+ struct cache_entry *ce, *stages[4] = { NULL, };
+ struct cache_entry *one, *two;
+ const char *name;
+ int len;
+ ce = active_cache[i];
+ len = ce_namelen(ce);
+ name = ce->name;
+ for (;;) {
+ int stage = ce_stage(ce);
+ stages[stage] = ce;
+ if (active_nr <= ++i)
+ break;
+ ce = active_cache[i];
+ if (ce_namelen(ce) != len ||
+ memcmp(name, ce->name, len))
+ break;
+ }
+ one = stages[stage1];
+ two = stages[stage2];
+ if (!one && !two)
+ continue;
+ if (!one)
+ diff_addremove('+', ntohl(two->ce_mode),
+ two->sha1, name, NULL);
+ else if (!two)
+ diff_addremove('-', ntohl(one->ce_mode),
+ one->sha1, name, NULL);
+ else if (memcmp(one->sha1, two->sha1, 20) ||
+ (one->ce_mode != two->ce_mode))
+ diff_change(ntohl(one->ce_mode), ntohl(two->ce_mode),
+ one->sha1, two->sha1, name, NULL);
+ }
+
+ diffcore_std(av,
+ detect_rename, diff_score_opt,
+ pickaxe, pickaxe_opts,
+ diff_break_opt,
+ orderfile);
+ diff_flush(diff_output_format, 1);
+ return 0;
+}
------------
^ permalink raw reply
* Re: [PATCH] Add git-diff-stages command.
From: Linus Torvalds @ 2005-06-09 22:30 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <7vekbbb2me.fsf_-_@assigned-by-dhcp.cox.net>
On Thu, 9 Jun 2005, Junio C Hamano wrote:
>
> The diff-* brothers acquired a sibling, git-diff-stages. With
> an unmerged index file, you specify two stage numbers and it
> shows the differences between them.
I hate how you do one big "main()" function that does it all.
I'll apply the patch, but really, this is pretty ugly.
Linus
^ permalink raw reply
* [PATCH] read-tree.c: rename local variables used in 3-way merge code.
From: Junio C Hamano @ 2005-06-09 22:45 UTC (permalink / raw)
To: Linus Torvalds; +Cc: git
In-Reply-To: <Pine.LNX.4.58.0506091152530.2286@ppc970.osdl.org>
I'd hate to do this, but every time I try to touch this code and
validate what it does against the case matrix in t1000 test I
get confused. The variable names are renamed to match the case
matrix. Now they are named as:
i -- entry from the index file (formerly known as "old")
o -- merge base (formerly known as "a")
a -- our head (formerly known as "b")
b -- merge head (formerly known as "c")
Signed-off-by: Junio C Hamano <junkio@cox.net>
---
*** Re-submit. I've rebased the series from the last night.
*** This is the first of them.
read-tree.c | 40 ++++++++++++++++++++--------------------
1 files changed, 20 insertions(+), 20 deletions(-)
diff --git a/read-tree.c b/read-tree.c
--- a/read-tree.c
+++ b/read-tree.c
@@ -40,9 +40,9 @@ static int same(struct cache_entry *a, s
* This removes all trivial merges that don't change the tree
* and collapses them to state 0.
*/
-static struct cache_entry *merge_entries(struct cache_entry *a,
- struct cache_entry *b,
- struct cache_entry *c)
+static struct cache_entry *merge_entries(struct cache_entry *o,
+ struct cache_entry *a,
+ struct cache_entry *b)
{
/*
* Ok, all three entries describe the same
@@ -58,16 +58,16 @@ static struct cache_entry *merge_entries
* The "all entries exactly the same" case falls out as
* a special case of any of the "two same" cases.
*
- * Here "a" is "original", and "b" and "c" are the two
+ * Here "o" is "original", and "a" and "b" are the two
* trees we are merging.
*/
- if (a && b && c) {
- if (same(b,c))
- return c;
+ if (o && a && b) {
if (same(a,b))
- return c;
- if (same(a,c))
return b;
+ if (same(o,a))
+ return b;
+ if (same(o,b))
+ return a;
}
return NULL;
}
@@ -126,29 +126,29 @@ static int merged_entry(struct cache_ent
static int threeway_merge(struct cache_entry *stages[4], struct cache_entry **dst)
{
- struct cache_entry *old = stages[0];
- struct cache_entry *a = stages[1], *b = stages[2], *c = stages[3];
+ struct cache_entry *i = stages[0];
+ struct cache_entry *o = stages[1], *a = stages[2], *b = stages[3];
struct cache_entry *merge;
int count;
/*
- * If we have an entry in the index cache ("old"), then we want
+ * If we have an entry in the index cache ("i"), then we want
* to make sure that it matches any entries in stage 2 ("first
- * branch", aka "b").
+ * branch", aka "a").
*/
- if (old) {
- if (!b || !same(old, b))
+ if (i) {
+ if (!a || !same(i, a))
return -1;
}
- merge = merge_entries(a, b, c);
+ merge = merge_entries(o, a, b);
if (merge)
- return merged_entry(merge, old, dst);
- if (old)
- verify_uptodate(old);
+ return merged_entry(merge, i, dst);
+ if (i)
+ verify_uptodate(i);
count = 0;
+ if (o) { *dst++ = o; count++; }
if (a) { *dst++ = a; count++; }
if (b) { *dst++ = b; count++; }
- if (c) { *dst++ = c; count++; }
return count;
}
------------
^ permalink raw reply
* [PATCH] Handle entry removals during merge correctly.
From: Junio C Hamano @ 2005-06-09 22:47 UTC (permalink / raw)
To: Linus Torvalds; +Cc: git
In-Reply-To: <Pine.LNX.4.58.0506091152530.2286@ppc970.osdl.org>
From: Linus Torvalds <torvalds@osdl.org>
We could handle delete the same way - to set the ce_mode to zero
and add them to the "dst" array, and teach write-cache not to
write them out. Then the same loop that goes around doing the
CE_UPDATE thing could check for the delete case.
Signed-off-by: Junio C Hamano <junkio@cox.net>
---
*** Linus, did I get the patch submission format right? This is
*** essentially what you wrote (with one correction) but not
*** really "a forwarded e-mail".
read-cache.c | 10 ++++++++--
read-tree.c | 30 ++++++++++++++++++++----------
2 files changed, 28 insertions(+), 12 deletions(-)
diff --git a/read-cache.c b/read-cache.c
--- a/read-cache.c
+++ b/read-cache.c
@@ -440,11 +440,15 @@ int write_cache(int newfd, struct cache_
{
SHA_CTX c;
struct cache_header hdr;
- int i;
+ int i, removed;
+
+ for (i = removed = 0; i < entries; i++)
+ if (!cache[i]->ce_mode)
+ removed++;
hdr.hdr_signature = htonl(CACHE_SIGNATURE);
hdr.hdr_version = htonl(2);
- hdr.hdr_entries = htonl(entries);
+ hdr.hdr_entries = htonl(entries - removed);
SHA1_Init(&c);
if (ce_write(&c, newfd, &hdr, sizeof(hdr)) < 0)
@@ -452,6 +456,8 @@ int write_cache(int newfd, struct cache_
for (i = 0; i < entries; i++) {
struct cache_entry *ce = cache[i];
+ if (!ce->ce_mode)
+ continue;
if (ce_write(&c, newfd, ce, ce_size(ce)) < 0)
return -1;
}
diff --git a/read-tree.c b/read-tree.c
--- a/read-tree.c
+++ b/read-tree.c
@@ -124,6 +124,15 @@ static int merged_entry(struct cache_ent
return 1;
}
+static int deleted_entry(struct cache_entry *ce, struct cache_entry *old, struct cache_entry **dst)
+{
+ if (old)
+ verify_uptodate(old);
+ ce->ce_mode = 0;
+ *dst++ = ce;
+ return 1;
+}
+
static int threeway_merge(struct cache_entry *stages[4], struct cache_entry **dst)
{
struct cache_entry *i = stages[0];
@@ -181,25 +190,21 @@ static int twoway_merge(struct cache_ent
*dst++ = current;
return 1;
}
- else if (oldtree && !newtree && same(current, oldtree)) {
+ else if (oldtree && !newtree && same(current, oldtree))
/* 10 or 11 */
- verify_uptodate(current);
- return 0;
- }
+ return deleted_entry(oldtree, current, dst);
else if (oldtree && newtree &&
- same(current, oldtree) && !same(current, newtree)) {
+ same(current, oldtree) && !same(current, newtree))
/* 20 or 21 */
- verify_uptodate(current);
- return merged_entry(newtree, NULL, dst);
- }
+ return merged_entry(newtree, current, dst);
else
/* all other failures */
return -1;
}
else if (newtree)
- return merged_entry(newtree, NULL, dst);
+ return merged_entry(newtree, current, dst);
else
- return 0;
+ return deleted_entry(oldtree, current, dst);
}
/*
@@ -236,6 +241,11 @@ static void check_updates(struct cache_e
unsigned short mask = htons(CE_UPDATE);
while (nr--) {
struct cache_entry *ce = *src++;
+ if (!ce->ce_mode) {
+ if (update)
+ unlink(ce->name);
+ continue;
+ }
if (ce->ce_flags & mask) {
ce->ce_flags &= ~mask;
if (update)
------------
^ permalink raw reply
* [PATCH] read-tree -m 3-way: loosen an index requirement that was too strict.
From: Junio C Hamano @ 2005-06-09 22:48 UTC (permalink / raw)
To: Linus Torvalds; +Cc: git
In-Reply-To: <Pine.LNX.4.58.0506091152530.2286@ppc970.osdl.org>
This patch teaches "read-tree -m O A B" that, when only "the
other tree" changed a path, and if the work tree already has
that change, we are not in a situation that would clobber the
cache and the working tree, and lets the merge succeed; this is
case #14ALT in t1000 test. It does not change the result of the
merge, but prevents it from failing when it should not.
Signed-off-by: Junio C Hamano <junkio@cox.net>
---
*** Rebased one from the last night.
read-tree.c | 16 ++++++++++++++++
t/t1000-read-tree-m-3way.sh | 9 +++++++++
2 files changed, 25 insertions(+), 0 deletions(-)
diff --git a/read-tree.c b/read-tree.c
--- a/read-tree.c
+++ b/read-tree.c
@@ -140,6 +140,22 @@ static int threeway_merge(struct cache_e
struct cache_entry *merge;
int count;
+ /* The case #14ALT is special in that it allows "i" to match
+ * the "merged branch", aka "b" and even be dirty, as an
+ * alternative to the usual 'must match "a" and be up-to-date'
+ * rule.
+ */
+ if (o && a && b && same(o, a) && !same(o, b)) {
+ if (i) {
+ if (same(i, b))
+ ; /* case #14ALT exception */
+ else if (same(i, a))
+ verify_uptodate(i);
+ else
+ return -1;
+ }
+ }
+ else /* otherwise the original rule applies */
/*
* If we have an entry in the index cache ("i"), then we want
* to make sure that it matches any entries in stage 2 ("first
diff --git a/t/t1000-read-tree-m-3way.sh b/t/t1000-read-tree-m-3way.sh
--- a/t/t1000-read-tree-m-3way.sh
+++ b/t/t1000-read-tree-m-3way.sh
@@ -455,6 +455,15 @@ test_expect_success \
git-read-tree -m $tree_O $tree_A $tree_B &&
check_result"
+test_expect_success \
+ '14ALT - in O && A && B && O==A && O!=B case, matching B is also OK' \
+ "rm -f .git/index NM &&
+ cp .orig-B/NM NM &&
+ git-update-cache --add NM &&
+ echo extra >>NM &&
+ git-read-tree -m $tree_O $tree_A $tree_B &&
+ check_result"
+
test_expect_failure \
'14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' \
"rm -f .git/index NM &&
------------
^ permalink raw reply
* [PATCH] read-tree -m 3-way: handle more trivial merges internally.
From: Junio C Hamano @ 2005-06-09 22:49 UTC (permalink / raw)
To: Linus Torvalds; +Cc: git
In-Reply-To: <Pine.LNX.4.58.0506091152530.2286@ppc970.osdl.org>
This patch teaches "read-tree -m O A B" that some more trivial
cases can be handled internally. This allows us to loosen
otherwise too strict index requirements in case #5ALT, where
both branches create a new file identically. The previous
code required index to be up-to-date and aborted the merge when
it is not, but there is no reason to require it to be up-to-date
in this case; it only needs to match A.
The test vector has been updated to match the new behaviour.
Signed-off-by: Junio C Hamano <junkio@cox.net>
---
*** This has the "removal" fixes.
read-tree.c | 16 ++++++++++++++++
t/t1000-read-tree-m-3way.sh | 27 +++++++++------------------
2 files changed, 25 insertions(+), 18 deletions(-)
diff --git a/read-tree.c b/read-tree.c
--- a/read-tree.c
+++ b/read-tree.c
@@ -69,6 +69,12 @@ static struct cache_entry *merge_entries
if (same(o,b))
return a;
}
+ /* #5ALT */
+ if (!o && a && b && same(a,b)) {
+ /* Match what git-merge-one-file-script does */
+ printf("Adding %s\n", a->name);
+ return a;
+ }
return NULL;
}
@@ -170,6 +176,16 @@ static int threeway_merge(struct cache_e
return merged_entry(merge, i, dst);
if (i)
verify_uptodate(i);
+
+ /* #6ALT, #8ALT, and #10ALT */
+ if ((o && !a && !b) ||
+ (o && !a && b && same(o, b)) ||
+ (o && a && !b && same(o, a))) {
+ /* Match what git-merge-one-file-script does */
+ printf("Removing %s\n", o->name);
+ return deleted_entry(o, i, dst);
+ }
+
count = 0;
if (o) { *dst++ = o; count++; }
if (a) { *dst++ = a; count++; }
diff --git a/t/t1000-read-tree-m-3way.sh b/t/t1000-read-tree-m-3way.sh
--- a/t/t1000-read-tree-m-3way.sh
+++ b/t/t1000-read-tree-m-3way.sh
@@ -75,21 +75,18 @@ In addition:
. ../lib-read-tree-m-3way.sh
################################################################
-# This is the "no trivial merge unless all three exists" table.
+# Trivial "majority when 3 stages exist" merge plus #5ALT, #6ALT,
+# #8ALT, #10ALT trivial merges.
cat >expected <<\EOF
100644 X 2 AA
100644 X 3 AA
100644 X 2 AN
-100644 X 1 DD
100644 X 3 DF
100644 X 2 DF/DF
100644 X 1 DM
100644 X 3 DM
-100644 X 1 DN
-100644 X 3 DN
-100644 X 2 LL
-100644 X 3 LL
+100644 X 0 LL
100644 X 1 MD
100644 X 2 MD
100644 X 1 MM
@@ -97,8 +94,6 @@ cat >expected <<\EOF
100644 X 3 MM
100644 X 0 MN
100644 X 3 NA
-100644 X 1 ND
-100644 X 2 ND
100644 X 0 NM
100644 X 0 NN
100644 X 0 SS
@@ -108,11 +103,8 @@ cat >expected <<\EOF
100644 X 2 Z/AA
100644 X 3 Z/AA
100644 X 2 Z/AN
-100644 X 1 Z/DD
100644 X 1 Z/DM
100644 X 3 Z/DM
-100644 X 1 Z/DN
-100644 X 3 Z/DN
100644 X 1 Z/MD
100644 X 2 Z/MD
100644 X 1 Z/MM
@@ -120,8 +112,6 @@ cat >expected <<\EOF
100644 X 3 Z/MM
100644 X 0 Z/MN
100644 X 3 Z/NA
-100644 X 1 Z/ND
-100644 X 2 Z/ND
100644 X 0 Z/NM
100644 X 0 Z/NN
EOF
@@ -289,23 +279,24 @@ test_expect_failure \
git-read-tree -m $tree_O $tree_A $tree_B"
test_expect_success \
- '5 - must match and be up-to-date in !O && A && B && A==B case.' \
+ '5 - must match in !O && A && B && A==B case.' \
"rm -f .git/index LL &&
cp .orig-A/LL LL &&
git-update-cache --add LL &&
git-read-tree -m $tree_O $tree_A $tree_B &&
check_result"
-test_expect_failure \
- '5 (fail) - must match and be up-to-date in !O && A && B && A==B case.' \
+test_expect_success \
+ '5 - must match in !O && A && B && A==B case.' \
"rm -f .git/index LL &&
cp .orig-A/LL LL &&
git-update-cache --add LL &&
echo extra >>LL &&
- git-read-tree -m $tree_O $tree_A $tree_B"
+ git-read-tree -m $tree_O $tree_A $tree_B &&
+ check_result"
test_expect_failure \
- '5 (fail) - must match and be up-to-date in !O && A && B && A==B case.' \
+ '5 (fail) - must match in !O && A && B && A==B case.' \
"rm -f .git/index LL &&
cp .orig-A/LL LL &&
echo extra >>LL &&
------------
^ permalink raw reply
* Re: [PATCH] Add support for author-oriented git-rev-list switches [rev 9]
From: Jon Seymour @ 2005-06-09 23:10 UTC (permalink / raw)
To: git; +Cc: Linus Torvalds
In-Reply-To: <2cfc4032050609085341e46242@mail.gmail.com>
[rev 9]
* oops, missed some whitespace in my commit.c edits - begone,
whitespace, begone!
* slight change to semantics of --prune-at-author - now includes the
author's own commits
but prunes the parents of the author's own changes. This helps put the
cut points into some kind of context when the author has contributions
in multiple branches.
As before available from:
http://blackcubes.dyndns.org/epoch/wrt-patch-latest.patch
If anyone has objections to the changed semantics, let's discuss.
jon.
^ permalink raw reply
* Re: Lost uncommitted changes and cogito
From: Jon Smirl @ 2005-06-10 0:28 UTC (permalink / raw)
To: git
In-Reply-To: <9e473391050609122372080863@mail.gmail.com>
After experimenting for a while it looks like any cg update to a
branch that the repository wasn't initially cloned from will lose the
pending uncomitted changes.
--
Jon Smirl
jonsmirl@gmail.com
^ permalink raw reply
* [PATCH] Add support for author-oriented git-rev-list switches [rev 10]
From: Jon Seymour @ 2005-06-10 3:35 UTC (permalink / raw)
To: git; +Cc: jon.seymour
This patch is a complete replacement for the previous --wrt
and --wrt-author patches; it adds support for --wrt-author,
--prune-at-author and --author switches to git-rev-list.
These switches perform various sorting and filtering operations that
help any git user reconstruct a linear view of merge history from
the point of view of any given change author.
When --wrt-author is specified, --merge-order is implied but
the linearisation of non-linear epochs is adjusted so that branches
contributed to by the author sort after branches contributed to by
others. This has the effect of presenting a linear history of changes as seen
by the author's own workspace.
For example, given this commit history:
a4 ---
| \ \
| b4 |
|/ | |
a3 | |
| | |
a2 | |
| | c3
| | |
| | c2
| b3 |
| | /|
| b2 |
| | c1
| | /
| b1 <-- commit authored by author@domain
a1 |
| |
a0 |
| /
root
--merge-order alone would sort it as follows:
= a4
| c3
| c2
| c1
^ b4
| b3
| b2
| b1
^ a3
| a2
| a1
| a0
= root
however, with --wrt-author --author=author@domain, git-rev-list sorts as follows:
= a4
| c3
| c2
| c1
^ b4
| a3
| a2
| a1
| a0
^ b3
| b2
| b1
= root
This has the effect of showing the first 3 of author@domain's changes
occuring before any of the changes in other branches. This is the
order in which author@domain perceived change occurring - changes
a0->a3 weren't visible in author@domain's workspace until the merge at b4.
If --prune-at-author is specified, then git-rev-list behaves as if a
^argument had been added for each commit authored by the author.
The --prune-at-author option allows an author to see a brief summary of
changes since they last made a contribution to the commit graph. For example:
git-rev-list --prune-at-author --pretty HEAD
To configure which author git-rev-list uses when processing the --wrt-author
and --prune-at-author switches, use the --author=author@domain option. If this
option is not specified, git-rev-list uses GIT_AUTHOR_EMAIL or a value
derived from the local user, host and domain names
(per git-commit-tree's algorithm)
To support this change, some additional functions were added to commit.c to
allow the extraction of author from the commit header.
Also, --show-breaks, like --wrt-author now implies --merge-order rather than
requires it.
This change also updates Documentation/git-rev-list.txt to include
documentation for --wrt-author and other git-rev-list options not currently
documented.
A test case has been included in t/t6001-rev-list-merge-order.sh to
verify this change works as expected.
Use of --wrt-author implies a small overhead (linear with respect to nodes
printed) compared with --merge-order.
This change extracts real email id formatting functions from commit-tree.c
into user.c and user.h.
Signed-off-by: Jon Seymour <jon.seymour@gmail.com>
---
NOTE TO REVIEWERS: please pay special attention to get_real_identity()
which was extracted from commit-tree.c and placed into user.c. It was
essentially a cut and paste, but since this has the potential to affect the
operation of git-commit-tree, it would be worth being very careful about
checking this change.
[rev 1] * original patch
[rev 2-4] * various cleanups
[rev 5] * --merge-order bug fixes already merged with Linus.
[rev 6] * remerged with Linus' head (98a96b00b88ee35866cd0b1e94697db76bd5ddf9)
* changed name of --exclude-author switch to --prune-at-author
* reformatted to be more consistent with preferred coding style, namely:
* aggressive elimination of intra-block blank lines
except immediately after declarations
* declarations at top of block
* one statement blocks without braces
* everything except first line of function declaration
wrapped at <= 80 columns
* [ undone in rev 8 ] modified test cases to use /dev/urandom to get
some entropy for commit text so that closely spaced commits aren't identical
* added struct epoch_methods to gather 3 function pointers now used
by merge order logic
* added a comment to commit.c to indicate that epoch.c is assuming
that parse_commit stacks parents read from the commit header
rather than queues them
[rev 7] * further whitespace and style edits
[rev 8] * d'oh - don't need /dev/urandom at all - re-written and simplified
test cases
[rev 9] * oops, missed some whitespace in my commit.c edits - begone!
* slight change to semantics of --prune-at-author - includes the
author changes, but prunes the parents of the author. This helps put
the cut points into some kind of context when the author has contributions
in multiple branches.
[rev 10] * added a function to commit.c to make dependency on commit header order
explicit. now if parse_commit changes, any caller who uses copy_parents_in_header_order
won't need to change provided copy_parents_in_header_order is updated appropriately.
* simplified sort_first_epoch by putting termination tests near top
* changed sort_list_in_local_last_order to sort_list_in_local_first_order
and made the code more concise
* added some more tests to check behaviour on degenerate graphs on 1,2 or 3 nodes.
This revision [rev 10] reposted to the list because the changes since [rev 6], particularly in [rev 9, 10]
have been non-trivial.
Diverged from 98a96b00b88ee35866cd0b1e94697db76bd5ddf9 by Linus Torvalds <torvalds@ppc970.osdl.org>
diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt
--- a/Documentation/git-rev-list.txt
+++ b/Documentation/git-rev-list.txt
@@ -9,65 +9,114 @@ git-rev-list - Lists commit objects in r
SYNOPSIS
--------
-'git-rev-list' [ *--max-count*=number ] [ *--max-age*=timestamp ] [ *--min-age*=timestamp ] [ *--merge-order* [ *--show-breaks* ] ] <commit>
+'git-rev-list' [ *--max-count*=number ] [ *--max-age*=timestamp ]
+[ *--min-age*=timestamp ] [ *--pretty* ] [ *--parents* ] [ *--header* ]
+[ *--merge-order* ] [ *--show-breaks* ] [ *--wrt-author* ]
+[ *--prune-at-author* ] [ *--author=author@domain* ]
+<head-to-include> <head-to-include> ...
+^<base-to-prune>
+^<base-to-prune> ...
DESCRIPTION
-----------
-Lists commit objects in reverse chronological order starting at the
-given commit, taking ancestry relationship into account. This is
-useful to produce human-readable log output.
-
-If *--merge-order* is specified, the commit history is decomposed into a
-unique sequence of minimal, non-linear epochs and maximal, linear epochs.
-Non-linear epochs are then linearised by sorting them into merge order, which
+
+Lists commit objects in reverse chronological order starting at the commits
+specified by head-to-include values, taking ancestry relationship into account.
+git-rev-list will not print any commits that are reachable by any of the
+base-to-prune values.
+
+If *--max-count* is specified, the number of commits output is
+limited to the number specified.
+
+If *--max-age* is specified, commits older than the date specified
+(in seconds since Jan 1, 1970) are excluded from the output.
+
+If *--min-age* is specified, commits younger than the date specified
+(in seconds since Jan 1, 1970) are excluded from the output.
+
+If *--pretty* is specified, git-rev-list prints a short summary of each
+commit on stdout. This can be usefully parsed by git-shortlog to produce a
+short log of changes.
+
+If *--parents* is specified, git-rev-list prints a space separated list
+of parents on the same line as the commit identifier.
+
+If *--header* is specified, git-rev-list prints to stdout, on lines after
+the identifier of each commit, the contents of the commit's header followed by
+a trailing NUL.
+
+If *--merge-order* is specified, the commit history is decomposed into a
+unique sequence of minimal, non-linear epochs and maximal, linear epochs.
+Non-linear epochs are then linearised by sorting them into merge order, which
is described below.
-Maximal, linear epochs correspond to periods of sequential development.
-Minimal, non-linear epochs correspond to periods of divergent development
-followed by a converging merge. The theory of epochs is described in more
-detail at
+Maximal, linear epochs correspond to periods of sequential development.
+Minimal, non-linear epochs correspond to periods of divergent development
+followed by a converging merge. The theory of epochs is described in more
+detail at
link:http://blackcubes.dyndns.org/epoch/[http://blackcubes.dyndns.org/epoch/].
-The merge order for a non-linear epoch is defined as a linearisation for which
-the following invariants are true:
-
- 1. if a commit P is reachable from commit N, commit P sorts after commit N
- in the linearised list.
- 2. if Pi and Pj are any two parents of a merge M (with i < j), then any
- commit N, such that N is reachable from Pj but not reachable from Pi,
- sorts before all commits reachable from Pi.
-
-Invariant 1 states that later commits appear before earlier commits they are
-derived from.
+The merge order for a non-linear epoch is defined as a linearisation for
+which the following invariants are true:
-Invariant 2 states that commits unique to "later" parents in a merge, appear
-before all commits from "earlier" parents of a merge.
+ 1. if a commit P is reachable from commit N, commit P sorts after
+ commit N in the linearised list.
+ 2. if Pi and Pj are any two parents of a merge M (with i < j), then
+ any commit N, such that N is reachable from Pj but not reachable from
+ Pi, sorts before all commits reachable from Pi.
+
+Invariant 1 states that later commits appear before earlier commits
+they are derived from.
+
+Invariant 2 states that commits unique to "later" parents in a merge,
+appear before all commits from "earlier" parents of a merge.
+
+If *--show-breaks* is specified, each item of the list is output with a
+2-character prefix consisting of one of: (|), (^), (=) followed by a space.
+
+Commits marked with (=) represent the boundaries of minimal, non-linear
+epochs and correspond either to the start of a period of divergent development
+or to the end of such a period.
-If *--show-breaks* is specified, each item of the list is output with a
-2-character prefix consisting of one of: (|), (^), (=) followed by a space.
-
-Commits marked with (=) represent the boundaries of minimal, non-linear epochs
-and correspond either to the start of a period of divergent development or to
-the end of such a period.
-
-Commits marked with (|) are direct parents of commits immediately preceding
+Commits marked with (|) are direct parents of commits immediately preceding
the marked commit in the list.
-Commits marked with (^) are not parents of the immediately preceding commit.
-These "breaks" represent necessary discontinuities implied by trying to
+Commits marked with (^) are not parents of the immediately preceding commit.
+These "breaks" represent necessary discontinuities implied by trying to
represent an arbtirary DAG in a linear form.
-*--show-breaks* is only valid if *--merge-order* is also specified.
+If *--wrt-author* is specified, then git-rev-list sorts commits so that
+contemporaneously developed commits appear to occur after (that is, sort before)
+any commits authored by the author specified by *--author*. This reflects the
+order in which that author sees contemporaneously developed commits appear
+in her own workspace.
+
+If *--prune-at-author* is specified, then the parent of any commit
+authored by the author is marked as uninteresting as if it had been
+specified with ^ on the command line. This option restricts git-rev-list's
+output to just the changes including and following the last change
+contributed by any author in a given branch.
+
+The *--author* option is used to specify the author used by the
+*--wrt-author* and *--prune-at-author* switches. If an *--author* option is
+not specified, the author used is derived from GIT_AUTHOR_EMAIL or, ultimately,
+from the local user, host and domain names as per the logic used by
+git-commit-tree.
+
+Use of *--show-breaks*, *--prune-at-author* or *--wrt-author*
+implies *--merge-order*.
Author
------
Written by Linus Torvalds <torvalds@osdl.org>
-Original *--merge-order* logic by Jon Seymour <jon.seymour@gmail.com>
+Original *--merge-order*, *--wrt-author* and *--prune-at-author* logic
+by Jon Seymour <jon.seymour@gmail.com>
Documentation
--------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+Documentation by David Greaves, Junio C Hamano, Jon Seymour and
+the git-list <git@vger.kernel.org>.
GIT
---
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -42,9 +42,9 @@ install: $(PROG) $(SCRIPTS)
LIB_OBJS=read-cache.o sha1_file.o usage.o object.o commit.o tree.o blob.o \
tag.o delta.o date.o index.o diff-delta.o patch-delta.o entry.o \
- epoch.o refs.o
+ epoch.o refs.o user.o
LIB_FILE=libgit.a
-LIB_H=cache.h object.h blob.h tree.h commit.h tag.h delta.h epoch.h
+LIB_H=cache.h object.h blob.h tree.h commit.h tag.h delta.h epoch.h user.h
LIB_H += strbuf.h
LIB_OBJS += strbuf.o
@@ -140,6 +140,7 @@ diffcore-pickaxe.o : $(LIB_H) diffcore.h
diffcore-break.o : $(LIB_H) diffcore.h
diffcore-order.o : $(LIB_H) diffcore.h
epoch.o: $(LIB_H)
+user.o: $(LIB_H)
test: all
$(MAKE) -C t/ all
diff --git a/commit-tree.c b/commit-tree.c
--- a/commit-tree.c
+++ b/commit-tree.c
@@ -4,8 +4,8 @@
* Copyright (C) Linus Torvalds, 2005
*/
#include "cache.h"
+#include "user.h"
-#include <pwd.h>
#include <time.h>
#include <ctype.h>
@@ -100,17 +100,16 @@ static char *commit_tree_usage = "git-co
int main(int argc, char **argv)
{
- int i, len;
+ int i;
int parents = 0;
unsigned char tree_sha1[20];
unsigned char parent_sha1[MAXPARENT][20];
unsigned char commit_sha1[20];
char *gecos, *realgecos, *commitgecos;
- char *email, *commitemail, realemail[1000];
+ char *email, *commitemail, *realemail;
char date[50], realdate[50];
char *audate, *cmdate;
char comment[1000];
- struct passwd *pw;
char *buffer;
unsigned int size;
@@ -128,19 +127,7 @@ int main(int argc, char **argv)
}
if (!parents)
fprintf(stderr, "Committing initial tree %s\n", argv[1]);
- pw = getpwuid(getuid());
- if (!pw)
- die("You don't exist. Go away!");
- realgecos = pw->pw_gecos;
- len = strlen(pw->pw_name);
- memcpy(realemail, pw->pw_name, len);
- realemail[len] = '@';
- gethostname(realemail+len+1, sizeof(realemail)-len-1);
- if (!strchr(realemail+len+1, '.')) {
- strcat(realemail, ".");
- getdomainname(realemail+strlen(realemail), sizeof(realemail)-strlen(realemail)-1);
- }
-
+ get_real_identity(&realemail, &realgecos);
datestamp(realdate, sizeof(realdate));
strcpy(date, realdate);
diff --git a/commit.c b/commit.c
--- a/commit.c
+++ b/commit.c
@@ -76,6 +76,12 @@ int parse_commit_buffer(struct commit *i
!get_sha1_hex(bufptr + 7, parent)) {
struct commit *new_parent = lookup_commit(parent);
if (new_parent) {
+ /**
+ * note to maintainers -
+ * please read and modify copy_parents_in_header_order
+ * if this behaviour ever changes from stacking
+ * to queueing behaviour.
+ */
commit_list_insert(new_parent, &item->parents);
add_ref(&item->object, &new_parent->object);
}
@@ -302,3 +308,69 @@ int count_parents(struct commit * commit
return count;
}
+int copy_commit_header(struct commit * commit, char * header, int index, char * buffer, int len)
+{
+ char * p = commit->buffer;
+
+ while (*p != '\n') {
+ char * q = header;
+ int matched;
+
+ for (matched = 1; *p != ' ' && *p != '\n'; p++, q++) {
+ matched = matched && (*q==*p);
+ }
+ if (matched && index) {
+ /*
+ * if we matched but we haven't seen the
+ * index'th element yet, just decrement
+ * the index then pretend we didn't match
+ */
+ index--;
+ matched = 0;
+ }
+ if (!matched) {
+ /* skip to start of next header line */
+ for (;*p!='\n';p++)
+ ;
+ p++;
+ } else {
+ int count = 0; /* number of characters in value */
+ if (*p == ' ') {
+ p++;
+ count = 0;
+ while(*p != '\n') {
+ if (len > 0)
+ *buffer++=*p;
+ p++;
+ count++;
+ len--;
+ }
+ }
+ if (len > 0)
+ *buffer = 0;
+ return (len > 0) ? count+1 : -(count+1);
+ }
+ }
+ return 0;
+}
+
+int copy_author(struct commit * commit, char * buffer, int len)
+{
+ return copy_commit_header(commit, "author", 0, buffer, len);
+}
+
+/*
+ * Always produces a list of parents in the order they actually
+ * appear in the commit header. The correct implementation
+ * of this function depends on decisions made in parse_commit().
+ */
+struct commit_list * copy_parents_in_header_order(struct commit * commit)
+{
+ struct commit_list * parents = commit->parents;
+ struct commit_list * result = NULL;
+
+ for (parents = commit->parents;parents;parents = parents->next) {
+ commit_list_insert(parents->item, &result);
+ }
+ return result;
+}
diff --git a/commit.h b/commit.h
--- a/commit.h
+++ b/commit.h
@@ -53,4 +53,35 @@ struct commit *pop_most_recent_commit(st
struct commit *pop_commit(struct commit_list **stack);
int count_parents(struct commit * commit);
+
+/*
+ * Copies the value of the (index+1)'th commit header matching the name
+ * specified into the buffer supplied and append a trailing NUL.
+ *
+ * Returns n<0 if the buffer was too short, where -n is the required length.
+ *
+ * Returns 0 if the header doesn't exist.
+ *
+ * Returns n>0 where n is the length of the copied zero-terminated value,
+ * including the terminating zero.
+ */
+int copy_commit_header(struct commit * commit, char * header, int index, char * buffer, int len);
+
+/*
+ * Copies the commit's author value into the buffer supplied.
+ * Return values as per copy_commit_header.
+ */
+int copy_author(struct commit * commit, char * buffer, int len);
+
+/*
+ * Make a copy of the parents in header order. This is the
+ * reverse of the parse order. Callers who need guarantees
+ * about parents being in header order should use this
+ * call whose semantics will never change even if the
+ * implementation of parse_commit does.
+ *
+ * Callers are expected to destroy the copied list when
+ * they are finished with it.
+ */
+struct commit_list * copy_parents_in_header_order(struct commit * commit);
#endif /* COMMIT_H */
diff --git a/epoch.c b/epoch.c
--- a/epoch.c
+++ b/epoch.c
@@ -8,7 +8,6 @@
*
*/
#include <stdlib.h>
-
/* Provides arbitrary precision integers required to accurately represent
* fractional mass: */
#include <openssl/bn.h>
@@ -22,17 +21,33 @@ struct fraction {
BIGNUM denominator;
};
-#define HAS_EXACTLY_ONE_PARENT(n) ((n)->parents && !(n)->parents->next)
-
static BN_CTX *context = NULL;
static struct fraction *one = NULL;
static struct fraction *zero = NULL;
+#define HAS_EXACTLY_ONE_PARENT(n) ((n)->parents && !(n)->parents->next)
+#define IS_UNINTERESTING(c) ((c)->object.flags & UNINTERESTING)
+#define IS_VISITED(c) ((c)->object.flags & VISITED)
+#define IS_BASE(c) ((c)->object.flags & BASE)
+#define IS_LOCAL(c) ((c)->object.flags & LOCAL)
+#define IS_DISCONTINUITY(c) ((c)->object.flags & DISCONTINUITY)
+#define ASSERT(x,m,c) if (!(x)) { assertion_failed(__LINE__, __FUNCTION__, m, c); }
+
+static void assertion_failed(int line, char * function, char * message, struct commit * item)
+{
+ die( "%s:%d:%s: assertion_failed: %s: commit %s, flags %x",
+ __FILE__,
+ line,
+ function,
+ message,
+ item ? sha1_to_hex(item->object.sha1) : "[]",
+ item ? item->object.flags : 0xFFFFFFFF);
+}
+
static BN_CTX *get_BN_CTX()
{
- if (!context) {
+ if (!context)
context = BN_CTX_new();
- }
return context;
}
@@ -58,10 +73,10 @@ static struct fraction *divide(struct fr
BN_init(&bn_divisor);
BN_set_word(&bn_divisor, divisor);
-
BN_copy(&result->numerator, &fraction->numerator);
- BN_mul(&result->denominator, &fraction->denominator, &bn_divisor, get_BN_CTX());
-
+ BN_mul( &result->denominator,
+ &fraction->denominator,
+ &bn_divisor, get_BN_CTX());
BN_clear(&bn_divisor);
return result;
}
@@ -86,9 +101,8 @@ static struct fraction *get_one()
static struct fraction *get_zero()
{
- if (!zero) {
+ if (!zero)
zero = new_zero();
- }
return zero;
}
@@ -99,28 +113,44 @@ static struct fraction *copy(struct frac
return to;
}
-static struct fraction *add(struct fraction *result, struct fraction *left, struct fraction *right)
+static struct fraction *inc(struct fraction *left, struct fraction *right)
{
BIGNUM a, b, gcd;
BN_init(&a);
BN_init(&b);
BN_init(&gcd);
-
- BN_mul(&a, &left->numerator, &right->denominator, get_BN_CTX());
- BN_mul(&b, &left->denominator, &right->numerator, get_BN_CTX());
- BN_mul(&result->denominator, &left->denominator, &right->denominator, get_BN_CTX());
- BN_add(&result->numerator, &a, &b);
-
- BN_gcd(&gcd, &result->denominator, &result->numerator, get_BN_CTX());
- BN_div(&result->denominator, NULL, &result->denominator, &gcd, get_BN_CTX());
- BN_div(&result->numerator, NULL, &result->numerator, &gcd, get_BN_CTX());
-
+ BN_mul( &a,
+ &left->numerator,
+ &right->denominator,
+ get_BN_CTX());
+ BN_mul( &b,
+ &left->denominator,
+ &right->numerator,
+ get_BN_CTX());
+ BN_mul( &left->denominator,
+ &left->denominator,
+ &right->denominator,
+ get_BN_CTX());
+ BN_add( &left->numerator,
+ &a,
+ &b);
+ BN_gcd( &gcd,
+ &left->denominator,
+ &left->numerator,
+ get_BN_CTX());
+ BN_div( &left->denominator,
+ NULL, &left->denominator,
+ &gcd,
+ get_BN_CTX());
+ BN_div( &left->numerator,
+ NULL, &left->numerator,
+ &gcd,
+ get_BN_CTX());
BN_clear(&a);
BN_clear(&b);
BN_clear(&gcd);
-
- return result;
+ return left;
}
static int compare(struct fraction *left, struct fraction *right)
@@ -130,15 +160,11 @@ static int compare(struct fraction *left
BN_init(&a);
BN_init(&b);
-
BN_mul(&a, &left->numerator, &right->denominator, get_BN_CTX());
BN_mul(&b, &left->denominator, &right->numerator, get_BN_CTX());
-
result = BN_cmp(&a, &b);
-
BN_clear(&a);
BN_clear(&b);
-
return result;
}
@@ -150,21 +176,15 @@ struct mass_counter {
static struct mass_counter *new_mass_counter(struct commit *commit, struct fraction *pending)
{
struct mass_counter *mass_counter = xmalloc(sizeof(*mass_counter));
- memset(mass_counter, 0, sizeof(*mass_counter));
+ ASSERT(!commit->object.util, "not already initialized", commit);
+
+ memset(mass_counter, 0, sizeof(*mass_counter));
init_fraction(&mass_counter->seen);
init_fraction(&mass_counter->pending);
-
copy(&mass_counter->pending, pending);
copy(&mass_counter->seen, get_zero());
-
- if (commit->object.util) {
- die("multiple attempts to initialize mass counter for %s",
- sha1_to_hex(commit->object.sha1));
- }
-
commit->object.util = mass_counter;
-
return mass_counter;
}
@@ -218,73 +238,63 @@ static int find_base_for_list(struct com
struct commit_list *cleaner = NULL;
struct commit_list *pending = NULL;
struct fraction injected;
- init_fraction(&injected);
- *boundary = NULL;
+ *boundary = NULL;
+ init_fraction(&injected);
for (; list; list = list->next) {
struct commit *item = list->item;
- if (item->object.util) {
- die("%s:%d:%s: logic error: this should not have happened - commit %s",
- __FILE__, __LINE__, __FUNCTION__,
- sha1_to_hex(item->object.sha1));
- }
-
+ ASSERT(!item->object.util, "no duplicates in list", item);
new_mass_counter(list->item, get_one());
- add(&injected, &injected, get_one());
-
+ inc(&injected, get_one());
commit_list_insert(list->item, &cleaner);
commit_list_insert(list->item, &pending);
}
-
while (!*boundary && pending && !ret) {
struct commit *latest = pop_commit(&pending);
- struct mass_counter *latest_node = (struct mass_counter *) latest->object.util;
+ struct mass_counter *latest_node;
int num_parents;
-
- if ((ret = parse_commit(latest)))
+
+ ret = parse_commit(latest);
+ if (ret)
continue;
- add(&latest_node->seen, &latest_node->seen, &latest_node->pending);
-
+ latest_node = (struct mass_counter *) latest->object.util;
+ inc(&latest_node->seen, &latest_node->pending);
num_parents = count_parents(latest);
if (num_parents) {
- struct fraction distribution;
- struct commit_list *parents;
-
- divide(init_fraction(&distribution), &latest_node->pending, num_parents);
+ struct fraction div;
+ struct commit_list *parents = latest->parents;
- for (parents = latest->parents; parents; parents = parents->next) {
- struct commit *parent = parents->item;
- struct mass_counter *parent_node = (struct mass_counter *) parent->object.util;
-
- if (!parent_node) {
- parent_node = new_mass_counter(parent, &distribution);
- insert_by_date(&pending, parent);
- commit_list_insert(parent, &cleaner);
- } else {
- if (!compare(&parent_node->pending, get_zero()))
- insert_by_date(&pending, parent);
- add(&parent_node->pending, &parent_node->pending, &distribution);
- }
+ divide( init_fraction(&div),
+ &latest_node->pending,
+ num_parents);
+ while (parents) {
+ struct mass_counter * pmc;
+ struct commit *pc = parents->item;
+
+ pmc = (struct mass_counter *) pc->object.util;
+ if (!pmc) {
+ pmc = new_mass_counter(pc, get_zero());
+ commit_list_insert(pc, &cleaner);
+ }
+ if (!compare(&pmc->pending, get_zero()))
+ insert_by_date(&pending, pc);
+ inc(&pmc->pending, &div);
+ parents = parents->next;
}
-
- clear_fraction(&distribution);
+ clear_fraction(&div);
}
-
if (!compare(&latest_node->seen, &injected))
*boundary = latest;
copy(&latest_node->pending, get_zero());
}
-
while (cleaner) {
struct commit *next = pop_commit(&cleaner);
free_mass_counter((struct mass_counter *) next->object.util);
next->object.util = NULL;
}
-
if (pending)
free_commit_list(pending);
-
clear_fraction(&injected);
return ret;
}
@@ -305,7 +315,6 @@ static int find_base(struct commit *head
}
ret = find_base_for_list(pending, boundary);
free_commit_list(pending);
-
return ret;
}
@@ -313,19 +322,15 @@ static int find_base(struct commit *head
* This procedure traverses to the boundary of the first epoch in the epoch
* sequence of the epoch headed at head_of_epoch. This is either the end of
* the maximal linear epoch or the base of a minimal non-linear epoch.
- *
- * The queue of pending nodes is sorted in reverse date order and each node
- * is currently in the queue at most once.
*/
static int find_next_epoch_boundary(struct commit *head_of_epoch, struct commit **boundary)
{
- int ret;
struct commit *item = head_of_epoch;
+ int ret;
ret = parse_commit(item);
if (ret)
return ret;
-
if (HAS_EXACTLY_ONE_PARENT(item)) {
/*
* We are at the start of a maximimal linear epoch.
@@ -336,7 +341,6 @@ static int find_next_epoch_boundary(stru
ret = parse_commit(item);
}
*boundary = item;
-
} else {
/*
* Otherwise, we are at the start of a minimal, non-linear
@@ -344,7 +348,6 @@ static int find_next_epoch_boundary(stru
*/
ret = find_base(item, boundary);
}
-
return ret;
}
@@ -354,34 +357,19 @@ static int find_next_epoch_boundary(stru
static int is_parent_of(struct commit *parent, struct commit *child)
{
struct commit_list *parents;
- for (parents = child->parents; parents; parents = parents->next) {
- if (!memcmp(parent->object.sha1, parents->item->object.sha1,
- sizeof(parents->item->object.sha1)))
+
+ for (parents = child->parents; parents; parents = parents->next)
+ if (!memcmp(parent->object.sha1,
+ parents->item->object.sha1,
+ sizeof(parents->item->object.sha1)))
return 1;
- }
return 0;
}
/*
- * Pushes an item onto the merge order stack. If the top of the stack is
- * marked as being a possible "break", we check to see whether it actually
- * is a break.
- */
-static void push_onto_merge_order_stack(struct commit_list **stack, struct commit *item)
-{
- struct commit_list *top = *stack;
- if (top && (top->item->object.flags & DISCONTINUITY)) {
- if (is_parent_of(top->item, item)) {
- top->item->object.flags &= ~DISCONTINUITY;
- }
- }
- commit_list_insert(item, stack);
-}
-
-/*
* Marks all interesting, visited commits reachable from this commit
* as uninteresting. We stop recursing when we reach the epoch boundary,
- * an unvisited node or a node that has already been marking uninteresting.
+ * an unvisited node or a node that has already been marked uninteresting.
*
* This doesn't actually mark all ancestors between the start node and the
* epoch boundary uninteresting, but does ensure that they will eventually
@@ -397,7 +385,6 @@ static void mark_ancestors_uninteresting
struct commit_list *next;
commit->object.flags |= UNINTERESTING;
-
/*
* We only need to recurse if
* we are not on the boundary and
@@ -413,95 +400,89 @@ static void mark_ancestors_uninteresting
* uninteresting or will be once the sort_first_epoch
* traverse reaches them.
*/
-
if (uninteresting || boundary || !visited)
return;
-
for (next = commit->parents; next; next = next->next)
mark_ancestors_uninteresting(next->item);
}
/*
- * Sorts the nodes of the first epoch of the epoch sequence of the epoch headed at head
- * into merge order.
+ * Sorts the nodes of the first epoch of the epoch sequence of the epoch
+ * headed at head into merge order.
*/
-static void sort_first_epoch(struct commit *head, struct commit_list **stack)
+static void sort_first_epoch(struct commit *head, struct commit_list **stack, struct epoch_methods * methods)
{
- struct commit_list *parents;
- struct commit_list *reversed_parents = NULL;
+ struct commit_list *reversed = NULL;
+ struct commit * top = *stack?(*stack)->item:NULL;
+ if (IS_VISITED(head))
+ return;
head->object.flags |= VISITED;
/*
- * parse_commit() builds the parent list in reverse order with respect
- * to the order of the git-commit-tree arguments. So we need to reverse
- * this list to output the oldest (or most "local") commits last.
- */
- for (parents = head->parents; parents; parents = parents->next)
- commit_list_insert(parents->item, &reversed_parents);
-
- /*
- * TODO: By sorting the parents in a different order, we can alter the
- * merge order to show contemporaneous changes in parallel branches
- * occurring after "local" changes. This is useful for a developer
- * when a developer wants to see all changes that were incorporated
- * into the same merge as her own changes occur after her own
- * changes.
- */
-
- while (reversed_parents) {
- struct commit *parent = pop_commit(&reversed_parents);
+ * give the sort tactics a chance to mark this commit uninteresting
+ */
+ if (methods->marker)
+ (*(methods->marker)) (head);
- if (head->object.flags & UNINTERESTING) {
+ if (IS_BASE(head)) {
+ ASSERT(!top, "stack empty on visit to base", head);
+ } else {
+ /*
+ * Reverse the parents so that "local" parents are pushed first
+ * and hence print last.
+ */
+ if (!head->object.util) {
/*
- * Propagates the uninteresting bit to all parents.
- * if we have already visited this parent, then
- * the uninteresting bit will be propagated to each
- * reachable commit that is still not marked
- * uninteresting and won't otherwise be reached.
+ * make a copy of the parents, but don't destroy them
*/
- mark_ancestors_uninteresting(parent);
+ reversed = copy_parents_in_header_order(head);
+ } else {
+ /*
+ * sort_list_in_local_first_order already does this
+ */
+ reversed = (struct commit_list *)head->object.util;
+ head->object.util = NULL;
}
+ while (reversed) {
+ struct commit *parent = pop_commit(&reversed);
- if (!(parent->object.flags & VISITED)) {
- if (parent->object.flags & BOUNDARY) {
- if (*stack) {
- die("something else is on the stack - %s",
- sha1_to_hex((*stack)->item->object.sha1));
- }
- push_onto_merge_order_stack(stack, parent);
- parent->object.flags |= VISITED;
-
- } else {
- sort_first_epoch(parent, stack);
- if (reversed_parents) {
- /*
- * This indicates a possible
- * discontinuity it may not be be
- * actual discontinuity if the head
- * of parent N happens to be the tail
- * of parent N+1.
- *
- * The next push onto the stack will
- * resolve the question.
- */
- (*stack)->item->object.flags |= DISCONTINUITY;
- }
+ if (IS_UNINTERESTING(head)) {
+ /*
+ * Propagates the uninteresting bit to all parents.
+ * If we have already visited this parent, then
+ * the uninteresting bit will be propagated to each
+ * reachable commit that is still not marked
+ * uninteresting and won't otherwise be reached by
+ * this traversal.
+ */
+ mark_ancestors_uninteresting(parent);
+ }
+ sort_first_epoch(parent, stack, methods);
+ top=(*stack)?(*stack)->item:NULL;
+ if (reversed) {
+ /*
+ * This indicates a possible discontinuity it
+ * may not be be actual discontinuity if the head
+ * of parent N happens to be the tail of
+ * parent N+1. The next push onto the stack will
+ * resolve the question.
+ */
+ top->object.flags |= DISCONTINUITY;
}
}
- }
-
- push_onto_merge_order_stack(stack, head);
+ }
+ if (top && IS_DISCONTINUITY(top) && is_parent_of(top, head))
+ top->object.flags &= ~DISCONTINUITY;
+ commit_list_insert(head, stack);
}
/*
* Emit the contents of the stack.
- *
* The stack is freed and replaced by NULL.
- *
* Sets the return value to STOP if no further output should be generated.
*/
-static int emit_stack(struct commit_list **stack, emitter_func emitter)
+static int emit_stack(struct commit_list **stack, struct epoch_methods * methods)
{
unsigned int seen = 0;
int action = CONTINUE;
@@ -510,15 +491,98 @@ static int emit_stack(struct commit_list
struct commit *next = pop_commit(stack);
seen |= next->object.flags;
if (*stack)
- action = (*emitter) (next);
+ action = (*(methods->emitter)) (next);
}
-
if (*stack) {
free_commit_list(*stack);
*stack = NULL;
+ }
+ if (seen & UNINTERESTING) {
+ /**
+ * We stop at the base of the stack, rather than
+ * when we encounter the first UNINTERESTING flag.
+ *
+ * The reason is that there may still be interesting
+ * stuff on the stack but once we reach the base
+ * there can be no more interesting stuff by definition
+ * of what the base of an epoch is - everything reachable
+ * from the base is also reachable from the UNINTERESTING
+ * node and hence is uninteresting.
+ */
+ action = STOP;
}
+ return action;
+}
- return (action == STOP || (seen & UNINTERESTING)) ? STOP : CONTINUE;
+/*
+ * Copy the parents of the commit and store the head of the list
+ * in object.util. Return the address of object.util itself.
+ */
+static struct commit_list ** copy_and_store_parents(struct commit * commit)
+{
+ struct commit_list * copied=copy_parents_in_header_order(commit);
+
+ commit->object.util = copied;
+ return (struct commit_list **)&commit->object.util;
+}
+
+/*
+ * Sort a list of commits in local first order. A commit is "local"
+ * if any of its ancestors (except the base) causes (*local_test)() to
+ * return a non-zero value.
+ *
+ * The sorted list is returned in *sorted. A side effect of this function
+ * is to set each object.util pointer in each ancestor up until the base
+ * to a list of parents is sorted in local first order.
+ *
+ * Does nothing if the list is empty.
+ *
+ * The return value contains LOCAL if any of the list is local or had a
+ * local ancestor.
+ */
+static unsigned int sort_list_in_local_first_order(struct commit_list ** list, struct epoch_methods * methods)
+{
+ struct commit_list *local = NULL;
+ struct commit_list **local_tail = &local;
+ struct commit_list **non_local_ptr = list;
+
+ ASSERT(methods->local_test, "only called if there is a local test", NULL);
+ if (!*list) {
+ return 0;
+ }
+ for (non_local_ptr = list; *non_local_ptr; ) {
+ struct commit *item = (*non_local_ptr)->item;
+ struct commit_list ** copied;
+
+ /*
+ * We don't descend past the base or visit
+ * a commit we have already visited.
+ */
+ if (!IS_BASE(item) && !item->object.util) {
+ if ((*(methods->local_test)) (item))
+ item->object.flags |= LOCAL;
+ copied=copy_and_store_parents(item);
+ item->object.flags |= sort_list_in_local_first_order(copied, methods);
+ }
+ /**
+ * Move any local item onto the local list.
+ */
+ if (IS_LOCAL(item)) {
+ *local_tail = *non_local_ptr;
+ *non_local_ptr = (*non_local_ptr)->next;
+ (*local_tail)->next = NULL;
+ local_tail = &(*local_tail)->next;
+ } else {
+ non_local_ptr = &(*non_local_ptr)->next;
+ }
+ }
+ /*
+ * Splice the non-local list onto the end and
+ * return with the LOCAL flag from the first item.
+ */
+ *local_tail = *list;
+ *list = local;
+ return ((*list)->item->object.flags & LOCAL);
}
/*
@@ -528,51 +592,53 @@ static int emit_stack(struct commit_list
* Note: this algorithm currently leaves traces of its execution in the
* object flags of nodes it discovers. This should probably be fixed.
*/
-static int sort_in_merge_order(struct commit *head_of_epoch, emitter_func emitter)
+static int sort_in_merge_order(struct commit *head_of_epoch, struct epoch_methods * methods)
{
struct commit *next = head_of_epoch;
int ret = 0;
int action = CONTINUE;
- ret = parse_commit(head_of_epoch);
-
+ ret = parse_commit(next);
+ next->object.flags |= BOUNDARY;
while (next && next->parents && !ret && (action != STOP)) {
struct commit *base = NULL;
ret = find_next_epoch_boundary(next, &base);
if (ret)
return ret;
- next->object.flags |= BOUNDARY;
+ next->object.flags &= ~(BASE|VISITED);
if (base)
- base->object.flags |= BOUNDARY;
-
+ base->object.flags |= (BOUNDARY|BASE);
if (HAS_EXACTLY_ONE_PARENT(next)) {
while (HAS_EXACTLY_ONE_PARENT(next)
&& (action != STOP)
&& !ret) {
- if (next->object.flags & UNINTERESTING) {
+ if (methods->marker)
+ (*(methods->marker)) (next);
+
+ if (IS_UNINTERESTING(next))
action = STOP;
- } else {
- action = (*emitter) (next);
- }
+ else
+ action = (*(methods->emitter)) (next);
+
if (action != STOP) {
next = next->parents->item;
ret = parse_commit(next);
}
}
-
} else {
struct commit_list *stack = NULL;
- sort_first_epoch(next, &stack);
- action = emit_stack(&stack, emitter);
+ if (methods->local_test)
+ sort_list_in_local_first_order(
+ copy_and_store_parents(next),
+ methods);
+ sort_first_epoch(next, &stack, methods);
+ action = emit_stack(&stack, methods);
next = base;
}
}
-
- if (next && (action != STOP) && !ret) {
- (*emitter) (next);
- }
-
+ if (next && (action != STOP) && !ret)
+ (*(methods->emitter))(next);
return ret;
}
@@ -582,68 +648,65 @@ static int sort_in_merge_order(struct co
* in this subgraph using the sort_first_epoch algorithm. Once we have
* reached the base we can continue sorting using sort_in_merge_order.
*/
-int sort_list_in_merge_order(struct commit_list *list, emitter_func emitter)
+int sort_list_in_merge_order(struct commit_list *list, struct epoch_methods * methods)
{
struct commit_list *stack = NULL;
+ struct commit_list *filtered = NULL;
struct commit *base;
int ret = 0;
int action = CONTINUE;
- struct commit_list *reversed = NULL;
+ if (!methods)
+ die("methods argument must not be null");
+ if (!methods->emitter)
+ die("an emitter method must be supplied");
+ /**
+ * Filter the list by removing all uninteresting
+ * and duplicate items. Removing duplicates
+ * prevents an assertion failure in find_base_for_list
+ */
for (; list; list = list->next) {
struct commit *next = list->item;
- if (!(next->object.flags & UNINTERESTING)) {
- if (next->object.flags & DUPCHECK) {
- fprintf(stderr, "%s: duplicate commit %s ignored\n",
- __FUNCTION__, sha1_to_hex(next->object.sha1));
- } else {
- next->object.flags |= DUPCHECK;
- commit_list_insert(list->item, &reversed);
- }
+ if (!(next->object.flags & (UNINTERESTING|DUPCHECK))) {
+ next->object.flags |= DUPCHECK;
+ commit_list_insert(list->item, &filtered);
}
}
-
- if (!reversed->next) {
- /*
- * If there is only one element in the list, we can sort it
- * using sort_in_merge_order.
- */
- base = reversed->item;
- } else {
+ /**
+ * Find the common base of the listed commits and
+ * sort up until that base.
+ */
+ if (!filtered->next)
+ base = filtered->item; /* sort with sort_in_merge_order */
+ else {
/*
* Otherwise, we search for the base of the list.
- */
- ret = find_base_for_list(reversed, &base);
+ */
+ ret = find_base_for_list(filtered, &base);
if (ret)
return ret;
if (base)
- base->object.flags |= BOUNDARY;
-
- while (reversed) {
- sort_first_epoch(pop_commit(&reversed), &stack);
- if (reversed) {
- /*
- * If we have more commits to push, then the
- * first push for the next parent may (or may
- * not) represent a discontinuity with respect
- * to the parent currently on the top of
- * the stack.
- *
- * Mark it for checking here, and check it
- * with the next push. See sort_first_epoch()
- * for more details.
- */
+ base->object.flags |= (BOUNDARY|BASE);
+ if (methods->local_test)
+ sort_list_in_local_first_order(&filtered, methods);
+ while (filtered) {
+ sort_first_epoch(pop_commit(&filtered), &stack, methods);
+ /* per discontintuity logic in sort_first_epoch */
+ if (filtered)
stack->item->object.flags |= DISCONTINUITY;
- }
}
-
- action = emit_stack(&stack, emitter);
- }
-
- if (base && (action != STOP)) {
- ret = sort_in_merge_order(base, emitter);
+ action = emit_stack(&stack, methods);
}
-
+ /**
+ * Sort the rest with the sort_in_merge_order algorithm.
+ */
+ if (base && (action != STOP))
+ ret = sort_in_merge_order(base, methods);
return ret;
}
+
+void init_epoch_methods(struct epoch_methods * methods)
+{
+ memset(methods, 0, sizeof(*methods));
+}
diff --git a/epoch.h b/epoch.h
--- a/epoch.h
+++ b/epoch.h
@@ -1,20 +1,56 @@
#ifndef EPOCH_H
#define EPOCH_H
-
-// return codes for emitter_func
-#define STOP 0
-#define CONTINUE 1
-#define DO 2
-typedef int (*emitter_func) (struct commit *);
-
-int sort_list_in_merge_order(struct commit_list *list, emitter_func emitter);
-
+/**
+ * Flags used by merge order logic and also by rev-list.c
+ */
#define UNINTERESTING (1u<<2)
#define BOUNDARY (1u<<3)
#define VISITED (1u<<4)
#define DISCONTINUITY (1u<<5)
#define DUPCHECK (1u<<6)
+#define LOCAL (1u<<7)
+#define BASE (1u<<8)
+/**
+ * Return codes for emitter method. Also used by rev-list.c
+ */
+#define STOP 0
+#define CONTINUE 1
+#define DO 2
-#endif /* EPOCH_H */
+struct epoch_methods {
+ /*
+ * Returns 0 if traversal should stop, non-zero if it should continue.
+ */
+ int (*emitter)(struct commit *);
+ /*
+ * Returns non-zero if the commit is regarded "local", 0 otherwise.
+ */
+ int (*local_test)(struct commit *);
+ /*
+ * Implementers may use this method to mark commits uninteresting
+ * according to some locally determined criteria. The tree
+ * will be pruned at any commit so marked.
+ */
+ void (*marker)(struct commit *);
+};
+
+/**
+ * Initializes an epoch_methods structure which
+ * may be customized by the caller by overriding any of the method pointers.
+ */
+extern void init_epoch_methods(struct epoch_methods *);
+
+/**
+ * Sorts the list of commits in merge order, using the methods specified
+ * to customize the tactics of the search.
+ *
+ * The prune points should be marked with the UNINTERESTING flags.
+ *
+ * Note: this algorithm is dirty in the sense that it leaves traces
+ * of its execution in the object.flags word of some or all of the commits
+ * visited.
+ */
+extern int sort_list_in_merge_order(struct commit_list *list, struct epoch_methods * methods);
+#endif /* EPOCH_H */
diff --git a/rev-list.c b/rev-list.c
--- a/rev-list.c
+++ b/rev-list.c
@@ -1,18 +1,24 @@
#include "cache.h"
#include "commit.h"
#include "epoch.h"
+#include "user.h"
#define SEEN (1u << 0)
#define INTERESTING (1u << 1)
static const char rev_list_usage[] =
"usage: git-rev-list [OPTION] commit-id <commit-id>\n"
- " --max-count=nr\n"
- " --max-age=epoch\n"
- " --min-age=epoch\n"
- " --header\n"
- " --pretty\n"
- " --merge-order [ --show-breaks ]";
+ " --max-count=nr\n"
+ " --max-age=epoch\n"
+ " --min-age=epoch\n"
+ " --header\n"
+ " --parents\n"
+ " --pretty\n"
+ " --merge-order\n"
+ " --wrt-author\n"
+ " --prune-at-author\n"
+ " --author=author@domain\n"
+ " --show-breaks ]";
static int verbose_header = 0;
static int show_parents = 0;
@@ -24,6 +30,9 @@ static int max_count = -1;
static enum cmit_fmt commit_format = CMIT_FMT_RAW;
static int merge_order = 0;
static int show_breaks = 0;
+static char * local_author = NULL;
+static int prune_at_author = 0;
+static int wrt_author = 0;
static void show_commit(struct commit *commit)
{
@@ -147,6 +156,26 @@ static enum cmit_fmt get_commit_format(c
usage(rev_list_usage);
}
+static int is_local_author(struct commit * commit)
+{
+ static char author[16384];
+ if (copy_author(commit, author, sizeof(author)) > 0) {
+ if (strstr(author, local_author)) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static void mark_authors_own_uninteresting(struct commit * commit)
+{
+ if (is_local_author(commit)) {
+ struct commit_list * parents = commit->parents;
+ for (;parents;parents=parents->next) {
+ parents->item->object.flags |= UNINTERESTING;
+ }
+ }
+}
int main(int argc, char **argv)
{
@@ -186,14 +215,26 @@ int main(int argc, char **argv)
show_parents = 1;
continue;
}
- if (!strncmp(arg, "--merge-order", 13)) {
+ if (!strcmp(arg, "--merge-order")) {
merge_order = 1;
continue;
}
- if (!strncmp(arg, "--show-breaks", 13)) {
+ if (!strcmp(arg, "--show-breaks")) {
show_breaks = 1;
continue;
}
+ if (!strncmp(arg, "--author=", 9)) {
+ local_author=strdup(&arg[9]);
+ continue;
+ }
+ if (!strcmp(arg, "--wrt-author")) {
+ wrt_author = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--prune-at-author")) {
+ prune_at_author = 1;
+ continue;
+ }
flags = 0;
if (*arg == '^') {
@@ -201,7 +242,7 @@ int main(int argc, char **argv)
arg++;
limited = 1;
}
- if (get_sha1(arg, sha1) || (show_breaks && !merge_order))
+ if (get_sha1(arg, sha1))
usage(rev_list_usage);
commit = lookup_commit_reference(sha1);
if (!commit || parse_commit(commit) < 0)
@@ -213,14 +254,38 @@ int main(int argc, char **argv)
if (!list)
usage(rev_list_usage);
+ if (!merge_order)
+ merge_order = wrt_author || prune_at_author || show_breaks;
+
if (!merge_order) {
if (limited)
list = limit_list(list);
show_commit_list(list);
- } else {
- if (sort_list_in_merge_order(list, &process_commit)) {
- die("merge order sort failed\n");
- }
+ } else {
+ struct epoch_methods methods;
+
+ init_epoch_methods(&methods);
+ if ((prune_at_author|wrt_author) && !local_author) {
+ local_author = gitenv("GIT_AUTHOR_EMAIL") ? : NULL;
+ if (!local_author)
+ get_real_identity(&local_author, NULL);
+ else
+ local_author = strdup(local_author);
+ }
+ if (local_author) {
+ /* add delimiters to improve accuracy of match */
+ char * tmp=xmalloc(strlen(local_author)+3);
+ sprintf(tmp, "<%s>", local_author);
+ free(local_author);
+ local_author = tmp;
+ }
+ methods.emitter = &process_commit;
+ if (wrt_author)
+ methods.local_test = &is_local_author;
+ if (prune_at_author)
+ methods.marker = &mark_authors_own_uninteresting;
+ if (sort_list_in_merge_order(list, &methods))
+ die("merge order sort failed\n");
}
return 0;
diff --git a/t/t6001-rev-list-merge-order.sh b/t/t6001-rev-list-merge-order.sh
old mode 100644
new mode 100755
--- a/t/t6001-rev-list-merge-order.sh
+++ b/t/t6001-rev-list-merge-order.sh
@@ -6,10 +6,53 @@
test_description='Test rev-list --merge-order
'
. ./test-lib.sh
+[ -d .git/refs/tags ] || mkdir -p .git/refs/tags
-function do_commit
+sed_script="";
+
+function tag
+{
+ local _tag=$1
+ [ -f .git/refs/tags/$_tag ] || error "tag: \"$_tag\" does not exist"
+ cat .git/refs/tags/$_tag
+}
+
+function commit
+{
+ local _tag=$1
+ local _tree=$2
+ shift 2
+ echo $_tag | git-commit-tree $(tag $_tree) "$@"
+}
+
+function save_tag
{
- git-commit-tree "$@" </dev/null
+ local _tag=$1
+ [ -n "$_tag" ] || error "usage: do_commit tag commit-args ..."
+ shift 1
+ "$@" >.git/refs/tags/$_tag
+ sed_script="s/$(tag $_tag)/$_tag/g${sed_script+;}$sed_script"
+}
+
+function entag
+{
+ sed "$sed_script"
+}
+
+function as_author
+{
+ local _author=$1
+ shift 1
+ local _save=$GIT_AUTHOR_EMAIL
+
+ export GIT_AUTHOR_EMAIL="$_author"
+ "$@"
+ export GIT_AUTHOR_EMAIL="$_save"
+}
+
+function hide_error
+{
+ "$@" 2>/dev/null
}
function check_adjacency
@@ -28,43 +71,97 @@ function check_adjacency
done
}
-function sed_script
+function check_output
{
- for c in root a0 a1 a2 a3 a4 b1 b2 b3 b4 c1 c2 c3 l0 l1 l2 l3 l4 l5
- do
- echo -n "s/${!c}/$c/;"
- done
+ local _name=$1
+ shift 1
+ if "$@" | entag > $_name.actual
+ then
+ diff $_name.expected $_name.actual
+ else
+ return 1;
+ fi
+
+}
+
+function name_from_description
+{
+ tr "'" '.' | tr '~`!@#$%^&*()_+={}[]|\;:"<>,/?-' '.' | tr -s '.' | tr ' ' '-' | tr '[A-Z]' '[a-z]'
+}
+
+#
+# stdin contains expected result
+#
+function test_output_expect_success
+{
+ local _description=$1
+ shift 1
+ local _name=$(echo $_description | name_from_description)
+ cat > $_name.expected
+ test_expect_success "$_description" "check_output $_name $*"
}
date >path0
git-update-cache --add path0
-tree=$(git-write-tree)
-root=$(do_commit $tree 2>/dev/null)
-export GIT_COMMITTER_NAME=foobar # to guarantee that the commit is different
-l0=$(do_commit $tree -p $root)
-l1=$(do_commit $tree -p $l0)
-l2=$(do_commit $tree -p $l1)
-a0=$(do_commit $tree -p $l2)
-a1=$(do_commit $tree -p $a0)
-export GIT_COMMITTER_NAME=foobar2 # to guarantee that the commit is different
-b1=$(do_commit $tree -p $a0)
-c1=$(do_commit $tree -p $b1)
-export GIT_COMMITTER_NAME=foobar3 # to guarantee that the commit is different
-b2=$(do_commit $tree -p $b1)
-b3=$(do_commit $tree -p $b2)
-c2=$(do_commit $tree -p $c1 -p $b2)
-c3=$(do_commit $tree -p $c2)
-a2=$(do_commit $tree -p $a1)
-a3=$(do_commit $tree -p $a2)
-b4=$(do_commit $tree -p $b3 -p $a3)
-a4=$(do_commit $tree -p $a3 -p $b4 -p $c3)
-l3=$(do_commit $tree -p $a4)
-l4=$(do_commit $tree -p $l3)
-l5=$(do_commit $tree -p $l4)
-echo $l5 > .git/HEAD
+save_tag tree git-write-tree
+hide_error save_tag root commit root tree
+save_tag l0 commit l0 tree -p root
+save_tag l1 commit l1 tree -p l0
+save_tag l2 commit l2 tree -p l1
+save_tag a0 commit a0 tree -p l2
+save_tag a1 commit a1 tree -p a0
+save_tag b1 commit b1 tree -p a0
+save_tag c1 commit c1 tree -p b1
+as_author foobar@example.com save_tag b2 commit b2 tree -p b1
+save_tag b3 commit b2 tree -p b2
+save_tag c2 commit c2 tree -p c1 -p b2
+save_tag c3 commit c3 tree -p c2
+save_tag a2 commit a2 tree -p a1
+save_tag a3 commit a3 tree -p a2
+save_tag b4 commit b4 tree -p b3 -p a3
+save_tag a4 commit a4 tree -p a3 -p b4 -p c3
+save_tag l3 commit l3 tree -p a4
+save_tag l4 commit l4 tree -p l3
+save_tag l5 commit l5 tree -p l4
+hide_error save_tag e1 as_author e@example.com commit e1 tree
+save_tag e2 as_author e@example.com commit e2 tree -p e1
+save_tag f1 as_author f@example.com commit f1 tree -p e1
+save_tag e3 as_author e@example.com commit e3 tree -p e2
+save_tag f2 as_author f@example.com commit f2 tree -p f1
+save_tag e4 as_author e@example.com commit e4 tree -p e3 -p f2
+save_tag e5 as_author e@example.com commit e5 tree -p e4
+save_tag f3 as_author f@example.com commit f3 tree -p f2
+save_tag f4 as_author f@example.com commit f4 tree -p f3
+save_tag e6 as_author e@example.com commit e6 tree -p e5 -p f4
+save_tag f5 as_author f@example.com commit f5 tree -p f4
+save_tag f6 as_author f@example.com commit f6 tree -p f5 -p e6
+save_tag e7 as_author e@example.com commit e7 tree -p e6
+save_tag e8 as_author e@example.com commit e8 tree -p e7
+save_tag e9 as_author e@example.com commit e9 tree -p e8
+save_tag f7 as_author f@example.com commit f7 tree -p f6
+save_tag f8 as_author f@example.com commit f8 tree -p f7
+save_tag f9 as_author f@example.com commit f9 tree -p f8
+save_tag e10 as_author e@example.com commit e1 tree -p e9 -p f8
+tag l5 > .git/HEAD
+
+#
+# cd to t/trash and use
+#
+# git-rev-list ... 2>&1 | sed "$(cat sed.script)"
+#
+# if you ever want to manually debug the operation of git-rev-list
+#
+echo $sed_script > sed.script
+
+test_expect_success 'Testing that the rev-list has correct number of entries' 'git-rev-list HEAD | wc -l | tr -s " "' <<EOF
+19
+EOF
+
+normal_adjacency_count=$(git-rev-list HEAD | check_adjacency | grep -c "\^" | tr -d ' ')
+merge_order_adjacency_count=$(git-rev-list --merge-order HEAD | check_adjacency | grep -c "\^" | tr -d ' ')
+test_expect_success 'Testing that --merge-order produces as many or fewer discontinuities' '[ $merge_order_adjacency_count -le $normal_adjacency_count ]'
-git-rev-list --merge-order --show-breaks HEAD | sed "$(sed_script)" > actual-merge-order
-cat > expected-merge-order <<EOF
+test_output_expect_success 'Simple Merge Order Test' 'git-rev-list --merge-order --show-breaks HEAD' <<EOF
= l5
| l4
| l3
@@ -86,15 +183,7 @@ cat > expected-merge-order <<EOF
= root
EOF
-git-rev-list HEAD | check_adjacency | sed "$(sed_script)" > actual-default-order
-normal_adjacency_count=$(git-rev-list HEAD | check_adjacency | grep -c "\^" | tr -d ' ')
-merge_order_adjacency_count=$(git-rev-list --merge-order HEAD | check_adjacency | grep -c "\^" | tr -d ' ')
-
-test_expect_success 'Testing that the rev-list has correct number of entries' '[ $(git-rev-list HEAD | wc -l) -eq 19 ]'
-test_expect_success 'Testing that --merge-order produces the correct result' 'diff expected-merge-order actual-merge-order'
-test_expect_success 'Testing that --merge-order produces as many or fewer discontinuities' '[ $merge_order_adjacency_count -le $normal_adjacency_count ]'
-
-cat > expected-merge-order-1 <<EOF
+test_output_expect_success 'Multiple heads' 'git-rev-list --merge-order a3 b3 c3' <<EOF
c3
c2
c1
@@ -111,10 +200,7 @@ l0
root
EOF
-git-rev-list --merge-order $a3 $b3 $c3 | sed "$(sed_script)" > actual-merge-order-1
-test_expect_success 'Testing multiple heads' 'diff expected-merge-order-1 actual-merge-order-1'
-
-cat > expected-merge-order-2 <<EOF
+test_output_expect_success 'Prune at a1' 'git-rev-list --merge-order a3 b3 c3 ^a1' <<EOF
c3
c2
c1
@@ -125,10 +211,7 @@ a3
a2
EOF
-git-rev-list --merge-order $a3 $b3 $c3 ^$a1 | sed "$(sed_script)" > actual-merge-order-2
-test_expect_success 'Testing stop' 'diff expected-merge-order-2 actual-merge-order-2'
-
-cat > expected-merge-order-3 <<EOF
+test_output_expect_success 'Prune at l1' 'git-rev-list --merge-order a3 b3 c3 ^l1' <<EOF
c3
c2
c1
@@ -142,10 +225,26 @@ a0
l2
EOF
-git-rev-list --merge-order $a3 $b3 $c3 ^$l1 | sed "$(sed_script)" > actual-merge-order-3
-test_expect_success 'Testing stop in linear epoch' 'diff expected-merge-order-3 actual-merge-order-3'
+test_output_expect_success 'Head at l5, Prune at l1' 'git-rev-list --merge-order l5 ^l1' <<EOF
+l5
+l4
+l3
+a4
+c3
+c2
+c1
+b4
+b3
+b2
+b1
+a3
+a2
+a1
+a0
+l2
+EOF
-cat > expected-merge-order-4 <<EOF
+test_output_expect_success 'Duplicated head arguments' 'git-rev-list --merge-order l5 l5 ^l1' <<EOF
l5
l4
l3
@@ -164,12 +263,136 @@ a0
l2
EOF
-git-rev-list --merge-order $l5 ^$l1 | sed "$(sed_script)" > actual-merge-order-4
-test_expect_success 'Testing start in linear epoch, stop after non-linear epoch' 'diff expected-merge-order-4 actual-merge-order-4'
+test_output_expect_success 'Prune near merge' 'git-rev-list --merge-order a4 ^c3' <<EOF
+a4
+b4
+b3
+a3
+a2
+a1
+EOF
+
+test_output_expect_success '--wrt-author foobar@example.com' 'git-rev-list --merge-order --show-breaks --wrt-author --author=foobar@example.com HEAD' <<EOF
+= l5
+| l4
+| l3
+= a4
+| c3
+| c2
+| c1
+^ b4
+| a3
+| a2
+| a1
+^ b3
+| b2
+| b1
+= a0
+| l2
+| l1
+| l0
+= root
+EOF
+
+test_output_expect_success "Prune at foobar@example.com" 'git-rev-list --show-breaks --wrt-author --author=foobar@example.com --prune-at-author HEAD' <<EOF
+= l5
+| l4
+| l3
+= a4
+| c3
+| c2
+| c1
+^ b4
+| a3
+| a2
+| a1
+^ b3
+| b2
+EOF
+
+test_output_expect_success "w.r.t e@example.com" 'git-rev-list --show-breaks --wrt-author --author=e@example.com e10 f9' <<EOF
+| f9
+^ e10
+| f8
+| f7
+| f6
+| f5
+^ e9
+| e8
+| e7
+| e6
+| f4
+| f3
+^ e5
+| e4
+| f2
+| f1
+^ e3
+| e2
+= e1
+EOF
-git-rev-list --merge-order $l5 $l5 ^$l1 2>/dev/null | sed "$(sed_script)" > actual-merge-order-5
-test_expect_success 'Testing duplicated start arguments' 'diff expected-merge-order-4 actual-merge-order-5'
-test_expect_success 'Testing exclusion near merge' 'git-rev-list --merge-order $a4 ^$c3 2>/dev/null'
+test_output_expect_success "w.r.t f@example.com" 'git-rev-list --show-breaks --wrt-author --author=f@example.com e10 f9' <<EOF
+| f9
+^ e10
+| f8
+| f7
+| f6
+| f5
+^ e9
+| e8
+| e7
+| e6
+| f4
+| f3
+^ e5
+| e4
+| e3
+| e2
+^ f2
+| f1
+= e1
+EOF
+
+as_author "" test_output_expect_success "w.r.t nobody" 'git-rev-list --show-breaks --wrt-author f9 e10' <<EOF
+| e10
+| e9
+| e8
+| e7
+^ f9
+| f8
+| f7
+| f6
+| e6
+| e5
+| e4
+| e3
+| e2
+^ f5
+| f4
+| f3
+| f2
+| f1
+= e1
+EOF
+
+test_output_expect_success "head has no parent" 'git-rev-list --show-breaks root' <<EOF
+= root
+EOF
+
+test_output_expect_success "two nodes - one head, one base" 'git-rev-list --show-breaks l0' <<EOF
+= l0
+= root
+EOF
+
+test_output_expect_success "three nodes one head, one internal, one base" 'git-rev-list --show-breaks l1' <<EOF
+= l1
+| l0
+= root
+EOF
+#
+#
+#
test_done
diff --git a/user.c b/user.c
new file mode 100644
--- /dev/null
+++ b/user.c
@@ -0,0 +1,29 @@
+#include "cache.h"
+#include "user.h"
+#include <string.h>
+#include <pwd.h>
+
+void get_real_identity(char **email, char **gecos)
+{
+ static char buffer[1000];
+ struct passwd *pw;
+ int len;
+
+ pw = getpwuid(getuid());
+ if (!pw)
+ die("You don't exist. Go away!");
+
+ len = strlen(pw->pw_name);
+ memcpy(buffer, pw->pw_name, len);
+ buffer[len] = '@';
+ gethostname(buffer + len + 1, sizeof(buffer) - len - 1);
+ if (!strchr(buffer + len + 1, '.')) {
+ strcat(buffer, ".");
+ getdomainname(buffer + strlen(buffer),
+ sizeof(buffer) - strlen(buffer) - 1);
+ }
+ if (gecos)
+ *gecos = strdup(pw->pw_gecos);
+ if (email)
+ *email = strdup(buffer);
+}
diff --git a/user.h b/user.h
new file mode 100644
--- /dev/null
+++ b/user.h
@@ -0,0 +1,8 @@
+#ifndef USER_H
+#define USER_H
+/*
+ * Allocates two new strings to contain the real email and
+ * name of the current user.
+ */
+extern void get_real_identity(char **email, char **gecos);
+#endif
^ permalink raw reply
* [PATCH] Re: "git cvsimport"
From: Tommy M. McGuire @ 2005-06-10 6:22 UTC (permalink / raw)
To: Linus Torvalds; +Cc: Git Mailing List
In-Reply-To: <Pine.LNX.4.58.0506071556000.2286@ppc970.osdl.org>
On Tue, Jun 07, 2005 at 04:03:53PM -0700, Linus Torvalds wrote:
> I'd love it if somebody tested the remote case (and, if it works, sends me
> a patch that just removes the anal tests in git-cvsimport-script), and
> maybe even updated the information a bit more.. As it is, that
> Documentation/cvs-migration.txt file is a bit on the light side.
>
> Finally, I don't know what to do about cvsps options. It seems that the
> default time-fuzz is a bit too long for some projects, so at least that
> one should be overridable. So my silly script is not exactly wonderful,
> but I think it's more approachable than people doing the magic by hand
> (and forgetting to set TZ to UTC and the "-A" flag etc etc).
>
> Linus
This is the first of a short sequence of patches, which just removes the
tests in in git-cvs-import-script. It does, indeed, just work.
Signed-off-by: Tommy McGuire <mcguire@crsr.net>
Remove unneeded sanity tests, allowing remote CVS repos.
---
commit 21b4b44a5deee7985f14d0f4776ad0d489092a96
tree a48fbf33e90511554077f2c18053f1c000c2edb5
parent 98a96b00b88ee35866cd0b1e94697db76bd5ddf9
author Tommy M. McGuire <mcguire@crsr.net> Thu, 09 Jun 2005 13:12:25 -0500
committer Tommy M. McGuire <mcguire@crsr.net> Thu, 09 Jun 2005 13:12:25 -0500
git-cvsimport-script | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/git-cvsimport-script b/git-cvsimport-script
--- a/git-cvsimport-script
+++ b/git-cvsimport-script
@@ -7,7 +7,7 @@ fi
export CVSROOT="$1"
export MODULE="$2"
-if [ ! "$CVSROOT" ] || [ ! "$MODULE" ] || [ ! -d $CVSROOT ] || [ ! -d $CVSROOT/CVSROOT ] || [ ! -d $CVSROOT/$MODULE ] ; then
+if [ ! "$CVSROOT" ] || [ ! "$MODULE" ] ; then
echo "Usage: git cvsimport <cvsroot> <module>"
exit 1
fi
\f
!-------------------------------------------------------------flip-
^ permalink raw reply
* [PATCH] git cvsimport sanity checking
From: Tommy M. McGuire @ 2005-06-10 6:27 UTC (permalink / raw)
To: Tommy M. McGuire; +Cc: Linus Torvalds, Git Mailing List
In-Reply-To: <20050610062206.GA3992@immutable.crsr.net>
This patch adds some sanity checking to git-cvsimport-script,
specifically forcing the use of cvsps -x (to get the latest information
from the repository, rather than whatever is in the cache) and aborting
early if cvsps does not produce any output.
I debated removing the $MODULE directory following an abort, but I
eventually decided leaving stuff behind would make debugging easier. On
the other hand, this patch should help with the "cvsimport left me with
an empty repository" complaints.
Signed-off-by: Tommy McGuire <mcguire@crsr.net>
Call cvsps with the -x flag, to get the current state of the repository,
and abort the cvs import early if cvsps does not produce any output.
---
commit 2e670826cec5642a11e2e7d6adcb48e02225ba6d
tree 25e4f590772b9c0bc8ac37f8a57f4a08435ed5c6
parent 21b4b44a5deee7985f14d0f4776ad0d489092a96
author Tommy M. McGuire <mcguire@crsr.net> Fri, 10 Jun 2005 01:38:42 -0500
committer Tommy M. McGuire <mcguire@crsr.net> Fri, 10 Jun 2005 01:38:42 -0500
git-cvsimport-script | 4 +++-
1 files changed, 3 insertions(+), 1 deletions(-)
diff --git a/git-cvsimport-script b/git-cvsimport-script
--- a/git-cvsimport-script
+++ b/git-cvsimport-script
@@ -20,6 +20,8 @@ cvsps -h 2>&1 | grep -q "cvsps version 2
mkdir "$MODULE" || exit 1
cd "$MODULE"
-TZ=UTC cvsps -A $MODULE | git-cvs2git $ARGS --cvsroot="$CVSROOT" --module="$MODULE" > .git-create-script || exit 1
+TZ=UTC cvsps -x -A $MODULE > .git-cvsps-result
+[ -s .git-cvsps-result ] || exit 1
+git-cvs2git $ARGS --cvsroot="$CVSROOT" --module="$MODULE" < .git-cvsps-result > .git-create-script || exit 1
sh .git-create-script
\f
!-------------------------------------------------------------flip-
--
Tommy McGuire
^ permalink raw reply
* [PATCH] cvs-migration.txt
From: Tommy M. McGuire @ 2005-06-10 6:33 UTC (permalink / raw)
To: Tommy M. McGuire; +Cc: Linus Torvalds, Git Mailing List
In-Reply-To: <20050610062206.GA3992@immutable.crsr.net>
The way I figure it, telling someone why cvsimport is taking so long
will improve their overall user experience. :-)
Signed-off-by: Tommy McGuire <mcguire@crsr.net>
Slightly expand the cvsimport description, and make a couple of syntax edits.
---
commit 9a861c244e79634abf3c8436d720e77140d0e0e3
tree 32155e131933b2e48383644a3e3198f541f6d4b8
parent 2e670826cec5642a11e2e7d6adcb48e02225ba6d
author Tommy M. McGuire <mcguire@crsr.net> Fri, 10 Jun 2005 02:11:14 -0500
committer Tommy M. McGuire <mcguire@crsr.net> Fri, 10 Jun 2005 02:11:14 -0500
Documentation/cvs-migration.txt | 12 +++++++-----
1 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/Documentation/cvs-migration.txt b/Documentation/cvs-migration.txt
--- a/Documentation/cvs-migration.txt
+++ b/Documentation/cvs-migration.txt
@@ -14,7 +14,7 @@ does a lot of things differently.
One particular suckage of CVS is very hard to work around: CVS is
basically a tool for tracking _file_ history, while git is a tool for
tracking _project_ history. This sometimes causes problems if you are
-used to doign very strange things in CVS, in particular if you're doing
+used to doing very strange things in CVS, in particular if you're doing
things like making branches of just a subset of the project. Git can't
track that, since git never tracks things on the level of an individual
file, only on the whole project level.
@@ -35,8 +35,8 @@ that you're actually working in (your wo
working directories _are_ the repositories. However, you can easily
emulate the CVS model by having one special "global repository", which
people can synchronize with. See details later, but in the meantime
-just keep in mind that with git, every checked out working tree will be
-a full revision control of its own.
+just keep in mind that with git, every checked out working tree will
+have a full revision control history of its own.
Importing a CVS archive
@@ -69,10 +69,12 @@ which will do exactly what you'd think i
archive of the named CVS module. The new archive will be created in a
subdirectory named <module>.
-It can take some time to actually do the conversion for a large archive,
+It can take some time to actually do the conversion for a large archive
+since it involves checking out from CVS every revision of every file,
and the conversion script can be reasonably chatty, but on some not very
scientific tests it averaged about eight revisions per second, so a
-medium-sized project should not take more than a couple of minutes.
+medium-sized project should not take more than a couple of minutes. For
+larger projects or remote repositories, the process may take longer.
Emulating CVS behaviour
\f
!-------------------------------------------------------------flip-
--
Tommy McGuire
^ permalink raw reply
* [PATCH] git cvsimport fuzz argument
From: Tommy M. McGuire @ 2005-06-10 6:38 UTC (permalink / raw)
To: Tommy M. McGuire; +Cc: Linus Torvalds, Git Mailing List
In-Reply-To: <20050610062206.GA3992@immutable.crsr.net>
Give the user the option of specifying the timestamp fuzz passed to
cvsps. Looking at the other arguments to it, I can't see anything else
that would be sane to play with. Also, use --cvs-direct, which speeds
up cvsps for remote repositories and doesn't seem to do anything bad to
local repositories.
Signed-off-by: Tommy McGuire <mcguire@crsr.net>
Add "-z fuzz" argument, passed to cvsps, and clean up argument processing.
Also, use "cvsps --cvs-direct", which is is somewhat faster.
---
commit 018aee88d1b71f96ce9ecdfba77608566f51c860
tree 623c7a40ea20ef8b68cf896605d55432cd0469ac
parent 9a861c244e79634abf3c8436d720e77140d0e0e3
author Tommy M. McGuire <mcguire@crsr.net> Fri, 10 Jun 2005 02:51:34 -0500
committer Tommy M. McGuire <mcguire@crsr.net> Fri, 10 Jun 2005 02:51:34 -0500
git-cvsimport-script | 27 +++++++++++++++++++--------
1 files changed, 19 insertions(+), 8 deletions(-)
diff --git a/git-cvsimport-script b/git-cvsimport-script
--- a/git-cvsimport-script
+++ b/git-cvsimport-script
@@ -1,15 +1,26 @@
#!/bin/sh
-ARGS=""
-if [ "$1" == "-v" ]; then
- ARGS=$1
+
+usage () {
+ echo "Usage: git cvsimport [-v] [-z fuzz] <cvsroot> <module>"
+ exit 1
+}
+
+CVS2GIT=""
+CVSPS="--cvs-direct -x -A"
+while true; do
+ case "$1" in
+ -v) CVS2GIT="$1" ;;
+ -z) shift; CVSPS="$CVSPS -z $1" ;;
+ -*) usage ;;
+ *) break ;;
+ esac
shift
-fi
+done
export CVSROOT="$1"
export MODULE="$2"
if [ ! "$CVSROOT" ] || [ ! "$MODULE" ] ; then
- echo "Usage: git cvsimport <cvsroot> <module>"
- exit 1
+ usage
fi
cvsps -h 2>&1 | grep -q "cvsps version 2.1" >& /dev/null || {
@@ -20,8 +31,8 @@ cvsps -h 2>&1 | grep -q "cvsps version 2
mkdir "$MODULE" || exit 1
cd "$MODULE"
-TZ=UTC cvsps -x -A $MODULE > .git-cvsps-result
+TZ=UTC cvsps $CVSPS $MODULE > .git-cvsps-result
[ -s .git-cvsps-result ] || exit 1
-git-cvs2git $ARGS --cvsroot="$CVSROOT" --module="$MODULE" < .git-cvsps-result > .git-create-script || exit 1
+git-cvs2git $CVS2GIT --cvsroot="$CVSROOT" --module="$MODULE" < .git-cvsps-result > .git-create-script || exit 1
sh .git-create-script
\f
!-------------------------------------------------------------flip-
--
Tommy McGuire
^ 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