GIT 0.99.9k

This is not 1.0rc4 yet, but to push the recent fixes out.

Signed-off-by: Junio C Hamano <junkio@cox.net>
diff --git a/.gitignore b/.gitignore
index 0dd7b9c..8a6bd02 100644
--- a/.gitignore
+++ b/.gitignore
@@ -75,6 +75,7 @@
 git-receive-pack
 git-relink
 git-repack
+git-repo-config
 git-request-pull
 git-reset
 git-resolve
diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt
index 3783858..2a8f371 100644
--- a/Documentation/git-daemon.txt
+++ b/Documentation/git-daemon.txt
@@ -8,7 +8,7 @@
 SYNOPSIS
 --------
 'git-daemon' [--verbose] [--syslog] [--inetd | --port=n] [--export-all]
-             [--timeout=n] [--init-timeout=n] [directory...]
+             [--timeout=n] [--init-timeout=n] [--strict-paths] [directory...]
 
 DESCRIPTION
 -----------
@@ -29,9 +29,15 @@
 
 OPTIONS
 -------
+--strict-paths::
+	Match paths exactly (i.e. don't allow "/foo/repo" when the real path is
+	"/foo/repo.git" or "/foo/repo/.git") and don't do user-relative paths.
+	git-daemon will refuse to start when this option is enabled and no
+	whitelist is specified.
+
 --export-all::
 	Allow pulling from all directories that look like GIT repositories
-	(have the 'objects' subdirectory and a 'HEAD' file), even if they
+	(have the 'objects' and 'refs' subdirectories), even if they
 	do not have the 'git-daemon-export-ok' file.
 
 --inetd::
@@ -57,9 +63,15 @@
 --verbose::
 	Log details about the incoming connections and requested files.
 
+<directory>::
+	A directory to add to the whitelist of allowed directories. Unless
+	--strict-paths is specified this will also include subdirectories
+	of each named directory.
+
 Author
 ------
-Written by Linus Torvalds <torvalds@osdl.org> and YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
+Written by Linus Torvalds <torvalds@osdl.org>, YOSHIFUJI Hideaki
+<yoshfuji@linux-ipv6.org> and the git-list <git@vger.kernel.org>
 
 Documentation
 --------------
diff --git a/Documentation/git-repo-config.txt b/Documentation/git-repo-config.txt
new file mode 100644
index 0000000..5eefe02
--- /dev/null
+++ b/Documentation/git-repo-config.txt
@@ -0,0 +1,170 @@
+git-repo-config(1)
+==================
+
+NAME
+----
+git-repo-config - Get and set options in .git/config.
+
+
+SYNOPSIS
+--------
+'git-repo-config' name [value [value_regex]]
+'git-repo-config' --replace-all name [value [value_regex]]
+'git-repo-config' --get name [value_regex]
+'git-repo-config' --get-all name [value_regex]
+'git-repo-config' --unset name [value_regex]
+'git-repo-config' --unset-all name [value_regex]
+
+DESCRIPTION
+-----------
+You can query/set/replace/unset options with this command. The name is
+actually the section and the key separated by a dot, and the value will be
+escaped.
+
+If you want to set/unset an option which can occor on multiple lines, you
+should provide a POSIX regex for the value. If you want to handle the lines
+*not* matching the regex, just prepend a single exlamation mark in front
+(see EXAMPLES).
+
+This command will fail if
+
+. .git/config is invalid,
+. .git/config can not be written to,
+. no section was provided,
+. the section or key is invalid,
+. you try to unset an option which does not exist, or
+. you try to unset/set an option for which multiple lines match.
+
+
+OPTIONS
+-------
+
+--replace-all::
+	Default behaviour is to replace at most one line. This replaces
+	all lines matching the key (and optionally the value_regex)
+
+--get::
+	Get the value for a given key (optionally filtered by a regex
+	matching the value).
+
+--get-all::
+	Like get, but does not fail if the number of values for the key
+	is not exactly one.
+
+--unset::
+	Remove the line matching the key from .git/config.
+
+--unset-all::
+	Remove all matching lines from .git/config.
+
+
+EXAMPLE
+-------
+
+Given a .git/config like this:
+
+	#
+	# This is the config file, and
+	# a '#' or ';' character indicates
+	# a comment
+	#
+
+	; core variables
+	[core]
+		; Don't trust file modes
+		filemode = false
+
+	; Our diff algorithm
+	[diff]
+		external = "/usr/local/bin/gnu-diff -u"
+		renames = true
+
+	; Proxy settings
+	[proxy]
+		command="ssh" for "ssh://kernel.org/"
+		command="proxy-command" for kernel.org
+		command="myprotocol-command" for "my://"
+		command=default-proxy ; for all the rest
+
+you can set the filemode to true with
+
+------------
+% git repo-config core.filemode true
+------------
+
+The hypothetic proxy command entries actually have a postfix to discern
+to what URL they apply. Here is how to change the entry for kernel.org
+to "ssh".
+
+------------
+% git repo-config proxy.command '"ssh" for kernel.org' 'for kernel.org$'
+------------
+
+This makes sure that only the key/value pair for kernel.org is replaced.
+
+To delete the entry for renames, do
+
+------------
+% git repo-config --unset diff.renames
+------------
+
+If you want to delete an entry for a multivar (like proxy.command above),
+you have to provide a regex matching the value of exactly one line.
+
+To query the value for a given key, do
+
+------------
+% git repo-config --get core.filemode
+------------
+
+or
+
+------------
+% git repo-config core.filemode
+------------
+
+or, to query a multivar:
+
+------------
+% git repo-config --get proxy.command "for kernel.org$"
+------------
+
+If you want to know all the values for a multivar, do:
+
+------------
+% git repo-config --get-all proxy.command
+------------
+
+If you like to live dangerous, you can replace *all* proxy.commands by a
+new one with
+
+------------
+% git repo-config --replace-all proxy.command ssh
+------------
+
+However, if you really only want to replace the line for the default proxy,
+i.e. the one without a "for ..." postfix, do something like this:
+
+------------
+% git repo-config proxy.command ssh '! for '
+------------
+
+To actually match only values with an exclamation mark, you have to
+
+------------
+% git repo-config section.key value '[!]'
+------------
+
+
+Author
+------
+Written by Johannes Schindelin <Johannes.Schindelin@gmx.de>
+
+Documentation
+--------------
+Documentation by Johannes Schindelin.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt
index 31ec207..6af3a4f 100644
--- a/Documentation/git-reset.txt
+++ b/Documentation/git-reset.txt
@@ -14,19 +14,30 @@
 Sets the current head to the specified commit and optionally resets the
 index and working tree to match.
 
+This command is useful if you notice some small error in a recent
+commit (or set of commits) and want to redo that part without showing
+the undo in the history.
+
+If you want to undo a commit other than the latest on a branch,
+gitlink:git-revert[1] is your friend.
+
 OPTIONS
 -------
 --mixed::
-	Like --soft but reports what has not been updated. This is the
-	default action.
+	Resets the index but not the working tree (ie, the changed files
+	are preserved but not marked for commit) and reports what has not
+	been updated. This is the default action.
 
 --soft::
 	Does not touch the index file nor the working tree at all, but
-	requires them in a good order.
+	requires them to be in a good order. This leaves all your changed
+	files "Updated but not checked in", as gitlink:git-status[1] would
+	put it.
 
 --hard::
 	Matches the working tree and index to that of the tree being
-	switched to.
+	switched to. Any changes to tracked files in the working tree
+	since <commit-ish> are lost.
 
 <commit-ish>::
 	Commit to make the current HEAD.
diff --git a/Documentation/git.txt b/Documentation/git.txt
index 338e5ac..a518249 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -108,6 +108,9 @@
 gitlink:git-read-tree[1]::
 	Reads tree information into the directory index
 
+gitlink:git-repo-config[1]::
+	Get and set options in .git/config.
+
 gitlink:git-unpack-objects[1]::
 	Unpacks objects out of a packed archive.
 
diff --git a/Documentation/howto/rebase-from-internal-branch.txt b/Documentation/howto/rebase-from-internal-branch.txt
index b2c021d..c2d4a91 100644
--- a/Documentation/howto/rebase-from-internal-branch.txt
+++ b/Documentation/howto/rebase-from-internal-branch.txt
@@ -40,10 +40,7 @@
     $ git checkout master
     $ cd Documentation; ed git.txt ...
     $ cd ..; git add Documentation/*.txt
-    $ git commit -s -v
-
-NOTE.  The -v flag to commit is a handy way to make sure that
-your additions are not introducing bogusly formatted lines.
+    $ git commit -s
 
 After the commit, the ancestry graph would look like this:
 
@@ -98,7 +95,7 @@
 Let's go back to the earlier picture, with different labels.
 
 You, as an individual developer, cloned upstream repository and
-amde a couple of commits on top of it.
+made a couple of commits on top of it.
 
                               *your "master" head
    upstream --> #1 --> #2 --> #3
diff --git a/Documentation/howto/update-hook-example.txt b/Documentation/howto/update-hook-example.txt
new file mode 100644
index 0000000..dacaf17
--- /dev/null
+++ b/Documentation/howto/update-hook-example.txt
@@ -0,0 +1,105 @@
+From: Junio C Hamano <junkio@cox.net>
+Subject: control access to branches.
+Date: Thu, 17 Nov 2005 23:55:32 -0800
+Message-ID: <7vfypumlu3.fsf@assigned-by-dhcp.cox.net>
+Abstract: An example hooks/update script is presented to
+ implement repository maintenance policies, such as who can push
+ into which branch and who can make a tag.
+
+When your developer runs git-push into the repository,
+git-receive-pack is run (either locally or over ssh) as that
+developer, so is hooks/update script.  Quoting from the relevant
+section of the documentation:
+
+    Before each ref is updated, if $GIT_DIR/hooks/update file exists
+    and executable, it is called with three parameters:
+
+           $GIT_DIR/hooks/update refname sha1-old sha1-new
+
+    The refname parameter is relative to $GIT_DIR; e.g. for the
+    master head this is "refs/heads/master".  Two sha1 are the
+    object names for the refname before and after the update.  Note
+    that the hook is called before the refname is updated, so either
+    sha1-old is 0{40} (meaning there is no such ref yet), or it
+    should match what is recorded in refname.
+
+So if your policy is (1) always require fast-forward push
+(i.e. never allow "git-push repo +branch:branch"), (2) you
+have a list of users allowed to update each branch, and (3) you
+do not let tags to be overwritten, then:
+
+	#!/bin/sh
+	# This is a sample hooks/update script, written by JC
+        # in his e-mail buffer, so naturally it is not tested
+        # but hopefully would convey the idea.
+
+	umask 002
+        case "$1" in
+        refs/tags/*)
+		# No overwriting an existing tag
+        	if test -f "$GIT_DIR/$1"
+                then
+                	exit 1
+		fi
+		;;
+	refs/heads/*)
+        	# No rebasing or rewinding
+                if expr "$2" : '0*$' >/dev/null
+                then
+                	# creating a new branch
+			;
+		else
+                	# updating -- make sure it is a fast forward
+        		mb=`git-merge-base "$2" "$3"`
+			case "$mb,$2" in
+                        "$2,$mb")
+                        	;; # fast forward -- happy
+			*)
+                        	exit 1 ;; # unhappy
+			esac
+		fi
+		;;
+	*)
+		# No funny refs allowed
+		exit 1
+		;;
+	esac
+
+	# Is the user allowed to update it?
+	me=`id -u -n` ;# e.g. "junio"
+	while read head_pattern users
+        do
+		if expr "$1" : "$head_pattern" >/dev/null
+		then
+			case " $users " in
+			*" $me "*)
+                        	exit 0 ;; # happy
+			' * ')
+                        	exit 0 ;; # anybody
+			esac
+		fi
+	done
+	exit 1
+
+For the sake of simplicity, I assumed that you keep something
+like this in $GIT_DIR/info/allowed-pushers file:
+
+	refs/heads/master	junio
+        refs/heads/cogito$	pasky
+	refs/heads/bw/		linus
+        refs/heads/tmp/		*
+        refs/tags/v[0-9]*	junio
+
+With this, Linus can push or create "bw/penguin" or "bw/zebra"
+or "bw/panda" branches, Pasky can do only "cogito", and I can do
+master branch and make versioned tags.  And anybody can do
+tmp/blah branches.  This assumes all the users are in a single
+group that can write into $GIT_DIR/ and underneath.
+
+
+
+
+
+
+
+
diff --git a/Documentation/pull-fetch-param.txt b/Documentation/pull-fetch-param.txt
index ddd5823..6413d52 100644
--- a/Documentation/pull-fetch-param.txt
+++ b/Documentation/pull-fetch-param.txt
@@ -5,11 +5,31 @@
 	to name the remote repository:
 +
 ===============================================================
-- Rsync URL:		rsync://remote.machine/path/to/repo.git/
-- HTTP(s) URL:		http://remote.machine/path/to/repo.git/
-- git URL:		git://remote.machine/path/to/repo.git/
-- ssh URL:		remote.machine:/path/to/repo.git/
-- Local directory:	/path/to/repo.git/
+- rsync://host.xz/path/to/repo.git/
+- http://host.xz/path/to/repo.git/
+- https://host.xz/path/to/repo.git/
+- git://host.xz/path/to/repo.git/
+- git://host.xz/~user/path/to/repo.git/
+- ssh://host.xz/path/to/repo.git/
+- ssh://host.xz/~user/path/to/repo.git/
+- ssh://host.xz/~/path/to/repo.git
+===============================================================
++
+	SSH Is the default transport protocol and also supports an
+	scp-like syntax.  Both syntaxes support username expansion,
+	as does the native git protocol. The following three are
+	identical to the last three above, respectively:
++
+===============================================================
+- host.xz:/path/to/repo.git/
+- host.xz:~user/path/to/repo.git/
+- host.xz:path/to/repo.git
+===============================================================
++
+       To sync with a local directory, use:
+
+===============================================================
+- /path/to/repo.git/
 ===============================================================
 +
 In addition to the above, as a short-hand, the name of a
diff --git a/Documentation/tutorial.txt b/Documentation/tutorial.txt
index 03eb421..e2dfb00 100644
--- a/Documentation/tutorial.txt
+++ b/Documentation/tutorial.txt
@@ -1534,7 +1534,10 @@
    the "project lead" person does.
 
 3. Copy over the packed files from "project lead" public
-   repository to your public repository.
+   repository to your public repository, unless the "project
+   lead" repository lives on the same machine as yours.  In the
+   latter case, you can use `objects/info/alternates` file to
+   point at the repository you are borrowing from.
 
 4. Push into the public repository from your primary
    repository. Run `git repack`, and possibly `git prune` if the
diff --git a/Makefile b/Makefile
index 2d8853d..adff025 100644
--- a/Makefile
+++ b/Makefile
@@ -50,7 +50,7 @@
 # Define USE_STDEV below if you want git to care about the underlying device
 # change being considered an inode change from the update-cache perspective.
 
-GIT_VERSION = 0.99.9j
+GIT_VERSION = 0.99.9k
 
 # CFLAGS and LDFLAGS are for the users to override from the command line.
 
@@ -102,6 +102,11 @@
 SCRIPT_PYTHON = \
 	git-merge-recursive.py
 
+SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
+	  $(patsubst %.perl,%,$(SCRIPT_PERL)) \
+	  $(patsubst %.py,%,$(SCRIPT_PYTHON)) \
+	  gitk git-cherry-pick
+
 # The ones that do not have to link with lcrypto nor lz.
 SIMPLE_PROGRAMS = \
 	git-get-tar-commit-id$X git-mailinfo$X git-mailsplit$X \
@@ -125,18 +130,36 @@
 	git-unpack-objects$X git-update-index$X git-update-server-info$X \
 	git-upload-pack$X git-verify-pack$X git-write-tree$X \
 	git-update-ref$X git-symbolic-ref$X git-check-ref-format$X \
-	git-name-rev$X git-pack-redundant$X git-var$X $(SIMPLE_PROGRAMS)
+	git-name-rev$X git-pack-redundant$X git-repo-config$X git-var$X
+
+# what 'all' will build and 'install' will install.
+ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) git$X
 
 # Backward compatibility -- to be removed after 1.0
 PROGRAMS += git-ssh-pull$X git-ssh-push$X
 
 GIT_LIST_TWEAK =
 
+# Set paths to tools early so that they can be used for version tests.
+ifndef SHELL_PATH
+	SHELL_PATH = /bin/sh
+endif
+ifndef PERL_PATH
+	PERL_PATH = /usr/bin/perl
+endif
+ifndef PYTHON_PATH
+	PYTHON_PATH = /usr/bin/python
+endif
+
 PYMODULES = \
 	gitMergeCommon.py
 
 ifdef WITH_OWN_SUBPROCESS_PY
 	PYMODULES += compat/subprocess.py
+else
+	ifneq ($(shell $(PYTHON_PATH) -c 'import subprocess;print"OK"' 2>/dev/null),OK)
+		PYMODULES += compat/subprocess.py
+	endif
 endif
 
 ifdef WITH_SEND_EMAIL
@@ -242,22 +265,15 @@
 		CURL_LIBCURL = -lcurl
 	endif
 	PROGRAMS += git-http-fetch$X
-	ifndef NO_EXPAT
-		EXPAT_LIBEXPAT = -lexpat
-		PROGRAMS += git-http-push$X
+	curl_check := $(shell (echo 070908; curl-config --vernum) | sort -r | sed -ne 2p)
+	ifeq "$(curl_check)" "070908"
+		ifndef NO_EXPAT
+			EXPAT_LIBEXPAT = -lexpat
+			PROGRAMS += git-http-push$X
+		endif
 	endif
 endif
 
-ifndef SHELL_PATH
-	SHELL_PATH = /bin/sh
-endif
-ifndef PERL_PATH
-	PERL_PATH = /usr/bin/perl
-endif
-ifndef PYTHON_PATH
-	PYTHON_PATH = /usr/bin/python
-endif
-
 ifndef NO_OPENSSL
 	LIB_OBJS += epoch.o
 	OPENSSL_LIBSSL = -lssl
@@ -330,25 +346,20 @@
 
 ALL_CFLAGS += -DSHA1_HEADER=$(call shellquote,$(SHA1_HEADER))
 
-SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
-	  $(patsubst %.perl,%,$(SCRIPT_PERL)) \
-	  $(patsubst %.py,%,$(SCRIPT_PYTHON)) \
-	  gitk git-cherry-pick
-
 export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir
 ### Build rules
 
-all: $(PROGRAMS) $(SCRIPTS) git
+all: $(ALL_PROGRAMS)
 
 all:
 	$(MAKE) -C templates
 
 # Only use $(CFLAGS). We don't need anything else.
-git: git.c Makefile
+git$(X): git.c Makefile
 	$(CC) -DGIT_EXEC_PATH='"$(bindir)"' -DGIT_VERSION='"$(GIT_VERSION)"' \
-		$(CFLAGS) $@.c -o $@
+		$(CFLAGS) $< -o $@
 
-$(filter-out git,$(patsubst %.sh,%,$(SCRIPT_SH))) : % : %.sh
+$(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
 	rm -f $@
 	sed -e '1s|#!.*/sh|#!$(call shq,$(SHELL_PATH))|' \
 	    -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
@@ -387,7 +398,8 @@
 	$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
 		$(LIB_FILE) $(SIMPLE_LIB)
 
-git-http-fetch$X: fetch.o
+git-http-fetch$X: fetch.o http.o
+git-http-push$X: http.o
 git-local-fetch$X: fetch.o
 git-ssh-fetch$X: rsh.o fetch.o
 git-ssh-upload$X: rsh.o
@@ -431,9 +443,9 @@
 
 ### Installation rules
 
-install: $(PROGRAMS) $(SCRIPTS) git
+install: all
 	$(INSTALL) -d -m755 $(call shellquote,$(DESTDIR)$(bindir))
-	$(INSTALL) git $(PROGRAMS) $(SCRIPTS) $(call shellquote,$(DESTDIR)$(bindir))
+	$(INSTALL) $(ALL_PROGRAMS) $(call shellquote,$(DESTDIR)$(bindir))
 	$(MAKE) -C templates install
 	$(INSTALL) -d -m755 $(call shellquote,$(DESTDIR)$(GIT_PYTHON_DIR))
 	$(INSTALL) $(PYMODULES) $(call shellquote,$(DESTDIR)$(GIT_PYTHON_DIR))
@@ -470,7 +482,8 @@
 ### Cleaning rules
 
 clean:
-	rm -f *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o git $(PROGRAMS) $(LIB_FILE)
+	rm -f *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o $(LIB_FILE)
+	rm -f $(PROGRAMS) $(SIMPLE_PROGRAMS) git$X
 	rm -f $(filter-out gitk,$(SCRIPTS))
 	rm -f *.spec *.pyc *.pyo
 	rm -rf $(GIT_TARNAME)
diff --git a/cache.h b/cache.h
index 99afa2c..6ac94c5 100644
--- a/cache.h
+++ b/cache.h
@@ -203,6 +203,7 @@
 
 int safe_create_leading_directories(char *path);
 char *safe_strncpy(char *, const char *, size_t);
+char *enter_repo(char *path, int strict);
 
 /* Read and unpack a sha1 file into memory, write memory to a sha1 file */
 extern int unpack_sha1_header(z_stream *stream, void *map, unsigned long mapsize, void *buffer, unsigned long size);
@@ -262,9 +263,8 @@
 unsigned long approxidate(const char *);
 
 extern int setup_ident(void);
-extern char *get_ident(const char *name, const char *email, const char *date_str);
-extern char *git_author_info(void);
-extern char *git_committer_info(void);
+extern const char *git_author_info(void);
+extern const char *git_committer_info(void);
 
 static inline void *xmalloc(size_t size)
 {
@@ -386,6 +386,8 @@
 extern int git_config(config_fn_t fn);
 extern int git_config_int(const char *, const char *);
 extern int git_config_bool(const char *, const char *);
+extern int git_config_set(const char *, const char *);
+extern int git_config_set_multivar(const char *, const char *, const char *, int);
 
 #define MAX_GITNAME (1000)
 extern char git_default_email[MAX_GITNAME];
diff --git a/config.c b/config.c
index 915bb97..5cc8535 100644
--- a/config.c
+++ b/config.c
@@ -1,5 +1,12 @@
-
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ * Copyright (C) Johannes Schindelin, 2005
+ *
+ */
 #include "cache.h"
+#include <regex.h>
 
 #define MAXNAME (256)
 
@@ -136,7 +143,7 @@
 			return -1;
 		if (c == ']')
 			return baselen;
-		if (!isalnum(c))
+		if (!isalnum(c) && c != '.')
 			return -1;
 		if (baselen > MAXNAME / 2)
 			return -1;
@@ -229,11 +236,6 @@
 		return 0;
 	}
 
-	if (!strcmp(var, "diff.renamelimit")) {
-		diff_rename_limit_default = git_config_int(var, value);
-		return 0;
-	}
-
 	/* Add other config variables here.. */
 	return 0;
 }
@@ -252,3 +254,327 @@
 	}
 	return ret;
 }
