summary refs log tree commit
diff options
context:
space:
mode:
authorJunio C Hamano <junkio@cox.net>2005-11-25 16:35:20 -0800
committerJunio C Hamano <junkio@cox.net>2005-11-25 16:35:20 -0800
commit93dcab2937624ebb97f91807576cddb242a55a46 (patch)
tree8d16464f16866a7f215401a95f8bdaa124799d49
parent52b6536c624b4998b3730b55b9bc3d7c4e1071a4 (diff)
parent2a1ddc58e4d2220feea7eba4623dac07a0f83f94 (diff)
This is not 1.0rc4 yet, but to push the recent fixes out.

Signed-off-by: Junio C Hamano <junkio@cox.net>
-rw-r--r--.gitignore1
-rw-r--r--Documentation/git-daemon.txt18
-rw-r--r--Documentation/git-repo-config.txt170
-rw-r--r--Documentation/git-reset.txt19
-rw-r--r--Documentation/git.txt3
-rw-r--r--Documentation/howto/rebase-from-internal-branch.txt7
-rw-r--r--Documentation/howto/update-hook-example.txt105
-rw-r--r--Documentation/pull-fetch-param.txt30
-rw-r--r--Documentation/tutorial.txt5
-rw-r--r--Makefile69
-rw-r--r--cache.h8
-rw-r--r--config.c340
-rw-r--r--connect.c168
-rw-r--r--daemon.c142
-rw-r--r--debian/changelog6
-rw-r--r--diff-files.c2
-rw-r--r--diff-index.c2
-rw-r--r--diff-stages.c3
-rw-r--r--diff-tree.c56
-rw-r--r--diff.c39
-rw-r--r--diff.h1
-rw-r--r--diffcore-rename.c3
-rwxr-xr-xgit-am.sh2
-rwxr-xr-xgit-applymbox.sh2
-rwxr-xr-xgit-applypatch.sh2
-rwxr-xr-xgit-bisect.sh2
-rwxr-xr-xgit-branch.sh2
-rwxr-xr-xgit-checkout.sh3
-rwxr-xr-xgit-cherry.sh2
-rwxr-xr-xgit-commit.sh7
-rwxr-xr-xgit-count-objects.sh18
-rwxr-xr-xgit-cvsimport.perl2
-rwxr-xr-xgit-fetch.sh2
-rwxr-xr-xgit-format-patch.sh7
-rwxr-xr-xgit-lost-found.sh2
-rwxr-xr-xgit-ls-remote.sh1
-rwxr-xr-xgit-merge-one-file.sh3
-rwxr-xr-xgit-merge-recursive.py6
-rwxr-xr-xgit-merge.sh22
-rwxr-xr-xgit-mv.perl25
-rwxr-xr-xgit-octopus.sh2
-rwxr-xr-xgit-parse-remote.sh4
-rwxr-xr-xgit-prune.sh2
-rwxr-xr-xgit-pull.sh2
-rwxr-xr-xgit-push.sh2
-rwxr-xr-xgit-rebase.sh2
-rwxr-xr-xgit-repack.sh39
-rwxr-xr-xgit-reset.sh2
-rwxr-xr-xgit-resolve.sh2
-rwxr-xr-xgit-revert.sh14
-rwxr-xr-xgit-sh-setup.sh20
-rwxr-xr-xgit-status.sh2
-rwxr-xr-xgit-tag.sh2
-rwxr-xr-xgit-verify-tag.sh2
-rw-r--r--git.c2
-rwxr-xr-xgitk265
-rw-r--r--http-fetch.c821
-rw-r--r--http-push.c809
-rw-r--r--http.c442
-rw-r--r--http.h95
-rw-r--r--ident.c15
-rw-r--r--name-rev.c2
-rw-r--r--pack-objects.c2
-rw-r--r--pack-redundant.c128
-rw-r--r--path.c115
-rw-r--r--receive-pack.c17
-rw-r--r--refs.c40
-rw-r--r--repo-config.c116
-rw-r--r--rev-list.c5
-rw-r--r--setup.c42
-rw-r--r--sha1_name.c17
-rw-r--r--show-branch.c13
-rw-r--r--t/t1300-repo-config.sh271
-rw-r--r--templates/hooks--update7
-rw-r--r--update-index.c13
-rw-r--r--upload-pack.c15
-rw-r--r--var.c4
77 files changed, 2856 insertions, 1804 deletions
diff --git a/.gitignore b/.gitignore
index 0dd7b9c7b4..8a6bd02d4f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -75,6 +75,7 @@ git-rebase
 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 3783858302..2a8f371ec9 100644
