Git development
 help / color / mirror / Atom feed
* Re: Git 1.3.2 on Solaris
From: Stefan Pfetzing @ 2006-05-23  3:20 UTC (permalink / raw)
  To: Git Mailing List
In-Reply-To: <6471.1147883724@lotus.CS.Berkeley.EDU>

Hi Jason,

2006/5/17, Jason Riedy <ejr@eecs.berkeley.edu>:
> And pkgsrc itself works just fine without the silly g prefix,
> or at least does for me as a mere user (and as well as it does
> work).  But if you intend on adding the package upstream, it'll
> need something to cope with the g.  And pkgsrc handles local
> patches...

Well I had some problems on NetBSD without the g prefix for the
gnu coreutils - since then I always used that prefix.

But now I have a completely different problem with the tests on
solaris. It seems on solaris access() always returns 0 if a file is
existant and the effective uid is 0.

so:
--- snip ---
#include <stdio.h>
#include <unistd.h>

int
main (int argc, char **argv)
{
  printf ("access: %d\n", access("/etc/motd", X_OK));
  return 0;
}
--- snap ---

will return 0 on solaris - when run as root, even though /etc/motd
is not executeable. This seems to break hooks on Solaris - but
I'm not sure if this is only a Solaris Express bug. (I have no Solaris
10 system to verify it)

bye

Stefan

-- 
       http://www.dreamind.de/
Oroborus and Debian GNU/Linux Developer.

^ permalink raw reply

* Re: Git 1.3.2 on Solaris
From: Jason Riedy @ 2006-05-23  4:51 UTC (permalink / raw)
  To: Stefan Pfetzing; +Cc: Git Mailing List
In-Reply-To: <f3d7535d0605222020j2d581bd9j602752659a4b3ac2@mail.gmail.com>

And "Stefan Pfetzing" writes:
 -   printf ("access: %d\n", access("/etc/motd", X_OK));
[...]
 - will return 0 on solaris - when run as root, even though /etc/motd
 - is not executeable.

This is explicitly allowed by the SUS, even for non-root:
  http://www.opengroup.org/onlinepubs/000095399/functions/access.html
For non-root, some ACL systems could allow you to execute
the file even if there are no execute bits.  What a joy
ACLs are.  Or NFS uid mappings could play tricks on you,
or...  And as you've noticed, this kills [ -x ].  (Failing
to run the hooks in receive-pack.c is noisy but not fatal.
It's the shell scripts that stop.)

I think you're stuck.  To disable the hooks for all possible
users, OSes, file systems, etc., you need to remove them.

Or just don't run as root, and hope that the OS isn't 
completely insane.

BTW, ERR_RUN_COMMAND_EXEC is never returned.  Any failure
to exec will produce an exit code of 128 from die.  This
will be an issue when commit becomes a built-in, right?

Jason

^ permalink raw reply

* Re: [PATCH] git status: ignore empty directories (because they cannot be added)
From: Matthias Lederhofer @ 2006-05-23  5:35 UTC (permalink / raw)
  To: git
In-Reply-To: <7vu07h8rzr.fsf@assigned-by-dhcp.cox.net>

> > and a new option -u / --untracked-files to show files in untracked
> > directories.
> >
> > ---
> > A few things I'm not sure about:
> > - Should there be another option to disable --no-empty-directory?
>
> I am not sure about this.  We used to show everything in a
> directory full of untracked directory, which was distracting and
> that was the reason we added --directory there.  Maybe it would
> be less confusing if we just updated the message
>
>           print "#\n# Untracked files:\n";
>           print "#   (use \"git add\" to add to commit)\n";
>           print "#\n";
>
> to say "use 'git add' on these files and files in these
> directories you wish to add", or something silly like that,
> without this patch?

I do not know if I understand you correctly, do you want to leave out
the -u part of the patch or the whole patch?

Well, anyway, here the reasons for this patch:
- Working in a git repository with a lot of empty directories is
  annoying, because all of them show up in git status even though they
  cannot be added. With --no-empty-directories they are hidden.
- If there is a directory which may be added because it is quite
  useful to have the -u option to see what is in there to add (without
  using ls path/to/directory).

^ permalink raw reply

* Re: irc usage..
From: Jeff King @ 2006-05-23  6:52 UTC (permalink / raw)
  To: Martin Langhoff; +Cc: Junio C Hamano, Matthias Urlichs, git
In-Reply-To: <46a038f90605221615j59583bcdqf128bab31603148e@mail.gmail.com>

On Tue, May 23, 2006 at 11:15:07AM +1200, Martin Langhoff wrote:

> >I think cvsimport predates that option, but these days that loop
> >can be optimized by feeding --index-info from standard input.
> Oh, yep, that'd be a good addition. I think we can also cut down on

This patch is relatively simple, and I'll post it in a moment.

I also made a few other cleanups to commit() which apply on top of that;
I'll post it also.

> - Stop abusing globals in commit() -- pass the commit data as parameters.

Some of the globals actually get modified in commit() (e.g., @old and
@new get cleared).  So we need to either pass them in as references or
remember to do that cleanup each time it is called (which is really only
twice, I think).

> Will be trying to do those things in the next few days, don't mind if
> someone jumps in as well.

I can look at the line/block CVS file slurping, but not tonight.

-Peff

^ permalink raw reply

* Re: irc usage..
From: Jeff King @ 2006-05-23  6:58 UTC (permalink / raw)
  To: Martin Langhoff, Junio C Hamano, Matthias Urlichs, git
In-Reply-To: <20060523065232.GA6180@coredump.intra.peff.net>

>From nobody Mon Sep 17 00:00:00 2001
From: Jeff King <peff@peff.net>
Date: Tue, 23 May 2006 01:16:07 -0400
Subject: [PATCH 1/2] cvsimport: use git-update-index --index-info

This should reduce the number of git-update-index forks required per
commit. We now do adds/removes in one call, and we are no longer forced to
deal with argv limitations.

---

cb6452bbfda9c52ad8dbeaac6e3440ae61099a05
 git-cvsimport.perl |   36 +++++++++++++-----------------------
 1 files changed, 13 insertions(+), 23 deletions(-)

