Anonymous avatar Anonymous committed 8ac93bc Merge

Merge branch 'master' of .

Comments (0)

Files changed (49)

 git-archimport
 git-bisect
 git-branch
-git-build-rev-cache
 git-cat-file
 git-checkout
 git-checkout-index
 git-rev-parse
 git-rev-tree
 git-revert
+git-send-email
 git-send-pack
 git-sh-setup
 git-shortlog
 git-show-branch
 git-show-index
-git-show-rev-cache
 git-ssh-fetch
+git-ssh-pull
+git-ssh-push
 git-ssh-upload
 git-status
 git-stripspace

Documentation/Makefile

 man7: $(DOC_MAN7)
 
 install:
-	$(INSTALL) -m755 -d $(DESTDIR)/$(man1) $(DESTDIR)/$(man7)
+	$(INSTALL) -d -m755 $(DESTDIR)/$(man1) $(DESTDIR)/$(man7)
 	$(INSTALL) $(DOC_MAN1) $(DESTDIR)/$(man1)
 	$(INSTALL) $(DOC_MAN7) $(DESTDIR)/$(man7)
 

Documentation/git-build-rev-cache.txt

-git-build-rev-cache(1)
-======================
-
-NAME
-----
-git-build-rev-cache - Create or update a rev-cache file.
-
-SYNOPSIS
---------
-'git-build-rev-cache' [-v] <rev-cache-file> < list-of-heads
-
-DESCRIPTION
------------
-Creates or updates a file that describes the commit ancestry reachable
-from the list-of-head read from stdin. This file is in an append-only
-binary format to make the server side friendly to rsync mirroring
-scheme, and can be read by the git-show-rev-cache command.
-
-OPTIONS
--------
--v::
-	Verbose.
-
-<rev-cache-file>::
-	The rev-cache to operate on.
-
-Author
-------
-Written by Junio C Hamano <junkio@cox.net>
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
-GIT
----
-Part of the link:git.html[git] suite
-

Documentation/git-fetch.txt

 -------
 include::pull-fetch-param.txt[]
 
+-u, \--update-head-ok::
+	By default 'git-fetch' refuses to update the head which
+	corresponds to the current branch.  This flag disables the
+	check.  Note that fetching into the current branch will not
+	update the index and working directory, so use it with care.
+
 
 Author
 ------

Documentation/git-rev-list.txt

 
 The *--bisect* flag limits output to the one commit object which is
 roughly halfway between the included and excluded commits. Thus,
-if "git-rev-list --bisect foo ^bar ^baz" outputs 'midpoint', the output
-of "git-rev-list foo ^midpoint" and "git-rev-list midpoint ^bar ^baz"
+if 'git-rev-list --bisect foo ^bar
+^baz' outputs 'midpoint', the output
+of 'git-rev-list foo ^midpoint' and 'git-rev-list midpoint
+^bar
+^baz'
 would be of roughly the same length. Finding the change which introduces
 a regression is thus reduced to a binary search: repeatedly generate and
 test new 'midpoint's until the commit chain is of length one.

Documentation/git-show-rev-cache.txt

-git-show-rev-cache(1)
-=====================
-
-NAME
-----
-git-show-rev-cache - Show the contents of a rev-cache file.
-
-SYNOPSIS
---------
-'git-show-rev-cache' <rev-cache-file>
-
-DESCRIPTION
------------
-Show the contents of <rev-cache-file>.
-
-A rev-cache file describes the commit ancestry reachable from references
-supplied as input to get-build-rev-cache. This file is in an
-append-only binary format to make the server side friendly to rsync
-mirroring scheme.
-
-OPTIONS
--------
-<rev-cache-file>::
-	Rev-cache file to display.
-
-Author
-------
-Written by Junio C Hamano <junkio@cox.net>
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
-GIT
----
-Part of the link:git.html[git] suite
-

Documentation/git-update-server-info.txt

 
 * info/refs
 
-* info/rev-cache
-
 
 BUGS
 ----

Documentation/git.txt

 
 Interrogators:
 
-link:git-build-rev-cache.html[git-build-rev-cache]::
-	Create or update a rev-cache file.
-
 link:git-cherry.html[git-cherry]::
 	Find commits not merged upstream.
 
 	Send patch e-mails out of "format-patch --mbox" output.
 	Previously this command was known as git-send-email-script.
 
-link:git-show-rev-cache.html[git-show-rev-cache]::
-	Show the contents of a rev-cache file.
-
 link:git-stripspace.html[git-stripspace]::
 	Filter out empty lines.
 

Documentation/pull-fetch-param.txt

           <ref>: when pulling/fetching, and <ref>:<ref> when
           pushing.  That is, do not store it locally if
           fetching, and update the same name if pushing.
+
+-a, \--append::
+	Append ref names and object names of fetched refs to the
+	existing contents of $GIT_DIR/FETCH_HEAD.  Without this
+	option old data in $GIT_DIR/FETCH_HEAD will be overwritten.
+
+-f, \--force::
+	Usually, the command refuses to update a local ref that is
+	not an ancestor of the remote ref used to overwrite it.
+	This flag disables the check.  What this means is that the
+	local repository can lose commits; use it with care.

Documentation/repository-layout.txt

 	listing their 40-byte hexadecimal object names separated
 	by a space and terminated by a newline.
 
-info/rev-cache::
-	No higher-level tool currently takes advantage of this
-	file, but it is generated when `git update-server-info`
-	is run.  It records the commit ancestry information of
-	the commits in this repository in a concise binary
-	format, and can be read with `git-show-rev-cache`.
-
 info/exclude::
 	This file, by convention among Porcelains, stores the
 	exclude pattern list.  `git status` looks at it, but
 
 # ... and all the rest
 PROGRAMS = \
-	git-apply git-build-rev-cache git-cat-file \
+	git-apply git-cat-file \
 	git-checkout-index git-clone-pack git-commit-tree \
 	git-convert-objects git-diff-files \
 	git-diff-helper git-diff-index git-diff-stages \
 	git-peek-remote git-prune-packed git-read-tree \
 	git-receive-pack git-rev-list git-rev-parse \
 	git-rev-tree git-send-pack git-show-branch \
-	git-show-index git-show-rev-cache git-ssh-fetch \
+	git-show-index git-ssh-fetch \
 	git-ssh-upload git-tar-tree git-unpack-file \
 	git-unpack-objects git-update-index git-update-server-info \
 	git-upload-pack git-verify-pack git-write-tree \
 	$(SIMPLE_PROGRAMS)
 
+# Backward compatibility -- to be removed in 0.99.8
+PROGRAMS += git-ssh-pull git-ssh-push
+
 PYMODULES = \
 	gitMergeCommon.py
 
 LIB_H = \
 	blob.h cache.h commit.h count-delta.h csum-file.h delta.h \
 	diff.h epoch.h object.h pack.h pkt-line.h quote.h refs.h \
-	rev-cache.h run-command.h strbuf.h tag.h tree.h
+	run-command.h strbuf.h tag.h tree.h
 
 DIFF_OBJS = \
 	diff.o diffcore-break.o diffcore-order.o diffcore-pathspec.o \
 	blob.o commit.o connect.o count-delta.o csum-file.o \
 	date.o diff-delta.o entry.o ident.o index.o \
 	object.o pack-check.o patch-delta.o path.o pkt-line.o \
-	quote.o read-cache.o refs.o rev-cache.o run-command.o \
+	quote.o read-cache.o refs.o run-command.o \
 	server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
 	tag.o tree.o usage.o $(DIFF_OBJS)
 
 endif
 ifeq ($(shell uname -s),SunOS)
 	NEEDS_SOCKET = YesPlease
-	PLATFORM_DEFINES += -DNO_GETDOMAINNAME=1
+	NEEDS_NSL = YesPlease
+	PLATFORM_DEFINES += -D__EXTENSIONS__
 endif
 
 ifndef SHELL_PATH
 	LIBS += -lsocket
 	SIMPLE_LIB += -lsocket
 endif
+ifdef NEEDS_NSL
+	LIBS += -lnsl
+	SIMPLE_LIB += -lnsl
+endif
 
 DEFINES += '-DSHA1_HEADER=$(SHA1_HEADER)'
 
 git-local-fetch: fetch.o
 git-ssh-fetch: rsh.o fetch.o
 git-ssh-upload: rsh.o
+git-ssh-pull: rsh.o fetch.o
+git-ssh-push: rsh.o
 
 git-http-fetch: LIBS += -lcurl
 git-rev-list: LIBS += $(OPENSSL_LIBSSL)
 ### Installation rules
 
 install: $(PROGRAMS) $(SCRIPTS)