--- a/Documentation/git-daemon.txt
+++ b/Documentation/git-daemon.txt
@@ -8,7 +8,7 @@ git-daemon - A really simple server for git repositories.
 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 @@ This is ideally suited for read-only updates, ie pulling from git repositories.
 
 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 @@ OPTIONS
 --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 0000000000..5eefe02437
--- /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 31ec2076e7..6af3a4fdb9 100644
--- a/Documentation/git-reset.txt
+++ b/Documentation/git-reset.txt
@@ -14,19 +14,30 @@ DESCRIPTION
 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 338e5acb8b..a518249863 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -108,6 +108,9 @@ gitlink:git-prune-packed[1]::
 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 b2c021d917..c2d4a91c7c 100644
--- a/Documentation/howto/rebase-from-internal-branch.txt
+++ b/Documentation/howto/rebase-from-internal-branch.txt
@@ -40,10 +40,7 @@ So I started from master, made a bunch of edits, and committed:
     $ 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 @@ to do cherrypicking using only the core GIT tools.
 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 0000000000..dacaf17c2e
--- /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 ddd5823df7..6413d525ce 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 03eb4216f3..e2dfb00ab1 100644
--- a/Documentation/tutorial.txt
+++ b/Documentation/tutorial.txt
@@ -1534,7 +1534,10 @@ on that project and has an own "public repository" goes like this:
    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 2d8853d69e..adff025a8a 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_PERL = \
 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 @@ PROGRAMS = \
         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 @@ ifndef NO_CURL
                 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 @@ endif
 
 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 @@ $(SIMPLE_PROGRAMS) : git-%$X : %.o
         $(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 @@ check:
 
 ### 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 @@ deb: dist
 ### 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 99afa2c3c4..6ac94c5a1f 100644
--- a/cache.h
+++ b/cache.h
@@ -203,6 +203,7 @@ int git_mkstemp(char *path, size_t n, const char *template);
 
 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 @@ void datestamp(char *buf, int bufsize);
 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_default_config(const char *, const char *);
 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 915bb97523..5cc853508a 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 @@ static int get_base_var(char *name)
                         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 @@ int git_default_config(const char *var, const char *value)
                 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 @@ int git_config(config_fn_t fn)
         }
         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 c2badc71aa..93f6f80d3e 100644
--- a/connect.c
+++ b/connect.c
@@ -427,7 +427,7 @@ static int git_tcp_connect(int fd[2], const char *prog, char *host, char *path)
                 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 @@ static int git_tcp_connect(int fd[2], const char *prog, char *host, char *path)
 
 #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;
-
-        host = NULL;
-        path = url;
-        colon = strchr(url, ':');
-        protocol = PROTO_LOCAL;
-        if (colon) {
-                *colon = 0;
+        enum protocol protocol = PROTO_LOCAL;
+
+        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 2b81152d71..91b96569cd 100644
--- a/daemon.c
+++ b/daemon.c
@@ -15,10 +15,11 @@ static int verbose;
 
 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 @@ static void loginfo(const char *err, ...)
         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;
+
+        loginfo("Request for '%s'", dir);
 
-        if ( chdir(dir) )
+        if (!(path = path_ok(dir)))
                 return -1;
 
         /*
@@ -152,45 +136,17 @@ static int set_dir(const char *dir)
          * 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 @@ static int execute(void)
         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 @@ static int socksetup(int port, int **socklist_p)
                 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 @@ int main(int argc, char **argv)
                         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 @@ int main(int argc, char **argv)
         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 1eda61fe57..7356fe7780 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 17899390b8..38599b5b75 100644
--- a/diff-files.c
+++ b/diff-files.c
@@ -38,7 +38,7 @@ int main(int argc, const char **argv)
         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 c9a9f4c74d..0054883a5e 100644
--- a/diff-index.c
+++ b/diff-index.c
@@ -180,7 +180,7 @@ int main(int argc, const char **argv)
         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 85170b21d6..9968d6ce1c 100644
--- a/diff-stages.c
+++ b/diff-stages.c
@@ -55,6 +55,9 @@ int main(int ac, const char **av)
 {
         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 09d16ad661..d56d921585 100644
--- a/diff-tree.c
+++ b/diff-tree.c
@@ -69,52 +69,50 @@ static int diff_root_tree(const unsigned char *new, const char *base)
         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 @@ static int diff_tree_commit(const unsigned char *commit, const char *name)
                          * don't print the diffs.
                          */
                 }