+
+/*
+ * Find all the stuff for git_config_set() below.
+ */
+
+#define MAX_MATCHES 512
+
+static struct {
+	int baselen;
+	char* key;
+	int do_not_match;
+	regex_t* value_regex;
+	int multi_replace;
+	off_t offset[MAX_MATCHES];
+	enum { START, SECTION_SEEN, SECTION_END_SEEN, KEY_SEEN } state;
+	int seen;
+} store;
+
+static int matches(const char* key, const char* value)
+{
+	return !strcmp(key, store.key) &&
+		(store.value_regex == NULL ||
+		 (store.do_not_match ^
+		  !regexec(store.value_regex, value, 0, NULL, 0)));
+}
+
+static int store_aux(const char* key, const char* value)
+{
+	switch (store.state) {
+	case KEY_SEEN:
+		if (matches(key, value)) {
+			if (store.seen == 1 && store.multi_replace == 0) {
+				fprintf(stderr,
+					"Warning: %s has multiple values\n",
+					key);
+			} else if (store.seen >= MAX_MATCHES) {
+				fprintf(stderr, "Too many matches\n");
+				return 1;
+			}
+
+			store.offset[store.seen] = ftell(config_file);
+			store.seen++;
+		}
+		break;
+	case SECTION_SEEN:
+		if (strncmp(key, store.key, store.baselen+1)) {
+			store.state = SECTION_END_SEEN;
+			break;
+		} else
+			/* do not increment matches: this is no match */
+			store.offset[store.seen] = ftell(config_file);
+		/* fallthru */
+	case SECTION_END_SEEN:
+	case START:
+		if (matches(key, value)) {
+			store.offset[store.seen] = ftell(config_file);
+			store.state = KEY_SEEN;
+			store.seen++;
+		} else if(!strncmp(key, store.key, store.baselen))
+			store.state = SECTION_SEEN;
+	}
+	return 0;
+}
+
+static void store_write_section(int fd, const char* key)
+{
+	write(fd, "[", 1);
+	write(fd, key, store.baselen);
+	write(fd, "]\n", 2);
+}
+
+static void store_write_pair(int fd, const char* key, const char* value)
+{
+	int i;
+
+	write(fd, "\t", 1);
+	write(fd, key+store.baselen+1,
+		strlen(key+store.baselen+1));
+	write(fd, " = ", 3);
+	for (i = 0; value[i]; i++)
+		switch (value[i]) {
+		case '\n': write(fd, "\\n", 2); break;
+		case '\t': write(fd, "\\t", 2); break;
+		case '"': case '\\': write(fd, "\\", 1);
+		default: write(fd, value+i, 1);
+	}
+	write(fd, "\n", 1);
+}
+
+static int find_beginning_of_line(const char* contents, int size,
+	int offset_, int* found_bracket)
+{
+	int equal_offset = size, bracket_offset = size;
+	int offset;
+
+	for (offset = offset_-2; offset > 0 
+			&& contents[offset] != '\n'; offset--)
+		switch (contents[offset]) {
+			case '=': equal_offset = offset; break;
+			case ']': bracket_offset = offset; break;
+		}
+	if (bracket_offset < equal_offset) {
+		*found_bracket = 1;
+		offset = bracket_offset+1;
+	} else
+		offset++;
+
+	return offset;
+}
+
+int git_config_set(const char* key, const char* value)
+{
+	return git_config_set_multivar(key, value, NULL, 0);
+}
+
+/*
+ * If value==NULL, unset in (remove from) config,
+ * if value_regex!=NULL, disregard key/value pairs where value does not match.
+ * if multi_replace==0, nothing, or only one matching key/value is replaced,
+ *     else all matching key/values (regardless how many) are removed,
+ *     before the new pair is written.
+ *
+ * Returns 0 on success.
+ *
+ * This function does this:
+ *
+ * - it locks the config file by creating ".git/config.lock"
+ *
+ * - it then parses the config using store_aux() as validator to find
+ *   the position on the key/value pair to replace. If it is to be unset,
+ *   it must be found exactly once.
+ *
+ * - the config file is mmap()ed and the part before the match (if any) is
+ *   written to the lock file, then the changed part and the rest.
+ *
+ * - the config file is removed and the lock file rename()d to it.
+ *
+ */
+int git_config_set_multivar(const char* key, const char* value,
+	const char* value_regex, int multi_replace)
+{
+	int i;
+	struct stat st;
+	int fd;
+	char* config_filename = strdup(git_path("config"));
+	char* lock_file = strdup(git_path("config.lock"));
+	const char* last_dot = strrchr(key, '.');
+
+	/*
+	 * Since "key" actually contains the section name and the real
+	 * key name separated by a dot, we have to know where the dot is.
+	 */
+
+	if (last_dot == NULL) {	
+		fprintf(stderr, "key does not contain a section: %s\n", key);
+		return 2;
+	}
+	store.baselen = last_dot - key;
+
+	store.multi_replace = multi_replace;
+
+	/*
+	 * Validate the key and while at it, lower case it for matching.
+	 */
+	store.key = (char*)malloc(strlen(key)+1);
+	for (i = 0; key[i]; i++)
+		if (i != store.baselen &&
+				((!isalnum(key[i]) && key[i] != '.') ||
+				 (i == store.baselen+1 && !isalpha(key[i])))) {
+			fprintf(stderr, "invalid key: %s\n", key);
+			free(store.key);
+			return 1;
+		} else
+			store.key[i] = tolower(key[i]);
+	store.key[i] = 0;
+
+	/*
+	 * The lock_file serves a purpose in addition to locking: the new
+	 * contents of .git/config will be written into it.
+	 */
+	fd = open(lock_file, O_WRONLY | O_CREAT | O_EXCL, 0666);
+	if (fd < 0) {
+		fprintf(stderr, "could not lock config file\n");
+		free(store.key);
+		return -1;
+	}
+
+	/*
+	 * If .git/config does not exist yet, write a minimal version.
+	 */
+	if (stat(config_filename, &st)) {
+		static const char contents[] =
+			"#\n"
+			"# This is the config file\n"
+			"#\n"
+			"\n";
+
+		free(store.key);
+
+		/* if nothing to unset, error out */
+		if (value == NULL) {
+			close(fd);
+			unlink(lock_file);
+			return 5;
+		}
+
+		store.key = (char*)key;
+
+		write(fd, contents, sizeof(contents)-1);
+		store_write_section(fd, key);
+		store_write_pair(fd, key, value);
+	} else{
+		int in_fd;
+		char* contents;
+		int i, copy_begin, copy_end, new_line = 0;
+
+		if (value_regex == NULL)
+			store.value_regex = NULL;
+		else {
+			if (value_regex[0] == '!') {
+				store.do_not_match = 1;
+				value_regex++;
+			} else
+				store.do_not_match = 0;
+
+			store.value_regex = (regex_t*)malloc(sizeof(regex_t));
+			if (regcomp(store.value_regex, value_regex,
+					REG_EXTENDED)) {
+				fprintf(stderr, "Invalid pattern: %s",
+					value_regex);
+				free(store.value_regex);
+				return 6;
+			}
+		}
+
+		store.offset[0] = 0;
+		store.state = START;
+		store.seen = 0;
+
+		/*
+		 * After this, store.offset will contain the *end* offset
+		 * of the last match, or remain at 0 if no match was found.
+		 * As a side effect, we make sure to transform only a valid
+		 * existing config file.
+		 */
+		if (git_config(store_aux)) {
+			fprintf(stderr, "invalid config file\n");
+			free(store.key);
+			if (store.value_regex != NULL) {
+				regfree(store.value_regex);
+				free(store.value_regex);
+			}
+			return 3;
+		}
+
+		free(store.key);
+		if (store.value_regex != NULL) {
+			regfree(store.value_regex);
+			free(store.value_regex);
+		}
+
+		/* if nothing to unset, or too many matches, error out */
+		if ((store.seen == 0 && value == NULL) ||
+				(store.seen > 1 && multi_replace == 0)) {
+			close(fd);
+			unlink(lock_file);
+			return 5;
+		}
+
+		in_fd = open(config_filename, O_RDONLY, 0666);
+		contents = mmap(NULL, st.st_size, PROT_READ,
+			MAP_PRIVATE, in_fd, 0);
+		close(in_fd);
+
+		if (store.seen == 0)
+			store.seen = 1;
+
+		for (i = 0, copy_begin = 0; i < store.seen; i++) {
+			if (store.offset[i] == 0) {
+				store.offset[i] = copy_end = st.st_size;
+			} else if (store.state != KEY_SEEN) {
+				copy_end = store.offset[i];
+			} else
+				copy_end = find_beginning_of_line(
+					contents, st.st_size,
+					store.offset[i]-2, &new_line);
+
+			/* write the first part of the config */
+			if (copy_end > copy_begin) {
+				write(fd, contents + copy_begin,
+				copy_end - copy_begin);
+				if (new_line)
+					write(fd, "\n", 1);
+			}
+			copy_begin = store.offset[i];
+		}
+
+		/* write the pair (value == NULL means unset) */
+		if (value != NULL) {
+			if (store.state == START)
+				store_write_section(fd, key);
+			store_write_pair(fd, key, value);
+		}
+
+		/* write the rest of the config */
+		if (copy_begin < st.st_size)
+			write(fd, contents + copy_begin,
+				st.st_size - copy_begin);
+
+		munmap(contents, st.st_size);
+		unlink(config_filename);
+	}
+
+	close(fd);
+
+	if (rename(lock_file, config_filename) < 0) {
+		fprintf(stderr, "Could not rename the lock file?\n");
+		return 4;
+	}
+
+	return 0;
+}
+
+
diff --git a/connect.c b/connect.c
index c2badc7..93f6f80 100644
--- a/connect.c
+++ b/connect.c
@@ -427,7 +427,7 @@
 		memset(&sa, 0, sizeof sa);
 		sa.sin_family = he->h_addrtype;
 		sa.sin_port = htons(nport);
-		memcpy(&sa.sin_addr, ap, he->h_length);
+		memcpy(&sa.sin_addr, *ap, he->h_length);
 
 		if (connect(sockfd, (struct sockaddr *)&sa, sizeof sa) < 0) {
 			close(sockfd);
@@ -448,42 +448,162 @@
 
 #endif /* NO_IPV6 */
 
+static char *git_proxy_command = NULL;
+static const char *rhost_name = NULL;
+static int rhost_len;
+
+static int git_proxy_command_options(const char *var, const char *value)
+{
+	if (!strcmp(var, "core.gitproxy")) {
+		const char *for_pos;
+		int matchlen = -1;
+		int hostlen;
+
+		if (git_proxy_command)
+			return 0;
+		/* [core]
+		 * ;# matches www.kernel.org as well
+		 * gitproxy = netcatter-1 for kernel.org
+		 * gitproxy = netcatter-2 for sample.xz
+		 * gitproxy = netcatter-default
+		 */
+		for_pos = strstr(value, " for ");
+		if (!for_pos)
+			/* matches everybody */
+			matchlen = strlen(value);
+		else {
+			hostlen = strlen(for_pos + 5);
+			if (rhost_len < hostlen)
+				matchlen = -1;
+			else if (!strncmp(for_pos + 5,
+					  rhost_name + rhost_len - hostlen,
+					  hostlen) &&
+				 ((rhost_len == hostlen) ||
+				  rhost_name[rhost_len - hostlen -1] == '.'))
+				matchlen = for_pos - value;
+			else
+				matchlen = -1;
+		}
+		if (0 <= matchlen) {
+			/* core.gitproxy = none for kernel.org */
+			if (matchlen == 4 && 
+			    !memcmp(value, "none", 4))
+				matchlen = 0;
+			git_proxy_command = xmalloc(matchlen + 1);
+			memcpy(git_proxy_command, value, matchlen);
+			git_proxy_command[matchlen] = 0;
+		}
+		return 0;
+	}
+
+	return git_default_config(var, value);
+}
+
+static int git_use_proxy(const char *host)
+{
+	rhost_name = host;
+	rhost_len = strlen(host);
+	git_proxy_command = getenv("GIT_PROXY_COMMAND");
+	git_config(git_proxy_command_options);
+	rhost_name = NULL;
+	return (git_proxy_command && *git_proxy_command);
+}
+
+static int git_proxy_connect(int fd[2], const char *prog, char *host, char *path)
+{
+	char *port = STR(DEFAULT_GIT_PORT);
+	char *colon, *end;
+	int pipefd[2][2];
+	pid_t pid;
+
+	if (host[0] == '[') {
+		end = strchr(host + 1, ']');
+		if (end) {
+			*end = 0;
+			end++;
+			host++;
+		} else
+			end = host;
+	} else
+		end = host;
+	colon = strchr(end, ':');
+
+	if (colon) {
+		*colon = 0;
+		port = colon + 1;
+	}
+
+	if (pipe(pipefd[0]) < 0 || pipe(pipefd[1]) < 0)
+		die("unable to create pipe pair for communication");
+	pid = fork();
+	if (!pid) {
+		dup2(pipefd[1][0], 0);
+		dup2(pipefd[0][1], 1);
+		close(pipefd[0][0]);
+		close(pipefd[0][1]);
+		close(pipefd[1][0]);
+		close(pipefd[1][1]);
+		execlp(git_proxy_command, git_proxy_command, host, port, NULL);
+		die("exec failed");
+	}
+	fd[0] = pipefd[0][0];
+	fd[1] = pipefd[1][1];
+	close(pipefd[0][1]);
+	close(pipefd[1][0]);
+	packet_write(fd[1], "%s %s\n", prog, path);
+	return pid;
+}
+
 /*
  * Yeah, yeah, fixme. Need to pass in the heads etc.
  */
 int git_connect(int fd[2], char *url, const char *prog)
 {
 	char command[1024];
-	char *host, *path;
-	char *colon;
+	char *host, *path = url;
+	char *colon = NULL;
 	int pipefd[2][2];
 	pid_t pid;
-	enum protocol protocol;
+	enum protocol protocol = PROTO_LOCAL;
 
-	host = NULL;
-	path = url;
-	colon = strchr(url, ':');
-	protocol = PROTO_LOCAL;
-	if (colon) {
-		*colon = 0;
+	host = strstr(url, "://");
+	if(host) {
+		*host = '\0';
+		protocol = get_protocol(url);
+		host += 3;
+		path = strchr(host, '/');
+	}
+	else {
 		host = url;
-		path = colon+1;
-		protocol = PROTO_SSH;
-		if (!memcmp(path, "//", 2)) {
-			char *slash = strchr(path + 2, '/');
-			if (slash) {
-				int nr = slash - path - 2;
-				memmove(path, path+2, nr);
-				path[nr] = 0;
-				protocol = get_protocol(url);
-				host = path;
-				path = slash;
-			}
+		if ((colon = strchr(host, ':'))) {
+			protocol = PROTO_SSH;
+			*colon = '\0';
+			path = colon + 1;
 		}
 	}
 
-	if (protocol == PROTO_GIT)
+	if (!path || !*path)
+		die("No path specified. See 'man git-pull' for valid url syntax");
+
+	/*
+	 * null-terminate hostname and point path to ~ for URL's like this:
+	 *    ssh://host.xz/~user/repo
+	 */
+	if (protocol != PROTO_LOCAL && host != url) {
+		char *ptr = path;
+		if (path[1] == '~')
+			path++;
+		else
+			path = strdup(ptr);
+
+		*ptr = '\0';
+	}
+
+	if (protocol == PROTO_GIT) {
+		if (git_use_proxy(host))
+			return git_proxy_connect(fd, prog, host, path);
 		return git_tcp_connect(fd, prog, host, path);
+	}
 
 	if (pipe(pipefd[0]) < 0 || pipe(pipefd[1]) < 0)
 		die("unable to create pipe pair for communication");
diff --git a/daemon.c b/daemon.c
index 2b81152..91b9656 100644
--- a/daemon.c
+++ b/daemon.c
@@ -15,10 +15,11 @@
 
 static const char daemon_usage[] =
 "git-daemon [--verbose] [--syslog] [--inetd | --port=n] [--export-all]\n"
-"           [--timeout=n] [--init-timeout=n] [directory...]";
+"           [--timeout=n] [--init-timeout=n] [--strict-paths] [directory...]";
 
 /* List of acceptable pathname prefixes */
 static char **ok_paths = NULL;
+static int strict_paths = 0;
 
 /* If this is set, git-daemon-export-ok is not required */
 static int export_all_trees = 0;
@@ -81,69 +82,52 @@
 	va_end(params);
 }
 
-static int path_ok(const char *dir)
+static char *path_ok(char *dir)
 {
-	const char *p = dir;
-	char **pp;
-	int sl, ndot;
+	char *path = enter_repo(dir, strict_paths);
 
-	/* The pathname here should be an absolute path. */
-	if ( *p++ != '/' )
-		return 0;
-
-	sl = 1;  ndot = 0;
-
-	for (;;) {
-		if ( *p == '.' ) {
-			ndot++;
-		} else if ( *p == '\0' ) {
-			/* Reject "." and ".." at the end of the path */
-			if ( sl && ndot > 0 && ndot < 3 )
-				return 0;
-
-			/* Otherwise OK */
-			break;
-		} else if ( *p == '/' ) {
-			/* Refuse "", "." or ".." */
-			if ( sl && ndot < 3 )
-				return 0;
-			sl = 1;
-			ndot = 0;
-		} else {
-			sl = ndot = 0;
-		}
-		p++;
+	if (!path) {
+		logerror("'%s': unable to chdir or not a git archive", dir);
+		return NULL;
 	}
 
 	if ( ok_paths && *ok_paths ) {
-		int ok = 0;
-		int dirlen = strlen(dir);
+		char **pp;
+		int pathlen = strlen(path);
 
+		/* The validation is done on the paths after enter_repo
+		 * canonicalization, so whitelist should be written in
+		 * terms of real pathnames (i.e. after ~user is expanded
+		 * and symlinks resolved).
+		 */
 		for ( pp = ok_paths ; *pp ; pp++ ) {
 			int len = strlen(*pp);
-			if ( len <= dirlen &&
-			     !strncmp(*pp, dir, len) &&
-			     (dir[len] == '/' || dir[len] == '\0') ) {
-				ok = 1;
-				break;
-			}
+			if (len <= pathlen &&
+			    !memcmp(*pp, path, len) &&
+			    (path[len] == '\0' ||
+			     (!strict_paths && path[len] == '/')))
+				return path;
 		}
-
-		if ( !ok )
-			return 0; /* Path not in whitelist */
+	}
+	else {
+		/* be backwards compatible */
+		if (!strict_paths)
+			return path;
 	}
 
-	return 1;		/* Path acceptable */
+	logerror("'%s': not in whitelist", path);
+	return NULL;		/* Fallthrough. Deny by default */
 }
 
-static int set_dir(const char *dir)
+static int upload(char *dir)
 {
-	if (!path_ok(dir)) {
-		errno = EACCES;
-		return -1;
-	}
+	/* Timeout as string */
+	char timeout_buf[64];
+	const char *path;
 
-	if ( chdir(dir) )
+	loginfo("Request for '%s'", dir);
+
+	if (!(path = path_ok(dir)))
 		return -1;
 
 	/*
@@ -152,45 +136,17 @@
 	 * We want a readable HEAD, usable "objects" directory, and
 	 * a "git-daemon-export-ok" flag that says that the other side
 	 * is ok with us doing this.
+	 *
+	 * path_ok() uses enter_repo() and does whitelist checking.
+	 * We only need to make sure the repository is exported.
 	 */
+
 	if (!export_all_trees && access("git-daemon-export-ok", F_OK)) {
+		logerror("'%s': repository not exported.", path);
 		errno = EACCES;
 		return -1;
 	}
 
-	if (access("objects/", X_OK) || access("HEAD", R_OK)) {
-		errno = EINVAL;
-		return -1;
-	}
-
-	/* If all this passed, we're OK */
-	return 0;
-}
-
-static int upload(char *dir)
-{
-	/* Try paths in this order */
-	static const char *paths[] = { "%s", "%s/.git", "%s.git", "%s.git/.git", NULL };
-	const char **pp;
-	/* Enough for the longest path above including final null */
-	int buflen = strlen(dir)+10;
-	char *dirbuf = xmalloc(buflen);
-	/* Timeout as string */
-	char timeout_buf[64];
-
-	loginfo("Request for '%s'", dir);
-
-	for ( pp = paths ; *pp ; pp++ ) {
-		snprintf(dirbuf, buflen, *pp, dir);
-		if ( !set_dir(dirbuf) )
-			break;
-	}
-
-	if ( !*pp ) {
-		logerror("Cannot set directory '%s': %s", dir, strerror(errno));
-		return -1;
-	}
-
 	/*
 	 * We'll ignore SIGTERM from now on, we have a
 	 * good client.
@@ -216,7 +172,7 @@
 	if (len && line[len-1] == '\n')
 		line[--len] = 0;
 
-	if (!strncmp("git-upload-pack /", line, 17))
+	if (!strncmp("git-upload-pack ", line, 16))
 		return upload(line+16);
 
 	logerror("Protocol error: '%s'", line);
@@ -510,8 +466,14 @@
 		return 0;
 	}
 
+	if (listen(sockfd, 5) < 0) {
+		close(sockfd);
+		return 0;
+	}
+
 	*socklist_p = xmalloc(sizeof(int));
 	**socklist_p = sockfd;
+	return 1;
 }
 
 #endif
@@ -617,6 +579,10 @@
 			init_timeout = atoi(arg+15);
 			continue;
 		}
+		if (!strcmp(arg, "--strict-paths")) {
+			strict_paths = 1;
+			continue;
+		}
 		if (!strcmp(arg, "--")) {
 			ok_paths = &argv[i+1];
 			break;
@@ -631,6 +597,14 @@
 	if (log_syslog)
 		openlog("git-daemon", 0, LOG_DAEMON);
 
+	if (strict_paths && (!ok_paths || !*ok_paths)) {
+		if (!inetd_mode)
+			die("git-daemon: option --strict-paths requires a whitelist");
+
+		logerror("option --strict-paths requires a whitelist");
+		exit (1);
+	}
+
 	if (inetd_mode) {
 		fclose(stderr); //FIXME: workaround
 		return execute();
diff --git a/debian/changelog b/debian/changelog
index 1eda61f..7356fe7 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+git-core (0.99.9k-0) unstable; urgency=low
+
+  * GIT 0.99.9k but not 1.0rc yet.
+
+ -- Junio C Hamano <junkio@cox.net>  Fri, 25 Nov 2005 16:33:11 -0800
+
 git-core (0.99.9j-0) unstable; urgency=low
 
   * GIT 0.99.9j aka 1.0rc3
diff --git a/diff-files.c b/diff-files.c
index 1789939..38599b5 100644
--- a/diff-files.c
+++ b/diff-files.c
@@ -38,7 +38,7 @@
 	const char *prefix = setup_git_directory();
 	int entries, i;
 
-	git_config(git_default_config);
+	git_config(git_diff_config);
 	diff_setup(&diff_options);
 	while (1 < argc && argv[1][0] == '-') {
 		if (!strcmp(argv[1], "--")) {
diff --git a/diff-index.c b/diff-index.c
index c9a9f4c..0054883 100644
--- a/diff-index.c
+++ b/diff-index.c
@@ -180,7 +180,7 @@
 	int allow_options = 1;
 	int i;
 
-	git_config(git_default_config);
+	git_config(git_diff_config);
 	diff_setup(&diff_options);
 	for (i = 1; i < argc; i++) {
 		const char *arg = argv[i];
diff --git a/diff-stages.c b/diff-stages.c
index 85170b2..9968d6c 100644
--- a/diff-stages.c
+++ b/diff-stages.c
@@ -55,6 +55,9 @@
 {
 	int stage1, stage2;
 
+	setup_git_directory();
+
+	git_config(git_diff_config);
 	read_cache();
 	diff_setup(&diff_options);
 	while (1 < ac && av[1][0] == '-') {
diff --git a/diff-tree.c b/diff-tree.c
index 09d16ad..d56d921 100644
--- a/diff-tree.c
+++ b/diff-tree.c
@@ -69,52 +69,50 @@
 	return retval;
 }
 
-static const char *generate_header(const char *commit, const char *parent, const char *msg, unsigned long len)
+static const char *generate_header(const char *commit, const char *parent, const char *msg)
 {
 	static char this_header[16384];
 	int offset;
+	unsigned long len;
 
 	if (!verbose_header)
 		return commit;
 
+	len = strlen(msg);
 	offset = sprintf(this_header, "%s%s (from %s)\n", header_prefix, commit, parent);
 	offset += pretty_print_commit(commit_format, msg, len, this_header + offset, sizeof(this_header) - offset);
 	return this_header;
 }
 
-static int diff_tree_commit(const unsigned char *commit, const char *name)
+static int diff_tree_commit(const unsigned char *commit_sha1)
 {
-	unsigned long size, offset;
-	char *buf = read_object_with_reference(commit, "commit", &size, NULL);
+	struct commit *commit;
+	struct commit_list *parents;
+	char name[50];
+	unsigned char sha1[20];
 
-	if (!buf)
+	sprintf(name, "%s^0", sha1_to_hex(commit_sha1));
+	if (get_sha1(name, sha1))
 		return -1;
-
-	if (!name) {
-		static char commit_name[60];
-		strcpy(commit_name, sha1_to_hex(commit));
-		name = commit_name;
-	}
-
+	name[40] = 0;
+	commit = lookup_commit(sha1);
+	
 	/* Root commit? */
-	if (show_root_diff && memcmp(buf + 46, "parent ", 7)) {
-		header = generate_header(name, "root", buf, size);
-		diff_root_tree(commit, "");
+	if (show_root_diff && !commit->parents) {
+		header = generate_header(name, "root", commit->buffer);
+		diff_root_tree(commit_sha1, "");
 	}
 
 	/* More than one parent? */
-	if (ignore_merges) {
-		if (!memcmp(buf + 46 + 48, "parent ", 7))
+	if (ignore_merges && commit->parents && commit->parents->next)
 			return 0;
-	}
 
-	offset = 46;
-	while (offset + 48 < size && !memcmp(buf + offset, "parent ", 7)) {
-		unsigned char parent[20];
-		if (get_sha1_hex(buf + offset + 7, parent))
-			return -1;
-		header = generate_header(name, sha1_to_hex(parent), buf, size);
-		diff_tree_sha1_top(parent, commit, "");
+	for (parents = commit->parents; parents; parents = parents->next) {
+		struct commit *parent = parents->item;
+		header = generate_header(name,
+					 sha1_to_hex(parent->object.sha1),
+					 commit->buffer);
+		diff_tree_sha1_top(parent->object.sha1, commit_sha1, "");
 		if (!header && verbose_header) {
 			header_prefix = "\ndiff-tree ";
 			/*
@@ -122,9 +120,7 @@
 			 * don't print the diffs.
 			 */
 		}
-		offset += 48;
 	}
-	free(buf);
 	return 0;
 }
 
@@ -147,7 +143,7 @@
 		return diff_tree_sha1_top(parent, commit, "");
 	}
 	line[40] = 0;
-	return diff_tree_commit(commit, line);
+	return diff_tree_commit(commit);
 }
 
 static const char diff_tree_usage[] =
@@ -164,7 +160,7 @@
 	unsigned char sha1[2][20];
 	const char *prefix = setup_git_directory();
 
-	git_config(git_default_config);
+	git_config(git_diff_config);
 	nr_sha1 = 0;
 	diff_setup(&diff_options);
 
@@ -250,7 +246,7 @@
 			usage(diff_tree_usage);
 		break;
 	case 1:
-		diff_tree_commit(sha1[0], NULL);
+		diff_tree_commit(sha1[0]);
 		break;
 	case 2:
 		diff_tree_sha1_top(sha1[0], sha1[1], "");
diff --git a/diff.c b/diff.c
index 0391e8c..2e0797b 100644
--- a/diff.c
+++ b/diff.c
@@ -15,6 +15,16 @@
 
 int diff_rename_limit_default = -1;
 
+int git_diff_config(const char *var, const char *value)
+{
+	if (!strcmp(var, "diff.renamelimit")) {
+		diff_rename_limit_default = git_config_int(var, value);
+		return 0;
+	}
+
+	return git_default_config(var, value);
+}
+
 static char *quote_one(const char *str)
 {
 	int needlen;
@@ -838,16 +848,29 @@
 
 static int parse_num(const char **cp_p)
 {
-	int num, scale, ch, cnt;
+	unsigned long num, scale;
+	int ch, dot;
 	const char *cp = *cp_p;
 
-	cnt = num = 0;
+	num = 0;
 	scale = 1;
-	while ('0' <= (ch = *cp) && ch <= '9') {
-		if (cnt++ < 5) {
-			/* We simply ignore more than 5 digits precision. */
-			scale *= 10;
-			num = num * 10 + ch - '0';
+	dot = 0;
+	for(;;) {
+		ch = *cp;
+		if ( !dot && ch == '.' ) {
+			scale = 1;
+			dot = 1;
+		} else if ( ch == '%' ) {
+			scale = dot ? scale*100 : 100;
+			cp++;	/* % is always at the end */
+			break;
+		} else if ( ch >= '0' && ch <= '9' ) {
+			if ( scale < 100000 ) {
+				scale *= 10;
+				num = (num*10) + (ch-'0');
+			}
+		} else {
+			break;
 		}
 		cp++;
 	}
@@ -856,7 +879,7 @@
 	/* user says num divided by scale and we say internally that
 	 * is MAX_SCORE * num / scale.
 	 */
-	return (MAX_SCORE * num / scale);
+	return (num >= scale) ? MAX_SCORE : (MAX_SCORE * num / scale);
 }
 
 int diff_scoreopt_parse(const char *opt)
diff --git a/diff.h b/diff.h
index 9b2e1e6..32b4780 100644
--- a/diff.h
+++ b/diff.h
@@ -77,6 +77,7 @@
 #define DIFF_SETUP_USE_CACHE		2
 #define DIFF_SETUP_USE_SIZE_CACHE	4
 
+extern int git_diff_config(const char *var, const char *value);
 extern void diff_setup(struct diff_options *);
 extern int diff_opt_parse(struct diff_options *, const char **, int);
 extern int diff_setup_done(struct diff_options *);
diff --git a/diffcore-rename.c b/diffcore-rename.c
index 6a9d95d..dba965c 100644
--- a/diffcore-rename.c
+++ b/diffcore-rename.c
@@ -307,6 +307,9 @@
 	if (rename_count == rename_dst_nr)
 		goto cleanup;
 
+	if (minimum_score == MAX_SCORE)
+		goto cleanup;
+
 	num_create = (rename_dst_nr - rename_count);
 	num_src = rename_src_nr;
 	mx = xmalloc(sizeof(*mx) * num_create * num_src);
diff --git a/git-am.sh b/git-am.sh
index 8f073c9..660b3a4 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -1,7 +1,7 @@
 #!/bin/sh
 #
 #
-. git-sh-setup || die "Not a git archive"
+. git-sh-setup
 
 usage () {
     echo >&2 "usage: $0 [--signoff] [--dotest=<dir>] [--utf8] [--binary] [--3way] <mbox>"
diff --git a/git-applymbox.sh b/git-applymbox.sh
index 6de6932..24d4a8c 100755
--- a/git-applymbox.sh
+++ b/git-applymbox.sh
@@ -18,7 +18,7 @@
 ##
 ## git-am is supposed to be the newer and better tool for this job.
 
-. git-sh-setup || die "Not a git archive"
+. git-sh-setup
 
 usage () {
     echo >&2 "applymbox [-u] [-k] [-q] [-m] (-c .dotest/<num> | mbox) [signoff]"
diff --git a/git-applypatch.sh b/git-applypatch.sh
index 66fd19a..f0549960 100755
--- a/git-applypatch.sh
+++ b/git-applypatch.sh
@@ -10,7 +10,7 @@
 ##	$3 - "info" file with Author, email and subject
 ##	$4 - optional file containing signoff to add
 ##
-. git-sh-setup || die "Not a git archive."
+. git-sh-setup
 
 final=.dotest/final-commit
 ##
diff --git a/git-bisect.sh b/git-bisect.sh
index 1ab2f18..d92993b 100755
--- a/git-bisect.sh
+++ b/git-bisect.sh
@@ -1,5 +1,5 @@
 #!/bin/sh
-. git-sh-setup || dir "Not a git archive"
+. git-sh-setup
 
 usage() {
     echo >&2 'usage: git bisect [start|bad|good|next|reset|visualize]
diff --git a/git-branch.sh b/git-branch.sh
index 2594518..4cd5da16 100755
--- a/git-branch.sh
+++ b/git-branch.sh
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-. git-sh-setup || die "Not a git archive"
+. git-sh-setup
 
 usage () {
     echo >&2 "usage: $(basename $0)"' [-d <branch>] | [[-f] <branch> [start-point]]
diff --git a/git-checkout.sh b/git-checkout.sh
index 4c08f36..4cf30e2 100755
--- a/git-checkout.sh
+++ b/git-checkout.sh
@@ -1,5 +1,5 @@
 #!/bin/sh
-. git-sh-setup || die "Not a git archive"
+. git-sh-setup
 
 usage () {
     die "usage: git checkout [-f] [-b <new_branch>] [<branch>] [<paths>...]"
@@ -82,7 +82,6 @@
 		# rescuing paths and is never meant to remove what
 		# is not in the named tree-ish.
 		git-ls-tree -r "$new" "$@" |
-		sed -ne 's/^\([0-7]*\) blob \(.*\)$/\1 \2/p' |
 		git-update-index --index-info || exit $?
 	fi
 	git-checkout-index -f -u -- "$@"
diff --git a/git-cherry.sh b/git-cherry.sh
index aad2e61..867522b 100755
--- a/git-cherry.sh
+++ b/git-cherry.sh
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano.
 #
 
-. git-sh-setup || die "Not a git archive."
+. git-sh-setup
 
 usage="usage: $0 "'[-v] <upstream> [<head>]
 
diff --git a/git-commit.sh b/git-commit.sh
index 41955e8..3d250ec 100755
--- a/git-commit.sh
+++ b/git-commit.sh
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Linus Torvalds
 #
 
-. git-sh-setup || die "Not a git archive"
+. git-sh-setup
 
 usage () {
 	die 'git commit [-a] [-s] [-v | --no-verify]  [-m <message> | -F <logfile> | (-C|-c) <commit>] [-e] [<path>...]'
@@ -92,10 +92,13 @@
 esac
 
 case "$all,$#" in
-t,*)
+t,0)
 	git-diff-files --name-only -z |
 	git-update-index --remove -z --stdin
 	;;
+t,*)
+	die "Cannot use -a and explicit files at the same time."
+	;;
 ,0)
 	;;
 *)
diff --git a/git-count-objects.sh b/git-count-objects.sh
index 843d2fd..d6e9a32 100755
--- a/git-count-objects.sh
+++ b/git-count-objects.sh
@@ -1,7 +1,25 @@
 #!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
 
 . git-sh-setup
 
+dc </dev/null 2>/dev/null || {
+	# This is not a real DC at all -- it just knows how
+	# this script feeds DC and does the computation itself.
+	dc () {
+		while read a b
+		do
+			case $a,$b in
+			0,)	acc=0 ;;
+			*,+)	acc=$(($acc + $a)) ;;
+			p,)	echo "$acc" ;;
+			esac
+		done
+	}
+}
+
 echo $(find "$GIT_DIR/objects"/?? -type f -print 2>/dev/null | wc -l) objects, \
 $({
     echo 0
diff --git a/git-cvsimport.perl b/git-cvsimport.perl
index efe1934..08a890c 100755
--- a/git-cvsimport.perl
+++ b/git-cvsimport.perl
@@ -502,7 +502,7 @@
 	if ($opt_P) {
 	    exec("cat", $opt_P);
 	} else {
-	    exec("cvsps",@opt,"-u","-A",'--root',$opt_d,$cvs_tree);
+	    exec("cvsps","--norc",@opt,"-u","-A",'--root',$opt_d,$cvs_tree);
 	    die "Could not start cvsps: $!\n";
 	}
 }
diff --git a/git-fetch.sh b/git-fetch.sh
index 6586e77..14ea295 100755
--- a/git-fetch.sh
+++ b/git-fetch.sh
@@ -1,6 +1,6 @@
 #!/bin/sh
 #
-. git-sh-setup || die "Not a git archive"
+. git-sh-setup
 . git-parse-remote
 _x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
 _x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
diff --git a/git-format-patch.sh b/git-format-patch.sh
index 7ee5d32..bc56876 100755
--- a/git-format-patch.sh
+++ b/git-format-patch.sh
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
-. git-sh-setup || die "Not a git archive."
+. git-sh-setup
 
 usage () {
     echo >&2 "usage: $0"' [-n] [-o dir | --stdout] [--keep-subject] [--mbox]
@@ -99,7 +99,7 @@
 # Also, "rev1.." should mean "rev1..HEAD"; git-diff users are
 # familiar with that syntax.
 
-case "$#,$1" in
+case "$#,$1$2" in
 1,?*..?*)
 	# single "rev1..rev2"
 	;;
@@ -131,7 +131,8 @@
 		rev2=`expr "$revpair" : '.*\.\.\(.*\)'`
 		;;
 	*)
-		usage
+		rev1="$revpair^"
+		rev2="$revpair"
 		;;
 	esac
 	git-rev-parse --verify "$rev1^0" >/dev/null 2>&1 ||
diff --git a/git-lost-found.sh b/git-lost-found.sh
index 3892f52..9dd7430 100755
--- a/git-lost-found.sh
+++ b/git-lost-found.sh
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-. git-sh-setup || die "Not a git archive."
+. git-sh-setup
 
 laf="$GIT_DIR/lost-found"
 rm -fr "$laf" && mkdir -p "$laf/commit" "$laf/other" || exit
diff --git a/git-ls-remote.sh b/git-ls-remote.sh
index f0f0b07..dc6a775 100755
--- a/git-ls-remote.sh
+++ b/git-ls-remote.sh
@@ -1,6 +1,5 @@
 #!/bin/sh
 #
-. git-sh-setup
 
 usage () {
     echo >&2 "usage: $0 [--heads] [--tags] <repository> <refs>..."
diff --git a/git-merge-one-file.sh b/git-merge-one-file.sh
index b08597d..c3eca8b 100755
--- a/git-merge-one-file.sh
+++ b/git-merge-one-file.sh
@@ -25,7 +25,8 @@
 		echo "Removing $4"
 	fi
 	if test -f "$4"; then
-		rm -f -- "$4"
+		rm -f -- "$4" &&
+		rmdir -p "$(expr "$4" : '\(.*\)/')" 2>/dev/null
 	fi &&
 		exec git-update-index --remove -- "$4"
 	;;
diff --git a/git-merge-recursive.py b/git-merge-recursive.py
index d7d36aa..0129233 100755
--- a/git-merge-recursive.py
+++ b/git-merge-recursive.py
@@ -245,7 +245,7 @@
 
             try:
                 createDir = not stat.S_ISDIR(os.lstat(p).st_mode)
-            except: 
+            except OSError:
                 createDir = True
             
             if createDir:
@@ -293,6 +293,10 @@
         except OSError, e:
             if e.errno != errno.ENOENT and e.errno != errno.EISDIR:
                 raise
+        try:
+            os.removedirs(os.path.dirname(path))
+        except OSError:
+            pass
 
 def uniquePath(path, branch):
     def fileExists(path):
diff --git a/git-merge.sh b/git-merge.sh
index 7f481e4..d352a3c 100755
--- a/git-merge.sh
+++ b/git-merge.sh
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
-. git-sh-setup || die "Not a git archive"
+. git-sh-setup
 
 LF='
 '
@@ -12,10 +12,8 @@
     die "git-merge [-n] [--no-commit] [-s <strategy>]... <merge-message> <head> <remote>+"
 }
 
-# all_strategies='resolve recursive stupid octopus'
-
 all_strategies='recursive octopus resolve stupid ours'
-default_strategies='resolve octopus'
+default_strategies='recursive'
 use_strategies=
 
 dropsave() {
@@ -90,11 +88,6 @@
 	shift
 done
 
-case "$use_strategies" in
-'')
-	use_strategies=$default_strategies
-	;;
-esac
 test "$#" -le 2 && usage ;# we need at least two heads.
 
 merge_msg="$1"
@@ -185,6 +178,17 @@
 	;;
 esac
 
+case "$use_strategies" in
+'')
+	case "$#" in
+	1)
+		use_strategies="$default_strategies" ;;
+	*)
+		use_strategies=octopus ;;
+	esac		
+	;;
+esac
+
 # At this point, we need a real merge.  No matter what strategy
 # we use, it would operate on the index, possibly affecting the
 # working tree, and when resolved cleanly, have the desired tree
diff --git a/git-mv.perl b/git-mv.perl
index a21d87e..bf54c38 100755
--- a/git-mv.perl
+++ b/git-mv.perl
@@ -103,13 +103,22 @@
 	$bad = "bad source '$src'";
     }
 
+    $safesrc = quotemeta($src);
+    @srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
+
     $overwritten{$dst} = 0;
     if (($bad eq "") && -e $dst) {
 	$bad = "destination '$dst' already exists";
-	if (-f $dst && $opt_f) {
-	    print "Warning: $bad; will overwrite!\n";
-	    $bad = "";
-	    $overwritten{$dst} = 1;
+	if ($opt_f) {
+	    # only files can overwrite each other: check both source and destination
+	    if (-f $dst && (scalar @srcfiles == 1)) {
+		print "Warning: $bad; will overwrite!\n";
+		$bad = "";
+		$overwritten{$dst} = 1;
+	    }
+	    else {
+		$bad = "Can not overwrite '$src' with '$dst'";
+	    }
 	}
     }
     
@@ -118,8 +127,6 @@
     }
 
     if ($bad eq "") {
-	$safesrc = quotemeta($src);
-	@srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
         if (scalar @srcfiles == 0) {
 	    $bad = "'$src' not under version control";
 	}
@@ -166,10 +173,12 @@
 
     push @deletedfiles, @srcfiles;
     if (scalar @srcfiles == 1) {
+	# $dst can be a directory with 1 file inside
 	if ($overwritten{$dst} ==1) {
-	    push @changedfiles, $dst;
+	    push @changedfiles, $dstfiles[0];
+
 	} else {
-	    push @addedfiles, $dst;
+	    push @addedfiles, $dstfiles[0];
 	}
     }
     else {
diff --git a/git-octopus.sh b/git-octopus.sh
index d2471af..2edbf52 100755
--- a/git-octopus.sh
+++ b/git-octopus.sh
@@ -4,7 +4,7 @@
 #
 # Resolve two or more trees recorded in $GIT_DIR/FETCH_HEAD.
 #
-. git-sh-setup || die "Not a git archive"
+. git-sh-setup
 
 usage () {
     die "usage: git octopus"
diff --git a/git-parse-remote.sh b/git-parse-remote.sh
index aea7b0e..5f158c6 100755
--- a/git-parse-remote.sh
+++ b/git-parse-remote.sh
@@ -1,6 +1,8 @@
 #!/bin/sh
 
-. git-sh-setup
+# git-ls-remote could be called from outside a git managed repository;
+# this would fail in that case and would issue an error message.
+GIT_DIR=$(git-rev-parse --git-dir 2>/dev/null) || :;
 
 get_data_source () {
 	case "$1" in
diff --git a/git-prune.sh b/git-prune.sh
index c4de7f5..1fd8c73 100755
--- a/git-prune.sh
+++ b/git-prune.sh
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-. git-sh-setup || die "Not a git archive"
+. git-sh-setup
 
 dryrun=
 echo=
diff --git a/git-pull.sh b/git-pull.sh
index 3b875ad..3a13984 100755
--- a/git-pull.sh
+++ b/git-pull.sh
@@ -4,7 +4,7 @@
 #
 # Fetch one or more remote refs and merge it/them into the current HEAD.
 
-. git-sh-setup || die "Not a git archive"
+. git-sh-setup
 
 usage () {
     echo >&2 "usage: $0"' [-n] [--no-commit] [--no-summary] [--help]
diff --git a/git-push.sh b/git-push.sh
index edc0b83..140c8f8 100755
--- a/git-push.sh
+++ b/git-push.sh
@@ -1,5 +1,5 @@
 #!/bin/sh
-. git-sh-setup || die "Not a git archive"
+. git-sh-setup
 
 usage () {
     die "Usage: git push [--all] [--force] <repository> [<refspec>]"
diff --git a/git-rebase.sh b/git-rebase.sh
index 5289762..2bc3a12 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano.
 #
 
-. git-sh-setup || die "Not a git archive."
+. git-sh-setup
 
 # The other head is given
 other=$(git-rev-parse --verify "$1^0") || exit
diff --git a/git-repack.sh b/git-repack.sh
index 55a7b27..430ddc5 100755
--- a/git-repack.sh
+++ b/git-repack.sh
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Linus Torvalds
 #
 
-. git-sh-setup || die "Not a git archive"
+. git-sh-setup
 	
 no_update_info= all_into_one= remove_redundant= local=
 while case "$#" in 0) break ;; esac
@@ -32,24 +32,20 @@
 	rev_list=
 	rev_parse='--all'
 	pack_objects=
+
+	# Redundancy check in all-into-one case is trivial.
+	existing=`cd "$PACKDIR" && \
+	    find . -type f \( -name '*.pack' -o -name '*.idx' \) -print`
 	;;
 esac
 if [ "$local" ]; then
 	pack_objects="$pack_objects --local"
 fi
-name=$(git-rev-list --objects $rev_list $(git-rev-parse $rev_parse) |
+name=$(git-rev-list --objects $rev_list $(git-rev-parse $rev_parse) 2>&1 |
 	git-pack-objects --non-empty $pack_objects .tmp-pack) ||
 	exit 1
 if [ -z "$name" ]; then
 	echo Nothing new to pack.
-	if test "$remove_redundant" = t ; then
-		echo "Removing redundant packs."
-		sync
-		redundant=$(git-pack-redundant --all)
-		if test "$redundant" != "" ; then
-			echo $redundant | xargs rm
-		fi
-	fi
 	exit 0
 fi
 echo "Pack pack-$name created."
@@ -62,23 +58,20 @@
 
 if test "$remove_redundant" = t
 then
-	sync
-	if test "$all_into_one" = t
+	# We know $existing are all redundant only when
+	# all-into-one is used.
+	if test "$all_into_one" != '' && test "$existing" != ''
 	then
-		cd "$PACKDIR"
-		existing=`find . -type f \( -name '*.pack' -o -name '*.idx' \) -print`
-		for e in $existing
-		do
+		sync
+		( cd "$PACKDIR" &&
+		  for e in $existing
+		  do
 			case "$e" in
 			./pack-$name.pack | ./pack-$name.idx) ;;
-			*)      rm -f $e ;;
+			*)	rm -f $e ;;
 			esac
-		done
-	else
-		redundant=$(git-pack-redundant --all)
-		if test "$redundant" != "" ; then
-			echo $redundant | xargs rm
-		fi
+		  done
+		)
 	fi
 fi
 
diff --git a/git-reset.sh b/git-reset.sh
index 2086d26..72ef303 100755
--- a/git-reset.sh
+++ b/git-reset.sh
@@ -1,5 +1,5 @@
 #!/bin/sh
-. git-sh-setup || die "Not a git archive"
+. git-sh-setup
 
 usage () {
 	die 'Usage: git reset [--mixed | --soft | --hard]  [<commit-ish>]'
diff --git a/git-resolve.sh b/git-resolve.sh
index 7d8fb54..fcc5ad7 100755
--- a/git-resolve.sh
+++ b/git-resolve.sh
@@ -4,7 +4,7 @@
 #
 # Resolve two trees.
 #
-. git-sh-setup || die "Not a git archive"
+. git-sh-setup
 
 usage () {
 	die "git-resolve <head> <remote> <merge-message>"
diff --git a/git-revert.sh b/git-revert.sh
index 4154fe0..c1aebb1 100755
--- a/git-revert.sh
+++ b/git-revert.sh
@@ -3,15 +3,17 @@
 # Copyright (c) 2005 Linus Torvalds
 # Copyright (c) 2005 Junio C Hamano
 #
-. git-sh-setup || die "Not a git archive"
+. git-sh-setup
 
 case "$0" in
 *-revert* )
+	test -t 0 && edit=-e
 	me=revert ;;
 *-cherry-pick* )
+	edit=
 	me=cherry-pick ;;
 * )
-	die "What are ou talking about?" ;;
+	die "What are you talking about?" ;;
 esac
 
 usage () {
@@ -33,6 +35,12 @@
 	    --no-commi|--no-commit)
 		no_commit=t
 		;;
+	-e|--e|--ed|--edi|--edit)
+		edit=-e
+		;;
+	-n|--n|--no|--no-|--no-e|--no-ed|--no-edi|--no-edit)
+		edit=
+		;;
 	-r|--r|--re|--rep|--repl|--repla|--replay)
 		replay=t
 		;;