-	$(INSTALL) -m755 -d $(DESTDIR)$(bindir)
+	$(INSTALL) -d -m755 $(DESTDIR)$(bindir)
 	$(INSTALL) $(PROGRAMS) $(SCRIPTS) $(DESTDIR)$(bindir)
 	$(INSTALL) git-revert $(DESTDIR)$(bindir)/git-cherry-pick
 	sh ./cmd-rename.sh $(DESTDIR)$(bindir)
 	$(MAKE) -C templates install
-	$(INSTALL) -m755 -d $(DESTDIR)$(GIT_PYTHON_DIR)
+	$(INSTALL) -d -m755 $(DESTDIR)$(GIT_PYTHON_DIR)
 	$(INSTALL) $(PYMODULES) $(DESTDIR)$(GIT_PYTHON_DIR)
 
 install-doc:

build-rev-cache.c

-#include "refs.h"
-#include "cache.h"
-#include "commit.h"
-#include "rev-cache.h"
-
-static void process_head_list(int verbose)
-{
-	char buf[512];
-
-	while (fgets(buf, sizeof(buf), stdin)) {
-		unsigned char sha1[20];
-		struct commit *commit;
-
-		if (get_sha1_hex(buf, sha1)) {
-			error("ignoring: %s", buf);
-			continue;
-		}
-		if (!(commit = lookup_commit_reference(sha1))) {
-			error("not a commit: %s", sha1_to_hex(sha1));
-			continue;
-		}
-		record_rev_cache(commit->object.sha1, verbose ? stderr : NULL);
-	}
-}
-
-
-static const char *build_rev_cache_usage =
-"git-build-rev-cache <rev-cache-file> < list-of-heads";
-
-int main(int ac, char **av)
-{
-	int verbose = 0;
-	const char *path;
-
-	while (1 < ac && av[1][0] == '-') {
-		if (!strcmp(av[1], "-v"))
-			verbose = 1;
-		else
-			usage(build_rev_cache_usage);
-		ac--; av++;
-	}
-
-	if (ac != 2)
-		usage(build_rev_cache_usage);
-
-	path = av[1];
-
-	/* read existing rev-cache */
-	read_rev_cache(path, NULL, 0);
-
-	process_head_list(verbose);
-
-	/* update the rev-cache database by appending newly found one to it */
-	write_rev_cache(path, path);
-	return 0;
-}
 git-verify-tag-script	git-verify-tag
 git-http-pull	git-http-fetch
 git-local-pull	git-local-fetch
-git-ssh-pull	git-ssh-fetch
 git-checkout-cache	git-checkout-index
 git-diff-cache	git-diff-index
 git-merge-cache	git-merge-index
 git-update-cache	git-update-index
-git-ssh-push	git-ssh-upload
 git-convert-cache	git-convert-objects
 git-fsck-cache	git-fsck-objects
 EOF
+
+# These two are a bit more than symlinks now.
+# git-ssh-push	git-ssh-upload
+# git-ssh-pull	git-ssh-fetch
 #include "commit.h"
 #include "cache.h"
 
+int save_commit_buffer = 1;
+
 struct sort_node
 {
 	/*
 			     sha1_to_hex(item->object.sha1));
 	}
 	ret = parse_commit_buffer(item, buffer, size);
-	if (!ret) {
+	if (save_commit_buffer && !ret) {
 		item->buffer = buffer;
 		return 0;
 	}
 	char *buffer;
 };
 
+extern int save_commit_buffer;
 extern const char *commit_type;
 
 struct commit *lookup_commit(const unsigned char *sha1);

convert-objects.c

 #define _XOPEN_SOURCE /* glibc2 needs this */
-#define __EXTENSIONS__ /* solaris needs this */
 #include <time.h>
 #include <ctype.h>
 #include "cache.h"
 Section: devel
 Priority: optional
 Maintainer: Junio C Hamano <junkio@cox.net>
-Build-Depends-Indep: libz-dev, libssl-dev, libcurl3-dev, asciidoc (>= 6.0.3), xmlto, debhelper (>= 4.0.0)
+Build-Depends-Indep: libz-dev, libssl-dev, libcurl3-dev, asciidoc (>= 6.0.3), xmlto, debhelper (>= 4.0.0), bc
 Standards-Version: 3.6.1
 
 Package: git-core
 Architecture: any
 Depends: ${shlibs:Depends}, ${perl:Depends}, ${misc:Depends}, patch, rcs
-Recommends: rsync, curl, ssh, libmail-sendmail-perl, libemail-valid-perl, python (>= 2.4.0)
+Recommends: rsync, curl, ssh, libmail-sendmail-perl, libemail-valid-perl, python (>= 2.4.0), less
 Suggests: cogito
 Conflicts: git, cogito (<< 0.13)
 Description: The git content addressable filesystem
 build: debian/build-stamp
 debian/build-stamp:
 	dh_testdir
-	$(MAKE) prefix=$(PREFIX) PYTHON_PATH=/usr/bin/python2.4 all doc
+	$(MAKE) prefix=$(PREFIX) PYTHON_PATH=/usr/bin/python2.4 all doc test
 	touch debian/build-stamp
 
 debian-clean:
 		}
 		offset += 48;
 	}
+	free(buf);
 	return 0;
 }
 
 {
 	int namelen = strlen(path);
 	struct diff_filespec *spec = xmalloc(sizeof(*spec) + namelen + 1);
+
+	memset(spec, 0, sizeof(*spec));
 	spec->path = (char *)(spec + 1);
-	strcpy(spec->path, path);
-	spec->should_free = spec->should_munmap = 0;
-	spec->xfrm_flags = 0;
-	spec->size = 0;
-	spec->data = NULL;
-	spec->mode = 0;
-	memset(spec->sha1, 0, 20);
+	memcpy(spec->path, path, namelen+1);
 	return spec;
 }
 
 	return 0;
 }
 
-void diff_free_filespec(struct diff_filespec *s)
+void diff_free_filespec_data(struct diff_filespec *s)
 {
 	if (s->should_free)
 		free(s->data);
 	else if (s->should_munmap)
 		munmap(s->data, s->size);
-	free(s);
+	s->should_free = s->should_munmap = 0;
+	s->data = NULL;
 }
 
 static void prep_temp_blob(struct diff_tempfile *temp,
 	dp->status = 0;
 	dp->source_stays = 0;
 	dp->broken_pair = 0;
-	diff_q(queue, dp);
+	if (queue)
+		diff_q(queue, dp);
 	return dp;
 }
 
 void diff_free_filepair(struct diff_filepair *p)
 {
-	diff_free_filespec(p->one);
-	diff_free_filespec(p->two);
+	diff_free_filespec_data(p->one);
+	diff_free_filespec_data(p->two);
+	free(p->one);
+	free(p->two);
 	free(p);
 }
 
 
 	dp = diff_queue(outq, d->one, c->two);
 	dp->score = p->score;
-	diff_free_filespec(d->two);
-	diff_free_filespec(c->one);
+	diff_free_filespec_data(d->two);
+	diff_free_filespec_data(c->one);
 	free(d);
 	free(c);
 }

diffcore-rename.c

 	if (first < rename_dst_nr)
 		memmove(rename_dst + first + 1, rename_dst + first,
 			(rename_dst_nr - first - 1) * sizeof(*rename_dst));
-	rename_dst[first].two = two;
+	rename_dst[first].two = alloc_filespec(two->path);
+	fill_filespec(rename_dst[first].two, two->sha1, two->mode);
 	rename_dst[first].pair = NULL;
 	return &(rename_dst[first]);
 }
 	return score;
 }
 
-static void record_rename_pair(struct diff_queue_struct *renq,
-			       int dst_index, int src_index, int score)
+static void record_rename_pair(int dst_index, int src_index, int score)
 {
 	struct diff_filespec *one, *two, *src, *dst;
 	struct diff_filepair *dp;
 	two = alloc_filespec(dst->path);
 	fill_filespec(two, dst->sha1, dst->mode);
 
-	dp = diff_queue(renq, one, two);
+	dp = diff_queue(NULL, one, two);
 	dp->score = score;
 	dp->source_stays = rename_src[src_index].src_path_left;
 	rename_dst[dst_index].pair = dp;
 void diffcore_rename(int detect_rename, int minimum_score)
 {
 	struct diff_queue_struct *q = &diff_queued_diff;
-	struct diff_queue_struct renq, outq;
+	struct diff_queue_struct outq;
 	struct diff_score *mx;
-	int i, j;
+	int i, j, rename_count;
 	int num_create, num_src, dst_cnt;
 
 	if (!minimum_score)
 		minimum_score = DEFAULT_RENAME_SCORE;
-	renq.queue = NULL;
-	renq.nr = renq.alloc = 0;
+	rename_count = 0;
 
 	for (i = 0; i < q->nr; i++) {
 		struct diff_filepair *p = q->queue[i];
 			struct diff_filespec *one = rename_src[j].one;
 			if (!is_exact_match(one, two))
 				continue;
-			record_rename_pair(&renq, i, j, MAX_SCORE);
+			record_rename_pair(i, j, MAX_SCORE);
+			rename_count++;
 			break; /* we are done with this entry */
 		}
 	}
-	diff_debug_queue("done detecting exact", &renq);
 
 	/* Have we run out the created file pool?  If so we can avoid
 	 * doing the delta matrix altogether.
 	 */
-	if (renq.nr == rename_dst_nr)
+	if (rename_count == rename_dst_nr)
 		goto cleanup;
 
-	num_create = (rename_dst_nr - renq.nr);
+	num_create = (rename_dst_nr - rename_count);
 	num_src = rename_src_nr;
 	mx = xmalloc(sizeof(*mx) * num_create * num_src);
 	for (dst_cnt = i = 0; i < rename_dst_nr; i++) {
 			continue; /* already done, either exact or fuzzy. */
 		if (mx[i].score < minimum_score)
 			break; /* there is no more usable pair. */
-		record_rename_pair(&renq, mx[i].dst, mx[i].src, mx[i].score);
+		record_rename_pair(mx[i].dst, mx[i].src, mx[i].score);
+		rename_count++;
 	}
 	free(mx);
-	diff_debug_queue("done detecting fuzzy", &renq);
 
  cleanup:
 	/* At this point, we have found some renames and copies and they
-	 * are kept in renq.  The original list is still in *q.
+	 * are recorded in rename_dst.  The original list is still in *q.
 	 */
 	outq.queue = NULL;
 	outq.nr = outq.alloc = 0;
 			 *
 			 * (1) this is a broken delete and the counterpart
 			 *     broken create remains in the output; or
-			 * (2) this is not a broken delete, and renq does
-			 *     not have a rename/copy to move p->one->path
-			 *     out.
+			 * (2) this is not a broken delete, and rename_dst
+			 *     does not have a rename/copy to move p->one->path
+			 *     out of existence.
 			 *
 			 * Otherwise, the counterpart broken create
 			 * has been turned into a rename-edit; or
 					pair_to_free = p;
 			}
 			else {
-				for (j = 0; j < renq.nr; j++)
-					if (!strcmp(renq.queue[j]->one->path,
-						    p->one->path))
-						break;
-				if (j < renq.nr)
+				for (j = 0; j < rename_dst_nr; j++) {
+					if (!rename_dst[j].pair)
+						continue;
+					if (strcmp(rename_dst[j].pair->
+						   one->path,
+						   p->one->path))
+						continue;
+					break;
+				}
+				if (j < rename_dst_nr)
 					/* this path remains */
 					pair_to_free = p;
 			}
 	}
 	diff_debug_queue("done copying original", &outq);
 
-	free(renq.queue);
 	free(q->queue);
 	*q = outq;
 	diff_debug_queue("done collapsing", q);
 		}
 	}
 
+	for (i = 0; i < rename_dst_nr; i++) {
+		diff_free_filespec_data(rename_dst[i].two);
+		free(rename_dst[i].two);
+	}
+
 	free(rename_dst);
 	rename_dst = NULL;
 	rename_dst_nr = rename_dst_alloc = 0;
 			  unsigned short);
 
 extern int diff_populate_filespec(struct diff_filespec *, int);
-extern void diff_free_filespec(struct diff_filespec *);
+extern void diff_free_filespec_data(struct diff_filespec *);
 
 struct diff_filepair {
 	struct diff_filespec *one;
 		what, missing_hex, sha1_to_hex(current_commit_sha1));
 }
 
-static int make_sure_we_have_it(const char *what, unsigned char *sha1)
-{
-	int status = 0;
-
-	if (!has_sha1_file(sha1)) {
-		status = fetch(sha1);
-		if (status && what)
-			report_missing(what, sha1);
-	}
-	return status;
-}
-
 static int process(unsigned char *sha1, const char *type);
 
 static int process_tree(struct tree *tree)
 {
-	struct tree_entry_list *entries;
+	struct tree_entry_list *entry;
 
 	if (parse_tree(tree))
 		return -1;
 
-	for (entries = tree->entries; entries; entries = entries->next) {
-		if (process(entries->item.any->sha1,
-			    entries->directory ? tree_type : blob_type))
+	entry = tree->entries;
+	tree->entries = NULL;
+	while (entry) {
+		struct tree_entry_list *next = entry->next;
+		if (process(entry->item.any->sha1,
+			    entry->directory ? tree_type : blob_type))
 			return -1;
+		free(entry);
+		entry = next;
 	}
 	return 0;
 }
 
+#define COMPLETE	1U
+#define TO_FETCH	2U
+#define TO_SCAN		4U
+#define SCANNED		8U
+
+static struct commit_list *complete = NULL;
+
 static int process_commit(struct commit *commit)
 {
 	if (parse_commit(commit))
 		return -1;
 
+	while (complete && complete->item->date >= commit->date) {
+		pop_most_recent_commit(&complete, COMPLETE);
+	}
+
+	if (commit->object.flags & COMPLETE)
+		return 0;
+
 	memcpy(current_commit_sha1, commit->object.sha1, 20);
 
+	pull_say("walk %s\n", sha1_to_hex(commit->object.sha1));
+
 	if (get_tree) {
 		if (process(commit->tree->object.sha1, tree_type))
 			return -1;
 	if (get_history) {
 		struct commit_list *parents = commit->parents;
 		for (; parents; parents = parents->next) {
-			if (has_sha1_file(parents->item->object.sha1))
-				continue;
-			if (process(parents->item->object.sha1,
-				    commit_type))
+			if (process(parents->item->object.sha1, commit_type))
 				return -1;
 		}
 	}
 
 static int process_object(struct object *obj)
 {
+	if (obj->flags & SCANNED)
+		return 0;
+	obj->flags |= SCANNED;
+
 	if (obj->type == commit_type) {
 		if (process_commit((struct commit *)obj))
 			return -1;
 static int process(unsigned char *sha1, const char *type)
 {
 	struct object *obj = lookup_object_type(sha1, type);
+
 	if (has_sha1_file(sha1)) {
 		parse_object(sha1);
 		/* We already have it, so we should scan it now. */
-		return process_object(obj);
+		if (obj->flags & (SCANNED | TO_SCAN))
+			return 0;
+		object_list_insert(obj, process_queue_end);
+		process_queue_end = &(*process_queue_end)->next;
+		obj->flags |= TO_SCAN;
+		return 0;
 	}
-	if (object_list_contains(process_queue, obj))
+	if (obj->flags & (COMPLETE | TO_FETCH))
 		return 0;
 	object_list_insert(obj, process_queue_end);
 	process_queue_end = &(*process_queue_end)->next;
+	obj->flags |= TO_FETCH;
 
-	//fprintf(stderr, "prefetch %s\n", sha1_to_hex(sha1));
 	prefetch(sha1);
 		
 	return 0;
 
 static int loop(void)
 {
+	struct object_list *elem;
+
 	while (process_queue) {
 		struct object *obj = process_queue->item;
-		/*
-		fprintf(stderr, "%d objects to pull\n", 
-			object_list_length(process_queue));
-		*/
-		process_queue = process_queue->next;
+		elem = process_queue;
+		process_queue = elem->next;
+		free(elem);
 		if (!process_queue)
 			process_queue_end = &process_queue;
 
-		//fprintf(stderr, "fetch %s\n", sha1_to_hex(obj->sha1));
-		
-		if (make_sure_we_have_it(obj->type ? obj->type : "object", 
-					 obj->sha1))
-			return -1;
+		/* If we are not scanning this object, we placed it in
+		 * the queue because we needed to fetch it first.
+		 */
+		if (! (obj->flags & TO_SCAN)) {
+			if (fetch(obj->sha1)) {
+				report_missing(obj->type
+					       ? obj->type
+					       : "object", obj->sha1);
+				return -1;
+			}
+		}
 		if (!obj->type)
 			parse_object(obj->sha1);
 		if (process_object(obj))
 	return -1;
 }
 
+static int mark_complete(const char *path, const unsigned char *sha1)
+{
+	struct commit *commit = lookup_commit_reference_gently(sha1, 1);
+	if (commit) {
+		commit->object.flags |= COMPLETE;
+		insert_by_date(commit, &complete);
+	}
+	return 0;
+}
 
 int pull(char *target)
 {
 	unsigned char sha1[20];
 	int fd = -1;
 
+	save_commit_buffer = 0;
 	if (write_ref && current_ref) {
 		fd = lock_ref_sha1(write_ref, current_ref);
 		if (fd < 0)
 			return -1;
 	}
 
+	for_each_ref(mark_complete);
+
 	if (interpret_target(target, sha1))
 		return error("Could not interpret %s as something to pull",
 			     target);
 }
 
 bisect_auto_next() {
-	bisect_next_check && bisect_next
+	bisect_next_check && bisect_next || :
 }
 
 bisect_next() {
 . git-sh-setup || die "Not a git archive"
 
 usage () {
-    echo >&2 "usage: $(basename $0)"' [<branchname> [start-point]]
+    echo >&2 "usage: $(basename $0)"' [-d <branch>] | [<branch> [start-point]]
 
 If no arguments, show available branches and mark current branch with a star.
 If one argument, create a new branch <branchname> based off of current HEAD.
     exit 1
 }
 
+delete_branch () {
+    option="$1" branch_name="$2"
+    headref=$(readlink "$GIT_DIR/HEAD" | sed -e 's|^refs/heads/||')
+    case ",$headref," in
+    ",$branch_name,")
+	die "Cannot delete the branch you are on." ;;
+    ,,)
+	die "What branch are you on anyway?" ;;
+    esac
+    branch=$(cat "$GIT_DIR/refs/heads/$branch_name") &&
+	branch=$(git-rev-parse --verify "$branch^0") ||
+	    die "Seriously, what branch are you talking about?"
+    case "$option" in
+    -D)
+	;;
+    *)
+	mbs=$(git-merge-base -a "$branch" HEAD | tr '\012' ' ')
+	case " $mbs " in
+	*' '$branch' '*)
+	    # the merge base of branch and HEAD contains branch --
+	    # which means that the HEAD contains everything in the HEAD.
+	    ;;
+	*)
+	    echo >&2 "The branch '$branch_name' is not a strict subset of your current HEAD.
+If you are sure you want to delete it, run 'git branch -D $branch_name'."
+	    exit 1
+	    ;;
+	esac
+	;;
+    esac
+    rm -f "$GIT_DIR/refs/heads/$branch_name"
+    echo "Deleted branch $branch_name."
+    exit 0
+}
+
+while case "$#,$1" in 0,*) break ;; *,-*) ;; *) break ;; esac
+do
+	case "$1" in
+	-d | -D)
+		delete_branch "$1" "$2"
+		exit
+		;;
+	--)
+		shift
+		break
+		;;
+	-*)
+		usage
+		;;
+	esac
+	shift
+done
+
 case "$#" in
 0)
 	headref=$(readlink "$GIT_DIR/HEAD" | sed -e 's|^refs/heads/||')
 esac
 branchname="$1"
 