-                offset += 48;
         }
-        free(buf);
         return 0;
 }
 
@@ -147,7 +143,7 @@ static int diff_tree_stdin(char *line)
                 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 @@ int main(int argc, const char **argv)
         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 @@ int main(int argc, const char **argv)
                         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 0391e8c423..2e0797bf3e 100644
--- a/diff.c
+++ b/diff.c
@@ -15,6 +15,16 @@ static int use_size_cache;
 
 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 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
 
 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 @@ static int parse_num(const char **cp_p)
         /* 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 9b2e1e62bb..32b4780173 100644
--- a/diff.h
+++ b/diff.h
@@ -77,6 +77,7 @@ extern int diff_scoreopt_parse(const char *opt);
 #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 6a9d95d059..dba965c0b4 100644
--- a/diffcore-rename.c
+++ b/diffcore-rename.c
@@ -307,6 +307,9 @@ void diffcore_rename(struct diff_options *options)
         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 8f073c90f6..660b3a4b61 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 6de6932879..24d4a8cb4e 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 66fd19ae2d..f0549960fb 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 1ab2f187dc..d92993b94e 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 2594518e9f..4cd5da16f7 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 4c08f36b59..4cf30e2c05 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 @@ then
                 # 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 aad2e6171f..867522b37f 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 41955e8e64..3d250ec853 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 @@ tt*)
 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 843d2fd9f2..d6e9a3221f 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 efe193439b..08a890c2bb 100755
--- a/git-cvsimport.perl
+++ b/git-cvsimport.perl
@@ -502,7 +502,7 @@ unless($pid) {
         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 6586e773e6..14ea295113 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 7ee5d328c0..bc56876531 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 @@ filelist=$tmp-files
 # 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 @@ do
                 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 3892f52005..9dd7430018 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 f0f0b07f6f..dc6a775a9b 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 b08597de29..c3eca8b332 100755
--- a/git-merge-one-file.sh
+++ b/git-merge-one-file.sh
@@ -25,7 +25,8 @@ case "${1:-.}${2:-.}${3:-.}" in
                 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 d7d36aa7d1..0129233550 100755
--- a/git-merge-recursive.py
+++ b/git-merge-recursive.py
@@ -245,7 +245,7 @@ def updateFileExt(sha, mode, path, updateCache, updateWd):
 
             try:
                 createDir = not stat.S_ISDIR(os.lstat(p).st_mode)
-            except:
+            except OSError:
                 createDir = True
             
             if createDir:
@@ -293,6 +293,10 @@ def removeFile(clean, path):
         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 7f481e4caa..d352a3cf65 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 @@ usage () {
     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 @@ do
         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 @@ case "$#,$common,$no_commit" in
         ;;
 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 a21d87eea8..bf54c38413 100755
--- a/git-mv.perl
+++ b/git-mv.perl
@@ -103,13 +103,22 @@ while(scalar @srcArgs > 0) {
         $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 @@ while(scalar @srcArgs > 0) {
     }
 
     if ($bad eq "") {
-        $safesrc = quotemeta($src);
-        @srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
         if (scalar @srcfiles == 0) {
             $bad = "'$src' not under version control";
         }
@@ -166,10 +173,12 @@ while(scalar @srcs > 0) {
 
     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 d2471af3c8..2edbf52c42 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 aea7b0e549..5f158c613f 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 c4de7f5f25..1fd8c731cd 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 3b875ad438..3a139849fb 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 edc0b8317a..140c8f85d5 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 5289762883..2bc3a12995 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 55a7b27dcd..430ddc5a70 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 @@ case ",$all_into_one," in
         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 @@ exit
 
 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 2086d26d34..72ef303aed 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 7d8fb54f95..fcc5ad7349 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 4154fe0d15..c1aebb159c 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 @@ do
             --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 @@ echo >&2 "Finished one $me."
 
 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 dbb98842bf..b4f10224ba 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 837f334d87..b90ffc198d 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 1375945307..16efc5b70a 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 ed4c893968..3c65f4a6b5 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 bdd3f8d01c..0b10b6e781 100644
--- a/git.c
+++ b/git.c
@@ -273,7 +273,7 @@ int main(int argc, char **argv, char **envp)
                 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 a9d37d9c73..3dd97e291e 100755
--- a/gitk
+++ b/gitk
@@ -60,7 +60,7 @@ proc getcommits {rargs} {
 
 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 @@ proc parsecommit {id contents listed olds} {
             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 parsecommit {id contents listed olds} {
 
 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 @@ Use and redistribute under the terms of the GNU General Public License} \
 }
 
 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 @@ proc bindline {t id} {
     $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 @@ proc drawcommitline {level} {
             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 @@ proc drawcommitline {level} {
     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 decidenext {{noread 0}} {
 }
 
 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 @@ proc drawcommit {id} {
         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
+        }
+        lappend displayorder $id
+        updatetodo $level 0
+    } else {
+        set level [decidenext 1]
+        if {$level == {} || $id != [lindex $todo $level]} {
+            return
         }
-        set id [lindex $todo $level]
-        if {![info exists commitlisted($id)]} {
-            break
+        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 @@ proc drawrest {} {
     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 @@ proc drawrest {} {
                 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 @@ proc selectline {l isnew} {
     $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 @@ proc gettreediffs {ids} {
     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 @@ proc getblobdiffs {ids} {
     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 @@ proc linehover {} {
     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 clickisonarrow {id y} {
 }
 
 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 @@ proc arrowjump {id dirn y} {
         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 @@ proc lineclick {x y id isnew} {
     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 @@ proc lineclick {x y id isnew} {
     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 @@ proc lineclick {x y id isnew} {
             $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 lineclick {x y id isnew} {
 proc normalline {} {
     global thickerline
     if {[info exists thickerline]} {
-        drawlines $thickerline 0
+        drawlines $thickerline 0 1
         unset thickerline
     }
 }
@@ -3650,6 +3635,23 @@ proc doquit {} {
     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 findmergefiles 0
 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 @@ foreach arg $argv {
         "^$" { }
         "^-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 21cc1b960c..435317342b 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 @@ struct alt_base
 
 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 @@ struct transfer_request
         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 @@ struct transfer_request
         int zret;
         int rename;
         struct active_request_slot *slot;
-        struct transfer_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 object_request *next;
 };
 
-struct alt_request {
+struct alternates_request {
         char *base;
         char *url;
         struct buffer *buffer;
@@ -100,120 +57,7 @@ struct alt_request {
         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 @@ static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
         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)
-{
-        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;
+static void process_object_response(void *callback_data);
 
-#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)
+static void start_object_request(struct object_request *obj_req)
 {
-#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 @@ static void start_request(struct transfer_request *request)
         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 @@ static void start_request(struct transfer_request *request)
                                 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 @@ static void start_request(struct transfer_request *request)
         /* 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 @@ static void start_request(struct transfer_request *request)
         }
 
         /* 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;
-        } else {
-                while (entry->next != NULL && entry->next != request)
-                        entry = entry->next;
-                if (entry->next == request)
-                        entry->next = entry->next->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;
+                }
         }
 
-        free(request->url);
-        free(request);
+        finish_object_request(obj_req);
 }
 
-#ifdef USE_CURL_MULTI
-static void process_curl_messages(void)
+static void release_object_request(struct object_request *obj_req)
 {
-        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");
-                        }
+        struct object_request *entry = object_queue_head;
 
-                        /* 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);
+        if (obj_req->local != -1)
+                error("fd leakage in release: %d", obj_req->local);
+        if (obj_req == object_queue_head) {
+                object_queue_head = obj_req->next;
+        } else {
+                while (entry->next != NULL && entry->next != obj_req)
+                        entry = entry->next;
+                if (entry->next == obj_req)
+                        entry->next = entry->next->next;
         }
+
+        free(obj_req->url);
+        free(obj_req);
 }
 
-static void process_request_queue(void)
+#ifdef USE_CURL_MULTI
+void fill_active_slots(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 @@ static void process_request_queue(void)
 
 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 @@ void prefetch(unsigned char *sha1)
                  "%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 @@ static int setup_index(struct alt_base *repo, unsigned char *sha1)
         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 @@ static void process_alternates(void *callback_data)
                                          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 @@ static void process_alternates(void *callback_data)
                 }
         }
 
-        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 @@ static void fetch_alternates(char *base)
         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 @@ static void fetch_alternates(char *base)
         /* Use a callback to process the result, since another request
            may fail and need to have alternates loaded before continuing */
         slot = get_active_slot();