@@ -163,7 +171,7 @@
 
 case "$no_commit" in
 '')
-	git-commit -n -F .msg
+	git-commit -n -F .msg $edit
 	rm -f .msg
 	;;
 esac
diff --git a/git-sh-setup.sh b/git-sh-setup.sh
index dbb9884..b4f1022 100755
--- a/git-sh-setup.sh
+++ b/git-sh-setup.sh
@@ -1,10 +1,9 @@
 #!/bin/sh
 #
-# Set up GIT_DIR and GIT_OBJECT_DIRECTORY
-# and return true if everything looks ok
-#
-: ${GIT_DIR=.git}
-: ${GIT_OBJECT_DIRECTORY="$GIT_DIR/objects"}
+# This is included in commands that either have to be run from the toplevel
+# of the repository, or with GIT_DIR environment variable properly.
+# If the GIT_DIR does not look like the right correct git-repository,
+# it dies.
 
 # Having this variable in your environment would break scripts because
 # you would cause "cd" to be be taken to unexpected places.  If you
@@ -12,14 +11,13 @@
 # exporting it.
 unset CDPATH
 
+: ${GIT_DIR=.git}
+: ${GIT_OBJECT_DIRECTORY="$GIT_DIR/objects"}
+
 die() {
 	echo >&2 "$@"
 	exit 1
 }
 