-case "$branchname" in
--*)
-	usage;;
-esac
-
 rev=$(git-rev-parse --verify "$head") || exit
 
 [ -e "$GIT_DIR/refs/heads/$branchname" ] && die "$branchname already exists"
 		force=1
 		;;
 	*)
-		rev=$(git-rev-parse --verify "$arg^0") || exit
+		rev=$(git-rev-parse --verify "$arg^0" 2>/dev/null) ||
+			die "hey dummy, branch '$arg' doesn't exist."
 		if [ -z "$rev" ]; then
 			echo "unknown flag $arg"
 			exit 1
 *)
 	case "$repo" in
 	rsync://*)
-		rsync $quiet -avz --ignore-existing "$repo/objects/" "$D/.git/objects/" &&
-		rsync $quiet -avz --ignore-existing "$repo/refs/" "$D/.git/refs/"
+		rsync $quiet -av --ignore-existing  \
+			--exclude info "$repo/objects/" "$D/.git/objects/" &&
+		rsync $quiet -av --ignore-existing  \
+			--exclude info "$repo/refs/" "$D/.git/refs/" || exit
+
+		# Look at objects/info/alternates for rsync -- http will
+		# support it natively and git native ones will do it on the
+		# remote end.  Not having that file is not a crime.
+		rsync -q "$repo/objects/info/alternates" "$D/.git/TMP_ALT" ||
+			rm -f "$D/.git/TMP_ALT"
+		if test -f "$D/.git/TMP_ALT"
+		then
+		    ( cd $D &&
+		      . git-parse-remote &&
+		      resolve_alternates "$repo" <"./.git/TMP_ALT" ) |
+		    while read alt
+		    do
+			case "$alt" in 'bad alternate: '*) die "$alt";; esac
+			case "$quiet" in
+			'')	echo >&2 "Getting alternate: $alt" ;;
+			esac
+			rsync $quiet -av --ignore-existing  \
+			    --exclude info "$alt" "$D/.git/objects" || exit
+		    done
+		    rm -f "$D/.git/TMP_ALT"
+		fi
 		;;
 	http://*)
 		clone_dumb_http "$repo" "$D"
 # Pass --without docs to rpmbuild if you don't want the documetnation
 Name: 		git-core
 Version: 	@@VERSION@@
-Release: 	1
-Vendor: 	Junio C Hamano <junkio@cox.net>
+Release: 	1%{?dist}
 Summary:  	Git core and tools
 License: 	GPL
 Group: 		Development/Tools
 URL: 		http://kernel.org/pub/software/scm/git/
 Source: 	http://kernel.org/pub/software/scm/git/%{name}-%{version}.tar.gz
 BuildRequires:	zlib-devel, openssl-devel, curl-devel  %{!?_without_docs:, xmlto, asciidoc > 6.0.3}
-BuildRoot:	%{_tmppath}/%{name}-%{version}-root
-Requires: 	sh-utils, curl, diffutils, rsync, rcs, openssh-clients, perl, python >= 2.4, tk
+BuildRoot:	%{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
+Requires:	rsync, rcs, curl, less, openssh-clients, python >= 2.4, tk
 
 %description
 This is a stupid (but extremely fast) directory content manager.  It
 %setup -q
 
 %build
-make prefix=%{_prefix} all %{!?_without_docs: doc}
+make COPTS="$RPM_OPT_FLAGS" prefix=%{_prefix} all %{!?_without_docs: doc}
 
 %install
 rm -rf $RPM_BUILD_ROOT
 %files
 %defattr(-,root,root)
 %{_bindir}/*
-%{_datadir}/git-core/templates/*
+%{_datadir}/git-core/
 %doc README COPYING Documentation/*.txt
 %{!?_without_docs: %doc Documentation/*.html }
 %{!?_without_docs: %{_mandir}/man1/*.1*}
 %{!?_without_docs: %{_mandir}/man7/*.7*}
 
 %changelog
+* Fri Sep 16 2005 Chris Wright <chrisw@osdl.org> 0.99.6-1
+- update to 0.99.6
+
+* Fri Sep 16 2005 Horst H. von Brand <vonbrand@inf.utfsm.cl>
+- Linus noticed that less is required, added to the dependencies
+
 * Sun Sep 11 2005 Horst H. von Brand <vonbrand@inf.utfsm.cl>
 - Updated dependencies
 - Don't assume manpages are gzipped
 
+* Thu Aug 18 2005 Chris Wright <chrisw@osdl.org> 0.99.4-4
+- drop sh_utils, sh-utils, diffutils, mktemp, and openssl Requires
+- use RPM_OPT_FLAGS in spec file, drop patch0
+
+* Wed Aug 17 2005 Tom "spot" Callaway <tcallawa@redhat.com> 0.99.4-3
+- use dist tag to differentiate between branches
+- use rpm optflags by default (patch0)
+- own %{_datadir}/git-core/
+
+* Mon Aug 15 2005 Chris Wright <chrisw@osdl.org>
+- update spec file to fix Buildroot, Requires, and drop Vendor
+
 * Sun Aug 07 2005 Horst H. von Brand <vonbrand@inf.utfsm.cl>
 - Redid the description
 - Cut overlong make line, loosened changelog a bit
 	;;
     rsync://*)
 	TMP_HEAD="$GIT_DIR/TMP_HEAD"
-	rsync -L "$remote/$remote_name" "$TMP_HEAD" || exit 1
+	rsync -L -q "$remote/$remote_name" "$TMP_HEAD" || exit 1
 	head=$(git-rev-parse TMP_HEAD)
 	rm -f "$TMP_HEAD"
 	test "$rsync_slurped_objects" || {
-	    rsync -avz --ignore-existing "$remote/objects/" \
-		"$GIT_OBJECT_DIRECTORY/" || exit
+	    rsync -av --ignore-existing --exclude info \
+		"$remote/objects/" "$GIT_OBJECT_DIRECTORY/" || exit
+
+	    # Look at objects/info/alternates for rsync -- http will
+	    # support it natively and git native ones will do it on the remote
+	    # end.  Not having that file is not a crime.
+	    rsync -q "$remote/objects/info/alternates" "$GIT_DIR/TMP_ALT" ||
+		    rm -f "$GIT_DIR/TMP_ALT"
+	    if test -f "$GIT_DIR/TMP_ALT"
+	    then
+		resolve_alternates "$remote" <"$GIT_DIR/TMP_ALT" |
+		while read alt
+		do
+		    case "$alt" in 'bad alternate: '*) die "$alt";; esac
+		    echo >&2 "Getting alternate: $alt"
+		    rsync -av --ignore-existing --exclude info \
+		    "$alt" "$GIT_OBJECT_DIRECTORY/" || exit
+		done
+		rm -f "$GIT_DIR/TMP_ALT"
+	    fi
 	    rsync_slurped_objects=t
 	}
 	;;

git-parse-remote.sh

 	    ;;
 	esac
 }
+
+resolve_alternates () {
+	# original URL (xxx.git)
+	top_=`expr "$1" : '\([^:]*:/*[^/]*\)/'`
+	while read path
+	do
+		case "$path" in
+		\#* | '')
+			continue ;;
+		/*)
+			echo "$top_$path/" ;;
+		../*)
+			# relative -- ugly but seems to work.
+			echo "$1/objects/$path/" ;;
+		*)
+			# exit code may not be caught by the reader.
+			echo "bad alternate: $path"
+			exit 1 ;;
+		esac
+	done
+}
 fi
 
 merge_head=$(sed -e 's/	.*//' "$GIT_DIR"/FETCH_HEAD | tr '\012' ' ')
-merge_name=$(sed -e 's/^[0-9a-f]*	//' "$GIT_DIR"/FETCH_HEAD |
-	 tr '\012' ' ')
+merge_name=$(
+    perl -e 'print join("; ", map { chomp; s/^[0-9a-f]*	//; $_ } <>)' \
+    "$GIT_DIR"/FETCH_HEAD
+)
 
 case "$merge_head" in
 '')
 	# all-into-one is used.
 	if test "$all_into_one" != '' && test "$existing" != ''
 	then
-		( cd "$PACKDIR" && rm -f $existing )
+		( cd "$PACKDIR" &&
+		  for e in $existing
+		  do
+			case "$e" in
+			./pack-$name.pack | ./pack-$name.idx) ;;
+			*)	rm -f $e ;;
+			esac
+		  done
+		)
 	fi
 fi
 

git-shortlog.perl

 		if ($pstate == 1) {
 			my ($email);
 
-			next unless /^Author: (.*)<(.*)>.*$/;
+			next unless /^[Aa]uthor:? (.*)<(.*)>.*$/;
 	
 			$n_records++;
 	
 #endif
 
 static CURL *curl;
+static struct curl_slist *no_pragma_header;
 
-static char *base;
+static char *initial_base;
+
+struct alt_base
+{
+	char *base;
+	int got_indices;
+	struct packed_git *packs;
+	struct alt_base *next;
+};
+
+struct alt_base *alt = NULL;
 
 static SHA_CTX c;
 static z_stream stream;
 {
 }
 
-static int got_indices = 0;
-
-static struct packed_git *packs = NULL;
+static int got_alternates = 0;
 
-static int fetch_index(unsigned char *sha1)
+static int fetch_index(struct alt_base *repo, unsigned char *sha1)
 {
 	char *filename;
 	char *url;
 		fprintf(stderr, "Getting index for pack %s\n",
 			sha1_to_hex(sha1));
 	
-	url = xmalloc(strlen(base) + 64);
+	url = xmalloc(strlen(repo->base) + 64);
 	sprintf(url, "%s/objects/pack/pack-%s.idx",
-		base, sha1_to_hex(sha1));
+		repo->base, sha1_to_hex(sha1));
 	
 	filename = sha1_pack_index_name(sha1);
 	indexfile = fopen(filename, "w");
 	curl_easy_setopt(curl, CURLOPT_FILE, indexfile);
 	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
 	curl_easy_setopt(curl, CURLOPT_URL, url);
+	curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_pragma_header);
 	
 	if (curl_easy_perform(curl)) {
 		fclose(indexfile);
 	return 0;
 }
 
-static int setup_index(unsigned char *sha1)
+static int setup_index(struct alt_base *repo, unsigned char *sha1)
 {
 	struct packed_git *new_pack;
 	if (has_pack_file(sha1))
 		return 0; // don't list this as something we can get
 
-	if (fetch_index(sha1))
+	if (fetch_index(repo, sha1))
 		return -1;
 
 	new_pack = parse_pack_index(sha1);
-	new_pack->next = packs;
-	packs = new_pack;
+	new_pack->next = repo->packs;
+	repo->packs = new_pack;
 	return 0;
 }
 
-static int fetch_indices(void)
+static int fetch_alternates(char *base)
+{
+	int ret = 0;
+	struct buffer buffer;
+	char *url;
+	char *data;
+	int i = 0;
+	if (got_alternates)
+		return 0;
+	data = xmalloc(4096);
+	buffer.size = 4096;
+	buffer.posn = 0;
+	buffer.buffer = data;
+
+	if (get_verbosely)
+		fprintf(stderr, "Getting alternates list\n");
+	
+	url = xmalloc(strlen(base) + 31);
+	sprintf(url, "%s/objects/info/http-alternates", base);
+
+	curl_easy_setopt(curl, CURLOPT_FILE, &buffer);
+	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
+	curl_easy_setopt(curl, CURLOPT_URL, url);
+
+	if (curl_easy_perform(curl) || !buffer.posn) {
+		sprintf(url, "%s/objects/info/alternates", base);
+		
+		curl_easy_setopt(curl, CURLOPT_FILE, &buffer);
+		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
+		curl_easy_setopt(curl, CURLOPT_URL, url);
+		
+		if (curl_easy_perform(curl)) {
+			return 0;
+		}
+	}
+
+	while (i < buffer.posn) {
+		int posn = i;
+		while (posn < buffer.posn && data[posn] != '\n')
+			posn++;
+		if (data[posn] == '\n') {
+			if (data[i] == '/') {
+				int serverlen = strchr(base + 8, '/') - base;
+				// skip 'objects' at end
+				char *target = 
+					xmalloc(serverlen + posn - i - 6);
+				struct alt_base *newalt;
+				strncpy(target, base, serverlen);
+				strncpy(target + serverlen, data + i,
+					posn - i - 7);
+				target[serverlen + posn - i - 7] = '\0';
+				if (get_verbosely)
+					fprintf(stderr, 
+						"Also look at %s\n", target);
+				newalt = xmalloc(sizeof(*newalt));
+				newalt->next = alt;
+				newalt->base = target;
+				newalt->got_indices = 0;
+				newalt->packs = NULL;
+				alt = newalt;
+				ret++;
+			}
+		}
+		i = posn + 1;
+	}
+	got_alternates = 1;
+	
+	return ret;
+}
+
+static int fetch_indices(struct alt_base *repo)
 {
 	unsigned char sha1[20];
 	char *url;
 	char *data;
 	int i = 0;
 
-	if (got_indices)
+	if (repo->got_indices)
 		return 0;
 
 	data = xmalloc(4096);
 	if (get_verbosely)
 		fprintf(stderr, "Getting pack list\n");
 	
-	url = xmalloc(strlen(base) + 21);
-	sprintf(url, "%s/objects/info/packs", base);
+	url = xmalloc(strlen(repo->base) + 21);
+	sprintf(url, "%s/objects/info/packs", repo->base);
 
 	curl_easy_setopt(curl, CURLOPT_FILE, &buffer);
 	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
 	curl_easy_setopt(curl, CURLOPT_URL, url);
+	curl_easy_setopt(curl, CURLOPT_HTTPHEADER, NULL);
 	
 	if (curl_easy_perform(curl)) {
 		return error("Unable to get pack index %s", url);
 	}
 
-	do {
+	while (i < buffer.posn) {
 		switch (data[i]) {
 		case 'P':
 			i++;
 			    !strncmp(data + i, " pack-", 6) &&
 			    !strncmp(data + i + 46, ".pack\n", 6)) {
 				get_sha1_hex(data + i + 6, sha1);
-				setup_index(sha1);
+				setup_index(repo, sha1);
 				i += 51;
 				break;
 			}
 				i++;
 		}
 		i++;
-	} while (i < buffer.posn);
+	}
 
-	got_indices = 1;
+	repo->got_indices = 1;
 	return 0;
 }
 
-static int fetch_pack(unsigned char *sha1)
+static int fetch_pack(struct alt_base *repo, unsigned char *sha1)
 {
 	char *url;
 	struct packed_git *target;
 	FILE *packfile;
 	char *filename;
 
-	if (fetch_indices())
+	if (fetch_indices(repo))
 		return -1;
-	target = find_sha1_pack(sha1, packs);
+	target = find_sha1_pack(sha1, repo->packs);
 	if (!target)
-		return error("Couldn't get %s: not separate or in any pack",
-			     sha1_to_hex(sha1));
+		return -1;
 
 	if (get_verbosely) {
 		fprintf(stderr, "Getting pack %s\n",
 			sha1_to_hex(sha1));
 	}
 
-	url = xmalloc(strlen(base) + 65);
+	url = xmalloc(strlen(repo->base) + 65);
 	sprintf(url, "%s/objects/pack/pack-%s.pack",
-		base, sha1_to_hex(target->sha1));
+		repo->base, sha1_to_hex(target->sha1));
 
 	filename = sha1_pack_name(target->sha1);
 	packfile = fopen(filename, "w");
 	curl_easy_setopt(curl, CURLOPT_FILE, packfile);
 	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
 	curl_easy_setopt(curl, CURLOPT_URL, url);
+	curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_pragma_header);
 	
 	if (curl_easy_perform(curl)) {
 		fclose(packfile);
 
 	fclose(packfile);
 
-	lst = &packs;
+	lst = &repo->packs;
 	while (*lst != target)
 		lst = &((*lst)->next);
 	*lst = (*lst)->next;
 	return 0;
 }
 
-int fetch(unsigned char *sha1)
+int fetch_object(struct alt_base *repo, unsigned char *sha1)
 {
 	char *hex = sha1_to_hex(sha1);
 	char *filename = sha1_file_name(sha1);
 	curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
 	curl_easy_setopt(curl, CURLOPT_FILE, NULL);
 	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file);
+	curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_pragma_header);
 
-	url = xmalloc(strlen(base) + 50);
-	strcpy(url, base);
-	posn = url + strlen(base);
+	url = xmalloc(strlen(repo->base) + 50);
+	strcpy(url, repo->base);
+	posn = url + strlen(repo->base);
 	strcpy(posn, "objects/");
 	posn += 8;
 	memcpy(posn, hex, 2);
 
 	if (curl_easy_perform(curl)) {
 		unlink(filename);
-		if (fetch_pack(sha1))
-			return error("Tried %s", url);
-		return 0;
+		return -1;
 	}
 
 	close(local);
 	return 0;
 }
 
+int fetch(unsigned char *sha1)
+{
+	struct alt_base *altbase = alt;
+	while (altbase) {
+		if (!fetch_object(altbase, sha1))
+			return 0;
+		if (!fetch_pack(altbase, sha1))
+			return 0;
+		if (fetch_alternates(altbase->base) > 0) {
+			altbase = alt;
+			continue;
+		}
+		altbase = altbase->next;
+	}
+	return error("Unable to find %s under %s\n", sha1_to_hex(sha1), 
+		     initial_base);
+}
+
 int fetch_ref(char *ref, unsigned char *sha1)
 {
         char *url, *posn;
         char hex[42];
         struct buffer buffer;
+	char *base = initial_base;
         buffer.size = 41;
         buffer.posn = 0;
         buffer.buffer = hex;
         
         curl_easy_setopt(curl, CURLOPT_FILE, &buffer);
         curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
+	curl_easy_setopt(curl, CURLOPT_HTTPHEADER, NULL);
 
         url = xmalloc(strlen(base) + 6 + strlen(ref));
         strcpy(url, base);
 	curl_global_init(CURL_GLOBAL_ALL);
 
 	curl = curl_easy_init();
+	no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:");
 
 	curl_ssl_verify = getenv("GIT_SSL_NO_VERIFY") ? 0 : 1;
 	curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, curl_ssl_verify);
 	curl_easy_setopt(curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
 #endif
 
-	base = url;
+	alt = xmalloc(sizeof(*alt));
+	alt->base = url;
+	alt->got_indices = 0;
+	alt->packs = NULL;
+	alt->next = NULL;
+	initial_base = url;
 
 	if (pull(commit_id))
 		return 1;
 
+	curl_slist_free_all(no_pragma_header);
 	curl_global_cleanup();
 	return 0;
 }
 	memcpy(real_email, pw->pw_name, len);
 	real_email[len++] = '@';
 	gethostname(real_email + len, sizeof(real_email) - len);
-#ifndef NO_GETDOMAINNAME
 	if (!strchr(real_email+len, '.')) {
 		len = strlen(real_email);
 		real_email[len++] = '.';
 		getdomainname(real_email+len, sizeof(real_email)-len);
 	}
-#endif
 	/* And set the default date */
 	datestamp(real_date, sizeof(real_date));
 	return 0;
 int nr_objs;
 static int obj_allocs;
 
+int track_object_refs = 1;
+
 static int find_object(const unsigned char *sha1)
 {
 	int first = 0, last = nr_objs;
 
 void add_ref(struct object *refer, struct object *target)
 {
-	struct object_list **pp = &refer->refs;
-	struct object_list *p;
-	
+	struct object_list **pp, *p;
+
+	if (!track_object_refs)
+		return;
+
+	pp = &refer->refs;
 	while ((p = *pp) != NULL) {
 		if (p->item == target)
 			return;
 {
 	struct object_list *p = obj->refs;
 
+	if (!track_object_refs)
+		die("cannot do reachability with object refs turned off");
 	/* If we've been here already, don't bother */
 	if (obj->flags & mask)
 		return;
 	void *util;
 };
 
+extern int track_object_refs;
 extern int nr_objs;
 extern struct object **objs;
 

rev-cache.c

-#include "refs.h"
-#include "cache.h"
-#include "rev-cache.h"
-
-struct rev_cache **rev_cache;
-int nr_revs, alloc_revs;
-
-static struct rev_list_elem *rle_free;
-
-#define BATCH_SIZE 512
-
-int find_rev_cache(const unsigned char *sha1)
-{
-	int lo = 0, hi = nr_revs;
-	while (lo < hi) {
-		int mi = (lo + hi) / 2;
-		struct rev_cache *ri = rev_cache[mi];
-		int cmp = memcmp(sha1, ri->sha1, 20);
-		if (!cmp)
-			return mi;
-		if (cmp < 0)
-			hi = mi;
-		else
-			lo = mi + 1;
-	}
-	return -lo - 1;
-}
-
-static struct rev_list_elem *alloc_list_elem(void)
-{
-	struct rev_list_elem *rle;
-	if (!rle_free) {
-		int i;
-
-		rle = xmalloc(sizeof(*rle) * BATCH_SIZE);
-		for (i = 0; i < BATCH_SIZE - 1; i++) {
-			rle[i].ri = NULL;
-			rle[i].next = &rle[i + 1];
-		}
-		rle[BATCH_SIZE - 1].ri = NULL;
-		rle[BATCH_SIZE - 1].next = NULL;
-		rle_free = rle;
-	}
-	rle = rle_free;
-	rle_free = rle->next;
-	return rle;
-}
-
-static struct rev_cache *create_rev_cache(const unsigned char *sha1)
-{
-	struct rev_cache *ri;
-	int pos = find_rev_cache(sha1);
-
-	if (0 <= pos)
-		return rev_cache[pos];
-	pos = -pos - 1;
-	if (alloc_revs <= ++nr_revs) {
-		alloc_revs = alloc_nr(alloc_revs);
-		rev_cache = xrealloc(rev_cache, sizeof(ri) * alloc_revs);
-	}
-	if (pos < nr_revs)
-		memmove(rev_cache + pos + 1, rev_cache + pos,
-			(nr_revs - pos - 1) * sizeof(ri));
-	ri = xcalloc(1, sizeof(*ri));
-	memcpy(ri->sha1, sha1, 20);
-	rev_cache[pos] = ri;
-	return ri;
-}
-
-static unsigned char last_sha1[20];
-
-static void write_one_rev_cache(FILE *rev_cache_file, struct rev_cache *ri)
-{
-	unsigned char flag;
-	struct rev_list_elem *rle;
-
-	if (ri->written)
-		return;
-
-	if (ri->parsed) {
-		/* We use last_sha1 compression only for the first parent;
-		 * otherwise the resulting rev-cache would lose the parent
-		 * order information.
-		 */
-		if (ri->parents &&
-		    !memcmp(ri->parents->ri->sha1, last_sha1, 20))
-			flag = (ri->num_parents - 1) | 0x80;
-		else
-			flag = ri->num_parents;
-
-		fwrite(ri->sha1, 20, 1, rev_cache_file);
-		fwrite(&flag, 1, 1, rev_cache_file);
-		for (rle = ri->parents; rle; rle = rle->next) {
-			if (flag & 0x80 && rle == ri->parents)
-				continue;
-			fwrite(rle->ri->sha1, 20, 1, rev_cache_file);
-		}
-		memcpy(last_sha1, ri->sha1, 20);
-		ri->written = 1;
-	}
-	/* recursively write children depth first */
-	for (rle = ri->children; rle; rle = rle->next)
-		write_one_rev_cache(rev_cache_file, rle->ri);
-}
-
-void write_rev_cache(const char *newpath, const char *oldpath)
-{
-	/* write the following commit ancestry information in
-	 * $GIT_DIR/info/rev-cache.
-	 *
-	 * The format is:
-	 * 20-byte SHA1 (commit ID)
-	 * 1-byte flag:
-	 * - bit 0-6 records "number of parent commit SHA1s to
-	 *   follow" (i.e. up to 127 children can be listed).
-	 * - when the bit 7 is on, then "the entry immediately
-	 *   before this entry is one of the parents of this
-         *   commit".
-	 * N x 20-byte SHA1 (parent commit IDs)
-	 */
-	FILE *rev_cache_file;
-	int i;
-	struct rev_cache *ri;
-
-	if (!strcmp(newpath, oldpath)) {
-		/* If we are doing it in place */
-		rev_cache_file = fopen(newpath, "a");
-	}
-	else {
-		char buf[8096];
-		size_t sz;
-		FILE *oldfp = fopen(oldpath, "r");
-		rev_cache_file = fopen(newpath, "w");
-		if (oldfp) {
-			while (1) {
-				sz = fread(buf, 1, sizeof(buf), oldfp);
-				if (sz == 0)
-					break;
-				fwrite(buf, 1, sz, rev_cache_file);
-			}
-			fclose(oldfp);
-		}
-	}
-
-	memset(last_sha1, 0, 20);
-
-	/* Go through available rev_cache structures, starting from
-	 * parentless ones first, so that we would get most out of
-	 * last_sha1 optimization by the depth first behaviour of
-	 * write_one_rev_cache().
-	 */
-	for (i = 0; i < nr_revs; i++) {
-		ri = rev_cache[i];
-		if (ri->num_parents)
-			continue;
-		write_one_rev_cache(rev_cache_file, ri);
-	}
-	/* Then the rest */
-	for (i = 0; i < nr_revs; i++) {
-		ri = rev_cache[i];
-		write_one_rev_cache(rev_cache_file, ri);
-	}
-	fclose(rev_cache_file);
-}
-
-static void add_parent(struct rev_cache *child,
-		       const unsigned char *parent_sha1)
-{
-	struct rev_cache *parent = create_rev_cache(parent_sha1);
-	struct rev_list_elem *e = alloc_list_elem();
-
-	/* Keep the parent list ordered in the same way the commit
-	 * object records them.
-	 */
-	e->ri = parent;
-	e->next = NULL;
-	if (!child->parents_tail)
-		child->parents = e;
-	else
-		child->parents_tail->next = e;
-	child->parents_tail = e;
-	child->num_parents++;
-
-	/* There is no inherent order of the children so we just
-	 * LIFO them together.
-	 */
-	e = alloc_list_elem();
-	e->next = parent->children;
-	parent->children = e;
-	e->ri = child;
-	parent->num_children++;
-}
-
-int read_rev_cache(const char *path, FILE *dumpfile, int dry_run)
-{
-	unsigned char *map;
-	int fd;
-	struct stat st;
-	unsigned long ofs, len;
-	struct rev_cache *ri = NULL;
-
-	fd = open(path, O_RDONLY);
-	if (fd < 0) {
-		if (dry_run)
-			return error("cannot open %s", path);
-		if (errno == ENOENT)
-			return 0;
-		return -1;
-	}
-	if (fstat(fd, &st)) {
-		close(fd);
-		return -1;
-	}
-	map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
-	close(fd);
-	if (map == MAP_FAILED)
-		return -1;
-
-	memset(last_sha1, 0, 20);
-	ofs = 0;
-	len = st.st_size;
-	while (ofs < len) {
-		unsigned char sha1[20];
-		int flag, cnt, i;
-		if (len < ofs + 21)
-			die("rev-cache too short");
-		memcpy(sha1, map + ofs, 20);
-		flag = map[ofs + 20];
-		ofs += 21;
-		cnt = (flag & 0x7f) + ((flag & 0x80) != 0);
-		if (len < ofs + (flag & 0x7f) * 20)
-			die("rev-cache too short to have %d more parents",
-			    (flag & 0x7f));
-		if (dumpfile)
-			fprintf(dumpfile, "%s", sha1_to_hex(sha1));
-		if (!dry_run) {
-			ri = create_rev_cache(sha1);
-			if (!ri)
-				die("cannot create rev-cache for %s",
-				    sha1_to_hex(sha1));
-			ri->written = ri->parsed = 1;
-		}
-		i = 0;
-		if (flag & 0x80) {
-			if (!dry_run)
-				add_parent(ri, last_sha1);
-			if (dumpfile)
-				fprintf(dumpfile, " %s",
-					sha1_to_hex(last_sha1));
-			i++;
-		}
-		while (i++ < cnt) {
-			if (!dry_run)
-				add_parent(ri, map + ofs);
-			if (dumpfile)
-				fprintf(dumpfile, " %s",
-					sha1_to_hex(last_sha1));
-			ofs += 20;
-		}
-		if (dumpfile)
-			fprintf(dumpfile, "\n");
-		memcpy(last_sha1, sha1, 20);
-	}
-	if (ofs != len)
-		die("rev-cache truncated?");
-	munmap(map, len);
-	return 0;
-}
-
-int record_rev_cache(const unsigned char *sha1, FILE *dumpfile)
-{
-	unsigned char parent[20];
-	char type[20];
-	unsigned long size, ofs;
-	unsigned int cnt, i;
-	void *buf;
-	struct rev_cache *ri;
-
-	buf = read_sha1_file(sha1, type, &size);
-	if (!buf)
-		return error("%s: not found", sha1_to_hex(sha1));
-	if (strcmp(type, "commit")) {
-		free(buf);
-		return error("%s: not a commit but a %s",
-			     sha1_to_hex(sha1), type);
-	}
-	ri = create_rev_cache(sha1);
-	if (ri->parsed)
-		return 0;
-	if (dumpfile)
-		fprintf(dumpfile, "commit %s\n", sha1_to_hex(sha1));
-
-	cnt = 0;
-	ofs = 46; /* "tree " + hex-sha1 + "\n" */
-	while (!memcmp(buf + ofs, "parent ", 7) &&
-	       !get_sha1_hex(buf + ofs + 7, parent)) {
-		ofs += 48;
-		cnt++;
-	}
-	if (cnt * 48 + 46 != ofs) {
-		free(buf);
-		die("internal error in record_rev_cache");
-	}
-
-	ri = create_rev_cache(sha1);
-	ri->parsed = 1;
-
-	for (i = 0; i < cnt; i++) {
-		unsigned char parent_sha1[20];
-
-		ofs = 46 + i * 48 + 7;
-		get_sha1_hex(buf + ofs, parent_sha1);
-		add_parent(ri, parent_sha1);
-		record_rev_cache(parent_sha1, dumpfile);
-	}
-	free(buf);
-	return 0;
-}

rev-cache.h

-#ifndef REV_CACHE_H
-#define REV_CACHE_H
-
-extern struct rev_cache {
-	struct rev_cache *head_list;
-	struct rev_list_elem *children;
-	struct rev_list_elem *parents;
-	struct rev_list_elem *parents_tail;
-	unsigned short num_parents;
-	unsigned short num_children;
-	unsigned int written : 1;
-	unsigned int parsed : 1;
-	unsigned int work : 30;
-	void *work_ptr;
-	unsigned char sha1[20];
-} **rev_cache;
-extern int nr_revs, alloc_revs;
-
-struct rev_list_elem {
-	struct rev_list_elem *next;
-	struct rev_cache *ri;
-};
-
-extern int find_rev_cache(const unsigned char *);
-extern int read_rev_cache(const char *, FILE *, int);
-extern int record_rev_cache(const unsigned char *, FILE *);
-extern void write_rev_cache(const char *new, const char *old);
-
-#endif
 		die("bad tree object %s", sha1_to_hex(obj->sha1));
 	obj->flags |= SEEN;
 	p = add_object(obj, p, name);
-	for (entry = tree->entries ; entry ; entry = entry->next) {
+	entry = tree->entries;
+	tree->entries = NULL;
+	while (entry) {
+		struct tree_entry_list *next = entry->next;
 		if (entry->directory)
 			p = process_tree(entry->item.tree, p, entry->name);
 		else
 			p = process_blob(entry->item.blob, p, entry->name);
+		free(entry);
+		entry = next;
 	}
 	return p;
 }
 	if (parse_tree(tree) < 0)
 		die("bad tree %s", sha1_to_hex(obj->sha1));
 	entry = tree->entries;
+	tree->entries = NULL;
 	while (entry) {
+		struct tree_entry_list *next = entry->next;
 		if (entry->directory)
 			mark_tree_uninteresting(entry->item.tree);
 		else
 			mark_blob_uninteresting(entry->item.blob);
-		entry = entry->next;
+		free(entry);
+		entry = next;
 	}
 }
 
 {
 	struct commit_list *parents = commit->parents;
 
-	if (tree_objects)
-		mark_tree_uninteresting(commit->tree);
 	while (parents) {
 		struct commit *commit = parents->item;
 		commit->object.flags |= UNINTERESTING;
 			continue;
 		return 0;
 	}
-
-	/*
-	 * Ok, go back and mark all the edge trees uninteresting,
-	 * since otherwise we can have situations where a parent
-	 * that was marked uninteresting (and we never even had
-	 * to look at) had lots of objects that we don't want to
-	 * include.
-	 *
-	 * NOTE! This still doesn't mean that the object list is
-	 * "correct", since we may end up listing objects that
-	 * even older commits (that we don't list) do actually
-	 * reference, but it gets us to a minimal list (or very
-	 * close) in practice.
-	 */
-	if (!tree_objects)
-		return 1;
-
-	while (orig) {
-		struct commit *commit = orig->item;
-		if (!parse_commit(commit) && commit->tree)
-			mark_tree_uninteresting(commit->tree);
-		orig = orig->next;
-	}
 	return 1;
 }
 
 	return best;
 }
 
+static void mark_edges_uninteresting(struct commit_list *list)
+{
+	for ( ; list; list = list->next) {
+		struct commit_list *parents = list->item->parents;
+
+		for ( ; parents; parents = parents->next) {
+			struct commit *commit = parents->item;
+			if (commit->object.flags & UNINTERESTING)
+				mark_tree_uninteresting(commit->tree);
+		}
+	}
+}
+
 static struct commit_list *limit_list(struct commit_list *list)
 {
 	struct commit_list *newlist = NULL;
 		}
 		p = &commit_list_insert(commit, p)->next;
 	}
+	if (tree_objects)
+		mark_edges_uninteresting(newlist);
 	if (bisect_list)
 		newlist = find_bisection(newlist);
 	return newlist;
 			struct commit *exclude = NULL;
 			struct commit *include = NULL;
 			*dotdot = 0;
+			if (!*next)
+				next = "HEAD";
 			exclude = get_commit_reference(arg, UNINTERESTING);
 			include = get_commit_reference(next, 0);
 			if (exclude && include) {
 				handle_one_commit(include, &list);
 				continue;
 			}
-			*next = '.';
+			*dotdot = '.';
 		}
 		if (*arg == '^') {
 			flags = UNINTERESTING;
 		handle_one_commit(commit, &list);
 	}
 
+	save_commit_buffer = verbose_header;
+	track_object_refs = 0;
+
 	if (!merge_order) {		
 		sort_by_date(&list);
 	        if (limited)
 
 #define COMMAND_SIZE 4096
 
+/*
+ * Write a shell-quoted version of a string into a buffer, and
+ * return bytes that ought to be output excluding final null.
+ */
+static int shell_quote(char *buf, int nmax, const char *str)
+{
+	char ch;
+	int nq;
+	int oc = 0;
+
+	while ( (ch = *str++) ) {
+		nq = 0;
+		if ( strchr(" !\"#$%&\'()*;<=>?[\\]^`{|}", ch) )
+			nq = 1;
+
+		if ( nq ) {
+			if ( nmax > 1 ) {
+				*buf++ = '\\';
+				nmax--;
+			}
+			oc++;
+		}
+
+		if ( nmax > 1 ) {
+			*buf++ = ch;
+			nmax--;
+		}
+		oc++;
+	}
+
+	if ( nmax )
+		*buf = '\0';
+
+	return oc;
+}
+			
+/*
+ * Append a string to a string buffer, with or without quoting.  Return true
+ * if the buffer overflowed.
+ */
+static int add_to_string(char **ptrp, int *sizep, const char *str, int quote)
+{
+	char *p = *ptrp;
+	int size = *sizep;
+	int oc;
+
+	if ( quote ) {
+		oc = shell_quote(p, size, str);
+	} else {
+		oc = strlen(str);
+		memcpy(p, str, (oc >= size) ? size-1 : oc);
+	}
+
+	if ( oc >= size ) {
+		p[size-1] = '\0';
+		*ptrp += size-1;
+		*sizep = 1;
+		return 1;	/* Overflow, string unusable */
+	}
+
+	*ptrp  += oc;
+	*sizep -= oc;
+	return 0;
+}
+
 int setup_connection(int *fd_in, int *fd_out, const char *remote_prog, 
 		     char *url, int rmt_argc, char **rmt_argv)
 {
 	int sv[2];
 	char command[COMMAND_SIZE];
 	char *posn;
+	int sizen;
+	int of;
 	int i;
 
 	if (!strcmp(url, "-")) {
 	if (!path) {
 		return error("Bad URL: %s", url);
 	}
-	/* ssh <host> 'cd <path>; stdio-pull <arg...> <commit-id>' */
-	snprintf(command, COMMAND_SIZE, 
-		 "%s='%s' %s",
-		 GIT_DIR_ENVIRONMENT, path, remote_prog);
-	*path = '\0';
-	posn = command + strlen(command);
-	for (i = 0; i < rmt_argc; i++) {
-		*(posn++) = ' ';
-		strncpy(posn, rmt_argv[i], COMMAND_SIZE - (posn - command));
-		posn += strlen(rmt_argv[i]);
-		if (posn - command + 4 >= COMMAND_SIZE) {
-			return error("Command line too long");
-		}
+	/* $GIT_RSH <host> "env GIR_DIR=<path> <remote_prog> <args...>" */
+	sizen = COMMAND_SIZE;
+	posn = command;
+	of = 0;
+	of |= add_to_string(&posn, &sizen, "env ", 0);
+	of |= add_to_string(&posn, &sizen, GIT_DIR_ENVIRONMENT, 0);
+	of |= add_to_string(&posn, &sizen, "=", 0);
+	of |= add_to_string(&posn, &sizen, path, 1);
+	of |= add_to_string(&posn, &sizen, " ", 0);
+	of |= add_to_string(&posn, &sizen, remote_prog, 1);
+
+	for ( i = 0 ; i < rmt_argc ; i++ ) {
+		of |= add_to_string(&posn, &sizen, " ", 0);
+		of |= add_to_string(&posn, &sizen, rmt_argv[i], 1);
 	}
-	strcpy(posn, " -");
-	if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv)) {
+
+	of |= add_to_string(&posn, &sizen, " -", 0);
+
+	if ( of )
+		return error("Command line too long");
+
+	if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv))
 		return error("Couldn't create socket");
-	}
+
 	if (!fork()) {
 		const char *ssh, *ssh_basename;
 		ssh = getenv("GIT_SSH");
 #include "object.h"
 #include "commit.h"
 #include "tag.h"
-#include "rev-cache.h"