-        slot->callback_func = process_alternates;
+        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 @@ static int fetch_indices(struct alt_base *repo)
 
         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_pack(struct alt_base *repo, unsigned char *sha1)
 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 @@ int main(int argc, char **argv)
         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 @@ int main(int argc, char **argv)
         commit_id = argv[arg];
         url = argv[arg + 1];
 
-        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);
+        http_init();
 
-        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 @@ int main(int argc, char **argv)
         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 8866189332..76c788673e 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 @@ enum XML_Status {
 
 #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
         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 @@ struct active_lock
         int refreshing;
 };
 
-struct lockprop
-{
-        int supported_lock;
-        int lock_entry;
-        int lock_scope;
-        int lock_type;
-        int lock_exclusive;
-        int lock_exclusive_write;
-};
+static void finish_request(struct transfer_request *request);
 
-static int http_options(const char *var, const char *value)
+static void process_response(void *callback_data)
 {
-        if (!strcmp("http.sslverify", var)) {
-                if (curl_ssl_verify == -1) {
-                        curl_ssl_verify = git_config_bool(var, value);
-                }
-                return 0;
-        }
+        struct transfer_request *request =
+                (struct transfer_request *)callback_data;
 
-        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 @@ static void start_check(struct transfer_request *request)
         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 @@ static void start_check(struct transfer_request *request)
         } else {
                 request->state = ABORTED;
                 free(request->url);
+                request->url = NULL;
         }
 }
 