-case "$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD 2>/dev/null)" in
-refs/*)	: ;;
-*)	false ;;
-esac &&
-[ -d "$GIT_DIR/refs" ] &&
-[ -d "$GIT_OBJECT_DIRECTORY/" ]
+# Make sure we are in a valid repository of a vintage we understand.
+GIT_DIR="$GIT_DIR" git-var GIT_AUTHOR_IDENT >/dev/null || exit
diff --git a/git-status.sh b/git-status.sh
index 837f334..b90ffc1 100755
--- a/git-status.sh
+++ b/git-status.sh
@@ -2,7 +2,7 @@
 #
 # Copyright (c) 2005 Linus Torvalds
 #
-. git-sh-setup || die "Not a git archive"
+GIT_DIR=$(git-rev-parse --git-dir) || exit
 
 report () {
   header="#
diff --git a/git-tag.sh b/git-tag.sh
index 1375945..16efc5b 100755
--- a/git-tag.sh
+++ b/git-tag.sh
@@ -1,7 +1,7 @@
 #!/bin/sh
 # Copyright (c) 2005 Linus Torvalds
 
-. git-sh-setup || die "Not a git archive"
+. git-sh-setup
 
 usage () {
     echo >&2 "Usage: git-tag [-a | -s | -u <key-id>] [-f | -d] [-m <msg>] <tagname> [<head>]"
diff --git a/git-verify-tag.sh b/git-verify-tag.sh
index ed4c893..3c65f4a 100755
--- a/git-verify-tag.sh
+++ b/git-verify-tag.sh
@@ -1,5 +1,5 @@
 #!/bin/sh
-. git-sh-setup || die "Not a git archive"
+. git-sh-setup
 
 type="$(git-cat-file -t "$1" 2>/dev/null)" ||
 	die "$1: no such object."
diff --git a/git.c b/git.c
index bdd3f8d..0b10b6e 100644
--- a/git.c
+++ b/git.c
@@ -273,7 +273,7 @@
 		while (!strncmp(exec_path, "./", 2)) {
 			exec_path += 2;
 			while (*exec_path == '/')
-				*exec_path++;
+				exec_path++;
 		}
 		snprintf(git_command + len, sizeof(git_command) - len,
 			 "/%s", exec_path);
diff --git a/gitk b/gitk
index a9d37d9..3dd97e2 100755
--- a/gitk
+++ b/gitk
@@ -60,7 +60,7 @@
 
 proc getcommitlines {commfd}  {
     global commits parents cdate children
-    global commitlisted phase commitinfo nextupdate
+    global commitlisted phase nextupdate
     global stopped redisplaying leftover
 
     set stuff [read $commfd]
@@ -196,42 +196,44 @@
 	    incr ncleft($p)
 	}
     }
-    foreach line [split $contents "\n"] {
-	if {$inhdr} {
-	    if {$line == {}} {
-		set inhdr 0
-	    } else {
-		set tag [lindex $line 0]
-		if {$tag == "author"} {
-		    set x [expr {[llength $line] - 2}]
-		    set audate [lindex $line $x]
-		    set auname [lrange $line 1 [expr {$x - 1}]]
-		} elseif {$tag == "committer"} {
-		    set x [expr {[llength $line] - 2}]
-		    set comdate [lindex $line $x]
-		    set comname [lrange $line 1 [expr {$x - 1}]]
-		}
-	    }
-	} else {
-	    if {$comment == {}} {
-		set headline [string trim $line]
-	    } else {
-		append comment "\n"
-	    }
-	    if {!$listed} {
-		# git-rev-list indents the comment by 4 spaces;
-		# if we got this via git-cat-file, add the indentation
-		append comment "    "
-	    }
-	    append comment $line
+    set hdrend [string first "\n\n" $contents]
+    if {$hdrend < 0} {
+	# should never happen...
+	set hdrend [string length $contents]
+    }
+    set header [string range $contents 0 [expr {$hdrend - 1}]]
+    set comment [string range $contents [expr {$hdrend + 2}] end]
+    foreach line [split $header "\n"] {
+	set tag [lindex $line 0]
+	if {$tag == "author"} {
+	    set audate [lindex $line end-1]
+	    set auname [lrange $line 1 end-2]
+	} elseif {$tag == "committer"} {
+	    set comdate [lindex $line end-1]
+	    set comname [lrange $line 1 end-2]
 	}
     }
-    if {$audate != {}} {
-	set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"]
+    set headline {}
+    # take the first line of the comment as the headline
+    set i [string first "\n" $comment]
+    if {$i >= 0} {
+	set headline [string trim [string range $comment 0 $i]]
+    } else {
+	set headline $comment
+    }
+    if {!$listed} {
+	# git-rev-list indents the comment by 4 spaces;
+	# if we got this via git-cat-file, add the indentation
+	set newcomment {}
+	foreach line [split $comment "\n"] {
+	    append newcomment "    "
+	    append newcomment $line
+	    append newcomment "\n"
+	}
+	set comment $newcomment
     }
     if {$comdate != {}} {
 	set cdate($id) $comdate
-	set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
     }
     set commitinfo($id) [list $headline $auname $audate \
 			     $comname $comdate $comment]
@@ -239,77 +241,43 @@
 
 proc readrefs {} {
     global tagids idtags headids idheads tagcontents
-
-    set tags [glob -nocomplain -types f [gitdir]/refs/tags/*]
-    foreach f $tags {
-	catch {
-	    set fd [open $f r]
-	    set line [read $fd]
-	    if {[regexp {^[0-9a-f]{40}} $line id]} {
-		set direct [file tail $f]
-		set tagids($direct) $id
-		lappend idtags($id) $direct
-		set tagblob [exec git-cat-file tag $id]
-		set contents [split $tagblob "\n"]
-		set obj {}
-		set type {}
-		set tag {}
-		foreach l $contents {
-		    if {$l == {}} break
-		    switch -- [lindex $l 0] {
-			"object" {set obj [lindex $l 1]}
-			"type" {set type [lindex $l 1]}
-			"tag" {set tag [string range $l 4 end]}
-		    }
-		}
-		if {$obj != {} && $type == "commit" && $tag != {}} {
-		    set tagids($tag) $obj
-		    lappend idtags($obj) $tag
-		    set tagcontents($tag) $tagblob
-		}
-	    }
-	    close $fd
-	}
-    }
-    set heads [glob -nocomplain -types f [gitdir]/refs/heads/*]
-    foreach f $heads {
-	catch {
-	    set fd [open $f r]
-	    set line [read $fd 40]
-	    if {[regexp {^[0-9a-f]{40}} $line id]} {
-		set head [file tail $f]
-		set headids($head) $line
-		lappend idheads($line) $head
-	    }
-	    close $fd
-	}
-    }
-    readotherrefs refs {} {tags heads}
-}
-
-proc readotherrefs {base dname excl} {
     global otherrefids idotherrefs
 
-    set git [gitdir]
-    set files [glob -nocomplain -types f [file join $git $base *]]
-    foreach f $files {
-	catch {
-	    set fd [open $f r]
-	    set line [read $fd 40]
-	    if {[regexp {^[0-9a-f]{40}} $line id]} {
-		set name "$dname[file tail $f]"
-		set otherrefids($name) $id
-		lappend idotherrefs($id) $name
+    set refd [open [list | git-ls-remote [gitdir]] r]
+    while {0 <= [set n [gets $refd line]]} {
+	if {![regexp {^([0-9a-f]{40})	refs/([^^]*)$} $line \
+	    match id path]} {
+	    continue
+	}
+	if {![regexp {^(tags|heads)/(.*)$} $path match type name]} {
+	    set type others
+	    set name $path
+	}
+	if {$type == "tags"} {
+	    set tagids($name) $id
+	    lappend idtags($id) $name
+	    set obj {}
+	    set type {}
+	    set tag {}
+	    catch {
+		set commit [exec git-rev-parse "$id^0"]
+		if {"$commit" != "$id"} {
+		    set tagids($name) $commit
+		    lappend idtags($commit) $name
+		}
+	    }		
+	    catch {
+	        set tagcontents($name) [exec git-cat-file tag "$id"]
 	    }
-	    close $fd
+	} elseif { $type == "heads" } {
+	    set headids($name) $id
+	    lappend idheads($id) $name
+	} else {
+	    set otherrefids($name) $id
+	    lappend idotherrefs($id) $name
 	}
     }
-    set dirs [glob -nocomplain -types d [file join $git $base *]]
-    foreach d $dirs {
-	set dir [file tail $d]
-	if {[lsearch -exact $excl $dir] >= 0} continue
-	readotherrefs [file join $base $dir] "$dname$dir/" {}
-    }
+    close $refd
 }
 
 proc error_popup msg {
@@ -683,7 +651,7 @@
 }
 
 proc assigncolor {id} {
-    global commitinfo colormap commcolors colors nextcolor
+    global colormap commcolors colors nextcolor
     global parents nparents children nchildren
     global cornercrossings crossings
 
@@ -783,10 +751,12 @@
     $canv bind $t <Button-1> "lineclick %x %y $id 1"
 }
 
-proc drawlines {id xtra} {
+proc drawlines {id xtra delold} {
     global mainline mainlinearrow sidelines lthickness colormap canv
 
-    $canv delete lines.$id
+    if {$delold} {
+	$canv delete lines.$id
+    }
     if {[info exists mainline($id)]} {
 	set t [$canv create line $mainline($id) \
 		   -width [expr {($xtra + 1) * $lthickness}] \
@@ -858,7 +828,7 @@
 	    set mainline($id) [trimdiagstart $mainline($id)]
 	}
     }
-    drawlines $id 0
+    drawlines $id 0 0
     set orad [expr {$linespc / 3}]
     set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \
 	       [expr $x + $orad - 1] [expr $y1 + $orad - 1] \
@@ -878,6 +848,7 @@
     set headline [lindex $commitinfo($id) 0]
     set name [lindex $commitinfo($id) 1]
     set date [lindex $commitinfo($id) 2]
+    set date [formatdate $date]
     set linehtag($lineno) [$canv create text $xt $y1 -anchor w \
 			       -text $headline -font $mainfont ]
     $canv bind $linehtag($lineno) <Button-3> "rowmenu %X %Y $id"
@@ -1446,8 +1417,8 @@
 }
 
 proc drawcommit {id} {
-    global phase todo nchildren datemode nextupdate
-    global numcommits ncmupdate displayorder todo onscreen
+    global phase todo nchildren datemode nextupdate revlistorder
+    global numcommits ncmupdate displayorder todo onscreen parents
 
     if {$phase != "incrdraw"} {
 	set phase incrdraw
@@ -1459,19 +1430,29 @@
 	lappend todo $id
 	set onscreen($id) 0
     }
-    set level [decidenext 1]
-    if {$level == {} || $id != [lindex $todo $level]} {
-	return
-    }
-    while 1 {
-	lappend displayorder [lindex $todo $level]
-	if {[updatetodo $level $datemode]} {
-	    set level [decidenext 1]
-	    if {$level == {}} break
+    if {$revlistorder} {
+	set level [lsearch -exact $todo $id]
+	if {$level < 0} {
+	    error_popup "oops, $id isn't in todo"
+	    return
 	}
-	set id [lindex $todo $level]
-	if {![info exists commitlisted($id)]} {
-	    break
+	lappend displayorder $id
+	updatetodo $level 0
+    } else {
+	set level [decidenext 1]
+	if {$level == {} || $id != [lindex $todo $level]} {
+	    return
+	}
+	while 1 {
+	    lappend displayorder [lindex $todo $level]
+	    if {[updatetodo $level $datemode]} {
+		set level [decidenext 1]
+		if {$level == {}} break
+	    }
+	    set id [lindex $todo $level]
+	    if {![info exists commitlisted($id)]} {
+		break
+	    }
 	}
     }
     drawmore 1
@@ -1523,7 +1504,7 @@
     global phase stopped redisplaying selectedline
     global datemode todo displayorder
     global numcommits ncmupdate
-    global nextupdate startmsecs
+    global nextupdate startmsecs revlistorder
 
     set level [decidenext]
     if {$level >= 0} {
@@ -1536,8 +1517,8 @@
 		if {$level < 0} break
 	    }
 	}
-	drawmore 0
     }
+    drawmore 0
     set phase {}
     set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs]
     #puts "overall $drawmsecs ms for $numcommits commits"
@@ -2146,8 +2127,10 @@
     $ctext mark set fmark.0 0.0
     $ctext mark gravity fmark.0 left
     set info $commitinfo($id)
-    $ctext insert end "Author: [lindex $info 1]  [lindex $info 2]\n"
-    $ctext insert end "Committer: [lindex $info 3]  [lindex $info 4]\n"
+    set date [formatdate [lindex $info 2]]
+    $ctext insert end "Author: [lindex $info 1]  $date\n"
+    set date [formatdate [lindex $info 4]]
+    $ctext insert end "Committer: [lindex $info 3]  $date\n"
     if {[info exists idtags($id)]} {
 	$ctext insert end "Tags:"
 	foreach tag $idtags($id) {
@@ -2805,8 +2788,7 @@
     set treepending $ids
     set treediff {}
     set id [lindex $ids 0]
-    set p [lindex $ids 1]
-    if [catch {set gdtf [open "|git-diff-tree -r $id" r]}] return
+    if [catch {set gdtf [open "|git-diff-tree --no-commit-id -r $id" r]}] return
     fconfigure $gdtf -blocking 0
     fileevent $gdtf readable [list gettreediffline $gdtf $ids]
 }
@@ -2840,9 +2822,8 @@
     global difffilestart nextupdate diffinhdr treediffs
 
     set id [lindex $ids 0]
-    set p [lindex $ids 1]
     set env(GIT_DIFF_OPTS) $diffopts
-    set cmd [list | git-diff-tree -r -p -C $id]
+    set cmd [list | git-diff-tree --no-commit-id -r -p -C $id]
     if {[catch {set bdf [open $cmd r]} err]} {
 	puts "error getting diffs: $err"
 	return
@@ -3143,7 +3124,7 @@
     set t [$canv create rectangle $x0 $y0 $x1 $y1 \
 	       -fill \#ffff80 -outline black -width 1 -tags hover]
     $canv raise $t
-    set t [$canv create text $x $y -anchor nw -text $text -tags hover]
+    set t [$canv create text $x $y -anchor nw -text $text -tags hover -font $mainfont]
     $canv raise $t
 }
 
@@ -3178,7 +3159,7 @@
 }
 
 proc arrowjump {id dirn y} {
-    global mainline sidelines canv
+    global mainline sidelines canv canv2 canv3
 
     set yt {}
     if {$dirn eq "down"} {
@@ -3216,6 +3197,8 @@
 	set yfrac 0
     }
     $canv yview moveto $yfrac
+    $canv2 yview moveto $yfrac
+    $canv3 yview moveto $yfrac
 }
 
 proc lineclick {x y id isnew} {
@@ -3226,7 +3209,7 @@
     normalline
     $canv delete hover
     # draw this line thicker than normal
-    drawlines $id 1
+    drawlines $id 1 1
     set thickerline $id
     if {$isnew} {
 	set ymax [lindex [$canv cget -scrollregion] 3]
@@ -3255,7 +3238,8 @@
     set info $commitinfo($id)
     $ctext insert end "\n\t[lindex $info 0]\n"
     $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
-    $ctext insert end "\tDate:\t[lindex $info 2]\n"
+    set date [formatdate [lindex $info 2]]
+    $ctext insert end "\tDate:\t$date\n"
     if {[info exists children($id)]} {
 	$ctext insert end "\nChildren:"
 	set i 0
@@ -3267,7 +3251,8 @@
 	    $ctext tag bind link$i <1> [list selbyid $child]
 	    $ctext insert end "\n\t[lindex $info 0]"
 	    $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
-	    $ctext insert end "\n\tDate:\t[lindex $info 2]\n"
+	    set date [formatdate [lindex $info 2]]
+	    $ctext insert end "\n\tDate:\t$date\n"
 	}
     }
     $ctext conf -state disabled
@@ -3278,7 +3263,7 @@
 proc normalline {} {
     global thickerline
     if {[info exists thickerline]} {
-	drawlines $thickerline 0
+	drawlines $thickerline 0 1
 	unset thickerline
     }
 }
@@ -3650,6 +3635,23 @@
     destroy .
 }
 
+proc formatdate {d} {
+    global hours nhours tfd fastdate
+
+    if {!$fastdate} {
+	return [clock format $d -format "%Y-%m-%d %H:%M:%S"]
+    }
+    set hr [expr {$d / 3600}]
+    set ms [expr {$d % 3600}]
+    if {![info exists hours($hr)]} {
+	set hours($hr) [clock format $d -format "%Y-%m-%d %H"]
+	set nhours($hr) 0
+    }
+    incr nhours($hr)
+    set minsec [format "%.2d:%.2d" [expr {$ms/60}] [expr {$ms%60}]]
+    return "$hours($hr):$minsec"
+}
+
 # defaults...
 set datemode 0
 set boldnames 0
@@ -3662,6 +3664,8 @@
 set gaudydiff 0
 set maxgraphpct 50
 set maxwidth 16
+set revlistorder 0
+set fastdate 0
 
 set colors {green red blue magenta darkgrey brown orange}
 
@@ -3678,6 +3682,7 @@
 	"^$" { }
 	"^-b" { set boldnames 1 }
 	"^-d" { set datemode 1 }
+	"^-r" { set revlistorder 1 }
 	default {
 	    lappend revtreeargs $arg
 	}
diff --git a/http-fetch.c b/http-fetch.c
index 21cc1b9..4353173 100644
--- a/http-fetch.c
+++ b/http-fetch.c
@@ -2,44 +2,14 @@
 #include "commit.h"
 #include "pack.h"
 #include "fetch.h"
-
-#include <curl/curl.h>
-#include <curl/easy.h>
-
-#if LIBCURL_VERSION_NUM >= 0x070908
-#define USE_CURL_MULTI
-#define DEFAULT_MAX_REQUESTS 5
-#endif
-
-#if LIBCURL_VERSION_NUM < 0x070704
-#define curl_global_cleanup() do { /* nothing */ } while(0)
-#endif
-#if LIBCURL_VERSION_NUM < 0x070800
-#define curl_global_init(a) do { /* nothing */ } while(0)
-#endif
-
-#if LIBCURL_VERSION_NUM < 0x070c04
-#define NO_CURL_EASY_DUPHANDLE
-#endif
+#include "http.h"
 
 #define PREV_BUF_SIZE 4096
 #define RANGE_HEADER_SIZE 30
 
 static int got_alternates = -1;
-static int active_requests = 0;
-static int data_received;
 
-#ifdef USE_CURL_MULTI
-static int max_requests = -1;
-static CURLM *curlm;
-#endif
-#ifndef NO_CURL_EASY_DUPHANDLE
-static CURL *curl_default;
-#endif
-static struct curl_slist *pragma_header;
 static struct curl_slist *no_pragma_header;
-static struct curl_slist *no_range_header;
-static char curl_errorstr[CURL_ERROR_SIZE];
 
 struct alt_base
 {
@@ -51,14 +21,14 @@
 
 static struct alt_base *alt = NULL;
 
-enum transfer_state {
+enum object_request_state {
 	WAITING,
 	ABORTED,
 	ACTIVE,
 	COMPLETE,
 };
 
-struct transfer_request
+struct object_request
 {
 	unsigned char sha1[20];
 	struct alt_base *repo;
@@ -66,7 +36,7 @@
 	char filename[PATH_MAX];
 	char tmpfile[PATH_MAX];
 	int local;
-	enum transfer_state state;
+	enum object_request_state state;
 	CURLcode curl_result;
 	char errorstr[CURL_ERROR_SIZE];
 	long http_code;
@@ -76,23 +46,10 @@
 	int zret;
 	int rename;
 	struct active_request_slot *slot;
-	struct transfer_request *next;
+	struct object_request *next;
 };
 
-struct active_request_slot
-{
-	CURL *curl;
-	FILE *local;
-	int in_use;
-	int done;
-	CURLcode curl_result;
-	long http_code;
-	void *callback_data;
-	void (*callback_func)(void *data);
-	struct active_request_slot *next;
-};
-
-struct alt_request {
+struct alternates_request {
 	char *base;
 	char *url;
 	struct buffer *buffer;
@@ -100,120 +57,7 @@
 	int http_specific;
 };
 
-static struct transfer_request *request_queue_head = NULL;
-static struct active_request_slot *active_queue_head = NULL;
-
-static int curl_ssl_verify = -1;
-static char *ssl_cert = NULL;
-#if LIBCURL_VERSION_NUM >= 0x070902
-static char *ssl_key = NULL;
-#endif
-#if LIBCURL_VERSION_NUM >= 0x070908
-static char *ssl_capath = NULL;
-#endif
-static char *ssl_cainfo = NULL;
-static long curl_low_speed_limit = -1;
-static long curl_low_speed_time = -1;
-
-struct buffer
-{
-        size_t posn;
-        size_t size;
-        void *buffer;
-};
-
-static int http_options(const char *var, const char *value)
-{
-	if (!strcmp("http.sslverify", var)) {
-		if (curl_ssl_verify == -1) {
-			curl_ssl_verify = git_config_bool(var, value);
-		}
-		return 0;
-	}
-
-	if (!strcmp("http.sslcert", var)) {
-		if (ssl_cert == NULL) {
-			ssl_cert = xmalloc(strlen(value)+1);
-			strcpy(ssl_cert, value);
-		}
-		return 0;
-	}
-#if LIBCURL_VERSION_NUM >= 0x070902
-	if (!strcmp("http.sslkey", var)) {
-		if (ssl_key == NULL) {
-			ssl_key = xmalloc(strlen(value)+1);
-			strcpy(ssl_key, value);
-		}
-		return 0;
-	}
-#endif
-#if LIBCURL_VERSION_NUM >= 0x070908
-	if (!strcmp("http.sslcapath", var)) {
-		if (ssl_capath == NULL) {
-			ssl_capath = xmalloc(strlen(value)+1);
-			strcpy(ssl_capath, value);
-		}
-		return 0;
-	}
-#endif
-	if (!strcmp("http.sslcainfo", var)) {
-		if (ssl_cainfo == NULL) {
-			ssl_cainfo = xmalloc(strlen(value)+1);
-			strcpy(ssl_cainfo, value);
-		}
-		return 0;
-	}
-
-#ifdef USE_CURL_MULTI	
-	if (!strcmp("http.maxrequests", var)) {
-		if (max_requests == -1)
-			max_requests = git_config_int(var, value);
-		return 0;
-	}
-#endif
-
-	if (!strcmp("http.lowspeedlimit", var)) {
-		if (curl_low_speed_limit == -1)
-			curl_low_speed_limit = (long)git_config_int(var, value);
-		return 0;
-	}
-	if (!strcmp("http.lowspeedtime", var)) {
-		if (curl_low_speed_time == -1)
-			curl_low_speed_time = (long)git_config_int(var, value);
-		return 0;
-	}
-
-	/* Fall back on the default ones */
-	return git_default_config(var, value);
-}
-
-static size_t fwrite_buffer(void *ptr, size_t eltsize, size_t nmemb,
-                            struct buffer *buffer)
-{
-        size_t size = eltsize * nmemb;
-        if (size > buffer->size - buffer->posn)
-                size = buffer->size - buffer->posn;
-        memcpy(buffer->buffer + buffer->posn, ptr, size);
-        buffer->posn += size;
-	data_received++;
-        return size;
-}
-
-static size_t fwrite_buffer_dynamic(const void *ptr, size_t eltsize,
-				    size_t nmemb, struct buffer *buffer)
-{
-	size_t size = eltsize * nmemb;
-	if (size > buffer->size - buffer->posn) {
-		buffer->size = buffer->size * 3 / 2;
-		if (buffer->size < buffer->posn + size)
-			buffer->size = buffer->posn + size;
-		buffer->buffer = xrealloc(buffer->buffer, buffer->size);
-	}
-	memcpy(buffer->buffer + buffer->posn, ptr, size);
-	buffer->posn += size;
-	data_received++;
-	return size;
-}
+static struct object_request *object_queue_head = NULL;
 
 static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
 			       void *data)
@@ -221,194 +65,35 @@
 	unsigned char expn[4096];
 	size_t size = eltsize * nmemb;
 	int posn = 0;
-	struct transfer_request *request = (struct transfer_request *)data;
+	struct object_request *obj_req = (struct object_request *)data;
 	do {
-		ssize_t retval = write(request->local,
+		ssize_t retval = write(obj_req->local,
 				       ptr + posn, size - posn);
 		if (retval < 0)
 			return posn;
 		posn += retval;
 	} while (posn < size);
 
-	request->stream.avail_in = size;
-	request->stream.next_in = ptr;
+	obj_req->stream.avail_in = size;
+	obj_req->stream.next_in = ptr;
 	do {
-		request->stream.next_out = expn;
-		request->stream.avail_out = sizeof(expn);
-		request->zret = inflate(&request->stream, Z_SYNC_FLUSH);
-		SHA1_Update(&request->c, expn,
-			    sizeof(expn) - request->stream.avail_out);
-	} while (request->stream.avail_in && request->zret == Z_OK);
+		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;
 }
 
-#ifdef USE_CURL_MULTI
-static void process_curl_messages(void);
-static void process_request_queue(void);
-#endif
 static void fetch_alternates(char *base);
 
-static CURL* get_curl_handle(void)
+static void process_object_response(void *callback_data);
+
+static void start_object_request(struct object_request *obj_req)
 {
-	CURL* result = curl_easy_init();
-
-	curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, curl_ssl_verify);
-#if LIBCURL_VERSION_NUM >= 0x070907
-	curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
-#endif
-
-	if (ssl_cert != NULL)
-		curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert);
-#if LIBCURL_VERSION_NUM >= 0x070902
-	if (ssl_key != NULL)
-		curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key);
-#endif
-#if LIBCURL_VERSION_NUM >= 0x070908
-	if (ssl_capath != NULL)
-		curl_easy_setopt(result, CURLOPT_CAPATH, ssl_capath);
-#endif
-	if (ssl_cainfo != NULL)
-		curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo);
-	curl_easy_setopt(result, CURLOPT_FAILONERROR, 1);
-
-	if (curl_low_speed_limit > 0 && curl_low_speed_time > 0) {
-		curl_easy_setopt(result, CURLOPT_LOW_SPEED_LIMIT,
-				 curl_low_speed_limit);
-		curl_easy_setopt(result, CURLOPT_LOW_SPEED_TIME,
-				 curl_low_speed_time);
-	}
-
-	curl_easy_setopt(result, CURLOPT_FOLLOWLOCATION, 1);
-
-	return result;
-}
-
-static struct active_request_slot *get_active_slot(void)
-{
-	struct active_request_slot *slot = active_queue_head;
-	struct active_request_slot *newslot;
-
-#ifdef USE_CURL_MULTI
-	int num_transfers;
-
-	/* Wait for a slot to open up if the queue is full */
-	while (active_requests >= max_requests) {
-		curl_multi_perform(curlm, &num_transfers);
-		if (num_transfers < active_requests) {
-			process_curl_messages();
-		}
-	}
-#endif
-
-	while (slot != NULL && slot->in_use) {
-		slot = slot->next;
-	}
-	if (slot == NULL) {
-		newslot = xmalloc(sizeof(*newslot));
-		newslot->curl = NULL;
-		newslot->in_use = 0;
-		newslot->next = NULL;
-
-		slot = active_queue_head;
-		if (slot == NULL) {
-			active_queue_head = newslot;
-		} else {
-			while (slot->next != NULL) {
-				slot = slot->next;
-			}
-			slot->next = newslot;
-		}
-		slot = newslot;
-	}
-
-	if (slot->curl == NULL) {
-#ifdef NO_CURL_EASY_DUPHANDLE
-		slot->curl = get_curl_handle();
-#else
-		slot->curl = curl_easy_duphandle(curl_default);
-#endif
-	}
-
-	active_requests++;
-	slot->in_use = 1;
-	slot->done = 0;
-	slot->local = NULL;
-	slot->callback_data = NULL;
-	slot->callback_func = NULL;
-	curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, pragma_header);
-	curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_range_header);
-	curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, curl_errorstr);
-
-	return slot;
-}
-
-static int start_active_slot(struct active_request_slot *slot)
-{
-#ifdef USE_CURL_MULTI
-	CURLMcode curlm_result = curl_multi_add_handle(curlm, slot->curl);
-
-	if (curlm_result != CURLM_OK &&
-	    curlm_result != CURLM_CALL_MULTI_PERFORM) {
-		active_requests--;
-		slot->in_use = 0;
-		return 0;
-	}
-#endif
-	return 1;
-}
-
-static void run_active_slot(struct active_request_slot *slot)
-{
-#ifdef USE_CURL_MULTI
-	int num_transfers;
-	long last_pos = 0;
-	long current_pos;
-	fd_set readfds;
-	fd_set writefds;
-	fd_set excfds;
-	int max_fd;
-	struct timeval select_timeout;
-	CURLMcode curlm_result;
-
-	while (!slot->done) {
-		data_received = 0;
-		do {
-			curlm_result = curl_multi_perform(curlm,
-							  &num_transfers);
-		} while (curlm_result == CURLM_CALL_MULTI_PERFORM);
-		if (num_transfers < active_requests) {
-			process_curl_messages();
-			process_request_queue();
-		}
-
-		if (!data_received && slot->local != NULL) {
-			current_pos = ftell(slot->local);
-			if (current_pos > last_pos)
-				data_received++;
-			last_pos = current_pos;
-		}
-
-		if (!slot->done && !data_received) {
-			max_fd = 0;
-			FD_ZERO(&readfds);
-			FD_ZERO(&writefds);
-			FD_ZERO(&excfds);
-			select_timeout.tv_sec = 0;
-			select_timeout.tv_usec = 50000;
-			select(max_fd, &readfds, &writefds,
-			       &excfds, &select_timeout);
-		}
-	}
-#else
-	slot->curl_result = curl_easy_perform(slot->curl);
-	active_requests--;
-#endif
-}
-
-static void start_request(struct transfer_request *request)
-{
-	char *hex = sha1_to_hex(request->sha1);
+	char *hex = sha1_to_hex(obj_req->sha1);
 	char prevfile[PATH_MAX];
 	char *url;
 	char *posn;
@@ -420,53 +105,53 @@
 	struct curl_slist *range_header = NULL;
 	struct active_request_slot *slot;
 
-	snprintf(prevfile, sizeof(prevfile), "%s.prev", request->filename);
+	snprintf(prevfile, sizeof(prevfile), "%s.prev", obj_req->filename);
 	unlink(prevfile);
-	rename(request->tmpfile, prevfile);
-	unlink(request->tmpfile);
+	rename(obj_req->tmpfile, prevfile);
+	unlink(obj_req->tmpfile);
 
-	if (request->local != -1)
-		error("fd leakage in start: %d", request->local);
-	request->local = open(request->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 (request->local < 0 && errno == ENOENT) {
-		char *dir = strrchr(request->tmpfile, '/');
+	if (obj_req->local < 0 && errno == ENOENT) {
+		char *dir = strrchr(obj_req->tmpfile, '/');
 		if (dir) {
 			*dir = 0;
-			mkdir(request->tmpfile, 0777);
+			mkdir(obj_req->tmpfile, 0777);
 			*dir = '/';
 		}
-		request->local = open(request->tmpfile,
+		obj_req->local = open(obj_req->tmpfile,
 				      O_WRONLY | O_CREAT | O_EXCL, 0666);
 	}
 
-	if (request->local < 0) {
-		request->state = ABORTED;
+	if (obj_req->local < 0) {
+		obj_req->state = ABORTED;
 		error("Couldn't create temporary file %s for %s: %s\n",
-		      request->tmpfile, request->filename, strerror(errno));
+		      obj_req->tmpfile, obj_req->filename, strerror(errno));
 		return;
 	}
 
-	memset(&request->stream, 0, sizeof(request->stream));
+	memset(&obj_req->stream, 0, sizeof(obj_req->stream));
 
-	inflateInit(&request->stream);
+	inflateInit(&obj_req->stream);
 
-	SHA1_Init(&request->c);
+	SHA1_Init(&obj_req->c);
 
-	url = xmalloc(strlen(request->repo->base) + 50);
-	request->url = xmalloc(strlen(request->repo->base) + 50);
-	strcpy(url, request->repo->base);
-	posn = url + strlen(request->repo->base);
+	url = xmalloc(strlen(obj_req->repo->base) + 50);
+	obj_req->url = xmalloc(strlen(obj_req->repo->base) + 50);
+	strcpy(url, obj_req->repo->base);
+	posn = url + strlen(obj_req->repo->base);
 	strcpy(posn, "objects/");
 	posn += 8;
 	memcpy(posn, hex, 2);
 	posn += 2;
 	*(posn++) = '/';
 	strcpy(posn, hex + 2);
-	strcpy(request->url, url);
+	strcpy(obj_req->url, url);
 
 	/* If a previous temp file is present, process what was already
 	   fetched. */
@@ -478,7 +163,7 @@
 				if (fwrite_sha1_file(prev_buf,
 						     1,
 						     prev_read,
-						     request) == prev_read) {
+						     obj_req) == prev_read) {
 					prev_posn += prev_read;
 				} else {
 					prev_read = -1;
@@ -492,20 +177,24 @@
 	/* 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(&request->stream, 0, sizeof(request->stream));
-		inflateInit(&request->stream);
-		SHA1_Init(&request->c);
+		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(request->local, SEEK_SET, 0);
-			ftruncate(request->local, 0);
+			lseek(obj_req->local, SEEK_SET, 0);
+			ftruncate(obj_req->local, 0);
 		}
 	}
 
 	slot = get_active_slot();
-	curl_easy_setopt(slot->curl, CURLOPT_FILE, request);
+	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, request->errorstr);
+	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);
 
@@ -523,151 +212,111 @@
 	}
 
 	/* Try to get the request started, abort the request on error */
+	obj_req->state = ACTIVE;
 	if (!start_active_slot(slot)) {
-		request->state = ABORTED;
-		close(request->local); request->local = -1;
-		free(request->url);
+		obj_req->state = ABORTED;
+		obj_req->slot = NULL;
+		close(obj_req->local); obj_req->local = -1;
+		free(obj_req->url);
 		return;
 	}
 	
-	request->slot = slot;
-	request->state = ACTIVE;
 }
 
-static void finish_request(struct transfer_request *request)
+static void finish_object_request(struct object_request *obj_req)
 {
 	struct stat st;
 
-	fchmod(request->local, 0444);
-	close(request->local); request->local = -1;
+	fchmod(obj_req->local, 0444);
+	close(obj_req->local); obj_req->local = -1;
 
-	if (request->http_code == 416) {
+	if (obj_req->http_code == 416) {
 		fprintf(stderr, "Warning: requested range invalid; we may already have all the data.\n");
-	} else if (request->curl_result != CURLE_OK) {
-		if (stat(request->tmpfile, &st) == 0)
+	} else if (obj_req->curl_result != CURLE_OK) {
+		if (stat(obj_req->tmpfile, &st) == 0)
 			if (st.st_size == 0)
-				unlink(request->tmpfile);
+				unlink(obj_req->tmpfile);
 		return;
 	}
 
-	inflateEnd(&request->stream);
-	SHA1_Final(request->real_sha1, &request->c);
-	if (request->zret != Z_STREAM_END) {
-		unlink(request->tmpfile);
+	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 (memcmp(request->sha1, request->real_sha1, 20)) {
-		unlink(request->tmpfile);
+	if (memcmp(obj_req->sha1, obj_req->real_sha1, 20)) {
+		unlink(obj_req->tmpfile);
 		return;
 	}
-	request->rename =
-		move_temp_to_file(request->tmpfile, request->filename);
+	obj_req->rename =
+		move_temp_to_file(obj_req->tmpfile, obj_req->filename);
 
-	if (request->rename == 0)
-		pull_say("got %s\n", sha1_to_hex(request->sha1));
+	if (obj_req->rename == 0)
+		pull_say("got %s\n", sha1_to_hex(obj_req->sha1));
 }
 
-static void release_request(struct transfer_request *request)
+static void process_object_response(void *callback_data)
 {
-	struct transfer_request *entry = request_queue_head;
+	struct object_request *obj_req =
+		(struct object_request *)callback_data;
 
-	if (request->local != -1)
-		error("fd leakage in release: %d", request->local);
-	if (request == request_queue_head) {
-		request_queue_head = request->next;
+	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 (obj_req->http_code == 404) {
+		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 != request)
+		while (entry->next != NULL && entry->next != obj_req)
 			entry = entry->next;
-		if (entry->next == request)
+		if (entry->next == obj_req)
 			entry->next = entry->next->next;
 	}
 
-	free(request->url);
-	free(request);
+	free(obj_req->url);
+	free(obj_req);
 }
 
 #ifdef USE_CURL_MULTI
-static void process_curl_messages(void)
+void fill_active_slots(void)
 {
-	int num_messages;
-	struct active_request_slot *slot;
-	struct transfer_request *request = NULL;
-	CURLMsg *curl_message = curl_multi_info_read(curlm, &num_messages);
-
-	while (curl_message != NULL) {
-		if (curl_message->msg == CURLMSG_DONE) {
-			int curl_result = curl_message->data.result;
-			slot = active_queue_head;
-			while (slot != NULL &&
-			       slot->curl != curl_message->easy_handle)
-				slot = slot->next;
-			if (slot != NULL) {
-				curl_multi_remove_handle(curlm, slot->curl);
-				active_requests--;
-				slot->done = 1;
-				slot->in_use = 0;
-				slot->curl_result = curl_result;
-				curl_easy_getinfo(slot->curl,
-						  CURLINFO_HTTP_CODE,
-						  &slot->http_code);
-				request = request_queue_head;
-				while (request != NULL &&
-				       request->slot != slot)
-					request = request->next;
-			} else {
-				fprintf(stderr, "Received DONE message for unknown request!\n");
-			}
-
-			/* Process slot callback if appropriate */
-			if (slot->callback_func != NULL) {
-				slot->callback_func(slot->callback_data);
-			}
-
-			if (request != NULL) {
-				request->curl_result = curl_result;
-				request->http_code = slot->http_code;
-				request->slot = NULL;
-				request->state = COMPLETE;
-
-				/* Use alternates if necessary */
-				if (request->http_code == 404) {
-					fetch_alternates(alt->base);
-					if (request->repo->next != NULL) {
-						request->repo =
-							request->repo->next;
-						close(request->local);
-							request->local = -1;
-						start_request(request);
-					} else {
-						finish_request(request);
-					}
-				} else {
-					finish_request(request);
-				}
-			}
-		} else {
-			fprintf(stderr, "Unknown CURL message received: %d\n",
-				(int)curl_message->msg);
-		}
-		curl_message = curl_multi_info_read(curlm, &num_messages);
-	}
-}
-
-static void process_request_queue(void)
-{
-	struct transfer_request *request = request_queue_head;
+	struct object_request *obj_req = object_queue_head;
 	struct active_request_slot *slot = active_queue_head;
 	int num_transfers;
 
-	while (active_requests < max_requests && request != NULL) {
-		if (request->state == WAITING) {
-			if (has_sha1_file(request->sha1))
-				release_request(request);
+	while (active_requests < max_requests && obj_req != NULL) {
+		if (obj_req->state == WAITING) {
+			if (has_sha1_file(obj_req->sha1))
+				release_object_request(obj_req);
 			else
-				start_request(request);
+				start_object_request(obj_req);
 			curl_multi_perform(curlm, &num_transfers);
 		}
-		request = request->next;
+		obj_req = obj_req->next;
 	}
 
 	while (slot != NULL) {
@@ -682,8 +331,8 @@
 
 void prefetch(unsigned char *sha1)
 {
-	struct transfer_request *newreq;
-	struct transfer_request *tail;
+	struct object_request *newreq;
+	struct object_request *tail;
 	char *filename = sha1_file_name(sha1);
 
 	newreq = xmalloc(sizeof(*newreq));
@@ -697,18 +346,19 @@
 		 "%s.temp", filename);
 	newreq->next = NULL;
 
-	if (request_queue_head == NULL) {
-		request_queue_head = newreq;
+	if (object_queue_head == NULL) {
+		object_queue_head = newreq;
 	} else {
-		tail = request_queue_head;
+		tail = object_queue_head;
 		while (tail->next != NULL) {
 			tail = tail->next;
 		}
 		tail->next = newreq;
 	}
+
 #ifdef USE_CURL_MULTI
-	process_request_queue();
-	process_curl_messages();
+	fill_active_slots();
+	step_active_slots();
 #endif
 }
 
@@ -793,9 +443,10 @@
 	return 0;
 }
 
-static void process_alternates(void *callback_data)
+static void process_alternates_response(void *callback_data)
 {
-	struct alt_request *alt_req = (struct alt_request *)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;
 	char *base = alt_req->base;
@@ -815,12 +466,11 @@
 					 alt_req->url);
 			active_requests++;
 			slot->in_use = 1;
-			slot->done = 0;
 			if (start_active_slot(slot)) {
 				return;
 			} else {
 				got_alternates = -1;
-				slot->done = 1;
+				slot->in_use = 0;
 				return;
 			}
 		}
@@ -831,7 +481,7 @@
 		}
 	}
 
-	fwrite_buffer_dynamic(&null_byte, 1, 1, alt_req->buffer);
+	fwrite_buffer(&null_byte, 1, 1, alt_req->buffer);
 	alt_req->buffer->posn--;
 	data = alt_req->buffer->buffer;
 
@@ -901,17 +551,16 @@
 	char *url;
 	char *data;
 	struct active_request_slot *slot;
-	static struct alt_request alt_req;
-	int num_transfers;
+	static 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) {
-		curl_multi_perform(curlm, &num_transfers);
-		process_curl_messages();
-		process_request_queue();
+		step_active_slots();
 	}
+#endif
 
 	/* Nothing to do if they've already been fetched */
 	if (got_alternates == 1)
@@ -934,12 +583,11 @@
 	/* 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;
+	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_dynamic);
+	curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
 	curl_easy_setopt(slot->curl, CURLOPT_URL, url);
 
 	alt_req.base = base;
@@ -983,17 +631,24 @@
 
 	slot = get_active_slot();
 	curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
-	curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
-			 fwrite_buffer_dynamic);
+	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 (slot->curl_result != CURLE_OK) {
-			free(buffer.buffer);
-			return error("%s", curl_errorstr);
+			if (slot->http_code == 404) {
+				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");
 	}
@@ -1115,94 +770,56 @@
 static int fetch_object(struct alt_base *repo, unsigned char *sha1)
 {
 	char *hex = sha1_to_hex(sha1);
-	int ret;
-	struct transfer_request *request = request_queue_head;
+	int ret = 0;
+	struct object_request *obj_req = object_queue_head;
 
-	while (request != NULL && memcmp(request->sha1, sha1, 20))
-		request = request->next;
-	if (request == NULL)
+	while (obj_req != NULL && memcmp(obj_req->sha1, sha1, 20))
+		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(request->sha1)) {
-		release_request(request);
+	if (has_sha1_file(obj_req->sha1)) {
+		release_object_request(obj_req);
 		return 0;
 	}
 
 #ifdef USE_CURL_MULTI
-	while (request->state == WAITING) {
-		int num_transfers;
-		curl_multi_perform(curlm, &num_transfers);
-		if (num_transfers < active_requests) {
-			process_curl_messages();
-			process_request_queue();
-		}
+	while (obj_req->state == WAITING) {
+		step_active_slots();
 	}
 #else
-	start_request(request);
+	start_object_request(obj_req);
 #endif
 
-	while (request->state == ACTIVE) {
-		run_active_slot(request->slot);
-#ifndef USE_CURL_MULTI
-		request->curl_result = request->slot->curl_result;
-		request->http_code = request->slot->http_code;
-		request->slot = NULL;
-
-		/* Use alternates if necessary */
-		if (request->http_code == 404) {
-			fetch_alternates(alt->base);
-			if (request->repo->next != NULL) {
-				request->repo = request->repo->next;
-				close(request->local); request->local = -1;
-				start_request(request);
-			}
-		} else {
-			finish_request(request);
-			request->state = COMPLETE;
-		}
-#endif
+	while (obj_req->state == ACTIVE) {
+		run_active_slot(obj_req->slot);
 	}
-	if (request->local != -1) {
-		close(request->local); request->local = -1;
+	if (obj_req->local != -1) {
+		close(obj_req->local); obj_req->local = -1;
 	}
 
-	if (request->state == ABORTED) {
-		release_request(request);
-		return error("Request for %s aborted", hex);
-	}
-
-	if (request->curl_result != CURLE_OK && request->http_code != 416) {
-		if (request->http_code == 404)
+	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 (obj_req->http_code == 404)
 			ret = -1; /* Be silent, it is probably in a pack. */
 		else
 			ret = error("%s (curl_result = %d, http_code = %ld, sha1 = %s)",
-				    request->errorstr, request->curl_result,
-				    request->http_code, hex);
-		release_request(request);
-		return ret;
-	}
-
-	if (request->zret != Z_STREAM_END) {
-		ret = error("File %s (%s) corrupt\n", hex, request->url);
-		release_request(request);
-		return ret;
-	}
-
-	if (memcmp(request->sha1, request->real_sha1, 20)) {
-		release_request(request);
-		return error("File %s has bad hash\n", hex);
-	}
-
-	if (request->rename < 0) {
+				    obj_req->errorstr, obj_req->curl_result,
+				    obj_req->http_code, hex);
+	} else if (obj_req->zret != Z_STREAM_END) {
+		ret = error("File %s (%s) corrupt\n", hex, obj_req->url);
+	} else if (memcmp(obj_req->sha1, obj_req->real_sha1, 20)) {
+		ret = error("File %s has bad hash\n", hex);
+	} else if (obj_req->rename < 0) {
 		ret = error("unable to write sha1 filename %s: %s",
-			    request->filename,
-			    strerror(request->rename));
-		release_request(request);
-		return ret;
+			    obj_req->filename,
+			    strerror(obj_req->rename));
 	}
 
-	release_request(request);
-	return 0;
+	release_object_request(obj_req);
+	return ret;
 }
 
 int fetch(unsigned char *sha1)
@@ -1303,10 +920,6 @@
 	char *commit_id;
 	char *url;
 	int arg = 1;
-	struct active_request_slot *slot;
-	char *low_speed_limit;
-	char *low_speed_time;
-	char *wait_url;
 	int rc = 0;
 
 	while (arg < argc && argv[arg][0] == '-') {
@@ -1335,58 +948,9 @@
 	commit_id = argv[arg];
 	url = argv[arg + 1];
 
-	curl_global_init(CURL_GLOBAL_ALL);
+	http_init();
 
-#ifdef USE_CURL_MULTI
-	{
-		char *http_max_requests = getenv("GIT_HTTP_MAX_REQUESTS");
-		if (http_max_requests != NULL)
-			max_requests = atoi(http_max_requests);
-	}
-
-	curlm = curl_multi_init();
-	if (curlm == NULL) {
-		fprintf(stderr, "Error creating curl multi handle.\n");
-		return 1;
-	}
-#endif
-
-	if (getenv("GIT_SSL_NO_VERIFY"))
-		curl_ssl_verify = 0;
-
-	ssl_cert = getenv("GIT_SSL_CERT");
-#if LIBCURL_VERSION_NUM >= 0x070902
-	ssl_key = getenv("GIT_SSL_KEY");
-#endif
-#if LIBCURL_VERSION_NUM >= 0x070908
-	ssl_capath = getenv("GIT_SSL_CAPATH");
-#endif
-	ssl_cainfo = getenv("GIT_SSL_CAINFO");
-
-	low_speed_limit = getenv("GIT_HTTP_LOW_SPEED_LIMIT");
-	if (low_speed_limit != NULL)
-		curl_low_speed_limit = strtol(low_speed_limit, NULL, 10);
-	low_speed_time = getenv("GIT_HTTP_LOW_SPEED_TIME");
-	if (low_speed_time != NULL)
-		curl_low_speed_time = strtol(low_speed_time, NULL, 10);
-
-	git_config(http_options);
-
-	if (curl_ssl_verify == -1)
-		curl_ssl_verify = 1;
-
-#ifdef USE_CURL_MULTI
-	if (max_requests < 1)
-		max_requests = DEFAULT_MAX_REQUESTS;
-#endif
-
-	pragma_header = curl_slist_append(pragma_header, "Pragma: no-cache");
 	no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:");
-	no_range_header = curl_slist_append(no_range_header, "Range:");
-
-#ifndef NO_CURL_EASY_DUPHANDLE
-	curl_default = get_curl_handle();
-#endif
 
 	alt = xmalloc(sizeof(*alt));
 	alt->base = url;
@@ -1397,30 +961,9 @@
 	if (pull(commit_id))
 		rc = 1;
 
-	curl_slist_free_all(pragma_header);
 	curl_slist_free_all(no_pragma_header);
-	curl_slist_free_all(no_range_header);
-#ifndef NO_CURL_EASY_DUPHANDLE
-	curl_easy_cleanup(curl_default);
-#endif
-	slot = active_queue_head;
-	while (slot != NULL) {
-		if (slot->in_use) {
-			if (get_verbosely) {
-				curl_easy_getinfo(slot->curl,
-						  CURLINFO_EFFECTIVE_URL,
-						  &wait_url);
-				fprintf(stderr, "Waiting for %s\n", wait_url);
-			}
-			run_active_slot(slot);
-		}
-		if (slot->curl != NULL)
-			curl_easy_cleanup(slot->curl);
-		slot = slot->next;
-	}
-#ifdef USE_CURL_MULTI
-	curl_multi_cleanup(curlm);
-#endif
-	curl_global_cleanup();
+
+	http_cleanup();
+
 	return rc;
 }
diff --git a/http-push.c b/http-push.c
index 8866189..76c7886 100644
--- a/http-push.c
+++ b/http-push.c
@@ -4,30 +4,13 @@
 #include "fetch.h"
 #include "tag.h"
 #include "blob.h"
+#include "http.h"
 
-#include <curl/curl.h>
-#include <curl/easy.h>
 #include <expat.h>
 
 static const char http_push_usage[] =
 "git-http-push [--complete] [--force] [--verbose] <url> <ref> [<ref>...]\n";
 
-#if LIBCURL_VERSION_NUM >= 0x070908
-#define USE_CURL_MULTI
-#define DEFAULT_MAX_REQUESTS 5
-#endif
-
-#if LIBCURL_VERSION_NUM < 0x070704
-#define curl_global_cleanup() do { /* nothing */ } while(0)
-#endif
-#if LIBCURL_VERSION_NUM < 0x070800
-#define curl_global_init(a) do { /* nothing */ } while(0)
-#endif
-
-#if LIBCURL_VERSION_NUM < 0x070c04
-#define NO_CURL_EASY_DUPHANDLE
-#endif
-
 #ifndef XML_STATUS_OK
 enum XML_Status {
   XML_STATUS_OK = 1,
@@ -39,47 +22,45 @@
 
 #define RANGE_HEADER_SIZE 30
 
-/* DAV method names and request body templates */
+/* DAV methods */
 #define DAV_LOCK "LOCK"
 #define DAV_MKCOL "MKCOL"
 #define DAV_MOVE "MOVE"
 #define DAV_PROPFIND "PROPFIND"
 #define DAV_PUT "PUT"
 #define DAV_UNLOCK "UNLOCK"
+
+/* DAV lock flags */
+#define DAV_PROP_LOCKWR (1u << 0)
+#define DAV_PROP_LOCKEX (1u << 1)
+#define DAV_LOCK_OK (1u << 2)
+
+/* DAV XML properties */
+#define DAV_CTX_LOCKENTRY ".multistatus.response.propstat.prop.supportedlock.lockentry"
+#define DAV_CTX_LOCKTYPE_WRITE ".multistatus.response.propstat.prop.supportedlock.lockentry.locktype.write"
+#define DAV_CTX_LOCKTYPE_EXCLUSIVE ".multistatus.response.propstat.prop.supportedlock.lockentry.lockscope.exclusive"
+#define DAV_ACTIVELOCK_OWNER ".prop.lockdiscovery.activelock.owner.href"
+#define DAV_ACTIVELOCK_TIMEOUT ".prop.lockdiscovery.activelock.timeout"
+#define DAV_ACTIVELOCK_TOKEN ".prop.lockdiscovery.activelock.locktoken.href"
+
+/* DAV request body templates */
 #define PROPFIND_REQUEST "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:prop xmlns:R=\"%s\">\n<D:supportedlock/>\n</D:prop>\n</D:propfind>"
 #define LOCK_REQUEST "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:lockinfo xmlns:D=\"DAV:\">\n<D:lockscope><D:exclusive/></D:lockscope>\n<D:locktype><D:write/></D:locktype>\n<D:owner>\n<D:href>mailto:%s</D:href>\n</D:owner>\n</D:lockinfo>"
 
 #define LOCK_TIME 600
 #define LOCK_REFRESH 30
 
-static int active_requests = 0;
-static int data_received;
 static int pushing = 0;
 static int aborted = 0;
 static char remote_dir_exists[256];
 
-#ifdef USE_CURL_MULTI
-static int max_requests = -1;
-static CURLM *curlm;
-#endif
-#ifndef NO_CURL_EASY_DUPHANDLE
-static CURL *curl_default;
-#endif
 static struct curl_slist *no_pragma_header;
 static struct curl_slist *default_headers;
-static char curl_errorstr[CURL_ERROR_SIZE];
 
 static int push_verbosely = 0;
 static int push_all = 0;
 static int force_all = 0;
 
-struct buffer
-{
-        size_t posn;
-        size_t size;
-        void *buffer;
-};
-
 struct repo
 {
 	char *url;
@@ -122,40 +103,19 @@
 	struct transfer_request *next;
 };
 
-struct active_request_slot
-{
-	CURL *curl;
-	FILE *local;
-	int in_use;
-	int done;
-	CURLcode curl_result;
-	long http_code;
-	struct active_request_slot *next;
-};
-
 static struct transfer_request *request_queue_head = NULL;
-static struct active_request_slot *active_queue_head = NULL;
 
-static int curl_ssl_verify = -1;
-static char *ssl_cert = NULL;
-#if LIBCURL_VERSION_NUM >= 0x070902
-static char *ssl_key = NULL;
-#endif
-#if LIBCURL_VERSION_NUM >= 0x070908
-static char *ssl_capath = NULL;
-#endif
-static char *ssl_cainfo = NULL;
-static long curl_low_speed_limit = -1;
-static long curl_low_speed_time = -1;
+struct xml_ctx
+{
+	char *name;
+	int len;
+	char *cdata;
+	void (*userFunc)(struct xml_ctx *ctx, int tag_closed);
+	void *userData;
+};
 
 struct active_lock
 {
-	int ctx_activelock;
-	int ctx_owner;
-	int ctx_owner_href;
-	int ctx_timeout;
-	int ctx_locktoken;
-	int ctx_locktoken_href;
 	char *url;
 	char *owner;
 	char *token;
@@ -164,270 +124,14 @@
 	int refreshing;
 };
 
-struct lockprop
+static void finish_request(struct transfer_request *request);
+
+static void process_response(void *callback_data)
 {
-	int supported_lock;
-	int lock_entry;
-	int lock_scope;
-	int lock_type;
-	int lock_exclusive;
-	int lock_exclusive_write;
-};
+	struct transfer_request *request =
+		(struct transfer_request *)callback_data;
 
-static int http_options(const char *var, const char *value)
-{
-	if (!strcmp("http.sslverify", var)) {
-		if (curl_ssl_verify == -1) {
-			curl_ssl_verify = git_config_bool(var, value);
-		}
-		return 0;
-	}
-
-	if (!strcmp("http.sslcert", var)) {
-		if (ssl_cert == NULL) {
-			ssl_cert = xmalloc(strlen(value)+1);
-			strcpy(ssl_cert, value);
-		}
-		return 0;
-	}
-#if LIBCURL_VERSION_NUM >= 0x070902
-	if (!strcmp("http.sslkey", var)) {
-		if (ssl_key == NULL) {
-			ssl_key = xmalloc(strlen(value)+1);
-			strcpy(ssl_key, value);
-		}
-		return 0;
-	}
-#endif
-#if LIBCURL_VERSION_NUM >= 0x070908
-	if (!strcmp("http.sslcapath", var)) {
-		if (ssl_capath == NULL) {
-			ssl_capath = xmalloc(strlen(value)+1);
-			strcpy(ssl_capath, value);
-		}
-		return 0;
-	}
-#endif
-	if (!strcmp("http.sslcainfo", var)) {
-		if (ssl_cainfo == NULL) {
-			ssl_cainfo = xmalloc(strlen(value)+1);
-			strcpy(ssl_cainfo, value);
-		}
-		return 0;
-	}
-
-#ifdef USE_CURL_MULTI	
-	if (!strcmp("http.maxrequests", var)) {
-		if (max_requests == -1)
-			max_requests = git_config_int(var, value);
-		return 0;
-	}
-#endif
-
-	if (!strcmp("http.lowspeedlimit", var)) {
-		if (curl_low_speed_limit == -1)
-			curl_low_speed_limit = (long)git_config_int(var, value);
-		return 0;
-	}
-	if (!strcmp("http.lowspeedtime", var)) {
-		if (curl_low_speed_time == -1)
-			curl_low_speed_time = (long)git_config_int(var, value);
-		return 0;
-	}
-
-	/* Fall back on the default ones */
-	return git_default_config(var, value);
-}
-
-static size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb,
-			   struct buffer *buffer)
-{
-	size_t size = eltsize * nmemb;
-	if (size > buffer->size - buffer->posn)
-		size = buffer->size - buffer->posn;
-	memcpy(ptr, buffer->buffer + buffer->posn, size);
-	buffer->posn += size;
-	return size;
-}
-
-static size_t fwrite_buffer_dynamic(const void *ptr, size_t eltsize,
-				    size_t nmemb, struct buffer *buffer)
-{
-	size_t size = eltsize * nmemb;
-	if (size > buffer->size - buffer->posn) {
-		buffer->size = buffer->size * 3 / 2;
-		if (buffer->size < buffer->posn + size)
-			buffer->size = buffer->posn + size;
-		buffer->buffer = xrealloc(buffer->buffer, buffer->size);
-	}
-	memcpy(buffer->buffer + buffer->posn, ptr, size);
-	buffer->posn += size;
-	data_received++;
-	return size;
-}
-
-static size_t fwrite_null(const void *ptr, size_t eltsize,
-			  size_t nmemb, struct buffer *buffer)
-{
-	data_received++;
-	return eltsize * nmemb;
-}
-
-#ifdef USE_CURL_MULTI
-static void process_curl_messages(void);
-static void process_request_queue(void);
-#endif
-
-static CURL* get_curl_handle(void)
-{
-	CURL* result = curl_easy_init();
-
-	curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, curl_ssl_verify);
-#if LIBCURL_VERSION_NUM >= 0x070907
-	curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
-#endif
-
-	if (ssl_cert != NULL)
-		curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert);
-#if LIBCURL_VERSION_NUM >= 0x070902
-	if (ssl_key != NULL)
-		curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key);
-#endif
-#if LIBCURL_VERSION_NUM >= 0x070908
-	if (ssl_capath != NULL)
-		curl_easy_setopt(result, CURLOPT_CAPATH, ssl_capath);
-#endif
-	if (ssl_cainfo != NULL)
-		curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo);
-	curl_easy_setopt(result, CURLOPT_FAILONERROR, 1);
-
-	if (curl_low_speed_limit > 0 && curl_low_speed_time > 0) {
-		curl_easy_setopt(result, CURLOPT_LOW_SPEED_LIMIT,
-				 curl_low_speed_limit);
-		curl_easy_setopt(result, CURLOPT_LOW_SPEED_TIME,
-				 curl_low_speed_time);
-	}
-
-	return result;
-}
-
-static struct active_request_slot *get_active_slot(void)
-{
-	struct active_request_slot *slot = active_queue_head;
-	struct active_request_slot *newslot;
-
-#ifdef USE_CURL_MULTI
-	int num_transfers;
-
-	/* Wait for a slot to open up if the queue is full */
-	while (active_requests >= max_requests) {
-		curl_multi_perform(curlm, &num_transfers);
-		if (num_transfers < active_requests) {
-			process_curl_messages();
-		}
-	}
-#endif
-
-	while (slot != NULL && slot->in_use) {
-		slot = slot->next;
-	}
-	if (slot == NULL) {
-		newslot = xmalloc(sizeof(*newslot));
-		newslot->curl = NULL;
-		newslot->in_use = 0;
-		newslot->next = NULL;
-
-		slot = active_queue_head;
-		if (slot == NULL) {
-			active_queue_head = newslot;
-		} else {
-			while (slot->next != NULL) {
-				slot = slot->next;
-			}
-			slot->next = newslot;
-		}
-		slot = newslot;
-	}
-
-	if (slot->curl == NULL) {
-#ifdef NO_CURL_EASY_DUPHANDLE
-		slot->curl = get_curl_handle();
-#else
-		slot->curl = curl_easy_duphandle(curl_default);
-#endif
-	}
-
-	active_requests++;
-	slot->in_use = 1;
-	slot->done = 0;
-	slot->local = NULL;
-	curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, default_headers);
-	curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, curl_errorstr);
-
-	return slot;
-}
-
-static int start_active_slot(struct active_request_slot *slot)
-{
-#ifdef USE_CURL_MULTI
-	CURLMcode curlm_result = curl_multi_add_handle(curlm, slot->curl);
-
-	if (curlm_result != CURLM_OK &&
-	    curlm_result != CURLM_CALL_MULTI_PERFORM) {
-		active_requests--;
-		slot->in_use = 0;
-		return 0;
-	}
-#endif
-	return 1;
-}
-
-static void run_active_slot(struct active_request_slot *slot)
-{
-#ifdef USE_CURL_MULTI
-	int num_transfers;
-	long last_pos = 0;
-	long current_pos;
-	fd_set readfds;
-	fd_set writefds;
-	fd_set excfds;
-	int max_fd;
-	struct timeval select_timeout;
-	CURLMcode curlm_result;
-
-	while (!slot->done) {
-		data_received = 0;
-		do {
-			curlm_result = curl_multi_perform(curlm,
-							  &num_transfers);
-		} while (curlm_result == CURLM_CALL_MULTI_PERFORM);
-		if (num_transfers < active_requests) {
-			process_curl_messages();
-			process_request_queue();
-		}
-
-		if (!data_received && slot->local != NULL) {
-			current_pos = ftell(slot->local);
-			if (current_pos > last_pos)
-				data_received++;
-			last_pos = current_pos;
-		}
-
-		if (!slot->done && !data_received) {
-			max_fd = 0;
-			FD_ZERO(&readfds);
-			FD_ZERO(&writefds);
-			FD_ZERO(&excfds);
-			select_timeout.tv_sec = 0;
-			select_timeout.tv_usec = 50000;
-			select(max_fd, &readfds, &writefds,
-			       &excfds, &select_timeout);
-		}
-	}
-#else
-	slot->curl_result = curl_easy_perform(slot->curl);
-	active_requests--;
-#endif
+	finish_request(request);
 }
 
 static void start_check(struct transfer_request *request)
@@ -447,6 +151,8 @@
 	strcpy(posn, hex + 2);
 
 	slot = get_active_slot();
+	slot->callback_func = process_response;
+	slot->callback_data = request;
 	curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, request->errorstr);
 	curl_easy_setopt(slot->curl, CURLOPT_URL, request->url);
 	curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 1);
@@ -457,6 +163,7 @@
 	} else {
 		request->state = ABORTED;
 		free(request->url);
+		request->url = NULL;
 	}
 }
 
@@ -476,6 +183,8 @@
 	strcpy(posn, "/");
 
 	slot = get_active_slot();
+	slot->callback_func = process_response;
+	slot->callback_data = request;
 	curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); /* undo PUT setup */
 	curl_easy_setopt(slot->curl, CURLOPT_URL, request->url);
 	curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, request->errorstr);
@@ -488,6 +197,7 @@
 	} else {
 		request->state = ABORTED;
 		free(request->url);
+		request->url = NULL;
 	}
 }
 
@@ -534,8 +244,6 @@
 	request->buffer.size = stream.total_out;
 	request->buffer.posn = 0;
 
-	if (request->url != NULL)
-		free(request->url);
 	request->url = xmalloc(strlen(remote->url) + 
 			       strlen(request->lock->token) + 51);
 	strcpy(request->url, remote->url);
@@ -553,6 +261,8 @@
 	strcpy(posn, request->lock->token);
 
 	slot = get_active_slot();
+	slot->callback_func = process_response;
+	slot->callback_data = request;
 	curl_easy_setopt(slot->curl, CURLOPT_INFILE, &request->buffer);
 	curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, request->buffer.size);
 	curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
@@ -569,6 +279,7 @@
 	} else {
 		request->state = ABORTED;
 		free(request->url);
+		request->url = NULL;
 	}
 }
 
@@ -578,6 +289,8 @@
 	struct curl_slist *dav_headers = NULL;
 
 	slot = get_active_slot();
+	slot->callback_func = process_response;
+	slot->callback_data = request;
 	curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); /* undo PUT setup */
 	curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_MOVE);
 	dav_headers = curl_slist_append(dav_headers, request->dest);
@@ -592,6 +305,7 @@
 	} else {
 		request->state = ABORTED;
 		free(request->url);
+		request->url = NULL;
 	}
 }
 
@@ -656,6 +370,13 @@
 
 	if (request->headers != NULL)
 		curl_slist_free_all(request->headers);
+
+	/* URL is reused for MOVE after PUT */
+	if (request->state != RUN_PUT) {
+		free(request->url);
+		request->url = NULL;
+	}		
+
 	if (request->state == RUN_HEAD) {
 		if (request->http_code == 404) {
 			request->state = NEED_PUSH;
@@ -721,52 +442,12 @@
 			entry->next = entry->next->next;
 	}
 
-	free(request->url);
+	if (request->url != NULL)
+		free(request->url);
 	free(request);
 }
 
-#ifdef USE_CURL_MULTI
-static void process_curl_messages(void)
-{
-	int num_messages;
-	struct active_request_slot *slot;
-	struct transfer_request *request = NULL;
-	CURLMsg *curl_message = curl_multi_info_read(curlm, &num_messages);
-
-	while (curl_message != NULL) {
-		if (curl_message->msg == CURLMSG_DONE) {
-			slot = active_queue_head;
-			while (slot != NULL &&
-			       slot->curl != curl_message->easy_handle)
-				slot = slot->next;
-			if (slot != NULL) {
-				int curl_result = curl_message->data.result;
-				curl_multi_remove_handle(curlm, slot->curl);
-				active_requests--;
-				slot->done = 1;
-				slot->in_use = 0;
-				slot->curl_result = curl_result;
-				curl_easy_getinfo(slot->curl,
-						  CURLINFO_HTTP_CODE,
-						  &slot->http_code);
-				request = request_queue_head;
-				while (request != NULL &&
-				       request->slot != slot)
-					request = request->next;
-				if (request != NULL)
-					finish_request(request);
-			} else {
-				fprintf(stderr, "Received DONE message for unknown request!\n");
-			}
-		} else {
-			fprintf(stderr, "Unknown CURL message received: %d\n",
-				(int)curl_message->msg);
-		}
-		curl_message = curl_multi_info_read(curlm, &num_messages);
-	}
-}
-
-static void process_request_queue(void)
+void fill_active_slots(void)
 {
 	struct transfer_request *request = request_queue_head;
 	struct active_request_slot *slot = active_queue_head;
@@ -797,20 +478,6 @@
 		slot = slot->next;
 	}				
 }
-#endif
-
-static void process_waiting_requests(void)
-{
-	struct active_request_slot *slot = active_queue_head;
-
-	while (slot != NULL)
-		if (slot->in_use) {
-			run_active_slot(slot);
-			slot = active_queue_head;
-		} else {
-			slot = slot->next;
-		}
-}
 
 static void add_request(unsigned char *sha1, struct active_lock *lock)
 {
@@ -834,10 +501,9 @@
 	request->state = NEED_CHECK;
 	request->next = request_queue_head;
 	request_queue_head = request;
-#ifdef USE_CURL_MULTI
-	process_request_queue();
-	process_curl_messages();
-#endif
+
+	fill_active_slots();
+	step_active_slots();
 }
 
 static int fetch_index(unsigned char *sha1)
@@ -917,6 +583,7 @@
 		}
 	} else {
 		free(url);
+		fclose(indexfile);
 		return error("Unable to start request");
 	}
 
@@ -963,8 +630,7 @@
 
 	slot = get_active_slot();
 	curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
-	curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
-			 fwrite_buffer_dynamic);
+	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)) {
@@ -1068,8 +734,7 @@
 	url = quote_ref_url(base, ref);
 	slot = get_active_slot();
 	curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
-	curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
-			 fwrite_buffer_dynamic);
+	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)) {
@@ -1086,107 +751,101 @@
         return 0;
 }
 
-static void
-start_activelock_element(void *userData, const char *name, const char **atts)
+static void handle_lockprop_ctx(struct xml_ctx *ctx, int tag_closed)
 {
-	struct active_lock *lock = (struct active_lock *)userData;
+	int *lock_flags = (int *)ctx->userData;
 
-	if (lock->ctx_activelock && !strcmp(name, "D:timeout"))
-		lock->ctx_timeout = 1;
-	else if (lock->ctx_owner && strstr(name, "href"))
-		lock->ctx_owner_href = 1;
-	else if (lock->ctx_activelock && strstr(name, "owner"))
-		lock->ctx_owner = 1;
-	else if (lock->ctx_locktoken && !strcmp(name, "D:href"))
-		lock->ctx_locktoken_href = 1;
-	else if (lock->ctx_activelock && !strcmp(name, "D:locktoken"))
-		lock->ctx_locktoken = 1;
-	else if (!strcmp(name, "D:activelock"))
-		lock->ctx_activelock = 1;
+	if (tag_closed) {
+		if (!strcmp(ctx->name, DAV_CTX_LOCKENTRY)) {
+			if ((*lock_flags & DAV_PROP_LOCKEX) &&
+			    (*lock_flags & DAV_PROP_LOCKWR)) {
+				*lock_flags |= DAV_LOCK_OK;
+			}
+			*lock_flags &= DAV_LOCK_OK;
+		} else if (!strcmp(ctx->name, DAV_CTX_LOCKTYPE_WRITE)) {
+			*lock_flags |= DAV_PROP_LOCKWR;
+		} else if (!strcmp(ctx->name, DAV_CTX_LOCKTYPE_EXCLUSIVE)) {
+			*lock_flags |= DAV_PROP_LOCKEX;
+		}
+	}
 }
 
-static void
-end_activelock_element(void *userData, const char *name)
+static void handle_new_lock_ctx(struct xml_ctx *ctx, int tag_closed)
 {
-	struct active_lock *lock = (struct active_lock *)userData;
+	struct active_lock *lock = (struct active_lock *)ctx->userData;
 
-	if (lock->ctx_timeout && !strcmp(name, "D:timeout")) {
-		lock->ctx_timeout = 0;
-	} else if (lock->ctx_owner_href && strstr(name, "href")) {
-		lock->ctx_owner_href = 0;
-	} else if (lock->ctx_owner && strstr(name, "owner")) {
-		lock->ctx_owner = 0;
-	} else if (lock->ctx_locktoken_href && !strcmp(name, "D:href")) {
-		lock->ctx_locktoken_href = 0;
-	} else if (lock->ctx_locktoken && !strcmp(name, "D:locktoken")) {
-		lock->ctx_locktoken = 0;
-	} else if (lock->ctx_activelock && !strcmp(name, "D:activelock")) {
-		lock->ctx_activelock = 0;
+	if (tag_closed && ctx->cdata) {
+		if (!strcmp(ctx->name, DAV_ACTIVELOCK_OWNER)) {
+			lock->owner = xmalloc(strlen(ctx->cdata) + 1);
+			strcpy(lock->owner, ctx->cdata);
+		} else if (!strcmp(ctx->name, DAV_ACTIVELOCK_TIMEOUT)) {
+			if (!strncmp(ctx->cdata, "Second-", 7))
+				lock->timeout =
+					strtol(ctx->cdata + 7, NULL, 10);
+		} else if (!strcmp(ctx->name, DAV_ACTIVELOCK_TOKEN)) {
+			if (!strncmp(ctx->cdata, "opaquelocktoken:", 16)) {
+				lock->token = xmalloc(strlen(ctx->cdata - 15));
+				strcpy(lock->token, ctx->cdata + 16);
+			}
+		}
 	}
 }
 
 static void
-activelock_cdata(void *userData, const XML_Char *s, int len)
+xml_start_tag(void *userData, const char *name, const char **atts)
 {
-	struct active_lock *lock = (struct active_lock *)userData;
-	char *this = malloc(len+1);
-	strncpy(this, s, len);
+	struct xml_ctx *ctx = (struct xml_ctx *)userData;
+	const char *c = index(name, ':');
+	int new_len;
 
-	if (lock->ctx_owner_href) {
-		lock->owner = malloc(len+1);
-		strcpy(lock->owner, this);
-	} else if (lock->ctx_locktoken_href) {
-		if (!strncmp(this, "opaquelocktoken:", 16)) {
-			lock->token = malloc(len-15);
-			strcpy(lock->token, this+16);
-		}
-	} else if (lock->ctx_timeout) {
-		if (!strncmp(this, "Second-", 7))
-			lock->timeout = strtol(this+7, NULL, 10);
+	if (c == NULL)
+		c = name;
+	else
+		c++;
+
+	new_len = strlen(ctx->name) + strlen(c) + 2;
+
+	if (new_len > ctx->len) {
+		ctx->name = xrealloc(ctx->name, new_len);
+		ctx->len = new_len;
+	}
+	strcat(ctx->name, ".");
+	strcat(ctx->name, c);
+
+	if (ctx->cdata) {
+		free(ctx->cdata);
+		ctx->cdata = NULL;
 	}
 
-	free(this);
+	ctx->userFunc(ctx, 0);
 }
 
 static void
-start_lockprop_element(void *userData, const char *name, const char **atts)
+xml_end_tag(void *userData, const char *name)
 {
-	struct lockprop *prop = (struct lockprop *)userData;
+	struct xml_ctx *ctx = (struct xml_ctx *)userData;
+	const char *c = index(name, ':');
+	char *ep;
 
-	if (prop->lock_type && !strcmp(name, "D:write")) {
-		if (prop->lock_exclusive) {
-			prop->lock_exclusive_write = 1;
-		}
-	} else if (prop->lock_scope && !strcmp(name, "D:exclusive")) {
-		prop->lock_exclusive = 1;
-	} else if (prop->lock_entry) {
-		if (!strcmp(name, "D:lockscope")) {
-			prop->lock_scope = 1;
-		} else if (!strcmp(name, "D:locktype")) {
-			prop->lock_type = 1;
-		}
-	} else if (prop->supported_lock) {
-		if (!strcmp(name, "D:lockentry")) {
-			prop->lock_entry = 1;
-		}
-	} else if (!strcmp(name, "D:supportedlock")) {
-		prop->supported_lock = 1;
-	}
+	ctx->userFunc(ctx, 1);
+
+	if (c == NULL)
+		c = name;
+	else
+		c++;
+
+	ep = ctx->name + strlen(ctx->name) - strlen(c) - 1;
+	*ep = 0;
 }
 
 static void
-end_lockprop_element(void *userData, const char *name)
+xml_cdata(void *userData, const XML_Char *s, int len)
 {
-	struct lockprop *prop = (struct lockprop *)userData;
-
-	if (!strcmp(name, "D:lockentry")) {
-		prop->lock_entry = 0;
-		prop->lock_scope = 0;
-		prop->lock_type = 0;
-		prop->lock_exclusive = 0;
-	} else if (!strcmp(name, "D:supportedlock")) {
-		prop->supported_lock = 0;
-	}
+	struct xml_ctx *ctx = (struct xml_ctx *)userData;
+	if (ctx->cdata)
+		free(ctx->cdata);
+	ctx->cdata = xcalloc(len+1, 1);
+	strncpy(ctx->cdata, s, len);
 }
 
 static struct active_lock *lock_remote(char *file, long timeout)
@@ -1199,10 +858,11 @@
 	char *url;
 	char *ep;
 	char timeout_header[25];
-	struct active_lock *new_lock;
+	struct active_lock *new_lock = NULL;
 	XML_Parser parser = XML_ParserCreate(NULL);
 	enum XML_Status result;
 	struct curl_slist *dav_headers = NULL;
+	struct xml_ctx ctx;
 
 	url = xmalloc(strlen(remote->url) + strlen(file) + 1);
 	sprintf(url, "%s%s", remote->url, file);
@@ -1246,12 +906,6 @@
 	in_buffer.posn = 0;
 	in_buffer.buffer = in_data;
 
-	new_lock = xcalloc(1, sizeof(*new_lock));
-	new_lock->owner = NULL;
-	new_lock->token = NULL;
-	new_lock->timeout = -1;
-	new_lock->refreshing = 0;
-
 	sprintf(timeout_header, "Timeout: Second-%ld", timeout);
 	dav_headers = curl_slist_append(dav_headers, timeout_header);
 	dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml");
@@ -1261,47 +915,47 @@
 	curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size);
 	curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
 	curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
-	curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
-			 fwrite_buffer_dynamic);
+	curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
 	curl_easy_setopt(slot->curl, CURLOPT_URL, url);
 	curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
 	curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_LOCK);
 	curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
 
+	new_lock = xcalloc(1, sizeof(*new_lock));
+	new_lock->owner = NULL;
+	new_lock->token = NULL;
+	new_lock->timeout = -1;
+	new_lock->refreshing = 0;
+
 	if (start_active_slot(slot)) {
 		run_active_slot(slot);
-		if (slot->curl_result != CURLE_OK) {
-			fprintf(stderr, "Got HTTP error %ld\n", slot->http_code);
-			free(new_lock);
-			free(url);
-			free(out_data);
-			free(in_data);
-			return NULL;
+		if (slot->curl_result == CURLE_OK) {
+			ctx.name = xcalloc(10, 1);
+			ctx.len = 0;
+			ctx.cdata = NULL;
+			ctx.userFunc = handle_new_lock_ctx;
+			ctx.userData = new_lock;
+			XML_SetUserData(parser, &ctx);
+			XML_SetElementHandler(parser, xml_start_tag,
+					      xml_end_tag);
+			XML_SetCharacterDataHandler(parser, xml_cdata);
+			result = XML_Parse(parser, in_buffer.buffer,
+					   in_buffer.posn, 1);
+			free(ctx.name);
+			if (result != XML_STATUS_OK) {
+				fprintf(stderr, "XML error: %s\n",
+					XML_ErrorString(
+						XML_GetErrorCode(parser)));
+				new_lock->timeout = -1;
+			}
 		}
 	} else {
-		free(new_lock);
-		free(url);
-		free(out_data);
-		free(in_data);
 		fprintf(stderr, "Unable to start request\n");
-		return NULL;
 	}
 
+	curl_slist_free_all(dav_headers);
 	free(out_data);
-
-	XML_SetUserData(parser, new_lock);
-	XML_SetElementHandler(parser, start_activelock_element,
-				      end_activelock_element);
-	XML_SetCharacterDataHandler(parser, activelock_cdata);
-	result = XML_Parse(parser, in_buffer.buffer, in_buffer.posn, 1);
 	free(in_data);
-	if (result != XML_STATUS_OK) {
-		fprintf(stderr, "%s", XML_ErrorString(
-				XML_GetErrorCode(parser)));
-		free(url);
-		free(new_lock);
-		return NULL;
-	}
 
 	if (new_lock->token == NULL || new_lock->timeout <= 0) {
 		if (new_lock->token != NULL)
@@ -1310,11 +964,12 @@
 			free(new_lock->owner);
 		free(url);
 		free(new_lock);
-		return NULL;
+		new_lock = NULL;
+	} else {
+		new_lock->url = url;
+		new_lock->start_time = time(NULL);
 	}
 
-	new_lock->url = url;
-	new_lock->start_time = time(NULL);
 	return new_lock;
 }
 
@@ -1353,13 +1008,15 @@
 	if (lock->owner != NULL)
 		free(lock->owner);
 	free(lock->url);
+/* Freeing the token causes a segfault...
 	free(lock->token);
+*/
 	free(lock);
 
 	return rc;
 }
 
-static int check_locking(void)
+static int locking_available(void)
 {
 	struct active_request_slot *slot;
 	struct buffer in_buffer;
@@ -1368,8 +1025,9 @@
 	char *out_data;
 	XML_Parser parser = XML_ParserCreate(NULL);
 	enum XML_Status result;
-	struct lockprop supported_lock;
 	struct curl_slist *dav_headers = NULL;
+	struct xml_ctx ctx;
+	int lock_flags = 0;
 
 	out_buffer.size = strlen(PROPFIND_REQUEST) + strlen(remote->url) - 2;
 	out_data = xmalloc(out_buffer.size + 1);
@@ -1390,8 +1048,7 @@
 	curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size);
 	curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
 	curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
-	curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
-			 fwrite_buffer_dynamic);
+	curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
 	curl_easy_setopt(slot->curl, CURLOPT_URL, remote->url);
 	curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
 	curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PROPFIND);
@@ -1399,30 +1056,35 @@
 
 	if (start_active_slot(slot)) {
 		run_active_slot(slot);
-		free(out_data);
-		if (slot->curl_result != CURLE_OK) {
-			free(in_buffer.buffer);
-			return -1;
-		}
+		if (slot->curl_result == CURLE_OK) {
+			ctx.name = xcalloc(10, 1);
+			ctx.len = 0;
+			ctx.cdata = NULL;
+			ctx.userFunc = handle_lockprop_ctx;
+			ctx.userData = &lock_flags;
+			XML_SetUserData(parser, &ctx);
+			XML_SetElementHandler(parser, xml_start_tag,
+					      xml_end_tag);
+			result = XML_Parse(parser, in_buffer.buffer,
+					   in_buffer.posn, 1);
+			free(ctx.name);
 
-		XML_SetUserData(parser, &supported_lock);
-		XML_SetElementHandler(parser, start_lockprop_element,
-				      end_lockprop_element);
-		result = XML_Parse(parser, in_buffer.buffer, in_buffer.posn, 1);
-		free(in_buffer.buffer);
-		if (result != XML_STATUS_OK)
-			return error("%s", XML_ErrorString(
-					     XML_GetErrorCode(parser)));
+			if (result != XML_STATUS_OK) {
+				fprintf(stderr, "XML error: %s\n",
+					XML_ErrorString(
+						XML_GetErrorCode(parser)));
+				lock_flags = 0;
+			}
+		}
 	} else {
-		free(out_data);
-		free(in_buffer.buffer);
-		return error("Unable to start request");
+		fprintf(stderr, "Unable to start request\n");
 	}
 
-	if (supported_lock.lock_exclusive_write)
-		return 0;
-	else
-		return 1;
+	free(out_data);
+	free(in_buffer.buffer);
+	curl_slist_free_all(dav_headers);
+
+	return lock_flags;
 }
 
 static int is_ancestor(unsigned char *sha1, struct commit *commit)
@@ -1560,8 +1222,6 @@
 
 int main(int argc, char **argv)
 {
-	struct active_request_slot *slot;
-	struct active_request_slot *next_slot;
 	struct transfer_request *request;
 	struct transfer_request *next_request;
 	int nr_refspec = 0;
@@ -1576,8 +1236,6 @@
 	unsigned char remote_sha1[20];
 	struct active_lock *remote_lock;
 	char *remote_path = NULL;
-	char *low_speed_limit;
-	char *low_speed_time;
 	int rc = 0;
 	int i;
 
@@ -1617,50 +1275,7 @@
 
 	memset(remote_dir_exists, 0, 256);
 
-	curl_global_init(CURL_GLOBAL_ALL);
-
-#ifdef USE_CURL_MULTI
-	{
-		char *http_max_requests = getenv("GIT_HTTP_MAX_REQUESTS");
-		if (http_max_requests != NULL)
-			max_requests = atoi(http_max_requests);
-	}
-
-	curlm = curl_multi_init();
-	if (curlm == NULL) {
-		fprintf(stderr, "Error creating curl multi handle.\n");
-		return 1;
-	}
-#endif
-
-	if (getenv("GIT_SSL_NO_VERIFY"))
-		curl_ssl_verify = 0;
-
-	ssl_cert = getenv("GIT_SSL_CERT");
-#if LIBCURL_VERSION_NUM >= 0x070902
-	ssl_key = getenv("GIT_SSL_KEY");
-#endif
-#if LIBCURL_VERSION_NUM >= 0x070908
-	ssl_capath = getenv("GIT_SSL_CAPATH");
-#endif
-	ssl_cainfo = getenv("GIT_SSL_CAINFO");
-
-	low_speed_limit = getenv("GIT_HTTP_LOW_SPEED_LIMIT");
-	if (low_speed_limit != NULL)
-		curl_low_speed_limit = strtol(low_speed_limit, NULL, 10);
-	low_speed_time = getenv("GIT_HTTP_LOW_SPEED_TIME");
-	if (low_speed_time != NULL)
-		curl_low_speed_time = strtol(low_speed_time, NULL, 10);
-
-	git_config(http_options);
-
-	if (curl_ssl_verify == -1)
-		curl_ssl_verify = 1;
-
-#ifdef USE_CURL_MULTI
-	if (max_requests < 1)
-		max_requests = DEFAULT_MAX_REQUESTS;
-#endif
+	http_init();
 
 	no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:");
 	default_headers = curl_slist_append(default_headers, "Range:");
@@ -1669,12 +1284,8 @@
 	default_headers = curl_slist_append(default_headers,
 					    "Pragma: no-cache");
 
-#ifndef NO_CURL_EASY_DUPHANDLE
-	curl_default = get_curl_handle();
-#endif
-
 	/* Verify DAV compliance/lock support */
-	if (check_locking() != 0) {
+	if (!locking_available()) {
 		fprintf(stderr, "Error: no DAV locking support on remote repo %s\n", remote->url);
 		rc = 1;
 		goto cleanup;
@@ -1766,13 +1377,13 @@
 			fetch_indices();
 		get_delta(push_all ? NULL : remote_sha1,
 			  local_object, remote_lock);
-		process_waiting_requests();
+		finish_all_active_slots();
 
 		/* Push missing objects to remote, this would be a
 		   convenient time to pack them first if appropriate. */
 		pushing = 1;
-		process_request_queue();
-		process_waiting_requests();
+		fill_active_slots();
+		finish_all_active_slots();
 
 		/* Update the remote branch if all went well */
 		if (do_remote_update) {
@@ -1802,14 +1413,7 @@
 	curl_slist_free_all(no_pragma_header);
 	curl_slist_free_all(default_headers);
 
-	slot = active_queue_head;
-	while (slot != NULL) {
-		next_slot = slot->next;
-		if (slot->curl != NULL)
-			curl_easy_cleanup(slot->curl);
-		free(slot);
-		slot = next_slot;
-	}
+	http_cleanup();
 
 	request = request_queue_head;
 	while (request != NULL) {
@@ -1818,12 +1422,5 @@
 		request = next_request;
 	}
 
-#ifndef NO_CURL_EASY_DUPHANDLE
-	curl_easy_cleanup(curl_default);
-#endif
-#ifdef USE_CURL_MULTI
-	curl_multi_cleanup(curlm);
-#endif
-	curl_global_cleanup();
 	return rc;
 }
diff --git a/http.c b/http.c
new file mode 100644
index 0000000..75e6717
--- /dev/null
+++ b/http.c
@@ -0,0 +1,442 @@
+#include "http.h"
+
+int data_received;
+int active_requests = 0;
+
+#ifdef USE_CURL_MULTI
+int max_requests = -1;
+CURLM *curlm;
+#endif
+#ifndef NO_CURL_EASY_DUPHANDLE
+CURL *curl_default;
+#endif
+char curl_errorstr[CURL_ERROR_SIZE];
+
+int curl_ssl_verify = -1;
+char *ssl_cert = NULL;
+#if LIBCURL_VERSION_NUM >= 0x070902
+char *ssl_key = NULL;
+#endif
+#if LIBCURL_VERSION_NUM >= 0x070908
+char *ssl_capath = NULL;
+#endif
+char *ssl_cainfo = NULL;
+long curl_low_speed_limit = -1;
+long curl_low_speed_time = -1;
+
+struct curl_slist *pragma_header;
+struct curl_slist *no_range_header;
+
+struct active_request_slot *active_queue_head = NULL;
+
+size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb,
+			   struct buffer *buffer)
+{
+	size_t size = eltsize * nmemb;
+	if (size > buffer->size - buffer->posn)
+		size = buffer->size - buffer->posn;
+	memcpy(ptr, buffer->buffer + buffer->posn, size);
+	buffer->posn += size;
+	return size;
+}
+
+size_t fwrite_buffer(const void *ptr, size_t eltsize,
+			    size_t nmemb, struct buffer *buffer)
+{
+	size_t size = eltsize * nmemb;
+	if (size > buffer->size - buffer->posn) {
+		buffer->size = buffer->size * 3 / 2;
+		if (buffer->size < buffer->posn + size)
+			buffer->size = buffer->posn + size;
+		buffer->buffer = xrealloc(buffer->buffer, buffer->size);
+	}
+	memcpy(buffer->buffer + buffer->posn, ptr, size);
+	buffer->posn += size;
+	data_received++;
+	return size;
+}
+
+size_t fwrite_null(const void *ptr, size_t eltsize,
+			  size_t nmemb, struct buffer *buffer)
+{
+	data_received++;
+	return eltsize * nmemb;
+}
+
+static void finish_active_slot(struct active_request_slot *slot);
+
+#ifdef USE_CURL_MULTI
+static void process_curl_messages(void)
+{
+	int num_messages;
+	struct active_request_slot *slot;
+	CURLMsg *curl_message = curl_multi_info_read(curlm, &num_messages);
+
+	while (curl_message != NULL) {
+		if (curl_message->msg == CURLMSG_DONE) {
+			int curl_result = curl_message->data.result;
+			slot = active_queue_head;
+			while (slot != NULL &&
+			       slot->curl != curl_message->easy_handle)
+				slot = slot->next;
+			if (slot != NULL) {
+				curl_multi_remove_handle(curlm, slot->curl);
+				slot->curl_result = curl_result;
+				finish_active_slot(slot);
+			} else {
+				fprintf(stderr, "Received DONE message for unknown request!\n");
+			}
+		} else {
+			fprintf(stderr, "Unknown CURL message received: %d\n",
+				(int)curl_message->msg);
+		}
+		curl_message = curl_multi_info_read(curlm, &num_messages);
+	}
+}
+#endif
+
+static int http_options(const char *var, const char *value)
+{
+	if (!strcmp("http.sslverify", var)) {
+		if (curl_ssl_verify == -1) {
+			curl_ssl_verify = git_config_bool(var, value);
+		}
+		return 0;
+	}
+
+	if (!strcmp("http.sslcert", var)) {
+		if (ssl_cert == NULL) {
+			ssl_cert = xmalloc(strlen(value)+1);
+			strcpy(ssl_cert, value);
+		}
+		return 0;
+	}
+#if LIBCURL_VERSION_NUM >= 0x070902
+	if (!strcmp("http.sslkey", var)) {
+		if (ssl_key == NULL) {
+			ssl_key = xmalloc(strlen(value)+1);
+			strcpy(ssl_key, value);
+		}
+		return 0;
+	}
+#endif
+#if LIBCURL_VERSION_NUM >= 0x070908
+	if (!strcmp("http.sslcapath", var)) {
+		if (ssl_capath == NULL) {
+			ssl_capath = xmalloc(strlen(value)+1);
+			strcpy(ssl_capath, value);
+		}
+		return 0;
+	}
+#endif
+	if (!strcmp("http.sslcainfo", var)) {
+		if (ssl_cainfo == NULL) {
+			ssl_cainfo = xmalloc(strlen(value)+1);
+			strcpy(ssl_cainfo, value);
+		}
+		return 0;
+	}
+
+#ifdef USE_CURL_MULTI	
+	if (!strcmp("http.maxrequests", var)) {
+		if (max_requests == -1)
+			max_requests = git_config_int(var, value);
+		return 0;
+	}
+#endif
+
+	if (!strcmp("http.lowspeedlimit", var)) {
+		if (curl_low_speed_limit == -1)
+			curl_low_speed_limit = (long)git_config_int(var, value);
+		return 0;
+	}
+	if (!strcmp("http.lowspeedtime", var)) {
+		if (curl_low_speed_time == -1)
+			curl_low_speed_time = (long)git_config_int(var, value);
+		return 0;
+	}
+
+	/* Fall back on the default ones */
+	return git_default_config(var, value);
+}
+
+static CURL* get_curl_handle(void)
+{
+	CURL* result = curl_easy_init();
+
+	curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, curl_ssl_verify);
+#if LIBCURL_VERSION_NUM >= 0x070907
+	curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
+#endif
+
+	if (ssl_cert != NULL)
+		curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert);
+#if LIBCURL_VERSION_NUM >= 0x070902
+	if (ssl_key != NULL)
+		curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key);
+#endif
+#if LIBCURL_VERSION_NUM >= 0x070908
+	if (ssl_capath != NULL)
+		curl_easy_setopt(result, CURLOPT_CAPATH, ssl_capath);
+#endif
+	if (ssl_cainfo != NULL)
+		curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo);
+	curl_easy_setopt(result, CURLOPT_FAILONERROR, 1);
+
+	if (curl_low_speed_limit > 0 && curl_low_speed_time > 0) {
+		curl_easy_setopt(result, CURLOPT_LOW_SPEED_LIMIT,
+				 curl_low_speed_limit);
+		curl_easy_setopt(result, CURLOPT_LOW_SPEED_TIME,
+				 curl_low_speed_time);
+	}
+
+	curl_easy_setopt(result, CURLOPT_FOLLOWLOCATION, 1);
+
+	return result;
+}
+
+void http_init(void)
+{
+	char *low_speed_limit;
+	char *low_speed_time;
+
+	curl_global_init(CURL_GLOBAL_ALL);
+
+	pragma_header = curl_slist_append(pragma_header, "Pragma: no-cache");
+	no_range_header = curl_slist_append(no_range_header, "Range:");
+
+#ifdef USE_CURL_MULTI
+	{
+		char *http_max_requests = getenv("GIT_HTTP_MAX_REQUESTS");
+		if (http_max_requests != NULL)
+			max_requests = atoi(http_max_requests);
+	}
+
+	curlm = curl_multi_init();
+	if (curlm == NULL) {
+		fprintf(stderr, "Error creating curl multi handle.\n");
+		exit(1);
+	}
+#endif
+
+	if (getenv("GIT_SSL_NO_VERIFY"))
+		curl_ssl_verify = 0;
+
+	ssl_cert = getenv("GIT_SSL_CERT");
+#if LIBCURL_VERSION_NUM >= 0x070902
+	ssl_key = getenv("GIT_SSL_KEY");
+#endif
+#if LIBCURL_VERSION_NUM >= 0x070908
+	ssl_capath = getenv("GIT_SSL_CAPATH");
+#endif
+	ssl_cainfo = getenv("GIT_SSL_CAINFO");
+
+	low_speed_limit = getenv("GIT_HTTP_LOW_SPEED_LIMIT");
+	if (low_speed_limit != NULL)
+		curl_low_speed_limit = strtol(low_speed_limit, NULL, 10);
+	low_speed_time = getenv("GIT_HTTP_LOW_SPEED_TIME");
+	if (low_speed_time != NULL)
+		curl_low_speed_time = strtol(low_speed_time, NULL, 10);
+
+	git_config(http_options);
+
+	if (curl_ssl_verify == -1)
+		curl_ssl_verify = 1;
+
+#ifdef USE_CURL_MULTI
+	if (max_requests < 1)
+		max_requests = DEFAULT_MAX_REQUESTS;
+#endif
+
+#ifndef NO_CURL_EASY_DUPHANDLE
+	curl_default = get_curl_handle();
+#endif
+}
+
+void http_cleanup(void)
+{
+	struct active_request_slot *slot = active_queue_head;
+#ifdef USE_CURL_MULTI
+	char *wait_url;
+#endif
+
+	while (slot != NULL) {
+#ifdef USE_CURL_MULTI
+		if (slot->in_use) {
+			curl_easy_getinfo(slot->curl,
+					  CURLINFO_EFFECTIVE_URL,
+					  &wait_url);
+			fprintf(stderr, "Waiting for %s\n", wait_url);
+			run_active_slot(slot);
+		}
+#endif
+		if (slot->curl != NULL)
+			curl_easy_cleanup(slot->curl);
+		slot = slot->next;
+	}
+
+#ifndef NO_CURL_EASY_DUPHANDLE
+	curl_easy_cleanup(curl_default);
+#endif
+
+#ifdef USE_CURL_MULTI
+	curl_multi_cleanup(curlm);
+#endif
+	curl_global_cleanup();
+	
+}
+
+struct active_request_slot *get_active_slot(void)
+{
+	struct active_request_slot *slot = active_queue_head;
+	struct active_request_slot *newslot;
+
+#ifdef USE_CURL_MULTI
+	int num_transfers;
+
+	/* Wait for a slot to open up if the queue is full */
+	while (active_requests >= max_requests) {
+		curl_multi_perform(curlm, &num_transfers);
+		if (num_transfers < active_requests) {
+			process_curl_messages();
+		}
+	}
+#endif
+
+	while (slot != NULL && slot->in_use) {
+		slot = slot->next;
+	}
+	if (slot == NULL) {
+		newslot = xmalloc(sizeof(*newslot));
+		newslot->curl = NULL;
+		newslot->in_use = 0;
+		newslot->next = NULL;
+
+		slot = active_queue_head;
+		if (slot == NULL) {
+			active_queue_head = newslot;
+		} else {
+			while (slot->next != NULL) {
+				slot = slot->next;
+			}
+			slot->next = newslot;
+		}
+		slot = newslot;
+	}
+
+	if (slot->curl == NULL) {
+#ifdef NO_CURL_EASY_DUPHANDLE
+		slot->curl = get_curl_handle();
+#else
+		slot->curl = curl_easy_duphandle(curl_default);
+#endif
+	}
+
+	active_requests++;
+	slot->in_use = 1;
+	slot->local = NULL;
+	slot->callback_data = NULL;
+	slot->callback_func = NULL;
+	curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, pragma_header);
+	curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_range_header);
+	curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, curl_errorstr);
+
+	return slot;
+}
+
+int start_active_slot(struct active_request_slot *slot)
+{
+#ifdef USE_CURL_MULTI
+	CURLMcode curlm_result = curl_multi_add_handle(curlm, slot->curl);
+
+	if (curlm_result != CURLM_OK &&
+	    curlm_result != CURLM_CALL_MULTI_PERFORM) {
+		active_requests--;
+		slot->in_use = 0;
+		return 0;
+	}
+#endif
+	return 1;
+}
+
+#ifdef USE_CURL_MULTI
+void step_active_slots(void)
+{
+	int num_transfers;
+	CURLMcode curlm_result;
+
+	do {
+		curlm_result = curl_multi_perform(curlm, &num_transfers);
+	} while (curlm_result == CURLM_CALL_MULTI_PERFORM);
+	if (num_transfers < active_requests) {
+		process_curl_messages();
+		fill_active_slots();
+	}
+}
+#endif
+
+void run_active_slot(struct active_request_slot *slot)
+{
+#ifdef USE_CURL_MULTI
+	long last_pos = 0;
+	long current_pos;
+	fd_set readfds;
+	fd_set writefds;
+	fd_set excfds;
+	int max_fd;
+	struct timeval select_timeout;
+
+	while (slot->in_use) {
+		data_received = 0;
+		step_active_slots();
+
+		if (!data_received && slot->local != NULL) {
+			current_pos = ftell(slot->local);
+			if (current_pos > last_pos)
+				data_received++;
+			last_pos = current_pos;
+		}
+
+		if (slot->in_use && !data_received) {
+			max_fd = 0;
+			FD_ZERO(&readfds);
+			FD_ZERO(&writefds);
+			FD_ZERO(&excfds);
+			select_timeout.tv_sec = 0;
+			select_timeout.tv_usec = 50000;
+			select(max_fd, &readfds, &writefds,
+			       &excfds, &select_timeout);
+		}
+	}
+#else
+	while (slot->in_use) {
+		slot->curl_result = curl_easy_perform(slot->curl);
+		finish_active_slot(slot);
+	}
+#endif
+}
+
+static void finish_active_slot(struct active_request_slot *slot)
+{
+        active_requests--;
+        slot->in_use = 0;
+        curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CODE, &slot->http_code);
+ 
+        /* Run callback if appropriate */
+        if (slot->callback_func != NULL) {
+                slot->callback_func(slot->callback_data);
+        }
+}
+
+void finish_all_active_slots(void)
+{
+	struct active_request_slot *slot = active_queue_head;
+
+	while (slot != NULL)
+		if (slot->in_use) {
+			run_active_slot(slot);
+			slot = active_queue_head;
+		} else {
+			slot = slot->next;
+		}
+}
diff --git a/http.h b/http.h
new file mode 100644
index 0000000..ed4ea33
--- /dev/null
+++ b/http.h
@@ -0,0 +1,95 @@
+#ifndef HTTP_H
+#define HTTP_H
+
+#include "cache.h"
+
+#include <curl/curl.h>
+#include <curl/easy.h>
+
+#if LIBCURL_VERSION_NUM >= 0x070908
+#define USE_CURL_MULTI
+#define DEFAULT_MAX_REQUESTS 5
+#endif
+
+#if LIBCURL_VERSION_NUM < 0x070704
+#define curl_global_cleanup() do { /* nothing */ } while(0)
+#endif
+#if LIBCURL_VERSION_NUM < 0x070800
+#define curl_global_init(a) do { /* nothing */ } while(0)
+#endif
+
+#if LIBCURL_VERSION_NUM < 0x070c04
+#define NO_CURL_EASY_DUPHANDLE
+#endif
+
+struct active_request_slot
+{
+	CURL *curl;
+	FILE *local;
+	int in_use;
+	CURLcode curl_result;
+	long http_code;
+	void *callback_data;
+	void (*callback_func)(void *data);
+	struct active_request_slot *next;
+};
+
+struct buffer
+{
+        size_t posn;
+        size_t size;
+        void *buffer;
+};
+
+/* Curl request read/write callbacks */
+extern size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb,
+			   struct buffer *buffer);
+extern size_t fwrite_buffer(const void *ptr, size_t eltsize,
+			    size_t nmemb, struct buffer *buffer);
+extern size_t fwrite_null(const void *ptr, size_t eltsize,
+			  size_t nmemb, struct buffer *buffer);
+
+/* Slot lifecycle functions */
+extern struct active_request_slot *get_active_slot(void);
+extern int start_active_slot(struct active_request_slot *slot);
+extern void run_active_slot(struct active_request_slot *slot);
+extern void finish_all_active_slots(void);
+
+#ifdef USE_CURL_MULTI
+extern void fill_active_slots(void);
+extern void step_active_slots(void);
+#endif
+
+extern void http_init(void);
+extern void http_cleanup(void);
+
+extern int data_received;
+extern int active_requests;
+
+#ifdef USE_CURL_MULTI
+extern int max_requests;
+extern CURLM *curlm;
+#endif
+#ifndef NO_CURL_EASY_DUPHANDLE
+extern CURL *curl_default;
+#endif
+extern char curl_errorstr[CURL_ERROR_SIZE];
+
+extern int curl_ssl_verify;
+extern char *ssl_cert;
+#if LIBCURL_VERSION_NUM >= 0x070902
+extern char *ssl_key;
+#endif
+#if LIBCURL_VERSION_NUM >= 0x070908
+extern char *ssl_capath;
+#endif
+extern char *ssl_cainfo;
+extern long curl_low_speed_limit;
+extern long curl_low_speed_time;
+
+extern struct curl_slist *pragma_header;
+extern struct curl_slist *no_range_header;
+
+extern struct active_request_slot *active_queue_head;
+
+#endif /* HTTP_H */
diff --git a/ident.c b/ident.c
index bc89e1d..ac1c27f 100644
--- a/ident.c
+++ b/ident.c
@@ -156,7 +156,8 @@
 	return offset;
 }
 
-char *get_ident(const char *name, const char *email, const char *date_str)
+static const char *get_ident(const char *name, const char *email,
+			     const char *date_str)
 {
 	static char buffer[1000];
 	char date[50];
@@ -181,12 +182,16 @@
 	return buffer;
 }
 
-char *git_author_info(void)
+const char *git_author_info(void)
 {
-	return get_ident(getenv("GIT_AUTHOR_NAME"), getenv("GIT_AUTHOR_EMAIL"), getenv("GIT_AUTHOR_DATE"));
+	return get_ident(getenv("GIT_AUTHOR_NAME"),
+			 getenv("GIT_AUTHOR_EMAIL"),
+			 getenv("GIT_AUTHOR_DATE"));
 }
 
-char *git_committer_info(void)
+const char *git_committer_info(void)
 {
-	return get_ident(getenv("GIT_COMMITTER_NAME"), getenv("GIT_COMMITTER_EMAIL"), getenv("GIT_COMMITTER_DATE"));
+	return get_ident(getenv("GIT_COMMITTER_NAME"),
+			 getenv("GIT_COMMITTER_EMAIL"),
+			 getenv("GIT_COMMITTER_DATE"));
 }
diff --git a/name-rev.c b/name-rev.c
index 59194f1..817e36b 100644
--- a/name-rev.c
+++ b/name-rev.c
@@ -230,8 +230,6 @@
 				fwrite(p_start, p - p_start, 1, stdout);
 		}
 	} else if (all) {
-		extern struct object **objs;
-		extern int nr_objs;
 		int i;
 
 		for (i = 0; i < nr_objs; i++)
diff --git a/pack-objects.c b/pack-objects.c
index 4e941e7..8864a31 100644
--- a/pack-objects.c
+++ b/pack-objects.c
@@ -524,7 +524,7 @@
 		unsigned char sha1[20];
 
 		if (get_sha1_hex(line, sha1))
-			die("expected sha1, got garbage");
+			die("expected sha1, got garbage:\n %s", line);
 		hash = 0;
 		p = line+40;
 		while (*p) {
diff --git a/pack-redundant.c b/pack-redundant.c
index fb6cb48..793fa08 100644
--- a/pack-redundant.c
+++ b/pack-redundant.c
@@ -11,19 +11,19 @@
 static const char pack_redundant_usage[] =
 "git-pack-redundant [ --verbose ] [ --alt-odb ] < --all | <.pack filename> ...>";
 
-int load_all_packs = 0, verbose = 0, alt_odb = 0;
+static int load_all_packs = 0, verbose = 0, alt_odb = 0;
 
 struct llist_item {
 	struct llist_item *next;
-	char *sha1;
+	unsigned char *sha1;
 };
-struct llist {
+static struct llist {
 	struct llist_item *front;
 	struct llist_item *back;
 	size_t size;
 } *all_objects; /* all objects which must be present in local packfiles */
 
-struct pack_list {
+static struct pack_list {
 	struct pack_list *next;
 	struct packed_git *pack;
 	struct llist *unique_objects;
@@ -36,23 +36,43 @@
 	size_t pl_size;
 };
 
-inline void llist_free(struct llist *list)
+static struct llist_item *free_nodes = NULL;
+
+static inline struct llist_item *llist_item_get()
+{
+	struct llist_item *new;
+	if ( free_nodes ) {
+		new = free_nodes;
+		free_nodes = free_nodes->next;
+	} else
+		new = xmalloc(sizeof(struct llist_item));
+
+	return new;
+}
+
+static inline void llist_item_put(struct llist_item *item)
+{
+	item->next = free_nodes;
+	free_nodes = item;
+}
+
+static void llist_free(struct llist *list)
 {
 	while((list->back = list->front)) {
 		list->front = list->front->next;
-		free(list->back);
+		llist_item_put(list->back);
 	}
 	free(list);
 }
 
-inline void llist_init(struct llist **list)
+static inline void llist_init(struct llist **list)
 {
 	*list = xmalloc(sizeof(struct llist));
 	(*list)->front = (*list)->back = NULL;
 	(*list)->size = 0;
 }
 
-struct llist * llist_copy(struct llist *list)
+static struct llist * llist_copy(struct llist *list)
 {
 	struct llist *ret;
 	struct llist_item *new, *old, *prev;
@@ -62,13 +82,13 @@
 	if ((ret->size = list->size) == 0)
 		return ret;
 
-	new = ret->front = xmalloc(sizeof(struct llist_item));
+	new = ret->front = llist_item_get();
 	new->sha1 = list->front->sha1;
 
 	old = list->front->next;
 	while (old) {
 		prev = new;
-		new = xmalloc(sizeof(struct llist_item));
+		new = llist_item_get();
 		prev->next = new;
 		new->sha1 = old->sha1;
 		old = old->next;
@@ -79,10 +99,11 @@
 	return ret;
 }
 
-inline struct llist_item * llist_insert(struct llist *list,
-					struct llist_item *after, char *sha1)
+static inline struct llist_item * llist_insert(struct llist *list,
+					       struct llist_item *after,
+					       unsigned char *sha1)
 {
-	struct llist_item *new = xmalloc(sizeof(struct llist_item));
+	struct llist_item *new = llist_item_get();
 	new->sha1 = sha1;
 	new->next = NULL;
 
@@ -102,13 +123,12 @@
 	return new;
 }
 
-inline struct llist_item * llist_insert_back(struct llist *list, char *sha1)
+static inline struct llist_item *llist_insert_back(struct llist *list, unsigned char *sha1)
 {
 	return llist_insert(list, list->back, sha1);
 }
 
-inline struct llist_item * llist_insert_sorted_unique(struct llist *list,
-					char *sha1, struct llist_item *hint)
+static inline struct llist_item *llist_insert_sorted_unique(struct llist *list, unsigned char *sha1, struct llist_item *hint)
 {
 	struct llist_item *prev = NULL, *l;
 
@@ -129,8 +149,7 @@
 }
 
 /* returns a pointer to an item in front of sha1 */
-inline struct llist_item * llist_sorted_remove(struct llist *list, char *sha1,
-					       struct llist_item *hint)
+static inline struct llist_item * llist_sorted_remove(struct llist *list, const unsigned char *sha1, struct llist_item *hint)
 {
 	struct llist_item *prev, *l;
 
@@ -153,7 +172,7 @@
 				prev->next = l->next;
 			if (l == list->back)
 				list->back = prev;
-			free(l);
+			llist_item_put(l);
 			list->size--;
 			return prev;
 		}
@@ -164,7 +183,7 @@
 }
 
 /* computes A\B */
-void llist_sorted_difference_inplace(struct llist *A,
+static void llist_sorted_difference_inplace(struct llist *A,
 				     struct llist *B)
 {
 	struct llist_item *hint, *b;
@@ -178,7 +197,7 @@
 	}
 }
 
-inline struct pack_list * pack_list_insert(struct pack_list **pl,
+static inline struct pack_list * pack_list_insert(struct pack_list **pl,
 					   struct pack_list *entry)
 {
 	struct pack_list *p = xmalloc(sizeof(struct pack_list));
@@ -188,7 +207,7 @@
 	return p;
 }
 
-inline size_t pack_list_size(struct pack_list *pl)
+static inline size_t pack_list_size(struct pack_list *pl)
 {
 	size_t ret = 0;
 	while(pl) {
@@ -198,10 +217,11 @@
 	return ret;
 }
 
-struct pack_list * pack_list_difference(struct pack_list *A,
-					struct pack_list *B)
+static struct pack_list * pack_list_difference(const struct pack_list *A,
+					       const struct pack_list *B)
 {
-	struct pack_list *ret, *pl;
+	struct pack_list *ret;
+	const struct pack_list *pl;
 
 	if (A == NULL)
 		return NULL;
@@ -218,7 +238,7 @@
 	return ret;
 }
 
-void cmp_two_packs(struct pack_list *p1, struct pack_list *p2)
+static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2)
 {
 	int p1_off, p2_off;
 	void *p1_base, *p2_base;
@@ -250,7 +270,7 @@
 	}
 }
 
-void pll_insert(struct pll **pll, struct pll **hint_table)
+static void pll_insert(struct pll **pll, struct pll **hint_table)
 {
 	struct pll *prev;
 	int i = (*pll)->pl_size - 1;
@@ -276,7 +296,7 @@
 /* all the permutations have to be free()d at the same time,
  * since they refer to each other
  */
-struct pll * get_all_permutations(struct pack_list *list)
+static struct pll * get_all_permutations(struct pack_list *list)
 {
 	struct pll *subset, *pll, *new_pll = NULL; /*silence warning*/
 	static struct pll **hint = NULL;
@@ -323,15 +343,14 @@
 	return hint[0];
 }
 
-int is_superset(struct pack_list *pl, struct llist *list)
+static int is_superset(struct pack_list *pl, struct llist *list)
 {
 	struct llist *diff;
 
 	diff = llist_copy(list);
 
 	while (pl) {
-		llist_sorted_difference_inplace(diff,
-						pl->all_objects);
+		llist_sorted_difference_inplace(diff, pl->all_objects);
 		if (diff->size == 0) { /* we're done */
 			llist_free(diff);
 			return 1;
@@ -342,7 +361,7 @@
 	return 0;
 }
 
-size_t sizeof_union(struct packed_git *p1, struct packed_git *p2)
+static size_t sizeof_union(struct packed_git *p1, struct packed_git *p2)
 {
 	size_t ret = 0;
 	int p1_off, p2_off;
@@ -373,14 +392,14 @@
 }
 
 /* another O(n^2) function ... */
-size_t get_pack_redundancy(struct pack_list *pl)
+static size_t get_pack_redundancy(struct pack_list *pl)
 {
 	struct pack_list *subset;
+	size_t ret = 0;
 
 	if (pl == NULL)
 		return 0;
 
-	size_t ret = 0;
 	while ((subset = pl->next)) {
 		while(subset) {
 			ret += sizeof_union(pl->pack, subset->pack);
@@ -391,7 +410,7 @@
 	return ret;
 }
 
-inline size_t pack_set_bytecount(struct pack_list *pl)
+static inline size_t pack_set_bytecount(struct pack_list *pl)
 {
 	size_t ret = 0;
 	while (pl) {
@@ -402,7 +421,7 @@
 	return ret;
 }
 
-void minimize(struct pack_list **min)
+static void minimize(struct pack_list **min)
 {
 	struct pack_list *pl, *unique = NULL,
 		*non_unique = NULL, *min_perm = NULL;
@@ -469,16 +488,14 @@
 	}
 }
 
-void load_all_objects()
+static void load_all_objects(void)
 {
 	struct pack_list *pl = local_packs;
 	struct llist_item *hint, *l;
-	int i;
 
 	llist_init(&all_objects);
 
 	while (pl) {
-		i = 0;
 		hint = NULL;
 		l = pl->all_objects->front;
 		while (l) {
@@ -497,7 +514,7 @@
 }
 
 /* this scales like O(n^2) */
-void cmp_local_packs()
+static void cmp_local_packs(void)
 {
 	struct pack_list *subset, *pl = local_packs;
 
@@ -508,7 +525,7 @@
 	}
 }
 
-void scan_alt_odb_packs()
+static void scan_alt_odb_packs(void)
 {
 	struct pack_list *local, *alt;
 
@@ -524,7 +541,7 @@
 	}
 }
 
-struct pack_list * add_pack(struct packed_git *p)
+static struct pack_list * add_pack(struct packed_git *p)
 {
 	struct pack_list l;
 	size_t off;
@@ -550,7 +567,7 @@
 		return pack_list_insert(&altodb_packs, &l);
 }
 
-struct pack_list * add_pack_file(char *filename)
+static struct pack_list * add_pack_file(char *filename)
 {
 	struct packed_git *p = packed_git;
 
@@ -565,7 +582,7 @@
 	die("Filename %s not found in packed_git\n", filename);
 }
 
-void load_all()
+static void load_all(void)
 {
 	struct packed_git *p = packed_git;
 
@@ -579,6 +596,9 @@
 {
 	int i;
 	struct pack_list *min, *red, *pl;
+	struct llist *ignore;
+	unsigned char *sha1;
+	char buf[42]; /* 40 byte sha1 + \n + \0 */
 
 	for (i = 1; i < argc; i++) {
 		const char *arg = argv[i];
@@ -621,6 +641,23 @@
 	if (alt_odb)
 		scan_alt_odb_packs();
 
+	/* ignore objects given on stdin */
+	llist_init(&ignore);
+	if (!isatty(0)) {
+		while (fgets(buf, sizeof(buf), stdin)) {
+			sha1 = xmalloc(20);
+			if (get_sha1_hex(buf, sha1))
+				die("Bad sha1 on stdin: %s", buf);
+			llist_insert_sorted_unique(ignore, sha1, NULL);
+		}
+	}
+	llist_sorted_difference_inplace(all_objects, ignore);
+	pl = local_packs;
+	while (pl) {
+		llist_sorted_difference_inplace(pl->unique_objects, ignore);
+		pl = pl->next;
+	}
+
 	minimize(&min);
 
 	if (verbose) {
@@ -647,6 +684,9 @@
 		       pl->pack->pack_name);
 		pl = pl->next;
 	}
+	if (verbose)
+		fprintf(stderr, "%luMB of redundant packs in total.\n",
+			(unsigned long)pack_set_bytecount(red)/(1024*1024));
 
 	return 0;
 }
diff --git a/path.c b/path.c
index 495d17c..4d88947 100644
--- a/path.c
+++ b/path.c
@@ -11,6 +11,7 @@
  * which is what it's designed for.
  */
 #include "cache.h"
+#include <pwd.h>
 
 static char pathname[PATH_MAX];
 static char bad_path[] = "/bad-path/";
@@ -89,3 +90,117 @@
 
 	return dest;
 }
+
+int validate_symref(const char *path)
+{
+	struct stat st;
+	char *buf, buffer[256];
+	int len, fd;
+
+	if (lstat(path, &st) < 0)
+		return -1;
+
+	/* Make sure it is a "refs/.." symlink */
+	if (S_ISLNK(st.st_mode)) {
+		len = readlink(path, buffer, sizeof(buffer)-1);
+		if (len >= 5 && !memcmp("refs/", buffer, 5))
+			return 0;
+		return -1;
+	}
+
+	/*
+	 * Anything else, just open it and try to see if it is a symbolic ref.
+	 */
+	fd = open(path, O_RDONLY);
+	if (fd < 0)
+		return -1;
+	len = read(fd, buffer, sizeof(buffer)-1);
+	close(fd);
+
+	/*
+	 * Is it a symbolic ref?
+	 */
+	if (len < 4 || memcmp("ref:", buffer, 4))
+		return -1;
+	buf = buffer + 4;
+	len -= 4;
+	while (len && isspace(*buf))
+		buf++, len--;
+	if (len >= 5 && !memcmp("refs/", buf, 5))
+		return 0;
+	return -1;
+}
+
+static char *current_dir(void)
+{
+	return getcwd(pathname, sizeof(pathname));
+}
+
+static int user_chdir(char *path)
+{
+	char *dir = path;
+
+	if(*dir == '~') {		/* user-relative path */
+		struct passwd *pw;
+		char *slash = strchr(dir, '/');
+
+		dir++;
+		/* '~/' and '~' (no slash) means users own home-dir */
+		if(!*dir || *dir == '/')
+			pw = getpwuid(getuid());
+		else {
+			if (slash) {
+				*slash = '\0';
+				pw = getpwnam(dir);
+				*slash = '/';
+			}
+			else
+				pw = getpwnam(dir);
+		}
+
+		/* make sure we got something back that we can chdir() to */
+		if(!pw || chdir(pw->pw_dir) < 0)
+			return -1;
+
+		if(!slash || !slash[1]) /* no path following username */
+			return 0;
+
+		dir = slash + 1;
+	}
+
+	/* ~foo/path/to/repo is now path/to/repo and we're in foo's homedir */
+	if(chdir(dir) < 0)
+		return -1;
+
+	return 0;
+}
+
+char *enter_repo(char *path, int strict)
+{
+	if(!path)
+		return NULL;
+
+	if (strict) {
+		if (chdir(path) < 0)
+			return NULL;
+	}
+	else {
+		if (!*path)
+			; /* happy -- no chdir */
+		else if (!user_chdir(path))
+			; /* happy -- as given */
+		else if (!user_chdir(mkpath("%s.git", path)))
+			; /* happy -- uemacs --> uemacs.git */
+		else
+			return NULL;
+		(void)chdir(".git");
+	}
+
+	if(access("objects", X_OK) == 0 && access("refs", X_OK) == 0 &&
+	   validate_symref("HEAD") == 0) {
+		putenv("GIT_DIR=.");
+		return current_dir();
+	}
+
+	return NULL;
+}
diff --git a/receive-pack.c b/receive-pack.c
index 8f157bc..1873506 100644
--- a/receive-pack.c
+++ b/receive-pack.c
@@ -248,11 +248,11 @@
 int main(int argc, char **argv)
 {
 	int i;
-	const char *dir = NULL;
+	char *dir = NULL;
 
 	argv++;
 	for (i = 1; i < argc; i++) {
-		const char *arg = *argv++;
+		char *arg = *argv++;
 
 		if (*arg == '-') {
 			/* Do flag handling here */
@@ -265,18 +265,9 @@
 	if (!dir)
 		usage(receive_pack_usage);
 
-	/* chdir to the directory. If that fails, try appending ".git" */
-	if (chdir(dir) < 0) {
-		if (chdir(mkpath("%s.git", dir)) < 0)
-			die("unable to cd to %s", dir);
-	}
+	if(!enter_repo(dir, 0))
+		die("'%s': unable to chdir or not a git archive", dir);
 
-	/* If we have a ".git" directory, chdir to it */
-	chdir(".git");
-	putenv("GIT_DIR=.");
-
-	if (access("objects", X_OK) < 0 || access("refs/heads", X_OK) < 0)
-		die("%s doesn't appear to be a git directory", dir);
 	write_head_info();
 
 	/* EOF */
diff --git a/refs.c b/refs.c
index f324be5..ac26198 100644
--- a/refs.c
+++ b/refs.c
@@ -10,46 +10,6 @@
 #define USE_SYMLINK_HEAD 1
 #endif
 
-int validate_symref(const char *path)
-{
-	struct stat st;
-	char *buf, buffer[256];
-	int len, fd;
-
-	if (lstat(path, &st) < 0)
-		return -1;
-
-	/* Make sure it is a "refs/.." symlink */
-	if (S_ISLNK(st.st_mode)) {
-		len = readlink(path, buffer, sizeof(buffer)-1);
-		if (len >= 5 && !memcmp("refs/", buffer, 5))
-			return 0;
-		return -1;
-	}
-
-	/*
-	 * Anything else, just open it and try to see if it is a symbolic ref.
-	 */
-	fd = open(path, O_RDONLY);
-	if (fd < 0)
-		return -1;
-	len = read(fd, buffer, sizeof(buffer)-1);
-	close(fd);
-
-	/*
-	 * Is it a symbolic ref?
-	 */
-	if (len < 4 || memcmp("ref:", buffer, 4))
-		return -1;
-	buf = buffer + 4;
-	len -= 4;
-	while (len && isspace(*buf))
-		buf++, len--;
-	if (len >= 5 && !memcmp("refs/", buf, 5))
-		return 0;
-	return -1;
-}
-
 const char *resolve_ref(const char *path, unsigned char *sha1, int reading)
 {
 	int depth = MAXDEPTH, len;
diff --git a/repo-config.c b/repo-config.c
new file mode 100644
index 0000000..b2569b7
--- /dev/null
+++ b/repo-config.c
@@ -0,0 +1,116 @@
+#include "cache.h"
+#include <regex.h>
+
+static const char git_config_set_usage[] =
+"git-repo-config [--get | --get-all | --replace-all | --unset | --unset-all] name [value [value_regex]]";
+
+static char* key = NULL;
+static char* value = NULL;
+static regex_t* regex = NULL;
+static int do_all = 0;
+static int do_not_match = 0;
+static int seen = 0;
+
+static int show_config(const char* key_, const char* value_)
+{
+	if (!strcmp(key_, key) &&
+			(regex == NULL ||
+			 (do_not_match ^
+			  !regexec(regex, value_, 0, NULL, 0)))) {
+		if (do_all) {
+			printf("%s\n", value_);
+			return 0;
+		}
+		if (seen > 0) {
+			fprintf(stderr, "More than one value: %s\n", value);
+			free(value);
+		}
+		value = strdup(value_);
+		seen++;
+	}
+	return 0;
+}
+
+static int get_value(const char* key_, const char* regex_)
+{
+	int i;
+
+	key = malloc(strlen(key_)+1);
+	for (i = 0; key_[i]; i++)
+		key[i] = tolower(key_[i]);
+	key[i] = 0;
+
+	if (regex_) {
+		if (regex_[0] == '!') {
+			do_not_match = 1;
+			regex_++;
+		}
+
+		regex = (regex_t*)malloc(sizeof(regex_t));
+		if (regcomp(regex, regex_, REG_EXTENDED)) {
+			fprintf(stderr, "Invalid pattern: %s\n", regex_);
+			return -1;
+		}
+	}
+
+	i = git_config(show_config);
+	if (value) {
+		printf("%s\n", value);
+		free(value);
+	}
+	free(key);
+	if (regex) {
+		regfree(regex);
+		free(regex);
+	}
+
+	if (do_all)
+		return 0;
+
+	return seen == 1 ? 0 : 1;
+}
+
+int main(int argc, const char **argv)
+{
+	setup_git_directory();
+	switch (argc) {
+	case 2:
+		return get_value(argv[1], NULL);
+	case 3:
+		if (!strcmp(argv[1], "--unset"))
+			return git_config_set(argv[2], NULL);
+		else if (!strcmp(argv[1], "--unset-all"))
+			return git_config_set_multivar(argv[2], NULL, NULL, 1);
+		else if (!strcmp(argv[1], "--get"))
+			return get_value(argv[2], NULL);
+		else if (!strcmp(argv[1], "--get-all")) {
+			do_all = 1;
+			return get_value(argv[2], NULL);
+		} else
+
+			return git_config_set(argv[1], argv[2]);
+	case 4:
+		if (!strcmp(argv[1], "--unset"))
+			return git_config_set_multivar(argv[2], NULL, argv[3], 0);
+		else if (!strcmp(argv[1], "--unset-all"))
+			return git_config_set_multivar(argv[2], NULL, argv[3], 1);
+		else if (!strcmp(argv[1], "--get"))
+			return get_value(argv[2], argv[3]);
+		else if (!strcmp(argv[1], "--get-all")) {
+			do_all = 1;
+			return get_value(argv[2], argv[3]);
+		} else if (!strcmp(argv[1], "--replace-all"))
+
+			return git_config_set_multivar(argv[2], argv[3], NULL, 1);
+		else
+
+			return git_config_set_multivar(argv[1], argv[2], argv[3], 0);
+	case 5:
+		if (!strcmp(argv[1], "--replace-all"))
+			return git_config_set_multivar(argv[2], argv[3], argv[4], 1);
+	case 1:
+	default:
+		usage(git_config_set_usage);
+	}
+	return 0;
+}
diff --git a/rev-list.c b/rev-list.c
index 6e6ffde..e17f928 100644
--- a/rev-list.c
+++ b/rev-list.c
@@ -124,8 +124,6 @@
 		stop_traversal=1;
 		return CONTINUE;
 	}
-	if (max_count != -1 && !max_count--)
-		return STOP;
 	if (no_merges && (commit->parents && commit->parents->next))
 		return CONTINUE;
 	if (paths && dense) {
@@ -148,6 +146,9 @@
 		return CONTINUE;
 	}
 
+	if (max_count != -1 && !max_count--)
+		return STOP;
+
 	show_commit(commit);
 
 	return CONTINUE;
diff --git a/setup.c b/setup.c
index c487d7e..ab3c778 100644
--- a/setup.c
+++ b/setup.c
@@ -73,8 +73,8 @@
 }
 
 /*
- * Test it it looks like we're at the top
- * level git directory. We want to see a
+ * Test if it looks like we're at the top level git directory.
+ * We want to see:
  *
  *  - either a .git/objects/ directory _or_ the proper
  *    GIT_OBJECT_DIRECTORY environment variable
@@ -92,17 +92,43 @@
 	return 1;
 }
 
-const char *setup_git_directory(void)
+static const char *setup_git_directory_1(void)
 {
 	static char cwd[PATH_MAX+1];
 	int len, offset;
 
 	/*
 	 * If GIT_DIR is set explicitly, we're not going
-	 * to do any discovery
+	 * to do any discovery, but we still do repository
+	 * validation.
 	 */
-	if (getenv(GIT_DIR_ENVIRONMENT))
+	if (getenv(GIT_DIR_ENVIRONMENT)) {
+		char path[PATH_MAX];
+		int len = strlen(getenv(GIT_DIR_ENVIRONMENT));
+		if (sizeof(path) - 40 < len)
+			die("'$%s' too big", GIT_DIR_ENVIRONMENT);
+		memcpy(path, getenv(GIT_DIR_ENVIRONMENT), len);
+		
+		strcpy(path + len, "/refs");
+		if (access(path, X_OK))
+			goto bad_dir_environ;
+		strcpy(path + len, "/HEAD");
+		if (validate_symref(path))
+			goto bad_dir_environ;
+		if (getenv(DB_ENVIRONMENT)) {
+			if (access(DB_ENVIRONMENT, X_OK))
+				goto bad_dir_environ;
+		}
+		else {
+			strcpy(path + len, "/objects");
+			if (access(path, X_OK))
+				goto bad_dir_environ;
+		}
 		return NULL;
+	bad_dir_environ:
+		path[len] = 0;
+		die("Not a git repository: '%s'", path);
+	}
 
 	if (!getcwd(cwd, sizeof(cwd)) || cwd[0] != '/')
 		die("Unable to read current working directory");
@@ -127,3 +153,9 @@
 	cwd[len] = 0;
 	return cwd + offset;
 }
+
+const char *setup_git_directory(void)
+{
+	const char *retval = setup_git_directory_1();
+	return retval;
+}
diff --git a/sha1_name.c b/sha1_name.c
index be1755a..faac158 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -236,6 +236,7 @@
 		NULL
 	};
 	const char **p;
+	int found = 0;
 
 	if (len == 40 && !get_sha1_hex(str, sha1))
 		return 0;
@@ -246,10 +247,20 @@
 
 	for (p = prefix; *p; p++) {
 		char *pathname = git_path("%s/%.*s", *p, len, str);
-		if (!read_ref(pathname, sha1))
-			return 0;
+		if (!read_ref(pathname, sha1)) {
+			/* Must be unique; i.e. when heads/foo and
+			 * tags/foo are both present, reject "foo".
+			 * Note that read_ref() eventually calls
+			 * get_sha1_hex() which can smudge initial
+			 * part of the buffer even if what is read
+			 * is found to be invalid halfway.
+			 */
+			if (1 < found++)
+				return -1;
+		}
 	}
-
+	if (found == 1)
+		return 0;
 	return -1;
 }
 
diff --git a/show-branch.c b/show-branch.c
index 631336c..d8808ee 100644
--- a/show-branch.c
+++ b/show-branch.c
@@ -313,9 +313,16 @@
 
 static int append_head_ref(const char *refname, const unsigned char *sha1)
 {
-	if (strncmp(refname, "refs/heads/", 11))
+	unsigned char tmp[20];
+	int ofs = 11;
+	if (strncmp(refname, "refs/heads/", ofs))
 		return 0;
-	return append_ref(refname + 11, sha1);
+	/* If both heads/foo and tags/foo exists, get_sha1 would
+	 * get confused.
+	 */
+	if (get_sha1(refname + ofs, tmp) || memcmp(tmp, sha1, 20))
+		ofs = 5;
+	return append_ref(refname + ofs, sha1);
 }
 
 static int append_tag_ref(const char *refname, const unsigned char *sha1)
@@ -470,7 +477,7 @@
 		if (MAX_REVS <= num_rev)
 			die("cannot handle more than %d revs.", MAX_REVS);
 		if (get_sha1(ref_name[num_rev], revkey))
-			usage(show_branch_usage);
+			die("'%s' is not a valid ref.\n", ref_name[num_rev]);
 		commit = lookup_commit_reference(revkey);
 		if (!commit)
 			die("cannot find commit %s (%s)",
diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh
new file mode 100644
index 0000000..5e994ff
--- /dev/null
+++ b/t/t1300-repo-config.sh
@@ -0,0 +1,271 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Johannes Schindelin
+#
+
+test_description='Test git-repo-config in different settings'
+
+. ./test-lib.sh
+
+test -f .git/config && rm .git/config
+
+git-repo-config core.penguin "little blue"
+
+cat > expect << EOF
+#
+# This is the config file
+#
+
+[core]
+	penguin = little blue
+EOF
+
+test_expect_success 'initial' 'cmp .git/config expect'
+
+git-repo-config Core.Movie BadPhysics
+
+cat > expect << EOF
+#
+# This is the config file
+#
+
+[core]
+	penguin = little blue
+	Movie = BadPhysics
+EOF
+
+test_expect_success 'mixed case' 'cmp .git/config expect'
+
+git-repo-config Cores.WhatEver Second
+
+cat > expect << EOF
+#
+# This is the config file
+#
+
+[core]
+	penguin = little blue
+	Movie = BadPhysics
+[Cores]
+	WhatEver = Second
+EOF
+
+test_expect_success 'similar section' 'cmp .git/config expect'
+
+git-repo-config CORE.UPPERCASE true
+
+cat > expect << EOF
+#
+# This is the config file
+#
+
+[core]
+	penguin = little blue
+	Movie = BadPhysics
+	UPPERCASE = true
+[Cores]
+	WhatEver = Second
+EOF
+
+test_expect_success 'similar section' 'cmp .git/config expect'
+
+test_expect_success 'replace with non-match' \
+	'git-repo-config core.penguin kingpin !blue'
+
+test_expect_success 'replace with non-match (actually matching)' \
+	'git-repo-config core.penguin "very blue" !kingpin'
+
+cat > expect << EOF
+#
+# This is the config file
+#
+
+[core]
+	penguin = very blue
+	Movie = BadPhysics
+	UPPERCASE = true
+	penguin = kingpin
+[Cores]
+	WhatEver = Second
+EOF
+
+test_expect_success 'non-match result' 'cmp .git/config expect'
+
+cat > .git/config << EOF
+[beta] ; silly comment # another comment
+noIndent= sillyValue ; 'nother silly comment
+
+# empty line
+		; comment
+		haha   ="beta" # last silly comment
+haha = hello
+	haha = bello
+[nextSection] noNewline = ouch
+EOF
+
+cp .git/config .git/config2
+
+test_expect_success 'multiple unset' \
+	'git-repo-config --unset-all beta.haha'
+
+cat > expect << EOF
+[beta] ; silly comment # another comment
+noIndent= sillyValue ; 'nother silly comment
+
+# empty line
+		; comment
+[nextSection] noNewline = ouch
+EOF
+
+test_expect_success 'multiple unset is correct' 'cmp .git/config expect'
+
+mv .git/config2 .git/config
+
+test_expect_success '--replace-all' \
+	'git-repo-config --replace-all beta.haha gamma'
+
+cat > expect << EOF
+[beta] ; silly comment # another comment
+noIndent= sillyValue ; 'nother silly comment
+
+# empty line
+		; comment
+	haha = gamma
+[nextSection] noNewline = ouch
+EOF
+
+test_expect_success 'all replaced' 'cmp .git/config expect'
+
+git-repo-config beta.haha alpha
+
+cat > expect << EOF
+[beta] ; silly comment # another comment
+noIndent= sillyValue ; 'nother silly comment
+
+# empty line
+		; comment
+	haha = alpha
+[nextSection] noNewline = ouch
+EOF
+
+test_expect_success 'really mean test' 'cmp .git/config expect'
+
+git-repo-config nextsection.nonewline wow
+
+cat > expect << EOF
+[beta] ; silly comment # another comment
+noIndent= sillyValue ; 'nother silly comment
+
+# empty line
+		; comment
+	haha = alpha
+[nextSection]
+	nonewline = wow
+EOF
+
+test_expect_success 'really really mean test' 'cmp .git/config expect'
+
+test_expect_success 'get value' 'test alpha = $(git-repo-config beta.haha)'
+git-repo-config --unset beta.haha
+
+cat > expect << EOF
+[beta] ; silly comment # another comment
+noIndent= sillyValue ; 'nother silly comment
+
+# empty line
+		; comment
+[nextSection]
+	nonewline = wow
+EOF
+
+test_expect_success 'unset' 'cmp .git/config expect'
+
+git-repo-config nextsection.NoNewLine "wow2 for me" "for me$"
+
+cat > expect << EOF
+[beta] ; silly comment # another comment
+noIndent= sillyValue ; 'nother silly comment
+
+# empty line
+		; comment
+[nextSection]
+	nonewline = wow
+	NoNewLine = wow2 for me
+EOF
+
+test_expect_success 'multivar' 'cmp .git/config expect'
+
+test_expect_success 'non-match' \
+	'git-repo-config --get nextsection.nonewline !for'
+
+test_expect_success 'non-match value' \
+	'test wow = $(git-repo-config --get nextsection.nonewline !for)'
+
+test_expect_failure 'ambiguous get' \
+	'git-repo-config --get nextsection.nonewline'
+
+test_expect_success 'get multivar' \
+	'git-repo-config --get-all nextsection.nonewline'
+
+git-repo-config nextsection.nonewline "wow3" "wow$"
+
+cat > expect << EOF
+[beta] ; silly comment # another comment
+noIndent= sillyValue ; 'nother silly comment
+
+# empty line
+		; comment
+[nextSection]
+	nonewline = wow3
+	NoNewLine = wow2 for me
+EOF
+
+test_expect_success 'multivar replace' 'cmp .git/config expect'
+
+test_expect_failure 'ambiguous value' 'git-repo-config nextsection.nonewline'
+
+test_expect_failure 'ambiguous unset' \
+	'git-repo-config --unset nextsection.nonewline'
+
+test_expect_failure 'invalid unset' \
+	'git-repo-config --unset somesection.nonewline'
+
+git-repo-config --unset nextsection.nonewline "wow3$"
+
+cat > expect << EOF
+[beta] ; silly comment # another comment
+noIndent= sillyValue ; 'nother silly comment
+
+# empty line
+		; comment
+[nextSection]
+	NoNewLine = wow2 for me
+EOF
+
+test_expect_success 'multivar unset' 'cmp .git/config expect'
+
+test_expect_failure 'invalid key' 'git-repo-config inval.2key blabla'
+
+test_expect_success 'correct key' 'git-repo-config 123456.a123 987'
+
+test_expect_success 'hierarchical section' \
+	'git-repo-config 1.2.3.alpha beta'
+
+cat > expect << EOF
+[beta] ; silly comment # another comment
+noIndent= sillyValue ; 'nother silly comment
+
+# empty line
+		; comment
+[nextSection]
+	NoNewLine = wow2 for me
+[123456]
+	a123 = 987
+[1.2.3]
+	alpha = beta
+EOF
+
+test_expect_success 'hierarchical section value' 'cmp .git/config expect'
+
+test_done
+
diff --git a/templates/hooks--update b/templates/hooks--update
index 3f38b82..6db555f 100644
--- a/templates/hooks--update
+++ b/templates/hooks--update
@@ -8,14 +8,14 @@
 # (2) make this file executable by "chmod +x update".
 #
 
-recipient="commit-list@mydomain.xz"
+recipient="commit-list@example.com"
 
 if expr "$2" : '0*$' >/dev/null
 then
 	echo "Created a new ref, with the following commits:"
 	git-rev-list --pretty "$3"
 else
-	$base=$(git-merge-base "$2" "$3")
+	base=$(git-merge-base "$2" "$3")
 	case "$base" in
 	"$2")
 		echo "New commits:"
@@ -24,8 +24,7 @@
 		echo "Rebased ref, commits from common ancestor:"
 		;;
 	esac
-fi
-git-rev-list --pretty "$3" "^$base"
+	git-rev-list --pretty "$3" "^$base"
 fi |
 mail -s "Changes to ref $1" "$recipient"
 exit 0
diff --git a/update-index.c b/update-index.c
index 5bbc3de..11b7f6a 100644
--- a/update-index.c
+++ b/update-index.c
@@ -338,7 +338,7 @@
 	struct strbuf buf;
 	strbuf_init(&buf);
 	while (1) {
-		char *ptr;
+		char *ptr, *tab;
 		char *path_name;
 		unsigned char sha1[20];
 		unsigned int mode;
@@ -348,12 +348,15 @@
 			break;
 
 		mode = strtoul(buf.buf, &ptr, 8);
-		if (ptr == buf.buf || *ptr != ' ' ||
-		    get_sha1_hex(ptr + 1, sha1) ||
-		    ptr[41] != '\t')
+		if (ptr == buf.buf || *ptr != ' ')
 			goto bad_line;
 
-		ptr += 42;
+		tab = strchr(ptr, '\t');
+		if (!tab || tab - ptr < 41)
+			goto bad_line;
+		if (get_sha1_hex(tab - 40, sha1) || tab[-41] != ' ')
+			goto bad_line;
+		ptr = tab + 1;
 
 		if (line_termination && ptr[0] == '"')
 			path_name = unquote_c_style(ptr, NULL);
diff --git a/upload-pack.c b/upload-pack.c
index be63132..1834b6b 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -248,7 +248,7 @@
 
 int main(int argc, char **argv)
 {
-	const char *dir;
+	char *dir;
 	int i;
 	int strict = 0;
 
@@ -275,18 +275,9 @@
 		usage(upload_pack_usage);
 	dir = argv[i];
 
-	/* chdir to the directory. If that fails, try appending ".git" */
-	if (chdir(dir) < 0) {
-		if (strict || chdir(mkpath("%s.git", dir)) < 0)
-			die("git-upload-pack unable to chdir to %s", dir);
-	}
-	if (!strict)
-		chdir(".git");
+	if (!enter_repo(dir, strict))
+		die("'%s': unable to chdir or not a git archive", dir);
 
-	if (access("objects", X_OK) || access("refs", X_OK))
-		die("git-upload-pack: %s doesn't seem to be a git archive", dir);
-
-	putenv("GIT_DIR=.");
 	upload_pack();
 	return 0;
 }
diff --git a/var.c b/var.c
index 51cf86a..59da56d 100644
--- a/var.c
+++ b/var.c
@@ -12,7 +12,7 @@
 
 struct git_var {
 	const char *name;
-	char *(*read)(void);
+	const char *(*read)(void);
 };
 static struct git_var git_vars[] = {
 	{ "GIT_COMMITTER_IDENT", git_committer_info },
@@ -57,6 +57,8 @@
 	if (argc != 2) {
 		usage(var_usage);
 	}
+
+	setup_git_directory();
 	setup_ident();
 	val = NULL;