Git development
 help / color / mirror / Atom feed
* [PATCH] git-cvsimport-script: fix branch switching
From: Sven Verdoolaege @ 2005-07-04 14:06 UTC (permalink / raw)
  To: Matthias Urlichs, git
In-Reply-To: <20050703103517.GJ5992MdfPADPa@garage.linux.student.kuleuven.ac.be>

git-cvsimport-script: fix branch switching

Previous patch broke branch switching.

---
commit 46e63efc072bc440e4c6aad33d3157b70f5172b6
tree 2c4fd7286e29e6041808d07874ef2151e3876676
parent 79ee456cf222982f7ee3f003440c57b5f7cffa8b
author Sven Verdoolaege <skimo@kotnet.org> Mon, 04 Jul 2005 15:28:36 +0200
committer Sven Verdoolaege <skimo@kotnet.org> Mon, 04 Jul 2005 15:28:36 +0200

 git-cvsimport-script |    3 ++-
 1 files changed, 2 insertions(+), 1 deletions(-)

diff --git a/git-cvsimport-script b/git-cvsimport-script
--- a/git-cvsimport-script
+++ b/git-cvsimport-script
@@ -651,9 +651,10 @@ while(<CVS>) {
 		}
 		if(($ancestor || $branch) ne $last_branch) {
 			print "Switching from $last_branch to $branch\n" if $opt_v;
-			system("git-read-tree","-m","$last_branch","$branch");
+			system("git-read-tree", $branch);
 			die "read-tree failed: $?\n" if $?;
 		}
+		$last_branch = $branch if $branch ne $last_branch;
 		$state = 9;
 	} elsif($state == 8) {
 		$logmsg .= "$_\n";

^ permalink raw reply

* [PATCH] git-cvsimport-script: update cvsps cache instead of rebuilding it
From: Sven Verdoolaege @ 2005-07-04 14:09 UTC (permalink / raw)
  To: Matthias Urlichs, git
In-Reply-To: <20050703103517.GJ5992MdfPADPa@garage.linux.student.kuleuven.ac.be>

git-cvsimport-script: update cvsps cache instead of rebuilding it

Updating the cache is sufficient for most purposes.
If users really want to rebuild the cache, they can specify
the option themselves.

---
commit 6e7e37b0bfc921aa1f0cb30560fc128e87a41966
tree 12a9303d4ba4566d9e081b2c375648685ce41e93
parent 46e63efc072bc440e4c6aad33d3157b70f5172b6
author Sven Verdoolaege <skimo@kotnet.org> Mon, 04 Jul 2005 15:35:30 +0200
committer Sven Verdoolaege <skimo@kotnet.org> Mon, 04 Jul 2005 15:35:30 +0200

 git-cvsimport-script |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/git-cvsimport-script b/git-cvsimport-script
--- a/git-cvsimport-script
+++ b/git-cvsimport-script
@@ -436,7 +436,7 @@ die "Cannot fork: $!\n" unless defined $
 unless($pid) {
 	my @opt;
 	@opt = split(/,/,$opt_p) if defined $opt_p;
-	exec("cvsps",@opt,"-x","-A","--cvs-direct",'--root',$opt_d,$cvs_tree);
+	exec("cvsps",@opt,"-u","-A","--cvs-direct",'--root',$opt_d,$cvs_tree);
 	die "Could not start cvsps: $!\n";
 }
 

^ permalink raw reply

* Re: [PATCH] cvsimport: rewritten in Perl
From: Matthias Urlichs @ 2005-07-04 14:36 UTC (permalink / raw)
  To: git
In-Reply-To: <20050704134611.GM18608MdfPADPa@garage.linux.student.kuleuven.ac.be>

[-- Attachment #1: Type: text/plain, Size: 966 bytes --]

Hi,

Sven Verdoolaege:
> > Because my code doesn't support compressed cvs connections:
> > a -z that doesn't work except for the rlog part would be a lie.
> 
> I was talking about the cvsps '-z' option (see current git-cvsimport-script).
> Are you saying you want to reserve that option to signify compressed
> cvs connections ?
> 
Sorry, I was confused -- with cvsps, -capital-Z says to compress.

Ideally, I'd prefer to recycle standard CVS options as much as possible, 
but given that the confusion is already there (worse: cvs' -z wants an
argument (compression level), cvsps' -Z doesn't) that may not actually
make sense. *Shrug*

I'm too happy when other people improve my tools to get hung up on
details like that. ;-)

-- 
Matthias Urlichs   |   {M:U} IT Design @ m-u-it.de   |  smurf@smurf.noris.de
Disclaimer: The quote was selected randomly. Really. | http://smurf.noris.de
 - -
You'll feel much better once you've given up hope.

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 189 bytes --]

^ permalink raw reply

* Re: [PATCH] cvsimport: rewritten in Perl
From: Sven Verdoolaege @ 2005-07-04 15:52 UTC (permalink / raw)
  To: Matthias Urlichs; +Cc: git
In-Reply-To: <20050704143637.GB32098@kiste.smurf.noris.de>

On Mon, Jul 04, 2005 at 04:36:37PM +0200, Matthias Urlichs wrote:
> Ideally, I'd prefer to recycle standard CVS options as much as possible, 
> but given that the confusion is already there (worse: cvs' -z wants an
> argument (compression level), cvsps' -Z doesn't) that may not actually
> make sense. *Shrug*
> 
> I'm too happy when other people improve my tools to get hung up on
> details like that. ;-)

Here it is, then.

skimo
--
git-cvsimport-script: provide direct support for cvsps -z option

---
commit 28537171e7ec23c8677ea6e77c208583f95caa28
tree ca80ed2fad05b150984c14a5364dac8d3e307120
parent 6e7e37b0bfc921aa1f0cb30560fc128e87a41966
author Sven Verdoolaege <skimo@kotnet.org> Mon, 04 Jul 2005 17:10:06 +0200
committer Sven Verdoolaege <skimo@kotnet.org> Mon, 04 Jul 2005 17:10:06 +0200

 git-cvsimport-script |    9 +++++----
 1 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/git-cvsimport-script b/git-cvsimport-script
--- a/git-cvsimport-script
+++ b/git-cvsimport-script
@@ -28,19 +28,19 @@ use POSIX qw(strftime dup2);
 $SIG{'PIPE'}="IGNORE";
 $ENV{'TZ'}="UTC";
 
-our($opt_h,$opt_o,$opt_v,$opt_d,$opt_p,$opt_C);
+our($opt_h,$opt_o,$opt_v,$opt_d,$opt_p,$opt_C,$opt_z);
 
 sub usage() {
 	print STDERR <<END;
 Usage: ${\basename $0}     # fetch/update GIT from CVS
-	   [ -o branch-for-HEAD ] [ -h ] [ -v ] [ -d CVSROOT ]
-       [ -p opts-for-cvsps ] [ -C GIT_repository ]
+       [ -o branch-for-HEAD ] [ -h ] [ -v ] [ -d CVSROOT ]
+       [ -p opts-for-cvsps ] [ -C GIT_repository ] [ -z fuzz ]
        [ CVS_module ]
 END
 	exit(1);
 }
 
-getopts("hqvo:d:p:C:") or usage();
+getopts("hqvo:d:p:C:z:") or usage();
 usage if $opt_h;
 
 @ARGV <= 1 or usage();
@@ -436,6 +436,7 @@ die "Cannot fork: $!\n" unless defined $
 unless($pid) {
 	my @opt;
 	@opt = split(/,/,$opt_p) if defined $opt_p;
+	unshift @opt, '-z', $opt_z if defined $opt_z;
 	exec("cvsps",@opt,"-u","-A","--cvs-direct",'--root',$opt_d,$cvs_tree);
 	die "Could not start cvsps: $!\n";
 }