@@ -476,6 +183,8 @@ static void start_mkcol(struct transfer_request *request)
         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 @@ static void start_mkcol(struct transfer_request *request)
         } else {
                 request->state = ABORTED;
                 free(request->url);
+                request->url = NULL;
         }
 }
 
@@ -534,8 +244,6 @@ static void start_put(struct transfer_request *request)
         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 @@ static void start_put(struct transfer_request *request)
         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 @@ static void start_put(struct transfer_request *request)
         } else {
                 request->state = ABORTED;
                 free(request->url);
+                request->url = NULL;
         }
 }
 
@@ -578,6 +289,8 @@ static void start_move(struct transfer_request *request)
         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 @@ static void start_move(struct transfer_request *request)
         } else {
                 request->state = ABORTED;
                 free(request->url);
+                request->url = NULL;
         }
 }
 
@@ -656,6 +370,13 @@ static void finish_request(struct transfer_request *request)
 
         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 @@ static void release_request(struct transfer_request *request)
                         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 @@ static void process_request_queue(void)
                 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 @@ static void add_request(unsigned char *sha1, struct active_lock *lock)
         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 @@ static int fetch_index(unsigned char *sha1)
                 }
         } else {
                 free(url);
+                fclose(indexfile);
                 return error("Unable to start request");
         }
 