cb6452bbfda9c52ad8dbeaac6e3440ae61099a05
diff --git a/git-cvsimport.perl b/git-cvsimport.perl
index d257e66..4efb0a5 100755
--- a/git-cvsimport.perl
+++ b/git-cvsimport.perl
@@ -565,29 +565,19 @@ my($patchset,$date,$author_name,$author_
 my(@old,@new,@skipped);
 sub commit {
 	my $pid;
-	while(@old) {
-		my @o2;
-		if(@old > 55) {
-			@o2 = splice(@old,0,50);
-		} else {
-			@o2 = @old;
-			@old = ();
-		}
-		system("git-update-index","--force-remove","--",@o2);
-		die "Cannot remove files: $?\n" if $?;
-	}
-	while(@new) {
-		my @n2;
-		if(@new > 12) {
-			@n2 = splice(@new,0,10);
-		} else {
-			@n2 = @new;
-			@new = ();
-		}
-		system("git-update-index","--add",
-			(map { ('--cacheinfo', @$_) } @n2));
-		die "Cannot add files: $?\n" if $?;
-	}
+
+      	open(my $fh, '|-', qw(git-update-index --index-info))
+		or die "unable to open git-update-index: $!";
+	print $fh 
+		(map { "0 0000000000000000000000000000000000000000\t$_\n" }
+			@old),
+		(map { '100' . sprintf('%o', $_->[0]) . " $_->[1]\t$_->[2]\n" }
+			@new)
+		or die "unable to write to git-update-index: $!";
+	close $fh
+		or die "unable to write to git-update-index: $!";
+	$? and die "git-update-index reported error: $?";
+	@old = @new = ();
 
 	$pid = open(C,"-|");
 	die "Cannot fork: $!" unless defined $pid;
-- 
1.3.3.gcb64-dirty

^ permalink raw reply related

* [PATCH 2/2] cvsimport: cleanup commit function
From: Jeff King @ 2006-05-23  7:00 UTC (permalink / raw)
  To: Martin Langhoff, Junio C Hamano, Matthias Urlichs, git
In-Reply-To: <20060523065232.GA6180@coredump.intra.peff.net>

This change attempts to clean up the commit function to make it a bit
easier to read (or at least the first half of it). It also improves
robustness and performance. Specifically:
  - report get_headref errors on opening ref unless the error is ENOENT
  - use regex to check for sha1 instead of length
  - use lexically scoped filehandles which get cleaned up automagically
  - check for error on both 'print' and 'close' (since output is buffered)
  - avoid "fork, do some perl, then exec" in commit(). It's not necessary,
    and we probably end up COW'ing parts of the perl process. Plus the code
    is much smaller because we can use open2()
  - avoid calling strftime over and over (mainly a readability cleanup)

---

I know this patch is quite large. I can try to split it if you want, but
I suspect it's not worth the effort (either you like refactoring or you
don't :) ).

9dc9f05ab5e1cbd8765238e7b1da0addd6f4296a
 git-cvsimport.perl |  150 ++++++++++++++++++++++------------------------------
 1 files changed, 64 insertions(+), 86 deletions(-)

9dc9f05ab5e1cbd8765238e7b1da0addd6f4296a
diff --git a/git-cvsimport.perl b/git-cvsimport.perl
index 4efb0a5..f8feb52 100755
--- a/git-cvsimport.perl
+++ b/git-cvsimport.perl
@@ -23,7 +23,7 @@ use File::Basename qw(basename dirname);
 use Time::Local;
 use IO::Socket;
 use IO::Pipe;
-use POSIX qw(strftime dup2);
+use POSIX qw(strftime dup2 :errno_h);
 use IPC::Open2;
 
 $SIG{'PIPE'}="IGNORE";
@@ -429,22 +429,25 @@ sub getwd() {
 	return $pwd;
 }
 
+sub is_sha1 {
+	my $s = shift;
+	return $s =~ /^[a-zA-Z0-9]{40}$/;
+}
 
-sub get_headref($$) {
+sub get_headref ($$) {
     my $name    = shift;
     my $git_dir = shift; 
-    my $sha;
     
-    if (open(C,"$git_dir/refs/heads/$name")) {
-	chomp($sha = <C>);
-	close(C);
-	length($sha) == 40
-	    or die "Cannot get head id for $name ($sha): $!\n";
+    my $f = "$git_dir/refs/heads/$name";
+    if(open(my $fh, $f)) {
+      	    chomp(my $r = <$fh>);
+	    is_sha1($r) or die "Cannot get head id for $name ($r): $!";
+	    return $r;
     }
-    return $sha;
+    die "unable to open $f: $!" unless $! == POSIX::ENOENT;
+    return undef;
 }
 
-
 -d $git_tree
 	or mkdir($git_tree,0777)
 	or die "Could not create $git_tree: $!";
@@ -561,90 +564,67 @@ #---------------------
 
 my $state = 0;
 
-my($patchset,$date,$author_name,$author_email,$branch,$ancestor,$tag,$logmsg);
-my(@old,@new,@skipped);
-sub commit {
-	my $pid;
-
+sub update_index (\@\@) {
+	my $old = shift;
+	my $new = shift;
       	open(my $fh, '|-', qw(git-update-index --index-info))
 		or die "unable to open git-update-index: $!";
 	print $fh 
 		(map { "0 0000000000000000000000000000000000000000\t$_\n" }
-			@old),
+			@$old),
 		(map { '100' . sprintf('%o', $_->[0]) . " $_->[1]\t$_->[2]\n" }
-			@new)
+			@$new)
 		or die "unable to write to git-update-index: $!";
 	close $fh
 		or die "unable to write to git-update-index: $!";
 	$? and die "git-update-index reported error: $?";
-	@old = @new = ();
+}
 
-	$pid = open(C,"-|");
-	die "Cannot fork: $!" unless defined $pid;
-	unless($pid) {
-		exec("git-write-tree");
-		die "Cannot exec git-write-tree: $!\n";
-	}
-	chomp(my $tree = <C>);
-	length($tree) == 40
-		or die "Cannot get tree id ($tree): $!\n";
-	close(C)
+sub write_tree () {
+	open(my $fh, '-|', qw(git-write-tree))
+		or die "unable to open git-write-tree: $!";
+	chomp(my $tree = <$fh>);
+	is_sha1($tree)
+		or die "Cannot get tree id ($tree): $!";
+	close($fh)
 		or die "Error running git-write-tree: $?\n";
 	print "Tree ID $tree\n" if $opt_v;
+	return $tree;
+}
 
-	my $parent = "";
-	if(open(C,"$git_dir/refs/heads/$last_branch")) {
-		chomp($parent = <C>);
-		close(C);
-		length($parent) == 40
-			or die "Cannot get parent id ($parent): $!\n";
-		print "Parent ID $parent\n" if $opt_v;
-	}
-
-	my $pr = IO::Pipe->new() or die "Cannot open pipe: $!\n";
-	my $pw = IO::Pipe->new() or die "Cannot open pipe: $!\n";
-	$pid = fork();
-	die "Fork: $!\n" unless defined $pid;
-	unless($pid) {
-		$pr->writer();
-		$pw->reader();
-		open(OUT,">&STDOUT");
-		dup2($pw->fileno(),0);
-		dup2($pr->fileno(),1);
-		$pr->close();
-		$pw->close();
-
-		my @par = ();
-		@par = ("-p",$parent) if $parent;
-
-		# loose detection of merges
-		# based on the commit msg
-		foreach my $rx (@mergerx) {
-			if ($logmsg =~ $rx) {
-				my $mparent = $1;
-				if ($mparent eq 'HEAD') { $mparent = $opt_o };
-				if ( -e "$git_dir/refs/heads/$mparent") {
-					$mparent = get_headref($mparent, $git_dir);
-					push @par, '-p', $mparent;
-					print OUT "Merge parent branch: $mparent\n" if $opt_v;
-				}
-			}
+my($patchset,$date,$author_name,$author_email,$branch,$ancestor,$tag,$logmsg);
+my(@old,@new,@skipped);
+sub commit {
+	update_index(@old, @new);
+	@old = @new = ();
+	my $tree = write_tree();
+	my $parent = get_headref($last_branch, $git_dir);
+	print "Parent ID " . ($parent ? $parent : "(empty)") . "\n" if $opt_v;
+
+	my @commit_args;
+	push @commit_args, ("-p", $parent) if $parent;
+
+	# loose detection of merges
+	# based on the commit msg
+	foreach my $rx (@mergerx) {
+		next unless $logmsg =~ $rx && $1;
+		my $mparent = $1 eq 'HEAD' ? $opt_o : $1;
+		if(my $sha1 = get_headref($mparent, $git_dir)) {
+			push @commit_args, '-p', $mparent;
+			print "Merge parent branch: $mparent\n" if $opt_v;
 		}
-
-		exec("env",
-			"GIT_AUTHOR_NAME=$author_name",
-			"GIT_AUTHOR_EMAIL=$author_email",
-			"GIT_AUTHOR_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
-			"GIT_COMMITTER_NAME=$author_name",
-			"GIT_COMMITTER_EMAIL=$author_email",
-			"GIT_COMMITTER_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
-			"git-commit-tree", $tree,@par);
-		die "Cannot exec git-commit-tree: $!\n";
-
-		close OUT;
 	}
-	$pw->writer();
-	$pr->reader();
+
+	my $commit_date = strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date));
+	my $pid = open2(my $commit_read, my $commit_write,
+		'env',
+		"GIT_AUTHOR_NAME=$author_name",
+		"GIT_AUTHOR_EMAIL=$author_email",
+		"GIT_AUTHOR_DATE=$commit_date",
+		"GIT_COMMITTER_NAME=$author_name",
+		"GIT_COMMITTER_EMAIL=$author_email",
+		"GIT_COMMITTER_DATE=$commit_date",
+		'git-commit-tree', $tree, @commit_args);
 
 	# compatibility with git2cvs
 	substr($logmsg,32767) = "" if length($logmsg) > 32767;
@@ -656,16 +636,14 @@ sub commit {
 	    @skipped = ();
 	}
 
-	print $pw "$logmsg\n"
+	print($commit_write "$logmsg\n") && close($commit_write)
 		or die "Error writing to git-commit-tree: $!\n";
-	$pw->close();
 
-	print "Committed patch $patchset ($branch ".strftime("%Y-%m-%d %H:%M:%S",gmtime($date)).")\n" if $opt_v;
-	chomp(my $cid = <$pr>);
-	length($cid) == 40
-		or die "Cannot get commit id ($cid): $!\n";
+	print "Committed patch $patchset ($branch $commit_date)\n" if $opt_v;
+	chomp(my $cid = <$commit_read>);
+	is_sha1($cid) or die "Cannot get commit id ($cid): $!\n";
 	print "Commit ID $cid\n" if $opt_v;
-	$pr->close();
+	close($commit_read);
 
 	waitpid($pid,0);
 	die "Error running git-commit-tree: $?\n" if $?;
-- 
1.3.3.gcb64-dirty

^ permalink raw reply related

* [PATCH 1/2] cvsimport: use git-update-index --index-info
From: Jeff King @ 2006-05-23  7:01 UTC (permalink / raw)
  To: Martin Langhoff, Junio C Hamano, Matthias Urlichs, git
In-Reply-To: <20060523065810.GB6180@coredump.intra.peff.net>

This should reduce the number of git-update-index forks required per
commit. We now do adds/removes in one call, and we are no longer forced to
deal with argv limitations.

---

Oops, apparently using a mail reader is too challenging for me. Here's a
repost with the headers correctly merged.

cb6452bbfda9c52ad8dbeaac6e3440ae61099a05
 git-cvsimport.perl |   36 +++++++++++++-----------------------
 1 files changed, 13 insertions(+), 23 deletions(-)

cb6452bbfda9c52ad8dbeaac6e3440ae61099a05
diff --git a/git-cvsimport.perl b/git-cvsimport.perl
index d257e66..4efb0a5 100755
--- a/git-cvsimport.perl
+++ b/git-cvsimport.perl
@@ -565,29 +565,19 @@ my($patchset,$date,$author_name,$author_
 my(@old,@new,@skipped);
 sub commit {
 	my $pid;
-	while(@old) {
-		my @o2;
-		if(@old > 55) {
-			@o2 = splice(@old,0,50);
-		} else {
-			@o2 = @old;
-			@old = ();
-		}
-		system("git-update-index","--force-remove","--",@o2);
-		die "Cannot remove files: $?\n" if $?;
-	}
-	while(@new) {
-		my @n2;
-		if(@new > 12) {
-			@n2 = splice(@new,0,10);
-		} else {
-			@n2 = @new;
-			@new = ();
-		}
-		system("git-update-index","--add",
-			(map { ('--cacheinfo', @$_) } @n2));
-		die "Cannot add files: $?\n" if $?;
-	}
+
+      	open(my $fh, '|-', qw(git-update-index --index-info))
+		or die "unable to open git-update-index: $!";
+	print $fh 
+		(map { "0 0000000000000000000000000000000000000000\t$_\n" }
+			@old),
+		(map { '100' . sprintf('%o', $_->[0]) . " $_->[1]\t$_->[2]\n" }
+			@new)
+		or die "unable to write to git-update-index: $!";
+	close $fh
+		or die "unable to write to git-update-index: $!";
+	$? and die "git-update-index reported error: $?";
+	@old = @new = ();
 
 	$pid = open(C,"-|");
 	die "Cannot fork: $!" unless defined $pid;
-- 
1.3.3.gcb64-dirty

^ permalink raw reply related

* Re: [PATCH 2/2] cvsimport: cleanup commit function
From: Jeff King @ 2006-05-23  7:13 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <7v4pzh6wtr.fsf@assigned-by-dhcp.cox.net>

[cc'd to list to get reactions on open2]

On Tue, May 23, 2006 at 12:10:08AM -0700, Junio C Hamano wrote:

> > +	return $s =~ /^[a-zA-Z0-9]{40}$/;
> [0-9a-f] (We always do lowercase).

Er, yes, that was a complete think-o on my part.

> Hmm.  I personally do not have problems with open2, but folks on
> some other platforms might.  I'll see how the list audience
> sounds.

FWIW, it was already being used in git-cvsimport.

-Peff

^ permalink raw reply

* [PATCH 2/2] cvsimport: cleanup commit function
From: Jeff King @ 2006-05-23  7:27 UTC (permalink / raw)
  To: git; +Cc: martin, junkio
In-Reply-To: <1148369266352-git-send-email-1>

This change attempts to clean up the commit function to make it a bit
easier to read (or at least the first half of it). It also improves
robustness and performance. Specifically:
  - report get_headref errors on opening ref unless the error is ENOENT
  - use regex to check for sha1 instead of length
  - use lexically scoped filehandles which get cleaned up automagically
  - check for error on both 'print' and 'close' (since output is buffered)
  - avoid "fork, do some perl, then exec" in commit(). It's not necessary,
    and we probably end up COW'ing parts of the perl process. Plus the code
    is much smaller because we can use open2()
  - avoid calling strftime over and over (mainly a readability cleanup)

---

This is a repost with some minor fixups from Junio (and based off of the
fixed 1/2 patch).

3408c8d8364f816a7c4a34a03045f466bf028540
 git-cvsimport.perl |  150 ++++++++++++++++++++++------------------------------
 1 files changed, 64 insertions(+), 86 deletions(-)

3408c8d8364f816a7c4a34a03045f466bf028540
diff --git a/git-cvsimport.perl b/git-cvsimport.perl
index a65bea6..219f6dc 100755
--- a/git-cvsimport.perl
+++ b/git-cvsimport.perl
@@ -23,7 +23,7 @@ use File::Basename qw(basename dirname);
 use Time::Local;
 use IO::Socket;
 use IO::Pipe;
-use POSIX qw(strftime dup2);
+use POSIX qw(strftime dup2 :errno_h);
 use IPC::Open2;
 
 $SIG{'PIPE'}="IGNORE";
@@ -429,22 +429,25 @@ sub getwd() {
 	return $pwd;
 }
 
+sub is_sha1 {
+	my $s = shift;
+	return $s =~ /^[a-f0-9]{40}$/;
+}
 
-sub get_headref($$) {
+sub get_headref ($$) {
     my $name    = shift;
     my $git_dir = shift; 
-    my $sha;
     
-    if (open(C,"$git_dir/refs/heads/$name")) {
-	chomp($sha = <C>);
-	close(C);
-	length($sha) == 40
-	    or die "Cannot get head id for $name ($sha): $!\n";
+    my $f = "$git_dir/refs/heads/$name";
+    if(open(my $fh, $f)) {
+      	    chomp(my $r = <$fh>);
+	    is_sha1($r) or die "Cannot get head id for $name ($r): $!";
+	    return $r;
     }
-    return $sha;
+    die "unable to open $f: $!" unless $! == POSIX::ENOENT;
+    return undef;
 }
 
-
 -d $git_tree
 	or mkdir($git_tree,0777)
 	or die "Could not create $git_tree: $!";
@@ -561,90 +564,67 @@ #---------------------
 
 my $state = 0;
 
-my($patchset,$date,$author_name,$author_email,$branch,$ancestor,$tag,$logmsg);
-my(@old,@new,@skipped);
-sub commit {
-	my $pid;
-
+sub update_index (\@\@) {
+	my $old = shift;
+	my $new = shift;
 	open(my $fh, '|-', qw(git-update-index -z --index-info))
 		or die "unable to open git-update-index: $!";
 	print $fh 
 		(map { "0 0000000000000000000000000000000000000000\t$_\0" }
-			@old),
+			@$old),
 		(map { '100' . sprintf('%o', $_->[0]) . " $_->[1]\t$_->[2]\0" }
-			@new)
+			@$new)
 		or die "unable to write to git-update-index: $!";
 	close $fh
 		or die "unable to write to git-update-index: $!";
 	$? and die "git-update-index reported error: $?";
-	@old = @new = ();
+}
 
-	$pid = open(C,"-|");
-	die "Cannot fork: $!" unless defined $pid;
-	unless($pid) {
-		exec("git-write-tree");
-		die "Cannot exec git-write-tree: $!\n";
-	}
-	chomp(my $tree = <C>);
-	length($tree) == 40
-		or die "Cannot get tree id ($tree): $!\n";
-	close(C)
+sub write_tree () {
+	open(my $fh, '-|', qw(git-write-tree))
+		or die "unable to open git-write-tree: $!";
+	chomp(my $tree = <$fh>);
+	is_sha1($tree)
+		or die "Cannot get tree id ($tree): $!";
+	close($fh)
 		or die "Error running git-write-tree: $?\n";
 	print "Tree ID $tree\n" if $opt_v;
+	return $tree;
+}
 
-	my $parent = "";
-	if(open(C,"$git_dir/refs/heads/$last_branch")) {
-		chomp($parent = <C>);
-		close(C);
-		length($parent) == 40
-			or die "Cannot get parent id ($parent): $!\n";
-		print "Parent ID $parent\n" if $opt_v;
-	}
-
-	my $pr = IO::Pipe->new() or die "Cannot open pipe: $!\n";
-	my $pw = IO::Pipe->new() or die "Cannot open pipe: $!\n";
-	$pid = fork();
-	die "Fork: $!\n" unless defined $pid;
-	unless($pid) {
-		$pr->writer();
-		$pw->reader();
-		open(OUT,">&STDOUT");
-		dup2($pw->fileno(),0);
-		dup2($pr->fileno(),1);
-		$pr->close();
-		$pw->close();
-
-		my @par = ();
-		@par = ("-p",$parent) if $parent;
-
-		# loose detection of merges
-		# based on the commit msg
-		foreach my $rx (@mergerx) {
-			if ($logmsg =~ $rx) {
-				my $mparent = $1;
-				if ($mparent eq 'HEAD') { $mparent = $opt_o };
-				if ( -e "$git_dir/refs/heads/$mparent") {
-					$mparent = get_headref($mparent, $git_dir);
-					push @par, '-p', $mparent;
-					print OUT "Merge parent branch: $mparent\n" if $opt_v;
-				}
-			}
+my($patchset,$date,$author_name,$author_email,$branch,$ancestor,$tag,$logmsg);
+my(@old,@new,@skipped);
+sub commit {
+	update_index(@old, @new);
+	@old = @new = ();
+	my $tree = write_tree();
+	my $parent = get_headref($last_branch, $git_dir);
+	print "Parent ID " . ($parent ? $parent : "(empty)") . "\n" if $opt_v;
+
+	my @commit_args;
+	push @commit_args, ("-p", $parent) if $parent;
+
+	# loose detection of merges
+	# based on the commit msg
+	foreach my $rx (@mergerx) {
+		next unless $logmsg =~ $rx && $1;
+		my $mparent = $1 eq 'HEAD' ? $opt_o : $1;
+		if(my $sha1 = get_headref($mparent, $git_dir)) {
+			push @commit_args, '-p', $mparent;
+			print "Merge parent branch: $mparent\n" if $opt_v;
 		}
-
-		exec("env",
-			"GIT_AUTHOR_NAME=$author_name",
-			"GIT_AUTHOR_EMAIL=$author_email",
-			"GIT_AUTHOR_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
-			"GIT_COMMITTER_NAME=$author_name",
-			"GIT_COMMITTER_EMAIL=$author_email",
-			"GIT_COMMITTER_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
-			"git-commit-tree", $tree,@par);
-		die "Cannot exec git-commit-tree: $!\n";
-
-		close OUT;
 	}
-	$pw->writer();
-	$pr->reader();
+
+	my $commit_date = strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date));
+	my $pid = open2(my $commit_read, my $commit_write,
+		'env',
+		"GIT_AUTHOR_NAME=$author_name",
+		"GIT_AUTHOR_EMAIL=$author_email",
+		"GIT_AUTHOR_DATE=$commit_date",
+		"GIT_COMMITTER_NAME=$author_name",
+		"GIT_COMMITTER_EMAIL=$author_email",
+		"GIT_COMMITTER_DATE=$commit_date",
+		'git-commit-tree', $tree, @commit_args);
 
 	# compatibility with git2cvs
 	substr($logmsg,32767) = "" if length($logmsg) > 32767;
@@ -656,16 +636,14 @@ sub commit {
 	    @skipped = ();
 	}
 
-	print $pw "$logmsg\n"
+	print($commit_write "$logmsg\n") && close($commit_write)
 		or die "Error writing to git-commit-tree: $!\n";
-	$pw->close();
 
-	print "Committed patch $patchset ($branch ".strftime("%Y-%m-%d %H:%M:%S",gmtime($date)).")\n" if $opt_v;
-	chomp(my $cid = <$pr>);
-	length($cid) == 40
-		or die "Cannot get commit id ($cid): $!\n";
+	print "Committed patch $patchset ($branch $commit_date)\n" if $opt_v;
+	chomp(my $cid = <$commit_read>);
+	is_sha1($cid) or die "Cannot get commit id ($cid): $!\n";
 	print "Commit ID $cid\n" if $opt_v;
-	$pr->close();
+	close($commit_read);
 
 	waitpid($pid,0);
 	die "Error running git-commit-tree: $?\n" if $?;
-- 
1.3.3.g3408

^ permalink raw reply related

* [PATCH 1/2] cvsimport: use git-update-index --index-info
From: Jeff King @ 2006-05-23  7:27 UTC (permalink / raw)
  To: git; +Cc: martin, junkio
In-Reply-To: <20060523070007.GC6180@coredump.intra.peff.net>

This should reduce the number of git-update-index forks required per
commit. We now do adds/removes in one call, and we are no longer forced to
deal with argv limitations.

---

This is a repost using -z/NUL instead of line feeds.

d82d215430ae5e79210f73a31f5f8a053f36c27f
 git-cvsimport.perl |   36 +++++++++++++-----------------------
 1 files changed, 13 insertions(+), 23 deletions(-)

d82d215430ae5e79210f73a31f5f8a053f36c27f
diff --git a/git-cvsimport.perl b/git-cvsimport.perl
index d257e66..a65bea6 100755
--- a/git-cvsimport.perl
+++ b/git-cvsimport.perl
@@ -565,29 +565,19 @@ my($patchset,$date,$author_name,$author_
 my(@old,@new,@skipped);
 sub commit {
 	my $pid;
-	while(@old) {
-		my @o2;
-		if(@old > 55) {
-			@o2 = splice(@old,0,50);
-		} else {
-			@o2 = @old;
-			@old = ();
-		}
-		system("git-update-index","--force-remove","--",@o2);
-		die "Cannot remove files: $?\n" if $?;
-	}
-	while(@new) {
-		my @n2;
-		if(@new > 12) {
-			@n2 = splice(@new,0,10);
-		} else {
-			@n2 = @new;
-			@new = ();
-		}
-		system("git-update-index","--add",
-			(map { ('--cacheinfo', @$_) } @n2));
-		die "Cannot add files: $?\n" if $?;
-	}
+
+	open(my $fh, '|-', qw(git-update-index -z --index-info))
+		or die "unable to open git-update-index: $!";
+	print $fh 
+		(map { "0 0000000000000000000000000000000000000000\t$_\0" }
+			@old),
+		(map { '100' . sprintf('%o', $_->[0]) . " $_->[1]\t$_->[2]\0" }
+			@new)
+		or die "unable to write to git-update-index: $!";
+	close $fh
+		or die "unable to write to git-update-index: $!";
+	$? and die "git-update-index reported error: $?";
+	@old = @new = ();
 
 	$pid = open(C,"-|");
 	die "Cannot fork: $!" unless defined $pid;
-- 
1.3.3.g3408

^ permalink raw reply related

* [PATCH] cvsimport: introduce _fetchfile() method and used a 1M buffer to read()
From: Martin Langhoff @ 2006-05-23  8:08 UTC (permalink / raw)
  To: git; +Cc: Martin Langhoff

File retrieval from the socket is now moved to _fetchfile() and we now
cap reads at 1MB. This should limit the memory growth of the cvsimport
process.

Signed-off-by: Martin Langhoff <martin@catalyst.net.nz>

---

 git-cvsimport.perl |   36 +++++++++++++++++++-----------------
 1 files changed, 19 insertions(+), 17 deletions(-)

c06eea55b2b061abe6c09fa0737d6bb87afa9d39
diff --git a/git-cvsimport.perl b/git-cvsimport.perl
index 5b68671..ace7087 100755
--- a/git-cvsimport.perl
+++ b/git-cvsimport.perl
@@ -315,15 +315,7 @@ sub _line {
 			chomp $cnt;
 			die "Duh: Filesize $cnt" if $cnt !~ /^\d+$/;
 			$line="";
-			$res=0;
-			while($cnt) {
-				my $buf;
-				my $num = $self->{'socketi'}->read($buf,$cnt);
-				die "Server: Filesize $cnt: $num: $!\n" if not defined $num or $num<=0;
-				print $fh $buf;
-				$res += $num;
-				$cnt -= $num;
-			}
+			$res = $self->_fetchfile($fh, $cnt);
 		} elsif($line =~ s/^ //) {
 			print $fh $line;
 			$res += length($line);
@@ -335,14 +327,7 @@ sub _line {
 			chomp $cnt;
 			die "Duh: Mbinary $cnt" if $cnt !~ /^\d+$/ or $cnt<1;
 			$line="";
-			while($cnt) {
-				my $buf;
-				my $num = $self->{'socketi'}->read($buf,$cnt);
-				die "S: Mbinary $cnt: $num: $!\n" if not defined $num or $num<=0;
-				print $fh $buf;
-				$res += $num;
-				$cnt -= $num;
-			}
+			$res += $self->_fetchfile($fh, $cnt);
 		} else {
 			chomp $line;
 			if($line eq "ok") {
@@ -384,6 +369,23 @@ sub file {
 
 	return ($name, $res);
 }
+sub _fetchfile {
+	my ($self, $fh, $cnt) = @_;
+	my $res;
+	my $bufsize = 1024 * 1024;
+	while($cnt) {
+	    if ($bufsize > $cnt) {
+		$bufsize = $cnt;
+	    }
+	    my $buf;      
+	    my $num = $self->{'socketi'}->read($buf,$bufsize);
+	    die "Server: Filesize $cnt: $num: $!\n" if not defined $num or $num<=0;
+	    print $fh $buf;
+	    $res += $num;
+	    $cnt -= $num;
+	}
+	return $res;
+}
 
 
 package main;
-- 
1.3.2.g82000

^ permalink raw reply related

* [PATCH] Add git-quiltimport to .gitignore.
From: Peter Eriksen @ 2006-05-23  8:10 UTC (permalink / raw)
  To: git

From: Peter Eriksen <s022018@student.dtu.dk>
Date: Mon, 22 May 2006 15:46:25 +0200

Signed-off-by: Peter Eriksen <s022018@student.dtu.dk>


---

24b65a30015aeddc9e1e90432e34b144cf8a3f30
 .gitignore |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

24b65a30015aeddc9e1e90432e34b144cf8a3f30
diff --git a/.gitignore b/.gitignore
index b5959d6..199cc31 100644
--- a/.gitignore
+++ b/.gitignore
@@ -77,6 +77,7 @@ git-prune
 git-prune-packed
 git-pull
 git-push
+git-quiltimport
 git-read-tree
 git-rebase
 git-receive-pack
-- 
1.3.3.g288c

^ permalink raw reply related

* [PATCH] Set the executable bit on gitMergeCommon.py.
From: Peter Eriksen @ 2006-05-23  8:12 UTC (permalink / raw)
  To: git

From: Peter Eriksen <s022018@student.dtu.dk>
Date: Mon, 22 May 2006 15:35:42 +0200

Signed-off-by: Peter Eriksen <s022018@student.dtu.dk>


---

6465b4c8cc760102ed5771ccded2c02904539475
 0 files changed, 0 insertions(+), 0 deletions(-)
 mode change 100644 => 100755 gitMergeCommon.py

6465b4c8cc760102ed5771ccded2c02904539475
diff --git a/gitMergeCommon.py b/gitMergeCommon.py
old mode 100644
new mode 100755
-- 
1.3.3.g288c

^ permalink raw reply

* Re: [PATCH 2/2] cvsimport: cleanup commit function
From: Martin Langhoff @ 2006-05-23  8:13 UTC (permalink / raw)
  To: Martin Langhoff, Junio C Hamano, Matthias Urlichs, git
In-Reply-To: <20060523070007.GC6180@coredump.intra.peff.net>

Jeff,

good stuff -- aiming at exactly the things that had been nagging me.
Some minor notes on top of what junio's mentioned...

> +    die "unable to open $f: $!" unless $! == POSIX::ENOENT;
> +    return undef;

Heh. Is that the return of the living dead?

> +sub update_index (\@\@) {
> +       my $old = shift;
> +       my $new = shift;

Would it not make more sense to just pass them as plain parameters?

> +       print "Committed patch $patchset ($branch $commit_date)\n" if

Given that we have that -- should we remember it and avoid re-reading
the headref from disk? A %seenheads cache would save us 99.9% of the
hassle.

In related news, I've dealt with file reads from the socket being
memorybound. Should merge ok.

cheers,


martin

^ permalink raw reply

* Make more commands builtin
From: Peter Eriksen @ 2006-05-23  8:23 UTC (permalink / raw)
  To: git


 Makefile                               |   26 +++++++++++++++-----------
 apply.c => builtin-apply.c             |    3 ++-
 commit-tree.c => builtin-commit-tree.c |    3 ++-
 diff-files.c => builtin-diff-files.c   |    3 ++-
 diff-index.c => builtin-diff-index.c   |    3 ++-
 diff-stages.c => builtin-diff-stages.c |    3 ++-
 diff-tree.c => builtin-diff-tree.c     |    3 ++-
 ls-files.c => builtin-ls-files.c       |    3 ++-
 ls-tree.c => builtin-ls-tree.c         |    3 ++-
 read-tree.c => builtin-read-tree.c     |    3 ++-
 show-branch.c => builtin-show-branch.c |    3 ++-
 tar-tree.c => builtin-tar-tree.c       |    3 ++-
 builtin.h                              |   12 ++++++++++++
 git.c                                  |   13 ++++++++++++-

I've tried to follow the trend of making commands builtin.
All patches have the same form.  This is my first use
of git-send-email, so this might come out wrong.

Peter Eriksen <s022018@student.dtu.dk>

^ permalink raw reply

* Re: [PATCH 2/2] cvsimport: cleanup commit function
From: Junio C Hamano @ 2006-05-23  8:24 UTC (permalink / raw)
  To: git
In-Reply-To: <46a038f90605230113x2f6b0e4bq5a2ea97308b495e0@mail.gmail.com>

"Martin Langhoff" <martin.langhoff@gmail.com> writes:

> Jeff,
>
> good stuff -- aiming at exactly the things that had been nagging me.
> Some minor notes on top of what junio's mentioned...
>
>> +    die "unable to open $f: $!" unless $! == POSIX::ENOENT;
>> +    return undef;
>
> Heh. Is that the return of the living dead?

Note the trailing "unless" there.

>> +sub update_index (\@\@) {
>> +       my $old = shift;
>> +       my $new = shift;
>
> Would it not make more sense to just pass them as plain parameters?

Meaning...?  Perl5 can pass only one flat array, so the above is
a standard way to pass two arrays.

>> +       print "Committed patch $patchset ($branch $commit_date)\n" if
>
> Given that we have that -- should we remember it and avoid re-reading
> the headref from disk? A %seenheads cache would save us 99.9% of the
> hassle.
>
> In related news, I've dealt with file reads from the socket being
> memorybound. Should merge ok.

Merged OK, and I think your last suggestion makes sense.  I'll
go to bed after pushing out Jeff's two patches and yours.

^ permalink raw reply

* Make more commands builtin
From: Peter Eriksen @ 2006-05-23  8:31 UTC (permalink / raw)
  To: git

 Makefile                               |   26 +++++++++++++++-----------
 apply.c => builtin-apply.c             |    3 ++-
 commit-tree.c => builtin-commit-tree.c |    3 ++-
 diff-files.c => builtin-diff-files.c   |    3 ++-
 diff-index.c => builtin-diff-index.c   |    3 ++-
 diff-stages.c => builtin-diff-stages.c |    3 ++-
 diff-tree.c => builtin-diff-tree.c     |    3 ++-
 ls-files.c => builtin-ls-files.c       |    3 ++-
 ls-tree.c => builtin-ls-tree.c         |    3 ++-
 read-tree.c => builtin-read-tree.c     |    3 ++-
 show-branch.c => builtin-show-branch.c |    3 ++-
 tar-tree.c => builtin-tar-tree.c       |    3 ++-
 builtin.h                              |   12 ++++++++++++
 git.c                                  |   13 ++++++++++++-

I've tried to follow the trend of making commands builtin.
All patches have the same form.  This is my second use
of git-send-email, so this might come out wrong.

Peter Eriksen <s022018@student.dtu.dk>

^ permalink raw reply

* [PATCH 2/8] Builtin git-ls-tree.
From: Peter Eriksen @ 2006-05-23  8:31 UTC (permalink / raw)
  To: git; +Cc: Peter Eriksen
In-Reply-To: <11483730802025-git-send-email->

From: Peter Eriksen <s022018@student.dtu.dk>

Signed-off-by: Peter Eriksen <s022018@student.dtu.dk>


---

419257801e5bc91fc435bd4ff9eb42aa8063ffbb
 Makefile          |    6 +-
 builtin-ls-tree.c |  156 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 builtin.h         |    1 
 git.c             |    3 +
 ls-tree.c         |  155 -----------------------------------------------------
 5 files changed, 162 insertions(+), 159 deletions(-)
 create mode 100644 builtin-ls-tree.c
 delete mode 100644 ls-tree.c

419257801e5bc91fc435bd4ff9eb42aa8063ffbb
diff --git a/Makefile b/Makefile
index e522730..9b02264 100644
--- a/Makefile
+++ b/Makefile
@@ -155,7 +155,7 @@ PROGRAMS = \
 	git-diff-index$X git-diff-stages$X \
 	git-diff-tree$X git-fetch-pack$X git-fsck-objects$X \
 	git-hash-object$X git-index-pack$X git-local-fetch$X \
-	git-ls-tree$X git-mailinfo$X git-merge-base$X \
+	git-mailinfo$X git-merge-base$X \
 	git-merge-index$X git-mktag$X git-mktree$X git-pack-objects$X git-patch-id$X \
 	git-peek-remote$X git-prune-packed$X git-read-tree$X \
 	git-receive-pack$X git-rev-parse$X \
@@ -171,7 +171,7 @@ PROGRAMS = \
 BUILT_INS = git-log$X git-whatchanged$X git-show$X \
 	git-count-objects$X git-diff$X git-push$X \
 	git-grep$X git-rev-list$X git-check-ref-format$X \
-	git-init-db$X git-ls-files$X
+	git-init-db$X git-ls-files$X git-ls-tree$X
 
 # what 'all' will build and 'install' will install, in gitexecdir
 ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
@@ -220,7 +220,7 @@ LIB_OBJS = \
 BUILTIN_OBJS = \
 	builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \
 	builtin-grep.o builtin-rev-list.o builtin-check-ref-format.o \
-	builtin-init-db.o builtin-ls-files.o
+	builtin-init-db.o builtin-ls-files.o builtin-ls-tree.o
 
 GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
 LIBS = $(GITLIBS) -lz
diff --git a/builtin-ls-tree.c b/builtin-ls-tree.c
new file mode 100644
index 0000000..b515307
--- /dev/null
+++ b/builtin-ls-tree.c
@@ -0,0 +1,156 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "blob.h"
+#include "tree.h"
+#include "quote.h"
+#include "builtin.h"
+
+static int line_termination = '\n';
+#define LS_RECURSIVE 1
+#define LS_TREE_ONLY 2
+#define LS_SHOW_TREES 4
+#define LS_NAME_ONLY 8
+static int abbrev = 0;
+static int ls_options = 0;
+const char **pathspec;
+static int chomp_prefix = 0;
+static const char *prefix;
+
+static const char ls_tree_usage[] =
+	"git-ls-tree [-d] [-r] [-t] [-z] [--name-only] [--name-status] [--full-name] [--abbrev[=<n>]] <tree-ish> [path...]";
+
+static int show_recursive(const char *base, int baselen, const char *pathname)
+{
+	const char **s;
+
+	if (ls_options & LS_RECURSIVE)
+		return 1;
+
+	s = pathspec;
+	if (!s)
+		return 0;
+
+	for (;;) {
+		const char *spec = *s++;
+		int len, speclen;
+
+		if (!spec)
+			return 0;
+		if (strncmp(base, spec, baselen))
+			continue;
+		len = strlen(pathname);
+		spec += baselen;
+		speclen = strlen(spec);
+		if (speclen <= len)
+			continue;
+		if (memcmp(pathname, spec, len))
+			continue;
+		return 1;
+	}
+}
+
+static int show_tree(unsigned char *sha1, const char *base, int baselen,
+		     const char *pathname, unsigned mode, int stage)
+{
+	int retval = 0;
+	const char *type = blob_type;
+
+	if (S_ISDIR(mode)) {
+		if (show_recursive(base, baselen, pathname)) {
+			retval = READ_TREE_RECURSIVE;
+			if (!(ls_options & LS_SHOW_TREES))
+				return retval;
+		}
+		type = tree_type;
+	}
+	else if (ls_options & LS_TREE_ONLY)
+		return 0;
+
+	if (chomp_prefix &&
+	    (baselen < chomp_prefix || memcmp(prefix, base, chomp_prefix)))
+		return 0;
+
+	if (!(ls_options & LS_NAME_ONLY))
+		printf("%06o %s %s\t", mode, type,
+				abbrev ? find_unique_abbrev(sha1,abbrev)
+					: sha1_to_hex(sha1));
+	write_name_quoted(base + chomp_prefix, baselen - chomp_prefix,
+			  pathname,
+			  line_termination, stdout);
+	putchar(line_termination);
+	return retval;
+}
+
+int cmd_ls_tree(int argc, const char **argv, char **envp)
+{
+	unsigned char sha1[20];
+	struct tree *tree;
+
+	prefix = setup_git_directory();
+	git_config(git_default_config);
+	if (prefix && *prefix)
+		chomp_prefix = strlen(prefix);
+	while (1 < argc && argv[1][0] == '-') {
+		switch (argv[1][1]) {
+		case 'z':
+			line_termination = 0;
+			break;
+		case 'r':
+			ls_options |= LS_RECURSIVE;
+			break;
+		case 'd':
+			ls_options |= LS_TREE_ONLY;
+			break;
+		case 't':
+			ls_options |= LS_SHOW_TREES;
+			break;
+		case '-':
+			if (!strcmp(argv[1]+2, "name-only") ||
+			    !strcmp(argv[1]+2, "name-status")) {
+				ls_options |= LS_NAME_ONLY;
+				break;
+			}
+			if (!strcmp(argv[1]+2, "full-name")) {
+				chomp_prefix = 0;
+				break;
+			}
+			if (!strncmp(argv[1]+2, "abbrev=",7)) {
+				abbrev = strtoul(argv[1]+9, NULL, 10);
+				if (abbrev && abbrev < MINIMUM_ABBREV)
+					abbrev = MINIMUM_ABBREV;
+				else if (abbrev > 40)
+					abbrev = 40;
+				break;
+			}
+			if (!strcmp(argv[1]+2, "abbrev")) {
+				abbrev = DEFAULT_ABBREV;
+				break;
+			}
+			/* otherwise fallthru */
+		default:
+			usage(ls_tree_usage);
+		}
+		argc--; argv++;
+	}
+	/* -d -r should imply -t, but -d by itself should not have to. */
+	if ( (LS_TREE_ONLY|LS_RECURSIVE) ==
+	    ((LS_TREE_ONLY|LS_RECURSIVE) & ls_options))
+		ls_options |= LS_SHOW_TREES;
+
+	if (argc < 2)
+		usage(ls_tree_usage);
+	if (get_sha1(argv[1], sha1))
+		die("Not a valid object name %s", argv[1]);
+
+	pathspec = get_pathspec(prefix, argv + 2);
+	tree = parse_tree_indirect(sha1);
+	if (!tree)
+		die("not a tree object");
+	read_tree_recursive(tree, "", 0, 0, pathspec, show_tree);
+
+	return 0;
+}
diff --git a/builtin.h b/builtin.h
index a0713d3..951f206 100644
--- a/builtin.h
+++ b/builtin.h
@@ -28,5 +28,6 @@ extern int cmd_rev_list(int argc, const 
 extern int cmd_check_ref_format(int argc, const char **argv, char **envp);
 extern int cmd_init_db(int argc, const char **argv, char **envp);
 extern int cmd_ls_files(int argc, const char **argv, char **envp);
+extern int cmd_ls_tree(int argc, const char **argv, char **envp);
 
 #endif
diff --git a/git.c b/git.c
index 9cfa9eb..8574775 100644
--- a/git.c
+++ b/git.c
@@ -53,7 +53,8 @@ static void handle_internal_command(int 
 		{ "rev-list", cmd_rev_list },
 		{ "init-db", cmd_init_db },
 		{ "check-ref-format", cmd_check_ref_format },
-		{ "ls-files", cmd_ls_files }
+		{ "ls-files", cmd_ls_files },
+		{ "ls-tree", cmd_ls_tree }
 	};
 	int i;
 
diff --git a/ls-tree.c b/ls-tree.c
deleted file mode 100644
index f2b3bc1..0000000
--- a/ls-tree.c
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * GIT - The information manager from hell
- *
- * Copyright (C) Linus Torvalds, 2005
- */
-#include "cache.h"
-#include "blob.h"
-#include "tree.h"
-#include "quote.h"
-
-static int line_termination = '\n';
-#define LS_RECURSIVE 1
-#define LS_TREE_ONLY 2
-#define LS_SHOW_TREES 4
-#define LS_NAME_ONLY 8
-static int abbrev = 0;
-static int ls_options = 0;
-const char **pathspec;
-static int chomp_prefix = 0;
-static const char *prefix;
-
-static const char ls_tree_usage[] =
-	"git-ls-tree [-d] [-r] [-t] [-z] [--name-only] [--name-status] [--full-name] [--abbrev[=<n>]] <tree-ish> [path...]";
-
-static int show_recursive(const char *base, int baselen, const char *pathname)
-{
-	const char **s;
-
-	if (ls_options & LS_RECURSIVE)
-		return 1;
-
-	s = pathspec;
-	if (!s)
-		return 0;
-
-	for (;;) {
-		const char *spec = *s++;
-		int len, speclen;
-
-		if (!spec)
-			return 0;
-		if (strncmp(base, spec, baselen))
-			continue;
-		len = strlen(pathname);
-		spec += baselen;
-		speclen = strlen(spec);
-		if (speclen <= len)
-			continue;
-		if (memcmp(pathname, spec, len))
-			continue;
-		return 1;
-	}
-}
-
-static int show_tree(unsigned char *sha1, const char *base, int baselen,
-		     const char *pathname, unsigned mode, int stage)
-{
-	int retval = 0;
-	const char *type = blob_type;
-
-	if (S_ISDIR(mode)) {
-		if (show_recursive(base, baselen, pathname)) {
-			retval = READ_TREE_RECURSIVE;
-			if (!(ls_options & LS_SHOW_TREES))
-				return retval;
-		}
-		type = tree_type;
-	}
-	else if (ls_options & LS_TREE_ONLY)
-		return 0;
-
-	if (chomp_prefix &&
-	    (baselen < chomp_prefix || memcmp(prefix, base, chomp_prefix)))
-		return 0;
-
-	if (!(ls_options & LS_NAME_ONLY))
-		printf("%06o %s %s\t", mode, type,
-				abbrev ? find_unique_abbrev(sha1,abbrev)
-					: sha1_to_hex(sha1));
-	write_name_quoted(base + chomp_prefix, baselen - chomp_prefix,
-			  pathname,
-			  line_termination, stdout);
-	putchar(line_termination);
-	return retval;
-}
-
-int main(int argc, const char **argv)
-{
-	unsigned char sha1[20];
-	struct tree *tree;
-
-	prefix = setup_git_directory();
-	git_config(git_default_config);
-	if (prefix && *prefix)
-		chomp_prefix = strlen(prefix);
-	while (1 < argc && argv[1][0] == '-') {
-		switch (argv[1][1]) {
-		case 'z':
-			line_termination = 0;
-			break;
-		case 'r':
-			ls_options |= LS_RECURSIVE;
-			break;
-		case 'd':
-			ls_options |= LS_TREE_ONLY;
-			break;
-		case 't':
-			ls_options |= LS_SHOW_TREES;
-			break;
-		case '-':
-			if (!strcmp(argv[1]+2, "name-only") ||
-			    !strcmp(argv[1]+2, "name-status")) {
-				ls_options |= LS_NAME_ONLY;
-				break;
-			}
-			if (!strcmp(argv[1]+2, "full-name")) {
-				chomp_prefix = 0;
-				break;
-			}
-			if (!strncmp(argv[1]+2, "abbrev=",7)) {
-				abbrev = strtoul(argv[1]+9, NULL, 10);
-				if (abbrev && abbrev < MINIMUM_ABBREV)
-					abbrev = MINIMUM_ABBREV;
-				else if (abbrev > 40)
-					abbrev = 40;
-				break;
-			}
-			if (!strcmp(argv[1]+2, "abbrev")) {
-				abbrev = DEFAULT_ABBREV;
-				break;
-			}
-			/* otherwise fallthru */
-		default:
-			usage(ls_tree_usage);
-		}
-		argc--; argv++;
-	}
-	/* -d -r should imply -t, but -d by itself should not have to. */
-	if ( (LS_TREE_ONLY|LS_RECURSIVE) ==
-	    ((LS_TREE_ONLY|LS_RECURSIVE) & ls_options))
-		ls_options |= LS_SHOW_TREES;
-
-	if (argc < 2)
-		usage(ls_tree_usage);
-	if (get_sha1(argv[1], sha1))
-		die("Not a valid object name %s", argv[1]);
-
-	pathspec = get_pathspec(prefix, argv + 2);
-	tree = parse_tree_indirect(sha1);
-	if (!tree)
-		die("not a tree object");
-	read_tree_recursive(tree, "", 0, 0, pathspec, show_tree);
-
-	return 0;
-}
-- 
1.3.3.g288c

^ permalink raw reply related

* [PATCH 3/8] Builtin git-tar-tree.
From: Peter Eriksen @ 2006-05-23  8:31 UTC (permalink / raw)
  To: git; +Cc: Peter Eriksen
In-Reply-To: <11483730803527-git-send-email->

From: Peter Eriksen <s022018@student.dtu.dk>

Signed-off-by: Peter Eriksen <s022018@student.dtu.dk>


---

9860ed2d4a598ad100c3b4f6b07dd0a88a4547a6
 Makefile           |    8 +
 builtin-tar-tree.c |  351 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 builtin.h          |    1 
 git.c              |    3 
 tar-tree.c         |  350 ----------------------------------------------------
 5 files changed, 359 insertions(+), 354 deletions(-)
 create mode 100644 builtin-tar-tree.c
 delete mode 100644 tar-tree.c

9860ed2d4a598ad100c3b4f6b07dd0a88a4547a6
diff --git a/Makefile b/Makefile
index 9b02264..966f7ee 100644
--- a/Makefile
+++ b/Makefile
@@ -161,7 +161,7 @@ PROGRAMS = \
 	git-receive-pack$X git-rev-parse$X \
 	git-send-pack$X git-show-branch$X git-shell$X \
 	git-show-index$X git-ssh-fetch$X \
-	git-ssh-upload$X git-tar-tree$X git-unpack-file$X \
+	git-ssh-upload$X git-unpack-file$X \
 	git-unpack-objects$X git-update-index$X git-update-server-info$X \
 	git-upload-pack$X git-verify-pack$X git-write-tree$X \
 	git-update-ref$X git-symbolic-ref$X \
@@ -171,7 +171,8 @@ PROGRAMS = \
 BUILT_INS = git-log$X git-whatchanged$X git-show$X \
 	git-count-objects$X git-diff$X git-push$X \
 	git-grep$X git-rev-list$X git-check-ref-format$X \
-	git-init-db$X git-ls-files$X git-ls-tree$X
+	git-init-db$X git-ls-files$X git-ls-tree$X \
+	git-tar-tree$X
 
 # what 'all' will build and 'install' will install, in gitexecdir
 ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
@@ -220,7 +221,8 @@ LIB_OBJS = \
 BUILTIN_OBJS = \
 	builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \
 	builtin-grep.o builtin-rev-list.o builtin-check-ref-format.o \
-	builtin-init-db.o builtin-ls-files.o builtin-ls-tree.o
+	builtin-init-db.o builtin-ls-files.o builtin-ls-tree.o \
+        builtin-tar-tree.o
 
 GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
 LIBS = $(GITLIBS) -lz
diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c
new file mode 100644
index 0000000..6ada04c
--- /dev/null
+++ b/builtin-tar-tree.c
@@ -0,0 +1,351 @@
+/*
+ * Copyright (c) 2005, 2006 Rene Scharfe
+ */
+#include <time.h>
+#include "cache.h"
+#include "tree-walk.h"
+#include "commit.h"
+#include "strbuf.h"
+#include "tar.h"
+#include "builtin.h"
+
+#define RECORDSIZE	(512)
+#define BLOCKSIZE	(RECORDSIZE * 20)
+
+static const char tar_tree_usage[] = "git-tar-tree <key> [basedir]";
+
+static char block[BLOCKSIZE];
+static unsigned long offset;
+
+static time_t archive_time;
+
+/* tries hard to write, either succeeds or dies in the attempt */
+static void reliable_write(void *buf, unsigned long size)
+{
+	while (size > 0) {
+		long ret = xwrite(1, buf, size);
+		if (ret < 0) {
+			if (errno == EPIPE)
+				exit(0);
+			die("git-tar-tree: %s", strerror(errno));
+		} else if (!ret) {
+			die("git-tar-tree: disk full?");
+		}
+		size -= ret;
+		buf += ret;
+	}
+}
+
+/* writes out the whole block, but only if it is full */
+static void write_if_needed(void)
+{
+	if (offset == BLOCKSIZE) {
+		reliable_write(block, BLOCKSIZE);
+		offset = 0;
+	}
+}
+
+/* acquire the next record from the buffer; user must call write_if_needed() */
+static char *get_record(void)
+{
+	char *p = block + offset;
+	memset(p, 0, RECORDSIZE);
+	offset += RECORDSIZE;
+	return p;
+}
+
+/*
+ * The end of tar archives is marked by 1024 nul bytes and after that
+ * follows the rest of the block (if any).
+ */
+static void write_trailer(void)
+{
+	get_record();
+	write_if_needed();
+	get_record();
+	write_if_needed();
+	while (offset) {
+		get_record();
+		write_if_needed();
+	}
+}
+
+/*
+ * queues up writes, so that all our write(2) calls write exactly one
+ * full block; pads writes to RECORDSIZE
+ */
+static void write_blocked(void *buf, unsigned long size)
+{
+	unsigned long tail;
+
+	if (offset) {
+		unsigned long chunk = BLOCKSIZE - offset;
+		if (size < chunk)
+			chunk = size;
+		memcpy(block + offset, buf, chunk);
+		size -= chunk;
+		offset += chunk;
+		buf += chunk;
+		write_if_needed();
+	}
+	while (size >= BLOCKSIZE) {
+		reliable_write(buf, BLOCKSIZE);
+		size -= BLOCKSIZE;
+		buf += BLOCKSIZE;
+	}
+	if (size) {
+		memcpy(block + offset, buf, size);
+		offset += size;
+	}
+	tail = offset % RECORDSIZE;
+	if (tail)  {
+		memset(block + offset, 0, RECORDSIZE - tail);
+		offset += RECORDSIZE - tail;
+	}
+	write_if_needed();
+}
+
+static void strbuf_append_string(struct strbuf *sb, const char *s)
+{
+	int slen = strlen(s);
+	int total = sb->len + slen;
+	if (total > sb->alloc) {
+		sb->buf = xrealloc(sb->buf, total);
+		sb->alloc = total;
+	}
+	memcpy(sb->buf + sb->len, s, slen);
+	sb->len = total;
+}
+
+/*
+ * pax extended header records have the format "%u %s=%s\n".  %u contains
+ * the size of the whole string (including the %u), the first %s is the
+ * keyword, the second one is the value.  This function constructs such a
+ * string and appends it to a struct strbuf.
+ */
+static void strbuf_append_ext_header(struct strbuf *sb, const char *keyword,
+                                     const char *value, unsigned int valuelen)
+{
+	char *p;
+	int len, total, tmp;
+
+	/* "%u %s=%s\n" */
+	len = 1 + 1 + strlen(keyword) + 1 + valuelen + 1;
+	for (tmp = len; tmp > 9; tmp /= 10)
+		len++;
+
+	total = sb->len + len;
+	if (total > sb->alloc) {
+		sb->buf = xrealloc(sb->buf, total);
+		sb->alloc = total;
+	}
+
+	p = sb->buf;
+	p += sprintf(p, "%u %s=", len, keyword);
+	memcpy(p, value, valuelen);
+	p += valuelen;
+	*p = '\n';
+	sb->len = total;
+}
+
+static unsigned int ustar_header_chksum(const struct ustar_header *header)
+{
+	char *p = (char *)header;
+	unsigned int chksum = 0;
+	while (p < header->chksum)
+		chksum += *p++;
+	chksum += sizeof(header->chksum) * ' ';
+	p += sizeof(header->chksum);
+	while (p < (char *)header + sizeof(struct ustar_header))
+		chksum += *p++;
+	return chksum;
+}
+
+static int get_path_prefix(const struct strbuf *path, int maxlen)
+{
+	int i = path->len;
+	if (i > maxlen)
+		i = maxlen;
+	while (i > 0 && path->buf[i] != '/')
+		i--;
+	return i;
+}
+
+static void write_entry(const unsigned char *sha1, struct strbuf *path,
+                        unsigned int mode, void *buffer, unsigned long size)
+{
+	struct ustar_header header;
+	struct strbuf ext_header;
+
+	memset(&header, 0, sizeof(header));
+	ext_header.buf = NULL;
+	ext_header.len = ext_header.alloc = 0;
+
+	if (!sha1) {
+		*header.typeflag = TYPEFLAG_GLOBAL_HEADER;
+		mode = 0100666;
+		strcpy(header.name, "pax_global_header");
+	} else if (!path) {
+		*header.typeflag = TYPEFLAG_EXT_HEADER;
+		mode = 0100666;
+		sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1));
+	} else {
+		if (S_ISDIR(mode)) {
+			*header.typeflag = TYPEFLAG_DIR;
+			mode |= 0777;
+		} else if (S_ISLNK(mode)) {
+			*header.typeflag = TYPEFLAG_LNK;
+			mode |= 0777;
+		} else if (S_ISREG(mode)) {
+			*header.typeflag = TYPEFLAG_REG;
+			mode |= (mode & 0100) ? 0777 : 0666;
+		} else {
+			error("unsupported file mode: 0%o (SHA1: %s)",
+			      mode, sha1_to_hex(sha1));
+			return;
+		}
+		if (path->len > sizeof(header.name)) {
+			int plen = get_path_prefix(path, sizeof(header.prefix));
+			int rest = path->len - plen - 1;
+			if (plen > 0 && rest <= sizeof(header.name)) {
+				memcpy(header.prefix, path->buf, plen);
+				memcpy(header.name, path->buf + plen + 1, rest);
+			} else {
+				sprintf(header.name, "%s.data",
+				        sha1_to_hex(sha1));
+				strbuf_append_ext_header(&ext_header, "path",
+				                         path->buf, path->len);
+			}
+		} else
+			memcpy(header.name, path->buf, path->len);
+	}
+
+	if (S_ISLNK(mode) && buffer) {
+		if (size > sizeof(header.linkname)) {
+			sprintf(header.linkname, "see %s.paxheader",
+			        sha1_to_hex(sha1));
+			strbuf_append_ext_header(&ext_header, "linkpath",
+			                         buffer, size);
+		} else
+			memcpy(header.linkname, buffer, size);
+	}
+
+	sprintf(header.mode, "%07o", mode & 07777);
+	sprintf(header.size, "%011lo", S_ISREG(mode) ? size : 0);
+	sprintf(header.mtime, "%011lo", archive_time);
+
+	/* XXX: should we provide more meaningful info here? */
+	sprintf(header.uid, "%07o", 0);
+	sprintf(header.gid, "%07o", 0);
+	strncpy(header.uname, "git", 31);
+	strncpy(header.gname, "git", 31);
+	sprintf(header.devmajor, "%07o", 0);
+	sprintf(header.devminor, "%07o", 0);
+
+	memcpy(header.magic, "ustar", 6);
+	memcpy(header.version, "00", 2);
+
+	sprintf(header.chksum, "%07o", ustar_header_chksum(&header));
+
+	if (ext_header.len > 0) {
+		write_entry(sha1, NULL, 0, ext_header.buf, ext_header.len);
+		free(ext_header.buf);
+	}
+	write_blocked(&header, sizeof(header));
+	if (S_ISREG(mode) && buffer && size > 0)
+		write_blocked(buffer, size);
+}
+
+static void write_global_extended_header(const unsigned char *sha1)
+{
+	struct strbuf ext_header;
+	ext_header.buf = NULL;
+	ext_header.len = ext_header.alloc = 0;
+	strbuf_append_ext_header(&ext_header, "comment", sha1_to_hex(sha1), 40);
+	write_entry(NULL, NULL, 0, ext_header.buf, ext_header.len);
+	free(ext_header.buf);
+}
+
+static void traverse_tree(struct tree_desc *tree, struct strbuf *path)
+{
+	int pathlen = path->len;
+
+	while (tree->size) {
+		const char *name;
+		const unsigned char *sha1;
+		unsigned mode;
+		void *eltbuf;
+		char elttype[20];
+		unsigned long eltsize;
+
+		sha1 = tree_entry_extract(tree, &name, &mode);
+		update_tree_entry(tree);
+
+		eltbuf = read_sha1_file(sha1, elttype, &eltsize);
+		if (!eltbuf)
+			die("cannot read %s", sha1_to_hex(sha1));
+
+		path->len = pathlen;
+		strbuf_append_string(path, name);
+		if (S_ISDIR(mode))
+			strbuf_append_string(path, "/");
+
+		write_entry(sha1, path, mode, eltbuf, eltsize);
+
+		if (S_ISDIR(mode)) {
+			struct tree_desc subtree;
+			subtree.buf = eltbuf;
+			subtree.size = eltsize;
+			traverse_tree(&subtree, path);
+		}
+		free(eltbuf);
+	}
+}
+
+int cmd_tar_tree(int argc, const char **argv, char** envp)
+{
+	unsigned char sha1[20], tree_sha1[20];
+	struct commit *commit;
+	struct tree_desc tree;
+	struct strbuf current_path;
+
+	current_path.buf = xmalloc(PATH_MAX);
+	current_path.alloc = PATH_MAX;
+	current_path.len = current_path.eof = 0;
+
+	setup_git_directory();
+	git_config(git_default_config);
+
+	switch (argc) {
+	case 3:
+		strbuf_append_string(&current_path, argv[2]);
+		strbuf_append_string(&current_path, "/");
+		/* FALLTHROUGH */
+	case 2:
+		if (get_sha1(argv[1], sha1))
+			die("Not a valid object name %s", argv[1]);
+		break;
+	default:
+		usage(tar_tree_usage);
+	}
+
+	commit = lookup_commit_reference_gently(sha1, 1);
+	if (commit) {
+		write_global_extended_header(commit->object.sha1);
+		archive_time = commit->date;
+	} else
+		archive_time = time(NULL);
+
+	tree.buf = read_object_with_reference(sha1, tree_type, &tree.size,
+	                                      tree_sha1);
+	if (!tree.buf)
+		die("not a reference to a tag, commit or tree object: %s",
+		    sha1_to_hex(sha1));
+
+	if (current_path.len > 0)
+		write_entry(tree_sha1, &current_path, 040777, NULL, 0);
+	traverse_tree(&tree, &current_path);
+	write_trailer();
+	free(current_path.buf);
+	return 0;
+}
diff --git a/builtin.h b/builtin.h
index 951f206..d210543 100644
--- a/builtin.h
+++ b/builtin.h
@@ -29,5 +29,6 @@ extern int cmd_check_ref_format(int argc
 extern int cmd_init_db(int argc, const char **argv, char **envp);
 extern int cmd_ls_files(int argc, const char **argv, char **envp);
 extern int cmd_ls_tree(int argc, const char **argv, char **envp);
+extern int cmd_tar_tree(int argc, const char **argv, char **envp);
 
 #endif
diff --git a/git.c b/git.c
index 8574775..c253e60 100644
--- a/git.c
+++ b/git.c
@@ -54,7 +54,8 @@ static void handle_internal_command(int 
 		{ "init-db", cmd_init_db },
 		{ "check-ref-format", cmd_check_ref_format },
 		{ "ls-files", cmd_ls_files },
-		{ "ls-tree", cmd_ls_tree }
+		{ "ls-tree", cmd_ls_tree },
+		{ "tar-tree", cmd_tar_tree }
 	};
 	int i;
 
diff --git a/tar-tree.c b/tar-tree.c
deleted file mode 100644
index 3308736..0000000
--- a/tar-tree.c
+++ /dev/null
@@ -1,350 +0,0 @@
-/*
- * Copyright (c) 2005, 2006 Rene Scharfe
- */
-#include <time.h>
-#include "cache.h"
-#include "tree-walk.h"
-#include "commit.h"
-#include "strbuf.h"
-#include "tar.h"
-
-#define RECORDSIZE	(512)
-#define BLOCKSIZE	(RECORDSIZE * 20)
-
-static const char tar_tree_usage[] = "git-tar-tree <key> [basedir]";
-
-static char block[BLOCKSIZE];
-static unsigned long offset;
-
-static time_t archive_time;
-
-/* tries hard to write, either succeeds or dies in the attempt */
-static void reliable_write(void *buf, unsigned long size)
-{
-	while (size > 0) {
-		long ret = xwrite(1, buf, size);
-		if (ret < 0) {
-			if (errno == EPIPE)
-				exit(0);
-			die("git-tar-tree: %s", strerror(errno));
-		} else if (!ret) {
-			die("git-tar-tree: disk full?");
-		}
-		size -= ret;
-		buf += ret;
-	}
-}
-
-/* writes out the whole block, but only if it is full */
-static void write_if_needed(void)
-{
-	if (offset == BLOCKSIZE) {
-		reliable_write(block, BLOCKSIZE);
-		offset = 0;
-	}
-}
-
-/* acquire the next record from the buffer; user must call write_if_needed() */
-static char *get_record(void)
-{
-	char *p = block + offset;
-	memset(p, 0, RECORDSIZE);
-	offset += RECORDSIZE;
-	return p;
-}
-
-/*
- * The end of tar archives is marked by 1024 nul bytes and after that
- * follows the rest of the block (if any).
- */
-static void write_trailer(void)
-{
-	get_record();
-	write_if_needed();
-	get_record();
-	write_if_needed();
-	while (offset) {
-		get_record();
-		write_if_needed();
-	}
-}
-
-/*
- * queues up writes, so that all our write(2) calls write exactly one
- * full block; pads writes to RECORDSIZE
- */
-static void write_blocked(void *buf, unsigned long size)
-{
-	unsigned long tail;
-
-	if (offset) {
-		unsigned long chunk = BLOCKSIZE - offset;
-		if (size < chunk)
-			chunk = size;
-		memcpy(block + offset, buf, chunk);
-		size -= chunk;
-		offset += chunk;
-		buf += chunk;
-		write_if_needed();
-	}
-	while (size >= BLOCKSIZE) {
-		reliable_write(buf, BLOCKSIZE);
-		size -= BLOCKSIZE;
-		buf += BLOCKSIZE;
-	}
-	if (size) {
-		memcpy(block + offset, buf, size);
-		offset += size;
-	}
-	tail = offset % RECORDSIZE;
-	if (tail)  {
-		memset(block + offset, 0, RECORDSIZE - tail);
-		offset += RECORDSIZE - tail;
-	}
-	write_if_needed();
-}
-
-static void strbuf_append_string(struct strbuf *sb, const char *s)
-{
-	int slen = strlen(s);
-	int total = sb->len + slen;
-	if (total > sb->alloc) {
-		sb->buf = xrealloc(sb->buf, total);
-		sb->alloc = total;
-	}
-	memcpy(sb->buf + sb->len, s, slen);
-	sb->len = total;
-}
-
-/*
- * pax extended header records have the format "%u %s=%s\n".  %u contains
- * the size of the whole string (including the %u), the first %s is the
- * keyword, the second one is the value.  This function constructs such a
- * string and appends it to a struct strbuf.
- */
-static void strbuf_append_ext_header(struct strbuf *sb, const char *keyword,
-                                     const char *value, unsigned int valuelen)
-{
-	char *p;
-	int len, total, tmp;
-
-	/* "%u %s=%s\n" */
-	len = 1 + 1 + strlen(keyword) + 1 + valuelen + 1;
-	for (tmp = len; tmp > 9; tmp /= 10)
-		len++;
-
-	total = sb->len + len;
-	if (total > sb->alloc) {
-		sb->buf = xrealloc(sb->buf, total);
-		sb->alloc = total;
-	}
-
-	p = sb->buf;
-	p += sprintf(p, "%u %s=", len, keyword);
-	memcpy(p, value, valuelen);
-	p += valuelen;
-	*p = '\n';
-	sb->len = total;
-}
-
-static unsigned int ustar_header_chksum(const struct ustar_header *header)
-{
-	char *p = (char *)header;
-	unsigned int chksum = 0;
-	while (p < header->chksum)
-		chksum += *p++;
-	chksum += sizeof(header->chksum) * ' ';
-	p += sizeof(header->chksum);
-	while (p < (char *)header + sizeof(struct ustar_header))
-		chksum += *p++;
-	return chksum;
-}
-
-static int get_path_prefix(const struct strbuf *path, int maxlen)
-{
-	int i = path->len;
-	if (i > maxlen)
-		i = maxlen;
-	while (i > 0 && path->buf[i] != '/')
-		i--;
-	return i;
-}
-
-static void write_entry(const unsigned char *sha1, struct strbuf *path,
-                        unsigned int mode, void *buffer, unsigned long size)
-{
-	struct ustar_header header;
-	struct strbuf ext_header;
-
-	memset(&header, 0, sizeof(header));
-	ext_header.buf = NULL;
-	ext_header.len = ext_header.alloc = 0;
-
-	if (!sha1) {
-		*header.typeflag = TYPEFLAG_GLOBAL_HEADER;
-		mode = 0100666;
-		strcpy(header.name, "pax_global_header");
-	} else if (!path) {
-		*header.typeflag = TYPEFLAG_EXT_HEADER;
-		mode = 0100666;
-		sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1));
-	} else {
-		if (S_ISDIR(mode)) {
-			*header.typeflag = TYPEFLAG_DIR;
-			mode |= 0777;
-		} else if (S_ISLNK(mode)) {
-			*header.typeflag = TYPEFLAG_LNK;
-			mode |= 0777;
-		} else if (S_ISREG(mode)) {
-			*header.typeflag = TYPEFLAG_REG;
-			mode |= (mode & 0100) ? 0777 : 0666;
-		} else {
-			error("unsupported file mode: 0%o (SHA1: %s)",
-			      mode, sha1_to_hex(sha1));
-			return;
-		}
-		if (path->len > sizeof(header.name)) {
-			int plen = get_path_prefix(path, sizeof(header.prefix));
-			int rest = path->len - plen - 1;
-			if (plen > 0 && rest <= sizeof(header.name)) {
-				memcpy(header.prefix, path->buf, plen);
-				memcpy(header.name, path->buf + plen + 1, rest);
-			} else {
-				sprintf(header.name, "%s.data",
-				        sha1_to_hex(sha1));
-				strbuf_append_ext_header(&ext_header, "path",
-				                         path->buf, path->len);
-			}
-		} else
-			memcpy(header.name, path->buf, path->len);
-	}
-
-	if (S_ISLNK(mode) && buffer) {
-		if (size > sizeof(header.linkname)) {
-			sprintf(header.linkname, "see %s.paxheader",
-			        sha1_to_hex(sha1));
-			strbuf_append_ext_header(&ext_header, "linkpath",
-			                         buffer, size);
-		} else
-			memcpy(header.linkname, buffer, size);
-	}
-
-	sprintf(header.mode, "%07o", mode & 07777);
-	sprintf(header.size, "%011lo", S_ISREG(mode) ? size : 0);
-	sprintf(header.mtime, "%011lo", archive_time);
-
-	/* XXX: should we provide more meaningful info here? */
-	sprintf(header.uid, "%07o", 0);
-	sprintf(header.gid, "%07o", 0);
-	strncpy(header.uname, "git", 31);
-	strncpy(header.gname, "git", 31);
-	sprintf(header.devmajor, "%07o", 0);
-	sprintf(header.devminor, "%07o", 0);
-
-	memcpy(header.magic, "ustar", 6);
-	memcpy(header.version, "00", 2);
-
-	sprintf(header.chksum, "%07o", ustar_header_chksum(&header));
-
-	if (ext_header.len > 0) {
-		write_entry(sha1, NULL, 0, ext_header.buf, ext_header.len);
-		free(ext_header.buf);
-	}
-	write_blocked(&header, sizeof(header));
-	if (S_ISREG(mode) && buffer && size > 0)
-		write_blocked(buffer, size);
-}
-
-static void write_global_extended_header(const unsigned char *sha1)
-{
-	struct strbuf ext_header;
-	ext_header.buf = NULL;
-	ext_header.len = ext_header.alloc = 0;
-	strbuf_append_ext_header(&ext_header, "comment", sha1_to_hex(sha1), 40);
-	write_entry(NULL, NULL, 0, ext_header.buf, ext_header.len);
-	free(ext_header.buf);
-}
-
-static void traverse_tree(struct tree_desc *tree, struct strbuf *path)
-{
-	int pathlen = path->len;
-
-	while (tree->size) {
-		const char *name;
-		const unsigned char *sha1;
-		unsigned mode;
-		void *eltbuf;
-		char elttype[20];
-		unsigned long eltsize;
-
-		sha1 = tree_entry_extract(tree, &name, &mode);
-		update_tree_entry(tree);
-
-		eltbuf = read_sha1_file(sha1, elttype, &eltsize);
-		if (!eltbuf)
-			die("cannot read %s", sha1_to_hex(sha1));
-
-		path->len = pathlen;
-		strbuf_append_string(path, name);
-		if (S_ISDIR(mode))
-			strbuf_append_string(path, "/");
-
-		write_entry(sha1, path, mode, eltbuf, eltsize);
-
-		if (S_ISDIR(mode)) {
-			struct tree_desc subtree;
-			subtree.buf = eltbuf;
-			subtree.size = eltsize;
-			traverse_tree(&subtree, path);
-		}
-		free(eltbuf);
-	}
-}
-
-int main(int argc, char **argv)
-{
-	unsigned char sha1[20], tree_sha1[20];
-	struct commit *commit;
-	struct tree_desc tree;
-	struct strbuf current_path;
-
-	current_path.buf = xmalloc(PATH_MAX);
-	current_path.alloc = PATH_MAX;
-	current_path.len = current_path.eof = 0;
-
-	setup_git_directory();
-	git_config(git_default_config);
-
-	switch (argc) {
-	case 3:
-		strbuf_append_string(&current_path, argv[2]);
-		strbuf_append_string(&current_path, "/");
-		/* FALLTHROUGH */
-	case 2:
-		if (get_sha1(argv[1], sha1))
-			die("Not a valid object name %s", argv[1]);
-		break;
-	default:
-		usage(tar_tree_usage);
-	}
-
-	commit = lookup_commit_reference_gently(sha1, 1);
-	if (commit) {
-		write_global_extended_header(commit->object.sha1);
-		archive_time = commit->date;
-	} else
-		archive_time = time(NULL);
-
-	tree.buf = read_object_with_reference(sha1, tree_type, &tree.size,
-	                                      tree_sha1);
-	if (!tree.buf)
-		die("not a reference to a tag, commit or tree object: %s",
-		    sha1_to_hex(sha1));
-
-	if (current_path.len > 0)
-		write_entry(tree_sha1, &current_path, 040777, NULL, 0);
-	traverse_tree(&tree, &current_path);
-	write_trailer();
-	free(current_path.buf);
-	return 0;
-}
-- 
1.3.3.g288c

^ permalink raw reply related

* [PATCH 5/8] Builtin git-commit-tree.
From: Peter Eriksen @ 2006-05-23  8:31 UTC (permalink / raw)
  To: git; +Cc: Peter Eriksen
In-Reply-To: <11483730804133-git-send-email->

From: Peter Eriksen <s022018@student.dtu.dk>

Signed-off-by: Peter Eriksen <s022018@student.dtu.dk>


---

762e5a2efcde796d84c573fe8bf3224c9fbf3588
 Makefile              |    6 +-
 builtin-commit-tree.c |  140 +++++++++++++++++++++++++++++++++++++++++++++++++
 builtin.h             |    1 
 commit-tree.c         |  139 -------------------------------------------------
 git.c                 |    3 +
 5 files changed, 146 insertions(+), 143 deletions(-)
 create mode 100644 builtin-commit-tree.c
 delete mode 100644 commit-tree.c

762e5a2efcde796d84c573fe8bf3224c9fbf3588
diff --git a/Makefile b/Makefile
index 667fa5d..a5efbc7 100644
--- a/Makefile
+++ b/Makefile
@@ -150,7 +150,7 @@ SIMPLE_PROGRAMS = \
 # ... and all the rest that could be moved out of bindir to gitexecdir
 PROGRAMS = \
 	git-apply$X git-cat-file$X \
-	git-checkout-index$X git-clone-pack$X git-commit-tree$X \
+	git-checkout-index$X git-clone-pack$X \
 	git-convert-objects$X git-diff-files$X \
 	git-diff-index$X git-diff-stages$X \
 	git-diff-tree$X git-fetch-pack$X git-fsck-objects$X \
@@ -172,7 +172,7 @@ BUILT_INS = git-log$X git-whatchanged$X 
 	git-count-objects$X git-diff$X git-push$X \
 	git-grep$X git-rev-list$X git-check-ref-format$X \
 	git-init-db$X git-ls-files$X git-ls-tree$X \
-	git-tar-tree$X git-read-tree$X
+	git-tar-tree$X git-read-tree$X git-commit-tree$X
 
 # what 'all' will build and 'install' will install, in gitexecdir
 ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
@@ -222,7 +222,7 @@ BUILTIN_OBJS = \
 	builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \
 	builtin-grep.o builtin-rev-list.o builtin-check-ref-format.o \
 	builtin-init-db.o builtin-ls-files.o builtin-ls-tree.o \
-        builtin-tar-tree.o builtin-read-tree.o
+        builtin-tar-tree.o builtin-read-tree.o builtin-commit-tree.o
 
 GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
 LIBS = $(GITLIBS) -lz
diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c
new file mode 100644
index 0000000..4ccdbec
--- /dev/null
+++ b/builtin-commit-tree.c
@@ -0,0 +1,140 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "commit.h"
+#include "tree.h"
+#include "builtin.h"
+
+#define BLOCKING (1ul << 14)
+
+/*
+ * FIXME! Share the code with "write-tree.c"
+ */
+static void init_buffer(char **bufp, unsigned int *sizep)
+{
+	char *buf = xmalloc(BLOCKING);
+	*sizep = 0;
+	*bufp = buf;
+}
+
+static void add_buffer(char **bufp, unsigned int *sizep, const char *fmt, ...)
+{
+	char one_line[2048];
+	va_list args;
+	int len;
+	unsigned long alloc, size, newsize;
+	char *buf;
+
+	va_start(args, fmt);
+	len = vsnprintf(one_line, sizeof(one_line), fmt, args);
+	va_end(args);
+	size = *sizep;
+	newsize = size + len;
+	alloc = (size + 32767) & ~32767;
+	buf = *bufp;
+	if (newsize > alloc) {
+		alloc = (newsize + 32767) & ~32767;
+		buf = xrealloc(buf, alloc);
+		*bufp = buf;
+	}
+	*sizep = newsize;
+	memcpy(buf + size, one_line, len);
+}
+
+static void check_valid(unsigned char *sha1, const char *expect)
+{
+	char type[20];
+
+	if (sha1_object_info(sha1, type, NULL))
+		die("%s is not a valid object", sha1_to_hex(sha1));
+	if (expect && strcmp(type, expect))
+		die("%s is not a valid '%s' object", sha1_to_hex(sha1),
+		    expect);
+}
+
+/*
+ * Having more than two parents is not strange at all, and this is
+ * how multi-way merges are represented.
+ */
+#define MAXPARENT (16)
+static unsigned char parent_sha1[MAXPARENT][20];
+
+static const char commit_tree_usage[] = "git-commit-tree <sha1> [-p <sha1>]* < changelog";
+
+static int new_parent(int idx)
+{
+	int i;
+	unsigned char *sha1 = parent_sha1[idx];
+	for (i = 0; i < idx; i++) {
+		if (!memcmp(parent_sha1[i], sha1, 20)) {
+			error("duplicate parent %s ignored", sha1_to_hex(sha1));
+			return 0;
+		}
+	}
+	return 1;
+}
+
+int cmd_commit_tree(int argc, const char **argv, char **envp)
+{
+	int i;
+	int parents = 0;
+	unsigned char tree_sha1[20];
+	unsigned char commit_sha1[20];
+	char comment[1000];
+	char *buffer;
+	unsigned int size;
+
+	setup_ident();
+	setup_git_directory();
+
+	git_config(git_default_config);
+
+	if (argc < 2)
+		usage(commit_tree_usage);
+	if (get_sha1(argv[1], tree_sha1))
+		die("Not a valid object name %s", argv[1]);
+
+	check_valid(tree_sha1, tree_type);
+	for (i = 2; i < argc; i += 2) {
+		char *a, *b;
+		a = argv[i]; b = argv[i+1];
+		if (!b || strcmp(a, "-p"))
+			usage(commit_tree_usage);
+		if (get_sha1(b, parent_sha1[parents]))
+			die("Not a valid object name %s", b);
+		check_valid(parent_sha1[parents], commit_type);
+		if (new_parent(parents))
+			parents++;
+	}
+	if (!parents)
+		fprintf(stderr, "Committing initial tree %s\n", argv[1]);
+
+	init_buffer(&buffer, &size);
+	add_buffer(&buffer, &size, "tree %s\n", sha1_to_hex(tree_sha1));
+
+	/*
+	 * NOTE! This ordering means that the same exact tree merged with a
+	 * different order of parents will be a _different_ changeset even
+	 * if everything else stays the same.
+	 */
+	for (i = 0; i < parents; i++)
+		add_buffer(&buffer, &size, "parent %s\n", sha1_to_hex(parent_sha1[i]));
+
+	/* Person/date information */
+	add_buffer(&buffer, &size, "author %s\n", git_author_info(1));
+	add_buffer(&buffer, &size, "committer %s\n\n", git_committer_info(1));
+
+	/* And add the comment */
+	while (fgets(comment, sizeof(comment), stdin) != NULL)
+		add_buffer(&buffer, &size, "%s", comment);
+
+	if (!write_sha1_file(buffer, size, commit_type, commit_sha1)) {
+		printf("%s\n", sha1_to_hex(commit_sha1));
+		return 0;
+	}
+	else
+		return 1;
+}
diff --git a/builtin.h b/builtin.h
index 88b3523..c6b07d9 100644
--- a/builtin.h
+++ b/builtin.h
@@ -31,5 +31,6 @@ extern int cmd_ls_files(int argc, const 
 extern int cmd_ls_tree(int argc, const char **argv, char **envp);
 extern int cmd_tar_tree(int argc, const char **argv, char **envp);
 extern int cmd_read_tree(int argc, const char **argv, char **envp);
+extern int cmd_commit_tree(int argc, const char **argv, char **envp);
 
 #endif
diff --git a/commit-tree.c b/commit-tree.c
deleted file mode 100644
index 0320036..0000000
--- a/commit-tree.c
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * GIT - The information manager from hell
- *
- * Copyright (C) Linus Torvalds, 2005
- */
-#include "cache.h"
-#include "commit.h"
-#include "tree.h"
-
-#define BLOCKING (1ul << 14)
-
-/*
- * FIXME! Share the code with "write-tree.c"
- */
-static void init_buffer(char **bufp, unsigned int *sizep)
-{
-	char *buf = xmalloc(BLOCKING);
-	*sizep = 0;
-	*bufp = buf;
-}
-
-static void add_buffer(char **bufp, unsigned int *sizep, const char *fmt, ...)
-{
-	char one_line[2048];
-	va_list args;
-	int len;
-	unsigned long alloc, size, newsize;
-	char *buf;
-
-	va_start(args, fmt);
-	len = vsnprintf(one_line, sizeof(one_line), fmt, args);
-	va_end(args);
-	size = *sizep;
-	newsize = size + len;
-	alloc = (size + 32767) & ~32767;
-	buf = *bufp;
-	if (newsize > alloc) {
-		alloc = (newsize + 32767) & ~32767;
-		buf = xrealloc(buf, alloc);
-		*bufp = buf;
-	}
-	*sizep = newsize;
-	memcpy(buf + size, one_line, len);
-}
-
-static void check_valid(unsigned char *sha1, const char *expect)
-{
-	char type[20];
-
-	if (sha1_object_info(sha1, type, NULL))
-		die("%s is not a valid object", sha1_to_hex(sha1));
-	if (expect && strcmp(type, expect))
-		die("%s is not a valid '%s' object", sha1_to_hex(sha1),
-		    expect);
-}
-
-/*
- * Having more than two parents is not strange at all, and this is
- * how multi-way merges are represented.
- */
-#define MAXPARENT (16)
-static unsigned char parent_sha1[MAXPARENT][20];
-
-static const char commit_tree_usage[] = "git-commit-tree <sha1> [-p <sha1>]* < changelog";
-
-static int new_parent(int idx)
-{
-	int i;
-	unsigned char *sha1 = parent_sha1[idx];
-	for (i = 0; i < idx; i++) {
-		if (!memcmp(parent_sha1[i], sha1, 20)) {
-			error("duplicate parent %s ignored", sha1_to_hex(sha1));
-			return 0;
-		}
-	}
-	return 1;
-}
-
-int main(int argc, char **argv)
-{
-	int i;
-	int parents = 0;
-	unsigned char tree_sha1[20];
-	unsigned char commit_sha1[20];
-	char comment[1000];
-	char *buffer;
-	unsigned int size;
-
-	setup_ident();
-	setup_git_directory();
-
-	git_config(git_default_config);
-
-	if (argc < 2)
-		usage(commit_tree_usage);
-	if (get_sha1(argv[1], tree_sha1))
-		die("Not a valid object name %s", argv[1]);
-
-	check_valid(tree_sha1, tree_type);
-	for (i = 2; i < argc; i += 2) {
-		char *a, *b;
-		a = argv[i]; b = argv[i+1];
-		if (!b || strcmp(a, "-p"))
-			usage(commit_tree_usage);
-		if (get_sha1(b, parent_sha1[parents]))
-			die("Not a valid object name %s", b);
-		check_valid(parent_sha1[parents], commit_type);
-		if (new_parent(parents))
-			parents++;
-	}
-	if (!parents)
-		fprintf(stderr, "Committing initial tree %s\n", argv[1]);
-
-	init_buffer(&buffer, &size);
-	add_buffer(&buffer, &size, "tree %s\n", sha1_to_hex(tree_sha1));
-
-	/*
-	 * NOTE! This ordering means that the same exact tree merged with a
-	 * different order of parents will be a _different_ changeset even
-	 * if everything else stays the same.
-	 */
-	for (i = 0; i < parents; i++)
-		add_buffer(&buffer, &size, "parent %s\n", sha1_to_hex(parent_sha1[i]));
-
-	/* Person/date information */
-	add_buffer(&buffer, &size, "author %s\n", git_author_info(1));
-	add_buffer(&buffer, &size, "committer %s\n\n", git_committer_info(1));
-
-	/* And add the comment */
-	while (fgets(comment, sizeof(comment), stdin) != NULL)
-		add_buffer(&buffer, &size, "%s", comment);
-
-	if (!write_sha1_file(buffer, size, commit_type, commit_sha1)) {
-		printf("%s\n", sha1_to_hex(commit_sha1));
-		return 0;
-	}
-	else
-		return 1;
-}
diff --git a/git.c b/git.c
index 300e2b2..4c2c062 100644
--- a/git.c
+++ b/git.c
@@ -56,7 +56,8 @@ static void handle_internal_command(int 
 		{ "ls-files", cmd_ls_files },
 		{ "ls-tree", cmd_ls_tree },
 		{ "tar-tree", cmd_tar_tree },
-		{ "read-tree", cmd_read_tree }
+		{ "read-tree", cmd_read_tree },
+		{ "commit-tree", cmd_commit_tree }
 	};
 	int i;
 
-- 
1.3.3.g288c

^ permalink raw reply related

* [PATCH 4/8] Builtin git-read-tree.
From: Peter Eriksen @ 2006-05-23  8:31 UTC (permalink / raw)
  To: git; +Cc: Peter Eriksen
In-Reply-To: <11483730801352-git-send-email->

From: Peter Eriksen <s022018@student.dtu.dk>

Signed-off-by: Peter Eriksen <s022018@student.dtu.dk>


---

baba8c18d8a5fff876d16a434b49677cd3ebbdb0
 Makefile            |    6 
 builtin-read-tree.c |  882 +++++++++++++++++++++++++++++++++++++++++++++++++++
 builtin.h           |    1 
 git.c               |    3 
 read-tree.c         |  881 ---------------------------------------------------
 5 files changed, 888 insertions(+), 885 deletions(-)
 create mode 100644 builtin-read-tree.c
 delete mode 100644 read-tree.c

baba8c18d8a5fff876d16a434b49677cd3ebbdb0
diff --git a/Makefile b/Makefile
index 966f7ee..667fa5d 100644
--- a/Makefile
+++ b/Makefile
@@ -157,7 +157,7 @@ PROGRAMS = \
 	git-hash-object$X git-index-pack$X git-local-fetch$X \
 	git-mailinfo$X git-merge-base$X \
 	git-merge-index$X git-mktag$X git-mktree$X git-pack-objects$X git-patch-id$X \
-	git-peek-remote$X git-prune-packed$X git-read-tree$X \
+	git-peek-remote$X git-prune-packed$X \
 	git-receive-pack$X git-rev-parse$X \
 	git-send-pack$X git-show-branch$X git-shell$X \
 	git-show-index$X git-ssh-fetch$X \
@@ -172,7 +172,7 @@ BUILT_INS = git-log$X git-whatchanged$X 
 	git-count-objects$X git-diff$X git-push$X \
 	git-grep$X git-rev-list$X git-check-ref-format$X \
 	git-init-db$X git-ls-files$X git-ls-tree$X \
-	git-tar-tree$X
+	git-tar-tree$X git-read-tree$X
 
 # what 'all' will build and 'install' will install, in gitexecdir
 ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
@@ -222,7 +222,7 @@ BUILTIN_OBJS = \
 	builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \
 	builtin-grep.o builtin-rev-list.o builtin-check-ref-format.o \
 	builtin-init-db.o builtin-ls-files.o builtin-ls-tree.o \
-        builtin-tar-tree.o
+        builtin-tar-tree.o builtin-read-tree.o
 
 GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
 LIBS = $(GITLIBS) -lz
diff --git a/builtin-read-tree.c b/builtin-read-tree.c
new file mode 100644
index 0000000..ec40d01
--- /dev/null
+++ b/builtin-read-tree.c
@@ -0,0 +1,882 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#define DBRT_DEBUG 1
+
+#include "cache.h"
+
+#include "object.h"
+#include "tree.h"
+#include <sys/time.h>
+#include <signal.h>
+#include "builtin.h"
+
+static int reset = 0;
+static int merge = 0;
+static int update = 0;
+static int index_only = 0;
+static int nontrivial_merge = 0;
+static int trivial_merges_only = 0;
+static int aggressive = 0;
+static int verbose_update = 0;
+static volatile int progress_update = 0;
+
+static int head_idx = -1;
+static int merge_size = 0;
+
+static struct object_list *trees = NULL;
+
+static struct cache_entry df_conflict_entry = { 
+};
+
+static struct tree_entry_list df_conflict_list = {
+	.name = NULL,
+	.next = &df_conflict_list
+};
+
+typedef int (*merge_fn_t)(struct cache_entry **src);
+
+static int entcmp(char *name1, int dir1, char *name2, int dir2)
+{
+	int len1 = strlen(name1);
+	int len2 = strlen(name2);
+	int len = len1 < len2 ? len1 : len2;
+	int ret = memcmp(name1, name2, len);
+	unsigned char c1, c2;
+	if (ret)
+		return ret;
+	c1 = name1[len];
+	c2 = name2[len];
+	if (!c1 && dir1)
+		c1 = '/';
+	if (!c2 && dir2)
+		c2 = '/';
+	ret = (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0;
+	if (c1 && c2 && !ret)
+		ret = len1 - len2;
+	return ret;
+}
+
+static int unpack_trees_rec(struct tree_entry_list **posns, int len,
+			    const char *base, merge_fn_t fn, int *indpos)
+{
+	int baselen = strlen(base);
+	int src_size = len + 1;
+	do {
+		int i;
+		char *first;
+		int firstdir = 0;
+		int pathlen;
+		unsigned ce_size;
+		struct tree_entry_list **subposns;
+		struct cache_entry **src;
+		int any_files = 0;
+		int any_dirs = 0;
+		char *cache_name;
+		int ce_stage;
+
+		/* Find the first name in the input. */
+
+		first = NULL;
+		cache_name = NULL;
+
+		/* Check the cache */
+		if (merge && *indpos < active_nr) {
+			/* This is a bit tricky: */
+			/* If the index has a subdirectory (with
+			 * contents) as the first name, it'll get a
+			 * filename like "foo/bar". But that's after
+			 * "foo", so the entry in trees will get
+			 * handled first, at which point we'll go into
+			 * "foo", and deal with "bar" from the index,
+			 * because the base will be "foo/". The only
+			 * way we can actually have "foo/bar" first of
+			 * all the things is if the trees don't
+			 * contain "foo" at all, in which case we'll
+			 * handle "foo/bar" without going into the
+			 * directory, but that's fine (and will return
+			 * an error anyway, with the added unknown
+			 * file case.
+			 */
+
+			cache_name = active_cache[*indpos]->name;
+			if (strlen(cache_name) > baselen &&
+			    !memcmp(cache_name, base, baselen)) {
+				cache_name += baselen;
+				first = cache_name;
+			} else {
+				cache_name = NULL;
+			}
+		}
+
+#if DBRT_DEBUG > 1
+		if (first)
+			printf("index %s\n", first);
+#endif
+		for (i = 0; i < len; i++) {
+			if (!posns[i] || posns[i] == &df_conflict_list)
+				continue;
+#if DBRT_DEBUG > 1
+			printf("%d %s\n", i + 1, posns[i]->name);
+#endif
+			if (!first || entcmp(first, firstdir,
+					     posns[i]->name, 
+					     posns[i]->directory) > 0) {
+				first = posns[i]->name;
+				firstdir = posns[i]->directory;
+			}
+		}
+		/* No name means we're done */
+		if (!first)
+			return 0;
+
+		pathlen = strlen(first);
+		ce_size = cache_entry_size(baselen + pathlen);
+
+		src = xcalloc(src_size, sizeof(struct cache_entry *));
+
+		subposns = xcalloc(len, sizeof(struct tree_list_entry *));
+
+		if (cache_name && !strcmp(cache_name, first)) {
+			any_files = 1;
+			src[0] = active_cache[*indpos];
+			remove_cache_entry_at(*indpos);
+		}
+
+		for (i = 0; i < len; i++) {
+			struct cache_entry *ce;
+
+			if (!posns[i] ||
+			    (posns[i] != &df_conflict_list &&
+			     strcmp(first, posns[i]->name))) {
+				continue;
+			}
+
+			if (posns[i] == &df_conflict_list) {
+				src[i + merge] = &df_conflict_entry;
+				continue;
+			}
+
+			if (posns[i]->directory) {
+				any_dirs = 1;
+				parse_tree(posns[i]->item.tree);
+				subposns[i] = posns[i]->item.tree->entries;
+				posns[i] = posns[i]->next;
+				src[i + merge] = &df_conflict_entry;
+				continue;
+			}
+
+			if (!merge)
+				ce_stage = 0;
+			else if (i + 1 < head_idx)
+				ce_stage = 1;
+			else if (i + 1 > head_idx)
+				ce_stage = 3;
+			else
+				ce_stage = 2;
+
+			ce = xcalloc(1, ce_size);
+			ce->ce_mode = create_ce_mode(posns[i]->mode);
+			ce->ce_flags = create_ce_flags(baselen + pathlen,
+						       ce_stage);
+			memcpy(ce->name, base, baselen);
+			memcpy(ce->name + baselen, first, pathlen + 1);
+
+			any_files = 1;
+
+			memcpy(ce->sha1, posns[i]->item.any->sha1, 20);
+			src[i + merge] = ce;
+			subposns[i] = &df_conflict_list;
+			posns[i] = posns[i]->next;
+		}
+		if (any_files) {
+			if (merge) {
+				int ret;
+
+#if DBRT_DEBUG > 1
+				printf("%s:\n", first);
+				for (i = 0; i < src_size; i++) {
+					printf(" %d ", i);
+					if (src[i])
+						printf("%s\n", sha1_to_hex(src[i]->sha1));
+					else
+						printf("\n");
+				}
+#endif
+				ret = fn(src);
+				
+#if DBRT_DEBUG > 1
+				printf("Added %d entries\n", ret);
+#endif
+				*indpos += ret;
+			} else {
+				for (i = 0; i < src_size; i++) {
+					if (src[i]) {
+						add_cache_entry(src[i], ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK);
+					}
+				}
+			}
+		}
+		if (any_dirs) {
+			char *newbase = xmalloc(baselen + 2 + pathlen);
+			memcpy(newbase, base, baselen);
+			memcpy(newbase + baselen, first, pathlen);
+			newbase[baselen + pathlen] = '/';
+			newbase[baselen + pathlen + 1] = '\0';
+			if (unpack_trees_rec(subposns, len, newbase, fn,
+					     indpos))
+				return -1;
+			free(newbase);
+		}
+		free(subposns);
+		free(src);
+	} while (1);
+}
+
+static void reject_merge(struct cache_entry *ce)
+{
+	die("Entry '%s' would be overwritten by merge. Cannot merge.", 
+	    ce->name);
+}
+
+/* Unlink the last component and attempt to remove leading
+ * directories, in case this unlink is the removal of the
+ * last entry in the directory -- empty directories are removed.
+ */
+static void unlink_entry(char *name)
+{
+	char *cp, *prev;
+
+	if (unlink(name))
+		return;
+	prev = NULL;
+	while (1) {
+		int status;
+		cp = strrchr(name, '/');
+		if (prev)
+			*prev = '/';
+		if (!cp)
+			break;
+
+		*cp = 0;
+		status = rmdir(name);
+		if (status) {
+			*cp = '/';
+			break;
+		}
+		prev = cp;
+	}
+}
+
+static void progress_interval(int signum)
+{
+	progress_update = 1;
+}
+
+static void setup_progress_signal(void)
+{
+	struct sigaction sa;
+	struct itimerval v;
+
+	memset(&sa, 0, sizeof(sa));
+	sa.sa_handler = progress_interval;
+	sigemptyset(&sa.sa_mask);
+	sa.sa_flags = SA_RESTART;
+	sigaction(SIGALRM, &sa, NULL);
+
+	v.it_interval.tv_sec = 1;
+	v.it_interval.tv_usec = 0;
+	v.it_value = v.it_interval;
+	setitimer(ITIMER_REAL, &v, NULL);
+}
+
+static void check_updates(struct cache_entry **src, int nr)
+{
+	static struct checkout state = {
+		.base_dir = "",
+		.force = 1,
+		.quiet = 1,
+		.refresh_cache = 1,
+	};
+	unsigned short mask = htons(CE_UPDATE);
+	unsigned last_percent = 200, cnt = 0, total = 0;
+
+	if (update && verbose_update) {
+		for (total = cnt = 0; cnt < nr; cnt++) {
+			struct cache_entry *ce = src[cnt];
+			if (!ce->ce_mode || ce->ce_flags & mask)
+				total++;
+		}
+
+		/* Don't bother doing this for very small updates */
+		if (total < 250)
+			total = 0;
+
+		if (total) {
+			fprintf(stderr, "Checking files out...\n");
+			setup_progress_signal();
+			progress_update = 1;
+		}
+		cnt = 0;
+	}
+
+	while (nr--) {
+		struct cache_entry *ce = *src++;
+
+		if (total) {
+			if (!ce->ce_mode || ce->ce_flags & mask) {
+				unsigned percent;
+				cnt++;
+				percent = (cnt * 100) / total;
+				if (percent != last_percent ||
+				    progress_update) {
+					fprintf(stderr, "%4u%% (%u/%u) done\r",
+						percent, cnt, total);
+					last_percent = percent;
+				}
+			}
+		}
+		if (!ce->ce_mode) {
+			if (update)
+				unlink_entry(ce->name);
+			continue;
+		}
+		if (ce->ce_flags & mask) {
+			ce->ce_flags &= ~mask;
+			if (update)
+				checkout_entry(ce, &state, NULL);
+		}
+	}
+	if (total) {
+		signal(SIGALRM, SIG_IGN);
+		fputc('\n', stderr);
+	}
+}
+
+static int unpack_trees(merge_fn_t fn)
+{
+	int indpos = 0;
+	unsigned len = object_list_length(trees);
+	struct tree_entry_list **posns;
+	int i;
+	struct object_list *posn = trees;
+	merge_size = len;
+
+	if (len) {
+		posns = xmalloc(len * sizeof(struct tree_entry_list *));
+		for (i = 0; i < len; i++) {
+			posns[i] = ((struct tree *) posn->item)->entries;
+			posn = posn->next;
+		}
+		if (unpack_trees_rec(posns, len, "", fn, &indpos))
+			return -1;
+	}
+
+	if (trivial_merges_only && nontrivial_merge)
+		die("Merge requires file-level merging");
+
+	check_updates(active_cache, active_nr);
+	return 0;
+}
+
+static int list_tree(unsigned char *sha1)
+{
+	struct tree *tree = parse_tree_indirect(sha1);
+	if (!tree)
+		return -1;
+	object_list_append(&tree->object, &trees);
+	return 0;
+}
+
+static int same(struct cache_entry *a, struct cache_entry *b)
+{
+	if (!!a != !!b)
+		return 0;
+	if (!a && !b)
+		return 1;
+	return a->ce_mode == b->ce_mode && 
+		!memcmp(a->sha1, b->sha1, 20);
+}
+
+
+/*
+ * When a CE gets turned into an unmerged entry, we
+ * want it to be up-to-date
+ */
+static void verify_uptodate(struct cache_entry *ce)
+{
+	struct stat st;
+
+	if (index_only || reset)
+		return;
+
+	if (!lstat(ce->name, &st)) {
+		unsigned changed = ce_match_stat(ce, &st, 1);
+		if (!changed)
+			return;
+		errno = 0;
+	}
+	if (reset) {
+		ce->ce_flags |= htons(CE_UPDATE);
+		return;
+	}
+	if (errno == ENOENT)
+		return;
+	die("Entry '%s' not uptodate. Cannot merge.", ce->name);
+}
+
+/*
+ * We do not want to remove or overwrite a working tree file that
+ * is not tracked.
+ */
+static void verify_absent(const char *path, const char *action)
+{
+	struct stat st;
+
+	if (index_only || reset || !update)
+		return;
+	if (!lstat(path, &st))
+		die("Untracked working tree file '%s' "
+		    "would be %s by merge.", path, action);
+}
+
+static int merged_entry(struct cache_entry *merge, struct cache_entry *old)
+{
+	merge->ce_flags |= htons(CE_UPDATE);
+	if (old) {
+		/*
+		 * See if we can re-use the old CE directly?
+		 * That way we get the uptodate stat info.
+		 *
+		 * This also removes the UPDATE flag on
+		 * a match.
+		 */
+		if (same(old, merge)) {
+			*merge = *old;
+		} else {
+			verify_uptodate(old);
+		}
+	}
+	else
+		verify_absent(merge->name, "overwritten");
+
+	merge->ce_flags &= ~htons(CE_STAGEMASK);
+	add_cache_entry(merge, ADD_CACHE_OK_TO_ADD);
+	return 1;
+}
+
+static int deleted_entry(struct cache_entry *ce, struct cache_entry *old)
+{
+	if (old)
+		verify_uptodate(old);
+	else
+		verify_absent(ce->name, "removed");
+	ce->ce_mode = 0;
+	add_cache_entry(ce, ADD_CACHE_OK_TO_ADD);
+	return 1;
+}
+
+static int keep_entry(struct cache_entry *ce)
+{
+	add_cache_entry(ce, ADD_CACHE_OK_TO_ADD);
+	return 1;
+}
+
+#if DBRT_DEBUG
+static void show_stage_entry(FILE *o,
+			     const char *label, const struct cache_entry *ce)
+{
+	if (!ce)
+		fprintf(o, "%s (missing)\n", label);
+	else
+		fprintf(o, "%s%06o %s %d\t%s\n",
+			label,
+			ntohl(ce->ce_mode),
+			sha1_to_hex(ce->sha1),
+			ce_stage(ce),
+			ce->name);
+}
+#endif
+
+static int threeway_merge(struct cache_entry **stages)
+{
+	struct cache_entry *index;
+	struct cache_entry *head; 
+	struct cache_entry *remote = stages[head_idx + 1];
+	int count;
+	int head_match = 0;
+	int remote_match = 0;
+	const char *path = NULL;
+
+	int df_conflict_head = 0;
+	int df_conflict_remote = 0;
+
+	int any_anc_missing = 0;
+	int no_anc_exists = 1;
+	int i;
+
+	for (i = 1; i < head_idx; i++) {
+		if (!stages[i])
+			any_anc_missing = 1;
+		else {
+			if (!path)
+				path = stages[i]->name;
+			no_anc_exists = 0;
+		}
+	}
+
+	index = stages[0];
+	head = stages[head_idx];
+
+	if (head == &df_conflict_entry) {
+		df_conflict_head = 1;
+		head = NULL;
+	}
+
+	if (remote == &df_conflict_entry) {
+		df_conflict_remote = 1;
+		remote = NULL;
+	}
+
+	if (!path && index)
+		path = index->name;
+	if (!path && head)
+		path = head->name;
+	if (!path && remote)
+		path = remote->name;
+
+	/* First, if there's a #16 situation, note that to prevent #13
+	 * and #14.
+	 */
+	if (!same(remote, head)) {
+		for (i = 1; i < head_idx; i++) {
+			if (same(stages[i], head)) {
+				head_match = i;
+			}
+			if (same(stages[i], remote)) {
+				remote_match = i;
+			}
+		}
+	}
+
+	/* We start with cases where the index is allowed to match
+	 * something other than the head: #14(ALT) and #2ALT, where it
+	 * is permitted to match the result instead.
+	 */
+	/* #14, #14ALT, #2ALT */
+	if (remote && !df_conflict_head && head_match && !remote_match) {
+		if (index && !same(index, remote) && !same(index, head))
+			reject_merge(index);
+		return merged_entry(remote, index);
+	}
+	/*
+	 * If we have an entry in the index cache, then we want to
+	 * make sure that it matches head.
+	 */
+	if (index && !same(index, head)) {
+		reject_merge(index);
+	}
+
+	if (head) {
+		/* #5ALT, #15 */
+		if (same(head, remote))
+			return merged_entry(head, index);
+		/* #13, #3ALT */
+		if (!df_conflict_remote && remote_match && !head_match)
+			return merged_entry(head, index);
+	}
+
+	/* #1 */
+	if (!head && !remote && any_anc_missing)
+		return 0;
+
+	/* Under the new "aggressive" rule, we resolve mostly trivial
+	 * cases that we historically had git-merge-one-file resolve.
+	 */
+	if (aggressive) {
+		int head_deleted = !head && !df_conflict_head;
+		int remote_deleted = !remote && !df_conflict_remote;
+		/*
+		 * Deleted in both.
+		 * Deleted in one and unchanged in the other.
+		 */
+		if ((head_deleted && remote_deleted) ||
+		    (head_deleted && remote && remote_match) ||
+		    (remote_deleted && head && head_match)) {
+			if (index)
+				return deleted_entry(index, index);
+			else if (path)
+				verify_absent(path, "removed");
+			return 0;
+		}
+		/*
+		 * Added in both, identically.
+		 */
+		if (no_anc_exists && head && remote && same(head, remote))
+			return merged_entry(head, index);
+
+	}
+
+	/* Below are "no merge" cases, which require that the index be
+	 * up-to-date to avoid the files getting overwritten with
+	 * conflict resolution files. 
+	 */
+	if (index) {
+		verify_uptodate(index);
+	}
+	else if (path)
+		verify_absent(path, "overwritten");
+
+	nontrivial_merge = 1;
+
+	/* #2, #3, #4, #6, #7, #9, #11. */
+	count = 0;
+	if (!head_match || !remote_match) {
+		for (i = 1; i < head_idx; i++) {
+			if (stages[i]) {
+				keep_entry(stages[i]);
+				count++;
+				break;
+			}
+		}
+	}
+#if DBRT_DEBUG
+	else {
+		fprintf(stderr, "read-tree: warning #16 detected\n");
+		show_stage_entry(stderr, "head   ", stages[head_match]);
+		show_stage_entry(stderr, "remote ", stages[remote_match]);
+	}
+#endif
+	if (head) { count += keep_entry(head); }
+	if (remote) { count += keep_entry(remote); }
+	return count;
+}
+
+/*
+ * Two-way merge.
+ *
+ * The rule is to "carry forward" what is in the index without losing
+ * information across a "fast forward", favoring a successful merge
+ * over a merge failure when it makes sense.  For details of the
+ * "carry forward" rule, please see <Documentation/git-read-tree.txt>.
+ *
+ */
+static int twoway_merge(struct cache_entry **src)
+{
+	struct cache_entry *current = src[0];
+	struct cache_entry *oldtree = src[1], *newtree = src[2];
+
+	if (merge_size != 2)
+		return error("Cannot do a twoway merge of %d trees",
+			     merge_size);
+
+	if (current) {
+		if ((!oldtree && !newtree) || /* 4 and 5 */
+		    (!oldtree && newtree &&
+		     same(current, newtree)) || /* 6 and 7 */
+		    (oldtree && newtree &&
+		     same(oldtree, newtree)) || /* 14 and 15 */
+		    (oldtree && newtree &&
+		     !same(oldtree, newtree) && /* 18 and 19*/
+		     same(current, newtree))) {
+			return keep_entry(current);
+		}
+		else if (oldtree && !newtree && same(current, oldtree)) {
+			/* 10 or 11 */
+			return deleted_entry(oldtree, current);
+		}
+		else if (oldtree && newtree &&
+			 same(current, oldtree) && !same(current, newtree)) {
+			/* 20 or 21 */
+			return merged_entry(newtree, current);
+		}
+		else {
+			/* all other failures */
+			if (oldtree)
+				reject_merge(oldtree);
+			if (current)
+				reject_merge(current);
+			if (newtree)
+				reject_merge(newtree);
+			return -1;
+		}
+	}
+	else if (newtree)
+		return merged_entry(newtree, current);
+	else
+		return deleted_entry(oldtree, current);
+}
+
+/*
+ * One-way merge.
+ *
+ * The rule is:
+ * - take the stat information from stage0, take the data from stage1
+ */
+static int oneway_merge(struct cache_entry **src)
+{
+	struct cache_entry *old = src[0];
+	struct cache_entry *a = src[1];
+
+	if (merge_size != 1)
+		return error("Cannot do a oneway merge of %d trees",
+			     merge_size);
+
+	if (!a)
+		return deleted_entry(old, old);
+	if (old && same(old, a)) {
+		if (reset) {
+			struct stat st;
+			if (lstat(old->name, &st) ||
+			    ce_match_stat(old, &st, 1))
+				old->ce_flags |= htons(CE_UPDATE);
+		}
+		return keep_entry(old);
+	}
+	return merged_entry(a, old);
+}
+
+static int read_cache_unmerged(void)
+{
+	int i, deleted;
+	struct cache_entry **dst;
+
+	read_cache();
+	dst = active_cache;
+	deleted = 0;
+	for (i = 0; i < active_nr; i++) {
+		struct cache_entry *ce = active_cache[i];
+		if (ce_stage(ce)) {
+			deleted++;
+			continue;
+		}
+		if (deleted)
+			*dst = ce;
+		dst++;
+	}
+	active_nr -= deleted;
+	return deleted;
+}
+
+static const char read_tree_usage[] = "git-read-tree (<sha> | -m [--aggressive] [-u | -i] <sha1> [<sha2> [<sha3>]])";
+
+static struct cache_file cache_file;
+
+int cmd_read_tree(int argc, const char **argv, char **envp)
+{
+	int i, newfd, stage = 0;
+	unsigned char sha1[20];
+	merge_fn_t fn = NULL;
+
+	setup_git_directory();
+	git_config(git_default_config);
+
+	newfd = hold_index_file_for_update(&cache_file, get_index_file());
+	if (newfd < 0)
+		die("unable to create new cachefile");
+
+	git_config(git_default_config);
+
+	merge = 0;
+	reset = 0;
+	for (i = 1; i < argc; i++) {
+		const char *arg = argv[i];
+
+		/* "-u" means "update", meaning that a merge will update
+		 * the working tree.
+		 */
+		if (!strcmp(arg, "-u")) {
+			update = 1;
+			continue;
+		}
+
+		if (!strcmp(arg, "-v")) {
+			verbose_update = 1;
+			continue;
+		}
+
+		/* "-i" means "index only", meaning that a merge will
+		 * not even look at the working tree.
+		 */
+		if (!strcmp(arg, "-i")) {
+			index_only = 1;
+			continue;
+		}
+
+		/* This differs from "-m" in that we'll silently ignore unmerged entries */
+		if (!strcmp(arg, "--reset")) {
+			if (stage || merge)
+				usage(read_tree_usage);
+			reset = 1;
+			merge = 1;
+			stage = 1;
+			read_cache_unmerged();
+			continue;
+		}
+
+		if (!strcmp(arg, "--trivial")) {
+			trivial_merges_only = 1;
+			continue;
+		}
+
+		if (!strcmp(arg, "--aggressive")) {
+			aggressive = 1;
+			continue;
+		}
+
+		/* "-m" stands for "merge", meaning we start in stage 1 */
+		if (!strcmp(arg, "-m")) {
+			if (stage || merge)
+				usage(read_tree_usage);
+			if (read_cache_unmerged())
+				die("you need to resolve your current index first");
+			stage = 1;
+			merge = 1;
+			continue;
+		}
+
+		/* using -u and -i at the same time makes no sense */
+		if (1 < index_only + update)
+			usage(read_tree_usage);
+
+		if (get_sha1(arg, sha1))
+			die("Not a valid object name %s", arg);
+		if (list_tree(sha1) < 0)
+			die("failed to unpack tree object %s", arg);
+		stage++;
+	}
+	if ((update||index_only) && !merge)
+		usage(read_tree_usage);
+
+	if (merge) {
+		if (stage < 2)
+			die("just how do you expect me to merge %d trees?", stage-1);
+		switch (stage - 1) {
+		case 1:
+			fn = oneway_merge;
+			break;
+		case 2:
+			fn = twoway_merge;
+			break;
+		case 3:
+			fn = threeway_merge;
+			break;
+		default:
+			fn = threeway_merge;
+			break;
+		}
+
+		if (stage - 1 >= 3)
+			head_idx = stage - 2;
+		else
+			head_idx = 1;
+	}
+
+	unpack_trees(fn);
+	if (write_cache(newfd, active_cache, active_nr) ||
+	    commit_index_file(&cache_file))
+		die("unable to write new index file");
+	return 0;
+}
diff --git a/builtin.h b/builtin.h
index d210543..88b3523 100644
--- a/builtin.h
+++ b/builtin.h
@@ -30,5 +30,6 @@ extern int cmd_init_db(int argc, const c
 extern int cmd_ls_files(int argc, const char **argv, char **envp);
 extern int cmd_ls_tree(int argc, const char **argv, char **envp);
 extern int cmd_tar_tree(int argc, const char **argv, char **envp);
+extern int cmd_read_tree(int argc, const char **argv, char **envp);
 
 #endif
diff --git a/git.c b/git.c
index c253e60..300e2b2 100644
--- a/git.c
+++ b/git.c
@@ -55,7 +55,8 @@ static void handle_internal_command(int 
 		{ "check-ref-format", cmd_check_ref_format },
 		{ "ls-files", cmd_ls_files },
 		{ "ls-tree", cmd_ls_tree },
-		{ "tar-tree", cmd_tar_tree }
+		{ "tar-tree", cmd_tar_tree },
+		{ "read-tree", cmd_read_tree }
 	};
 	int i;
 
diff --git a/read-tree.c b/read-tree.c
deleted file mode 100644
index 82e2a9a..0000000
--- a/read-tree.c
+++ /dev/null
@@ -1,881 +0,0 @@
-/*
- * GIT - The information manager from hell
- *
- * Copyright (C) Linus Torvalds, 2005
- */
-#define DBRT_DEBUG 1
-
-#include "cache.h"
-
-#include "object.h"
-#include "tree.h"
-#include <sys/time.h>
-#include <signal.h>
-
-static int reset = 0;
-static int merge = 0;
-static int update = 0;
-static int index_only = 0;
-static int nontrivial_merge = 0;
-static int trivial_merges_only = 0;
-static int aggressive = 0;
-static int verbose_update = 0;
-static volatile int progress_update = 0;
-
-static int head_idx = -1;
-static int merge_size = 0;
-
-static struct object_list *trees = NULL;
-
-static struct cache_entry df_conflict_entry = { 
-};
-
-static struct tree_entry_list df_conflict_list = {
-	.name = NULL,
-	.next = &df_conflict_list
-};
-
-typedef int (*merge_fn_t)(struct cache_entry **src);
-
-static int entcmp(char *name1, int dir1, char *name2, int dir2)
-{
-	int len1 = strlen(name1);
-	int len2 = strlen(name2);
-	int len = len1 < len2 ? len1 : len2;
-	int ret = memcmp(name1, name2, len);
-	unsigned char c1, c2;
-	if (ret)
-		return ret;
-	c1 = name1[len];
-	c2 = name2[len];
-	if (!c1 && dir1)
-		c1 = '/';
-	if (!c2 && dir2)
-		c2 = '/';
-	ret = (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0;
-	if (c1 && c2 && !ret)
-		ret = len1 - len2;
-	return ret;
-}
-
-static int unpack_trees_rec(struct tree_entry_list **posns, int len,
-			    const char *base, merge_fn_t fn, int *indpos)
-{
-	int baselen = strlen(base);
-	int src_size = len + 1;
-	do {
-		int i;
-		char *first;
-		int firstdir = 0;
-		int pathlen;
-		unsigned ce_size;
-		struct tree_entry_list **subposns;
-		struct cache_entry **src;
-		int any_files = 0;
-		int any_dirs = 0;
-		char *cache_name;
-		int ce_stage;
-
-		/* Find the first name in the input. */
-
-		first = NULL;
-		cache_name = NULL;
-
-		/* Check the cache */
-		if (merge && *indpos < active_nr) {
-			/* This is a bit tricky: */
-			/* If the index has a subdirectory (with
-			 * contents) as the first name, it'll get a
-			 * filename like "foo/bar". But that's after
-			 * "foo", so the entry in trees will get
-			 * handled first, at which point we'll go into
-			 * "foo", and deal with "bar" from the index,
-			 * because the base will be "foo/". The only
-			 * way we can actually have "foo/bar" first of
-			 * all the things is if the trees don't
-			 * contain "foo" at all, in which case we'll
-			 * handle "foo/bar" without going into the
-			 * directory, but that's fine (and will return
-			 * an error anyway, with the added unknown
-			 * file case.
-			 */
-
-			cache_name = active_cache[*indpos]->name;
-			if (strlen(cache_name) > baselen &&
-			    !memcmp(cache_name, base, baselen)) {
-				cache_name += baselen;
-				first = cache_name;
-			} else {
-				cache_name = NULL;
-			}
-		}
-
-#if DBRT_DEBUG > 1
-		if (first)
-			printf("index %s\n", first);
-#endif
-		for (i = 0; i < len; i++) {
-			if (!posns[i] || posns[i] == &df_conflict_list)
-				continue;
-#if DBRT_DEBUG > 1
-			printf("%d %s\n", i + 1, posns[i]->name);
-#endif
-			if (!first || entcmp(first, firstdir,
-					     posns[i]->name, 
-					     posns[i]->directory) > 0) {
-				first = posns[i]->name;
-				firstdir = posns[i]->directory;
-			}
-		}
-		/* No name means we're done */
-		if (!first)
-			return 0;
-
-		pathlen = strlen(first);
-		ce_size = cache_entry_size(baselen + pathlen);
-
-		src = xcalloc(src_size, sizeof(struct cache_entry *));
-
-		subposns = xcalloc(len, sizeof(struct tree_list_entry *));
-
-		if (cache_name && !strcmp(cache_name, first)) {
-			any_files = 1;
-			src[0] = active_cache[*indpos];
-			remove_cache_entry_at(*indpos);
-		}
-
-		for (i = 0; i < len; i++) {
-			struct cache_entry *ce;
-
-			if (!posns[i] ||
-			    (posns[i] != &df_conflict_list &&
-			     strcmp(first, posns[i]->name))) {
-				continue;
-			}
-
-			if (posns[i] == &df_conflict_list) {
-				src[i + merge] = &df_conflict_entry;
-				continue;
-			}
-
-			if (posns[i]->directory) {
-				any_dirs = 1;
-				parse_tree(posns[i]->item.tree);
-				subposns[i] = posns[i]->item.tree->entries;
-				posns[i] = posns[i]->next;
-				src[i + merge] = &df_conflict_entry;
-				continue;
-			}
-
-			if (!merge)
-				ce_stage = 0;
-			else if (i + 1 < head_idx)
-				ce_stage = 1;
-			else if (i + 1 > head_idx)
-				ce_stage = 3;
-			else
-				ce_stage = 2;
-
-			ce = xcalloc(1, ce_size);
-			ce->ce_mode = create_ce_mode(posns[i]->mode);
-			ce->ce_flags = create_ce_flags(baselen + pathlen,
-						       ce_stage);
-			memcpy(ce->name, base, baselen);
-			memcpy(ce->name + baselen, first, pathlen + 1);
-
-			any_files = 1;
-
-			memcpy(ce->sha1, posns[i]->item.any->sha1, 20);
-			src[i + merge] = ce;
-			subposns[i] = &df_conflict_list;
-			posns[i] = posns[i]->next;
-		}
-		if (any_files) {
-			if (merge) {
-				int ret;
-
-#if DBRT_DEBUG > 1
-				printf("%s:\n", first);
-				for (i = 0; i < src_size; i++) {
-					printf(" %d ", i);
-					if (src[i])
-						printf("%s\n", sha1_to_hex(src[i]->sha1));
-					else
-						printf("\n");
-				}
-#endif
-				ret = fn(src);
-				
-#if DBRT_DEBUG > 1
-				printf("Added %d entries\n", ret);
-#endif
-				*indpos += ret;
-			} else {
-				for (i = 0; i < src_size; i++) {
-					if (src[i]) {
-						add_cache_entry(src[i], ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK);
-					}
-				}
-			}
-		}
-		if (any_dirs) {
-			char *newbase = xmalloc(baselen + 2 + pathlen);
-			memcpy(newbase, base, baselen);
-			memcpy(newbase + baselen, first, pathlen);
-			newbase[baselen + pathlen] = '/';
-			newbase[baselen + pathlen + 1] = '\0';
-			if (unpack_trees_rec(subposns, len, newbase, fn,
-					     indpos))
-				return -1;
-			free(newbase);
-		}
-		free(subposns);
-		free(src);
-	} while (1);
-}
-
-static void reject_merge(struct cache_entry *ce)
-{
-	die("Entry '%s' would be overwritten by merge. Cannot merge.", 
-	    ce->name);
-}
-
-/* Unlink the last component and attempt to remove leading
- * directories, in case this unlink is the removal of the
- * last entry in the directory -- empty directories are removed.
- */
-static void unlink_entry(char *name)
-{
-	char *cp, *prev;
-
-	if (unlink(name))
-		return;
-	prev = NULL;
-	while (1) {
-		int status;
-		cp = strrchr(name, '/');
-		if (prev)
-			*prev = '/';
-		if (!cp)
-			break;
-
-		*cp = 0;
-		status = rmdir(name);
-		if (status) {
-			*cp = '/';
-			break;
-		}
-		prev = cp;
-	}
-}
-
-static void progress_interval(int signum)
-{
-	progress_update = 1;
-}
-
-static void setup_progress_signal(void)
-{
-	struct sigaction sa;
-	struct itimerval v;
-
-	memset(&sa, 0, sizeof(sa));
-	sa.sa_handler = progress_interval;
-	sigemptyset(&sa.sa_mask);
-	sa.sa_flags = SA_RESTART;
-	sigaction(SIGALRM, &sa, NULL);
-
-	v.it_interval.tv_sec = 1;
-	v.it_interval.tv_usec = 0;
-	v.it_value = v.it_interval;
-	setitimer(ITIMER_REAL, &v, NULL);
-}
-
-static void check_updates(struct cache_entry **src, int nr)
-{
-	static struct checkout state = {
-		.base_dir = "",
-		.force = 1,
-		.quiet = 1,
-		.refresh_cache = 1,
-	};
-	unsigned short mask = htons(CE_UPDATE);
-	unsigned last_percent = 200, cnt = 0, total = 0;
-
-	if (update && verbose_update) {
-		for (total = cnt = 0; cnt < nr; cnt++) {
-			struct cache_entry *ce = src[cnt];
-			if (!ce->ce_mode || ce->ce_flags & mask)
-				total++;
-		}
-
-		/* Don't bother doing this for very small updates */
-		if (total < 250)
-			total = 0;
-
-		if (total) {
-			fprintf(stderr, "Checking files out...\n");
-			setup_progress_signal();
-			progress_update = 1;
-		}
-		cnt = 0;
-	}
-
-	while (nr--) {
-		struct cache_entry *ce = *src++;
-
-		if (total) {
-			if (!ce->ce_mode || ce->ce_flags & mask) {
-				unsigned percent;
-				cnt++;
-				percent = (cnt * 100) / total;
-				if (percent != last_percent ||
-				    progress_update) {
-					fprintf(stderr, "%4u%% (%u/%u) done\r",
-						percent, cnt, total);
-					last_percent = percent;
-				}
-			}
-		}
-		if (!ce->ce_mode) {
-			if (update)
-				unlink_entry(ce->name);
-			continue;
-		}
-		if (ce->ce_flags & mask) {
-			ce->ce_flags &= ~mask;
-			if (update)
-				checkout_entry(ce, &state, NULL);
-		}
-	}
-	if (total) {
-		signal(SIGALRM, SIG_IGN);
-		fputc('\n', stderr);
-	}
-}
-
-static int unpack_trees(merge_fn_t fn)
-{
-	int indpos = 0;
-	unsigned len = object_list_length(trees);
-	struct tree_entry_list **posns;
-	int i;
-	struct object_list *posn = trees;
-	merge_size = len;
-
-	if (len) {
-		posns = xmalloc(len * sizeof(struct tree_entry_list *));
-		for (i = 0; i < len; i++) {
-			posns[i] = ((struct tree *) posn->item)->entries;
-			posn = posn->next;
-		}
-		if (unpack_trees_rec(posns, len, "", fn, &indpos))
-			return -1;
-	}
-
-	if (trivial_merges_only && nontrivial_merge)
-		die("Merge requires file-level merging");
-
-	check_updates(active_cache, active_nr);
-	return 0;
-}
-
-static int list_tree(unsigned char *sha1)
-{
-	struct tree *tree = parse_tree_indirect(sha1);
-	if (!tree)
-		return -1;
-	object_list_append(&tree->object, &trees);
-	return 0;
-}
-
-static int same(struct cache_entry *a, struct cache_entry *b)
-{
-	if (!!a != !!b)
-		return 0;
-	if (!a && !b)
-		return 1;
-	return a->ce_mode == b->ce_mode && 
-		!memcmp(a->sha1, b->sha1, 20);
-}
-
-
-/*
- * When a CE gets turned into an unmerged entry, we
- * want it to be up-to-date
- */
-static void verify_uptodate(struct cache_entry *ce)
-{
-	struct stat st;
-
-	if (index_only || reset)
-		return;
-
-	if (!lstat(ce->name, &st)) {
-		unsigned changed = ce_match_stat(ce, &st, 1);
-		if (!changed)
-			return;
-		errno = 0;
-	}
-	if (reset) {
-		ce->ce_flags |= htons(CE_UPDATE);
-		return;
-	}
-	if (errno == ENOENT)
-		return;
-	die("Entry '%s' not uptodate. Cannot merge.", ce->name);
-}
-
-/*
- * We do not want to remove or overwrite a working tree file that
- * is not tracked.
- */
-static void verify_absent(const char *path, const char *action)
-{
-	struct stat st;
-
-	if (index_only || reset || !update)
-		return;
-	if (!lstat(path, &st))
-		die("Untracked working tree file '%s' "
-		    "would be %s by merge.", path, action);
-}
-
-static int merged_entry(struct cache_entry *merge, struct cache_entry *old)
-{
-	merge->ce_flags |= htons(CE_UPDATE);
-	if (old) {
-		/*
-		 * See if we can re-use the old CE directly?
-		 * That way we get the uptodate stat info.
-		 *
-		 * This also removes the UPDATE flag on
-		 * a match.
-		 */
-		if (same(old, merge)) {
-			*merge = *old;
-		} else {
-			verify_uptodate(old);
-		}
-	}
-	else
-		verify_absent(merge->name, "overwritten");
-
-	merge->ce_flags &= ~htons(CE_STAGEMASK);
-	add_cache_entry(merge, ADD_CACHE_OK_TO_ADD);
-	return 1;
-}
-
-static int deleted_entry(struct cache_entry *ce, struct cache_entry *old)
-{
-	if (old)
-		verify_uptodate(old);
-	else
-		verify_absent(ce->name, "removed");
-	ce->ce_mode = 0;
-	add_cache_entry(ce, ADD_CACHE_OK_TO_ADD);
-	return 1;
-}
-
-static int keep_entry(struct cache_entry *ce)
-{
-	add_cache_entry(ce, ADD_CACHE_OK_TO_ADD);
-	return 1;
-}
-
-#if DBRT_DEBUG
-static void show_stage_entry(FILE *o,
-			     const char *label, const struct cache_entry *ce)
-{
-	if (!ce)
-		fprintf(o, "%s (missing)\n", label);
-	else
-		fprintf(o, "%s%06o %s %d\t%s\n",
-			label,
-			ntohl(ce->ce_mode),
-			sha1_to_hex(ce->sha1),
-			ce_stage(ce),
-			ce->name);
-}
-#endif
-
-static int threeway_merge(struct cache_entry **stages)
-{
-	struct cache_entry *index;
-	struct cache_entry *head; 
-	struct cache_entry *remote = stages[head_idx + 1];
-	int count;
-	int head_match = 0;
-	int remote_match = 0;
-	const char *path = NULL;
-
-	int df_conflict_head = 0;
-	int df_conflict_remote = 0;
-
-	int any_anc_missing = 0;
-	int no_anc_exists = 1;
-	int i;
-
-	for (i = 1; i < head_idx; i++) {
-		if (!stages[i])
-			any_anc_missing = 1;
-		else {
-			if (!path)
-				path = stages[i]->name;
-			no_anc_exists = 0;
-		}
-	}
-
-	index = stages[0];
-	head = stages[head_idx];
-
-	if (head == &df_conflict_entry) {
-		df_conflict_head = 1;
-		head = NULL;
-	}
-
-	if (remote == &df_conflict_entry) {
-		df_conflict_remote = 1;
-		remote = NULL;
-	}
-
-	if (!path && index)
-		path = index->name;
-	if (!path && head)
-		path = head->name;
-	if (!path && remote)
-		path = remote->name;
-
-	/* First, if there's a #16 situation, note that to prevent #13
-	 * and #14.
-	 */
-	if (!same(remote, head)) {
-		for (i = 1; i < head_idx; i++) {
-			if (same(stages[i], head)) {
-				head_match = i;
-			}
-			if (same(stages[i], remote)) {
-				remote_match = i;
-			}
-		}
-	}
-
-	/* We start with cases where the index is allowed to match
-	 * something other than the head: #14(ALT) and #2ALT, where it
-	 * is permitted to match the result instead.
-	 */
-	/* #14, #14ALT, #2ALT */
-	if (remote && !df_conflict_head && head_match && !remote_match) {
-		if (index && !same(index, remote) && !same(index, head))
-			reject_merge(index);
-		return merged_entry(remote, index);
-	}
-	/*
-	 * If we have an entry in the index cache, then we want to
-	 * make sure that it matches head.
-	 */
-	if (index && !same(index, head)) {
-		reject_merge(index);
-	}
-
-	if (head) {
-		/* #5ALT, #15 */
-		if (same(head, remote))
-			return merged_entry(head, index);
-		/* #13, #3ALT */
-		if (!df_conflict_remote && remote_match && !head_match)
-			return merged_entry(head, index);
-	}
-
-	/* #1 */
-	if (!head && !remote && any_anc_missing)
-		return 0;
-
-	/* Under the new "aggressive" rule, we resolve mostly trivial
-	 * cases that we historically had git-merge-one-file resolve.
-	 */
-	if (aggressive) {
-		int head_deleted = !head && !df_conflict_head;
-		int remote_deleted = !remote && !df_conflict_remote;
-		/*
-		 * Deleted in both.
-		 * Deleted in one and unchanged in the other.
-		 */
-		if ((head_deleted && remote_deleted) ||
-		    (head_deleted && remote && remote_match) ||
-		    (remote_deleted && head && head_match)) {
-			if (index)
-				return deleted_entry(index, index);
-			else if (path)
-				verify_absent(path, "removed");
-			return 0;
-		}
-		/*
-		 * Added in both, identically.
-		 */
-		if (no_anc_exists && head && remote && same(head, remote))
-			return merged_entry(head, index);
-
-	}
-
-	/* Below are "no merge" cases, which require that the index be
-	 * up-to-date to avoid the files getting overwritten with
-	 * conflict resolution files. 
-	 */
-	if (index) {
-		verify_uptodate(index);
-	}
-	else if (path)
-		verify_absent(path, "overwritten");
-
-	nontrivial_merge = 1;
-
-	/* #2, #3, #4, #6, #7, #9, #11. */
-	count = 0;
-	if (!head_match || !remote_match) {
-		for (i = 1; i < head_idx; i++) {
-			if (stages[i]) {
-				keep_entry(stages[i]);
-				count++;
-				break;
-			}
-		}
-	}
-#if DBRT_DEBUG
-	else {
-		fprintf(stderr, "read-tree: warning #16 detected\n");
-		show_stage_entry(stderr, "head   ", stages[head_match]);
-		show_stage_entry(stderr, "remote ", stages[remote_match]);
-	}
-#endif
-	if (head) { count += keep_entry(head); }
-	if (remote) { count += keep_entry(remote); }
-	return count;
-}
-
-/*
- * Two-way merge.
- *
- * The rule is to "carry forward" what is in the index without losing
- * information across a "fast forward", favoring a successful merge
- * over a merge failure when it makes sense.  For details of the
- * "carry forward" rule, please see <Documentation/git-read-tree.txt>.
- *
- */
-static int twoway_merge(struct cache_entry **src)
-{
-	struct cache_entry *current = src[0];
-	struct cache_entry *oldtree = src[1], *newtree = src[2];
-
-	if (merge_size != 2)
-		return error("Cannot do a twoway merge of %d trees",
-			     merge_size);
-
-	if (current) {
-		if ((!oldtree && !newtree) || /* 4 and 5 */
-		    (!oldtree && newtree &&
-		     same(current, newtree)) || /* 6 and 7 */
-		    (oldtree && newtree &&
-		     same(oldtree, newtree)) || /* 14 and 15 */
-		    (oldtree && newtree &&
-		     !same(oldtree, newtree) && /* 18 and 19*/
-		     same(current, newtree))) {
-			return keep_entry(current);
-		}
-		else if (oldtree && !newtree && same(current, oldtree)) {
-			/* 10 or 11 */
-			return deleted_entry(oldtree, current);
-		}
-		else if (oldtree && newtree &&
-			 same(current, oldtree) && !same(current, newtree)) {
-			/* 20 or 21 */
-			return merged_entry(newtree, current);
-		}
-		else {
-			/* all other failures */
-			if (oldtree)
-				reject_merge(oldtree);
-			if (current)
-				reject_merge(current);
-			if (newtree)
-				reject_merge(newtree);
-			return -1;
-		}
-	}
-	else if (newtree)
-		return merged_entry(newtree, current);
-	else
-		return deleted_entry(oldtree, current);
-}
-
-/*
- * One-way merge.
- *
- * The rule is:
- * - take the stat information from stage0, take the data from stage1
- */
-static int oneway_merge(struct cache_entry **src)
-{
-	struct cache_entry *old = src[0];
-	struct cache_entry *a = src[1];
-
-	if (merge_size != 1)
-		return error("Cannot do a oneway merge of %d trees",
-			     merge_size);
-
-	if (!a)
-		return deleted_entry(old, old);
-	if (old && same(old, a)) {
-		if (reset) {
-			struct stat st;
-			if (lstat(old->name, &st) ||
-			    ce_match_stat(old, &st, 1))
-				old->ce_flags |= htons(CE_UPDATE);
-		}
-		return keep_entry(old);
-	}
-	return merged_entry(a, old);
-}
-
-static int read_cache_unmerged(void)
-{
-	int i, deleted;
-	struct cache_entry **dst;
-
-	read_cache();
-	dst = active_cache;
-	deleted = 0;
-	for (i = 0; i < active_nr; i++) {
-		struct cache_entry *ce = active_cache[i];
-		if (ce_stage(ce)) {
-			deleted++;
-			continue;
-		}
-		if (deleted)
-			*dst = ce;
-		dst++;
-	}
-	active_nr -= deleted;
-	return deleted;
-}
-
-static const char read_tree_usage[] = "git-read-tree (<sha> | -m [--aggressive] [-u | -i] <sha1> [<sha2> [<sha3>]])";
-
-static struct cache_file cache_file;
-
-int main(int argc, char **argv)
-{
-	int i, newfd, stage = 0;
-	unsigned char sha1[20];
-	merge_fn_t fn = NULL;
-
-	setup_git_directory();
-	git_config(git_default_config);
-
-	newfd = hold_index_file_for_update(&cache_file, get_index_file());
-	if (newfd < 0)
-		die("unable to create new cachefile");
-
-	git_config(git_default_config);
-
-	merge = 0;
-	reset = 0;
-	for (i = 1; i < argc; i++) {
-		const char *arg = argv[i];
-
-		/* "-u" means "update", meaning that a merge will update
-		 * the working tree.
-		 */
-		if (!strcmp(arg, "-u")) {
-			update = 1;
-			continue;
-		}
-
-		if (!strcmp(arg, "-v")) {
-			verbose_update = 1;
-			continue;
-		}
-
-		/* "-i" means "index only", meaning that a merge will
-		 * not even look at the working tree.
-		 */
-		if (!strcmp(arg, "-i")) {
-			index_only = 1;
-			continue;
-		}
-
-		/* This differs from "-m" in that we'll silently ignore unmerged entries */
-		if (!strcmp(arg, "--reset")) {
-			if (stage || merge)
-				usage(read_tree_usage);
-			reset = 1;
-			merge = 1;
-			stage = 1;
-			read_cache_unmerged();
-			continue;
-		}
-
-		if (!strcmp(arg, "--trivial")) {
-			trivial_merges_only = 1;
-			continue;
-		}
-
-		if (!strcmp(arg, "--aggressive")) {
-			aggressive = 1;
-			continue;
-		}
-
-		/* "-m" stands for "merge", meaning we start in stage 1 */
-		if (!strcmp(arg, "-m")) {
-			if (stage || merge)
-				usage(read_tree_usage);
-			if (read_cache_unmerged())
-				die("you need to resolve your current index first");
-			stage = 1;
-			merge = 1;
-			continue;
-		}
-
-		/* using -u and -i at the same time makes no sense */
-		if (1 < index_only + update)
-			usage(read_tree_usage);
-
-		if (get_sha1(arg, sha1))
-			die("Not a valid object name %s", arg);
-		if (list_tree(sha1) < 0)
-			die("failed to unpack tree object %s", arg);
-		stage++;
-	}
-	if ((update||index_only) && !merge)
-		usage(read_tree_usage);
-
-	if (merge) {
-		if (stage < 2)
-			die("just how do you expect me to merge %d trees?", stage-1);
-		switch (stage - 1) {
-		case 1:
-			fn = oneway_merge;
-			break;
-		case 2:
-			fn = twoway_merge;
-			break;
-		case 3:
-			fn = threeway_merge;
-			break;
-		default:
-			fn = threeway_merge;
-			break;
-		}
-
-		if (stage - 1 >= 3)
-			head_idx = stage - 2;
-		else
-			head_idx = 1;
-	}
-
-	unpack_trees(fn);
-	if (write_cache(newfd, active_cache, active_nr) ||
-	    commit_index_file(&cache_file))
-		die("unable to write new index file");
-	return 0;
-}
-- 
1.3.3.g288c

^ permalink raw reply related

* [PATCH 7/8] Builtin git-show-branch.
From: Peter Eriksen @ 2006-05-23  8:31 UTC (permalink / raw)
  To: git; +Cc: Peter Eriksen
In-Reply-To: <1148373081381-git-send-email->

From: Peter Eriksen <s022018@student.dtu.dk>

Signed-off-by: Peter Eriksen <s022018@student.dtu.dk>


---

d0d1cfb7985fa90442e423a55ca23e6bc83f912d
 Makefile              |    6 
 builtin-show-branch.c |  789 +++++++++++++++++++++++++++++++++++++++++++++++++
 builtin.h             |    1 
 git.c                 |    3 
 show-branch.c         |  788 -------------------------------------------------
 5 files changed, 795 insertions(+), 792 deletions(-)
 create mode 100644 builtin-show-branch.c
 delete mode 100644 show-branch.c

d0d1cfb7985fa90442e423a55ca23e6bc83f912d
diff --git a/Makefile b/Makefile
index eeb4fdb..b438a90 100644
--- a/Makefile
+++ b/Makefile
@@ -159,7 +159,7 @@ PROGRAMS = \
 	git-merge-index$X git-mktag$X git-mktree$X git-pack-objects$X git-patch-id$X \
 	git-peek-remote$X git-prune-packed$X \
 	git-receive-pack$X git-rev-parse$X \
-	git-send-pack$X git-show-branch$X git-shell$X \
+	git-send-pack$X git-shell$X \
 	git-show-index$X git-ssh-fetch$X \
 	git-ssh-upload$X git-unpack-file$X \
 	git-unpack-objects$X git-update-index$X git-update-server-info$X \
@@ -173,7 +173,7 @@ BUILT_INS = git-log$X git-whatchanged$X 
 	git-grep$X git-rev-list$X git-check-ref-format$X \
 	git-init-db$X git-ls-files$X git-ls-tree$X \
 	git-tar-tree$X git-read-tree$X git-commit-tree$X \
-	git-apply$X 
+	git-apply$X git-show-branch$X
 
 # what 'all' will build and 'install' will install, in gitexecdir
 ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
@@ -224,7 +224,7 @@ BUILTIN_OBJS = \
 	builtin-grep.o builtin-rev-list.o builtin-check-ref-format.o \
 	builtin-init-db.o builtin-ls-files.o builtin-ls-tree.o \
         builtin-tar-tree.o builtin-read-tree.o builtin-commit-tree.o \
-	builtin-apply.o
+	builtin-apply.o builtin-show-branch.o
 
 GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
 LIBS = $(GITLIBS) -lz
diff --git a/builtin-show-branch.c b/builtin-show-branch.c
new file mode 100644
index 0000000..d1be8bb
--- /dev/null
+++ b/builtin-show-branch.c
@@ -0,0 +1,789 @@
+#include <stdlib.h>
+#include <fnmatch.h>
+#include "cache.h"
+#include "commit.h"
+#include "refs.h"
+#include "builtin.h"
+
+static const char show_branch_usage[] =
+"git-show-branch [--dense] [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...]";
+
+static int default_num = 0;
+static int default_alloc = 0;
+static char **default_arg = NULL;
+
+#define UNINTERESTING	01
+
+#define REV_SHIFT	 2
+#define MAX_REVS	29 /* should not exceed bits_per_int - REV_SHIFT */
+
+static struct commit *interesting(struct commit_list *list)
+{
+	while (list) {
+		struct commit *commit = list->item;
+		list = list->next;
+		if (commit->object.flags & UNINTERESTING)
+			continue;
+		return commit;
+	}
+	return NULL;
+}
+
+static struct commit *pop_one_commit(struct commit_list **list_p)
+{
+	struct commit *commit;
+	struct commit_list *list;
+	list = *list_p;
+	commit = list->item;
+	*list_p = list->next;
+	free(list);
+	return commit;
+}
+
+struct commit_name {
+	const char *head_name; /* which head's ancestor? */
+	int generation; /* how many parents away from head_name */
+};
+
+/* Name the commit as nth generation ancestor of head_name;
+ * we count only the first-parent relationship for naming purposes.
+ */
+static void name_commit(struct commit *commit, const char *head_name, int nth)
+{
+	struct commit_name *name;
+	if (!commit->object.util)
+		commit->object.util = xmalloc(sizeof(struct commit_name));
+	name = commit->object.util;
+	name->head_name = head_name;
+	name->generation = nth;
+}
+
+/* Parent is the first parent of the commit.  We may name it
+ * as (n+1)th generation ancestor of the same head_name as
+ * commit is nth generation ancestor of, if that generation
+ * number is better than the name it already has.
+ */
+static void name_parent(struct commit *commit, struct commit *parent)
+{
+	struct commit_name *commit_name = commit->object.util;
+	struct commit_name *parent_name = parent->object.util;
+	if (!commit_name)
+		return;
+	if (!parent_name ||
+	    commit_name->generation + 1 < parent_name->generation)
+		name_commit(parent, commit_name->head_name,
+			    commit_name->generation + 1);
+}
+
+static int name_first_parent_chain(struct commit *c)
+{
+	int i = 0;
+	while (c) {
+		struct commit *p;
+		if (!c->object.util)
+			break;
+		if (!c->parents)
+			break;
+		p = c->parents->item;
+		if (!p->object.util) {
+			name_parent(c, p);
+			i++;
+		}
+		c = p;
+	}
+	return i;
+}
+
+static void name_commits(struct commit_list *list,
+			 struct commit **rev,
+			 char **ref_name,
+			 int num_rev)
+{
+	struct commit_list *cl;
+	struct commit *c;
+	int i;
+
+	/* First give names to the given heads */
+	for (cl = list; cl; cl = cl->next) {
+		c = cl->item;
+		if (c->object.util)
+			continue;
+		for (i = 0; i < num_rev; i++) {
+			if (rev[i] == c) {
+				name_commit(c, ref_name[i], 0);
+				break;
+			}
+		}
+	}
+
+	/* Then commits on the first parent ancestry chain */
+	do {
+		i = 0;
+		for (cl = list; cl; cl = cl->next) {
+			i += name_first_parent_chain(cl->item);
+		}
+	} while (i);
+
+	/* Finally, any unnamed commits */
+	do {
+		i = 0;
+		for (cl = list; cl; cl = cl->next) {
+			struct commit_list *parents;
+			struct commit_name *n;
+			int nth;
+			c = cl->item;
+			if (!c->object.util)
+				continue;
+			n = c->object.util;
+			parents = c->parents;
+			nth = 0;
+			while (parents) {
+				struct commit *p = parents->item;
+				char newname[1000], *en;
+				parents = parents->next;
+				nth++;
+				if (p->object.util)
+					continue;
+				en = newname;
+				switch (n->generation) {
+				case 0:
+					en += sprintf(en, "%s", n->head_name);
+					break;
+				case 1:
+					en += sprintf(en, "%s^", n->head_name);
+					break;
+				default:
+					en += sprintf(en, "%s~%d",
+						n->head_name, n->generation);
+					break;
+				}
+				if (nth == 1)
+					en += sprintf(en, "^");
+				else
+					en += sprintf(en, "^%d", nth);
+				name_commit(p, strdup(newname), 0);
+				i++;
+				name_first_parent_chain(p);
+			}
+		}
+	} while (i);
+}
+
+static int mark_seen(struct commit *commit, struct commit_list **seen_p)
+{
+	if (!commit->object.flags) {
+		insert_by_date(commit, seen_p);
+		return 1;
+	}
+	return 0;
+}
+
+static void join_revs(struct commit_list **list_p,
+		      struct commit_list **seen_p,
+		      int num_rev, int extra)
+{
+	int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
+	int all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
+
+	while (*list_p) {
+		struct commit_list *parents;
+		int still_interesting = !!interesting(*list_p);
+		struct commit *commit = pop_one_commit(list_p);
+		int flags = commit->object.flags & all_mask;
+
+		if (!still_interesting && extra <= 0)
+			break;
+
+		mark_seen(commit, seen_p);
+		if ((flags & all_revs) == all_revs)
+			flags |= UNINTERESTING;
+		parents = commit->parents;
+
+		while (parents) {
+			struct commit *p = parents->item;
+			int this_flag = p->object.flags;
+			parents = parents->next;
+			if ((this_flag & flags) == flags)
+				continue;
+			if (!p->object.parsed)
+				parse_commit(p);
+			if (mark_seen(p, seen_p) && !still_interesting)
+				extra--;
+			p->object.flags |= flags;
+			insert_by_date(p, list_p);
+		}
+	}
+
+	/*
+	 * Postprocess to complete well-poisoning.
+	 *
+	 * At this point we have all the commits we have seen in
+	 * seen_p list (which happens to be sorted chronologically but
+	 * it does not really matter).  Mark anything that can be
+	 * reached from uninteresting commits not interesting.
+	 */
+	for (;;) {
+		int changed = 0;
+		struct commit_list *s;
+		for (s = *seen_p; s; s = s->next) {
+			struct commit *c = s->item;
+			struct commit_list *parents;
+
+			if (((c->object.flags & all_revs) != all_revs) &&
+			    !(c->object.flags & UNINTERESTING))
+				continue;
+
+			/* The current commit is either a merge base or
+			 * already uninteresting one.  Mark its parents
+			 * as uninteresting commits _only_ if they are
+			 * already parsed.  No reason to find new ones
+			 * here.
+			 */
+			parents = c->parents;
+			while (parents) {
+				struct commit *p = parents->item;
+				parents = parents->next;
+				if (!(p->object.flags & UNINTERESTING)) {
+					p->object.flags |= UNINTERESTING;
+					changed = 1;
+				}
+			}
+		}
+		if (!changed)
+			break;
+	}
+}
+
+static void show_one_commit(struct commit *commit, int no_name)
+{
+	char pretty[256], *cp;
+	struct commit_name *name = commit->object.util;
+	if (commit->object.parsed)
+		pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0,
+				    pretty, sizeof(pretty), 0);
+	else
+		strcpy(pretty, "(unavailable)");
+	if (!strncmp(pretty, "[PATCH] ", 8))
+		cp = pretty + 8;
+	else
+		cp = pretty;
+
+	if (!no_name) {
+		if (name && name->head_name) {
+			printf("[%s", name->head_name);
+			if (name->generation) {
+				if (name->generation == 1)
+					printf("^");
+				else
+					printf("~%d", name->generation);
+			}
+			printf("] ");
+		}
+		else
+			printf("[%s] ",
+			       find_unique_abbrev(commit->object.sha1, 7));
+	}
+	puts(cp);
+}
+
+static char *ref_name[MAX_REVS + 1];
+static int ref_name_cnt;
+
+static const char *find_digit_prefix(const char *s, int *v)
+{
+	const char *p;
+	int ver;
+	char ch;
+
+	for (p = s, ver = 0;
+	     '0' <= (ch = *p) && ch <= '9';
+	     p++)
+		ver = ver * 10 + ch - '0';
+	*v = ver;
+	return p;
+}
+
+
+static int version_cmp(const char *a, const char *b)
+{
+	while (1) {
+		int va, vb;
+
+		a = find_digit_prefix(a, &va);
+		b = find_digit_prefix(b, &vb);
+		if (va != vb)
+			return va - vb;
+
+		while (1) {
+			int ca = *a;
+			int cb = *b;
+			if ('0' <= ca && ca <= '9')
+				ca = 0;
+			if ('0' <= cb && cb <= '9')
+				cb = 0;
+			if (ca != cb)
+				return ca - cb;
+			if (!ca)
+				break;
+			a++;
+			b++;
+		}
+		if (!*a && !*b)
+			return 0;
+	}
+}
+
+static int compare_ref_name(const void *a_, const void *b_)
+{
+	const char * const*a = a_, * const*b = b_;
+	return version_cmp(*a, *b);
+}
+
+static void sort_ref_range(int bottom, int top)
+{
+	qsort(ref_name + bottom, top - bottom, sizeof(ref_name[0]),
+	      compare_ref_name);
+}
+
+static int append_ref(const char *refname, const unsigned char *sha1)
+{
+	struct commit *commit = lookup_commit_reference_gently(sha1, 1);
+	int i;
+
+	if (!commit)
+		return 0;
+	/* Avoid adding the same thing twice */
+	for (i = 0; i < ref_name_cnt; i++)
+		if (!strcmp(refname, ref_name[i]))
+			return 0;
+
+	if (MAX_REVS <= ref_name_cnt) {
+		fprintf(stderr, "warning: ignoring %s; "
+			"cannot handle more than %d refs\n",
+			refname, MAX_REVS);
+		return 0;
+	}
+	ref_name[ref_name_cnt++] = strdup(refname);
+	ref_name[ref_name_cnt] = NULL;
+	return 0;
+}
+
+static int append_head_ref(const char *refname, const unsigned char *sha1)
+{
+	unsigned char tmp[20];
+	int ofs = 11;
+	if (strncmp(refname, "refs/heads/", ofs))
+		return 0;
+	/* If both heads/foo and tags/foo exists, get_sha1 would
+	 * get confused.
+	 */
+	if (get_sha1(refname + ofs, tmp) || memcmp(tmp, sha1, 20))
+		ofs = 5;
+	return append_ref(refname + ofs, sha1);
+}
+
+static int append_tag_ref(const char *refname, const unsigned char *sha1)
+{
+	if (strncmp(refname, "refs/tags/", 10))
+		return 0;
+	return append_ref(refname + 5, sha1);
+}
+
+static const char *match_ref_pattern = NULL;
+static int match_ref_slash = 0;
+static int count_slash(const char *s)
+{
+	int cnt = 0;
+	while (*s)
+		if (*s++ == '/')
+			cnt++;
+	return cnt;
+}
+
+static int append_matching_ref(const char *refname, const unsigned char *sha1)
+{
+	/* we want to allow pattern hold/<asterisk> to show all
+	 * branches under refs/heads/hold/, and v0.99.9? to show
+	 * refs/tags/v0.99.9a and friends.
+	 */
+	const char *tail;
+	int slash = count_slash(refname);
+	for (tail = refname; *tail && match_ref_slash < slash; )
+		if (*tail++ == '/')
+			slash--;
+	if (!*tail)
+		return 0;
+	if (fnmatch(match_ref_pattern, tail, 0))
+		return 0;
+	if (!strncmp("refs/heads/", refname, 11))
+		return append_head_ref(refname, sha1);
+	if (!strncmp("refs/tags/", refname, 10))
+		return append_tag_ref(refname, sha1);
+	return append_ref(refname, sha1);
+}
+
+static void snarf_refs(int head, int tag)
+{
+	if (head) {
+		int orig_cnt = ref_name_cnt;
+		for_each_ref(append_head_ref);
+		sort_ref_range(orig_cnt, ref_name_cnt);
+	}
+	if (tag) {
+		int orig_cnt = ref_name_cnt;
+		for_each_ref(append_tag_ref);
+		sort_ref_range(orig_cnt, ref_name_cnt);
+	}
+}
+
+static int rev_is_head(char *head_path, int headlen, char *name,
+		       unsigned char *head_sha1, unsigned char *sha1)
+{
+	int namelen;
+	if ((!head_path[0]) ||
+	    (head_sha1 && sha1 && memcmp(head_sha1, sha1, 20)))
+		return 0;
+	namelen = strlen(name);
+	if ((headlen < namelen) ||
+	    memcmp(head_path + headlen - namelen, name, namelen))
+		return 0;
+	if (headlen == namelen ||
+	    head_path[headlen - namelen - 1] == '/')
+		return 1;
+	return 0;
+}
+
+static int show_merge_base(struct commit_list *seen, int num_rev)
+{
+	int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
+	int all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
+	int exit_status = 1;
+
+	while (seen) {
+		struct commit *commit = pop_one_commit(&seen);
+		int flags = commit->object.flags & all_mask;
+		if (!(flags & UNINTERESTING) &&
+		    ((flags & all_revs) == all_revs)) {
+			puts(sha1_to_hex(commit->object.sha1));
+			exit_status = 0;
+			commit->object.flags |= UNINTERESTING;
+		}
+	}
+	return exit_status;
+}
+
+static int show_independent(struct commit **rev,
+			    int num_rev,
+			    char **ref_name,
+			    unsigned int *rev_mask)
+{
+	int i;
+
+	for (i = 0; i < num_rev; i++) {
+		struct commit *commit = rev[i];
+		unsigned int flag = rev_mask[i];
+
+		if (commit->object.flags == flag)
+			puts(sha1_to_hex(commit->object.sha1));
+		commit->object.flags |= UNINTERESTING;
+	}
+	return 0;
+}
+
+static void append_one_rev(const char *av)
+{
+	unsigned char revkey[20];
+	if (!get_sha1(av, revkey)) {
+		append_ref(av, revkey);
+		return;
+	}
+	if (strchr(av, '*') || strchr(av, '?') || strchr(av, '[')) {
+		/* glob style match */
+		int saved_matches = ref_name_cnt;
+		match_ref_pattern = av;
+		match_ref_slash = count_slash(av);
+		for_each_ref(append_matching_ref);
+		if (saved_matches == ref_name_cnt &&
+		    ref_name_cnt < MAX_REVS)
+			error("no matching refs with %s", av);
+		if (saved_matches + 1 < ref_name_cnt)
+			sort_ref_range(saved_matches, ref_name_cnt);
+		return;
+	}
+	die("bad sha1 reference %s", av);
+}
+
+static int git_show_branch_config(const char *var, const char *value)
+{
+	if (!strcmp(var, "showbranch.default")) {
+		if (default_alloc <= default_num + 1) {
+			default_alloc = default_alloc * 3 / 2 + 20;
+			default_arg = xrealloc(default_arg, sizeof *default_arg * default_alloc);
+		}
+		default_arg[default_num++] = strdup(value);
+		default_arg[default_num] = NULL;
+		return 0;
+	}
+
+	return git_default_config(var, value);
+}
+
+static int omit_in_dense(struct commit *commit, struct commit **rev, int n)
+{
+	/* If the commit is tip of the named branches, do not
+	 * omit it.
+	 * Otherwise, if it is a merge that is reachable from only one
+	 * tip, it is not that interesting.
+	 */
+	int i, flag, count;
+	for (i = 0; i < n; i++)
+		if (rev[i] == commit)
+			return 0;
+	flag = commit->object.flags;
+	for (i = count = 0; i < n; i++) {
+		if (flag & (1u << (i + REV_SHIFT)))
+			count++;
+	}
+	if (count == 1)
+		return 1;
+	return 0;
+}
+
+int cmd_show_branch(int ac, const char **av, char **envp)
+{
+	struct commit *rev[MAX_REVS], *commit;
+	struct commit_list *list = NULL, *seen = NULL;
+	unsigned int rev_mask[MAX_REVS];
+	int num_rev, i, extra = 0;
+	int all_heads = 0, all_tags = 0;
+	int all_mask, all_revs;
+	int lifo = 1;
+	char head_path[128];
+	const char *head_path_p;
+	int head_path_len;
+	unsigned char head_sha1[20];
+	int merge_base = 0;
+	int independent = 0;
+	int no_name = 0;
+	int sha1_name = 0;
+	int shown_merge_point = 0;
+	int with_current_branch = 0;
+	int head_at = -1;
+	int topics = 0;
+	int dense = 1;
+
+	setup_git_directory();
+	git_config(git_show_branch_config);
+
+	/* If nothing is specified, try the default first */
+	if (ac == 1 && default_num) {
+		ac = default_num + 1;
+		av = default_arg - 1; /* ick; we would not address av[0] */
+	}
+
+	while (1 < ac && av[1][0] == '-') {
+		char *arg = av[1];
+		if (!strcmp(arg, "--")) {
+			ac--; av++;
+			break;
+		}
+		else if (!strcmp(arg, "--all"))
+			all_heads = all_tags = 1;
+		else if (!strcmp(arg, "--heads"))
+			all_heads = 1;
+		else if (!strcmp(arg, "--tags"))
+			all_tags = 1;
+		else if (!strcmp(arg, "--more"))
+			extra = 1;
+		else if (!strcmp(arg, "--list"))
+			extra = -1;
+		else if (!strcmp(arg, "--no-name"))
+			no_name = 1;
+		else if (!strcmp(arg, "--current"))
+			with_current_branch = 1;
+		else if (!strcmp(arg, "--sha1-name"))
+			sha1_name = 1;
+		else if (!strncmp(arg, "--more=", 7))
+			extra = atoi(arg + 7);
+		else if (!strcmp(arg, "--merge-base"))
+			merge_base = 1;
+		else if (!strcmp(arg, "--independent"))
+			independent = 1;
+		else if (!strcmp(arg, "--topo-order"))
+			lifo = 1;
+		else if (!strcmp(arg, "--topics"))
+			topics = 1;
+		else if (!strcmp(arg, "--sparse"))
+			dense = 0;
+		else if (!strcmp(arg, "--date-order"))
+			lifo = 0;
+		else
+			usage(show_branch_usage);
+		ac--; av++;
+	}
+	ac--; av++;
+
+	/* Only one of these is allowed */
+	if (1 < independent + merge_base + (extra != 0))
+		usage(show_branch_usage);
+
+	/* If nothing is specified, show all branches by default */
+	if (ac + all_heads + all_tags == 0)
+		all_heads = 1;
+
+	if (all_heads + all_tags)
+		snarf_refs(all_heads, all_tags);
+	while (0 < ac) {
+		append_one_rev(*av);
+		ac--; av++;
+	}
+
+	head_path_p = resolve_ref(git_path("HEAD"), head_sha1, 1);
+	if (head_path_p) {
+		head_path_len = strlen(head_path_p);
+		memcpy(head_path, head_path_p, head_path_len + 1);
+	}
+	else {
+		head_path_len = 0;
+		head_path[0] = 0;
+	}
+
+	if (with_current_branch && head_path_p) {
+		int has_head = 0;
+		for (i = 0; !has_head && i < ref_name_cnt; i++) {
+			/* We are only interested in adding the branch
+			 * HEAD points at.
+			 */
+			if (rev_is_head(head_path,
+					head_path_len,
+					ref_name[i],
+					head_sha1, NULL))
+				has_head++;
+		}
+		if (!has_head) {
+			int pfxlen = strlen(git_path("refs/heads/"));
+			append_one_rev(head_path + pfxlen);
+		}
+	}
+
+	if (!ref_name_cnt) {
+		fprintf(stderr, "No revs to be shown.\n");
+		exit(0);
+	}
+
+	for (num_rev = 0; ref_name[num_rev]; num_rev++) {
+		unsigned char revkey[20];
+		unsigned int flag = 1u << (num_rev + REV_SHIFT);
+
+		if (MAX_REVS <= num_rev)
+			die("cannot handle more than %d revs.", MAX_REVS);
+		if (get_sha1(ref_name[num_rev], revkey))
+			die("'%s' is not a valid ref.", ref_name[num_rev]);
+		commit = lookup_commit_reference(revkey);
+		if (!commit)
+			die("cannot find commit %s (%s)",
+			    ref_name[num_rev], revkey);
+		parse_commit(commit);
+		mark_seen(commit, &seen);
+
+		/* rev#0 uses bit REV_SHIFT, rev#1 uses bit REV_SHIFT+1,
+		 * and so on.  REV_SHIFT bits from bit 0 are used for
+		 * internal bookkeeping.
+		 */
+		commit->object.flags |= flag;
+		if (commit->object.flags == flag)
+			insert_by_date(commit, &list);
+		rev[num_rev] = commit;
+	}
+	for (i = 0; i < num_rev; i++)
+		rev_mask[i] = rev[i]->object.flags;
+
+	if (0 <= extra)
+		join_revs(&list, &seen, num_rev, extra);
+
+	if (merge_base)
+		return show_merge_base(seen, num_rev);
+
+	if (independent)
+		return show_independent(rev, num_rev, ref_name, rev_mask);
+
+	/* Show list; --more=-1 means list-only */
+	if (1 < num_rev || extra < 0) {
+		for (i = 0; i < num_rev; i++) {
+			int j;
+			int is_head = rev_is_head(head_path,
+						  head_path_len,
+						  ref_name[i],
+						  head_sha1,
+						  rev[i]->object.sha1);
+			if (extra < 0)
+				printf("%c [%s] ",
+				       is_head ? '*' : ' ', ref_name[i]);
+			else {
+				for (j = 0; j < i; j++)
+					putchar(' ');
+				printf("%c [%s] ",
+				       is_head ? '*' : '!', ref_name[i]);
+			}
+			/* header lines never need name */
+			show_one_commit(rev[i], 1);
+			if (is_head)
+				head_at = i;
+		}
+		if (0 <= extra) {
+			for (i = 0; i < num_rev; i++)
+				putchar('-');
+			putchar('\n');
+		}
+	}
+	if (extra < 0)
+		exit(0);
+
+	/* Sort topologically */
+	sort_in_topological_order(&seen, lifo);
+
+	/* Give names to commits */
+	if (!sha1_name && !no_name)
+		name_commits(seen, rev, ref_name, num_rev);
+
+	all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
+	all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
+
+	while (seen) {
+		struct commit *commit = pop_one_commit(&seen);
+		int this_flag = commit->object.flags;
+		int is_merge_point = ((this_flag & all_revs) == all_revs);
+
+		shown_merge_point |= is_merge_point;
+
+		if (1 < num_rev) {
+			int is_merge = !!(commit->parents &&
+					  commit->parents->next);
+			if (topics &&
+			    !is_merge_point &&
+			    (this_flag & (1u << REV_SHIFT)))
+				continue;
+			if (dense && is_merge &&
+			    omit_in_dense(commit, rev, num_rev))
+				continue;
+			for (i = 0; i < num_rev; i++) {
+				int mark;
+				if (!(this_flag & (1u << (i + REV_SHIFT))))
+					mark = ' ';
+				else if (is_merge)
+					mark = '-';
+				else if (i == head_at)
+					mark = '*';
+				else
+					mark = '+';
+				putchar(mark);
+			}
+			putchar(' ');
+		}
+		show_one_commit(commit, no_name);
+
+		if (shown_merge_point && --extra < 0)
+			break;
+	}
+	return 0;
+}
diff --git a/builtin.h b/builtin.h
index d6ff88e..01882ec 100644
--- a/builtin.h
+++ b/builtin.h
@@ -33,5 +33,6 @@ extern int cmd_tar_tree(int argc, const 
 extern int cmd_read_tree(int argc, const char **argv, char **envp);
 extern int cmd_commit_tree(int argc, const char **argv, char **envp);
 extern int cmd_apply(int argc, const char **argv, char **envp);
+extern int cmd_show_branch(int argc, const char **argv, char **envp);
 
 #endif
diff --git a/git.c b/git.c
index f44e08b..d29505c 100644
--- a/git.c
+++ b/git.c
@@ -58,7 +58,8 @@ static void handle_internal_command(int 
 		{ "tar-tree", cmd_tar_tree },
 		{ "read-tree", cmd_read_tree },
 		{ "commit-tree", cmd_commit_tree },
-		{ "apply", cmd_apply }
+		{ "apply", cmd_apply },
+		{ "show-branch", cmd_show_branch }
 	};
 	int i;
 
diff --git a/show-branch.c b/show-branch.c
deleted file mode 100644
index 268c57b..0000000
--- a/show-branch.c
+++ /dev/null
@@ -1,788 +0,0 @@
-#include <stdlib.h>
-#include <fnmatch.h>
-#include "cache.h"
-#include "commit.h"
-#include "refs.h"
-
-static const char show_branch_usage[] =
-"git-show-branch [--dense] [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...]";
-
-static int default_num = 0;
-static int default_alloc = 0;
-static char **default_arg = NULL;
-
-#define UNINTERESTING	01
-
-#define REV_SHIFT	 2
-#define MAX_REVS	29 /* should not exceed bits_per_int - REV_SHIFT */
-
-static struct commit *interesting(struct commit_list *list)
-{
-	while (list) {
-		struct commit *commit = list->item;
-		list = list->next;
-		if (commit->object.flags & UNINTERESTING)
-			continue;
-		return commit;
-	}
-	return NULL;
-}
-
-static struct commit *pop_one_commit(struct commit_list **list_p)
-{
-	struct commit *commit;
-	struct commit_list *list;
-	list = *list_p;
-	commit = list->item;
-	*list_p = list->next;
-	free(list);
-	return commit;
-}
-
-struct commit_name {
-	const char *head_name; /* which head's ancestor? */
-	int generation; /* how many parents away from head_name */
-};
-
-/* Name the commit as nth generation ancestor of head_name;
- * we count only the first-parent relationship for naming purposes.
- */
-static void name_commit(struct commit *commit, const char *head_name, int nth)
-{
-	struct commit_name *name;
-	if (!commit->object.util)
-		commit->object.util = xmalloc(sizeof(struct commit_name));
-	name = commit->object.util;
-	name->head_name = head_name;
-	name->generation = nth;
-}
-
-/* Parent is the first parent of the commit.  We may name it
- * as (n+1)th generation ancestor of the same head_name as
- * commit is nth generation ancestor of, if that generation
- * number is better than the name it already has.
- */
-static void name_parent(struct commit *commit, struct commit *parent)
-{
-	struct commit_name *commit_name = commit->object.util;
-	struct commit_name *parent_name = parent->object.util;
-	if (!commit_name)
-		return;
-	if (!parent_name ||
-	    commit_name->generation + 1 < parent_name->generation)
-		name_commit(parent, commit_name->head_name,
-			    commit_name->generation + 1);
-}
-
-static int name_first_parent_chain(struct commit *c)
-{
-	int i = 0;
-	while (c) {
-		struct commit *p;
-		if (!c->object.util)
-			break;
-		if (!c->parents)
-			break;
-		p = c->parents->item;
-		if (!p->object.util) {
-			name_parent(c, p);
-			i++;
-		}
-		c = p;
-	}
-	return i;
-}
-
-static void name_commits(struct commit_list *list,
-			 struct commit **rev,
-			 char **ref_name,
-			 int num_rev)
-{
-	struct commit_list *cl;
-	struct commit *c;
-	int i;
-
-	/* First give names to the given heads */
-	for (cl = list; cl; cl = cl->next) {
-		c = cl->item;
-		if (c->object.util)
-			continue;
-		for (i = 0; i < num_rev; i++) {
-			if (rev[i] == c) {
-				name_commit(c, ref_name[i], 0);
-				break;
-			}
-		}
-	}
-
-	/* Then commits on the first parent ancestry chain */
-	do {
-		i = 0;
-		for (cl = list; cl; cl = cl->next) {
-			i += name_first_parent_chain(cl->item);
-		}
-	} while (i);
-
-	/* Finally, any unnamed commits */
-	do {
-		i = 0;
-		for (cl = list; cl; cl = cl->next) {
-			struct commit_list *parents;
-			struct commit_name *n;
-			int nth;
-			c = cl->item;
-			if (!c->object.util)
-				continue;
-			n = c->object.util;
-			parents = c->parents;
-			nth = 0;
-			while (parents) {
-				struct commit *p = parents->item;
-				char newname[1000], *en;
-				parents = parents->next;
-				nth++;
-				if (p->object.util)
-					continue;
-				en = newname;
-				switch (n->generation) {
-				case 0:
-					en += sprintf(en, "%s", n->head_name);
-					break;
-				case 1:
-					en += sprintf(en, "%s^", n->head_name);
-					break;
-				default:
-					en += sprintf(en, "%s~%d",
-						n->head_name, n->generation);
-					break;
-				}
-				if (nth == 1)
-					en += sprintf(en, "^");
-				else
-					en += sprintf(en, "^%d", nth);
-				name_commit(p, strdup(newname), 0);
-				i++;
-				name_first_parent_chain(p);
-			}
-		}
-	} while (i);
-}
-
-static int mark_seen(struct commit *commit, struct commit_list **seen_p)
-{
-	if (!commit->object.flags) {
-		insert_by_date(commit, seen_p);
-		return 1;
-	}
-	return 0;
-}
-
-static void join_revs(struct commit_list **list_p,
-		      struct commit_list **seen_p,
-		      int num_rev, int extra)
-{
-	int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
-	int all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
-
-	while (*list_p) {
-		struct commit_list *parents;
-		int still_interesting = !!interesting(*list_p);
-		struct commit *commit = pop_one_commit(list_p);
-		int flags = commit->object.flags & all_mask;
-
-		if (!still_interesting && extra <= 0)
-			break;
-
-		mark_seen(commit, seen_p);
-		if ((flags & all_revs) == all_revs)
-			flags |= UNINTERESTING;
-		parents = commit->parents;
-
-		while (parents) {
-			struct commit *p = parents->item;
-			int this_flag = p->object.flags;
-			parents = parents->next;
-			if ((this_flag & flags) == flags)
-				continue;
-			if (!p->object.parsed)
-				parse_commit(p);
-			if (mark_seen(p, seen_p) && !still_interesting)
-				extra--;
-			p->object.flags |= flags;
-			insert_by_date(p, list_p);
-		}
-	}
-
-	/*
-	 * Postprocess to complete well-poisoning.
-	 *
-	 * At this point we have all the commits we have seen in
-	 * seen_p list (which happens to be sorted chronologically but
-	 * it does not really matter).  Mark anything that can be
-	 * reached from uninteresting commits not interesting.
-	 */
-	for (;;) {
-		int changed = 0;
-		struct commit_list *s;
-		for (s = *seen_p; s; s = s->next) {
-			struct commit *c = s->item;
-			struct commit_list *parents;
-
-			if (((c->object.flags & all_revs) != all_revs) &&
-			    !(c->object.flags & UNINTERESTING))
-				continue;
-
-			/* The current commit is either a merge base or
-			 * already uninteresting one.  Mark its parents
-			 * as uninteresting commits _only_ if they are
-			 * already parsed.  No reason to find new ones
-			 * here.
-			 */
-			parents = c->parents;
-			while (parents) {
-				struct commit *p = parents->item;
-				parents = parents->next;
-				if (!(p->object.flags & UNINTERESTING)) {
-					p->object.flags |= UNINTERESTING;
-					changed = 1;
-				}
-			}
-		}
-		if (!changed)
-			break;
-	}
-}
-
-static void show_one_commit(struct commit *commit, int no_name)
-{
-	char pretty[256], *cp;
-	struct commit_name *name = commit->object.util;
-	if (commit->object.parsed)
-		pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0,
-				    pretty, sizeof(pretty), 0);
-	else
-		strcpy(pretty, "(unavailable)");
-	if (!strncmp(pretty, "[PATCH] ", 8))
-		cp = pretty + 8;
-	else
-		cp = pretty;
-
-	if (!no_name) {
-		if (name && name->head_name) {
-			printf("[%s", name->head_name);
-			if (name->generation) {
-				if (name->generation == 1)
-					printf("^");
-				else
-					printf("~%d", name->generation);
-			}
-			printf("] ");
-		}
-		else
-			printf("[%s] ",
-			       find_unique_abbrev(commit->object.sha1, 7));
-	}
-	puts(cp);
-}
-
-static char *ref_name[MAX_REVS + 1];
-static int ref_name_cnt;
-
-static const char *find_digit_prefix(const char *s, int *v)
-{
-	const char *p;
-	int ver;
-	char ch;
-
-	for (p = s, ver = 0;
-	     '0' <= (ch = *p) && ch <= '9';
-	     p++)
-		ver = ver * 10 + ch - '0';
-	*v = ver;
-	return p;
-}
-
-
-static int version_cmp(const char *a, const char *b)
-{
-	while (1) {
-		int va, vb;
-
-		a = find_digit_prefix(a, &va);
-		b = find_digit_prefix(b, &vb);
-		if (va != vb)
-			return va - vb;
-
-		while (1) {
-			int ca = *a;
-			int cb = *b;
-			if ('0' <= ca && ca <= '9')
-				ca = 0;
-			if ('0' <= cb && cb <= '9')
-				cb = 0;
-			if (ca != cb)
-				return ca - cb;
-			if (!ca)
-				break;
-			a++;
-			b++;
-		}
-		if (!*a && !*b)
-			return 0;
-	}
-}
-
-static int compare_ref_name(const void *a_, const void *b_)
-{
-	const char * const*a = a_, * const*b = b_;
-	return version_cmp(*a, *b);
-}
-
-static void sort_ref_range(int bottom, int top)
-{
-	qsort(ref_name + bottom, top - bottom, sizeof(ref_name[0]),
-	      compare_ref_name);
-}
-
-static int append_ref(const char *refname, const unsigned char *sha1)
-{
-	struct commit *commit = lookup_commit_reference_gently(sha1, 1);
-	int i;
-
-	if (!commit)
-		return 0;
-	/* Avoid adding the same thing twice */
-	for (i = 0; i < ref_name_cnt; i++)
-		if (!strcmp(refname, ref_name[i]))
-			return 0;
-
-	if (MAX_REVS <= ref_name_cnt) {
-		fprintf(stderr, "warning: ignoring %s; "
-			"cannot handle more than %d refs\n",
-			refname, MAX_REVS);
-		return 0;
-	}
-	ref_name[ref_name_cnt++] = strdup(refname);
-	ref_name[ref_name_cnt] = NULL;
-	return 0;
-}
-
-static int append_head_ref(const char *refname, const unsigned char *sha1)
-{
-	unsigned char tmp[20];
-	int ofs = 11;
-	if (strncmp(refname, "refs/heads/", ofs))
-		return 0;
-	/* If both heads/foo and tags/foo exists, get_sha1 would
-	 * get confused.
-	 */
-	if (get_sha1(refname + ofs, tmp) || memcmp(tmp, sha1, 20))
-		ofs = 5;
-	return append_ref(refname + ofs, sha1);
-}
-
-static int append_tag_ref(const char *refname, const unsigned char *sha1)
-{
-	if (strncmp(refname, "refs/tags/", 10))
-		return 0;
-	return append_ref(refname + 5, sha1);
-}
-
-static const char *match_ref_pattern = NULL;
-static int match_ref_slash = 0;
-static int count_slash(const char *s)
-{
-	int cnt = 0;
-	while (*s)
-		if (*s++ == '/')
-			cnt++;
-	return cnt;
-}
-
-static int append_matching_ref(const char *refname, const unsigned char *sha1)
-{
-	/* we want to allow pattern hold/<asterisk> to show all
-	 * branches under refs/heads/hold/, and v0.99.9? to show
-	 * refs/tags/v0.99.9a and friends.
-	 */
-	const char *tail;
-	int slash = count_slash(refname);
-	for (tail = refname; *tail && match_ref_slash < slash; )
-		if (*tail++ == '/')
-			slash--;
-	if (!*tail)
-		return 0;
-	if (fnmatch(match_ref_pattern, tail, 0))
-		return 0;
-	if (!strncmp("refs/heads/", refname, 11))
-		return append_head_ref(refname, sha1);
-	if (!strncmp("refs/tags/", refname, 10))
-		return append_tag_ref(refname, sha1);
-	return append_ref(refname, sha1);
-}
-
-static void snarf_refs(int head, int tag)
-{
-	if (head) {
-		int orig_cnt = ref_name_cnt;
-		for_each_ref(append_head_ref);
-		sort_ref_range(orig_cnt, ref_name_cnt);
-	}
-	if (tag) {
-		int orig_cnt = ref_name_cnt;
-		for_each_ref(append_tag_ref);
-		sort_ref_range(orig_cnt, ref_name_cnt);
-	}
-}
-
-static int rev_is_head(char *head_path, int headlen, char *name,
-		       unsigned char *head_sha1, unsigned char *sha1)
-{
-	int namelen;
-	if ((!head_path[0]) ||
-	    (head_sha1 && sha1 && memcmp(head_sha1, sha1, 20)))
-		return 0;
-	namelen = strlen(name);
-	if ((headlen < namelen) ||
-	    memcmp(head_path + headlen - namelen, name, namelen))
-		return 0;
-	if (headlen == namelen ||
-	    head_path[headlen - namelen - 1] == '/')
-		return 1;
-	return 0;
-}
-
-static int show_merge_base(struct commit_list *seen, int num_rev)
-{
-	int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
-	int all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
-	int exit_status = 1;
-
-	while (seen) {
-		struct commit *commit = pop_one_commit(&seen);
-		int flags = commit->object.flags & all_mask;
-		if (!(flags & UNINTERESTING) &&
-		    ((flags & all_revs) == all_revs)) {
-			puts(sha1_to_hex(commit->object.sha1));
-			exit_status = 0;
-			commit->object.flags |= UNINTERESTING;
-		}
-	}
-	return exit_status;
-}
-
-static int show_independent(struct commit **rev,
-			    int num_rev,
-			    char **ref_name,
-			    unsigned int *rev_mask)
-{
-	int i;
-
-	for (i = 0; i < num_rev; i++) {
-		struct commit *commit = rev[i];
-		unsigned int flag = rev_mask[i];
-
-		if (commit->object.flags == flag)
-			puts(sha1_to_hex(commit->object.sha1));
-		commit->object.flags |= UNINTERESTING;
-	}
-	return 0;
-}
-
-static void append_one_rev(const char *av)
-{
-	unsigned char revkey[20];
-	if (!get_sha1(av, revkey)) {
-		append_ref(av, revkey);
-		return;
-	}
-	if (strchr(av, '*') || strchr(av, '?') || strchr(av, '[')) {
-		/* glob style match */
-		int saved_matches = ref_name_cnt;
-		match_ref_pattern = av;
-		match_ref_slash = count_slash(av);
-		for_each_ref(append_matching_ref);
-		if (saved_matches == ref_name_cnt &&
-		    ref_name_cnt < MAX_REVS)
-			error("no matching refs with %s", av);
-		if (saved_matches + 1 < ref_name_cnt)
-			sort_ref_range(saved_matches, ref_name_cnt);
-		return;
-	}
-	die("bad sha1 reference %s", av);
-}
-
-static int git_show_branch_config(const char *var, const char *value)
-{
-	if (!strcmp(var, "showbranch.default")) {
-		if (default_alloc <= default_num + 1) {
-			default_alloc = default_alloc * 3 / 2 + 20;
-			default_arg = xrealloc(default_arg, sizeof *default_arg * default_alloc);
-		}
-		default_arg[default_num++] = strdup(value);
-		default_arg[default_num] = NULL;
-		return 0;
-	}
-
-	return git_default_config(var, value);
-}
-
-static int omit_in_dense(struct commit *commit, struct commit **rev, int n)
-{
-	/* If the commit is tip of the named branches, do not
-	 * omit it.
-	 * Otherwise, if it is a merge that is reachable from only one
-	 * tip, it is not that interesting.
-	 */
-	int i, flag, count;
-	for (i = 0; i < n; i++)
-		if (rev[i] == commit)
-			return 0;
-	flag = commit->object.flags;
-	for (i = count = 0; i < n; i++) {
-		if (flag & (1u << (i + REV_SHIFT)))
-			count++;
-	}
-	if (count == 1)
-		return 1;
-	return 0;
-}
-
-int main(int ac, char **av)
-{
-	struct commit *rev[MAX_REVS], *commit;
-	struct commit_list *list = NULL, *seen = NULL;
-	unsigned int rev_mask[MAX_REVS];
-	int num_rev, i, extra = 0;
-	int all_heads = 0, all_tags = 0;
-	int all_mask, all_revs;
-	int lifo = 1;
-	char head_path[128];
-	const char *head_path_p;
-	int head_path_len;
-	unsigned char head_sha1[20];
-	int merge_base = 0;
-	int independent = 0;
-	int no_name = 0;
-	int sha1_name = 0;
-	int shown_merge_point = 0;
-	int with_current_branch = 0;
-	int head_at = -1;
-	int topics = 0;
-	int dense = 1;
-
-	setup_git_directory();
-	git_config(git_show_branch_config);
-
-	/* If nothing is specified, try the default first */
-	if (ac == 1 && default_num) {
-		ac = default_num + 1;
-		av = default_arg - 1; /* ick; we would not address av[0] */
-	}
-
-	while (1 < ac && av[1][0] == '-') {
-		char *arg = av[1];
-		if (!strcmp(arg, "--")) {
-			ac--; av++;
-			break;
-		}
-		else if (!strcmp(arg, "--all"))
-			all_heads = all_tags = 1;
-		else if (!strcmp(arg, "--heads"))
-			all_heads = 1;
-		else if (!strcmp(arg, "--tags"))
-			all_tags = 1;
-		else if (!strcmp(arg, "--more"))
-			extra = 1;
-		else if (!strcmp(arg, "--list"))
-			extra = -1;
-		else if (!strcmp(arg, "--no-name"))
-			no_name = 1;
-		else if (!strcmp(arg, "--current"))
-			with_current_branch = 1;
-		else if (!strcmp(arg, "--sha1-name"))
-			sha1_name = 1;
-		else if (!strncmp(arg, "--more=", 7))
-			extra = atoi(arg + 7);
-		else if (!strcmp(arg, "--merge-base"))
-			merge_base = 1;
-		else if (!strcmp(arg, "--independent"))
-			independent = 1;
-		else if (!strcmp(arg, "--topo-order"))
-			lifo = 1;
-		else if (!strcmp(arg, "--topics"))
-			topics = 1;
-		else if (!strcmp(arg, "--sparse"))
-			dense = 0;
-		else if (!strcmp(arg, "--date-order"))
-			lifo = 0;
-		else
-			usage(show_branch_usage);
-		ac--; av++;
-	}
-	ac--; av++;
-
-	/* Only one of these is allowed */
-	if (1 < independent + merge_base + (extra != 0))
-		usage(show_branch_usage);
-
-	/* If nothing is specified, show all branches by default */
-	if (ac + all_heads + all_tags == 0)
-		all_heads = 1;
-
-	if (all_heads + all_tags)
-		snarf_refs(all_heads, all_tags);
-	while (0 < ac) {
-		append_one_rev(*av);
-		ac--; av++;
-	}
-
-	head_path_p = resolve_ref(git_path("HEAD"), head_sha1, 1);
-	if (head_path_p) {
-		head_path_len = strlen(head_path_p);
-		memcpy(head_path, head_path_p, head_path_len + 1);
-	}
-	else {
-		head_path_len = 0;
-		head_path[0] = 0;
-	}
-
-	if (with_current_branch && head_path_p) {
-		int has_head = 0;
-		for (i = 0; !has_head && i < ref_name_cnt; i++) {
-			/* We are only interested in adding the branch
-			 * HEAD points at.
-			 */
-			if (rev_is_head(head_path,
-					head_path_len,
-					ref_name[i],
-					head_sha1, NULL))
-				has_head++;
-		}
-		if (!has_head) {
-			int pfxlen = strlen(git_path("refs/heads/"));
-			append_one_rev(head_path + pfxlen);
-		}
-	}
-
-	if (!ref_name_cnt) {
-		fprintf(stderr, "No revs to be shown.\n");
-		exit(0);
-	}
-
-	for (num_rev = 0; ref_name[num_rev]; num_rev++) {
-		unsigned char revkey[20];
-		unsigned int flag = 1u << (num_rev + REV_SHIFT);
-
-		if (MAX_REVS <= num_rev)
-			die("cannot handle more than %d revs.", MAX_REVS);
-		if (get_sha1(ref_name[num_rev], revkey))
-			die("'%s' is not a valid ref.", ref_name[num_rev]);
-		commit = lookup_commit_reference(revkey);
-		if (!commit)
-			die("cannot find commit %s (%s)",
-			    ref_name[num_rev], revkey);
-		parse_commit(commit);
-		mark_seen(commit, &seen);
-
-		/* rev#0 uses bit REV_SHIFT, rev#1 uses bit REV_SHIFT+1,
-		 * and so on.  REV_SHIFT bits from bit 0 are used for
-		 * internal bookkeeping.
-		 */
-		commit->object.flags |= flag;
-		if (commit->object.flags == flag)
-			insert_by_date(commit, &list);
-		rev[num_rev] = commit;
-	}
-	for (i = 0; i < num_rev; i++)
-		rev_mask[i] = rev[i]->object.flags;
-
-	if (0 <= extra)
-		join_revs(&list, &seen, num_rev, extra);
-
-	if (merge_base)
-		return show_merge_base(seen, num_rev);
-
-	if (independent)
-		return show_independent(rev, num_rev, ref_name, rev_mask);
-
-	/* Show list; --more=-1 means list-only */
-	if (1 < num_rev || extra < 0) {
-		for (i = 0; i < num_rev; i++) {
-			int j;
-			int is_head = rev_is_head(head_path,
-						  head_path_len,
-						  ref_name[i],
-						  head_sha1,
-						  rev[i]->object.sha1);
-			if (extra < 0)
-				printf("%c [%s] ",
-				       is_head ? '*' : ' ', ref_name[i]);
-			else {
-				for (j = 0; j < i; j++)
-					putchar(' ');
-				printf("%c [%s] ",
-				       is_head ? '*' : '!', ref_name[i]);
-			}
-			/* header lines never need name */
-			show_one_commit(rev[i], 1);
-			if (is_head)
-				head_at = i;
-		}
-		if (0 <= extra) {
-			for (i = 0; i < num_rev; i++)
-				putchar('-');
-			putchar('\n');
-		}
-	}
-	if (extra < 0)
-		exit(0);
-
-	/* Sort topologically */
-	sort_in_topological_order(&seen, lifo);
-
-	/* Give names to commits */
-	if (!sha1_name && !no_name)
-		name_commits(seen, rev, ref_name, num_rev);
-
-	all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
-	all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
-
-	while (seen) {
-		struct commit *commit = pop_one_commit(&seen);
-		int this_flag = commit->object.flags;
-		int is_merge_point = ((this_flag & all_revs) == all_revs);
-
-		shown_merge_point |= is_merge_point;
-
-		if (1 < num_rev) {
-			int is_merge = !!(commit->parents &&
-					  commit->parents->next);
-			if (topics &&
-			    !is_merge_point &&
-			    (this_flag & (1u << REV_SHIFT)))
-				continue;
-			if (dense && is_merge &&
-			    omit_in_dense(commit, rev, num_rev))
-				continue;
-			for (i = 0; i < num_rev; i++) {
-				int mark;
-				if (!(this_flag & (1u << (i + REV_SHIFT))))
-					mark = ' ';
-				else if (is_merge)
-					mark = '-';
-				else if (i == head_at)
-					mark = '*';
-				else
-					mark = '+';
-				putchar(mark);
-			}
-			putchar(' ');
-		}
-		show_one_commit(commit, no_name);
-
-		if (shown_merge_point && --extra < 0)
-			break;
-	}
-	return 0;
-}
-- 
1.3.3.g288c

^ permalink raw reply related

* [PATCH 8/8] Builtin git-diff-files, git-diff-index, git-diff-stages, and git-diff-tree.
From: Peter Eriksen @ 2006-05-23  8:31 UTC (permalink / raw)
  To: git; +Cc: Peter Eriksen
In-Reply-To: <11483730813930-git-send-email->

From: Peter Eriksen <s022018@student.dtu.dk>

Signed-off-by: Peter Eriksen <s022018@student.dtu.dk>


---

e2d2468b8550f2760b46a2eb461b051abfef4dff
 Makefile              |   12 ++--
 builtin-diff-files.c  |   55 ++++++++++++++++++
 builtin-diff-index.c  |   39 +++++++++++++
 builtin-diff-stages.c |  105 +++++++++++++++++++++++++++++++++++
 builtin-diff-tree.c   |  148 +++++++++++++++++++++++++++++++++++++++++++++++++
 builtin.h             |    5 ++
 diff-files.c          |   54 ------------------
 diff-index.c          |   38 -------------
 diff-stages.c         |  104 ----------------------------------
 diff-tree.c           |  147 -------------------------------------------------
 git.c                 |    6 ++
 11 files changed, 363 insertions(+), 350 deletions(-)
 create mode 100644 builtin-diff-files.c
 create mode 100644 builtin-diff-index.c
 create mode 100644 builtin-diff-stages.c
 create mode 100644 builtin-diff-tree.c
 delete mode 100644 diff-files.c
 delete mode 100644 diff-index.c
 delete mode 100644 diff-stages.c
 delete mode 100644 diff-tree.c

e2d2468b8550f2760b46a2eb461b051abfef4dff
diff --git a/Makefile b/Makefile
index b438a90..9dc1326 100644
--- a/Makefile
+++ b/Makefile
@@ -151,9 +151,7 @@ # ... and all the rest that could be mov
 PROGRAMS = \
 	git-cat-file$X \
 	git-checkout-index$X git-clone-pack$X \
-	git-convert-objects$X git-diff-files$X \
-	git-diff-index$X git-diff-stages$X \
-	git-diff-tree$X git-fetch-pack$X git-fsck-objects$X \
+	git-convert-objects$X git-fetch-pack$X git-fsck-objects$X \
 	git-hash-object$X git-index-pack$X git-local-fetch$X \
 	git-mailinfo$X git-merge-base$X \
 	git-merge-index$X git-mktag$X git-mktree$X git-pack-objects$X git-patch-id$X \
@@ -173,7 +171,8 @@ BUILT_INS = git-log$X git-whatchanged$X 
 	git-grep$X git-rev-list$X git-check-ref-format$X \
 	git-init-db$X git-ls-files$X git-ls-tree$X \
 	git-tar-tree$X git-read-tree$X git-commit-tree$X \
-	git-apply$X git-show-branch$X
+	git-apply$X git-show-branch$X git-diff-files$X \
+	git-diff-index$X git-diff-stages$X git-diff-tree$X
 
 # what 'all' will build and 'install' will install, in gitexecdir
 ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
@@ -223,8 +222,9 @@ BUILTIN_OBJS = \
 	builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \
 	builtin-grep.o builtin-rev-list.o builtin-check-ref-format.o \
 	builtin-init-db.o builtin-ls-files.o builtin-ls-tree.o \
-        builtin-tar-tree.o builtin-read-tree.o builtin-commit-tree.o \
-	builtin-apply.o builtin-show-branch.o
+	builtin-tar-tree.o builtin-read-tree.o builtin-commit-tree.o \
+	builtin-apply.o builtin-show-branch.o builtin-diff-files.o \
+	builtin-diff-index.o builtin-diff-stages.o builtin-diff-tree.o
 
 GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
 LIBS = $(GITLIBS) -lz
diff --git a/builtin-diff-files.c b/builtin-diff-files.c
new file mode 100644
index 0000000..cebda82
--- /dev/null
+++ b/builtin-diff-files.c
@@ -0,0 +1,55 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "diff.h"
+#include "commit.h"
+#include "revision.h"
+#include "builtin.h"
+
+static const char diff_files_usage[] =
+"git-diff-files [-q] [-0/-1/2/3 |-c|--cc] [<common diff options>] [<path>...]"
+COMMON_DIFF_OPTIONS_HELP;
+
+int cmd_diff_files(int argc, const char **argv, char **envp)
+{
+	struct rev_info rev;
+	int silent = 0;
+
+	git_config(git_diff_config);
+	init_revisions(&rev);
+	rev.abbrev = 0;
+
+	argc = setup_revisions(argc, argv, &rev, NULL);
+	while (1 < argc && argv[1][0] == '-') {
+		if (!strcmp(argv[1], "--base"))
+			rev.max_count = 1;
+		else if (!strcmp(argv[1], "--ours"))
+			rev.max_count = 2;
+		else if (!strcmp(argv[1], "--theirs"))
+			rev.max_count = 3;
+		else if (!strcmp(argv[1], "-q"))
+			silent = 1;
+		else
+			usage(diff_files_usage);
+		argv++; argc--;
+	}
+	/*
+	 * Make sure there are NO revision (i.e. pending object) parameter,
+	 * rev.max_count is reasonable (0 <= n <= 3),
+	 * there is no other revision filtering parameters.
+	 */
+	if (rev.pending_objects ||
+	    rev.min_age != -1 || rev.max_age != -1)
+		usage(diff_files_usage);
+	/*
+	 * Backward compatibility wart - "diff-files -s" used to
+	 * defeat the common diff option "-s" which asked for
+	 * DIFF_FORMAT_NO_OUTPUT.
+	 */
+	if (rev.diffopt.output_format == DIFF_FORMAT_NO_OUTPUT)
+		rev.diffopt.output_format = DIFF_FORMAT_RAW;
+	return run_diff_files(&rev, silent);
+}
diff --git a/builtin-diff-index.c b/builtin-diff-index.c
new file mode 100644
index 0000000..1958580
--- /dev/null
+++ b/builtin-diff-index.c
@@ -0,0 +1,39 @@
+#include "cache.h"
+#include "diff.h"
+#include "commit.h"
+#include "revision.h"
+#include "builtin.h"
+
+static const char diff_cache_usage[] =
+"git-diff-index [-m] [--cached] "
+"[<common diff options>] <tree-ish> [<path>...]"
+COMMON_DIFF_OPTIONS_HELP;
+
+int cmd_diff_index(int argc, const char **argv, char **envp)
+{
+	struct rev_info rev;
+	int cached = 0;
+	int i;
+
+	git_config(git_diff_config);
+	init_revisions(&rev);
+	rev.abbrev = 0;
+
+	argc = setup_revisions(argc, argv, &rev, NULL);
+	for (i = 1; i < argc; i++) {
+		const char *arg = argv[i];
+			
+		if (!strcmp(arg, "--cached"))
+			cached = 1;
+		else
+			usage(diff_cache_usage);
+	}
+	/*
+	 * Make sure there is one revision (i.e. pending object),
+	 * and there is no revision filtering parameters.
+	 */
+	if (!rev.pending_objects || rev.pending_objects->next ||
+	    rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1)
+		usage(diff_cache_usage);
+	return run_diff_index(&rev, cached);
+}
diff --git a/builtin-diff-stages.c b/builtin-diff-stages.c
new file mode 100644
index 0000000..7c157ca
--- /dev/null
+++ b/builtin-diff-stages.c
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2005 Junio C Hamano
+ */
+
+#include "cache.h"
+#include "diff.h"
+#include "builtin.h"
+
+static struct diff_options diff_options;
+
+static const char diff_stages_usage[] =
+"git-diff-stages [<common diff options>] <stage1> <stage2> [<path>...]"
+COMMON_DIFF_OPTIONS_HELP;
+
+static void diff_stages(int stage1, int stage2, const char **pathspec)
+{
+	int i = 0;
+	while (i < active_nr) {
+		struct cache_entry *ce, *stages[4] = { NULL, };
+		struct cache_entry *one, *two;
+		const char *name;
+		int len, skip;
+
+		ce = active_cache[i];
+		skip = !ce_path_match(ce, pathspec);
+		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 (skip || (!one && !two))
+			continue;
+		if (!one)
+			diff_addremove(&diff_options, '+', ntohl(two->ce_mode),
+				       two->sha1, name, NULL);
+		else if (!two)
+			diff_addremove(&diff_options, '-', ntohl(one->ce_mode),
+				       one->sha1, name, NULL);
+		else if (memcmp(one->sha1, two->sha1, 20) ||
+			 (one->ce_mode != two->ce_mode) ||
+			 diff_options.find_copies_harder)
+			diff_change(&diff_options,
+				    ntohl(one->ce_mode), ntohl(two->ce_mode),
+				    one->sha1, two->sha1, name, NULL);
+	}
+}
+
+int cmd_diff_stages(int ac, const char **av, char **envp)
+{
+	int stage1, stage2;
+	const char *prefix = setup_git_directory();
+	const char **pathspec = NULL;
+
+	git_config(git_diff_config);
+	read_cache();
+	diff_setup(&diff_options);
+	while (1 < ac && av[1][0] == '-') {
+		const char *arg = av[1];
+		if (!strcmp(arg, "-r"))
+			; /* as usual */
+		else {
+			int diff_opt_cnt;
+			diff_opt_cnt = diff_opt_parse(&diff_options,
+						      av+1, ac-1);
+			if (diff_opt_cnt < 0)
+				usage(diff_stages_usage);
+			else if (diff_opt_cnt) {
+				av += diff_opt_cnt;
+				ac -= diff_opt_cnt;
+				continue;
+			}
+			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. */
+	pathspec = get_pathspec(prefix, av);
+
+	if (diff_setup_done(&diff_options) < 0)
+		usage(diff_stages_usage);
+
+	diff_stages(stage1, stage2, pathspec);
+	diffcore_std(&diff_options);
+	diff_flush(&diff_options);
+	return 0;
+}
diff --git a/builtin-diff-tree.c b/builtin-diff-tree.c
new file mode 100644
index 0000000..cc53b81
--- /dev/null
+++ b/builtin-diff-tree.c
@@ -0,0 +1,148 @@
+#include "cache.h"
+#include "diff.h"
+#include "commit.h"
+#include "log-tree.h"
+#include "builtin.h"
+
+static struct rev_info log_tree_opt;
+
+static int diff_tree_commit_sha1(const unsigned char *sha1)
+{
+	struct commit *commit = lookup_commit_reference(sha1);
+	if (!commit)
+		return -1;
+	return log_tree_commit(&log_tree_opt, commit);
+}
+
+static int diff_tree_stdin(char *line)
+{
+	int len = strlen(line);
+	unsigned char sha1[20];
+	struct commit *commit;
+
+	if (!len || line[len-1] != '\n')
+		return -1;
+	line[len-1] = 0;
+	if (get_sha1_hex(line, sha1))
+		return -1;
+	commit = lookup_commit(sha1);
+	if (!commit || parse_commit(commit))
+		return -1;
+	if (isspace(line[40]) && !get_sha1_hex(line+41, sha1)) {
+		/* Graft the fake parents locally to the commit */
+		int pos = 41;
+		struct commit_list **pptr, *parents;
+
+		/* Free the real parent list */
+		for (parents = commit->parents; parents; ) {
+			struct commit_list *tmp = parents->next;
+			free(parents);
+			parents = tmp;
+		}
+		commit->parents = NULL;
+		pptr = &(commit->parents);
+		while (line[pos] && !get_sha1_hex(line + pos, sha1)) {
+			struct commit *parent = lookup_commit(sha1);
+			if (parent) {
+				pptr = &commit_list_insert(parent, pptr)->next;
+			}
+			pos += 41;
+		}
+	}
+	return log_tree_commit(&log_tree_opt, commit);
+}
+
+static const char diff_tree_usage[] =
+"git-diff-tree [--stdin] [-m] [-c] [--cc] [-s] [-v] [--pretty] [-t] [-r] [--root] "
+"[<common diff options>] <tree-ish> [<tree-ish>] [<path>...]\n"
+"  -r            diff recursively\n"
+"  --root        include the initial commit as diff against /dev/null\n"
+COMMON_DIFF_OPTIONS_HELP;
+
+int cmd_diff_tree(int argc, const char **argv, char **envp)
+{
+	int nr_sha1;
+	char line[1000];
+	struct object *tree1, *tree2;
+	static struct rev_info *opt = &log_tree_opt;
+	struct object_list *list;
+	int read_stdin = 0;
+
+	git_config(git_diff_config);
+	nr_sha1 = 0;
+	init_revisions(opt);
+	opt->abbrev = 0;
+	opt->diff = 1;
+	argc = setup_revisions(argc, argv, opt, NULL);
+
+	while (--argc > 0) {
+		const char *arg = *++argv;
+
+		if (!strcmp(arg, "--stdin")) {
+			read_stdin = 1;
+			continue;
+		}
+		usage(diff_tree_usage);
+	}
+
+	/*
+	 * NOTE! "setup_revisions()" will have inserted the revisions
+	 * it parsed in reverse order. So if you do
+	 *
+	 *	git-diff-tree a b
+	 *
+	 * the commit list will be "b" -> "a" -> NULL, so we reverse
+	 * the order of the objects if the first one is not marked
+	 * UNINTERESTING.
+	 */
+	nr_sha1 = 0;
+	list = opt->pending_objects;
+	if (list) {
+		nr_sha1++;
+		tree1 = list->item;
+		list = list->next;
+		if (list) {
+			nr_sha1++;
+			tree2 = tree1;
+			tree1 = list->item;
+			if (list->next)
+				usage(diff_tree_usage);
+			/* Switch them around if the second one was uninteresting.. */
+			if (tree2->flags & UNINTERESTING) {
+				struct object *tmp = tree2;
+				tree2 = tree1;
+				tree1 = tmp;
+			}
+		}
+	}
+
+	switch (nr_sha1) {
+	case 0:
+		if (!read_stdin)
+			usage(diff_tree_usage);
+		break;
+	case 1:
+		diff_tree_commit_sha1(tree1->sha1);
+		break;
+	case 2:
+		diff_tree_sha1(tree1->sha1,
+			       tree2->sha1,
+			       "", &opt->diffopt);
+		log_tree_diff_flush(opt);
+		break;
+	}
+
+	if (!read_stdin)
+		return 0;
+
+	if (opt->diffopt.detect_rename)
+		opt->diffopt.setup |= (DIFF_SETUP_USE_SIZE_CACHE |
+				       DIFF_SETUP_USE_CACHE);
+	while (fgets(line, sizeof(line), stdin))
+		if (line[0] == '\n')
+			fflush(stdout);
+		else
+			diff_tree_stdin(line);
+
+	return 0;
+}
diff --git a/builtin.h b/builtin.h
index 01882ec..7620984 100644
--- a/builtin.h
+++ b/builtin.h
@@ -34,5 +34,10 @@ extern int cmd_read_tree(int argc, const
 extern int cmd_commit_tree(int argc, const char **argv, char **envp);
 extern int cmd_apply(int argc, const char **argv, char **envp);
 extern int cmd_show_branch(int argc, const char **argv, char **envp);
+extern int cmd_diff_files(int argc, const char **argv, char **envp);
+extern int cmd_diff_index(int argc, const char **argv, char **envp);
+extern int cmd_diff_stages(int argc, const char **argv, char **envp);
+extern int cmd_diff_tree(int argc, const char **argv, char **envp);
+
 
 #endif
diff --git a/diff-files.c b/diff-files.c
deleted file mode 100644
index b9d193d..0000000
--- a/diff-files.c
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * GIT - The information manager from hell
- *
- * Copyright (C) Linus Torvalds, 2005
- */
-#include "cache.h"
-#include "diff.h"
-#include "commit.h"
-#include "revision.h"
-
-static const char diff_files_usage[] =
-"git-diff-files [-q] [-0/-1/2/3 |-c|--cc] [<common diff options>] [<path>...]"
-COMMON_DIFF_OPTIONS_HELP;
-
-int main(int argc, const char **argv)
-{
-	struct rev_info rev;
-	int silent = 0;
-
-	git_config(git_diff_config);
-	init_revisions(&rev);
-	rev.abbrev = 0;
-
-	argc = setup_revisions(argc, argv, &rev, NULL);
-	while (1 < argc && argv[1][0] == '-') {
-		if (!strcmp(argv[1], "--base"))
-			rev.max_count = 1;
-		else if (!strcmp(argv[1], "--ours"))
-			rev.max_count = 2;
-		else if (!strcmp(argv[1], "--theirs"))
-			rev.max_count = 3;
-		else if (!strcmp(argv[1], "-q"))
-			silent = 1;
-		else
-			usage(diff_files_usage);
-		argv++; argc--;
-	}
-	/*
-	 * Make sure there are NO revision (i.e. pending object) parameter,
-	 * rev.max_count is reasonable (0 <= n <= 3),
-	 * there is no other revision filtering parameters.
-	 */
-	if (rev.pending_objects ||
-	    rev.min_age != -1 || rev.max_age != -1)
-		usage(diff_files_usage);
-	/*
-	 * Backward compatibility wart - "diff-files -s" used to
-	 * defeat the common diff option "-s" which asked for
-	 * DIFF_FORMAT_NO_OUTPUT.
-	 */
-	if (rev.diffopt.output_format == DIFF_FORMAT_NO_OUTPUT)
-		rev.diffopt.output_format = DIFF_FORMAT_RAW;
-	return run_diff_files(&rev, silent);
-}
diff --git a/diff-index.c b/diff-index.c
deleted file mode 100644
index 8c9f601..0000000
--- a/diff-index.c
+++ /dev/null
@@ -1,38 +0,0 @@
-#include "cache.h"
-#include "diff.h"
-#include "commit.h"
-#include "revision.h"
-
-static const char diff_cache_usage[] =
-"git-diff-index [-m] [--cached] "
-"[<common diff options>] <tree-ish> [<path>...]"
-COMMON_DIFF_OPTIONS_HELP;
-
-int main(int argc, const char **argv)
-{
-	struct rev_info rev;
-	int cached = 0;
-	int i;
-
-	git_config(git_diff_config);
-	init_revisions(&rev);
-	rev.abbrev = 0;
-
-	argc = setup_revisions(argc, argv, &rev, NULL);
-	for (i = 1; i < argc; i++) {
-		const char *arg = argv[i];
-			
-		if (!strcmp(arg, "--cached"))
-			cached = 1;
-		else
-			usage(diff_cache_usage);
-	}
-	/*
-	 * Make sure there is one revision (i.e. pending object),
-	 * and there is no revision filtering parameters.
-	 */
-	if (!rev.pending_objects || rev.pending_objects->next ||
-	    rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1)
-		usage(diff_cache_usage);
-	return run_diff_index(&rev, cached);
-}
diff --git a/diff-stages.c b/diff-stages.c
deleted file mode 100644
index dcd20e7..0000000
--- a/diff-stages.c
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (c) 2005 Junio C Hamano
- */
-
-#include "cache.h"
-#include "diff.h"
-
-static struct diff_options diff_options;
-
-static const char diff_stages_usage[] =
-"git-diff-stages [<common diff options>] <stage1> <stage2> [<path>...]"
-COMMON_DIFF_OPTIONS_HELP;
-
-static void diff_stages(int stage1, int stage2, const char **pathspec)
-{
-	int i = 0;
-	while (i < active_nr) {
-		struct cache_entry *ce, *stages[4] = { NULL, };
-		struct cache_entry *one, *two;
-		const char *name;
-		int len, skip;
-
-		ce = active_cache[i];
-		skip = !ce_path_match(ce, pathspec);
-		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 (skip || (!one && !two))
-			continue;
-		if (!one)
-			diff_addremove(&diff_options, '+', ntohl(two->ce_mode),
-				       two->sha1, name, NULL);
-		else if (!two)
-			diff_addremove(&diff_options, '-', ntohl(one->ce_mode),
-				       one->sha1, name, NULL);
-		else if (memcmp(one->sha1, two->sha1, 20) ||
-			 (one->ce_mode != two->ce_mode) ||
-			 diff_options.find_copies_harder)
-			diff_change(&diff_options,
-				    ntohl(one->ce_mode), ntohl(two->ce_mode),
-				    one->sha1, two->sha1, name, NULL);
-	}
-}
-
-int main(int ac, const char **av)
-{
-	int stage1, stage2;
-	const char *prefix = setup_git_directory();
-	const char **pathspec = NULL;
-
-	git_config(git_diff_config);
-	read_cache();
-	diff_setup(&diff_options);
-	while (1 < ac && av[1][0] == '-') {
-		const char *arg = av[1];
-		if (!strcmp(arg, "-r"))
-			; /* as usual */
-		else {
-			int diff_opt_cnt;
-			diff_opt_cnt = diff_opt_parse(&diff_options,
-						      av+1, ac-1);
-			if (diff_opt_cnt < 0)
-				usage(diff_stages_usage);
-			else if (diff_opt_cnt) {
-				av += diff_opt_cnt;
-				ac -= diff_opt_cnt;
-				continue;
-			}
-			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. */
-	pathspec = get_pathspec(prefix, av);
-
-	if (diff_setup_done(&diff_options) < 0)
-		usage(diff_stages_usage);
-
-	diff_stages(stage1, stage2, pathspec);
-	diffcore_std(&diff_options);
-	diff_flush(&diff_options);
-	return 0;
-}
diff --git a/diff-tree.c b/diff-tree.c
deleted file mode 100644
index 69bb74b..0000000
--- a/diff-tree.c
+++ /dev/null
@@ -1,147 +0,0 @@
-#include "cache.h"
-#include "diff.h"
-#include "commit.h"
-#include "log-tree.h"
-
-static struct rev_info log_tree_opt;
-
-static int diff_tree_commit_sha1(const unsigned char *sha1)
-{
-	struct commit *commit = lookup_commit_reference(sha1);
-	if (!commit)
-		return -1;
-	return log_tree_commit(&log_tree_opt, commit);
-}
-
-static int diff_tree_stdin(char *line)
-{
-	int len = strlen(line);
-	unsigned char sha1[20];
-	struct commit *commit;
-
-	if (!len || line[len-1] != '\n')
-		return -1;
-	line[len-1] = 0;
-	if (get_sha1_hex(line, sha1))
-		return -1;
-	commit = lookup_commit(sha1);
-	if (!commit || parse_commit(commit))
-		return -1;
-	if (isspace(line[40]) && !get_sha1_hex(line+41, sha1)) {
-		/* Graft the fake parents locally to the commit */
-		int pos = 41;
-		struct commit_list **pptr, *parents;
-
-		/* Free the real parent list */
-		for (parents = commit->parents; parents; ) {
-			struct commit_list *tmp = parents->next;
-			free(parents);
-			parents = tmp;
-		}
-		commit->parents = NULL;
-		pptr = &(commit->parents);
-		while (line[pos] && !get_sha1_hex(line + pos, sha1)) {
-			struct commit *parent = lookup_commit(sha1);
-			if (parent) {
-				pptr = &commit_list_insert(parent, pptr)->next;
-			}
-			pos += 41;
-		}
-	}
-	return log_tree_commit(&log_tree_opt, commit);
-}
-
-static const char diff_tree_usage[] =
-"git-diff-tree [--stdin] [-m] [-c] [--cc] [-s] [-v] [--pretty] [-t] [-r] [--root] "
-"[<common diff options>] <tree-ish> [<tree-ish>] [<path>...]\n"
-"  -r            diff recursively\n"
-"  --root        include the initial commit as diff against /dev/null\n"
-COMMON_DIFF_OPTIONS_HELP;
-
-int main(int argc, const char **argv)
-{
-	int nr_sha1;
-	char line[1000];
-	struct object *tree1, *tree2;
-	static struct rev_info *opt = &log_tree_opt;
-	struct object_list *list;
-	int read_stdin = 0;
-
-	git_config(git_diff_config);
-	nr_sha1 = 0;
-	init_revisions(opt);
-	opt->abbrev = 0;
-	opt->diff = 1;
-	argc = setup_revisions(argc, argv, opt, NULL);
-
-	while (--argc > 0) {
-		const char *arg = *++argv;
-
-		if (!strcmp(arg, "--stdin")) {
-			read_stdin = 1;
-			continue;
-		}
-		usage(diff_tree_usage);
-	}
-
-	/*
-	 * NOTE! "setup_revisions()" will have inserted the revisions
-	 * it parsed in reverse order. So if you do
-	 *
-	 *	git-diff-tree a b
-	 *
-	 * the commit list will be "b" -> "a" -> NULL, so we reverse
-	 * the order of the objects if the first one is not marked
-	 * UNINTERESTING.
-	 */
-	nr_sha1 = 0;
-	list = opt->pending_objects;
-	if (list) {
-		nr_sha1++;
-		tree1 = list->item;
-		list = list->next;
-		if (list) {
-			nr_sha1++;
-			tree2 = tree1;
-			tree1 = list->item;
-			if (list->next)
-				usage(diff_tree_usage);
-			/* Switch them around if the second one was uninteresting.. */
-			if (tree2->flags & UNINTERESTING) {
-				struct object *tmp = tree2;
-				tree2 = tree1;
-				tree1 = tmp;
-			}
-		}
-	}
-
-	switch (nr_sha1) {
-	case 0:
-		if (!read_stdin)
-			usage(diff_tree_usage);
-		break;
-	case 1:
-		diff_tree_commit_sha1(tree1->sha1);
-		break;
-	case 2:
-		diff_tree_sha1(tree1->sha1,
-			       tree2->sha1,
-			       "", &opt->diffopt);
-		log_tree_diff_flush(opt);
-		break;
-	}
-
-	if (!read_stdin)
-		return 0;
-
-	if (opt->diffopt.detect_rename)
-		opt->diffopt.setup |= (DIFF_SETUP_USE_SIZE_CACHE |
-				       DIFF_SETUP_USE_CACHE);
-	while (fgets(line, sizeof(line), stdin))
-		if (line[0] == '\n')
-			fflush(stdout);
-		else
-			diff_tree_stdin(line);
-
-	return 0;
-}
diff --git a/git.c b/git.c
index d29505c..8749748 100644
--- a/git.c
+++ b/git.c
@@ -59,7 +59,11 @@ static void handle_internal_command(int 
 		{ "read-tree", cmd_read_tree },
 		{ "commit-tree", cmd_commit_tree },
 		{ "apply", cmd_apply },
-		{ "show-branch", cmd_show_branch }
+		{ "show-branch", cmd_show_branch },
+		{ "diff-files", cmd_diff_files },
+		{ "diff-index", cmd_diff_index },
+		{ "diff-stages", cmd_diff_stages },
+		{ "diff-tree", cmd_diff_tree }
 	};
 	int i;
 
-- 
1.3.3.g288c

^ permalink raw reply related

* Re: [PATCH] Set the executable bit on gitMergeCommon.py.
From: Junio C Hamano @ 2006-05-23  8:34 UTC (permalink / raw)
  To: Peter Eriksen; +Cc: git
In-Reply-To: <20060523081238.GB4038@bohr.gbar.dtu.dk>

"Peter Eriksen" <s022018@student.dtu.dk> writes:

> From: Peter Eriksen <s022018@student.dtu.dk>
> Date: Mon, 22 May 2006 15:35:42 +0200
>
> Signed-off-by: Peter Eriksen <s022018@student.dtu.dk>

It makes some sense to make git-merge-recursive.py executable,
just in case somebody wants to run it without preprocessing.

However, gitMergeCommon.py is a module without the main program.
It does not even have the usual "primarily meant to be a library
but you could run it standalone" idiom at the end:

	if __name__ == '__main__': test()

So I am not sure if this patch makes sense -- does it misbehave
on your system when installed without +x bit?

^ permalink raw reply

* Re: Make more commands builtin
From: Junio C Hamano @ 2006-05-23  8:41 UTC (permalink / raw)
  To: Peter Eriksen; +Cc: git
In-Reply-To: <35036.1277036884$1148372628@news.gmane.org>

"Peter Eriksen" <s022018@student.dtu.dk> writes:

>  Makefile                               |   26 +++++++++++++++-----------
>  apply.c => builtin-apply.c             |    3 ++-
>  commit-tree.c => builtin-commit-tree.c |    3 ++-
>  diff-files.c => builtin-diff-files.c   |    3 ++-
>  diff-index.c => builtin-diff-index.c   |    3 ++-
>  diff-stages.c => builtin-diff-stages.c |    3 ++-
>  diff-tree.c => builtin-diff-tree.c     |    3 ++-
>  ls-files.c => builtin-ls-files.c       |    3 ++-
>  ls-tree.c => builtin-ls-tree.c         |    3 ++-
>  read-tree.c => builtin-read-tree.c     |    3 ++-
>  show-branch.c => builtin-show-branch.c |    3 ++-
>  tar-tree.c => builtin-tar-tree.c       |    3 ++-
>  builtin.h                              |   12 ++++++++++++
>  git.c                                  |   13 ++++++++++++-
>
> I've tried to follow the trend of making commands builtin.
> All patches have the same form.  This is my first use
> of git-send-email, so this might come out wrong.
>
> Peter Eriksen <s022018@student.dtu.dk>

Was this intentional?

	Reply-To: Patches/@bohr.gbar.dtu.dk
	Reply-To: Patches/0001-Builtin-git-ls-files.txt@bohr.gbar.dtu.dk

Otherwise the form looks OK, except that with this particular
series, I would have much preferred to see these with
"format-patch -M"; it is really hard to review otherwise.

BTW, I already have tar-tree built-in in "next" branch.

I'll go to bed now, so please expect review Ack/Nack until
evening my time (it is 01:40 here) from me.

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox