Git development
 help / color / mirror / Atom feed
* Re: Start deprecating "git-command" in favor of "git command"
From: Junio C Hamano @ 2007-07-03  3:50 UTC (permalink / raw)
  To: Nicolas Pitre; +Cc: Linus Torvalds, Yann Dirson, Git Mailing List
In-Reply-To: <alpine.LFD.0.999.0707022253580.26459@xanadu.home>

Nicolas Pitre <nico@cam.org> writes:

> On Sat, 30 Jun 2007, Junio C Hamano wrote:
>
>> Your patch _closes the door_ for us to implement overriding
>> aliases later if we wanted to; we would need to go back to the
>> scripts and say "git --no-alias xyzzy" again.
>
> I think such aliases would be evil.  Closing the door on them is most 
> certainly a good thing IMHO.

Ok.

Now my 'next' is coming very close to 'master' while preparing
for 1.5.3-rc0, it may be a good time to apply Linus's magic.

^ permalink raw reply

* Re: being nice to patch(1)
From: Junio C Hamano @ 2007-07-03  4:00 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: Andrew Morton, git, quilt-dev
In-Reply-To: <alpine.LFD.0.98.0707021713200.9434@woody.linux-foundation.org>

Linus Torvalds <torvalds@linux-foundation.org> writes:

> So I would suggest that in quilt and other systems, you either:
>
>  - strip all headers manually
>
>  - forget about "patch", and use "git-apply" instead that does things 
>    right and doesn't screw up like this (and can do rename diffs etc too).
>
> I guess the second choice generally isn't an option, but dammit, 
> "git-apply" really is the better program here.

Why not?  git-apply works outside of a git repo ;-)

^ permalink raw reply

* Re: git-rm isn't the inverse action of git-add
From: Jeff King @ 2007-07-03  4:12 UTC (permalink / raw)
  To: Christian Jaeger; +Cc: Yann Dirson, Johannes Schindelin, git
In-Reply-To: <46896C3B.1050406@jaeger.mine.nu>

On Mon, Jul 02, 2007 at 11:20:59PM +0200, Christian Jaeger wrote:

> So there's no -f flag in normal git-rm usage. It thus has a different
> meaning, namely "force the operation pair of removing from index and
> unlinking", not "force this operation also onto the checked out files"
> as is the case with cogito.

Yes, git-rm is used in several situations, and the idea is that it
should behave safely in all situations; that is, without -f you can't
delete any data that can't be recovered from your history (but maybe
that means we shouldn't suggest -f in so cavalier a fashion).

Each file has three "states" that we care about: in the HEAD (H), in the
index (I), and in the working tree (W). Let's say you call 'git-rm'.
Here's a table of possibilities (A is some content "A", B is some
content not equal to "A", and N is "non-existant"):

H I W | ok? | why?
---------------------------------------------------
* N * | no  | file is not tracked
N A N | ?   | currently ok, but 'A' recoverable only through fsck
N A A | no  | 'A' recoverable only through fsck
N A B | no  | local modification 'B' would be lost
A A N | yes | 'A' recoverable through history
A A A | yes | 'A' recoverable through history
A A B | no  | local modification 'B' would be lost
A B N | ?   | currently ok, but 'B' recoverable only through fsck
A B A | no  | 'B' recoverable only through fsck
A B B | no  | 'B' recoverable only through fsck
B * * |     | equivalent to H=A

With --cached on, it is a little different:

H I W | ok? | why?
---------------------------------------------------
* N * | no  | file is not tracked
N A N |  ?  | currently ok, but 'A' recoverable only through fsck
N A A |  ?  | currently not ok, but 'A' still available in W
N A B | no  | 'A' recoverable only through fsck
A A N | yes | 'A' recoverable through history
A A A | yes | 'A' recoverable through history or working tree
A A B |  ?  | currently not ok, but 'A' still available in H
A B N |  ?  | currently ok, but 'B' recoverable only through fsck
A B A | no  | 'B' recoverable only through fsck
A B B |  ?  | currently not ok, but 'B' still available in W
B * * |     | equivalent to H=A

So it looks like our safety valve is a bit overbearing in a few
situations, and still misses some situations where data has to be pulled
out of the database with git-fsck.

I think if we actually spell out these possible states in the code, we
can get more accurate behavior, but also more accurate error messages. I
will try to work up a patch.

-Peff

^ permalink raw reply

* Re: being nice to patch(1)
From: Linus Torvalds @ 2007-07-03  4:14 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Andrew Morton, git, quilt-dev
In-Reply-To: <7vhcomuofl.fsf@assigned-by-dhcp.cox.net>



On Mon, 2 Jul 2007, Junio C Hamano wrote:
> Linus Torvalds <torvalds@linux-foundation.org> writes:
> 
> > So I would suggest that in quilt and other systems, you either:
> >
> >  - strip all headers manually
> >
> >  - forget about "patch", and use "git-apply" instead that does things 
> >    right and doesn't screw up like this (and can do rename diffs etc too).
> >
> > I guess the second choice generally isn't an option, but dammit, 
> > "git-apply" really is the better program here.
> 
> Why not?  git-apply works outside of a git repo ;-)

I was more thinking that people are not necessarily willing to install git 
just to get the "git-apply" program..

		Linus

^ permalink raw reply

* Re: git-fetch will leave a ref pointing to a tag
From: Jeff King @ 2007-07-03  4:18 UTC (permalink / raw)
  To: linux; +Cc: Johannes.Schindelin, git
In-Reply-To: <20070703032315.7279.qmail@science.horizon.com>

On Mon, Jul 02, 2007 at 11:23:15PM -0400, linux@horizon.com wrote:

> And until git-merge-ff is available, what's the recommended way to
> "advance master to tag <foo>, but only if that wouldn't lose anything?"

You can ask "do I have anything that foo doesn't?":

  test "`git-rev-list foo.. | wc -l`" -gt 0

-Peff

^ permalink raw reply

* Re: [PATCH] Teach git-stash to "apply --index"
From: Junio C Hamano @ 2007-07-03  4:33 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git
In-Reply-To: <Pine.LNX.4.64.0707021213350.4438@racer.site>

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

> 	I am not quite sure if this should not be the default, with
> 	--skip-index to turn it off if the trivial index merge fails,
> 	and the user might be interested only in the working directory
> 	changes anyway.
>
> 	Comments?

There is a bit of impedance mismatch between a rename-aware
three-way merge (aka merge-recursive) used to update the working
tree and a patch that updates the index.  The "rename-aware"
thing can be fixed by doing the diff with -M, though.

It might be easier to explain (1) not have --index option, but
attempt to do this always, and (2) even if the index cannot be
updated, keep going, without worrying about losing the
difference between I and W, probably with a message to the end
user.

Suppose you are the user who gets "perhaps you would want to run
without --index?" hint.  What can you do?  Run without --index,
or forget about unstashing.  But the latter does not sound like
an option.  So it feels to me that --index is an unnecessary
option.

The potential confusion problem I mentioned in two of my earlier
messages due to the program doing one thing sometimes and
another thing some other times still applies, though.

^ permalink raw reply

* Re: pushing a project started in git back to a subversion repository...
From: Jeff King @ 2007-07-03  4:37 UTC (permalink / raw)
  To: Patrick Doyle; +Cc: git
In-Reply-To: <e2a1d0aa0707021329p29242393ua10817c85ca7422a@mail.gmail.com>

On Mon, Jul 02, 2007 at 04:29:11PM -0400, Patrick Doyle wrote:

> Here is what I did:
> 1) packed up the existing git repository with "git-pack-objects"
> [...]
> 2) Used git-rev-list to find the name of first and last commits in my repo.
> [...]
> 3) Cloned the SVN repository, which I had previously created with an
> empty directory into which I wanted to import my new project
> [...]
> 4) unpacked the packfile into the new .git repo
> [...]

That's a bit complex. How about:
  1) clone the svn repository as in your step 3
  2) fetch the old master into a local branch

     git-fetch ../path/to/original master:original

> 5) Created a .git/info/grafts file with the first commit from my old
> .git repo grafted onto the single (empty directory) commit of this new
> repo
>
> $ echo XXX YYY > .git/info/grafts

So you are grafting onto a totally uninteresting commit? That seems like
a hack to base your commits on something that is "in" git-svn. I suspect
there is a way to do it more elegantly, but I've never used git-svn, so
I can't comment.

You could of course now do the graft as:

  echo "`git-rev-parse original` `git-rev-parse master`" >.git/info/grafts

> 6) Here's the scary part -- I overwrote .git/refs/heads/master with
> [...]
> 7) Fetched the files into my empy directory

You could also do this (while on the master branch):

  git reset --hard original

which will set master's sha1 to the same as your "original" branch, and
update the working tree to have all of the files from that commit
(because we used --hard).

At this point you can clean up the original branch:

  git-branch -d original

> **** 2nd question: ****
> How many of you cringed when I said I did this?  If so, why?

I cringed a little. It's actually not all that different than what
git-reset does, but git-reset will do nice things like locking and
putting an entry in the reflog.

> **** Last question: ****
> What happened to all of those objects I imported from the pack file?

They were placed in the second repository's object database.

> I can still see them in the .git/objects/*/* directories, but they
> don't show up in git-ls-revs.  git-fsck doesn't report any errors.

There is no git-ls-revs. Not sure what you mean here. There are no
errors because all of the objects are referenced (by your new master
branch).

> I'm also curious what experts would think of my fumbling efforts to
> transfer this git-managed project into a git/svn-managed project,
> maintaining the initial history.  Did I go tremendously out of my way
> to do something that was a one-line command?

More or less. ;)

Presumably you could take your _existing_ repository, point it at your
svn upstream (look in the .git/config of your svn-cloned repo, and there
is presumably some svn config), and do the dcommit. Unfortunately, I
have the privilege of never having used svn or git-svn, so I can't
comment further.

-Peff

^ permalink raw reply

* Re: git-rm isn't the inverse action of git-add
From: Junio C Hamano @ 2007-07-03  4:47 UTC (permalink / raw)
  To: Jeff King; +Cc: Christian Jaeger, Yann Dirson, Johannes Schindelin, git
In-Reply-To: <20070703041241.GA4007@coredump.intra.peff.net>

Jeff King <peff@peff.net> writes:

> H I W | ok? | why?
> ---------------------------------------------------
> N A N | ?   | currently ok, but 'A' recoverable only through fsck
> A B N | ?   | currently ok, but 'B' recoverable only through fsck

These were explicitly done per request from git-rm users (myself
not one of them) who wanted to:

	rm the-file
        git rm the-file

sequence not to barf.  I suspect they were from CVS background
who are used to the SCM that complains if you still have the
file in the working tree when you say "scm rm".

I would not mind requiring -f for these cases.

> With --cached on, it is a little different:
>
> H I W | ok? | why?
> ---------------------------------------------------
> N A N |  ?  | currently ok, but 'A' recoverable only through fsck
> N A A |  ?  | currently not ok, but 'A' still available in W
> A A B |  ?  | currently not ok, but 'A' still available in H
> A B N |  ?  | currently ok, but 'B' recoverable only through fsck
> A B B |  ?  | currently not ok, but 'B' still available in W

I personally do not think we would need any safety check for
"git rm --cached", as it does not touch the working tree.  If
one cares about the differences among three states, one would
not issue "rm --cached" anyway.  The only reason "rm --cached"
is used is because one _knows_ that any blob should not exist at
that path in the index.

^ permalink raw reply

* Re: [PATCH] git-repack: generational repacking (and example hook script)
From: Sam Vilain @ 2007-07-03  4:58 UTC (permalink / raw)
  To: Nicolas Pitre; +Cc: Junio C Hamano, git
In-Reply-To: <alpine.LFD.0.999.0707022331080.26459@xanadu.home>

Nicolas Pitre wrote:
>> Add an option to git-repack that makes the repack run suitable for
>> running very often.  The idea is that packs get given a "generation",
>> and that the number of packs in each generation (except the last one)
>> is bounded.
> 
> Please explain again why this should be useful and is worth the 
> complexity it brings along.  Last time this was discussed I wasn't 
> convinced at all, and I'm still not convinced this time either.

First I think we should establish some common ground.

1. Do you agree that some users would want their git repositories to be
"maintenance free"?

2. Do you agree that having thousands of packs would add measurable
overhead?

Sam.

^ permalink raw reply

* Re: git-rm isn't the inverse action of git-add
From: Jeff King @ 2007-07-03  4:59 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Christian Jaeger, Yann Dirson, Johannes Schindelin, git
In-Reply-To: <7vhcomt7oa.fsf@assigned-by-dhcp.cox.net>

On Mon, Jul 02, 2007 at 09:47:33PM -0700, Junio C Hamano wrote:

> These were explicitly done per request from git-rm users (myself
> not one of them) who wanted to:
> 
> 	rm the-file
>         git rm the-file

Ah, makes sense (if such a thing can be said about CVS behavior).

> > H I W | ok? | why?
> > ---------------------------------------------------
> > N A N |  ?  | currently ok, but 'A' recoverable only through fsck
> > N A A |  ?  | currently not ok, but 'A' still available in W
> > A A B |  ?  | currently not ok, but 'A' still available in H
> > A B N |  ?  | currently ok, but 'B' recoverable only through fsck
> > A B B |  ?  | currently not ok, but 'B' still available in W
> 
> I personally do not think we would need any safety check for
> "git rm --cached", as it does not touch the working tree.  If

It depends on how we want to define "lost" data. In many cases, we are
protecting against losing content that will still be available until the
next git-prune. Should our safety valve protect against that case, or
should it not? We are totally inconsistent.

The main one for --cached, of course, is when that content exists _only_
in the index, but no longer in the working tree (!A A N or !A A B).  You
really should be using regular git-rm (in the first case, since you are
saying "I don't want this file anymore") or git-add (throw out the old
data, use my new version).

OTOH, clearly git-add can "lose" data in this way as well, since a
"modify, git-add, modify, git-add" will "lose" any reference to the
index state after the first add. So maybe that is not worth worrying
about at all (in which case our safety valve is too strict in many
places).

We could also issue a warning when "losing" reference to data that is in
the object db, which would include the sha1; in that case, an immediate
"oops" could be rectified with git-show.

> one cares about the differences among three states, one would
> not issue "rm --cached" anyway.  The only reason "rm --cached"
> is used is because one _knows_ that any blob should not exist at
> that path in the index.

How about:

  git-add foo
  echo changes >>foo
  # oops, I don't want to commit foo just yet
  git-rm --cached foo

but in that case, maybe the user doesn't actually _care_ about that
intermediate state of 'foo'.

-Peff

^ permalink raw reply

* Re: git-rm isn't the inverse action of git-add
From: Junio C Hamano @ 2007-07-03  5:09 UTC (permalink / raw)
  To: Jeff King; +Cc: Christian Jaeger, Yann Dirson, Johannes Schindelin, git
In-Reply-To: <20070703045948.GE4007@coredump.intra.peff.net>

Jeff King <peff@peff.net> writes:

> OTOH, clearly git-add can "lose" data in this way as well, since a
> "modify, git-add, modify, git-add" will "lose" any reference to the
> index state after the first add. So maybe that is not worth worrying
> about at all (in which case our safety valve is too strict in many
> places).

Exactly.  And not considering that lossage helps us keep our
sanity.  I think "git rm --cached" falls into the same
category.  If the user wants to discard what is in the index
without losing a copy in the working tree, I think we should let
him do without fuss.

>   git-add foo
>   echo changes >>foo
>   # oops, I don't want to commit foo just yet
>   git-rm --cached foo
>
> but in that case, maybe the user doesn't actually _care_ about that
> intermediate state of 'foo'.

Yes, that is (at least, "used to be") exactly the use case "rm
--cached" is supposed to help.  Added something prematurely to
the index, not ready to commit that part of the changes yet.
Of course you could do partial commits with "add --interactive"
these days, so there is not as much need for this as it used to
be anymore.

^ permalink raw reply

* Re: git-rm isn't the inverse action of git-add
From: Jeff King @ 2007-07-03  5:12 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Christian Jaeger, Yann Dirson, Johannes Schindelin, git
In-Reply-To: <7v4pkmt6nk.fsf@assigned-by-dhcp.cox.net>

On Mon, Jul 02, 2007 at 10:09:35PM -0700, Junio C Hamano wrote:

> Exactly.  And not considering that lossage helps us keep our
> sanity.  I think "git rm --cached" falls into the same
> category.  If the user wants to discard what is in the index
> without losing a copy in the working tree, I think we should let
> him do without fuss.

OK. So should we _remove_ the safety valve in all cases where we're just
losing stuff that's in the index? It is, after all, recoverable. Should
there be a warning (I suspect it would get annoying very quickly)?

I think this would help by making the use of '-f' more rare, which is
the thing that can _really_ screw you, since it turns off the safety
valve even for things that aren't recoverable.

-Peff

^ permalink raw reply

* [PATCH 1/2] Remove obsolete commit-walkers
From: Daniel Barkalow @ 2007-07-03  5:28 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Removes the commit-walkers that are no longer useful, as well as
library code that was only used by ssh-fetch/push.

Signed-off-by: Daniel Barkalow <barkalow@iabervon.org>
---
 Makefile      |   20 +----
 local-fetch.c |  254 ---------------------------------------------------------
 rsh.c         |   83 -------------------
 rsh.h         |    7 --
 ssh-fetch.c   |  166 -------------------------------------
 ssh-pull.c    |    4 -
 ssh-push.c    |    4 -
 ssh-upload.c  |  143 --------------------------------
 8 files changed, 4 insertions(+), 677 deletions(-)
 delete mode 100644 local-fetch.c
 delete mode 100644 rsh.c
 delete mode 100644 rsh.h
 delete mode 100644 ssh-fetch.c
 delete mode 100644 ssh-pull.c
 delete mode 100644 ssh-push.c
 delete mode 100644 ssh-upload.c

diff --git a/Makefile b/Makefile
index 4ea5e45..705dde5 100644
--- a/Makefile
+++ b/Makefile
@@ -239,14 +239,14 @@ endif
 # ... and all the rest that could be moved out of bindir to gitexecdir
 PROGRAMS = \
 	git-convert-objects$X git-fetch-pack$X \
-	git-hash-object$X git-index-pack$X git-local-fetch$X \
+	git-hash-object$X git-index-pack$X \
 	git-fast-import$X \
 	git-daemon$X \
 	git-merge-index$X git-mktag$X git-mktree$X git-patch-id$X \
 	git-peek-remote$X git-receive-pack$X \
 	git-send-pack$X git-shell$X \
-	git-show-index$X git-ssh-fetch$X \
-	git-ssh-upload$X git-unpack-file$X \
+	git-show-index$X \
+	git-unpack-file$X \
 	git-update-server-info$X \
 	git-upload-pack$X \
 	git-pack-redundant$X git-var$X \
@@ -274,9 +274,6 @@ ifndef NO_TCLTK
 OTHER_PROGRAMS += gitk-wish
 endif
 
-# Backward compatibility -- to be removed after 1.0
-PROGRAMS += git-ssh-pull$X git-ssh-push$X
-
 # Set paths to tools early so that they can be used for version tests.
 ifndef SHELL_PATH
 	SHELL_PATH = /bin/sh
@@ -884,14 +881,6 @@ endif
 git-%$X: %.o $(GITLIBS)
 	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
 
-ssh-pull.o: ssh-fetch.c
-ssh-push.o: ssh-upload.c
-git-local-fetch$X: fetch.o
-git-ssh-fetch$X: rsh.o fetch.o
-git-ssh-upload$X: rsh.o
-git-ssh-pull$X: rsh.o fetch.o
-git-ssh-push$X: rsh.o
-
 git-imap-send$X: imap-send.o $(LIB_FILE)
 
 http.o http-fetch.o http-push.o: http.h
@@ -1103,8 +1092,7 @@ check-docs::
 		git-merge-octopus | git-merge-ours | git-merge-recursive | \
 		git-merge-resolve | git-merge-stupid | \
 		git-add--interactive | git-fsck-objects | git-init-db | \
-		git-repo-config | git-fetch--tool | \
-		git-ssh-pull | git-ssh-push ) continue ;; \
+		git-repo-config | git-fetch--tool ) continue ;; \
 		esac ; \
 		test -f "Documentation/$$v.txt" || \
 		echo "no doc: $$v"; \
diff --git a/local-fetch.c b/local-fetch.c
deleted file mode 100644
index bf7ec6c..0000000
--- a/local-fetch.c
+++ /dev/null
@@ -1,254 +0,0 @@
-/*
- * Copyright (C) 2005 Junio C Hamano
- */
-#include "cache.h"
-#include "commit.h"
-#include "fetch.h"
-
-static int use_link;
-static int use_symlink;
-static int use_filecopy = 1;
-static int commits_on_stdin;
-
-static const char *path; /* "Remote" git repository */
-
-void prefetch(unsigned char *sha1)
-{
-}
-
-static struct packed_git *packs;
-
-static void setup_index(unsigned char *sha1)
-{
-	struct packed_git *new_pack;
-	char filename[PATH_MAX];
-	strcpy(filename, path);
-	strcat(filename, "/objects/pack/pack-");
-	strcat(filename, sha1_to_hex(sha1));
-	strcat(filename, ".idx");
-	new_pack = parse_pack_index_file(sha1, filename);
-	new_pack->next = packs;
-	packs = new_pack;
-}
-
-static int setup_indices(void)
-{
-	DIR *dir;
-	struct dirent *de;
-	char filename[PATH_MAX];
-	unsigned char sha1[20];
-	sprintf(filename, "%s/objects/pack/", path);
-	dir = opendir(filename);
-	if (!dir)
-		return -1;
-	while ((de = readdir(dir)) != NULL) {
-		int namelen = strlen(de->d_name);
-		if (namelen != 50 ||
-		    !has_extension(de->d_name, ".pack"))
-			continue;
-		get_sha1_hex(de->d_name + 5, sha1);
-		setup_index(sha1);
-	}
-	closedir(dir);
-	return 0;
-}
-
-static int copy_file(const char *source, char *dest, const char *hex,
-		     int warn_if_not_exists)
-{
-	safe_create_leading_directories(dest);
-	if (use_link) {
-		if (!link(source, dest)) {
-			pull_say("link %s\n", hex);
-			return 0;
-		}
-		/* If we got ENOENT there is no point continuing. */
-		if (errno == ENOENT) {
-			if (!warn_if_not_exists)
-				return -1;
-			return error("does not exist %s", source);
-		}
-	}
-	if (use_symlink) {
-		struct stat st;
-		if (stat(source, &st)) {
-			if (!warn_if_not_exists && errno == ENOENT)
-				return -1;
-			return error("cannot stat %s: %s", source,
-				     strerror(errno));
-		}
-		if (!symlink(source, dest)) {
-			pull_say("symlink %s\n", hex);
-			return 0;
-		}
-	}
-	if (use_filecopy) {
-		int ifd, ofd, status = 0;
-
-		ifd = open(source, O_RDONLY);
-		if (ifd < 0) {
-			if (!warn_if_not_exists && errno == ENOENT)
-				return -1;
-			return error("cannot open %s", source);
-		}
-		ofd = open(dest, O_WRONLY | O_CREAT | O_EXCL, 0666);
-		if (ofd < 0) {
-			close(ifd);
-			return error("cannot open %s", dest);
-		}
-		status = copy_fd(ifd, ofd);
-		close(ofd);
-		if (status)
-			return error("cannot write %s", dest);
-		pull_say("copy %s\n", hex);
-		return 0;
-	}
-	return error("failed to copy %s with given copy methods.", hex);
-}
-
-static int fetch_pack(const unsigned char *sha1)
-{
-	struct packed_git *target;
-	char filename[PATH_MAX];
-	if (setup_indices())
-		return -1;
-	target = find_sha1_pack(sha1, packs);
-	if (!target)
-		return error("Couldn't find %s: not separate or in any pack",
-			     sha1_to_hex(sha1));
-	if (get_verbosely) {
-		fprintf(stderr, "Getting pack %s\n",
-			sha1_to_hex(target->sha1));
-		fprintf(stderr, " which contains %s\n",
-			sha1_to_hex(sha1));
-	}
-	sprintf(filename, "%s/objects/pack/pack-%s.pack",
-		path, sha1_to_hex(target->sha1));
-	copy_file(filename, sha1_pack_name(target->sha1),
-		  sha1_to_hex(target->sha1), 1);
-	sprintf(filename, "%s/objects/pack/pack-%s.idx",
-		path, sha1_to_hex(target->sha1));
-	copy_file(filename, sha1_pack_index_name(target->sha1),
-		  sha1_to_hex(target->sha1), 1);
-	install_packed_git(target);
-	return 0;
-}
-
-static int fetch_file(const unsigned char *sha1)
-{
-	static int object_name_start = -1;
-	static char filename[PATH_MAX];
-	char *hex = sha1_to_hex(sha1);
-	char *dest_filename = sha1_file_name(sha1);
-
-	if (object_name_start < 0) {
-		strcpy(filename, path); /* e.g. git.git */
-		strcat(filename, "/objects/");
-		object_name_start = strlen(filename);
-	}
-	filename[object_name_start+0] = hex[0];
-	filename[object_name_start+1] = hex[1];
-	filename[object_name_start+2] = '/';
-	strcpy(filename + object_name_start + 3, hex + 2);
-	return copy_file(filename, dest_filename, hex, 0);
-}
-
-int fetch(unsigned char *sha1)
-{
-	if (has_sha1_file(sha1))
-		return 0;
-	else
-		return fetch_file(sha1) && fetch_pack(sha1);
-}
-
-int fetch_ref(char *ref, unsigned char *sha1)
-{
-	static int ref_name_start = -1;
-	static char filename[PATH_MAX];
-	static char hex[41];
-	int ifd;
-
-	if (ref_name_start < 0) {
-		sprintf(filename, "%s/refs/", path);
-		ref_name_start = strlen(filename);
-	}
-	strcpy(filename + ref_name_start, ref);
-	ifd = open(filename, O_RDONLY);
-	if (ifd < 0) {
-		close(ifd);
-		return error("cannot open %s", filename);
-	}
-	if (read_in_full(ifd, hex, 40) != 40 || get_sha1_hex(hex, sha1)) {
-		close(ifd);
-		return error("cannot read from %s", filename);
-	}
-	close(ifd);
-	pull_say("ref %s\n", sha1_to_hex(sha1));
-	return 0;
-}
-
-static const char local_pull_usage[] =
-"git-local-fetch [-c] [-t] [-a] [-v] [-w filename] [--recover] [-l] [-s] [-n] [--stdin] commit-id path";
-
-/*
- * By default we only use file copy.
- * If -l is specified, a hard link is attempted.
- * If -s is specified, then a symlink is attempted.
- * If -n is _not_ specified, then a regular file-to-file copy is done.
- */
-int main(int argc, const char **argv)
-{
-	int commits;
-	const char **write_ref = NULL;
-	char **commit_id;
-	int arg = 1;
-
-	setup_git_directory();
-	git_config(git_default_config);
-
-	while (arg < argc && argv[arg][0] == '-') {
-		if (argv[arg][1] == 't')
-			get_tree = 1;
-		else if (argv[arg][1] == 'c')
-			get_history = 1;
-		else if (argv[arg][1] == 'a') {
-			get_all = 1;
-			get_tree = 1;
-			get_history = 1;
-		}
-		else if (argv[arg][1] == 'l')
-			use_link = 1;
-		else if (argv[arg][1] == 's')
-			use_symlink = 1;
-		else if (argv[arg][1] == 'n')
-			use_filecopy = 0;
-		else if (argv[arg][1] == 'v')
-			get_verbosely = 1;
-		else if (argv[arg][1] == 'w')
-			write_ref = &argv[++arg];
-		else if (!strcmp(argv[arg], "--recover"))
-			get_recover = 1;
-		else if (!strcmp(argv[arg], "--stdin"))
-			commits_on_stdin = 1;
-		else
-			usage(local_pull_usage);
-		arg++;
-	}
-	if (argc < arg + 2 - commits_on_stdin)
-		usage(local_pull_usage);
-	if (commits_on_stdin) {
-		commits = pull_targets_stdin(&commit_id, &write_ref);
-	} else {
-		commit_id = (char **) &argv[arg++];
-		commits = 1;
-	}
-	path = argv[arg];
-
-	if (pull(commits, commit_id, write_ref, path))
-		return 1;
-
-	if (commits_on_stdin)
-		pull_targets_free(commits, commit_id, write_ref);
-
-	return 0;
-}
diff --git a/rsh.c b/rsh.c
deleted file mode 100644
index 5754a23..0000000
--- a/rsh.c
+++ /dev/null
@@ -1,83 +0,0 @@
-#include "cache.h"
-#include "rsh.h"
-#include "quote.h"
-
-#define COMMAND_SIZE 4096
-
-int setup_connection(int *fd_in, int *fd_out, const char *remote_prog,
-		     char *url, int rmt_argc, char **rmt_argv)
-{
-	char *host;
-	char *path;
-	int sv[2];
-	char command[COMMAND_SIZE];
-	char *posn;
-	int sizen;
-	int of;
-	int i;
-	pid_t pid;
-
-	if (!strcmp(url, "-")) {
-		*fd_in = 0;
-		*fd_out = 1;
-		return 0;
-	}
-
-	host = strstr(url, "//");
-	if (host) {
-		host += 2;
-		path = strchr(host, '/');
-	} else {
-		host = url;
-		path = strchr(host, ':');
-		if (path)
-			*(path++) = '\0';
-	}
-	if (!path) {
-		return error("Bad URL: %s", url);
-	}
-	/* $GIT_RSH <host> "env GIT_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, 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);
-	}
-
-	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");
-
-	pid = fork();
-	if (pid < 0)
-		return error("Couldn't fork");
-	if (!pid) {
-		const char *ssh, *ssh_basename;
-		ssh = getenv("GIT_SSH");
-		if (!ssh) ssh = "ssh";
-		ssh_basename = strrchr(ssh, '/');
-		if (!ssh_basename)
-			ssh_basename = ssh;
-		else
-			ssh_basename++;
-		close(sv[1]);
-		dup2(sv[0], 0);
-		dup2(sv[0], 1);
-		execlp(ssh, ssh_basename, host, command, NULL);
-	}
-	close(sv[0]);
-	*fd_in = sv[1];
-	*fd_out = sv[1];
-	return 0;
-}
diff --git a/rsh.h b/rsh.h
deleted file mode 100644
index ee2f499..0000000
--- a/rsh.h
+++ /dev/null
@@ -1,7 +0,0 @@
-#ifndef RSH_H
-#define RSH_H
-
-int setup_connection(int *fd_in, int *fd_out, const char *remote_prog,
-		     char *url, int rmt_argc, char **rmt_argv);
-
-#endif
diff --git a/ssh-fetch.c b/ssh-fetch.c
deleted file mode 100644
index bdf51a7..0000000
--- a/ssh-fetch.c
+++ /dev/null
@@ -1,166 +0,0 @@
-#ifndef COUNTERPART_ENV_NAME
-#define COUNTERPART_ENV_NAME "GIT_SSH_UPLOAD"
-#endif
-#ifndef COUNTERPART_PROGRAM_NAME
-#define COUNTERPART_PROGRAM_NAME "git-ssh-upload"
-#endif
-#ifndef MY_PROGRAM_NAME
-#define MY_PROGRAM_NAME "git-ssh-fetch"
-#endif
-
-#include "cache.h"
-#include "commit.h"
-#include "rsh.h"
-#include "fetch.h"
-#include "refs.h"
-
-static int fd_in;
-static int fd_out;
-
-static unsigned char remote_version;
-static unsigned char local_version = 1;
-
-static int prefetches;
-
-static struct object_list *in_transit;
-static struct object_list **end_of_transit = &in_transit;
-
-void prefetch(unsigned char *sha1)
-{
-	char type = 'o';
-	struct object_list *node;
-	if (prefetches > 100) {
-		fetch(in_transit->item->sha1);
-	}
-	node = xmalloc(sizeof(struct object_list));
-	node->next = NULL;
-	node->item = lookup_unknown_object(sha1);
-	*end_of_transit = node;
-	end_of_transit = &node->next;
-	/* XXX: what if these writes fail? */
-	write_in_full(fd_out, &type, 1);
-	write_in_full(fd_out, sha1, 20);
-	prefetches++;
-}
-
-static char conn_buf[4096];
-static size_t conn_buf_posn;
-
-int fetch(unsigned char *sha1)
-{
-	int ret;
-	signed char remote;
-	struct object_list *temp;
-
-	if (hashcmp(sha1, in_transit->item->sha1)) {
-		/* we must have already fetched it to clean the queue */
-		return has_sha1_file(sha1) ? 0 : -1;
-	}
-	prefetches--;
-	temp = in_transit;
-	in_transit = in_transit->next;
-	if (!in_transit)
-		end_of_transit = &in_transit;
-	free(temp);
-
-	if (conn_buf_posn) {
-		remote = conn_buf[0];
-		memmove(conn_buf, conn_buf + 1, --conn_buf_posn);
-	} else {
-		if (xread(fd_in, &remote, 1) < 1)
-			return -1;
-	}
-	/* fprintf(stderr, "Got %d\n", remote); */
-	if (remote < 0)
-		return remote;
-	ret = write_sha1_from_fd(sha1, fd_in, conn_buf, 4096, &conn_buf_posn);
-	if (!ret)
-		pull_say("got %s\n", sha1_to_hex(sha1));
-	return ret;
-}
-
-static int get_version(void)
-{
-	char type = 'v';
-	if (write_in_full(fd_out, &type, 1) != 1 ||
-	    write_in_full(fd_out, &local_version, 1)) {
-		return error("Couldn't request version from remote end");
-	}
-	if (xread(fd_in, &remote_version, 1) < 1) {
-		return error("Couldn't read version from remote end");
-	}
-	return 0;
-}
-
-int fetch_ref(char *ref, unsigned char *sha1)
-{
-	signed char remote;
-	char type = 'r';
-	int length = strlen(ref) + 1;
-	if (write_in_full(fd_out, &type, 1) != 1 ||
-	    write_in_full(fd_out, ref, length) != length)
-		return -1;
-
-	if (read_in_full(fd_in, &remote, 1) != 1)
-		return -1;
-	if (remote < 0)
-		return remote;
-	if (read_in_full(fd_in, sha1, 20) != 20)
-		return -1;
-	return 0;
-}
-
-static const char ssh_fetch_usage[] =
-  MY_PROGRAM_NAME
-  " [-c] [-t] [-a] [-v] [--recover] [-w ref] commit-id url";
-int main(int argc, char **argv)
-{
-	const char *write_ref = NULL;
-	char *commit_id;
-	char *url;
-	int arg = 1;
-	const char *prog;
-
-	prog = getenv("GIT_SSH_PUSH");
-	if (!prog) prog = "git-ssh-upload";
-
-	setup_git_directory();
-	git_config(git_default_config);
-
-	while (arg < argc && argv[arg][0] == '-') {
-		if (argv[arg][1] == 't') {
-			get_tree = 1;
-		} else if (argv[arg][1] == 'c') {
-			get_history = 1;
-		} else if (argv[arg][1] == 'a') {
-			get_all = 1;
-			get_tree = 1;
-			get_history = 1;
-		} else if (argv[arg][1] == 'v') {
-			get_verbosely = 1;
-		} else if (argv[arg][1] == 'w') {
-			write_ref = argv[arg + 1];
-			arg++;
-		} else if (!strcmp(argv[arg], "--recover")) {
-			get_recover = 1;
-		}
-		arg++;
-	}
-	if (argc < arg + 2) {
-		usage(ssh_fetch_usage);
-		return 1;
-	}
-	commit_id = argv[arg];
-	url = argv[arg + 1];
-
-	if (setup_connection(&fd_in, &fd_out, prog, url, arg, argv + 1))
-		return 1;
-
-	if (get_version())
-		return 1;
-
-	if (pull(1, &commit_id, &write_ref, url))
-		return 1;
-
-	return 0;
-}
diff --git a/ssh-pull.c b/ssh-pull.c
deleted file mode 100644
index 868ce4d..0000000
--- a/ssh-pull.c
+++ /dev/null
@@ -1,4 +0,0 @@
-#define COUNTERPART_ENV_NAME "GIT_SSH_PUSH"
-#define COUNTERPART_PROGRAM_NAME "git-ssh-push"
-#define MY_PROGRAM_NAME "git-ssh-pull"
-#include "ssh-fetch.c"
diff --git a/ssh-push.c b/ssh-push.c
deleted file mode 100644
index a562df1..0000000
--- a/ssh-push.c
+++ /dev/null
@@ -1,4 +0,0 @@
-#define COUNTERPART_ENV_NAME "GIT_SSH_PULL"
-#define COUNTERPART_PROGRAM_NAME "git-ssh-pull"
-#define MY_PROGRAM_NAME "git-ssh-push"
-#include "ssh-upload.c"
diff --git a/ssh-upload.c b/ssh-upload.c
deleted file mode 100644
index 20c35f0..0000000
--- a/ssh-upload.c
+++ /dev/null
@@ -1,143 +0,0 @@
-#ifndef COUNTERPART_ENV_NAME
-#define COUNTERPART_ENV_NAME "GIT_SSH_FETCH"
-#endif
-#ifndef COUNTERPART_PROGRAM_NAME
-#define COUNTERPART_PROGRAM_NAME "git-ssh-fetch"
-#endif
-#ifndef MY_PROGRAM_NAME
-#define MY_PROGRAM_NAME "git-ssh-upload"
-#endif
-
-#include "cache.h"
-#include "rsh.h"
-#include "refs.h"
-
-static unsigned char local_version = 1;
-static unsigned char remote_version;
-
-static int verbose;
-
-static int serve_object(int fd_in, int fd_out) {
-	ssize_t size;
-	unsigned char sha1[20];
-	signed char remote;
-
-	size = read_in_full(fd_in, sha1, 20);
-	if (size < 0) {
-		perror("git-ssh-upload: read ");
-		return -1;
-	}
-	if (!size)
-		return -1;
-
-	if (verbose)
-		fprintf(stderr, "Serving %s\n", sha1_to_hex(sha1));
-
-	remote = 0;
-
-	if (!has_sha1_file(sha1)) {
-		fprintf(stderr, "git-ssh-upload: could not find %s\n",
-			sha1_to_hex(sha1));
-		remote = -1;
-	}
-
-	if (write_in_full(fd_out, &remote, 1) != 1)
-		return 0;
-
-	if (remote < 0)
-		return 0;
-
-	return write_sha1_to_fd(fd_out, sha1);
-}
-
-static int serve_version(int fd_in, int fd_out)
-{
-	if (xread(fd_in, &remote_version, 1) < 1)
-		return -1;
-	write_in_full(fd_out, &local_version, 1);
-	return 0;
-}
-
-static int serve_ref(int fd_in, int fd_out)
-{
-	char ref[PATH_MAX];
-	unsigned char sha1[20];
-	int posn = 0;
-	signed char remote = 0;
-	do {
-		if (posn >= PATH_MAX || xread(fd_in, ref + posn, 1) < 1)
-			return -1;
-		posn++;
-	} while (ref[posn - 1]);
-
-	if (verbose)
-		fprintf(stderr, "Serving %s\n", ref);
-
-	if (get_ref_sha1(ref, sha1))
-		remote = -1;
-	if (write_in_full(fd_out, &remote, 1) != 1)
-		return 0;
-	if (remote)
-		return 0;
-	write_in_full(fd_out, sha1, 20);
-        return 0;
-}
-
-
-static void service(int fd_in, int fd_out) {
-	char type;
-	ssize_t retval;
-	do {
-		retval = xread(fd_in, &type, 1);
-		if (retval < 1) {
-			if (retval < 0)
-				perror("git-ssh-upload: read ");
-			return;
-		}
-		if (type == 'v' && serve_version(fd_in, fd_out))
-			return;
-		if (type == 'o' && serve_object(fd_in, fd_out))
-			return;
-		if (type == 'r' && serve_ref(fd_in, fd_out))
-			return;
-	} while (1);
-}
-
-static const char ssh_push_usage[] =
-	MY_PROGRAM_NAME " [-c] [-t] [-a] [-w ref] commit-id url";
-
-int main(int argc, char **argv)
-{
-	int arg = 1;
-        char *commit_id;
-        char *url;
-	int fd_in, fd_out;
-	const char *prog;
-	unsigned char sha1[20];
-	char hex[41];
-
-	prog = getenv(COUNTERPART_ENV_NAME);
-	if (!prog) prog = COUNTERPART_PROGRAM_NAME;
-
-	setup_git_directory();
-
-	while (arg < argc && argv[arg][0] == '-') {
-		if (argv[arg][1] == 'w')
-			arg++;
-                arg++;
-        }
-	if (argc < arg + 2)
-		usage(ssh_push_usage);
-	commit_id = argv[arg];
-	url = argv[arg + 1];
-	if (get_sha1(commit_id, sha1))
-		die("Not a valid object name %s", commit_id);
-	memcpy(hex, sha1_to_hex(sha1), sizeof(hex));
-	argv[arg] = hex;
-
-	if (setup_connection(&fd_in, &fd_out, prog, url, arg, argv + 1))
-		return 1;
-
-	service(fd_in, fd_out);
-	return 0;
-}
-- 
1.5.2.2.1391.gcad2-dirty

^ permalink raw reply related

* [PATCH 2/2] Modularize commit-walker
From: Daniel Barkalow @ 2007-07-03  5:28 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

This turns the extern functions to be provided by the backend into a
struct of pointers, renames the functions to be more
namespace-friendly, and updates http-fetch to this interface. It
removes the unused include from http-push.c. It makes git-http-fetch a
builtin (with the implementation a separate file, accessible
directly).

Signed-off-by: Daniel Barkalow <barkalow@iabervon.org>
---
This is moderately tested. git-http-fetch actually supports file:/// URLs, 
so I tried fetching a bunch of stuff from a different local repository    
with it, and it worked with all of the features I was using. I don't have 
an "alternates" setup to test against, however. I also built it without 
curl to make sure that works.

 Makefile             |   15 +-
 builtin-http-fetch.c |   77 ++++
 builtin.h            |    1 +
 fetch.c              |  317 ---------------
 fetch.h              |   54 ---
 git.c                |    3 +
 http-fetch.c         | 1064 --------------------------------------------------
 http-push.c          |    1 -
 http-walker.c        | 1038 ++++++++++++++++++++++++++++++++++++++++++++++++
 walker.c             |  318 +++++++++++++++
 walker.h             |   37 ++
 11 files changed, 1482 insertions(+), 1443 deletions(-)
 create mode 100644 builtin-http-fetch.c
 delete mode 100644 fetch.c
 delete mode 100644 fetch.h
 delete mode 100644 http-fetch.c
 create mode 100644 http-walker.c
 create mode 100644 walker.c
 create mode 100644 walker.h

diff --git a/Makefile b/Makefile
index 705dde5..47f5fb8 100644
--- a/Makefile
+++ b/Makefile
@@ -508,7 +508,9 @@ else
 	CC_LD_DYNPATH = -R
 endif
 
-ifndef NO_CURL
+ifdef NO_CURL
+	BASIC_CFLAGS += -DNO_CURL
+else
 	ifdef CURLDIR
 		# Try "-Wl,-rpath=$(CURLDIR)/lib" in such a case.
 		BASIC_CFLAGS += -I$(CURLDIR)/include
@@ -516,7 +518,9 @@ ifndef NO_CURL
 	else
 		CURL_LIBCURL = -lcurl
 	endif
-	PROGRAMS += git-http-fetch$X
+	BUILTIN_OBJS += builtin-http-fetch.o
+	EXTLIBS += $(CURL_LIBCURL)
+	LIB_OBJS += http.o walker.o http-walker.o
 	curl_check := $(shell (echo 070908; curl-config --vernum) | sort -r | sed -ne 2p)
 	ifeq "$(curl_check)" "070908"
 		ifndef NO_EXPAT
@@ -883,16 +887,13 @@ git-%$X: %.o $(GITLIBS)
 
 git-imap-send$X: imap-send.o $(LIB_FILE)
 
-http.o http-fetch.o http-push.o: http.h
-git-http-fetch$X: fetch.o http.o http-fetch.o $(GITLIBS)
-	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
-		$(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
+http.o builtin-http-fetch.o http-push.o: http.h
 
 git-http-push$X: revision.o http.o http-push.o $(GITLIBS)
 	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
 		$(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
 
-$(LIB_OBJS) $(BUILTIN_OBJS) fetch.o: $(LIB_H)
+$(LIB_OBJS) $(BUILTIN_OBJS) walker.o: $(LIB_H)
 $(patsubst git-%$X,%.o,$(PROGRAMS)): $(LIB_H) $(wildcard */*.h)
 $(DIFF_OBJS): diffcore.h
 
diff --git a/builtin-http-fetch.c b/builtin-http-fetch.c
new file mode 100644
index 0000000..4a50dbd
--- /dev/null
+++ b/builtin-http-fetch.c
@@ -0,0 +1,77 @@
+#include "cache.h"
+#include "walker.h"
+
+int cmd_http_fetch(int argc, const char **argv, const char *prefix)
+{
+	struct walker *walker;
+	int commits_on_stdin = 0;
+	int commits;
+	const char **write_ref = NULL;
+	char **commit_id;
+	const char *url;
+	int arg = 1;
+	int rc = 0;
+	int get_tree = 0;
+	int get_history = 0;
+	int get_all = 0;
+	int get_verbosely = 0;
+	int get_recover = 0;
+
+	git_config(git_default_config);
+
+	while (arg < argc && argv[arg][0] == '-') {
+		if (argv[arg][1] == 't') {
+			get_tree = 1;
+		} else if (argv[arg][1] == 'c') {
+			get_history = 1;
+		} else if (argv[arg][1] == 'a') {
+			get_all = 1;
+			get_tree = 1;
+			get_history = 1;
+		} else if (argv[arg][1] == 'v') {
+			get_verbosely = 1;
+		} else if (argv[arg][1] == 'w') {
+			write_ref = &argv[arg + 1];
+			arg++;
+		} else if (!strcmp(argv[arg], "--recover")) {
+			get_recover = 1;
+		} else if (!strcmp(argv[arg], "--stdin")) {
+			commits_on_stdin = 1;
+		}
+		arg++;
+	}
+	if (argc < arg + 2 - commits_on_stdin) {
+		usage("git-http-fetch [-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin] commit-id url");
+		return 1;
+	}
+	if (commits_on_stdin) {
+		commits = walker_targets_stdin(&commit_id, &write_ref);
+	} else {
+		commit_id = (char **) &argv[arg++];
+		commits = 1;
+	}
+	url = argv[arg];
+
+	walker = get_http_walker(url);
+	walker->get_tree = get_tree;
+	walker->get_history = get_history;
+	walker->get_all = get_all;
+	walker->get_verbosely = get_verbosely;
+	walker->get_recover = get_recover;
+
+	rc = walker_fetch(walker, commits, commit_id, write_ref, url);
+
+	if (commits_on_stdin)
+		walker_targets_free(commits, commit_id, write_ref);
+
+	if (walker->corrupt_object_found) {
+		fprintf(stderr,
+"Some loose object were found to be corrupt, but they might be just\n"
+"a false '404 Not Found' error message sent with incorrect HTTP\n"
+"status code.  Suggest running git-fsck.\n");
+	}
+
+	walker_free(walker);
+
+	return rc;
+}
diff --git a/builtin.h b/builtin.h
index 661a92f..bd06af7 100644
--- a/builtin.h
+++ b/builtin.h
@@ -40,6 +40,7 @@ extern int cmd_gc(int argc, const char **argv, const char *prefix);
 extern int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix);
 extern int cmd_grep(int argc, const char **argv, const char *prefix);
 extern int cmd_help(int argc, const char **argv, const char *prefix);
+extern int cmd_http_fetch(int argc, const char **argv, const char *prefix);
 extern int cmd_init_db(int argc, const char **argv, const char *prefix);
 extern int cmd_log(int argc, const char **argv, const char *prefix);
 extern int cmd_log_reflog(int argc, const char **argv, const char *prefix);
diff --git a/fetch.c b/fetch.c
deleted file mode 100644
index 811be87..0000000
--- a/fetch.c
+++ /dev/null
@@ -1,317 +0,0 @@
-#include "cache.h"
-#include "fetch.h"
-#include "commit.h"
-#include "tree.h"
-#include "tree-walk.h"
-#include "tag.h"
-#include "blob.h"
-#include "refs.h"
-#include "strbuf.h"
-
-int get_tree = 0;
-int get_history = 0;
-int get_all = 0;
-int get_verbosely = 0;
-int get_recover = 0;
-static unsigned char current_commit_sha1[20];
-
-void pull_say(const char *fmt, const char *hex)
-{
-	if (get_verbosely)
-		fprintf(stderr, fmt, hex);
-}
-
-static void report_missing(const struct object *obj)
-{
-	char missing_hex[41];
-	strcpy(missing_hex, sha1_to_hex(obj->sha1));;
-	fprintf(stderr, "Cannot obtain needed %s %s\n",
-		obj->type ? typename(obj->type): "object", missing_hex);
-	if (!is_null_sha1(current_commit_sha1))
-		fprintf(stderr, "while processing commit %s.\n",
-			sha1_to_hex(current_commit_sha1));
-}
-
-static int process(struct object *obj);
-
-static int process_tree(struct tree *tree)
-{
-	struct tree_desc desc;
-	struct name_entry entry;
-
-	if (parse_tree(tree))
-		return -1;
-
-	init_tree_desc(&desc, tree->buffer, tree->size);
-	while (tree_entry(&desc, &entry)) {
-		struct object *obj = NULL;
-
-		/* submodule commits are not stored in the superproject */
-		if (S_ISGITLINK(entry.mode))
-			continue;
-		if (S_ISDIR(entry.mode)) {
-			struct tree *tree = lookup_tree(entry.sha1);
-			if (tree)
-				obj = &tree->object;
-		}
-		else {
-			struct blob *blob = lookup_blob(entry.sha1);
-			if (blob)
-				obj = &blob->object;
-		}
-		if (!obj || process(obj))
-			return -1;
-	}
-	free(tree->buffer);
-	tree->buffer = NULL;
-	tree->size = 0;
-	return 0;
-}
-
-#define COMPLETE	(1U << 0)
-#define SEEN		(1U << 1)
-#define TO_SCAN		(1U << 2)
-
-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;
-
-	hashcpy(current_commit_sha1, commit->object.sha1);
-
-	pull_say("walk %s\n", sha1_to_hex(commit->object.sha1));
-
-	if (get_tree) {
-		if (process(&commit->tree->object))
-			return -1;
-		if (!get_all)
-			get_tree = 0;
-	}
-	if (get_history) {
-		struct commit_list *parents = commit->parents;
-		for (; parents; parents = parents->next) {
-			if (process(&parents->item->object))
-				return -1;
-		}
-	}
-	return 0;
-}
-
-static int process_tag(struct tag *tag)
-{
-	if (parse_tag(tag))
-		return -1;
-	return process(tag->tagged);
-}
-
-static struct object_list *process_queue = NULL;
-static struct object_list **process_queue_end = &process_queue;
-
-static int process_object(struct object *obj)
-{
-	if (obj->type == OBJ_COMMIT) {
-		if (process_commit((struct commit *)obj))
-			return -1;
-		return 0;
-	}
-	if (obj->type == OBJ_TREE) {
-		if (process_tree((struct tree *)obj))
-			return -1;
-		return 0;
-	}
-	if (obj->type == OBJ_BLOB) {
-		return 0;
-	}
-	if (obj->type == OBJ_TAG) {
-		if (process_tag((struct tag *)obj))
-			return -1;
-		return 0;
-	}
-	return error("Unable to determine requirements "
-		     "of type %s for %s",
-		     typename(obj->type), sha1_to_hex(obj->sha1));
-}
-
-static int process(struct object *obj)
-{
-	if (obj->flags & SEEN)
-		return 0;
-	obj->flags |= SEEN;
-
-	if (has_sha1_file(obj->sha1)) {
-		/* We already have it, so we should scan it now. */
-		obj->flags |= TO_SCAN;
-	}
-	else {
-		if (obj->flags & COMPLETE)
-			return 0;
-		prefetch(obj->sha1);
-	}
-
-	object_list_insert(obj, process_queue_end);
-	process_queue_end = &(*process_queue_end)->next;
-	return 0;
-}
-
-static int loop(void)
-{
-	struct object_list *elem;
-
-	while (process_queue) {
-		struct object *obj = process_queue->item;
-		elem = process_queue;
-		process_queue = elem->next;
-		free(elem);
-		if (!process_queue)
-			process_queue_end = &process_queue;
-
-		/* 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);
-				return -1;
-			}
-		}
-		if (!obj->type)
-			parse_object(obj->sha1);
-		if (process_object(obj))
-			return -1;
-	}
-	return 0;
-}
-
-static int interpret_target(char *target, unsigned char *sha1)
-{
-	if (!get_sha1_hex(target, sha1))
-		return 0;
-	if (!check_ref_format(target)) {
-		if (!fetch_ref(target, sha1)) {
-			return 0;
-		}
-	}
-	return -1;
-}
-
-static int mark_complete(const char *path, const unsigned char *sha1, int flag, void *cb_data)
-{
-	struct commit *commit = lookup_commit_reference_gently(sha1, 1);
-	if (commit) {
-		commit->object.flags |= COMPLETE;
-		insert_by_date(commit, &complete);
-	}
-	return 0;
-}
-
-int pull_targets_stdin(char ***target, const char ***write_ref)
-{
-	int targets = 0, targets_alloc = 0;
-	struct strbuf buf;
-	*target = NULL; *write_ref = NULL;
-	strbuf_init(&buf);
-	while (1) {
-		char *rf_one = NULL;
-		char *tg_one;
-
-		read_line(&buf, stdin, '\n');
-		if (buf.eof)
-			break;
-		tg_one = buf.buf;
-		rf_one = strchr(tg_one, '\t');
-		if (rf_one)
-			*rf_one++ = 0;
-
-		if (targets >= targets_alloc) {
-			targets_alloc = targets_alloc ? targets_alloc * 2 : 64;
-			*target = xrealloc(*target, targets_alloc * sizeof(**target));
-			*write_ref = xrealloc(*write_ref, targets_alloc * sizeof(**write_ref));
-		}
-		(*target)[targets] = xstrdup(tg_one);
-		(*write_ref)[targets] = rf_one ? xstrdup(rf_one) : NULL;
-		targets++;
-	}
-	return targets;
-}
-
-void pull_targets_free(int targets, char **target, const char **write_ref)
-{
-	while (targets--) {
-		free(target[targets]);
-		if (write_ref && write_ref[targets])
-			free((char *) write_ref[targets]);
-	}
-}
-
-int pull(int targets, char **target, const char **write_ref,
-         const char *write_ref_log_details)
-{
-	struct ref_lock **lock = xcalloc(targets, sizeof(struct ref_lock *));
-	unsigned char *sha1 = xmalloc(targets * 20);
-	char *msg;
-	int ret;
-	int i;
-
-	save_commit_buffer = 0;
-	track_object_refs = 0;
-
-	for (i = 0; i < targets; i++) {
-		if (!write_ref || !write_ref[i])
-			continue;
-
-		lock[i] = lock_ref_sha1(write_ref[i], NULL);
-		if (!lock[i]) {
-			error("Can't lock ref %s", write_ref[i]);
-			goto unlock_and_fail;
-		}
-	}
-
-	if (!get_recover)
-		for_each_ref(mark_complete, NULL);
-
-	for (i = 0; i < targets; i++) {
-		if (interpret_target(target[i], &sha1[20 * i])) {
-			error("Could not interpret %s as something to pull", target[i]);
-			goto unlock_and_fail;
-		}
-		if (process(lookup_unknown_object(&sha1[20 * i])))
-			goto unlock_and_fail;
-	}
-
-	if (loop())
-		goto unlock_and_fail;
-
-	if (write_ref_log_details) {
-		msg = xmalloc(strlen(write_ref_log_details) + 12);
-		sprintf(msg, "fetch from %s", write_ref_log_details);
-	} else {
-		msg = NULL;
-	}
-	for (i = 0; i < targets; i++) {
-		if (!write_ref || !write_ref[i])
-			continue;
-		ret = write_ref_sha1(lock[i], &sha1[20 * i], msg ? msg : "fetch (unknown)");
-		lock[i] = NULL;
-		if (ret)
-			goto unlock_and_fail;
-	}
-	free(msg);
-
-	return 0;
-
-
-unlock_and_fail:
-	for (i = 0; i < targets; i++)
-		if (lock[i])
-			unlock_ref(lock[i]);
-	return -1;
-}
diff --git a/fetch.h b/fetch.h
deleted file mode 100644
index be48c6f..0000000
--- a/fetch.h
+++ /dev/null
@@ -1,54 +0,0 @@
-#ifndef PULL_H
-#define PULL_H
-
-/*
- * Fetch object given SHA1 from the remote, and store it locally under
- * GIT_OBJECT_DIRECTORY.  Return 0 on success, -1 on failure.  To be
- * provided by the particular implementation.
- */
-extern int fetch(unsigned char *sha1);
-
-/*
- * Fetch the specified object and store it locally; fetch() will be
- * called later to determine success. To be provided by the particular
- * implementation.
- */
-extern void prefetch(unsigned char *sha1);
-
-/*
- * Fetch ref (relative to $GIT_DIR/refs) from the remote, and store
- * the 20-byte SHA1 in sha1.  Return 0 on success, -1 on failure.  To
- * be provided by the particular implementation.
- */
-extern int fetch_ref(char *ref, unsigned char *sha1);
-
-/* Set to fetch the target tree. */
-extern int get_tree;
-
-/* Set to fetch the commit history. */
-extern int get_history;
-
-/* Set to fetch the trees in the commit history. */
-extern int get_all;
-
-/* Set to be verbose */
-extern int get_verbosely;
-
-/* Set to check on all reachable objects. */
-extern int get_recover;
-
-/* Report what we got under get_verbosely */
-extern void pull_say(const char *, const char *);
-
-/* Load pull targets from stdin */
-extern int pull_targets_stdin(char ***target, const char ***write_ref);
-
-/* Free up loaded targets */
-extern void pull_targets_free(int targets, char **target, const char **write_ref);
-
-/* If write_ref is set, the ref filename to write the target value to. */
-/* If write_ref_log_details is set, additional text will appear in the ref log. */
-extern int pull(int targets, char **target, const char **write_ref,
-		const char *write_ref_log_details);
-
-#endif /* PULL_H */
diff --git a/git.c b/git.c
index b949cbb..4c2d6da 100644
--- a/git.c
+++ b/git.c
@@ -317,6 +317,9 @@ static void handle_internal_command(int argc, const char **argv)
 		{ "get-tar-commit-id", cmd_get_tar_commit_id },
 		{ "grep", cmd_grep, RUN_SETUP | USE_PAGER },
 		{ "help", cmd_help },
+#ifndef NO_CURL
+		{ "http-fetch", cmd_http_fetch, RUN_SETUP },
+#endif
 		{ "init", cmd_init_db },
 		{ "init-db", cmd_init_db },
 		{ "log", cmd_log, RUN_SETUP | USE_PAGER },
diff --git a/http-fetch.c b/http-fetch.c
deleted file mode 100644
index 202fae0..0000000
--- a/http-fetch.c
+++ /dev/null
@@ -1,1064 +0,0 @@
-#include "cache.h"
-#include "commit.h"
-#include "pack.h"
-#include "fetch.h"
-#include "http.h"
-
-#define PREV_BUF_SIZE 4096
-#define RANGE_HEADER_SIZE 30
-
-static int commits_on_stdin;
-
-static int got_alternates = -1;
-static int corrupt_object_found;
-
-static struct curl_slist *no_pragma_header;
-
-struct alt_base
-{
-	char *base;
-	int got_indices;
-	struct packed_git *packs;
-	struct alt_base *next;
-};
-
-static struct alt_base *alt;
-
-enum object_request_state {
-	WAITING,
-	ABORTED,
-	ACTIVE,
-	COMPLETE,
-};
-
-struct object_request
-{
-	unsigned char sha1[20];
-	struct alt_base *repo;
-	char *url;
-	char filename[PATH_MAX];
-	char tmpfile[PATH_MAX];
-	int local;
-	enum object_request_state state;
-	CURLcode curl_result;
-	char errorstr[CURL_ERROR_SIZE];
-	long http_code;
-	unsigned char real_sha1[20];
-	SHA_CTX c;
-	z_stream stream;
-	int zret;
-	int rename;
-	struct active_request_slot *slot;
-	struct object_request *next;
-};
-
-struct alternates_request {
-	const char *base;
-	char *url;
-	struct buffer *buffer;
-	struct active_request_slot *slot;
-	int http_specific;
-};
-
-static struct object_request *object_queue_head;
-
-static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
-			       void *data)
-{
-	unsigned char expn[4096];
-	size_t size = eltsize * nmemb;
-	int posn = 0;
-	struct object_request *obj_req = (struct object_request *)data;
-	do {
-		ssize_t retval = xwrite(obj_req->local,
-				       (char *) ptr + posn, size - posn);
-		if (retval < 0)
-			return posn;
-		posn += retval;
-	} while (posn < size);
-
-	obj_req->stream.avail_in = size;
-	obj_req->stream.next_in = ptr;
-	do {
-		obj_req->stream.next_out = expn;
-		obj_req->stream.avail_out = sizeof(expn);
-		obj_req->zret = inflate(&obj_req->stream, Z_SYNC_FLUSH);
-		SHA1_Update(&obj_req->c, expn,
-			    sizeof(expn) - obj_req->stream.avail_out);
-	} while (obj_req->stream.avail_in && obj_req->zret == Z_OK);
-	data_received++;
-	return size;
-}
-
-static int missing__target(int code, int result)
-{
-	return	/* file:// URL -- do we ever use one??? */
-		(result == CURLE_FILE_COULDNT_READ_FILE) ||
-		/* http:// and https:// URL */
-		(code == 404 && result == CURLE_HTTP_RETURNED_ERROR) ||
-		/* ftp:// URL */
-		(code == 550 && result == CURLE_FTP_COULDNT_RETR_FILE)
-		;
-}
-
-#define missing_target(a) missing__target((a)->http_code, (a)->curl_result)
-
-static void fetch_alternates(const char *base);
-
-static void process_object_response(void *callback_data);
-
-static void start_object_request(struct object_request *obj_req)
-{
-	char *hex = sha1_to_hex(obj_req->sha1);
-	char prevfile[PATH_MAX];
-	char *url;
-	char *posn;
-	int prevlocal;
-	unsigned char prev_buf[PREV_BUF_SIZE];
-	ssize_t prev_read = 0;
-	long prev_posn = 0;
-	char range[RANGE_HEADER_SIZE];
-	struct curl_slist *range_header = NULL;
-	struct active_request_slot *slot;
-
-	snprintf(prevfile, sizeof(prevfile), "%s.prev", obj_req->filename);
-	unlink(prevfile);
-	rename(obj_req->tmpfile, prevfile);
-	unlink(obj_req->tmpfile);
-
-	if (obj_req->local != -1)
-		error("fd leakage in start: %d", obj_req->local);
-	obj_req->local = open(obj_req->tmpfile,
-			      O_WRONLY | O_CREAT | O_EXCL, 0666);
-	/* This could have failed due to the "lazy directory creation";
-	 * try to mkdir the last path component.
-	 */
-	if (obj_req->local < 0 && errno == ENOENT) {
-		char *dir = strrchr(obj_req->tmpfile, '/');
-		if (dir) {
-			*dir = 0;
-			mkdir(obj_req->tmpfile, 0777);
-			*dir = '/';
-		}
-		obj_req->local = open(obj_req->tmpfile,
-				      O_WRONLY | O_CREAT | O_EXCL, 0666);
-	}
-
-	if (obj_req->local < 0) {
-		obj_req->state = ABORTED;
-		error("Couldn't create temporary file %s for %s: %s",
-		      obj_req->tmpfile, obj_req->filename, strerror(errno));
-		return;
-	}
-
-	memset(&obj_req->stream, 0, sizeof(obj_req->stream));
-
-	inflateInit(&obj_req->stream);
-
-	SHA1_Init(&obj_req->c);
-
-	url = xmalloc(strlen(obj_req->repo->base) + 51);
-	obj_req->url = xmalloc(strlen(obj_req->repo->base) + 51);
-	strcpy(url, obj_req->repo->base);
-	posn = url + strlen(obj_req->repo->base);
-	strcpy(posn, "/objects/");
-	posn += 9;
-	memcpy(posn, hex, 2);
-	posn += 2;
-	*(posn++) = '/';
-	strcpy(posn, hex + 2);
-	strcpy(obj_req->url, url);
-
-	/* If a previous temp file is present, process what was already
-	   fetched. */
-	prevlocal = open(prevfile, O_RDONLY);
-	if (prevlocal != -1) {
-		do {
-			prev_read = xread(prevlocal, prev_buf, PREV_BUF_SIZE);
-			if (prev_read>0) {
-				if (fwrite_sha1_file(prev_buf,
-						     1,
-						     prev_read,
-						     obj_req) == prev_read) {
-					prev_posn += prev_read;
-				} else {
-					prev_read = -1;
-				}
-			}
-		} while (prev_read > 0);
-		close(prevlocal);
-	}
-	unlink(prevfile);
-
-	/* Reset inflate/SHA1 if there was an error reading the previous temp
-	   file; also rewind to the beginning of the local file. */
-	if (prev_read == -1) {
-		memset(&obj_req->stream, 0, sizeof(obj_req->stream));
-		inflateInit(&obj_req->stream);
-		SHA1_Init(&obj_req->c);
-		if (prev_posn>0) {
-			prev_posn = 0;
-			lseek(obj_req->local, 0, SEEK_SET);
-			ftruncate(obj_req->local, 0);
-		}
-	}
-
-	slot = get_active_slot();
-	slot->callback_func = process_object_response;
-	slot->callback_data = obj_req;
-	obj_req->slot = slot;
-
-	curl_easy_setopt(slot->curl, CURLOPT_FILE, obj_req);
-	curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file);
-	curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, obj_req->errorstr);
-	curl_easy_setopt(slot->curl, CURLOPT_URL, url);
-	curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
-
-	/* If we have successfully processed data from a previous fetch
-	   attempt, only fetch the data we don't already have. */
-	if (prev_posn>0) {
-		if (get_verbosely)
-			fprintf(stderr,
-				"Resuming fetch of object %s at byte %ld\n",
-				hex, prev_posn);
-		sprintf(range, "Range: bytes=%ld-", prev_posn);
-		range_header = curl_slist_append(range_header, range);
-		curl_easy_setopt(slot->curl,
-				 CURLOPT_HTTPHEADER, range_header);
-	}
-
-	/* Try to get the request started, abort the request on error */
-	obj_req->state = ACTIVE;
-	if (!start_active_slot(slot)) {
-		obj_req->state = ABORTED;
-		obj_req->slot = NULL;
-		close(obj_req->local); obj_req->local = -1;
-		free(obj_req->url);
-		return;
-	}
-}
-
-static void finish_object_request(struct object_request *obj_req)
-{
-	struct stat st;
-
-	fchmod(obj_req->local, 0444);
-	close(obj_req->local); obj_req->local = -1;
-
-	if (obj_req->http_code == 416) {
-		fprintf(stderr, "Warning: requested range invalid; we may already have all the data.\n");
-	} else if (obj_req->curl_result != CURLE_OK) {
-		if (stat(obj_req->tmpfile, &st) == 0)
-			if (st.st_size == 0)
-				unlink(obj_req->tmpfile);
-		return;
-	}
-
-	inflateEnd(&obj_req->stream);
-	SHA1_Final(obj_req->real_sha1, &obj_req->c);
-	if (obj_req->zret != Z_STREAM_END) {
-		unlink(obj_req->tmpfile);
-		return;
-	}
-	if (hashcmp(obj_req->sha1, obj_req->real_sha1)) {
-		unlink(obj_req->tmpfile);
-		return;
-	}
-	obj_req->rename =
-		move_temp_to_file(obj_req->tmpfile, obj_req->filename);
-
-	if (obj_req->rename == 0)
-		pull_say("got %s\n", sha1_to_hex(obj_req->sha1));
-}
-
-static void process_object_response(void *callback_data)
-{
-	struct object_request *obj_req =
-		(struct object_request *)callback_data;
-
-	obj_req->curl_result = obj_req->slot->curl_result;
-	obj_req->http_code = obj_req->slot->http_code;
-	obj_req->slot = NULL;
-	obj_req->state = COMPLETE;
-
-	/* Use alternates if necessary */
-	if (missing_target(obj_req)) {
-		fetch_alternates(alt->base);
-		if (obj_req->repo->next != NULL) {
-			obj_req->repo =
-				obj_req->repo->next;
-			close(obj_req->local);
-			obj_req->local = -1;
-			start_object_request(obj_req);
-			return;
-		}
-	}
-
-	finish_object_request(obj_req);
-}
-
-static void release_object_request(struct object_request *obj_req)
-{
-	struct object_request *entry = object_queue_head;
-
-	if (obj_req->local != -1)
-		error("fd leakage in release: %d", obj_req->local);
-	if (obj_req == object_queue_head) {
-		object_queue_head = obj_req->next;
-	} else {
-		while (entry->next != NULL && entry->next != obj_req)
-			entry = entry->next;
-		if (entry->next == obj_req)
-			entry->next = entry->next->next;
-	}
-
-	free(obj_req->url);
-	free(obj_req);
-}
-
-#ifdef USE_CURL_MULTI
-void fill_active_slots(void)
-{
-	struct object_request *obj_req = object_queue_head;
-	struct active_request_slot *slot = active_queue_head;
-	int num_transfers;
-
-	while (active_requests < max_requests && obj_req != NULL) {
-		if (obj_req->state == WAITING) {
-			if (has_sha1_file(obj_req->sha1))
-				obj_req->state = COMPLETE;
-			else
-				start_object_request(obj_req);
-			curl_multi_perform(curlm, &num_transfers);
-		}
-		obj_req = obj_req->next;
-	}
-
-	while (slot != NULL) {
-		if (!slot->in_use && slot->curl != NULL) {
-			curl_easy_cleanup(slot->curl);
-			slot->curl = NULL;
-		}
-		slot = slot->next;
-	}
-}
-#endif
-
-void prefetch(unsigned char *sha1)
-{
-	struct object_request *newreq;
-	struct object_request *tail;
-	char *filename = sha1_file_name(sha1);
-
-	newreq = xmalloc(sizeof(*newreq));
-	hashcpy(newreq->sha1, sha1);
-	newreq->repo = alt;
-	newreq->url = NULL;
-	newreq->local = -1;
-	newreq->state = WAITING;
-	snprintf(newreq->filename, sizeof(newreq->filename), "%s", filename);
-	snprintf(newreq->tmpfile, sizeof(newreq->tmpfile),
-		 "%s.temp", filename);
-	newreq->slot = NULL;
-	newreq->next = NULL;
-
-	if (object_queue_head == NULL) {
-		object_queue_head = newreq;
-	} else {
-		tail = object_queue_head;
-		while (tail->next != NULL) {
-			tail = tail->next;
-		}
-		tail->next = newreq;
-	}
-
-#ifdef USE_CURL_MULTI
-	fill_active_slots();
-	step_active_slots();
-#endif
-}
-
-static int fetch_index(struct alt_base *repo, unsigned char *sha1)
-{
-	char *hex = sha1_to_hex(sha1);
-	char *filename;
-	char *url;
-	char tmpfile[PATH_MAX];
-	long prev_posn = 0;
-	char range[RANGE_HEADER_SIZE];
-	struct curl_slist *range_header = NULL;
-
-	FILE *indexfile;
-	struct active_request_slot *slot;
-	struct slot_results results;
-
-	if (has_pack_index(sha1))
-		return 0;
-
-	if (get_verbosely)
-		fprintf(stderr, "Getting index for pack %s\n", hex);
-
-	url = xmalloc(strlen(repo->base) + 64);
-	sprintf(url, "%s/objects/pack/pack-%s.idx", repo->base, hex);
-
-	filename = sha1_pack_index_name(sha1);
-	snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
-	indexfile = fopen(tmpfile, "a");
-	if (!indexfile)
-		return error("Unable to open local file %s for pack index",
-			     filename);
-
-	slot = get_active_slot();
-	slot->results = &results;
-	curl_easy_setopt(slot->curl, CURLOPT_FILE, indexfile);
-	curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
-	curl_easy_setopt(slot->curl, CURLOPT_URL, url);
-	curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
-	slot->local = indexfile;
-
-	/* If there is data present from a previous transfer attempt,
-	   resume where it left off */
-	prev_posn = ftell(indexfile);
-	if (prev_posn>0) {
-		if (get_verbosely)
-			fprintf(stderr,
-				"Resuming fetch of index for pack %s at byte %ld\n",
-				hex, prev_posn);
-		sprintf(range, "Range: bytes=%ld-", prev_posn);
-		range_header = curl_slist_append(range_header, range);
-		curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header);
-	}
-
-	if (start_active_slot(slot)) {
-		run_active_slot(slot);
-		if (results.curl_result != CURLE_OK) {
-			fclose(indexfile);
-			return error("Unable to get pack index %s\n%s", url,
-				     curl_errorstr);
-		}
-	} else {
-		fclose(indexfile);
-		return error("Unable to start request");
-	}
-
-	fclose(indexfile);
-
-	return move_temp_to_file(tmpfile, filename);
-}
-
-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(repo, sha1))
-		return -1;
-
-	new_pack = parse_pack_index(sha1);
-	new_pack->next = repo->packs;
-	repo->packs = new_pack;
-	return 0;
-}
-
-static void process_alternates_response(void *callback_data)
-{
-	struct alternates_request *alt_req =
-		(struct alternates_request *)callback_data;
-	struct active_request_slot *slot = alt_req->slot;
-	struct alt_base *tail = alt;
-	const char *base = alt_req->base;
-	static const char null_byte = '\0';
-	char *data;
-	int i = 0;
-
-	if (alt_req->http_specific) {
-		if (slot->curl_result != CURLE_OK ||
-		    !alt_req->buffer->posn) {
-
-			/* Try reusing the slot to get non-http alternates */
-			alt_req->http_specific = 0;
-			sprintf(alt_req->url, "%s/objects/info/alternates",
-				base);
-			curl_easy_setopt(slot->curl, CURLOPT_URL,
-					 alt_req->url);
-			active_requests++;
-			slot->in_use = 1;
-			if (slot->finished != NULL)
-				(*slot->finished) = 0;
-			if (!start_active_slot(slot)) {
-				got_alternates = -1;
-				slot->in_use = 0;
-				if (slot->finished != NULL)
-					(*slot->finished) = 1;
-			}
-			return;
-		}
-	} else if (slot->curl_result != CURLE_OK) {
-		if (!missing_target(slot)) {
-			got_alternates = -1;
-			return;
-		}
-	}
-
-	fwrite_buffer(&null_byte, 1, 1, alt_req->buffer);
-	alt_req->buffer->posn--;
-	data = alt_req->buffer->buffer;
-
-	while (i < alt_req->buffer->posn) {
-		int posn = i;
-		while (posn < alt_req->buffer->posn && data[posn] != '\n')
-			posn++;
-		if (data[posn] == '\n') {
-			int okay = 0;
-			int serverlen = 0;
-			struct alt_base *newalt;
-			char *target = NULL;
-			if (data[i] == '/') {
-				/* This counts
-				 * http://git.host/pub/scm/linux.git/
-				 * -----------here^
-				 * so memcpy(dst, base, serverlen) will
-				 * copy up to "...git.host".
-				 */
-				const char *colon_ss = strstr(base,"://");
-				if (colon_ss) {
-					serverlen = (strchr(colon_ss + 3, '/')
-						     - base);
-					okay = 1;
-				}
-			} else if (!memcmp(data + i, "../", 3)) {
-				/* Relative URL; chop the corresponding
-				 * number of subpath from base (and ../
-				 * from data), and concatenate the result.
-				 *
-				 * The code first drops ../ from data, and
-				 * then drops one ../ from data and one path
-				 * from base.  IOW, one extra ../ is dropped
-				 * from data than path is dropped from base.
-				 *
-				 * This is not wrong.  The alternate in
-				 *     http://git.host/pub/scm/linux.git/
-				 * to borrow from
-				 *     http://git.host/pub/scm/linus.git/
-				 * is ../../linus.git/objects/.  You need
-				 * two ../../ to borrow from your direct
-				 * neighbour.
-				 */
-				i += 3;
-				serverlen = strlen(base);
-				while (i + 2 < posn &&
-				       !memcmp(data + i, "../", 3)) {
-					do {
-						serverlen--;
-					} while (serverlen &&
-						 base[serverlen - 1] != '/');
-					i += 3;
-				}
-				/* If the server got removed, give up. */
-				okay = strchr(base, ':') - base + 3 <
-					serverlen;
-			} else if (alt_req->http_specific) {
-				char *colon = strchr(data + i, ':');
-				char *slash = strchr(data + i, '/');
-				if (colon && slash && colon < data + posn &&
-				    slash < data + posn && colon < slash) {
-					okay = 1;
-				}
-			}
-			/* skip "objects\n" at end */
-			if (okay) {
-				target = xmalloc(serverlen + posn - i - 6);
-				memcpy(target, base, serverlen);
-				memcpy(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 = NULL;
-				newalt->base = target;
-				newalt->got_indices = 0;
-				newalt->packs = NULL;
-
-				while (tail->next != NULL)
-					tail = tail->next;
-				tail->next = newalt;
-			}
-		}
-		i = posn + 1;
-	}
-
-	got_alternates = 1;
-}
-
-static void fetch_alternates(const char *base)
-{
-	struct buffer buffer;
-	char *url;
-	char *data;
-	struct active_request_slot *slot;
-	struct alternates_request alt_req;
-
-	/* If another request has already started fetching alternates,
-	   wait for them to arrive and return to processing this request's
-	   curl message */
-#ifdef USE_CURL_MULTI
-	while (got_alternates == 0) {
-		step_active_slots();
-	}
-#endif
-
-	/* Nothing to do if they've already been fetched */
-	if (got_alternates == 1)
-		return;
-
-	/* Start the fetch */
-	got_alternates = 0;
-
-	data = xmalloc(4096);
-	buffer.size = 4096;
-	buffer.posn = 0;
-	buffer.buffer = data;
-
-	if (get_verbosely)
-		fprintf(stderr, "Getting alternates list for %s\n", base);
-
-	url = xmalloc(strlen(base) + 31);
-	sprintf(url, "%s/objects/info/http-alternates", base);
-
-	/* Use a callback to process the result, since another request
-	   may fail and need to have alternates loaded before continuing */
-	slot = get_active_slot();
-	slot->callback_func = process_alternates_response;
-	slot->callback_data = &alt_req;
-
-	curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
-	curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
-	curl_easy_setopt(slot->curl, CURLOPT_URL, url);
-
-	alt_req.base = base;
-	alt_req.url = url;
-	alt_req.buffer = &buffer;
-	alt_req.http_specific = 1;
-	alt_req.slot = slot;
-
-	if (start_active_slot(slot))
-		run_active_slot(slot);
-	else
-		got_alternates = -1;
-
-	free(data);
-	free(url);
-}
-
-static int fetch_indices(struct alt_base *repo)
-{
-	unsigned char sha1[20];
-	char *url;
-	struct buffer buffer;
-	char *data;
-	int i = 0;
-
-	struct active_request_slot *slot;
-	struct slot_results results;
-
-	if (repo->got_indices)
-		return 0;
-
-	data = xmalloc(4096);
-	buffer.size = 4096;
-	buffer.posn = 0;
-	buffer.buffer = data;
-
-	if (get_verbosely)
-		fprintf(stderr, "Getting pack list for %s\n", repo->base);
-
-	url = xmalloc(strlen(repo->base) + 21);
-	sprintf(url, "%s/objects/info/packs", repo->base);
-
-	slot = get_active_slot();
-	slot->results = &results;
-	curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
-	curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
-	curl_easy_setopt(slot->curl, CURLOPT_URL, url);
-	curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
-	if (start_active_slot(slot)) {
-		run_active_slot(slot);
-		if (results.curl_result != CURLE_OK) {
-			if (missing_target(&results)) {
-				repo->got_indices = 1;
-				free(buffer.buffer);
-				return 0;
-			} else {
-				repo->got_indices = 0;
-				free(buffer.buffer);
-				return error("%s", curl_errorstr);
-			}
-		}
-	} else {
-		repo->got_indices = 0;
-		free(buffer.buffer);
-		return error("Unable to start request");
-	}
-
-	data = buffer.buffer;
-	while (i < buffer.posn) {
-		switch (data[i]) {
-		case 'P':
-			i++;
-			if (i + 52 <= buffer.posn &&
-			    !prefixcmp(data + i, " pack-") &&
-			    !prefixcmp(data + i + 46, ".pack\n")) {
-				get_sha1_hex(data + i + 6, sha1);
-				setup_index(repo, sha1);
-				i += 51;
-				break;
-			}
-		default:
-			while (i < buffer.posn && data[i] != '\n')
-				i++;
-		}
-		i++;
-	}
-
-	free(buffer.buffer);
-	repo->got_indices = 1;
-	return 0;
-}
-
-static int fetch_pack(struct alt_base *repo, unsigned char *sha1)
-{
-	char *url;
-	struct packed_git *target;
-	struct packed_git **lst;
-	FILE *packfile;
-	char *filename;
-	char tmpfile[PATH_MAX];
-	int ret;
-	long prev_posn = 0;
-	char range[RANGE_HEADER_SIZE];
-	struct curl_slist *range_header = NULL;
-
-	struct active_request_slot *slot;
-	struct slot_results results;
-
-	if (fetch_indices(repo))
-		return -1;
-	target = find_sha1_pack(sha1, repo->packs);
-	if (!target)
-		return -1;
-
-	if (get_verbosely) {
-		fprintf(stderr, "Getting pack %s\n",
-			sha1_to_hex(target->sha1));
-		fprintf(stderr, " which contains %s\n",
-			sha1_to_hex(sha1));
-	}
-
-	url = xmalloc(strlen(repo->base) + 65);
-	sprintf(url, "%s/objects/pack/pack-%s.pack",
-		repo->base, sha1_to_hex(target->sha1));
-
-	filename = sha1_pack_name(target->sha1);
-	snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
-	packfile = fopen(tmpfile, "a");
-	if (!packfile)
-		return error("Unable to open local file %s for pack",
-			     filename);
-
-	slot = get_active_slot();
-	slot->results = &results;
-	curl_easy_setopt(slot->curl, CURLOPT_FILE, packfile);
-	curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
-	curl_easy_setopt(slot->curl, CURLOPT_URL, url);
-	curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
-	slot->local = packfile;
-
-	/* If there is data present from a previous transfer attempt,
-	   resume where it left off */
-	prev_posn = ftell(packfile);
-	if (prev_posn>0) {
-		if (get_verbosely)
-			fprintf(stderr,
-				"Resuming fetch of pack %s at byte %ld\n",
-				sha1_to_hex(target->sha1), prev_posn);
-		sprintf(range, "Range: bytes=%ld-", prev_posn);
-		range_header = curl_slist_append(range_header, range);
-		curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header);
-	}
-
-	if (start_active_slot(slot)) {
-		run_active_slot(slot);
-		if (results.curl_result != CURLE_OK) {
-			fclose(packfile);
-			return error("Unable to get pack file %s\n%s", url,
-				     curl_errorstr);
-		}
-	} else {
-		fclose(packfile);
-		return error("Unable to start request");
-	}
-
-	target->pack_size = ftell(packfile);
-	fclose(packfile);
-
-	ret = move_temp_to_file(tmpfile, filename);
-	if (ret)
-		return ret;
-
-	lst = &repo->packs;
-	while (*lst != target)
-		lst = &((*lst)->next);
-	*lst = (*lst)->next;
-
-	if (verify_pack(target, 0))
-		return -1;
-	install_packed_git(target);
-
-	return 0;
-}
-
-static void abort_object_request(struct object_request *obj_req)
-{
-	if (obj_req->local >= 0) {
-		close(obj_req->local);
-		obj_req->local = -1;
-	}
-	unlink(obj_req->tmpfile);
-	if (obj_req->slot) {
-		release_active_slot(obj_req->slot);
-		obj_req->slot = NULL;
-	}
-	release_object_request(obj_req);
-}
-
-static int fetch_object(struct alt_base *repo, unsigned char *sha1)
-{
-	char *hex = sha1_to_hex(sha1);
-	int ret = 0;
-	struct object_request *obj_req = object_queue_head;
-
-	while (obj_req != NULL && hashcmp(obj_req->sha1, sha1))
-		obj_req = obj_req->next;
-	if (obj_req == NULL)
-		return error("Couldn't find request for %s in the queue", hex);
-
-	if (has_sha1_file(obj_req->sha1)) {
-		abort_object_request(obj_req);
-		return 0;
-	}
-
-#ifdef USE_CURL_MULTI
-	while (obj_req->state == WAITING) {
-		step_active_slots();
-	}
-#else
-	start_object_request(obj_req);
-#endif
-
-	while (obj_req->state == ACTIVE) {
-		run_active_slot(obj_req->slot);
-	}
-	if (obj_req->local != -1) {
-		close(obj_req->local); obj_req->local = -1;
-	}
-
-	if (obj_req->state == ABORTED) {
-		ret = error("Request for %s aborted", hex);
-	} else if (obj_req->curl_result != CURLE_OK &&
-		   obj_req->http_code != 416) {
-		if (missing_target(obj_req))
-			ret = -1; /* Be silent, it is probably in a pack. */
-		else
-			ret = error("%s (curl_result = %d, http_code = %ld, sha1 = %s)",
-				    obj_req->errorstr, obj_req->curl_result,
-				    obj_req->http_code, hex);
-	} else if (obj_req->zret != Z_STREAM_END) {
-		corrupt_object_found++;
-		ret = error("File %s (%s) corrupt", hex, obj_req->url);
-	} else if (hashcmp(obj_req->sha1, obj_req->real_sha1)) {
-		ret = error("File %s has bad hash", hex);
-	} else if (obj_req->rename < 0) {
-		ret = error("unable to write sha1 filename %s",
-			    obj_req->filename);
-	}
-
-	release_object_request(obj_req);
-	return ret;
-}
-
-int fetch(unsigned char *sha1)
-{
-	struct alt_base *altbase = alt;
-
-	if (!fetch_object(altbase, sha1))
-		return 0;
-	while (altbase) {
-		if (!fetch_pack(altbase, sha1))
-			return 0;
-		fetch_alternates(alt->base);
-		altbase = altbase->next;
-	}
-	return error("Unable to find %s under %s", sha1_to_hex(sha1),
-		     alt->base);
-}
-
-static inline int needs_quote(int ch)
-{
-	if (((ch >= 'A') && (ch <= 'Z'))
-			|| ((ch >= 'a') && (ch <= 'z'))
-			|| ((ch >= '0') && (ch <= '9'))
-			|| (ch == '/')
-			|| (ch == '-')
-			|| (ch == '.'))
-		return 0;
-	return 1;
-}
-
-static inline int hex(int v)
-{
-	if (v < 10) return '0' + v;
-	else return 'A' + v - 10;
-}
-
-static char *quote_ref_url(const char *base, const char *ref)
-{
-	const char *cp;
-	char *dp, *qref;
-	int len, baselen, ch;
-
-	baselen = strlen(base);
-	len = baselen + 7; /* "/refs/" + NUL */
-	for (cp = ref; (ch = *cp) != 0; cp++, len++)
-		if (needs_quote(ch))
-			len += 2; /* extra two hex plus replacement % */
-	qref = xmalloc(len);
-	memcpy(qref, base, baselen);
-	memcpy(qref + baselen, "/refs/", 6);
-	for (cp = ref, dp = qref + baselen + 6; (ch = *cp) != 0; cp++) {
-		if (needs_quote(ch)) {
-			*dp++ = '%';
-			*dp++ = hex((ch >> 4) & 0xF);
-			*dp++ = hex(ch & 0xF);
-		}
-		else
-			*dp++ = ch;
-	}
-	*dp = 0;
-
-	return qref;
-}
-
-int fetch_ref(char *ref, unsigned char *sha1)
-{
-        char *url;
-        char hex[42];
-        struct buffer buffer;
-	const char *base = alt->base;
-	struct active_request_slot *slot;
-	struct slot_results results;
-        buffer.size = 41;
-        buffer.posn = 0;
-        buffer.buffer = hex;
-        hex[41] = '\0';
-
-	url = quote_ref_url(base, ref);
-	slot = get_active_slot();
-	slot->results = &results;
-	curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
-	curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
-	curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
-	curl_easy_setopt(slot->curl, CURLOPT_URL, url);
-	if (start_active_slot(slot)) {
-		run_active_slot(slot);
-		if (results.curl_result != CURLE_OK)
-			return error("Couldn't get %s for %s\n%s",
-				     url, ref, curl_errorstr);
-	} else {
-		return error("Unable to start request");
-	}
-
-        hex[40] = '\0';
-        get_sha1_hex(hex, sha1);
-        return 0;
-}
-
-int main(int argc, const char **argv)
-{
-	int commits;
-	const char **write_ref = NULL;
-	char **commit_id;
-	const char *url;
-	char *s;
-	int arg = 1;
-	int rc = 0;
-
-	setup_git_directory();
-	git_config(git_default_config);
-
-	while (arg < argc && argv[arg][0] == '-') {
-		if (argv[arg][1] == 't') {
-			get_tree = 1;
-		} else if (argv[arg][1] == 'c') {
-			get_history = 1;
-		} else if (argv[arg][1] == 'a') {
-			get_all = 1;
-			get_tree = 1;
-			get_history = 1;
-		} else if (argv[arg][1] == 'v') {
-			get_verbosely = 1;
-		} else if (argv[arg][1] == 'w') {
-			write_ref = &argv[arg + 1];
-			arg++;
-		} else if (!strcmp(argv[arg], "--recover")) {
-			get_recover = 1;
-		} else if (!strcmp(argv[arg], "--stdin")) {
-			commits_on_stdin = 1;
-		}
-		arg++;
-	}
-	if (argc < arg + 2 - commits_on_stdin) {
-		usage("git-http-fetch [-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin] commit-id url");
-		return 1;
-	}
-	if (commits_on_stdin) {
-		commits = pull_targets_stdin(&commit_id, &write_ref);
-	} else {
-		commit_id = (char **) &argv[arg++];
-		commits = 1;
-	}
-	url = argv[arg];
-
-	http_init();
-
-	no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:");
-
-	alt = xmalloc(sizeof(*alt));
-	alt->base = xmalloc(strlen(url) + 1);
-	strcpy(alt->base, url);
-	for (s = alt->base + strlen(alt->base) - 1; *s == '/'; --s)
-		*s = 0;
-	alt->got_indices = 0;
-	alt->packs = NULL;
-	alt->next = NULL;
-
-	if (pull(commits, commit_id, write_ref, url))
-		rc = 1;
-
-	http_cleanup();
-
-	curl_slist_free_all(no_pragma_header);
-
-	if (commits_on_stdin)
-		pull_targets_free(commits, commit_id, write_ref);
-
-	if (corrupt_object_found) {
-		fprintf(stderr,
-"Some loose object were found to be corrupt, but they might be just\n"
-"a false '404 Not Found' error message sent with incorrect HTTP\n"
-"status code.  Suggest running git-fsck.\n");
-	}
-	return rc;
-}
diff --git a/http-push.c b/http-push.c
index 7c3720f..1e9cf89 100644
--- a/http-push.c
+++ b/http-push.c
@@ -1,7 +1,6 @@
 #include "cache.h"
 #include "commit.h"
 #include "pack.h"
-#include "fetch.h"
 #include "tag.h"
 #include "blob.h"
 #include "http.h"
diff --git a/http-walker.c b/http-walker.c
new file mode 100644
index 0000000..4bd42f2
--- /dev/null
+++ b/http-walker.c
@@ -0,0 +1,1038 @@
+#include "cache.h"
+#include "commit.h"
+#include "pack.h"
+#include "walker.h"
+#include "http.h"
+
+#define PREV_BUF_SIZE 4096
+#define RANGE_HEADER_SIZE 30
+
+struct alt_base
+{
+	char *base;
+	int got_indices;
+	struct packed_git *packs;
+	struct alt_base *next;
+};
+
+enum object_request_state {
+	WAITING,
+	ABORTED,
+	ACTIVE,
+	COMPLETE,
+};
+
+struct object_request
+{
+	struct walker *walker;
+	unsigned char sha1[20];
+	struct alt_base *repo;
+	char *url;
+	char filename[PATH_MAX];
+	char tmpfile[PATH_MAX];
+	int local;
+	enum object_request_state state;
+	CURLcode curl_result;
+	char errorstr[CURL_ERROR_SIZE];
+	long http_code;
+	unsigned char real_sha1[20];
+	SHA_CTX c;
+	z_stream stream;
+	int zret;
+	int rename;
+	struct active_request_slot *slot;
+	struct object_request *next;
+};
+
+struct alternates_request {
+	struct walker *walker;
+	const char *base;
+	char *url;
+	struct buffer *buffer;
+	struct active_request_slot *slot;
+	int http_specific;
+};
+
+struct walker_data {
+	const char *url;
+	int got_alternates;
+	struct alt_base *alt;
+	struct curl_slist *no_pragma_header;
+};
+
+static struct object_request *object_queue_head;
+
+static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
+			       void *data)
+{
+	unsigned char expn[4096];
+	size_t size = eltsize * nmemb;
+	int posn = 0;
+	struct object_request *obj_req = (struct object_request *)data;
+	do {
+		ssize_t retval = xwrite(obj_req->local,
+				       (char *) ptr + posn, size - posn);
+		if (retval < 0)
+			return posn;
+		posn += retval;
+	} while (posn < size);
+
+	obj_req->stream.avail_in = size;
+	obj_req->stream.next_in = ptr;
+	do {
+		obj_req->stream.next_out = expn;
+		obj_req->stream.avail_out = sizeof(expn);
+		obj_req->zret = inflate(&obj_req->stream, Z_SYNC_FLUSH);
+		SHA1_Update(&obj_req->c, expn,
+			    sizeof(expn) - obj_req->stream.avail_out);
+	} while (obj_req->stream.avail_in && obj_req->zret == Z_OK);
+	data_received++;
+	return size;
+}
+
+static int missing__target(int code, int result)
+{
+	return	/* file:// URL -- do we ever use one??? */
+		(result == CURLE_FILE_COULDNT_READ_FILE) ||
+		/* http:// and https:// URL */
+		(code == 404 && result == CURLE_HTTP_RETURNED_ERROR) ||
+		/* ftp:// URL */
+		(code == 550 && result == CURLE_FTP_COULDNT_RETR_FILE)
+		;
+}
+
+#define missing_target(a) missing__target((a)->http_code, (a)->curl_result)
+
+static void fetch_alternates(struct walker *walker, const char *base);
+
+static void process_object_response(void *callback_data);
+
+static void start_object_request(struct walker *walker,
+				 struct object_request *obj_req)
+{
+	char *hex = sha1_to_hex(obj_req->sha1);
+	char prevfile[PATH_MAX];
+	char *url;
+	char *posn;
+	int prevlocal;
+	unsigned char prev_buf[PREV_BUF_SIZE];
+	ssize_t prev_read = 0;
+	long prev_posn = 0;
+	char range[RANGE_HEADER_SIZE];
+	struct curl_slist *range_header = NULL;
+	struct active_request_slot *slot;
+	struct walker_data *data = walker->data;
+
+	snprintf(prevfile, sizeof(prevfile), "%s.prev", obj_req->filename);
+	unlink(prevfile);
+	rename(obj_req->tmpfile, prevfile);
+	unlink(obj_req->tmpfile);
+
+	if (obj_req->local != -1)
+		error("fd leakage in start: %d", obj_req->local);
+	obj_req->local = open(obj_req->tmpfile,
+			      O_WRONLY | O_CREAT | O_EXCL, 0666);
+	/* This could have failed due to the "lazy directory creation";
+	 * try to mkdir the last path component.
+	 */
+	if (obj_req->local < 0 && errno == ENOENT) {
+		char *dir = strrchr(obj_req->tmpfile, '/');
+		if (dir) {
+			*dir = 0;
+			mkdir(obj_req->tmpfile, 0777);
+			*dir = '/';
+		}
+		obj_req->local = open(obj_req->tmpfile,
+				      O_WRONLY | O_CREAT | O_EXCL, 0666);
+	}
+
+	if (obj_req->local < 0) {
+		obj_req->state = ABORTED;
+		error("Couldn't create temporary file %s for %s: %s",
+		      obj_req->tmpfile, obj_req->filename, strerror(errno));
+		return;
+	}
+
+	memset(&obj_req->stream, 0, sizeof(obj_req->stream));
+
+	inflateInit(&obj_req->stream);
+
+	SHA1_Init(&obj_req->c);
+
+	url = xmalloc(strlen(obj_req->repo->base) + 51);
+	obj_req->url = xmalloc(strlen(obj_req->repo->base) + 51);
+	strcpy(url, obj_req->repo->base);
+	posn = url + strlen(obj_req->repo->base);
+	strcpy(posn, "/objects/");
+	posn += 9;
+	memcpy(posn, hex, 2);
+	posn += 2;
+	*(posn++) = '/';
+	strcpy(posn, hex + 2);
+	strcpy(obj_req->url, url);
+
+	/* If a previous temp file is present, process what was already
+	   fetched. */
+	prevlocal = open(prevfile, O_RDONLY);
+	if (prevlocal != -1) {
+		do {
+			prev_read = xread(prevlocal, prev_buf, PREV_BUF_SIZE);
+			if (prev_read>0) {
+				if (fwrite_sha1_file(prev_buf,
+						     1,
+						     prev_read,
+						     obj_req) == prev_read) {
+					prev_posn += prev_read;
+				} else {
+					prev_read = -1;
+				}
+			}
+		} while (prev_read > 0);
+		close(prevlocal);
+	}
+	unlink(prevfile);
+
+	/* Reset inflate/SHA1 if there was an error reading the previous temp
+	   file; also rewind to the beginning of the local file. */
+	if (prev_read == -1) {
+		memset(&obj_req->stream, 0, sizeof(obj_req->stream));
+		inflateInit(&obj_req->stream);
+		SHA1_Init(&obj_req->c);
+		if (prev_posn>0) {
+			prev_posn = 0;
+			lseek(obj_req->local, 0, SEEK_SET);
+			ftruncate(obj_req->local, 0);
+		}
+	}
+
+	slot = get_active_slot();
+	slot->callback_func = process_object_response;
+	slot->callback_data = obj_req;
+	obj_req->slot = slot;
+
+	curl_easy_setopt(slot->curl, CURLOPT_FILE, obj_req);
+	curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file);
+	curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, obj_req->errorstr);
+	curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+	curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, data->no_pragma_header);
+
+	/* If we have successfully processed data from a previous fetch
+	   attempt, only fetch the data we don't already have. */
+	if (prev_posn>0) {
+		if (walker->get_verbosely)
+			fprintf(stderr,
+				"Resuming fetch of object %s at byte %ld\n",
+				hex, prev_posn);
+		sprintf(range, "Range: bytes=%ld-", prev_posn);
+		range_header = curl_slist_append(range_header, range);
+		curl_easy_setopt(slot->curl,
+				 CURLOPT_HTTPHEADER, range_header);
+	}
+
+	/* Try to get the request started, abort the request on error */
+	obj_req->state = ACTIVE;
+	if (!start_active_slot(slot)) {
+		obj_req->state = ABORTED;
+		obj_req->slot = NULL;
+		close(obj_req->local); obj_req->local = -1;
+		free(obj_req->url);
+		return;
+	}
+}
+
+static void finish_object_request(struct object_request *obj_req)
+{
+	struct stat st;
+
+	fchmod(obj_req->local, 0444);
+	close(obj_req->local); obj_req->local = -1;
+
+	if (obj_req->http_code == 416) {
+		fprintf(stderr, "Warning: requested range invalid; we may already have all the data.\n");
+	} else if (obj_req->curl_result != CURLE_OK) {
+		if (stat(obj_req->tmpfile, &st) == 0)
+			if (st.st_size == 0)
+				unlink(obj_req->tmpfile);
+		return;
+	}
+
+	inflateEnd(&obj_req->stream);
+	SHA1_Final(obj_req->real_sha1, &obj_req->c);
+	if (obj_req->zret != Z_STREAM_END) {
+		unlink(obj_req->tmpfile);
+		return;
+	}
+	if (hashcmp(obj_req->sha1, obj_req->real_sha1)) {
+		unlink(obj_req->tmpfile);
+		return;
+	}
+	obj_req->rename =
+		move_temp_to_file(obj_req->tmpfile, obj_req->filename);
+
+	if (obj_req->rename == 0)
+		walker_say(obj_req->walker, "got %s\n", sha1_to_hex(obj_req->sha1));
+}
+
+static void process_object_response(void *callback_data)
+{
+	struct object_request *obj_req =
+		(struct object_request *)callback_data;
+	struct walker *walker = obj_req->walker;
+	struct walker_data *data = walker->data;
+	struct alt_base *alt = data->alt;
+
+	obj_req->curl_result = obj_req->slot->curl_result;
+	obj_req->http_code = obj_req->slot->http_code;
+	obj_req->slot = NULL;
+	obj_req->state = COMPLETE;
+
+	/* Use alternates if necessary */
+	if (missing_target(obj_req)) {
+		fetch_alternates(walker, alt->base);
+		if (obj_req->repo->next != NULL) {
+			obj_req->repo =
+				obj_req->repo->next;
+			close(obj_req->local);
+			obj_req->local = -1;
+			start_object_request(walker, obj_req);
+			return;
+		}
+	}
+
+	finish_object_request(obj_req);
+}
+
+static void release_object_request(struct object_request *obj_req)
+{
+	struct object_request *entry = object_queue_head;
+
+	if (obj_req->local != -1)
+		error("fd leakage in release: %d", obj_req->local);
+	if (obj_req == object_queue_head) {
+		object_queue_head = obj_req->next;
+	} else {
+		while (entry->next != NULL && entry->next != obj_req)
+			entry = entry->next;
+		if (entry->next == obj_req)
+			entry->next = entry->next->next;
+	}
+
+	free(obj_req->url);
+	free(obj_req);
+}
+
+#ifdef USE_CURL_MULTI
+void fill_active_slots(struct walker *walker)
+{
+	struct object_request *obj_req = object_queue_head;
+	struct active_request_slot *slot = active_queue_head;
+	int num_transfers;
+
+	while (active_requests < max_requests && obj_req != NULL) {
+		if (obj_req->state == WAITING) {
+			if (has_sha1_file(obj_req->sha1))
+				obj_req->state = COMPLETE;
+			else
+				start_object_request(walker, obj_req);
+			curl_multi_perform(curlm, &num_transfers);
+		}
+		obj_req = obj_req->next;
+	}
+
+	while (slot != NULL) {
+		if (!slot->in_use && slot->curl != NULL) {
+			curl_easy_cleanup(slot->curl);
+			slot->curl = NULL;
+		}
+		slot = slot->next;
+	}
+}
+#endif
+
+static void prefetch(struct walker *impl, unsigned char *sha1)
+{
+	struct object_request *newreq;
+	struct object_request *tail;
+	struct walker_data *data = impl->data;
+	char *filename = sha1_file_name(sha1);
+
+	newreq = xmalloc(sizeof(*newreq));
+	newreq->walker = impl;
+	hashcpy(newreq->sha1, sha1);
+	newreq->repo = data->alt;
+	newreq->url = NULL;
+	newreq->local = -1;
+	newreq->state = WAITING;
+	snprintf(newreq->filename, sizeof(newreq->filename), "%s", filename);
+	snprintf(newreq->tmpfile, sizeof(newreq->tmpfile),
+		 "%s.temp", filename);
+	newreq->slot = NULL;
+	newreq->next = NULL;
+
+	if (object_queue_head == NULL) {
+		object_queue_head = newreq;
+	} else {
+		tail = object_queue_head;
+		while (tail->next != NULL) {
+			tail = tail->next;
+		}
+		tail->next = newreq;
+	}
+
+#ifdef USE_CURL_MULTI
+	fill_active_slots();
+	step_active_slots();
+#endif
+}
+
+static int fetch_index(struct walker *walker, struct alt_base *repo, unsigned char *sha1)
+{
+	char *hex = sha1_to_hex(sha1);
+	char *filename;
+	char *url;
+	char tmpfile[PATH_MAX];
+	long prev_posn = 0;
+	char range[RANGE_HEADER_SIZE];
+	struct curl_slist *range_header = NULL;
+	struct walker_data *data = walker->data;
+
+	FILE *indexfile;
+	struct active_request_slot *slot;
+	struct slot_results results;
+
+	if (has_pack_index(sha1))
+		return 0;
+
+	if (walker->get_verbosely)
+		fprintf(stderr, "Getting index for pack %s\n", hex);
+
+	url = xmalloc(strlen(repo->base) + 64);
+	sprintf(url, "%s/objects/pack/pack-%s.idx", repo->base, hex);
+
+	filename = sha1_pack_index_name(sha1);
+	snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
+	indexfile = fopen(tmpfile, "a");
+	if (!indexfile)
+		return error("Unable to open local file %s for pack index",
+			     filename);
+
+	slot = get_active_slot();
+	slot->results = &results;
+	curl_easy_setopt(slot->curl, CURLOPT_FILE, indexfile);
+	curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
+	curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+	curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, data->no_pragma_header);
+	slot->local = indexfile;
+
+	/* If there is data present from a previous transfer attempt,
+	   resume where it left off */
+	prev_posn = ftell(indexfile);
+	if (prev_posn>0) {
+		if (walker->get_verbosely)
+			fprintf(stderr,
+				"Resuming fetch of index for pack %s at byte %ld\n",
+				hex, prev_posn);
+		sprintf(range, "Range: bytes=%ld-", prev_posn);
+		range_header = curl_slist_append(range_header, range);
+		curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header);
+	}
+
+	if (start_active_slot(slot)) {
+		run_active_slot(slot);
+		if (results.curl_result != CURLE_OK) {
+			fclose(indexfile);
+			return error("Unable to get pack index %s\n%s", url,
+				     curl_errorstr);
+		}
+	} else {
+		fclose(indexfile);
+		return error("Unable to start request");
+	}
+
+	fclose(indexfile);
+
+	return move_temp_to_file(tmpfile, filename);
+}
+
+static int setup_index(struct walker *walker, 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(walker, repo, sha1))
+		return -1;
+
+	new_pack = parse_pack_index(sha1);
+	new_pack->next = repo->packs;
+	repo->packs = new_pack;
+	return 0;
+}
+
+static void process_alternates_response(void *callback_data)
+{
+	struct alternates_request *alt_req =
+		(struct alternates_request *)callback_data;
+	struct walker *walker = alt_req->walker;
+	struct walker_data *cdata = walker->data;
+	struct active_request_slot *slot = alt_req->slot;
+	struct alt_base *tail = cdata->alt;
+	const char *base = alt_req->base;
+	static const char null_byte = '\0';
+	char *data;
+	int i = 0;
+
+	if (alt_req->http_specific) {
+		if (slot->curl_result != CURLE_OK ||
+		    !alt_req->buffer->posn) {
+
+			/* Try reusing the slot to get non-http alternates */
+			alt_req->http_specific = 0;
+			sprintf(alt_req->url, "%s/objects/info/alternates",
+				base);
+			curl_easy_setopt(slot->curl, CURLOPT_URL,
+					 alt_req->url);
+			active_requests++;
+			slot->in_use = 1;
+			if (slot->finished != NULL)
+				(*slot->finished) = 0;
+			if (!start_active_slot(slot)) {
+				cdata->got_alternates = -1;
+				slot->in_use = 0;
+				if (slot->finished != NULL)
+					(*slot->finished) = 1;
+			}
+			return;
+		}
+	} else if (slot->curl_result != CURLE_OK) {
+		if (!missing_target(slot)) {
+			cdata->got_alternates = -1;
+			return;
+		}
+	}
+
+	fwrite_buffer(&null_byte, 1, 1, alt_req->buffer);
+	alt_req->buffer->posn--;
+	data = alt_req->buffer->buffer;
+
+	while (i < alt_req->buffer->posn) {
+		int posn = i;
+		while (posn < alt_req->buffer->posn && data[posn] != '\n')
+			posn++;
+		if (data[posn] == '\n') {
+			int okay = 0;
+			int serverlen = 0;
+			struct alt_base *newalt;
+			char *target = NULL;
+			if (data[i] == '/') {
+				/* This counts
+				 * http://git.host/pub/scm/linux.git/
+				 * -----------here^
+				 * so memcpy(dst, base, serverlen) will
+				 * copy up to "...git.host".
+				 */
+				const char *colon_ss = strstr(base,"://");
+				if (colon_ss) {
+					serverlen = (strchr(colon_ss + 3, '/')
+						     - base);
+					okay = 1;
+				}
+			} else if (!memcmp(data + i, "../", 3)) {
+				/* Relative URL; chop the corresponding
+				 * number of subpath from base (and ../
+				 * from data), and concatenate the result.
+				 *
+				 * The code first drops ../ from data, and
+				 * then drops one ../ from data and one path
+				 * from base.  IOW, one extra ../ is dropped
+				 * from data than path is dropped from base.
+				 *
+				 * This is not wrong.  The alternate in
+				 *     http://git.host/pub/scm/linux.git/
+				 * to borrow from
+				 *     http://git.host/pub/scm/linus.git/
+				 * is ../../linus.git/objects/.  You need
+				 * two ../../ to borrow from your direct
+				 * neighbour.
+				 */
+				i += 3;
+				serverlen = strlen(base);
+				while (i + 2 < posn &&
+				       !memcmp(data + i, "../", 3)) {
+					do {
+						serverlen--;
+					} while (serverlen &&
+						 base[serverlen - 1] != '/');
+					i += 3;
+				}
+				/* If the server got removed, give up. */
+				okay = strchr(base, ':') - base + 3 <
+					serverlen;
+			} else if (alt_req->http_specific) {
+				char *colon = strchr(data + i, ':');
+				char *slash = strchr(data + i, '/');
+				if (colon && slash && colon < data + posn &&
+				    slash < data + posn && colon < slash) {
+					okay = 1;
+				}
+			}
+			/* skip "objects\n" at end */
+			if (okay) {
+				target = xmalloc(serverlen + posn - i - 6);
+				memcpy(target, base, serverlen);
+				memcpy(target + serverlen, data + i,
+				       posn - i - 7);
+				target[serverlen + posn - i - 7] = 0;
+				if (walker->get_verbosely)
+					fprintf(stderr,
+						"Also look at %s\n", target);
+				newalt = xmalloc(sizeof(*newalt));
+				newalt->next = NULL;
+				newalt->base = target;
+				newalt->got_indices = 0;
+				newalt->packs = NULL;
+
+				while (tail->next != NULL)
+					tail = tail->next;
+				tail->next = newalt;
+			}
+		}
+		i = posn + 1;
+	}
+
+	cdata->got_alternates = 1;
+}
+
+static void fetch_alternates(struct walker *walker, const char *base)
+{
+	struct buffer buffer;
+	char *url;
+	char *data;
+	struct active_request_slot *slot;
+	struct alternates_request alt_req;
+	struct walker_data *cdata = walker->data;
+
+	/* If another request has already started fetching alternates,
+	   wait for them to arrive and return to processing this request's
+	   curl message */
+#ifdef USE_CURL_MULTI
+	while (cdata->got_alternates == 0) {
+		step_active_slots();
+	}
+#endif
+
+	/* Nothing to do if they've already been fetched */
+	if (cdata->got_alternates == 1)
+		return;
+
+	/* Start the fetch */
+	cdata->got_alternates = 0;
+
+	data = xmalloc(4096);
+	buffer.size = 4096;
+	buffer.posn = 0;
+	buffer.buffer = data;
+
+	if (walker->get_verbosely)
+		fprintf(stderr, "Getting alternates list for %s\n", base);
+
+	url = xmalloc(strlen(base) + 31);
+	sprintf(url, "%s/objects/info/http-alternates", base);
+
+	/* Use a callback to process the result, since another request
+	   may fail and need to have alternates loaded before continuing */
+	slot = get_active_slot();
+	slot->callback_func = process_alternates_response;
+	alt_req.walker = walker;
+	slot->callback_data = &alt_req;
+
+	curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
+	curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
+	curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+
+	alt_req.base = base;
+	alt_req.url = url;
+	alt_req.buffer = &buffer;
+	alt_req.http_specific = 1;
+	alt_req.slot = slot;
+
+	if (start_active_slot(slot))
+		run_active_slot(slot);
+	else
+		cdata->got_alternates = -1;
+
+	free(data);
+	free(url);
+}
+
+static int fetch_indices(struct walker *walker, struct alt_base *repo)
+{
+	unsigned char sha1[20];
+	char *url;
+	struct buffer buffer;
+	char *data;
+	int i = 0;
+
+	struct active_request_slot *slot;
+	struct slot_results results;
+
+	if (repo->got_indices)
+		return 0;
+
+	data = xmalloc(4096);
+	buffer.size = 4096;
+	buffer.posn = 0;
+	buffer.buffer = data;
+
+	if (walker->get_verbosely)
+		fprintf(stderr, "Getting pack list for %s\n", repo->base);
+
+	url = xmalloc(strlen(repo->base) + 21);
+	sprintf(url, "%s/objects/info/packs", repo->base);
+
+	slot = get_active_slot();
+	slot->results = &results;
+	curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
+	curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
+	curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+	curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
+	if (start_active_slot(slot)) {
+		run_active_slot(slot);
+		if (results.curl_result != CURLE_OK) {
+			if (missing_target(&results)) {
+				repo->got_indices = 1;
+				free(buffer.buffer);
+				return 0;
+			} else {
+				repo->got_indices = 0;
+				free(buffer.buffer);
+				return error("%s", curl_errorstr);
+			}
+		}
+	} else {
+		repo->got_indices = 0;
+		free(buffer.buffer);
+		return error("Unable to start request");
+	}
+
+	data = buffer.buffer;
+	while (i < buffer.posn) {
+		switch (data[i]) {
+		case 'P':
+			i++;
+			if (i + 52 <= buffer.posn &&
+			    !prefixcmp(data + i, " pack-") &&
+			    !prefixcmp(data + i + 46, ".pack\n")) {
+				get_sha1_hex(data + i + 6, sha1);
+				setup_index(walker, repo, sha1);
+				i += 51;
+				break;
+			}
+		default:
+			while (i < buffer.posn && data[i] != '\n')
+				i++;
+		}
+		i++;
+	}
+
+	free(buffer.buffer);
+	repo->got_indices = 1;
+	return 0;
+}
+
+static int fetch_pack(struct walker *walker, struct alt_base *repo, unsigned char *sha1)
+{
+	char *url;
+	struct packed_git *target;
+	struct packed_git **lst;
+	FILE *packfile;
+	char *filename;
+	char tmpfile[PATH_MAX];
+	int ret;
+	long prev_posn = 0;
+	char range[RANGE_HEADER_SIZE];
+	struct curl_slist *range_header = NULL;
+	struct walker_data *data = walker->data;
+
+	struct active_request_slot *slot;
+	struct slot_results results;
+
+	if (fetch_indices(walker, repo))
+		return -1;
+	target = find_sha1_pack(sha1, repo->packs);
+	if (!target)
+		return -1;
+
+	if (walker->get_verbosely) {
+		fprintf(stderr, "Getting pack %s\n",
+			sha1_to_hex(target->sha1));
+		fprintf(stderr, " which contains %s\n",
+			sha1_to_hex(sha1));
+	}
+
+	url = xmalloc(strlen(repo->base) + 65);
+	sprintf(url, "%s/objects/pack/pack-%s.pack",
+		repo->base, sha1_to_hex(target->sha1));
+
+	filename = sha1_pack_name(target->sha1);
+	snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
+	packfile = fopen(tmpfile, "a");
+	if (!packfile)
+		return error("Unable to open local file %s for pack",
+			     filename);
+
+	slot = get_active_slot();
+	slot->results = &results;
+	curl_easy_setopt(slot->curl, CURLOPT_FILE, packfile);
+	curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
+	curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+	curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, data->no_pragma_header);
+	slot->local = packfile;
+
+	/* If there is data present from a previous transfer attempt,
+	   resume where it left off */
+	prev_posn = ftell(packfile);
+	if (prev_posn>0) {
+		if (walker->get_verbosely)
+			fprintf(stderr,
+				"Resuming fetch of pack %s at byte %ld\n",
+				sha1_to_hex(target->sha1), prev_posn);
+		sprintf(range, "Range: bytes=%ld-", prev_posn);
+		range_header = curl_slist_append(range_header, range);
+		curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header);
+	}
+
+	if (start_active_slot(slot)) {
+		run_active_slot(slot);
+		if (results.curl_result != CURLE_OK) {
+			fclose(packfile);
+			return error("Unable to get pack file %s\n%s", url,
+				     curl_errorstr);
+		}
+	} else {
+		fclose(packfile);
+		return error("Unable to start request");
+	}
+
+	target->pack_size = ftell(packfile);
+	fclose(packfile);
+
+	ret = move_temp_to_file(tmpfile, filename);
+	if (ret)
+		return ret;
+
+	lst = &repo->packs;
+	while (*lst != target)
+		lst = &((*lst)->next);
+	*lst = (*lst)->next;
+
+	if (verify_pack(target, 0))
+		return -1;
+	install_packed_git(target);
+
+	return 0;
+}
+
+static void abort_object_request(struct object_request *obj_req)
+{
+	if (obj_req->local >= 0) {
+		close(obj_req->local);
+		obj_req->local = -1;
+	}
+	unlink(obj_req->tmpfile);
+	if (obj_req->slot) {
+		release_active_slot(obj_req->slot);
+		obj_req->slot = NULL;
+	}
+	release_object_request(obj_req);
+}
+
+static int fetch_object(struct walker *walker, struct alt_base *repo, unsigned char *sha1)
+{
+	char *hex = sha1_to_hex(sha1);
+	int ret = 0;
+	struct object_request *obj_req = object_queue_head;
+
+	while (obj_req != NULL && hashcmp(obj_req->sha1, sha1))
+		obj_req = obj_req->next;
+	if (obj_req == NULL)
+		return error("Couldn't find request for %s in the queue", hex);
+
+	if (has_sha1_file(obj_req->sha1)) {
+		abort_object_request(obj_req);
+		return 0;
+	}
+
+#ifdef USE_CURL_MULTI
+	while (obj_req->state == WAITING) {
+		step_active_slots();
+	}
+#else
+	start_object_request(walker, obj_req);
+#endif
+
+	while (obj_req->state == ACTIVE) {
+		run_active_slot(obj_req->slot);
+	}
+	if (obj_req->local != -1) {
+		close(obj_req->local); obj_req->local = -1;
+	}
+
+	if (obj_req->state == ABORTED) {
+		ret = error("Request for %s aborted", hex);
+	} else if (obj_req->curl_result != CURLE_OK &&
+		   obj_req->http_code != 416) {
+		if (missing_target(obj_req))
+			ret = -1; /* Be silent, it is probably in a pack. */
+		else
+			ret = error("%s (curl_result = %d, http_code = %ld, sha1 = %s)",
+				    obj_req->errorstr, obj_req->curl_result,
+				    obj_req->http_code, hex);
+	} else if (obj_req->zret != Z_STREAM_END) {
+		walker->corrupt_object_found++;
+		ret = error("File %s (%s) corrupt", hex, obj_req->url);
+	} else if (hashcmp(obj_req->sha1, obj_req->real_sha1)) {
+		ret = error("File %s has bad hash", hex);
+	} else if (obj_req->rename < 0) {
+		ret = error("unable to write sha1 filename %s",
+			    obj_req->filename);
+	}
+
+	release_object_request(obj_req);
+	return ret;
+}
+
+static int fetch(struct walker *impl, unsigned char *sha1)
+{
+	struct walker_data *data = impl->data;
+	struct alt_base *altbase = data->alt;
+
+	if (!fetch_object(impl, altbase, sha1))
+		return 0;
+	while (altbase) {
+		if (!fetch_pack(impl, altbase, sha1))
+			return 0;
+		fetch_alternates(impl, data->alt->base);
+		altbase = altbase->next;
+	}
+	return error("Unable to find %s under %s", sha1_to_hex(sha1),
+		     data->alt->base);
+}
+
+static inline int needs_quote(int ch)
+{
+	if (((ch >= 'A') && (ch <= 'Z'))
+			|| ((ch >= 'a') && (ch <= 'z'))
+			|| ((ch >= '0') && (ch <= '9'))
+			|| (ch == '/')
+			|| (ch == '-')
+			|| (ch == '.'))
+		return 0;
+	return 1;
+}
+
+static inline int hex(int v)
+{
+	if (v < 10) return '0' + v;
+	else return 'A' + v - 10;
+}
+
+static char *quote_ref_url(const char *base, const char *ref)
+{
+	const char *cp;
+	char *dp, *qref;
+	int len, baselen, ch;
+
+	baselen = strlen(base);
+	len = baselen + 7; /* "/refs/" + NUL */
+	for (cp = ref; (ch = *cp) != 0; cp++, len++)
+		if (needs_quote(ch))
+			len += 2; /* extra two hex plus replacement % */
+	qref = xmalloc(len);
+	memcpy(qref, base, baselen);
+	memcpy(qref + baselen, "/refs/", 6);
+	for (cp = ref, dp = qref + baselen + 6; (ch = *cp) != 0; cp++) {
+		if (needs_quote(ch)) {
+			*dp++ = '%';
+			*dp++ = hex((ch >> 4) & 0xF);
+			*dp++ = hex(ch & 0xF);
+		}
+		else
+			*dp++ = ch;
+	}
+	*dp = 0;
+
+	return qref;
+}
+
+static int fetch_ref(struct walker *impl, char *ref, unsigned char *sha1)
+{
+        char *url;
+        char hex[42];
+        struct buffer buffer;
+	struct walker_data *data = impl->data;
+	const char *base = data->alt->base;
+	struct active_request_slot *slot;
+	struct slot_results results;
+        buffer.size = 41;
+        buffer.posn = 0;
+        buffer.buffer = hex;
+        hex[41] = '\0';
+
+	url = quote_ref_url(base, ref);
+	slot = get_active_slot();
+	slot->results = &results;
+	curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
+	curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
+	curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
+	curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+	if (start_active_slot(slot)) {
+		run_active_slot(slot);
+		if (results.curl_result != CURLE_OK)
+			return error("Couldn't get %s for %s\n%s",
+				     url, ref, curl_errorstr);
+	} else {
+		return error("Unable to start request");
+	}
+
+        hex[40] = '\0';
+        get_sha1_hex(hex, sha1);
+        return 0;
+}
+
+static void cleanup(struct walker *walker)
+{
+	struct walker_data *data = walker->data;
+	http_cleanup();
+
+	curl_slist_free_all(data->no_pragma_header);
+}
+
+struct walker *get_http_walker(const char *url)
+{
+	char *s;
+	struct walker_data *data = xmalloc(sizeof(struct walker_data));
+	struct walker *walker = xmalloc(sizeof(struct walker));
+
+	http_init();
+
+	data->no_pragma_header = curl_slist_append(NULL, "Pragma:");
+
+	data->alt = xmalloc(sizeof(*data->alt));
+	data->alt->base = xmalloc(strlen(url) + 1);
+	strcpy(data->alt->base, url);
+	for (s = data->alt->base + strlen(data->alt->base) - 1; *s == '/'; --s)
+		*s = 0;
+	data->alt->got_indices = 0;
+	data->alt->packs = NULL;
+	data->alt->next = NULL;
+	data->got_alternates = -1;
+
+	walker->corrupt_object_found = 0;
+	walker->fetch = fetch;
+	walker->fetch_ref = fetch_ref;
+	walker->prefetch = prefetch;
+	walker->cleanup = cleanup;
+	walker->data = data;
+	return walker;
+}
diff --git a/walker.c b/walker.c
new file mode 100644
index 0000000..5c65ea4
--- /dev/null
+++ b/walker.c
@@ -0,0 +1,318 @@
+#include "cache.h"
+#include "walker.h"
+#include "commit.h"
+#include "tree.h"
+#include "tree-walk.h"
+#include "tag.h"
+#include "blob.h"
+#include "refs.h"
+#include "strbuf.h"
+
+static unsigned char current_commit_sha1[20];
+
+void walker_say(struct walker *walker, const char *fmt, const char *hex)
+{
+	if (walker->get_verbosely)
+		fprintf(stderr, fmt, hex);
+}
+
+static void report_missing(const struct object *obj)
+{
+	char missing_hex[41];
+	strcpy(missing_hex, sha1_to_hex(obj->sha1));;
+	fprintf(stderr, "Cannot obtain needed %s %s\n",
+		obj->type ? typename(obj->type): "object", missing_hex);
+	if (!is_null_sha1(current_commit_sha1))
+		fprintf(stderr, "while processing commit %s.\n",
+			sha1_to_hex(current_commit_sha1));
+}
+
+static int process(struct walker *walker, struct object *obj);
+
+static int process_tree(struct walker *walker, struct tree *tree)
+{
+	struct tree_desc desc;
+	struct name_entry entry;
+
+	if (parse_tree(tree))
+		return -1;
+
+	init_tree_desc(&desc, tree->buffer, tree->size);
+	while (tree_entry(&desc, &entry)) {
+		struct object *obj = NULL;
+
+		/* submodule commits are not stored in the superproject */
+		if (S_ISGITLINK(entry.mode))
+			continue;
+		if (S_ISDIR(entry.mode)) {
+			struct tree *tree = lookup_tree(entry.sha1);
+			if (tree)
+				obj = &tree->object;
+		}
+		else {
+			struct blob *blob = lookup_blob(entry.sha1);
+			if (blob)
+				obj = &blob->object;
+		}
+		if (!obj || process(walker, obj))
+			return -1;
+	}
+	free(tree->buffer);
+	tree->buffer = NULL;
+	tree->size = 0;
+	return 0;
+}
+
+#define COMPLETE	(1U << 0)
+#define SEEN		(1U << 1)
+#define TO_SCAN		(1U << 2)
+
+static struct commit_list *complete = NULL;
+
+static int process_commit(struct walker *walker, 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;
+
+	hashcpy(current_commit_sha1, commit->object.sha1);
+
+	walker_say(walker, "walk %s\n", sha1_to_hex(commit->object.sha1));
+
+	if (walker->get_tree) {
+		if (process(walker, &commit->tree->object))
+			return -1;
+		if (!walker->get_all)
+			walker->get_tree = 0;
+	}
+	if (walker->get_history) {
+		struct commit_list *parents = commit->parents;
+		for (; parents; parents = parents->next) {
+			if (process(walker, &parents->item->object))
+				return -1;
+		}
+	}
+	return 0;
+}
+
+static int process_tag(struct walker *walker, struct tag *tag)
+{
+	if (parse_tag(tag))
+		return -1;
+	return process(walker, tag->tagged);
+}
+
+static struct object_list *process_queue = NULL;
+static struct object_list **process_queue_end = &process_queue;
+
+static int process_object(struct walker *walker, struct object *obj)
+{
+	if (obj->type == OBJ_COMMIT) {
+		if (process_commit(walker, (struct commit *)obj))
+			return -1;
+		return 0;
+	}
+	if (obj->type == OBJ_TREE) {
+		if (process_tree(walker, (struct tree *)obj))
+			return -1;
+		return 0;
+	}
+	if (obj->type == OBJ_BLOB) {
+		return 0;
+	}
+	if (obj->type == OBJ_TAG) {
+		if (process_tag(walker, (struct tag *)obj))
+			return -1;
+		return 0;
+	}
+	return error("Unable to determine requirements "
+		     "of type %s for %s",
+		     typename(obj->type), sha1_to_hex(obj->sha1));
+}
+
+static int process(struct walker *walker, struct object *obj)
+{
+	if (obj->flags & SEEN)
+		return 0;
+	obj->flags |= SEEN;
+
+	if (has_sha1_file(obj->sha1)) {
+		/* We already have it, so we should scan it now. */
+		obj->flags |= TO_SCAN;
+	}
+	else {
+		if (obj->flags & COMPLETE)
+			return 0;
+		walker->prefetch(walker, obj->sha1);
+	}
+
+	object_list_insert(obj, process_queue_end);
+	process_queue_end = &(*process_queue_end)->next;
+	return 0;
+}
+
+static int loop(struct walker *walker)
+{
+	struct object_list *elem;
+
+	while (process_queue) {
+		struct object *obj = process_queue->item;
+		elem = process_queue;
+		process_queue = elem->next;
+		free(elem);
+		if (!process_queue)
+			process_queue_end = &process_queue;
+
+		/* 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 (walker->fetch(walker, obj->sha1)) {
+				report_missing(obj);
+				return -1;
+			}
+		}
+		if (!obj->type)
+			parse_object(obj->sha1);
+		if (process_object(walker, obj))
+			return -1;
+	}
+	return 0;
+}
+
+static int interpret_target(struct walker *walker, char *target, unsigned char *sha1)
+{
+	if (!get_sha1_hex(target, sha1))
+		return 0;
+	if (!check_ref_format(target)) {
+		if (!walker->fetch_ref(walker, target, sha1)) {
+			return 0;
+		}
+	}
+	return -1;
+}
+
+static int mark_complete(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+{
+	struct commit *commit = lookup_commit_reference_gently(sha1, 1);
+	if (commit) {
+		commit->object.flags |= COMPLETE;
+		insert_by_date(commit, &complete);
+	}
+	return 0;
+}
+
+int walker_targets_stdin(char ***target, const char ***write_ref)
+{
+	int targets = 0, targets_alloc = 0;
+	struct strbuf buf;
+	*target = NULL; *write_ref = NULL;
+	strbuf_init(&buf);
+	while (1) {
+		char *rf_one = NULL;
+		char *tg_one;
+
+		read_line(&buf, stdin, '\n');
+		if (buf.eof)
+			break;
+		tg_one = buf.buf;
+		rf_one = strchr(tg_one, '\t');
+		if (rf_one)
+			*rf_one++ = 0;
+
+		if (targets >= targets_alloc) {
+			targets_alloc = targets_alloc ? targets_alloc * 2 : 64;
+			*target = xrealloc(*target, targets_alloc * sizeof(**target));
+			*write_ref = xrealloc(*write_ref, targets_alloc * sizeof(**write_ref));
+		}
+		(*target)[targets] = xstrdup(tg_one);
+		(*write_ref)[targets] = rf_one ? xstrdup(rf_one) : NULL;
+		targets++;
+	}
+	return targets;
+}
+
+void walker_targets_free(int targets, char **target, const char **write_ref)
+{
+	while (targets--) {
+		free(target[targets]);
+		if (write_ref && write_ref[targets])
+			free((char *) write_ref[targets]);
+	}
+}
+
+int walker_fetch(struct walker *walker, int targets, char **target,
+		 const char **write_ref, const char *write_ref_log_details)
+{
+	struct ref_lock **lock = xcalloc(targets, sizeof(struct ref_lock *));
+	unsigned char *sha1 = xmalloc(targets * 20);
+	char *msg;
+	int ret;
+	int i;
+
+	save_commit_buffer = 0;
+	track_object_refs = 0;
+
+	for (i = 0; i < targets; i++) {
+		if (!write_ref || !write_ref[i])
+			continue;
+
+		lock[i] = lock_ref_sha1(write_ref[i], NULL);
+		if (!lock[i]) {
+			error("Can't lock ref %s", write_ref[i]);
+			goto unlock_and_fail;
+		}
+	}
+
+	if (!walker->get_recover)
+		for_each_ref(mark_complete, NULL);
+
+	for (i = 0; i < targets; i++) {
+		if (interpret_target(walker, target[i], &sha1[20 * i])) {
+			error("Could not interpret %s as something to pull", target[i]);
+			goto unlock_and_fail;
+		}
+		if (process(walker, lookup_unknown_object(&sha1[20 * i])))
+			goto unlock_and_fail;
+	}
+
+	if (loop(walker))
+		goto unlock_and_fail;
+
+	if (write_ref_log_details) {
+		msg = xmalloc(strlen(write_ref_log_details) + 12);
+		sprintf(msg, "fetch from %s", write_ref_log_details);
+	} else {
+		msg = NULL;
+	}
+	for (i = 0; i < targets; i++) {
+		if (!write_ref || !write_ref[i])
+			continue;
+		ret = write_ref_sha1(lock[i], &sha1[20 * i], msg ? msg : "fetch (unknown)");
+		lock[i] = NULL;
+		if (ret)
+			goto unlock_and_fail;
+	}
+	free(msg);
+
+	return 0;
+
+unlock_and_fail:
+	for (i = 0; i < targets; i++)
+		if (lock[i])
+			unlock_ref(lock[i]);
+
+	return -1;
+}
+
+void walker_free(struct walker *walker)
+{
+	walker->cleanup(walker);
+	free(walker);
+}
diff --git a/walker.h b/walker.h
new file mode 100644
index 0000000..ea2c363
--- /dev/null
+++ b/walker.h
@@ -0,0 +1,37 @@
+#ifndef WALKER_H
+#define WALKER_H
+
+struct walker {
+	void *data;
+	int (*fetch_ref)(struct walker *, char *ref, unsigned char *sha1);
+	void (*prefetch)(struct walker *, unsigned char *sha1);
+	int (*fetch)(struct walker *, unsigned char *sha1);
+	void (*cleanup)(struct walker *);
+	int get_tree;
+	int get_history;
+	int get_all;
+	int get_verbosely;
+	int get_recover;
+
+	int corrupt_object_found;
+};
+
+/* Report what we got under get_verbosely */
+void walker_say(struct walker *walker, const char *, const char *);
+
+/* Load pull targets from stdin */
+int walker_targets_stdin(char ***target, const char ***write_ref);
+
+/* Free up loaded targets */
+void walker_targets_free(int targets, char **target, const char **write_ref);
+
+/* If write_ref is set, the ref filename to write the target value to. */
+/* If write_ref_log_details is set, additional text will appear in the ref log. */
+int walker_fetch(struct walker *impl, int targets, char **target,
+		 const char **write_ref, const char *write_ref_log_details);
+
+void walker_free(struct walker *walker);
+
+struct walker *get_http_walker(const char *url);
+
+#endif /* WALKER_H */
-- 
1.5.2.2.1391.gcad2-dirty

^ permalink raw reply related

* Re: [PATCH 1/2] Remove obsolete commit-walkers
From: Junio C Hamano @ 2007-07-03  6:22 UTC (permalink / raw)
  To: Daniel Barkalow; +Cc: git
In-Reply-To: <Pine.LNX.4.64.0707030128110.14638@iabervon.org>

I'll apply this after 1.5.3.  In the meantime, I'll do this for 1.5.3.

diff --git a/Documentation/git-local-fetch.txt b/Documentation/git-local-fetch.txt
index 19b5f88..141b767 100644
--- a/Documentation/git-local-fetch.txt
+++ b/Documentation/git-local-fetch.txt
@@ -14,6 +14,8 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
+THIS COMMAND IS DEPRECATED.
+
 Duplicates another git repository on a local system.
 
 OPTIONS
diff --git a/Documentation/git-ssh-fetch.txt b/Documentation/git-ssh-fetch.txt
index aaf3db0..8d3e2ff 100644
--- a/Documentation/git-ssh-fetch.txt
+++ b/Documentation/git-ssh-fetch.txt
@@ -13,6 +13,8 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
+THIS COMMAND IS DEPRECATED.
+
 Pulls from a remote repository over ssh connection, invoking
 git-ssh-upload on the other end. It functions identically to
 git-ssh-upload, aside from which end you run it on.
diff --git a/Documentation/git-ssh-upload.txt b/Documentation/git-ssh-upload.txt
index 4796224..5e2ca8d 100644
--- a/Documentation/git-ssh-upload.txt
+++ b/Documentation/git-ssh-upload.txt
@@ -12,6 +12,8 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
+THIS COMMAND IS DEPRECATED.
+
 Pushes from a remote repository over ssh connection, invoking
 git-ssh-fetch on the other end. It functions identically to
 git-ssh-fetch, aside from which end you run it on.

^ permalink raw reply related

* Re: git-rm isn't the inverse action of git-add
From: Junio C Hamano @ 2007-07-03  6:26 UTC (permalink / raw)
  To: Jeff King; +Cc: Christian Jaeger, Yann Dirson, Johannes Schindelin, git
In-Reply-To: <20070703051254.GA6477@coredump.intra.peff.net>

Jeff King <peff@peff.net> writes:

> On Mon, Jul 02, 2007 at 10:09:35PM -0700, Junio C Hamano wrote:
>
>> Exactly.  And not considering that lossage helps us keep our
>> sanity.  I think "git rm --cached" falls into the same
>> category.  If the user wants to discard what is in the index
>> without losing a copy in the working tree, I think we should let
>> him do without fuss.
>
> OK. So should we _remove_ the safety valve in all cases where we're just
> losing stuff that's in the index? It is, after all, recoverable. Should
> there be a warning (I suspect it would get annoying very quickly)?

I personally do not think we would need any safety check for
"git rm --cached", as it does not touch the working tree.  For
non-cached case I think the current behaviour is fine.

But I should warn you that I rarely use "git rm" myself.

^ permalink raw reply

* Re: [PATCH] git-mergetool: add support for ediff
From: Sam Vilain @ 2007-07-03  6:27 UTC (permalink / raw)
  To: Theodore Tso; +Cc: Jason Sewall, Junio C Hamano, git
In-Reply-To: <20070703010955.GA5322@thunk.org>

Theodore Tso wrote:
> I'm just exploring alternatives.  Basically, it just seems interesting
> that ediff has a lot of nice features, but also has some incredibly
> user-hostile features.  The first time I tried using ediff, I indeed
> tried saving the buffer and exiting it.  That's when I discovered that
> after I changed the focus to the merge window and saved it, when I
> tried typing ^X^C, the exit failed with the error message "Attempt to
> delete a surrogate minibuffer frame".  That's the sort of thing that
> will cause non-elisp programmers to run screaming off into the
> distance.

Ouch.  Yes, I've never seen that before and no doubt if I had've I'd
feel the same way.  I just save the merge buffer and quit, and it is
pretty obedient for me.

However I guess it wouldn't be nice to have a merge mode that did not
work out of the box for a large number of users.

Your .el file certainly does the trick for me - I reckon throw it in
contrib/

Sam.

^ permalink raw reply

* Re: [PATCH] Make '!' aliases more useful
From: Alex Riesen @ 2007-07-03  7:07 UTC (permalink / raw)
  To: Theodore Tso; +Cc: Johannes Schindelin, git, gitster
In-Reply-To: <20070702160810.GD4720@thunk.org>

On 7/2/07, Theodore Tso <tytso@mit.edu> wrote:
> On Mon, Jul 02, 2007 at 04:55:24PM +0100, Johannes Schindelin wrote:
> > > But what if you don't want the argument passed at the end of the
> > > alias, but somewhere else?  I suspect the better answer would be to
> > > support $* and $1, $2, $3, et. al interpolation, no?  It was on my
> > > list of things to do when I had a spare moment, but I never got around
> > > to it.
> >
> > There is a point where you do not want to complicate git, but rather write
> > a script. This is such a point IMHO.
>
> Such a point exists, I agree, but I would draw after $* and $1/$2/$3
> interpolation.  There is a lot more value that gets added with
> positional arguments support, and it makes git aliases more usable on
> platforms such as Windows where scripting capability is much more
> limited.

I don't think it is _possible_ to make it work on Windows properly: you have
to quote the arguments with whitespaces as there is nothing like argv there
(windows programs don't use command line, so it does not exist).
As the quoting is implemented in the programs the rules are all different
and mostly suboptimal.

^ permalink raw reply

* Re: [PATCH] Make '!' aliases more useful
From: Johannes Sixt @ 2007-07-03  7:24 UTC (permalink / raw)
  To: Theodore Tso; +Cc: git, Johannes Schindelin, Junio C Hamano
In-Reply-To: <20070703011429.GB5322@thunk.org>

Theodore Tso wrote:
> 
> On Mon, Jul 02, 2007 at 04:11:21PM -0700, Junio C Hamano wrote:
> > > Such a point exists, I agree, but I would draw after $* and $1/$2/$3
> > > interpolation.  There is a lot more value that gets added with
> > > positional arguments support, and it makes git aliases more usable on
> > > platforms such as Windows where scripting capability is much more
> > > limited.
> >
> > That actually sounds sensible, but you could alias
> >
> >       !sh -c 'command $2 $1 $3'
> >
> > to reorder the parameters, couldn't you?
> 
> Um, how would that work on the Windows platform?

Shouldn't be a problem for Cygwin users, nor for MinGW port users. The
latter need a Bourne shell anyway for the various scripted git tools.

-- Hannes

^ permalink raw reply

* Starting 1.5.3 stabilization cycle
From: Junio C Hamano @ 2007-07-03  7:29 UTC (permalink / raw)
  To: git

I tagged the tip of 'master' as 1.5.3-rc0; in addition to the
topics described in the latest "What's in", most of the topics
that have been cooking in 'next' are there.  The time some of
them have spent before going to 'master' might be shorter than
usual and there are a handful patches that I took directly to
'master', but the tip of 'master' should be as solid as usual.

There probably could be a few bits that we have already seen and
was reviewed on the list that deserve to be in v1.5.3 (the only
ones I have in mind right now are a few gitweb ones), but other
than that, I think this is pretty much it, modulo bugfixes.

Oh, by the way, a maintenance release 1.5.2.3 is also out.

----------------------------------------------------------------

GIT v1.5.3 Release Notes (draft as of -rc0)
========================

Updates since v1.5.2
--------------------

* The commit walkers other than http are officially deprecated
  (but still supported).

* The submodule support has Porcelain layer now.

* An initial interation of Porcelain level superproject support
  started to take shape.

* Thee are a handful pack-objects changes to help you cope better with
  repositories with pathologically large blobs in them.

* For people who need to import from Perforce, a front-end for
  fast-import is in contrib/fast-import/ now.

* Comes with git-gui 0.8.0.

* Comes with updated gitk.

* New commands and options.

  - "git stash" allows you to quickly save away your work in
    progress and replay it later on an updated state.

  - "git rebase" learned an "interactive" mode.

  - "git fsck" can save its findings in lost-found, without a
    separate invocation of "git lost-found" command.

  - GIT_WORK_TREE environment variable can be used together with
    GIT_DIR to work in a subdirectory of a working tree that is
    not located at "$GIT_DIR/..".

  - "git log" learned a new option '--follow', to follow
    renaming history of a single file.

  - "git-filter-branch" is a reborn cg-admin-rewritehist.

  - "git-cvsserver" learned new options (--base-path, --export-all,
    --strict-paths) inspired by git-daemon.

  - "git-submodule" command helps you manage the projects from
    the superproject that contain them.

  - In addition to core.compression configuration option,
    core.loosecompression and pack.compression options can
    independently tweak zlib compression levels used for loose
    and packed objects.

  - "git-ls-tree -l" shows size of blobs pointed at by the
    tree entries, similar to "/bin/ls -l".

  - "git-rev-list" learned --regexp-ignore-case and
    --extended-regexp options to tweak its matching logic used
    for --grep fitering.

  - "git-describe --contains" is a handier way to call more
    obscure command "git-name-rev --tags".

  - "git gc --aggressive" tells the command to spend more cycles
    to optimize the repository harder.

  - "git repack" can be told to split resulting packs to avoid
    exceeding limit specified with "--max-pack-size".

  - "git fsck" gained --verbose option.  This is really really
    verbose but it might help you identify exact commit that is
    corrupt in your repository.

  - "git format-patch" learned --numbered-files option.  This
    may be useful for MH users.

  - "git format-patch" learned format.subjectprefix
    configuration variable, which serves the same purpose as the
    existing --subject-prefix option.

  - "git tag -n -l" shows tag annotations while listing tags.

  - "git cvsimport" can optionally use the separate-remote layout.

  - "git blame" can be told to see through commits that changes
    whitespaces and indentation levels with "-w" option.

  - "git send-email" can be told not to thread the messages when
    sending out more than one patches.

  - "git config" learned NUL terminated output format via -z to
    help scripts.

  - "git init -q" makes it quieter.

* Updated behavior of existing commands.

  - "git svn dcommit" retains local merge information.

  - "git config" to set values also honor type flags like --bool
    and --int.

  - core.quotepath configuration can be used to make textual git
    output to emit most of the characters in the path literally.

  - "git mergetool" chooses its backend more wisely, taking
    notice of its environment such as use of X, Gnome/KDE, etc.

  - "gitweb" shows merge commits a lot nicer than before.  The
    default view uses more compact --cc format, while the UI
    allows to choose normal diff with any parent.

  - snapshot files "gitweb" creates from a repository at
    $path/$project/.git are more useful.  We use $project part
    in the filename, which we used to discard.

  - "git cvsimort" creates lightweight tag; there is not any
    interesting information we can record in an annotated tag,
    and the handcrafted ones the old code created was not
    properly formed anyway.

  - "git-push" pretends that you immediately fetched back from
    the remote by updating corresponding remote tracking
    branches if you have any.

  - The diffstat given after a merge (or a pull) honors the
    color.diff configuration.

  - "git-apply --whitespace=strip" removes blank lines added at
    the end of the file.

  - "git-fetch" over git native protocols with -v shows connection
    status, and the IP address of the other end, to help
    diagnosing problems.

  - We used to have core.legacyheaders configuration, when
    set to false, allowed git to write loose objects in a format
    that mimicks the format used by objects stored in packs.  It
    turns out that this was not so useful.  Although we will
    continue to read objects written in that format, we do not
    honor that configuration anymore and create loose objects in
    the legacy/traditional format.

  - "--find-copies-harder" option to diff family can now be
    spelled as "-C -C" for brevity.

  - "git-mailsplit" (hence "git-am") can read from Maildir
    formatted mailboxes.

  - "git-cvsserver" does not barf upon seeing "cvs login"
    request.

  - "pack-objects" honors "delta" attribute set in
    .gitattributes.  It does not attempt to deltify blobs that
    come from paths with delta attribute set to false.

  - new-workdir script (in contrib) can now be used with a bare
    repository.

  - "git-mergetool" learned to use gvimdiff.

  - "gitview" (in contrib) has a better blame interface.

  - "git log" and friends did not handle a commit log message
    that is larger than 16kB; they do now.

  - "--pretty=oneline" output format for "git log" and friends
    deals with "malformed" commit log messages that have more
    than one lines in the first paragraph better.  We used to
    show the first line, cutting the title at mid-sentence; we
    concatenate them into a single line and treat the result as
    "oneline".

* Builds

  - old-style function definitions (most notably, a function
    without parameter defined with "func()", not "func(void)")
    have been eradicated.

* Performance Tweaks

  - git-pack-objects avoids re-deltification cost by caching
    small enough delta results it creates while looking for the
    best delta candidates.

  - diff-delta code that is used for packing has been improved
    to work better on big files.

  - when there are more than one pack files in the repository,
    the runtime used to try finding an object always from the
    newest packfile; it now tries the same packfile as we found
    the object requested the last time, which exploits the
    locality of references.

  - verifying pack contents done by "git fsck --full" got boost
    by carefully choosing the order to verify objects in them.


Fixes since v1.5.2
------------------

All of the fixes in v1.5.2 maintenance series are included in
this release, unless otherwise noted.

* Bugfixes

  - "gitweb" had trouble handling non UTF-8 text with older
    Encode.pm Perl module.

^ permalink raw reply

* [PATCH] repack: don't report "Nothing new to pack." if -q is given
From: Uwe Kleine-König @ 2007-07-03  8:47 UTC (permalink / raw)
  To: Git Mailing List

Signed-off-by: Uwe Kleine-König <ukleinek@informatik.uni-freiburg.de>
---
This patch is on top of maint.  For master and next you need
s/name/names/.

Best regards
Uwe

 git-repack.sh |    4 +++-
 1 files changed, 3 insertions(+), 1 deletions(-)

diff --git a/git-repack.sh b/git-repack.sh
index ddfa8b4..d980275 100755
--- a/git-repack.sh
+++ b/git-repack.sh
@@ -65,7 +65,9 @@ args="$args $local $quiet $no_reuse_delta$extra"
 name=$(git-pack-objects --non-empty --all --reflog $args </dev/null "$PACKTMP") ||
 	exit 1
 if [ -z "$name" ]; then
-	echo Nothing new to pack.
+	if test -q "$quiet"; then
+		echo Nothing new to pack.
+	fi
 else
 	chmod a-w "$PACKTMP-$name.pack"
 	chmod a-w "$PACKTMP-$name.idx"
-- 
1.5.2.2.1451.gb0e5e


-- 
Uwe Kleine-König

exit vi, lesson II:
: w q ! <CR>

NB: write the current file

^ permalink raw reply related

* [PATCH] stash: end commit log with a newline
From: Uwe Kleine-König @ 2007-07-03  8:59 UTC (permalink / raw)
  To: Git Mailing List

If I do

	git cat-file commit $commitid

for a commit created by stash, the next prompt starts directly after the
shortlog of HEAD.

Signed-off-by: Uwe Kleine-König <ukleinek@informatik.uni-freiburg.de>
---
 git-stash.sh |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/git-stash.sh b/git-stash.sh
index 16979ab..9deda44 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -67,7 +67,7 @@ save_stash () {
 		die "Cannot save the current worktree state"
 
 	# create the stash
-	w_commit=$(printf 'WIP on %s' "$msg" |
+	w_commit=$(printf 'WIP on %s\n' "$msg" |
 		git commit-tree $w_tree -p $b_commit -p $i_commit) ||
 		die "Cannot record working tree state"
 
-- 
1.5.2.2.1451.gb0e5e


-- 
Uwe Kleine-König

cal 9 1752 | grep 10

^ permalink raw reply related

* git-svn failed on apr
From: Dongsheng Song @ 2007-07-03  9:05 UTC (permalink / raw)
  To: Git Mailing List

[ ~/wc/git-svn]$ mkdir apr
[ ~/wc/git-svn]$ cd apr/
[ ~/wc/git-svn/apr]$ ls
[ ~/wc/git-svn/apr]$ git-svn init -T trunk -b branches -t tags
http://svn.apache.org/repos/asf/apr/apr
Initialized empty Git repository in .git/
Using higher level of URL: http://svn.apache.org/repos/asf/apr/apr =>
http://svn.apache.org/repos/asf
[ ~/wc/git-svn/apr]$ git-svn fetch
W: Ignoring error from SVN, path probably does not exist: (175002): RA
layer request failed: REPORT request failed on
'/repos/asf/!svn/bc/2200': REPORT of '/repos/asf/!svn/bc/2200': 200 OK
(http://svn.apache.org)

What's wrong?

---
Dongsheng

^ permalink raw reply

* Can git-svn treat specify paths as branches ?
From: Dongsheng Song @ 2007-07-03  9:12 UTC (permalink / raw)
  To: Git Mailing List

For google code projects, the repositories organized is:

branches  tags  trunk  wiki

Can I treat wiki as a branch? i.e.

[svn-remote "svn"]
        url = http://serf.googlecode.com/svn
        fetch = trunk:refs/remotes/trunk
        branches = branches/*,wiki:refs/remotes/*
        tags = tags/*:refs/remotes/tags/*

---
Dongsheng

^ permalink raw reply

* Re: git-svn dcommit fails if the URL has spaces in it
From: Rogan Dawes @ 2007-07-03  9:49 UTC (permalink / raw)
  Cc: Git Mailing List, Eric Wong
In-Reply-To: <4680F75A.9010704@dawes.za.net>

Rogan Dawes wrote:
> Hi folks,
> 
> I used git-svn to clone the webgoat repository at GoogleCode. As 
> mentioned at the time, for some bizarre reason, I ended up getting a 
> directory with a space character in it.
> 
> See http://article.gmane.org/gmane.comp.version-control.git/49179
> 
> In order to get a checkout that looks the same as the SVN checkout, I 
> had to use a command line like:
> 
> git-svn clone -T "trunk/ webgoat" https://webgoat.googlecode.com/svn/
> 
> Unfortunately, this results in git-svn-id metadata entries in the 
> commits that look like:
> 
> git-svn-id: https://webgoat.googlecode.com/svn/trunk/ webgoat@2 
> 4033779f-a91e-0410-96ef-6bf7bf53c507
> 
> (Should be all on one line). As you can see, the space has made it 
> through into the git-svn-id. Which then breaks this pattern match in 
> git-svn:extract_metadata:
> 
> my ($url, $rev, $uuid) = ($id =~ /^git-svn-id:\s(\S+?)\@(\d+)
>                                                 \s([a-f\d\-]+)$/x);
> 
> Unfortunately, hacking it to ignore the space doesn't help either, as I 
> then get:
> 
> [rdawes@lucas webgoat]$ git-svn --username=rogan.dawes dcommit
> ID = git-svn-id: https://webgoat.googlecode.com/svn/trunk/ webgoat@125 
> 4033779f-a91e-0410-96ef-6bf7bf53c507
> ID = git-svn-id: https://webgoat.googlecode.com/svn/trunk/ webgoat@125 
> 4033779f-a91e-0410-96ef-6bf7bf53c507
> RA layer request failed: PROPFIND request failed on '/svn/trunk/ 
> webgoat': PROPFIND of '/svn/trunk/ webgoat': 400 Bad Request 
> (https://webgoat.googlecode.com) at /home/rdawes/bin/git-svn line 406
> 
> Which is entirely unsurprising, since I suspect that the URL was not 
> being quoted/escaped correctly, and we were sending something like:
> 
> PROPFIND /svn/trunk/ webgoat/ HTTP/1.0
> 
> Which is clearly illegal, having an additional field.
> 
> However, I tried encoding the space to %20, and got back to my original 
> problem, i.e.
> 
> Unable to determine upstream SVN information from HEAD history
> 
> If I run "git-svn -n dcommit", however, it does print out a list of 
> "diff-tree <sha>~1 <sha>" entries.
> 
> At this point, I am pretty well stuck. Any suggestions would be much 
> appreciated.
> 
> Regards,
> 
> Rogan Dawes

Anyone got any ideas?

Rogan

^ 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