@@ -963,8 +630,7 @@ static int fetch_indices(void)
 
         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 @@ int fetch_ref(char *ref, unsigned char *sha1)
         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 @@ int fetch_ref(char *ref, unsigned char *sha1)
         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;
-
-        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;
+        int *lock_flags = (int *)ctx->userData;
+
+        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;
-
-        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;
+        struct active_lock *lock = (struct active_lock *)ctx->userData;
+
+        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);
-
-        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);
+        struct xml_ctx *ctx = (struct xml_ctx *)userData;
+        const char *c = index(name, ':');
+        int new_len;
+
+        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 @@ static struct active_lock *lock_remote(char *file, long timeout)
         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 @@ static struct active_lock *lock_remote(char *file, long timeout)
         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 @@ static struct active_lock *lock_remote(char *file, long timeout)
         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 @@ static struct active_lock *lock_remote(char *file, long timeout)
                         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 @@ static int unlock_remote(struct active_lock *lock)
         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 @@ static int check_locking(void)
         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 @@ static int check_locking(void)
         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 @@ static int check_locking(void)
 
         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);
+
+                        if (result != XML_STATUS_OK) {
+                                fprintf(stderr, "XML error: %s\n",
+                                        XML_ErrorString(
+                                                XML_GetErrorCode(parser)));
+                                lock_flags = 0;
+                        }
                 }
-
-                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)));
         } 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 @@ static int update_remote(unsigned char *sha1, struct active_lock *lock)
 
 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 @@ int main(int argc, char **argv)
         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 @@ int main(int argc, char **argv)
 
         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 @@ int main(int argc, char **argv)
         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 @@ int main(int argc, char **argv)
                         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 @@ int main(int argc, char **argv)
         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 @@ int main(int argc, char **argv)
                 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 0000000000..75e6717a94
--- /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 0000000000..ed4ea3340e
--- /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 bc89e1d04c..ac1c27f199 100644
--- a/ident.c
+++ b/ident.c
@@ -156,7 +156,8 @@ static int copy(char *buf, int size, int offset, const char *src)
         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 @@ char *get_ident(const char *name, const char *email, const char *date_str)
         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 59194f1349..817e36b793 100644
--- a/name-rev.c
+++ b/name-rev.c
@@ -230,8 +230,6 @@ int main(int argc, char **argv)
                                 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 4e941e7392..8864a31cc1 100644