^ permalink raw reply

* Re: Stacked GIT 0.3 (now more Quilt-like)
From: randy_dunlap @ 2005-07-04 17:09 UTC (permalink / raw)
  To: Peter Osterlund; +Cc: catalin.marinas, git
In-Reply-To: <m3y88m21ln.fsf@telia.com>

On 04 Jul 2005 14:32:36 +0200 Peter Osterlund wrote:

| Catalin Marinas <catalin.marinas@gmail.com> writes:
| 
| > On Sun, 2005-07-03 at 14:38 +0200, Peter Osterlund wrote:
| > > Catalin Marinas <catalin.marinas@gmail.com> writes:
| > > > I know that using -A gives a more detailed output in case of a conflict.
| > > > The problem is that you will get a conflict even if the changes are
| > > > identical, making it impossible to detect when a patch was merged
| > > > upstream.
| > > 
| > > OK, I see. How about using wiggle instead?
| > > 
| > >         http://cgi.cse.unsw.edu.au/~neilb/source/wiggle/
| > > 
| > > That's what patch-utils uses if you run "pushpatch -m". wiggle is also
| > > a lot smarter than diff3, so there will be fewer cases that result in
| > > a conflict. Maybe a parameter to "stg push" could enable wiggle mode.
| > 
| > I haven't used wiggle before but I will give it a try (though I prefer
| > such a tool not to be too smart since it might make mistakes). Anyway, I
| > will make this configurable, i.e. you could put something like below in
| > the .stgitrc file:
| > 
| > merger = 'diff3 -m -E %(branch1)s %(ancestor)s %(branch2)s'
| > 
| > or
| > 
| > merger = 'wiggle -m %(branch1)s %(ancestor)s %(branch2)s'
| > 
| > > Is there a way in StGIT to undo a push that results in a large mess of
| > > conflicts?
| > 
| > Good point. No, there isn't yet. I will think about an undo command. At
| > the moment, the old top and bottom ids of a patch are saved so that the
| > patch before the merge can be retrieved but there isn't any command to
| > make use of them.
| 
| I agree with the other comments, it's probably not wise to rely on
| wiggle, and wiggle sometimes makes a mess. However, it often does the
| right thing, and with a configurable merge program and an undo
| function, this should not be a problem. Just undo and try again if you
| don't like the result.

You could try Chris Mason's 'rej' as well.
ftp://ftp.oregonstate.edu/pub/suse/people/mason/rej/rej-0.15.tar.gz
(from one mirror site)

---
~Randy

^ permalink raw reply

* [PATCH 0/2] Support for transferring pack files in git-ssh-*
From: Daniel Barkalow @ 2005-07-04 18:48 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: git

This series allows git-ssh-* to transfer objects packed into pack files in
the case of updating a ref file. It is a proof-of-concept for transferring
pack files in any situation where it's useful.

The general method is that the fetch() method has the option of
additionally getting other objects in addition to the one
specified; objects which aren't needed are specified with
dont_fetch() (when it makes sense to exclude them). In this version, it
only excludes an object when it is the current value of a ref file that is
being updated, but further exclusions are clearly possible.

In the case of git-ssh-*, the target specifies objects to exclude, and the
source responds (asynchronously) with whether or not it knows how to
exclude them (i.e., whether or not it has them). If the target has gotten
an object excluded, it requests a pack file instead of a single object,
and the source provides all objects referenced from the given hash,
excluding those specified for exclusion.

	-Daniel
*This .sig left intentionally blank*

^ permalink raw reply

* [PATCH 1/2] Specify object not useful to pull
From: Daniel Barkalow @ 2005-07-04 18:50 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: git
In-Reply-To: <Pine.LNX.4.21.0507041416240.30848-100000@iabervon.org>

Add support for the pull common code to specify to a pull implementation
hashes which wouldn't be useful to fetch implicitly. This can be used to
infer (possibly) what hashes would be useful to fetch implicitly, such that
a later call to fetch can also fetch extra stuff.

Signed-off-by: Daniel Barkalow <barkalow@iabervon.org>

---
commit 9bc0256d2e834e101d7bd3f4867a330c92104929
tree a2b4cfdaac954c46babffd585a61f3faf68969e1
parent d0efc8a71da1855c705fd2074b219bcb158b6dbd
author Daniel Barkalow <barkalow@iabervon.org> 1120411439 -0400
committer Daniel Barkalow <barkalow@silva-tulga.(none)> 1120411439 -0400

Index: http-pull.c
===================================================================
--- f26b700095ec30154fede14638a099f49744981d/http-pull.c  (mode:100644 sha1:1f9d60b9b1d5eed85b24d96c240666bbfc5a22ed)
+++ a2b4cfdaac954c46babffd585a61f3faf68969e1/http-pull.c  (mode:100644 sha1:f252a4b9d5448afa7af8a62176808a631429b9cd)
@@ -139,6 +139,10 @@
         return 0;
 }
 
+void dont_fetch(const unsigned char *sha1)
+{
+}
+
 int main(int argc, char **argv)
 {
 	char *commit_id;
Index: local-pull.c
===================================================================
--- f26b700095ec30154fede14638a099f49744981d/local-pull.c  (mode:100644 sha1:2f06fbee8b840a7ae642f5a22e2cb993687f3470)
+++ a2b4cfdaac954c46babffd585a61f3faf68969e1/local-pull.c  (mode:100644 sha1:270e3a0b8405793cd70e6efa70ec6aa4b1674141)
@@ -73,6 +73,10 @@
 	return -1;
 }
 
+void dont_fetch(const unsigned char *sha1)
+{
+}
+
 int fetch_ref(char *ref, unsigned char *sha1)
 {
 	static int ref_name_start = -1;
Index: pull.c
===================================================================
--- f26b700095ec30154fede14638a099f49744981d/pull.c  (mode:100644 sha1:ed3078e3b27c62c07558fd94f339801cbd685593)
+++ a2b4cfdaac954c46babffd585a61f3faf68969e1/pull.c  (mode:100644 sha1:f7f5a89aef36ffc2436dbd30170e4c8dbb2ba3a3)
@@ -155,6 +155,10 @@
 	unsigned char sha1[20];
 	int fd = -1;
 
+	if (current_ref) {
+		dont_fetch(current_ref);
+	}
+
 	if (write_ref && current_ref) {
 		fd = lock_ref_sha1(write_ref, current_ref);
 		if (fd < 0)
Index: pull.h
===================================================================
--- f26b700095ec30154fede14638a099f49744981d/pull.h  (mode:100644 sha1:e173ae3337c4465da87d849f4e5c9da203fdf01d)
+++ a2b4cfdaac954c46babffd585a61f3faf68969e1/pull.h  (mode:100644 sha1:6a35d39fd69bb884faa2d5e70c79e5c40b3ba436)
@@ -15,6 +15,12 @@
  */
 extern int fetch_ref(char *ref, unsigned char *sha1);
 
+/*
+ * Specify that the given SHA1, and everything it references, need not
+ * be fetched.  To be provided by the particular implementation. 
+ */
+extern void dont_fetch(const unsigned char *sha1);
+
 /* If set, the ref filename to write the target value to. */
 extern const char *write_ref;
 
Index: ssh-pull.c
===================================================================
--- f26b700095ec30154fede14638a099f49744981d/ssh-pull.c  (mode:100644 sha1:87d523899a83d8c0d3c5ff721208ded30c1a38f4)
+++ a2b4cfdaac954c46babffd585a61f3faf68969e1/ssh-pull.c  (mode:100644 sha1:362318071333420a7cf2450ada7269a94ec2cc7c)
@@ -53,6 +53,10 @@
 	return 0;
 }
 
+void dont_fetch(const unsigned char *sha1)
+{
+}
+
 int main(int argc, char **argv)
 {
 	char *commit_id;

^ permalink raw reply

* [PATCH 2/2] Pull well-bounded pack files over ssh
From: Daniel Barkalow @ 2005-07-04 18:51 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: git
In-Reply-To: <Pine.LNX.4.21.0507041416240.30848-100000@iabervon.org>

Initial version of support for transferring pack files over ssh. This works
by sending the source hashes that the target already has, and checking (in
bulk) whether the source has them as well. If the source has some hash that
the target has, the target will request a pack for the fetch, instead of
requesting individual objects, and the source will provide a pack that has
the requested object and all references, excluding those mentioned previously
and any they refer to.

Signed-off-by: Daniel Barkalow <barkalow@iabervon.org>

---
commit 2045e6098dfa0f8760b6a4a65227a6ea51de990d
tree 3d3e671c0ac57ea66434768c2b3432352b7c20ae
parent a3d68b56cd5efaa06a7493aca61febb4f376fb49
author Daniel Barkalow <barkalow@iabervon.org> 1120501001 -0400
committer Daniel Barkalow <barkalow@silva-tulga.(none)> 1120501001 -0400

Index: ssh-pull.c
===================================================================
--- 8c3f58421885a9c1472f141bb32b668c2e65a5fc/ssh-pull.c  (mode:100644 sha1:0f2c3b5b9db324e20eb6ed0c52cc884b86349a08)
+++ 3d3e671c0ac57ea66434768c2b3432352b7c20ae/ssh-pull.c  (mode:100644 sha1:f792135e7afba6a8d68946a7f14aa339c4fc02e7)
@@ -4,11 +4,86 @@
 #include "pull.h"
 #include "refs.h"
 
+#include "pkt-line.h"
+#include <sys/wait.h>
+
+static const char *unpacker = "git-unpack-objects";
+
 static int fd_in;
 static int fd_out;
 
 static unsigned char remote_version = 0;
-static unsigned char local_version = 1;
+static unsigned char local_version = 2;
+
+static int sent_excludes = 0;
+static int accepted_excludes = 0;
+
+static int write_pack_from_fd(int fd)
+{
+	pid_t pid = fork();
+
+	if (pid < 0)
+		die("unpack fork failed");
+	if (!pid) {
+		execlp(unpacker, unpacker, NULL);
+		die("unpack execute failed");
+	}
+
+	for (;;) {
+		int status, code;
+		int retval = waitpid(pid, &status, 0);
+
+		if (retval < 0) {
+			if (errno == EINTR)
+				continue;
+			die("waitpid failed (%s)", strerror(retval));
+		}
+		if (retval != pid)
+			die("waitpid is confused");
+		if (WIFSIGNALED(status))
+			die("%s died of signal %d", unpacker, WTERMSIG(status));
+		if (!WIFEXITED(status))
+			die("%s died out of really strange complications", unpacker);
+		code = WEXITSTATUS(status);
+		if (code)
+			die("%s exited with error code %d", unpacker, code);
+		return 0;
+	}
+}
+
+static int handle_deferred_reads()
+{
+	signed char remote;
+	while (sent_excludes) {
+		if (read(fd_in, &remote, 1) < 1)
+			return -1;
+		if (!remote) {
+			if (get_verbosely) {
+				fprintf(stderr, "Exclude accepted\n");
+			}
+			accepted_excludes++;
+		}
+		sent_excludes--;
+	}
+	return 0;
+}
+
+static int fetch_pack(unsigned char *sha1)
+{
+	int ret;
+	char type = 'p';
+	signed char remote;
+	write(fd_out, &type, 1);
+	write(fd_out, sha1, 20);
+	if (read(fd_in, &remote, 1) < 1)
+		return -1;
+	if (remote < 0)
+		return remote;
+	if (get_verbosely)
+		fprintf(stderr, "Getting pack\n");
+	ret = write_pack_from_fd(fd_in);
+	return ret;
+}
 
 int fetch(unsigned char *sha1)
 {
@@ -17,6 +92,10 @@
 	char type = 'o';
 	if (has_sha1_file(sha1))
 		return 0;
+	if (handle_deferred_reads())
+		return -1;
+	if (accepted_excludes)
+		return fetch_pack(sha1);
 	write(fd_out, &type, 1);
 	write(fd_out, sha1, 20);
 	if (read(fd_in, &remote, 1) < 1)
@@ -44,6 +123,8 @@
 {
 	signed char remote;
 	char type = 'r';
+	if (handle_deferred_reads())
+		return -1;
 	write(fd_out, &type, 1);
 	write(fd_out, ref, strlen(ref) + 1);
 	read(fd_in, &remote, 1);
@@ -55,6 +136,15 @@
 
 void dont_fetch(const unsigned char *sha1)
 {
+	char type = 'd';
+	if (remote_version < 2)
+		return;
+	write(fd_out, &type, 1);
+	write(fd_out, sha1, 20);
+	sent_excludes++;
+	if (get_verbosely) {
+		fprintf(stderr, "Won't get %s\n", sha1_to_hex(sha1));
+	}
 }
 
 int main(int argc, char **argv)
@@ -76,7 +166,12 @@
 		} else if (argv[arg][1] == 'v') {
 			get_verbosely = 1;
 		} else if (argv[arg][1] == 'w') {
+			unsigned char *current = malloc(20);
 			write_ref = argv[arg + 1];
+			if (get_ref_sha1(write_ref, current))
+				free(current);
+			else
+				current_ref = current;
 			arg++;
 		}
 		arg++;
Index: ssh-push.c
===================================================================
--- 8c3f58421885a9c1472f141bb32b668c2e65a5fc/ssh-push.c  (mode:100644 sha1:090d6f9f8fbde2d736ac5bf563415b0fa402b5aa)
+++ 3d3e671c0ac57ea66434768c2b3432352b7c20ae/ssh-push.c  (mode:100644 sha1:10390948efacfa06f4f6fc6b2f3631cec6fcb876)
@@ -4,16 +4,88 @@
 
 #include <string.h>
 
-static unsigned char local_version = 1;
+static unsigned char local_version = 2;
 static unsigned char remote_version = 0;
 
-static int serve_object(int fd_in, int fd_out) {
+static void exec_pack_objects(void)
+{
+	static char *args[] = {
+		"git-pack-objects",
+		"--stdout",
+		NULL
+	};
+	execvp("git-pack-objects", args);
+	die("git-pack-objects exec failed (%s)", strerror(errno));
+}
+
+static void exec_rev_list(unsigned char *incl, unsigned char *excl)
+{
+	static char *args[1000];
+	char *buf = malloc(100);
+	int i = 0;
+
+	args[i++] = "git-rev-list";	/* 0 */
+	args[i++] = "--objects";	/* 1 */
+
+	args[i++] = buf;
+	snprintf(buf, 50, "^%s", sha1_to_hex(excl));
+	buf += 50;
+	args[i++] = buf;
+	snprintf(buf, 50, "%s", sha1_to_hex(incl));
+
+	args[i] = NULL;
+	execvp("git-rev-list", args);
+	die("git-rev-list exec failed (%s)", strerror(errno));
+}
+
+static void rev_list(int fd, unsigned char *incl, unsigned char *excl)
+{
+	int pipe_fd[2];
+	pid_t pack_objects_pid;
+
+	if (pipe(pipe_fd) < 0)
+		die("rev-list setup: pipe failed");
+	pack_objects_pid = fork();
+	if (!pack_objects_pid) {
+		dup2(pipe_fd[0], 0);
+		dup2(fd, 1);
+		close(pipe_fd[0]);
+		close(pipe_fd[1]);
+		close(fd);
+		exec_pack_objects();
+		die("pack-objects setup failed");
+	}
+	if (pack_objects_pid < 0)
+		die("pack-objects fork failed");
+	dup2(pipe_fd[1], 1);
+	close(pipe_fd[0]);
+	close(pipe_fd[1]);
+	close(fd);
+	exec_rev_list(incl, excl);
+}
+
+static int pack_objects(int fd, unsigned char *incl, unsigned char *excl)
+{
+	pid_t rev_list_pid;
+
+	rev_list_pid = fork();
+	if (!rev_list_pid) {
+		rev_list(fd, incl, excl);
+		die("rev-list setup failed");
+	}
+	if (rev_list_pid < 0)
+		die("rev-list fork failed");
+	/*
+	 * We don't wait for the rev-list pipeline in the parent:
+	 * we end up waiting for the other end instead
+	 */
+	return 0;
+}
+
+static int read_sha1(int fd_in, unsigned char *sha1)
+{
 	ssize_t size;
 	int posn = 0;
-	unsigned char sha1[20];
-	unsigned long objsize;
-	void *buf;
-	signed char remote;
 	do {
 		size = read(fd_in, sha1 + posn, 20 - posn);
 		if (size < 0) {
@@ -24,6 +96,18 @@
 			return -1;
 		posn += size;
 	} while (posn < 20);
+	return 0;
+}
+
+static int serve_object(int fd_in, int fd_out) {
+	unsigned char sha1[20];
+	unsigned long objsize;
+	void *buf;
+	signed char remote;
+	int posn;
+	ssize_t size;
+	if (read_sha1(fd_in, sha1))
+		return -1;
 	
 	/* fprintf(stderr, "Serving %s\n", sha1_to_hex(sha1)); */
 	remote = 0;
@@ -85,6 +169,48 @@
         return 0;
 }
 
+static unsigned char *excluded = NULL;
+
+static int serve_exclude(int fd_in, int fd_out)
+{
+	unsigned char sha1[20];
+	signed char remote;
+	if (read_sha1(fd_in, sha1))
+		return -1;
+
+	if (has_sha1_file(sha1)) {
+		remote = 0;
+		if (!excluded) {
+			excluded = malloc(20);
+			memcpy(excluded, sha1, 20);
+		}
+	} else {
+		remote = -1;
+	}
+
+	write(fd_out, &remote, 1);
+	
+	return 0;
+}
+
+static int serve_pack(int fd_in, int fd_out)
+{
+	unsigned char sha1[20];
+	signed char remote = 0;
+	if (read_sha1(fd_in, sha1))
+		return -1;
+
+	if (!has_sha1_file(sha1))
+		remote = -1;
+
+	write(fd_out, &remote, 1);
+
+	if (remote)
+		return 0;
+	if (pack_objects(fd_out, sha1, excluded))
+		return -1;
+	return 0;
+}
 
 static void service(int fd_in, int fd_out) {
 	char type;
@@ -102,6 +228,10 @@
 			return;
 		if (type == 'r' && serve_ref(fd_in, fd_out))
 			return;
+		if (type == 'd' && serve_exclude(fd_in, fd_out))
+			return;
+		if (type == 'p' && serve_pack(fd_in, fd_out))
+			return;
 	} while (1);
 }
 

^ permalink raw reply

* expensive local git clone
From: David S. Miller @ 2005-07-04 19:57 UTC (permalink / raw)
  To: git


I'm still seeing git-local-pull going crazy when attemping local
clones of kernel trees, this is from top:

22032 davem     18   0 63392  59m  920 R 76.0  5.9   5:33.15 git-local-pull     

and it's still chugging along.  This is from a simple:

bash$ git-clone-script /home/davem/src/GIT/linux-2.6/.git /home/davem/src/GIT/sparc-2.6

invocation.

I can't be the only person in the world seeing this :-)
It usually grows to around 88MB and runs for 8 minutes before
it finishes.

It must be walking the whole commit tree to the root or something
silly like that.

^ permalink raw reply

* [PATCH] Better error message from git-ssh-push
From: Daniel Barkalow @ 2005-07-04 20:12 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: git

If git-ssh-push can't interpret the commit-id, there are various possible
issues. Just giving the usage message makes it hard to identify what could
be wrong.

Signed-off-by: Daniel Barkalow <barkalow@iabervon.org>

---
commit 7a274ce1f93e6092dcf226d546a58d2d6df9d13c
tree 1f045fa8aa017cabbac613cf8c1ea2bd63ccc46c
parent 8934c88118c900fe38abbf60f893ee9ef4e83b3c
author Daniel Barkalow <barkalow@iabervon.org> 1120507167 -0400
committer Daniel Barkalow <barkalow@silva-tulga.(none)> 1120507167 -0400

Index: ssh-push.c
===================================================================
--- 62a74516551505e5fd2b5c2fd14486f3ac8a400e/ssh-push.c  (mode:100644 sha1:10390948efacfa06f4f6fc6b2f3631cec6fcb876)
+++ 1f045fa8aa017cabbac613cf8c1ea2bd63ccc46c/ssh-push.c  (mode:100644 sha1:6b1406b527ba6ede8602a04ab031003edb7da2b0)
@@ -257,8 +257,12 @@
 		usage(ssh_push_usage);
 	commit_id = argv[arg];
 	url = argv[arg + 1];
-	if (get_sha1(commit_id, sha1))
-		usage(ssh_push_usage);
+	if (get_sha1(commit_id, sha1)) {
+		fprintf(stderr, 
+			"Unable to interpret %s as something to push.\n",
+			commit_id);
+		return 1;
+	}
 	memcpy(hex, sha1_to_hex(sha1), sizeof(hex));
 	argv[arg] = hex;
 

^ permalink raw reply

* [PATCH] Unlock refs files on error exit
From: Daniel Barkalow @ 2005-07-04 20:08 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: git

If a program locks a refs file (as git-ssh-pull -w now does for an
existing file), it should unlock it again if it exits without changing
it. Some trickiness is required to make this not happen in forks of the
process.

Signed-off-by: Daniel Barkalow <barkalow@iabervon.org>

---
commit 8934c88118c900fe38abbf60f893ee9ef4e83b3c
tree 62a74516551505e5fd2b5c2fd14486f3ac8a400e
parent 2045e6098dfa0f8760b6a4a65227a6ea51de990d
author Daniel Barkalow <barkalow@iabervon.org> 1120507109 -0400
committer Daniel Barkalow <barkalow@silva-tulga.(none)> 1120507109 -0400

Index: refs.c
===================================================================
--- 3d3e671c0ac57ea66434768c2b3432352b7c20ae/refs.c  (mode:100644 sha1:447080edde385a470a815944c2f002c1e51699a5)
+++ 62a74516551505e5fd2b5c2fd14486f3ac8a400e/refs.c  (mode:100644 sha1:6e75e82665f5c8e3e155579228c5104c16318239)
@@ -2,6 +2,14 @@
 #include "cache.h"
 
 #include <errno.h>
+#include <signal.h>
+
+#define MAX_LOCKS 10
+
+static int setup = 0;
+
+static int num_locks = 0;
+static char *locknames[MAX_LOCKS];
 
 static int read_ref(const char *path, unsigned char *sha1)
 {
@@ -88,6 +96,22 @@
 	return ret;
 }
 
+static void remove_lock_files(void)
+{
+	int i;
+	if (getpid() != setup)
+		return;
+	for (i = 0; i < num_locks; i++) {
+		unlink(locknames[i]);
+	}
+	num_locks = 0;
+}
+
+static void remove_lock_files_on_signal(int signo) 
+{
+	remove_lock_files();
+}
+
 static int read_ref_file(const char *filename, unsigned char *sha1) {
 	int fd = open(filename, O_RDONLY);
 	char hex[41];
@@ -120,9 +144,12 @@
 static int lock_ref_file(const char *filename, const char *lock_filename,
 			 const unsigned char *old_sha1)
 {
-	int fd = open(lock_filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
+	int fd;
 	unsigned char current_sha1[20];
 	int retval;
+	if (num_locks >= MAX_LOCKS)
+		return error("Too many locks in refs.c");
+	fd = open(lock_filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
 	if (fd < 0) {
 		return error("Couldn't open lock file for %s: %s",
 			     filename, strerror(errno));
@@ -151,6 +178,12 @@
 				     sha1_to_hex(current_sha1), filename);
 		}
 	}
+	if (!setup) {
+		signal(SIGINT, remove_lock_files_on_signal);
+		atexit(remove_lock_files);
+		setup = getpid();
+	}
+	locknames[num_locks++] = strdup(lock_filename);
 	return fd;
 }
 
@@ -175,6 +208,7 @@
 {
 	char *hex = sha1_to_hex(sha1);
 	char term = '\n';
+	int i;
 	if (write(fd, hex, 40) < 40 ||
 	    write(fd, &term, 1) < 1) {
 		error("Couldn't write %s\n", filename);
@@ -182,7 +216,14 @@
 		return -1;
 	}
 	close(fd);
-	rename(lock_filename, filename);
+	if (rename(lock_filename, filename))
+		return error("Couldn't update %s", filename);
+	for (i = 0; i < num_locks; i++) {
+		if (!strcmp(lock_filename, locknames[i])) {
+			locknames[i] = locknames[--num_locks];
+			break;
+		}
+	}
 	return 0;
 }
 

^ permalink raw reply

* Re: expensive local git clone
From: Linus Torvalds @ 2005-07-04 20:39 UTC (permalink / raw)
  To: David S. Miller; +Cc: git
In-Reply-To: <20050704.125744.59481768.davem@davemloft.net>



On Mon, 4 Jul 2005, David S. Miller wrote:
> 
> I can't be the only person in the world seeing this :-)
> It usually grows to around 88MB and runs for 8 minutes before
> it finishes.

I'm making something based on pack-files.

git-local-pull is indeed walking the whole tree and copying one file at a
time. We shouldn't use "git-fetch-script" in "git clone" (it's really
designed for doing one file at a time and for small updates),

That said, the pack-file thing I'm working on won't be perfect either, and
the main advantage is that it should work over ssh. The fastest way to do
a clone is really to do a recursive hardlinked tree, so you may well want
to just do

	#!/bin/sh
	mkdir "$2" && cp -rl "$1/.git" "$2/.git"

and it should be about a million times faster and equally effective.

Untested, of course.

		Linus

^ permalink raw reply

* Re: expensive local git clone
From: Petr Baudis @ 2005-07-04 20:42 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: David S. Miller, git
In-Reply-To: <Pine.LNX.4.58.0507041327580.3570@g5.osdl.org>

Dear diary, on Mon, Jul 04, 2005 at 10:39:09PM CEST, I got a letter
where Linus Torvalds <torvalds@osdl.org> told me that...
> That said, the pack-file thing I'm working on won't be perfect either, and
> the main advantage is that it should work over ssh. The fastest way to do
> a clone is really to do a recursive hardlinked tree, so you may well want
> to just do
> 
> 	#!/bin/sh
> 	mkdir "$2" && cp -rl "$1/.git" "$2/.git"
> 
> and it should be about a million times faster and equally effective.
> 
> Untested, of course.

I wouldn't do that on anything but .git/objects. ;-)

-- 
				Petr "Pasky" Baudis
Stuff: http://pasky.or.cz/
<Espy> be careful, some twit might quote you out of context..

^ permalink raw reply

* Re: Stacked GIT 0.3 (now more Quilt-like)
From: Catalin Marinas @ 2005-07-04 20:42 UTC (permalink / raw)
  To: Peter Osterlund; +Cc: GIT
In-Reply-To: <m3y88m21ln.fsf@telia.com>

On Mon, 2005-07-04 at 14:32 +0200, Peter Osterlund wrote:
> I agree with the other comments, it's probably not wise to rely on
> wiggle, and wiggle sometimes makes a mess. However, it often does the
> right thing, and with a configurable merge program and an undo
> function, this should not be a problem. Just undo and try again if you
> don't like the result.

I uploaded a new snapshot (stgit-20050704.tar.gz) which has support for
merge tool configuration. It installs a default /etc/stgitrc file but
you can define your own options in ~/.stgitrc or .git/stgitrc (the
latter overrides the options in the earlier files). You can even define
something like - 'diff3 ... || xxdiff ...' so that if one tool fails it
will try the next one (the whole command is executed with 'sh -c').

I will try to write an 'undo' command in the next days.

--
Catalin

^ permalink raw reply

* Re: expensive local git clone
From: David S. Miller @ 2005-07-04 21:00 UTC (permalink / raw)
  To: pasky; +Cc: torvalds, git
In-Reply-To: <20050704204235.GE21128@pasky.ji.cz>

From: Petr Baudis <pasky@suse.cz>
Date: Mon, 4 Jul 2005 22:42:35 +0200

> Dear diary, on Mon, Jul 04, 2005 at 10:39:09PM CEST, I got a letter
> where Linus Torvalds <torvalds@osdl.org> told me that...
> > That said, the pack-file thing I'm working on won't be perfect either, and
> > the main advantage is that it should work over ssh. The fastest way to do
> > a clone is really to do a recursive hardlinked tree, so you may well want
> > to just do
> > 
> > 	#!/bin/sh
> > 	mkdir "$2" && cp -rl "$1/.git" "$2/.git"
> > 
> > and it should be about a million times faster and equally effective.
> > 
> > Untested, of course.
> 
> I wouldn't do that on anything but .git/objects. ;-)

Agreed.  When I do it by hand (but I shouldn't have to
do this by hand, git-clone-script should do it this way
if that's the best and most efficient) I usually link the
object directory then copy the rest of the stuff non-linked.

I keep hoping git-clone-script is going to be a good way
to clone two local trees.  Is my hope misguided?  :-)

^ permalink raw reply

* Re: expensive local git clone
From: Junio C Hamano @ 2005-07-04 21:40 UTC (permalink / raw)
  To: David S. Miller; +Cc: Linus Torvalds, git
In-Reply-To: <20050704.140043.112609056.davem@davemloft.net>

>>>>> "DSM" == David S Miller <davem@davemloft.net> writes:

DSM> I keep hoping git-clone-script is going to be a good way
DSM> to clone two local trees.  Is my hope misguided?  :-)

Something along these lines?
------------
Short-cut "git fetch" when cloning locally.

Instead of calling local-pull to "do the right thing", just
hardlink or copy the object files over when we know we are doing
the clone locally.

Signed-off-by: Junio C Hamano <junkio@cox.net>
---

cd /opt/packrat/playpen/public/in-place/git/git.junio/
jit-diff
# - master: git-format-patch: Prepare patches for e-mail submission.
# + (working tree)
diff --git a/git-clone-script b/git-clone-script
--- a/git-clone-script
+++ b/git-clone-script
@@ -1,7 +1,39 @@
 #!/bin/sh
 repo="$1"
 dir="$2"
-mkdir $dir || exit 1
-cd $dir
-git-init-db
-git fetch "$repo" && ( git-rev-parse FETCH_HEAD > .git/HEAD )
+mkdir "$dir" &&
+D=$(
+    (cd "$dir" && git-init-db && pwd)
+) || exit 1
+
+# See if repo is a local directory.
+if (
+	cd "$repo/objects" 2>/dev/null
+)
+then
+	# See if we can hardlink and drop "l" if not.
+	sample_file=$(cd "$repo" && find objects -type f -print | sed -e 1q)
+
+	# objects directory should not be empty since we are cloning!
+	test -f "$repo/$sample_file" || exit 
+
+	if ln "$repo/$sample_file" "$D/.git/objects/sample" 2>/dev/null
+	then
+	    l=l
+	else
+	    l=
+	fi &&
+	rm -f "$D/.git/objects/sample" &&
+
+	cd "$repo" &&
+	find objects -type f -print |
+	cpio -puam$l "$D/.git" || exit 1
+
+	# FETCH_HEAD is always HEAD because we do not do the
+	# extra parameter to "git fetch".
+	pwd
+	cat "HEAD" >"$D/.git/FETCH_HEAD"
+	cd "$D"
+else
+	cd "$D" && git fetch "$repo"
+fi && ( git-rev-parse FETCH_HEAD > .git/HEAD )

Compilation finished at Mon Jul  4 14:37:29

^ permalink raw reply

* Re: Last mile for 1.0 again
From: Daniel Barkalow @ 2005-07-04 21:40 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Linus Torvalds
In-Reply-To: <7v4qbhfxad.fsf_-_@assigned-by-dhcp.cox.net>

On Wed, 29 Jun 2005, Junio C Hamano wrote:

>  - Blame/Annotate.  Does anybody have a fast and correct one

How about an option to git-rev-list to take a path, and (1) exclude any
branch where the version at that path ends up ignored in a merge and
(2) not list any revision where the version at that path is identical to a
parent?

This should give you the list of all commits which are directly
responsible for the present state of the file, which can then be formatted
as desired.

	-Daniel
*This .sig left intentionally blank*

^ permalink raw reply

* Re: expensive local git clone
From: Linus Torvalds @ 2005-07-04 21:44 UTC (permalink / raw)
  To: David S. Miller; +Cc: pasky, git
In-Reply-To: <20050704.140043.112609056.davem@davemloft.net>



On Mon, 4 Jul 2005, David S. Miller wrote:
> 
> I keep hoping git-clone-script is going to be a good way
> to clone two local trees.  Is my hope misguided?  :-)

Well, I'm not working on it, but tested patches...

		Linus

^ permalink raw reply

* Re: Last mile for 1.0 again
From: Junio C Hamano @ 2005-07-04 21:45 UTC (permalink / raw)
  To: Daniel Barkalow; +Cc: git, Linus Torvalds
In-Reply-To: <Pine.LNX.4.21.0507041635350.30848-100000@iabervon.org>

>>>>> "DB" == Daniel Barkalow <barkalow@iabervon.org> writes:

DB> On Wed, 29 Jun 2005, Junio C Hamano wrote:
>> - Blame/Annotate.  Does anybody have a fast and correct one

DB> How about an option to git-rev-list to take a path, and (1) exclude any
DB> branch where the version at that path ends up ignored in a merge and
DB> (2) not list any revision where the version at that path is identical to a
DB> parent?

DB> This should give you the list of all commits which are directly
DB> responsible for the present state of the file, which can then be formatted
DB> as desired.

Sounds close enough if you do not care about copies and
complete rewrites.

^ permalink raw reply

* Re: Last mile for 1.0 again
From: Linus Torvalds @ 2005-07-04 21:59 UTC (permalink / raw)
  To: Daniel Barkalow; +Cc: Junio C Hamano, git
In-Reply-To: <Pine.LNX.4.21.0507041635350.30848-100000@iabervon.org>



On Mon, 4 Jul 2005, Daniel Barkalow wrote:
> 
> How about an option to git-rev-list to take a path, and (1) exclude any
> branch where the version at that path ends up ignored in a merge and
> (2) not list any revision where the version at that path is identical to a
> parent?

Hmm. How is that different from "git-whatchanged path", really?

			Linus

^ permalink raw reply

* Re: Last mile for 1.0 again
From: Daniel Barkalow @ 2005-07-04 22:41 UTC (permalink / raw)
  To: Linus Torvalds, Junio C Hamano; +Cc: git
In-Reply-To: <Pine.LNX.4.58.0507041459030.3570@g5.osdl.org>

On Mon, 4 Jul 2005, Linus Torvalds wrote:

> On Mon, 4 Jul 2005, Daniel Barkalow wrote:
> > 
> > How about an option to git-rev-list to take a path, and (1) exclude any
> > branch where the version at that path ends up ignored in a merge and
> > (2) not list any revision where the version at that path is identical to a
> > parent?
> 
> Hmm. How is that different from "git-whatchanged path", really?

It would short-circuit going up areas of the history which don't
contribute (i.e., lead up to a merge which took its version from a
different parent). It could also stop when it ran out of branches that
have the file at all. Neither of these is all that significant, I guess.

Junio: what's missing from annotate/blame?

	-Daniel
*This .sig left intentionally blank*

^ permalink raw reply

* Re: expensive local git clone
From: David S. Miller @ 2005-07-04 22:49 UTC (permalink / raw)
  To: junkio; +Cc: torvalds, git
In-Reply-To: <7v7jg6i72c.fsf@assigned-by-dhcp.cox.net>

From: Junio C Hamano <junkio@cox.net>
Date: Mon, 04 Jul 2005 14:40:11 -0700

> >>>>> "DSM" == David S Miller <davem@davemloft.net> writes:
> 
> DSM> I keep hoping git-clone-script is going to be a good way
> DSM> to clone two local trees.  Is my hope misguided?  :-)
> 
> Something along these lines?

Looks interesting.  Any particular reason to use "cpio"
instead of "cp"?

^ permalink raw reply

* Re: expensive local git clone
From: Junio C Hamano @ 2005-07-04 23:07 UTC (permalink / raw)
  To: David S. Miller; +Cc: junkio, torvalds, git
In-Reply-To: <20050704.154933.104033112.davem@davemloft.net>

>>>>> "DSM" == David S Miller <davem@davemloft.net> writes:

DSM> Looks interesting.  Any particular reason to use "cpio"
DSM> instead of "cp"?

No particular reason other than I am just used to doing things
that way.

^ permalink raw reply

* Re: Last mile for 1.0 again
From: Junio C Hamano @ 2005-07-04 23:06 UTC (permalink / raw)
  To: Daniel Barkalow; +Cc: Linus Torvalds, git
In-Reply-To: <Pine.LNX.4.21.0507041801040.30848-100000@iabervon.org>

>>>>> "DB" == Daniel Barkalow <barkalow@iabervon.org> writes:

DB> Junio: what's missing from annotate/blame?

Which one are you talking about?

What I use to generate http://members.cox.net/junkio/Summary.txt
is an implementation of an algorithm I consider "complete" in
that it does rename/copy and complete rewrite correctly.  What
is missing from the implementation is efficiency.

------------
#!/usr/bin/perl -w

use strict;

package main;
$::debug = 0;

sub read_blob {
    my $sha1 = shift;
    my $fh = undef;
    my $result;
    local ($/) = undef;
    open $fh, '-|', 'git-cat-file', 'blob', $sha1
	or die "cannot read blob $sha1";
    $result = join('', <$fh>);
    close $fh
	or die "failure while closing pipe to git-cat-file";
    return $result;
}

sub read_diff_raw {
    my ($parent, $filename) = @_;
    my $fh = undef;
    local ($/) = "\0";
    my @result = (); 
    my ($meta, $status, $sha1_1, $sha1_2, $file1, $file2);

    print STDERR "* diff-cache --cached $parent $filename\n" if $::debug;
    my $has_changes = 0;
    open $fh, '-|', 'git-diff-cache', '--cached', '-z', $parent, $filename
	or die "cannot read git-diff-cache $parent $filename";
    while (defined ($meta = <$fh>)) {
	$has_changes = 1;
    }
    close $fh
	or die "failure while closing pipe to git-diff-cache";
    if (!$has_changes) {
	return ();
    }

    $fh = undef;
    print STDERR "* diff-cache -B -C --find-copies-harder --cached $parent\n" if $::debug;
    open($fh, '-|', 'git-diff-cache', '-B', '-C', '--find-copies-harder',
	 '--cached', '-z', $parent)
	or die "cannot read git-diff-cache with $parent";
    while (defined ($meta = <$fh>)) {
	chomp($meta);
	(undef, undef, $sha1_1, $sha1_2, $status) = split(/ /, $meta);
	$file1 = <$fh>;
	chomp($file1);
	if ($status =~ /^[CR]/) {
	    $file2 = <$fh>;
	    chomp($file2);
	} elsif ($status =~ /^D/) {
	    next;
	} else {
	    $file2 = $file1;
	}
	if ($file2 eq $filename) {
	    push @result, [$status, $sha1_1, $sha1_2, $file1, $file2];
	}
    }
    close $fh
	or die "failure while closing pipe to git-diff-cache";
    return @result;
}

sub write_temp_blob {
    my ($sha1, $temp) = @_;
    my $fh = undef;
    my $blob = read_blob($sha1);
    open $fh, '>', $temp
	or die "cannot open temporary file $temp";
    print $fh $blob;
    close($fh);
}

package Git::Patch;
sub new {
    my ($class, $sha1_1, $sha1_2) = @_;
    my $self = bless [], $class;
    my $fh = undef;
    ::write_temp_blob($sha1_1, "/tmp/blame-$$-1");
    ::write_temp_blob($sha1_2, "/tmp/blame-$$-2");
    open $fh, '-|', 'diff', '-u0', "/tmp/blame-$$-1", "/tmp/blame-$$-2"
	or die "cannot read diff";
    while (<$fh>) {
	if (/^\@\@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? \@\@/) {
	    push @$self, [$1, (defined $2 ? $2 : 1),
			  $3, (defined $4 ? $4 : 1)];
	}
    }
    close $fh;
    unlink "/tmp/blame-$$-1", "/tmp/blame-$$-2";
    return $self;
}

sub find_parent_line {
    my ($self, $commit_lineno) = @_;
    my $ofs = 0;
    for (@$self) {
	my ($line_1, $len_1, $line_2, $len_2) = @$_;
	if ($commit_lineno < $line_2) {
	    return $commit_lineno - $ofs;
	}
	if ($line_2 <= $commit_lineno && $commit_lineno < $line_2 + $len_2) {
	    return -1; # changed by commit.
	}
	$ofs += ($len_1 - $len_2);
    }
    return $commit_lineno + $ofs;
}

package Git::Commit;

my %author_name_canon = 
('Linus Torvalds <torvalds@ppc970.osdl.org.(none)>' =>
 'Linus Torvalds <torvalds@ppc970.osdl.org>');

sub canon_author_name {
    my ($name) = @_;
    if (exists $author_name_canon{$name}) {
	return $author_name_canon{$name};
    }
    return $name;
}

sub new {
    my $class = shift;
    my $self = bless {
	PARENT => [],
	TREE => undef,
	AUTHOR => undef,
	COMMITTER => undef,
    }, $class;
    my $commit_sha1 = shift;
    $self->{SHA1} = $commit_sha1;
    my $fh = undef;
    open $fh, '-|', 'git-cat-file', 'commit', $commit_sha1
	or die "cannot read commit object $commit_sha1";
    while (<$fh>) {
	chomp;
	if (/^tree ([0-9a-f]{40})$/) { $self->{TREE} = $1; }
	elsif (/^parent ([0-9a-f]{40})$/) { push @{$self->{PARENT}}, $1; }
	elsif (/^author ([^>]+>)/) {
	    $self->{AUTHOR} = canon_author_name($1);
	}
	elsif (/^committer ([^>]+>)/) {
	    $self->{COMMITTER} = canon_author_name($1);
	}
    }
    close $fh
	or die "failure while closing pipe to git-cat-file";
    return $self;
}

sub find_file {
    my ($commit, $path) = @_;
    my $result = undef;
    my $fh = undef;
    local ($/) = "\0";
    open $fh, '-|', 'git-ls-tree', '-z', '-r', '-d', $commit->{TREE}, $path
	or die "cannot read git-ls-tree $commit->{TREE}";
    while (<$fh>) {
	chomp;
	if (/^[0-7]{6} blob ([0-9a-f]{40})	(.*)$/) {
	    if ($2 ne $path) {
		die "$2 ne $path???";
	    }
	    $result = $1;
	    last;
	}
    }
    close $fh
	or die "failure while closing pipe to git-ls-tree";
    return $result;
}

package Git::Blame;
sub new {
    my $class = shift;
    my $self = bless {
	LINE => [],
	UNKNOWN => undef,
	WORK => [],
    }, $class;
    my $commit = shift;
    my $filename = shift;
    my $sha1 = $commit->find_file($filename);
    my $blob = ::read_blob($sha1);
    my @blob = (split(/\n/, $blob));
    for (my $i = 0; $i < @blob; $i++) {
	$self->{LINE}[$i] = +{
	    COMMIT => $commit,
	    FOUND => undef,
	    FILENAME => $filename,
	    LINENO => ($i + 1),
	};
    }
    $self->{UNKNOWN} = scalar @blob;
    push @{$self->{WORK}}, [$commit, $filename];
    return $self;
}

sub read_blame_cache {
    my $self = shift;
    my $filename = shift;
    my $fh = undef;
    my $pi = $self->{'PATHINFO'} = {};
    open $fh, '<', $filename;
    while (<$fh>) {
	chomp;
	my ($commit, $parent, $path) = split(/\t/, $_);
	$pi->{$path}{$commit}{$parent} = 1;
    }
    close $fh;
}

sub print {
    my $self = shift;
    my $line_termination = shift;
    for (my $i = 0; $i < @{$self->{LINE}}; $i++) {
	my $l = $self->{LINE}[$i];
	print ($l->{FOUND} ? ':' : '?');;
	print "$l->{COMMIT}->{SHA1}	";
	print "$l->{COMMIT}->{AUTHOR}	";
	print "$l->{COMMIT}->{COMMITTER}	";
	print "$l->{LINENO}	$l->{FILENAME}";
	print $line_termination;
    }
}

sub take_responsibility {
    my ($self, $commit) = @_;
    for (my $i = 0; $i < @{$self->{LINE}}; $i++) {
	my $l = $self->{LINE}[$i];
	if (! $l->{FOUND} && ($l->{COMMIT}->{SHA1} eq $commit->{SHA1})) {
	    $l->{FOUND} = 1;
	    $self->{UNKNOWN}--;
	}
    }
}

sub blame_parent {
    my ($self, $commit, $parent, $filename) = @_;
    my @diff = ::read_diff_raw($parent->{SHA1}, $filename);
    my $filename_in_parent;
    my $passed_blame_to_parent = undef;
    if (@diff == 0) {
	# We have not touched anything.  Blame parent for everything
	# that we are suspected for.
	for (my $i = 0; $i < @{$self->{LINE}}; $i++) {
	    my $l = $self->{LINE}[$i];
	    if (! $l->{FOUND} && ($l->{COMMIT}->{SHA1} eq $commit->{SHA1})) {
		$l->{COMMIT} = $parent;
		$passed_blame_to_parent = 1;
	    }
	}
	$filename_in_parent = $filename;
    }
    elsif (@diff != 1) {
	# This should not happen.
	for (@diff) {
	    print "** @$_\n";
	}
	die "Oops";
    }
    else {
	my ($status, $sha1_1, $sha1_2, $file1, $file2) = @{$diff[0]};
	print STDERR "** $status $file1 $file2\n" if $::debug;
	if ($status =~ /N/ || $status =~ /M[0-9][0-9]/) {
	    # Either some of other parents created it, or we did.
	    # At this point the only thing we know is that this
	    # parent is not responsible for it.
	    ;
	}
	else {
	    my $patch = Git::Patch->new($sha1_1, $sha1_2);
	    $filename_in_parent = $file1;
	    for (my $i = 0; $i < @{$self->{LINE}}; $i++) {
		my $l = $self->{LINE}[$i];
		if (! $l->{FOUND} && $l->{COMMIT}->{SHA1} eq $commit->{SHA1}) {
		    # We are suspected to have introduced this line.
		    # Does it exist in the parent?
		    my $lineno = $l->{LINENO};
		    my $parent_line = $patch->find_parent_line($lineno);
		    if ($parent_line < 0) {
			# No, we may be the guilty ones, or some other
			# parent might be.  We do not assign blame to
			# ourselves here yet.
			;
		    }
		    else {
			# This line is coming from the parent, so pass
			# blame to it.
			$l->{COMMIT} = $parent;
			$l->{FILENAME} = $file1;
			$l->{LINENO} = $parent_line;
			$passed_blame_to_parent = 1;
		    }
		}
	    }
	}
    }
    if ($passed_blame_to_parent && $self->{UNKNOWN}) {
	unshift @{$self->{WORK}},
	[$parent, $filename_in_parent];
    }
}

sub assign {
    my ($self, $commit, $filename) = @_;
    # We do read-tree of the current commit and diff-cache
    # with each parents, instead of running diff-tree.  This
    # is because diff-tree does not look for copies hard enough.

    if (exists $self->{'PATHINFO'} && exists $self->{'PATHINFO'}{$filename} &&
	!exists $self->{'PATHINFO'}{$filename}{$commit->{SHA1}} &&
	@{$commit->{PARENT}} == 1) {
	# This commit did not touch the path at all, and
	# has only one parent.  It is all that parent's fault.

	my $parent = Git::Commit->new($commit->{PARENT}[0]);
	my $passed_blame_to_parent = 0;
	for (my $i = 0; $i < @{$self->{LINE}}; $i++) {
	    my $l = $self->{LINE}[$i];
	    if (! $l->{FOUND} &&
		($l->{COMMIT}->{SHA1} eq $commit->{SHA1})) {
		$l->{COMMIT} = $parent;
		$passed_blame_to_parent = 1;
	    }
	}
	if ($passed_blame_to_parent && $self->{UNKNOWN}) {
	    unshift @{$self->{WORK}},
	    [$parent, $filename];
	}
	return;
    }

    print STDERR "* read-tree  $commit->{SHA1}\n" if $::debug;
    system('git-read-tree', '-m', $commit->{SHA1});
    for my $parent (@{$commit->{PARENT}}) {
	$self->blame_parent($commit, Git::Commit->new($parent), $filename);
    }
    $self->take_responsibility($commit);
}

sub assign_blame {
    my ($self) = @_;
    while ($self->{UNKNOWN} && @{$self->{WORK}}) {
	my $wk = shift @{$self->{WORK}};
	my ($commit, $filename) = @$wk;
	$self->assign($commit, $filename);
    }
}



################################################################
package main;
my $usage = "blame [-z] <commit> filename";
my $line_termination = "\n";

$::ENV{GIT_INDEX_FILE} = "/tmp/blame-$$-index";
unlink($::ENV{GIT_INDEX_FILE});

if ($ARGV[0] eq '-z') {
    $line_termination = "\0";
    shift;
}

if (@ARGV != 2) {
    die $usage;
}

my $head_commit = Git::Commit->new($ARGV[0]);
my $filename = $ARGV[1];
my $blame = Git::Blame->new($head_commit, $filename);
if (-f ".blame-cache") {
    $blame->read_blame_cache(".blame-cache");
}

$blame->assign_blame();
$blame->print($line_termination);

unlink($::ENV{GIT_INDEX_FILE});

__END__

How does this work, and what do we do about merges?

The algorithm considers that the first parent is our main line of
development and treats it somewhat special than other parents.  So we
pass on the blame to the first parent if a line has not changed from
it.  For lines that have changed from the first parent, we must have
either inherited that change from some other parent, or it could have
been merge conflict resolution edit we did on our own.

The following picture illustrates how we pass on and assign blames.

In the sample, the original O was forked into A and B and then merged
into M.  Line 1, 2, and 4 did not change.  Line 3 and 5 are changed in
A, and Line 5 and 6 are changed in B.  M made its own decision to
resolve merge conflicts at Line 5 to something different from A and B:

                A: 1 2 T 4 T 6
               /               \ 
O: 1 2 3 4 5 6                  M: 1 2 T 4 M S
               \               / 
                B: 1 2 3 4 S S

In the following picture, each line is annotated with a blame letter.
A lowercase blame (e.g. "a" for "1") means that commit or its ancestor
is the guilty party but we do not know which particular ancestor is
responsible for the change yet.  An uppercase blame means that we know
that commit is the guilty party.

First we look at M (the HEAD) and initialize Git::Blame->{LINE} like
this:

             M: 1 2 T 4 M S
                m m m m m m

That is, we know all lines are results of modification made by some
ancestor of M, so we assign lowercase 'm' to all of them.

Then we examine our first parent A.  Throughout the algorithm, we are
always only interested in the lines we are the suspect, but this being
the initial round, we are the suspect for all of them.  We notice that
1 2 T 4 are the same as the parent A, so we pass the blame for these
four lines to A.  M and S are different from A, so we leave them as
they are (note that we do not immediately take the blame for them):

             M: 1 2 T 4 M S
                a a a a m m

Next we go on to examine parent B.  Again, we are only interested in
the lines we are still the suspect (i.e. M and S).  We notice S is
something we inherited from B, so we pass the blame on to it, like
this:

             M: 1 2 T 4 M S
                a a a a m b

Once we exhausted the parents, we look at the results and take
responsibility for the remaining ones that we are still the suspect:

             M: 1 2 T 4 M S
                a a a a M b

We are done with M.  And we know commits A and B need to be examined
further, so we do them recursively.  When we look at A, we again only
look at the lines that A is the suspect:

             A: 1 2 T 4 T 6
                a a a a M b

Among 1 2 T 4, comparing against its parent O, we notice 1 2 4 are
the same so pass the blame for those lines to O:

             A: 1 2 T 4 T 6
                o o a o M b

A is a non-merge commit; we have already exhausted the parents and
take responsibility for the remaining ones that A is the suspect:

             A: 1 2 T 4 T 6
                o o A o M b

We go on like this and the final result would become:

             O: 1 2 3 4 5 6
                O O A O M B

^ permalink raw reply

* git-fetch-pack
From: Linus Torvalds @ 2005-07-05  0:08 UTC (permalink / raw)
  To: Git Mailing List


Ok,
 I've written a "git-fetch-pack" along the lines I outlined a few days 
ago, ie the person doing the fetching ends up listing the commits it has, 
and then both ends negotiate the missing objects.

It's probably buggy in many ways, but I actually used it to fetch the last 
merge with Russell King:

	remote=$(git-fetch-pack $repo master)
	if [ "$remote" ]; then
		git resolve $(git-rev-parse HEAD) $remote "Merge $repo"
	fi

ends up doing something sane.

In general, the format is to tell git-fetch-pack where to fetch things
from, and what branch to use (if you give none, or you give multiple, it
does various magic things, you shouldn't do it). It will then fetch and 
unpack the pack, and return the SHA1 corresponding to the remote ref if 
everything was successful.

So the above will fetch a remote ref, and resolve it (ie it's basically a 
specialized "git pull").

		Linus

^ 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