--- a/pack-objects.c
+++ b/pack-objects.c
@@ -524,7 +524,7 @@ int main(int argc, char **argv)
                 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 fb6cb48502..793fa08096 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 @@ struct pll {
         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 @@ struct llist * llist_copy(struct llist *list)
         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 @@ struct llist * llist_copy(struct llist *list)
         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 @@ inline struct llist_item * llist_insert(struct llist *list,
         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 @@ inline struct llist_item * llist_insert_sorted_unique(struct llist *list,
 }
 
 /* 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 @@ redo_from_start:
                                 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 @@ redo_from_start:
 }
 
 /* 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 @@ void llist_sorted_difference_inplace(struct llist *A,
         }
 }
 
-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 @@ inline struct pack_list * pack_list_insert(struct pack_list **pl,
         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 @@ inline size_t pack_list_size(struct pack_list *pl)
         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 @@ struct pack_list * pack_list_difference(struct pack_list *A,
         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 cmp_two_packs(struct pack_list *p1, struct pack_list *p2)
         }
 }
 
-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 @@ void pll_insert(struct pll **pll, struct pll **hint_table)
 /* 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 @@ struct pll * get_all_permutations(struct pack_list *list)
         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 @@ int is_superset(struct pack_list *pl, struct llist *list)
         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 @@ size_t sizeof_union(struct packed_git *p1, struct packed_git *p2)
 }
 
 /* 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 @@ size_t get_pack_redundancy(struct pack_list *pl)
         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 @@ inline size_t pack_set_bytecount(struct pack_list *pl)
         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 minimize(struct pack_list **min)
         }
 }
 
-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 @@ void load_all_objects()
 }
 
 /* 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 cmp_local_packs()
         }
 }
 
-void scan_alt_odb_packs()
+static void scan_alt_odb_packs(void)
 {
         struct pack_list *local, *alt;
 
@@ -524,7 +541,7 @@ void scan_alt_odb_packs()
         }
 }
 
-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 @@ struct pack_list * add_pack(struct packed_git *p)
                 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 @@ struct pack_list * add_pack_file(char *filename)
         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 main(int argc, char **argv)
 {
         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 @@ int main(int argc, char **argv)
         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 @@ int main(int argc, char **argv)
                        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 495d17ca4c..4d889473a7 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 @@ char *safe_strncpy(char *dest, const char *src, size_t n)
 
         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 8f157bc3f0..1873506120 100644
--- a/receive-pack.c
+++ b/receive-pack.c
@@ -248,11 +248,11 @@ static void unpack(void)
 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 @@ int main(int argc, char **argv)
         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 we have a ".git" directory, chdir to it */
-        chdir(".git");
-        putenv("GIT_DIR=.");
+        if(!enter_repo(dir, 0))
+                die("'%s': unable to chdir or not a git archive", 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 f324be5032..ac2619851d 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 0000000000..b2569b7901
--- /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 6e6ffde396..e17f928061 100644
--- a/rev-list.c
+++ b/rev-list.c
@@ -124,8 +124,6 @@ static int filter_commit(struct commit * commit)
                 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 @@ static int process_commit(struct commit * commit)
                 return CONTINUE;
         }
 
+        if (max_count != -1 && !max_count--)
+                return STOP;
+
         show_commit(commit);
 
         return CONTINUE;
diff --git a/setup.c b/setup.c
index c487d7eb9d..ab3c778e80 100644
--- a/setup.c
+++ b/setup.c
@@ -73,8 +73,8 @@ const char **get_pathspec(const char *prefix, const char **pathspec)
 }
 
 /*
- * 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 @@ static int is_toplevel_directory(void)
         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 @@ const char *setup_git_directory(void)
         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 be1755a70b..faac158b16 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -236,6 +236,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
                 NULL
         };
         const char **p;
+        int found = 0;
 
         if (len == 40 && !get_sha1_hex(str, sha1))
                 return 0;
@@ -246,10 +247,20 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
 
         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 631336cd9d..d8808eefce 100644
--- a/show-branch.c
+++ b/show-branch.c
@@ -313,9 +313,16 @@ static int append_ref(const char *refname, const unsigned char *sha1)
 
 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 @@ int main(int ac, char **av)
                 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 0000000000..5e994ff009
--- /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 3f38b82a47..6db555f658 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 @@ else
                 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 5bbc3de289..11b7f6a516 100644
--- a/update-index.c
+++ b/update-index.c
@@ -338,7 +338,7 @@ static void read_index_info(int line_termination)
         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 @@ static void read_index_info(int line_termination)
                         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 be63132804..1834b6ba8c 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -248,7 +248,7 @@ static int upload_pack(void)
 
 int main(int argc, char **argv)
 {
-        const char *dir;
+        char *dir;
         int i;
         int strict = 0;
 
@@ -275,18 +275,9 @@ int main(int argc, char **argv)
                 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 (access("objects", X_OK) || access("refs", X_OK))
-                die("git-upload-pack: %s doesn't seem to be a git archive", dir);
+        if (!enter_repo(dir, strict))
+                die("'%s': unable to chdir or not a git archive", dir);
 
-        putenv("GIT_DIR=.");
         upload_pack();
         return 0;
 }
diff --git a/var.c b/var.c
index 51cf86a584..59da56da0f 100644
--- a/var.c
+++ b/var.c
@@ -12,7 +12,7 @@ static const char var_usage[] = "git-var [-l | <variable>]";
 
 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 @@ int main(int argc, char **argv)
         if (argc != 2) {
                 usage(var_usage);
         }
+
+        setup_git_directory();
         setup_ident();
         val = NULL;