Merge 1.5.0.7 in

Signed-off-by: Junio C Hamano <junkio@cox.net>
diff --git a/.gitignore b/.gitignore
index d99372a..e8d2731 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,7 @@
 git-bisect
 git-blame
 git-branch
+git-bundle
 git-cat-file
 git-check-ref-format
 git-checkout
@@ -33,11 +34,11 @@
 git-diff
 git-diff-files
 git-diff-index
-git-diff-stages
 git-diff-tree
 git-describe
 git-fast-import
 git-fetch
+git-fetch--tool
 git-fetch-pack
 git-findtags
 git-fmt-merge-msg
@@ -75,6 +76,7 @@
 git-merge-recursive
 git-merge-resolve
 git-merge-stupid
+git-mergetool
 git-mktag
 git-mktree
 git-name-rev
@@ -101,7 +103,6 @@
 git-request-pull
 git-rerere
 git-reset
-git-resolve
 git-rev-list
 git-rev-parse
 git-revert
@@ -141,6 +142,7 @@
 git-write-tree
 git-core-*/?*
 gitweb/gitweb.cgi
+test-chmtime
 test-date
 test-delta
 test-dump-cache-tree
diff --git a/.mailmap b/.mailmap
index c7a3a75..3a624ea 100644
--- a/.mailmap
+++ b/.mailmap
@@ -27,6 +27,7 @@
 Ramsay Allan Jones <ramsay@ramsay1.demon.co.uk>
 René Scharfe <rene.scharfe@lsrfire.ath.cx>
 Robert Fitzsimons <robfitz@273k.net>
+Sam Vilain <sam@vilain.net>
 Santi Béjar <sbejar@gmail.com>
 Sean Estabrooks <seanlkml@sympatico.ca>
 Shawn O. Pearce <spearce@spearce.org>
diff --git a/Documentation/RelNotes-1.5.1.txt b/Documentation/RelNotes-1.5.1.txt
new file mode 100644
index 0000000..a5d2dd3
--- /dev/null
+++ b/Documentation/RelNotes-1.5.1.txt
@@ -0,0 +1,204 @@
+GIT v1.5.1 Release Notes
+========================
+
+Updates since v1.5.0
+--------------------
+
+* Deprecated commands and options.
+
+  - git-diff-stages and git-resolve have been removed.
+
+* New commands and options.
+
+  - "git log" and friends take --reverse, which instructs them
+    to give their output in the order opposite from their usual.
+    They typically output from new to old, but with this option
+    their output would read from old to new.  "git shortlog"
+    usually lists older commits first, but with this option,
+    they are shown from new to old.
+
+  - "git log --pretty=format:<string>" to allow more flexible
+    custom log output.
+
+  - "git diff" learned --ignore-space-at-eol.  This is a weaker
+    form of --ignore-space-change.
+
+  - "git diff --no-index pathA pathB" can be used as diff
+    replacement with git specific enhancements.
+
+  - "git diff --no-index" can read from '-' (standard input).
+
+  - "git diff" also learned --exit-code to exit with non-zero
+    status when it found differences.  In the future we might
+    want to make this the default but that would be a rather big
+    backward incompatible change; it will stay as an option for
+    now.
+
+  - "git diff --quiet" is --exit-code with output turned off,
+    meant for scripted use to quickly determine if there is any
+    tree-level difference.
+
+  - Textual patch generation with "git diff" without -w/-b
+    option has been significantly optimized.  "git blame" got
+    faster because of the same change.
+
+  - "git log" and "git rev-list" has been optimized
+    significantly when they are used with pathspecs.
+
+  - "git branch --track" can be used to set up configuration
+    variables to help it easier to base your work on branches
+    you track from a remote site.
+
+  - "git format-patch --attach" now emits attachments.  Use
+    --inline to get an inlined multipart/mixed.
+
+  - "git name-rev" learned --refs=<pattern>, to limit the tags
+    used for naming the given revisions only to the ones
+    matching the given pattern.
+
+  - "git remote update" is to run "git fetch" for defined remotes
+    to update tracking branches.
+
+  - "git cvsimport" can now take '-d' to talk with a CVS
+    repository different from what are recorded in CVS/Root
+    (overriding it with environment CVSROOT does not work).
+
+  - "git bundle" can help sneaker-netting your changes between
+    repositories.
+
+  - "git mergetool" can help 3-way file-level conflict
+    resolution with your favorite graphical merge tools.
+
+  - A new configuration "core.symlinks" can be used to disable
+    symlinks on filesystems that do not support them; they are
+    checked out as regular files instead.
+
+  - You can name a commit object with its first line of the
+    message.  The syntax to use is ':/message text'.  E.g.
+
+    $ git show ":/object name: introduce ':/<oneline prefix>' notation"
+
+    means the same thing as:
+
+    $ git show 28a4d940443806412effa246ecc7768a21553ec7
+
+  - "git bisect" learned a new command "run" that takes a script
+    to run after each revision is checked out to determine if it
+    is good or bad, to automate the bisection process.
+
+  - "git log" family learned a new traversal option --first-parent,
+    which does what the name suggests.
+
+
+* Updated behavior of existing commands.
+
+  - "git-merge-recursive" used to barf when there are more than
+    one common ancestors for the merge, and merging them had a
+    rename/rename conflict.  This has been fixed.
+
+  - "git fsck" does not barf on corrupt loose objects.
+
+  - "git rm" does not remove newly added files without -f.
+
+  - "git archimport" allows remapping when coming up with git
+    branch names from arch names.
+
+  - git-svn got almost a rewrite.
+
+  - core.autocrlf configuration, when set to 'true', makes git
+    to convert CRLF at the end of lines in text files to LF when
+    reading from the filesystem, and convert in reverse when
+    writing to the filesystem.  The variable can be set to
+    'input', in which case the conversion happens only while
+    reading from the filesystem but files are written out with
+    LF at the end of lines.  Currently, which paths to consider
+    'text' (i.e. be subjected to the autocrlf mechanism) is
+    decided purely based on the contents, but the plan is to
+    allow users to explicitly override this heuristic based on
+    paths.
+
+  - The behavior of 'git-apply', when run in a subdirectory,
+    without --index nor --cached were inconsistent with that of
+    the command with these options.  This was fixed to match the
+    behavior with --index.  A patch that is meant to be applied
+    with -p1 from the toplevel of the project tree can be
+    applied with any custom -p<n> option.  A patch that is not
+    relative to the toplevel needs to be applied with -p<n>
+    option with or without --index (or --cached).
+
+  - "git diff" outputs a trailing HT when pathnames have embedded
+    SP on +++/--- header lines, in order to help "GNU patch" to
+    parse its output.  "git apply" was already updated to accept
+    this modified output format since ce74618d (Sep 22, 2006).
+
+  - "git cvsserver" runs hooks/update and honors its exit status.
+
+  - "git cvsserver" can be told to send everything with -kb.
+
+  - "git diff --check" also honors the --color output option.
+
+  - "git name-rev" used to stress the fact that a ref is a tag too
+    much, by saying something like "v1.2.3^0~22".  It now says
+    "v1.2.3~22" in such a case (it still says "v1.2.3^0" if it does
+    not talk about an ancestor of the commit that is tagged, which
+    makes sense).
+
+  - "git rev-list --boundary" now shows boundary markers for the
+    commits omitted by --max-age and --max-count condition.
+
+  - The configuration mechanism now reads $(prefix)/etc/gitconfig.
+
+  - "git apply --verbose" shows what preimage lines were wanted
+    when it couldn't find them.
+
+  - "git status" in a read-only repository got a bit saner.
+
+  - "git fetch" (hence "git clone" and "git pull") are less
+    noisy when the output does not go to tty.
+
+  - "git fetch" between repositories with many refs were slow
+    even when there are not many changes that needed
+    transferring.  This has been sped up by partially rewriting
+    the heaviest parts in C.
+
+  - "git mailinfo" which splits an e-mail into a patch and the
+    meta-information was rewritten, thanks to Don Zickus.  It
+    handles nested multipart better.  The command was broken for
+    a brief period on 'master' branch since 1.5.0 but the
+    breakage is fixed now.
+
+  - send-email learned configurable bcc and chain-reply-to.
+
+  - "git remote show $remote" also talks about branches that
+    would be pushed if you run "git push remote".
+
+  - Using objects from packs is now seriously optimized by clever
+    use of a cache.  This should be most noticeable in git-log
+    family of commands that involve reading many tree objects.
+    In addition, traversing revisions while filtering changes
+    with pathspecs is made faster by terminating the comparison
+    between the trees as early as possible.
+
+
+* Hooks
+
+  - The part to send out notification e-mails was removed from
+    the sample update hook, as it was not an appropriate place
+    to do so.  The proper place to do this is the new post-receive
+    hook.  An example hook has been added to contrib/hooks/.
+
+
+* Others
+
+  - git-revert, git-gc and git-cherry-pick are now built-ins.
+
+
+--
+exec >/var/tmp/1
+O=v1.5.1-rc3-29-gd8b6a1a
+echo O=`git describe master`
+git shortlog --no-merges $O..master ^maint
+
+# Local Variables:
+# mode: text
+# End:
diff --git a/Documentation/cmd-list.perl b/Documentation/cmd-list.perl
index 75f4791..b54382b 100755
--- a/Documentation/cmd-list.perl
+++ b/Documentation/cmd-list.perl
@@ -70,6 +70,7 @@
 git-bisect                              mainporcelain
 git-blame                               ancillaryinterrogators
 git-branch                              mainporcelain
+git-bundle                              mainporcelain
 git-cat-file                            plumbinginterrogators
 git-checkout-index                      plumbingmanipulators
 git-checkout                            mainporcelain
@@ -90,7 +91,6 @@
 git-diff-files                          plumbinginterrogators
 git-diff-index                          plumbinginterrogators
 git-diff                                mainporcelain
-git-diff-stages                         plumbinginterrogators
 git-diff-tree                           plumbinginterrogators
 git-fast-import				ancillarymanipulators
 git-fetch                               mainporcelain
@@ -124,6 +124,7 @@
 git-merge                               mainporcelain
 git-merge-one-file                      purehelpers
 git-merge-tree                          ancillaryinterrogators
+git-mergetool                           ancillarymanipulators
 git-mktag                               plumbingmanipulators
 git-mktree                              plumbingmanipulators
 git-mv                                  mainporcelain
@@ -150,7 +151,6 @@
 git-request-pull                        foreignscminterface
 git-rerere                              ancillaryinterrogators
 git-reset                               mainporcelain
-git-resolve                             mainporcelain
 git-revert                              mainporcelain
 git-rev-list                            plumbinginterrogators
 git-rev-parse                           ancillaryinterrogators
diff --git a/Documentation/config.txt b/Documentation/config.txt
index d9c12f1..cf1e040 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -5,7 +5,8 @@
 the git command's behavior. `.git/config` file for each repository
 is used to store the information for that repository, and
 `$HOME/.gitconfig` is used to store per user information to give
-fallback values for `.git/config` file.
+fallback values for `.git/config` file. The file `/etc/gitconfig`
+can be used to store system-wide defaults.
 
 They can be used by both the git plumbing
 and the porcelains. The variables are divided into sections, where
@@ -116,6 +117,13 @@
 	the working copy are ignored; useful on broken filesystems like FAT.
 	See gitlink:git-update-index[1]. True by default.
 
+core.symlinks::
+	If false, symbolic links are checked out as small plain files that
+	contain the link text. gitlink:git-update-index[1] and
+	gitlink:git-add[1] will not change the recorded type to regular
+	file. Useful on filesystems like FAT that do not support
+	symbolic links. True by default.
+
 core.gitProxy::
 	A "proxy command" to execute (as 'command host port') instead
 	of establishing direct connection to the remote server when
@@ -232,6 +240,19 @@
 +
 Common unit suffixes of 'k', 'm', or 'g' are supported.
 
+core.deltaBaseCacheLimit::
+	Maximum number of bytes to reserve for caching base objects
+	that multiple deltafied objects reference.  By storing the
+	entire decompressed base objects in a cache Git is able
+	to avoid unpacking and decompressing frequently used base
+	objects multiple times.
++
+Default is 16 MiB on all platforms.  This should be reasonable
+for all users/operating systems, except on the largest projects.
+You probably do not need to adjust this value.
++
+Common unit suffixes of 'k', 'm', or 'g' are supported.
+
 alias.*::
 	Command aliases for the gitlink:git[1] command wrapper - e.g.
 	after defining "alias.last = cat-file commit HEAD", the invocation
@@ -264,6 +285,10 @@
 	`git fetch`) to lookup the default branch for merging. Without
 	this option, `git pull` defaults to merge the first refspec fetched.
 	Specify multiple values to get an octopus merge.
+	If you wish to setup `git pull` so that it merges into <name> from
+	another branch in the local repository, you can point
+	branch.<name>.merge to the desired branch, and use the special setting
+	`.` (a period) for branch.<name>.remote.
 
 color.branch::
 	A boolean to enable/disable color in the output of
@@ -445,6 +470,11 @@
 	Whether to include summaries of merged commits in newly created
 	merge commit messages. False by default.
 
+merge.tool::
+	Controls which merge resolution program is used by
+	gitlink:git-mergetool[l].  Valid values are: "kdiff3", "tkdiff",
+	"meld", "xxdiff", "emerge", "vimdiff"
+
 merge.verbosity::
 	Controls the amount of output shown by the recursive merge
 	strategy.  Level 0 outputs nothing except a final error
@@ -475,6 +505,10 @@
 	The default set of "refspec" for gitlink:git-push[1]. See
 	gitlink:git-push[1].
 
+remote.<name>.skipDefaultUpdate::
+	If true, this remote will be skipped by default when updating
+	using the remote subcommand of gitlink:git-remote[1].
+
 remote.<name>.receivepack::
 	The default program to execute on the remote side when pushing.  See
 	option \--exec of gitlink:git-push[1].
@@ -483,6 +517,14 @@
 	The default program to execute on the remote side when fetching.  See
 	option \--exec of gitlink:git-fetch-pack[1].
 
+remote.<name>.tagopt::
+	Setting this value to --no-tags disables automatic tag following when fetching
+	from remote <name>
+
+remotes.<group>::
+	The list of remotes which are fetched by "git remote update
+	<group>".  See gitlink:git-remote[1].
+
 repack.usedeltabaseoffset::
 	Allow gitlink:git-repack[1] to create packs that uses
 	delta-base offset.  Defaults to false.
diff --git a/Documentation/core-intro.txt b/Documentation/core-intro.txt
index 6bee448..eea44d9 100644
--- a/Documentation/core-intro.txt
+++ b/Documentation/core-intro.txt
@@ -588,4 +588,5 @@
 
 	git-merge-index git-merge-one-file hello.c
 
-and that is what higher level `git resolve` is implemented with.
+and that is what higher level `git merge -s resolve` is implemented
+with.
diff --git a/Documentation/core-tutorial.txt b/Documentation/core-tutorial.txt
index 9c28bea..97cdb90 100644
--- a/Documentation/core-tutorial.txt
+++ b/Documentation/core-tutorial.txt
@@ -977,7 +977,7 @@
 Now, let's pretend you are the one who did all the work in
 `mybranch`, and the fruit of your hard work has finally been merged
 to the `master` branch. Let's go back to `mybranch`, and run
-resolve to get the "upstream changes" back to your branch.
+`git merge` to get the "upstream changes" back to your branch.
 
 ------------
 $ git checkout mybranch
@@ -996,7 +996,7 @@
 ----------------
 
 Because your branch did not contain anything more than what are
-already merged into the `master` branch, the resolve operation did
+already merged into the `master` branch, the merge operation did
 not actually do a merge. Instead, it just updated the top of
 the tree of your branch to that of the `master` branch. This is
 often called 'fast forward' merge.
@@ -1099,11 +1099,11 @@
 usefulness when git Native and SSH transports were introduced,
 and not used by `git pull` or `git push` scripts.
 
-Once you fetch from the remote repository, you `resolve` that
+Once you fetch from the remote repository, you `merge` that
 with your current branch.
 
 However -- it's such a common thing to `fetch` and then
-immediately `resolve`, that it's called `git pull`, and you can
+immediately `merge`, that it's called `git pull`, and you can
 simply do
 
 ----------------
diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
index 019a39f..1689c74 100644
--- a/Documentation/diff-options.txt
+++ b/Documentation/diff-options.txt
@@ -140,6 +140,9 @@
 -a::
 	Shorthand for "--text".
 
+--ignore-space-at-eol::
+	Ignore changes in white spaces at EOL.
+
 --ignore-space-change::
 	Ignore changes in amount of white space.  This ignores white
 	space at line end, and consider all other sequences of one or
@@ -156,5 +159,13 @@
 -w::
 	Shorthand for "--ignore-all-space".
 
+--exit-code::
+	Make the program exit with codes similar to diff(1).
+	That is, it exits with 1 if there were differences and
+	0 means no differences.
+
+--quiet::
+	Disable all output of the program. Implies --exit-code.
+
 For more detailed explanation on these common options, see also
 link:diffcore.html[diffcore documentation].
diff --git a/Documentation/diffcore.txt b/Documentation/diffcore.txt
index cb4e562..34cd306 100644
--- a/Documentation/diffcore.txt
+++ b/Documentation/diffcore.txt
@@ -6,8 +6,8 @@
 Introduction
 ------------
 
-The diff commands git-diff-index, git-diff-files, git-diff-tree, and
-git-diff-stages can be told to manipulate differences they find in
+The diff commands git-diff-index, git-diff-files, and git-diff-tree
+can be told to manipulate differences they find in
 unconventional ways before showing diff(1) output.  The manipulation
 is collectively called "diffcore transformation".  This short note
 describes what they are and how to use them to produce diff outputs
@@ -30,9 +30,6 @@
 
  - git-diff-tree compares contents of two "tree" objects;
 
- - git-diff-stages compares contents of blobs at two stages in an
-   unmerged index file.
-
 In all of these cases, the commands themselves compare
 corresponding paths in the two sets of files.  The result of
 comparison is passed from these commands to what is internally
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 13a7389..148ce40 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -87,6 +87,33 @@
 DISCUSSION
 ----------
 
+The commit author name is taken from the "From: " line of the
+message, and commit author time is taken from the "Date: " line
+of the message.  The "Subject: " line is used as the title of
+the commit, after stripping common prefix "[PATCH <anything>]".
+It is supposed to describe what the commit is about concisely as
+a one line text.
+
+The body of the message (iow, after a blank line that terminates
+RFC2822 headers) can begin with "Subject: " and "From: " lines
+that are different from those of the mail header, to override
+the values of these fields.
+
+The commit message is formed by the title taken from the
+"Subject: ", a blank line and the body of the message up to
+where the patch begins.  Excess whitespaces at the end of the
+lines are automatically stripped.
+
+The patch is expected to be inline, directly following the
+message.  Any line that is of form:
+
+* three-dashes and end-of-line, or
+* a line that begins with "diff -", or
+* a line that begins with "Index: "
+
+is taken as the beginning of a patch, and the commit log message
+is terminated before the first occurrence of such a line.
+
 When initially invoking it, you give it names of the mailboxes
 to crunch.  Upon seeing the first patch that does not apply, it
 aborts in the middle, just like 'git-applymbox' does.  You can
diff --git a/Documentation/git-archimport.txt b/Documentation/git-archimport.txt
index 5a13187..82cb41d 100644
--- a/Documentation/git-archimport.txt
+++ b/Documentation/git-archimport.txt
@@ -10,7 +10,7 @@
 --------
 [verse]
 'git-archimport' [-h] [-v] [-o] [-a] [-f] [-T] [-D depth] [-t tempdir]
-               <archive/branch> [ <archive/branch> ]
+               <archive/branch>[:<git-branch>] ...
 
 DESCRIPTION
 -----------
@@ -39,6 +39,19 @@
 `git-archimport` with the same parameters as the initial import to perform 
 incremental imports.
 
+While git-archimport will try to create sensible branch names for the
+archives that it imports, it is also possible to specify git branch names
+manually.  To do so, write a git branch name after each <archive/branch>
+parameter, separated by a colon.  This way, you can shorten the Arch
+branch names and convert Arch jargon to git jargon, for example mapping a
+"PROJECT--devo--VERSION" branch to "master".
+
+Associating multiple Arch branches to one git branch is possible; the
+result will make the most sense only if no commits are made to the first
+branch, after the second branch is created.  Still, this is useful to
+convert Arch repositories that had been rotated periodically.
+
+
 MERGES
 ------
 Patch merge data from Arch is used to mark merges in git as well. git 
@@ -73,7 +86,9 @@
 	Use this for compatibility with old-style branch names used by
 	earlier versions of git-archimport.  Old-style branch names
 	were category--branch, whereas new-style branch names are
-	archive,category--branch--version.
+	archive,category--branch--version.  In both cases, names given
+	on the command-line will override the automatically-generated
+	ones.
 
 -D <depth>::
 	Follow merge ancestry and attempt to import trees that have been
diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt
index 16ec726..b2bc58d 100644
--- a/Documentation/git-bisect.txt
+++ b/Documentation/git-bisect.txt
@@ -12,8 +12,8 @@
 
 DESCRIPTION
 -----------
-The command takes various subcommands, and different options
-depending on the subcommand:
+The command takes various subcommands, and different options depending
+on the subcommand:
 
  git bisect start [<paths>...]
  git bisect bad <rev>
@@ -22,30 +22,34 @@
  git bisect visualize
  git bisect replay <logfile>
  git bisect log
+ git bisect run <cmd>...
 
-This command uses 'git-rev-list --bisect' option to help drive
-the binary search process to find which change introduced a bug,
-given an old "good" commit object name and a later "bad" commit
-object name.
+This command uses 'git-rev-list --bisect' option to help drive the
+binary search process to find which change introduced a bug, given an
+old "good" commit object name and a later "bad" commit object name.
+
+Basic bisect commands: start, bad, good
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 The way you use it is:
 
 ------------------------------------------------
 $ git bisect start
-$ git bisect bad			# Current version is bad
-$ git bisect good v2.6.13-rc2		# v2.6.13-rc2 was the last version
-					# tested that was good
+$ git bisect bad                 # Current version is bad
+$ git bisect good v2.6.13-rc2    # v2.6.13-rc2 was the last version
+                                 # tested that was good
 ------------------------------------------------
 
-When you give at least one bad and one good versions, it will
-bisect the revision tree and say something like:
+When you give at least one bad and one good versions, it will bisect
+the revision tree and say something like:
 
 ------------------------------------------------
 Bisecting: 675 revisions left to test after this
 ------------------------------------------------
 
-and check out the state in the middle. Now, compile that kernel, and boot
-it. Now, let's say that this booted kernel works fine, then just do
+and check out the state in the middle. Now, compile that kernel, and
+boot it. Now, let's say that this booted kernel works fine, then just
+do
 
 ------------------------------------------------
 $ git bisect good			# this one is good
@@ -57,12 +61,15 @@
 Bisecting: 337 revisions left to test after this
 ------------------------------------------------
 
-and you continue along, compiling that one, testing it, and depending on
-whether it is good or bad, you say "git bisect good" or "git bisect bad",
-and ask for the next bisection.
+and you continue along, compiling that one, testing it, and depending
+on whether it is good or bad, you say "git bisect good" or "git bisect
+bad", and ask for the next bisection.
 
-Until you have no more left, and you'll have been left with the first bad
-kernel rev in "refs/bisect/bad".
+Until you have no more left, and you'll have been left with the first
+bad kernel rev in "refs/bisect/bad".
+
+Bisect reset
+~~~~~~~~~~~~
 
 Oh, and then after you want to reset to the original head, do a
 
@@ -70,10 +77,13 @@
 $ git bisect reset
 ------------------------------------------------
 
-to get back to the master branch, instead of being in one of the bisection
-branches ("git bisect start" will do that for you too, actually: it will
-reset the bisection state, and before it does that it checks that you're
-not using some old bisection branch).
+to get back to the master branch, instead of being in one of the
+bisection branches ("git bisect start" will do that for you too,
+actually: it will reset the bisection state, and before it does that
+it checks that you're not using some old bisection branch).
+
+Bisect visualize
+~~~~~~~~~~~~~~~~
 
 During the bisection process, you can say
 
@@ -83,9 +93,17 @@
 
 to see the currently remaining suspects in `gitk`.
 
-The good/bad input is logged, and `git bisect
-log` shows what you have done so far.  You can truncate its
-output somewhere and save it in a file, and run
+Bisect log and bisect replay
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The good/bad input is logged, and
+
+------------
+$ git bisect log
+------------
+
+shows what you have done so far. You can truncate its output somewhere
+and save it in a file, and run
 
 ------------
 $ git bisect replay that-file
@@ -94,12 +112,16 @@
 if you find later you made a mistake telling good/bad about a
 revision.
 
-If in a middle of bisect session, you know what the bisect
-suggested to try next is not a good one to test (e.g. the change
-the commit introduces is known not to work in your environment
-and you know it does not have anything to do with the bug you
-are chasing), you may want to find a near-by commit and try that
-instead.  It goes something like this:
+Avoiding to test a commit
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If in a middle of bisect session, you know what the bisect suggested
+to try next is not a good one to test (e.g. the change the commit
+introduces is known not to work in your environment and you know it
+does not have anything to do with the bug you are chasing), you may
+want to find a near-by commit and try that instead.
+
+It goes something like this:
 
 ------------
 $ git bisect good/bad			# previous round was good/bad.
@@ -109,18 +131,52 @@
 					# was suggested
 ------------
 
-Then compile and test the one you chose to try.  After that,
-tell bisect what the result was as usual.
+Then compile and test the one you chose to try. After that, tell
+bisect what the result was as usual.
 
-You can further cut down the number of trials if you know what
-part of the tree is involved in the problem you are tracking
-down, by giving paths parameters when you say `bisect start`,
-like this:
+Cutting down bisection by giving path parameter to bisect start
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can further cut down the number of trials if you know what part of
+the tree is involved in the problem you are tracking down, by giving
+paths parameters when you say `bisect start`, like this:
 
 ------------
 $ git bisect start arch/i386 include/asm-i386
 ------------
 
+Bisect run
+~~~~~~~~~~
+
+If you have a script that can tell if the current source code is good
+or bad, you can automatically bisect using:
+
+------------
+$ git bisect run my_script
+------------
+
+Note that the "run" script (`my_script` in the above example) should
+exit with code 0 in case the current source code is good and with a
+code between 1 and 127 (included) in case the current source code is
+bad.
+
+Any other exit code will abort the automatic bisect process. (A
+program that does "exit(-1)" leaves $? = 255, see exit(3) manual page,
+the value is chopped with "& 0377".)
+
+You may often find that during bisect you want to have near-constant
+tweaks (e.g., s/#define DEBUG 0/#define DEBUG 1/ in a header file, or
+"revision that does not have this commit needs this patch applied to
+work around other problem this bisection is not interested in")
+applied to the revision being tested.
+
+To cope with such a situation, after the inner git-bisect finds the
+next revision to test, with the "run" script, you can apply that tweak
+before compiling, run the real test, and after the test decides if the
+revision (possibly with the needed tweaks) passed the test, rewind the
+tree to the pristine state.  Finally the "run" script can exit with
+the status of the real test to let "git bisect run" command loop to
+know the outcome.
 
 Author
 ------
diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index aa1fdd4..603f87f 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -8,8 +8,9 @@
 SYNOPSIS
 --------
 [verse]
-'git-branch' [--color | --no-color] [-r | -a] [-v [--abbrev=<length>]]
-'git-branch' [-l] [-f] <branchname> [<start-point>]
+'git-branch' [--color | --no-color] [-r | -a]
+	   [-v [--abbrev=<length> | --no-abbrev]]
+'git-branch' [--track | --no-track] [-l] [-f] <branchname> [<start-point>]
 'git-branch' (-m | -M) [<oldbranch>] <newbranch>
 'git-branch' (-d | -D) [-r] <branchname>...
 
@@ -25,6 +26,13 @@
 If no <start-point> is given, the branch will be created with a head
 equal to that of the currently checked out branch.
 
+When a local branch is started off a remote branch, git can setup the
+branch so that gitlink:git-pull[1] will appropriately merge from that
+remote branch.  If this behavior is desired, it is possible to make it
+the default using the global `branch.autosetupmerge` configuration
+flag.  Otherwise, it can be chosen per-branch using the `--track`
+and `--no-track` options.
+
 With a '-m' or '-M' option, <oldbranch> will be renamed to <newbranch>.
 If <oldbranch> had a corresponding reflog, it is renamed to match
 <newbranch>, and a reflog entry is created to remember the branch
@@ -80,6 +88,9 @@
 	Alter minimum display length for sha1 in output listing,
 	default value is 7.
 
+--no-abbrev::
+	Display the full sha1s in output listing rather than abbreviating them.
+
 <branchname>::
 	The name of the branch to create or delete.
 	The new branch name must pass all checks defined by
diff --git a/Documentation/git-bundle.txt b/Documentation/git-bundle.txt
new file mode 100644
index 0000000..92e7a68
--- /dev/null
+++ b/Documentation/git-bundle.txt
@@ -0,0 +1,139 @@
+git-bundle(1)
+=============
+
+NAME
+----
+git-bundle - Move objects and refs by archive
+
+
+SYNOPSIS
+--------
+'git-bundle' create <file> [git-rev-list args]
+'git-bundle' verify <file>
+'git-bundle' list-heads <file> [refname...]
+'git-bundle' unbundle <file> [refname...]
+
+DESCRIPTION
+-----------
+
+Some workflows require that one or more branches of development on one
+machine be replicated on another machine, but the two machines cannot
+be directly connected so the interactive git protocols (git, ssh,
+rsync, http) cannot be used.  This command provides support for
+git-fetch and git-pull to operate by packaging objects and references
+in an archive at the originating machine, then importing those into
+another repository using gitlink:git-fetch[1] and gitlink:git-pull[1]
+after moving the archive by some means (i.e., by sneakernet).  As no
+direct connection between repositories exists, the user must specify a
+basis for the bundle that is held by the destination repository: the
+bundle assumes that all objects in the basis are already in the
+destination repository.
+
+OPTIONS
+-------
+
+create <file>::
+       Used to create a bundle named 'file'.  This requires the
+       git-rev-list arguments to define the bundle contents.
+
+verify <file>::
+       Used to check that a bundle file is valid and will apply
+       cleanly to the current repository.  This includes checks on the
+       bundle format itself as well as checking that the prerequisite
+       commits exist and are fully linked in the current repository.
+       git-bundle prints a list of missing commits, if any, and exits
+       with non-zero status.
+
+list-heads <file>::
+       Lists the references defined in the bundle.  If followed by a
+       list of references, only references matching those given are
+       printed out.
+
+unbundle <file>::
+       Passes the objects in the bundle to gitlink:git-index-pack[1]
+       for storage in the repository, then prints the names of all
+       defined references. If a reflist is given, only references
+       matching those in the given list are printed. This command is
+       really plumbing, intended to be called only by
+       gitlink:git-fetch[1].
+
+[git-rev-list-args...]::
+       A list of arguments, acceptable to git-rev-parse and
+       git-rev-list, that specify the specific objects and references
+       to transport.  For example, "master~10..master" causes the
+       current master reference to be packaged along with all objects
+       added since its 10th ancestor commit.  There is no explicit
+       limit to the number of references and objects that may be
+       packaged.
+
+
+[refname...]::
+       A list of references used to limit the references reported as
+       available. This is principally of use to git-fetch, which
+       expects to receive only those references asked for and not
+       necessarily everything in the pack (in this case, git-bundle is
+       acting like gitlink:git-fetch-pack[1]).
+
+SPECIFYING REFERENCES
+---------------------
+
+git-bundle will only package references that are shown by
+git-show-ref: this includes heads, tags, and remote heads.  References
+such as master~1 cannot be packaged, but are perfectly suitable for
+defining the basis.  More than one reference may be packaged, and more
+than one basis can be specified.  The objects packaged are those not
+contained in the union of the given bases.  Each basis can be
+specified explicitly (e.g., ^master~10), or implicitly (e.g.,
+master~10..master, master --since=10.days.ago).
+
+It is very important that the basis used be held by the destination.
+It is okay to err on the side of conservatism, causing the bundle file
+to contain objects already in the destination as these are ignored
+when unpacking at the destination.
+
+EXAMPLE
+-------
+
+Assume two repositories exist as R1 on machine A, and R2 on machine B.
+For whatever reason, direct connection between A and B is not allowed,
+but we can move data from A to B via some mechanism (CD, email, etc).
+We want to update R2 with developments made on branch master in R1.
+We set a tag in R1 (lastR2bundle) after the previous such transport,
+and move it afterwards to help build the bundle.
+
+in R1 on A:
+$ git-bundle create mybundle master ^lastR2bundle
+$ git tag -f lastR2bundle master
+
+(move mybundle from A to B by some mechanism)
+
+in R2 on B:
+$ git-bundle verify mybundle
+$ git-fetch mybundle  refspec
+
+where refspec is refInBundle:localRef
+
+
+Also, with something like this in your config:
+
+[remote "bundle"]
+    url = /home/me/tmp/file.bdl
+    fetch = refs/heads/*:refs/remotes/origin/*
+
+You can first sneakernet the bundle file to ~/tmp/file.bdl and
+then these commands:
+
+$ git ls-remote bundle
+$ git fetch bundle
+$ git pull bundle
+
+would treat it as if it is talking with a remote side over the
+network.
+
+Author
+------
+Written by Mark Levedahl <mdl123@verizon.net>
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 1ae77be..f5b2d50 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -8,7 +8,7 @@
 SYNOPSIS
 --------
 [verse]
-'git-checkout' [-q] [-f] [-b <new_branch> [-l]] [-m] [<branch>]
+'git-checkout' [-q] [-f] [-b [--track | --no-track] <new_branch> [-l]] [-m] [<branch>]
 'git-checkout' [<tree-ish>] <paths>...
 
 DESCRIPTION
@@ -18,7 +18,8 @@
 updating the index and working tree to reflect the specified
 branch, <branch>, and updating HEAD to be <branch> or, if
 specified, <new_branch>.  Using -b will cause <new_branch> to
-be created.
+be created; in this case you can use the --track or --no-track
+options, which will be passed to `git branch`.
 
 When <paths> are given, this command does *not* switch
 branches.  It updates the named paths in the working tree from
@@ -45,6 +46,16 @@
 	by gitlink:git-check-ref-format[1].  Some of these checks
 	may restrict the characters allowed in a branch name.
 
+--track::
+	When -b is given and a branch is created off a remote branch,
+	setup so that git-pull will automatically retrieve data from
+	the remote branch.
+
+--no-track::
+	When -b is given and a branch is created off a remote branch,
+	force that git-pull will automatically retrieve data from
+	the remote branch independent of the configuration settings.
+
 -l::
 	Create the new branch's ref log.  This activates recording of
 	all changes to made the branch ref, enabling use of date
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index 2187eee..53a7bb0 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -8,8 +8,9 @@
 SYNOPSIS
 --------
 [verse]
-'git-commit' [-a] [-s] [-v] [(-c | -C) <commit> | -F <file> | -m <msg> |
-	    --amend] [--no-verify] [-e] [--author <author>]
+'git-commit' [-a | --interactive] [-s] [-v]
+	   [(-c | -C) <commit> | -F <file> | -m <msg> | --amend]
+	   [--no-verify] [-e] [--author <author>]
 	   [--] [[-i | -o ]<file>...]
 
 DESCRIPTION
@@ -35,6 +36,10 @@
    before, and to automatically "rm" files that have been
    removed from the working tree, and perform the actual commit.
 
+5. by using the --interactive switch with the 'commit' command to decide one
+   by one which files should be part of the commit, before finalizing the
+   operation.  Currently, this is done by invoking `git-add --interactive`.
+
 The gitlink:git-status[1] command can be used to obtain a
 summary of what is included by any of the above for the next
 commit by giving the same set of parameters you would give to
diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt
index ccb8b36..c759efb 100644
--- a/Documentation/git-config.txt
+++ b/Documentation/git-config.txt
@@ -16,6 +16,8 @@
 'git-config' [--global] [type] --get-all name [value_regex]
 'git-config' [--global] [type] --unset name [value_regex]
 'git-config' [--global] [type] --unset-all name [value_regex]
+'git-config' [--global] [type] --rename-section old_name new_name
+'git-config' [--global] [type] --remove-section name
 'git-config' [--global] -l | --list
 
 DESCRIPTION
@@ -74,6 +76,12 @@
 --global::
 	Use global ~/.gitconfig file rather than the repository .git/config.
 
+--remove-section::
+	Remove the given section from the configuration file.
+
+--rename-section::
+	Rename the given section to a new name.
+
 --unset::
 	Remove the line matching the key from config file.
 
diff --git a/Documentation/git-cvsexportcommit.txt b/Documentation/git-cvsexportcommit.txt
index 27d531b..555b823 100644
--- a/Documentation/git-cvsexportcommit.txt
+++ b/Documentation/git-cvsexportcommit.txt
@@ -8,7 +8,7 @@
 
 SYNOPSIS
 --------
-'git-cvsexportcommit' [-h] [-v] [-c] [-P] [-p] [-a] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID
+'git-cvsexportcommit' [-h] [-v] [-c] [-P] [-p] [-a] [-d cvsroot] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID
 
 
 DESCRIPTION
@@ -43,6 +43,11 @@
 	Add authorship information. Adds Author line, and Committer (if
 	different from Author) to the message.
 
+-d::
+	Set an alternative CVSROOT to use.  This corresponds to the CVS
+	-d parameter.  Usually users will not want to set this, except
+	if using CVS in an asymmetric fashion.
+
 -f::
 	Force the merge even if the files are not up to date.
 
diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt
index 1c6f6a7..85d0950 100644
--- a/Documentation/git-cvsserver.txt
+++ b/Documentation/git-cvsserver.txt
@@ -134,9 +134,11 @@
 Legacy monitoring operations are not supported (edit, watch and related).
 Exports and tagging (tags and branches) are not supported at this stage.
 
-The server will set the -k mode to binary when relevant. In proper GIT
-tradition, the contents of the files are always respected.
-No keyword expansion or newline munging is supported.
+The server should set the -k mode to binary when relevant, however,
+this is not really implemented yet. For now, you can force the server
+to set `-kb` for all files by setting the `gitcvs.allbinary` config
+variable. In proper GIT tradition, the contents of the files are
+always respected. No keyword expansion or newline munging is supported.
 
 Dependencies
 ------------
diff --git a/Documentation/git-diff-files.txt b/Documentation/git-diff-files.txt
index 7248b35..b78c4c6 100644
--- a/Documentation/git-diff-files.txt
+++ b/Documentation/git-diff-files.txt
@@ -8,7 +8,7 @@
 
 SYNOPSIS
 --------
-'git-diff-files' [-q] [-0|-1|-2|-3|-c|--cc] [<common diff options>] [<path>...]
+'git-diff-files' [-q] [-0|-1|-2|-3|-c|--cc|-n|--no-index] [<common diff options>] [<path>...]
 
 DESCRIPTION
 -----------
@@ -36,6 +36,9 @@
 	diff, similar to the way 'diff-tree' shows a merge
 	commit with these flags.
 
+\-n,\--no-index::
+	Compare the two given files / directories.
+
 -q::
 	Remain silent even on nonexistent files
 
diff --git a/Documentation/git-diff-stages.txt b/Documentation/git-diff-stages.txt
deleted file mode 100644
index b8f45b8..0000000
--- a/Documentation/git-diff-stages.txt
+++ /dev/null
@@ -1,42 +0,0 @@
-git-diff-stages(1)
-==================
-
-NAME
-----
-git-diff-stages - Compares two merge stages in the index
-
-
-SYNOPSIS
---------
-'git-diff-stages' [<common diff options>] <stage1> <stage2> [<path>...]
-
-DESCRIPTION
------------
-DEPRECATED and will be removed in 1.5.1.
-
-Compares the content and mode of the blobs in two stages in an
-unmerged index file.
-
-OPTIONS
--------
-include::diff-options.txt[]
-
-<stage1>,<stage2>::
-	The stage number to be compared.
-
-Output format
--------------
-include::diff-format.txt[]
-
-
-Author
-------
-Written by Junio C Hamano <junkio@cox.net>
-
-Documentation
---------------
-Documentation by Junio C Hamano.
-
-GIT
----
-Part of the gitlink:git[7] suite
diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt
index b88764f..044cee9 100644
--- a/Documentation/git-diff.txt
+++ b/Documentation/git-diff.txt
@@ -23,6 +23,10 @@
 	further add to the index but you still haven't.  You can
 	stage these changes by using gitlink:git-add[1].
 
+	If exactly two paths are given, and at least one is untracked,
+	compare the two files / directories. This behavior can be
+	forced by --no-index.
+
 'git-diff' [--options] --cached [<commit>] [--] [<path>...]::
 
 	This form is to view the changes you staged for the next
diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt
index a7d255d..eaba6fd 100644
--- a/Documentation/git-fast-import.txt
+++ b/Documentation/git-fast-import.txt
@@ -62,7 +62,18 @@
 	Dumps the internal marks table to <file> when complete.
 	Marks are written one per line as `:markid SHA-1`.
 	Frontends can use this file to validate imports after they
-	have been completed.
+	have been completed, or to save the marks table across
+	incremental runs.  As <file> is only opened and truncated
+	at checkpoint (or completion) the same path can also be
+	safely given to \--import-marks.
+
+--import-marks=<file>::
+	Before processing any input, load the marks specified in
+	<file>.  The input file must exist, must be readable, and
+	must use the same format as produced by \--export-marks.
+	Multiple options may be supplied to import more than one
+	set of marks.  If a mark is defined to different values,
+	the last file wins.
 
 --export-pack-edges=<file>::
 	After creating a packfile, print a line of data to
diff --git a/Documentation/git-fetch-pack.txt b/Documentation/git-fetch-pack.txt
index 105d76b..a99a5b3 100644
--- a/Documentation/git-fetch-pack.txt
+++ b/Documentation/git-fetch-pack.txt
@@ -8,7 +8,7 @@
 
 SYNOPSIS
 --------
-'git-fetch-pack' [--all] [--quiet|-q] [--keep|-k] [--thin] [--upload-pack=<git-upload-pack>] [--depth=<n>] [-v] [<host>:]<directory> [<refs>...]
+'git-fetch-pack' [--all] [--quiet|-q] [--keep|-k] [--thin] [--upload-pack=<git-upload-pack>] [--depth=<n>] [--no-progress] [-v] [<host>:]<directory> [<refs>...]
 
 DESCRIPTION
 -----------
@@ -63,6 +63,9 @@
 \--depth=<n>::
 	Limit fetching to ancestor-chains not longer than n.
 
+\--no-progress::
+	Do not show the progress.
+
 \-v::
 	Run verbosely.
 
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 84eabeb..111d7c6 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -9,8 +9,9 @@
 SYNOPSIS
 --------
 [verse]
-'git-format-patch' [<common diff options>] [-n | -k] [-o <dir> | --stdout]
-		   [--attach] [--thread] [-s | --signoff] [--start-number <n>]
+'git-format-patch' [-n | -k] [-o <dir> | --stdout] [--thread]
+	           [--attach[=<boundary>] | --inline[=<boundary>]]
+	           [-s | --signoff] [<common diff options>] [--start-number <n>]
 		   [--in-reply-to=Message-Id] [--suffix=.<sfx>]
 		   [--ignore-if-in-upstream]
 		   <since>[..<until>]
@@ -70,8 +71,15 @@
 	Print all commits to the standard output in mbox format,
 	instead of creating a file for each one.
 
---attach::
-	Create attachments instead of inlining patches.
+--attach[=<boundary>]::
+	Create multipart/mixed attachment, the first part of
+	which is the commit message and the patch itself in the
+	second part, with "Content-Disposition: attachment".
+
+--inline[=<boundary>]::
+	Create multipart/mixed attachment, the first part of
+	which is the commit message and the patch itself in the
+	second part, with "Content-Disposition: inline".
 
 --thread::
 	Add In-Reply-To and References headers to make the second and
diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt
index 361eaec..030edaf 100644
--- a/Documentation/git-log.txt
+++ b/Documentation/git-log.txt
@@ -38,6 +38,11 @@
 	and <until>, see "SPECIFYING REVISIONS" section in
 	gitlink:git-rev-parse[1].
 
+--first-parent::
+	Follow only the first parent commit upon seeing a merge
+	commit.  This  option gives a better overview of the
+	evolution of a particular branch.
+
 -p::
 	Show the change the commit introduces in a patch form.
 
diff --git a/Documentation/git-mergetool.txt b/Documentation/git-mergetool.txt
new file mode 100644
index 0000000..34288fe
--- /dev/null
+++ b/Documentation/git-mergetool.txt
@@ -0,0 +1,46 @@
+git-mergetool(1)
+================
+
+NAME
+----
+git-mergetool - Run merge conflict resolution tools to resolve merge conflicts
+
+SYNOPSIS
+--------
+'git-mergetool' [--tool=<tool>] [<file>]...
+
+DESCRIPTION
+-----------
+
+Use 'git mergetool' to run one of several merge utilities to resolve
+merge conflicts.  It is typically run after gitlink:git-merge[1].
+
+If one or more <file> parameters are given, the merge tool program will
+be run to resolve differences on each file.  If no <file> names are
+specified, 'git mergetool' will run the merge tool program on every file
+with merge conflicts.
+
+OPTIONS
+-------
+-t or --tool=<tool>::
+	Use the merge resolution program specified by <tool>.
+	Valid merge tools are:
+	kdiff3, tkdiff, meld, xxdiff, emerge, and vimdiff.
++
+If a merge resolution program is not specified, 'git mergetool'
+will use the configuration variable merge.tool.  If the
+configuration variable merge.tool is not set, 'git mergetool'
+will pick a suitable default.
+
+Author
+------
+Written by Theodore Y Ts'o <tytso@mit.edu>
+
+Documentation
+--------------
+Documentation by Theodore Y Ts'o.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-name-rev.txt b/Documentation/git-name-rev.txt
index 37fbf66..5b5c4c8 100644
--- a/Documentation/git-name-rev.txt
+++ b/Documentation/git-name-rev.txt
@@ -8,7 +8,8 @@
 
 SYNOPSIS
 --------
-'git-name-rev' [--tags] ( --all | --stdin | <committish>... )
+'git-name-rev' [--tags] [--refs=<pattern>]
+	       ( --all | --stdin | <committish>... )
 
 DESCRIPTION
 -----------
@@ -22,6 +23,9 @@
 --tags::
 	Do not use branch names, but only tags to name the commits
 
+--refs=<pattern>::
+	Only use refs whose names match a given shell pattern.
+
 --all::
 	List all commits reachable from all refs
 
diff --git a/Documentation/git-receive-pack.txt b/Documentation/git-receive-pack.txt
index 10e8c46..6914aa5 100644
--- a/Documentation/git-receive-pack.txt
+++ b/Documentation/git-receive-pack.txt
@@ -25,62 +25,127 @@
 local end receive-pack runs, but to the user who is sitting at
 the send-pack end, it is updating the remote.  Confused?)
 
-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.
-
-The hook should exit with non-zero status if it wants to
-disallow updating the named ref.  Otherwise it should exit with
-zero.
-
-Using this hook, it is easy to generate mails on updates to
-the local repository. This example script sends a mail with
-the commits pushed to the repository:
-
-	#!/bin/sh
-	# mail out commit update information.
-	if expr "$2" : '0*$' >/dev/null
-	then
-		echo "Created a new ref, with the following commits:"
-		git-rev-list --pretty "$2"
-	else
-		echo "New commits:"
-		git-rev-list --pretty "$3" "^$2"
-	fi |
-	mail -s "Changes to ref $1" commit-list@mydomain
-	exit 0
-
-Another hook $GIT_DIR/hooks/post-update, if exists and
-executable, is called with the list of refs that have been
-updated.  This can be used to implement repository wide cleanup
-task if needed.  The exit code from this hook invocation is
-ignored; the only thing left for git-receive-pack to do at that
-point is to exit itself anyway.  This hook can be used, for
-example, to run "git-update-server-info" if the repository is
-packed and is served via a dumb transport.
-
-	#!/bin/sh
-	exec git-update-server-info
-
 There are other real-world examples of using update and
 post-update hooks found in the Documentation/howto directory.
 
-git-receive-pack honours the receive.denyNonFastforwards flag, which
-tells it if updates to a ref should be denied if they are not fast-forwards.
+git-receive-pack honours the receive.denyNonFastForwards config
+option, which tells it if updates to a ref should be denied if they
+are not fast-forwards.
 
 OPTIONS
 -------
 <directory>::
 	The repository to sync into.
 
+pre-receive Hook
+----------------
+Before any ref is updated, if $GIT_DIR/hooks/pre-receive file exists
+and is executable, it will be invoked once with no parameters.  The
+standard input of the hook will be one line per ref to be updated:
+
+       sha1-old SP sha1-new SP refname LF
+
+The refname value is relative to $GIT_DIR; e.g. for the master
+head this is "refs/heads/master".  The two sha1 values before
+each refname are the object names for the refname before and after
+the update.  Refs to be created will have sha1-old equal to 0{40},
+while refs to be deleted will have sha1-new equal to 0{40}, otherwise
+sha1-old and sha1-new should be valid objects in the repository.
+
+This hook is called before any refname is updated and before any
+fast-forward checks are performed.
+
+If the pre-receive hook exits with a non-zero exit status no updates
+will be performed, and the update, post-receive and post-update
+hooks will not be invoked either.  This can be useful to quickly
+bail out if the update is not to be supported.
+
+update Hook
+-----------
+Before each ref is updated, if $GIT_DIR/hooks/update file exists
+and is executable, it is invoked once per ref, 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".  The two sha1 arguments 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.
+
+The hook should exit with non-zero status if it wants to disallow
+updating the named ref.  Otherwise it should exit with zero.
+
+Successful execution (a zero exit status) of this hook does not
+ensure the ref will actully be updated, it is only a prerequisite.
+As such it is not a good idea to send notices (e.g. email) from
+this hook.  Consider using the post-receive hook instead.
+
+post-receive Hook
+-----------------
+After all refs were updated (or attempted to be updated), if any
+ref update was successful, and if $GIT_DIR/hooks/post-receive
+file exists and is executable, it will be invoke once with no
+parameters.  The standard input of the hook will be one line
+for each successfully updated ref:
+
+       sha1-old SP sha1-new SP refname LF
+
+The refname value is relative to $GIT_DIR; e.g. for the master
+head this is "refs/heads/master".  The two sha1 values before
+each refname are the object names for the refname before and after
+the update.  Refs that were created will have sha1-old equal to
+0{40}, while refs that were deleted will have sha1-new equal to
+0{40}, otherwise sha1-old and sha1-new should be valid objects in
+the repository.
+
+Using this hook, it is easy to generate mails describing the updates
+to the repository.  This example script sends one mail message per
+ref listing the commits pushed to the repository:
+
+	#!/bin/sh
+	# mail out commit update information.
+	while read oval nval ref
+	do
+		if expr "$oval" : '0*$' >/dev/null
+		then
+			echo "Created a new ref, with the following commits:"
+			git-rev-list --pretty "$nval"
+		else
+			echo "New commits:"
+			git-rev-list --pretty "$nval" "^$oval"
+		fi |
+		mail -s "Changes to ref $ref" commit-list@mydomain
+	done
+	exit 0
+
+The exit code from this hook invocation is ignored, however a
+non-zero exit code will generate an error message.
+
+Note that it is possible for refname to not have sha1-new when this
+hook runs.  This can easily occur if another user modifies the ref
+after it was updated by receive-pack, but before the hook was able
+to evaluate it.  It is recommended that hooks rely on sha1-new
+rather than the current value of refname.
+
+post-update Hook
+----------------
+After all other processing, if at least one ref was updated, and
+if $GIT_DIR/hooks/post-update file exists and is executable, then
+post-update will called with the list of refs that have been updated.
+This can be used to implement any repository wide cleanup tasks.
+
+The exit code from this hook invocation is ignored; the only thing
+left for git-receive-pack to do at that point is to exit itself
+anyway.
+
+This hook can be used, for example, to run "git-update-server-info"
+if the repository is packed and is served via a dumb transport.
+
+	#!/bin/sh
+	exec git-update-server-info
+
 
 SEE ALSO
 --------
diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt
index f96b304..a9fb6a9 100644
--- a/Documentation/git-remote.txt
+++ b/Documentation/git-remote.txt
@@ -13,6 +13,7 @@
 'git-remote' add [-t <branch>] [-m <branch>] [-f] <name> <url>
 'git-remote' show <name>
 'git-remote' prune <name>
+'git-remote' update [group]
 
 DESCRIPTION
 -----------
@@ -53,7 +54,17 @@
 
 Deletes all stale tracking branches under <name>.
 These stale branches have already been removed from the remote repository
-referenced by <name>, but are still locally available in "remotes/<name>".
+referenced by <name>, but are still locally available in
+"remotes/<name>".
+
+'update'::
+
+Fetch updates for a named set of remotes in the repository as defined by
+remotes.<group>.  If a named group is not specified on the command line,
+the configuration parameter remotes.default will get used; if
+remotes.default is not defined, all remotes which do not the
+configuration parameter remote.<name>.skipDefaultUpdate set to true will
+be updated.  (See gitlink:git-config[1]).
 
 
 DISCUSSION
diff --git a/Documentation/git-resolve.txt b/Documentation/git-resolve.txt
deleted file mode 100644
index 7fde665..0000000
--- a/Documentation/git-resolve.txt
+++ /dev/null
@@ -1,38 +0,0 @@
-git-resolve(1)
-==============
-
-NAME
-----
-git-resolve - Merge two commits
-
-
-SYNOPSIS
---------
-'git-resolve' <current> <merged> <message>
-
-DESCRIPTION
------------
-DEPRECATED and will be removed in 1.5.1.  Use `git-merge` instead.
-
-Given two commits and a merge message, merge the <merged> commit
-into <current> commit, with the commit log message <message>.
-
-When <current> is a descendant of <merged>, or <current> is an
-ancestor of <merged>, no new commit is created and the <message>
-is ignored.  The former is informally called "already up to
-date", and the latter is often called "fast forward".
-
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org> and
-Dan Holmsand <holmsand@gmail.com>.
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
-GIT
----
-Part of the gitlink:git[7] suite
-
diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt
index c742117..4f145ea 100644
--- a/Documentation/git-rev-list.txt
+++ b/Documentation/git-rev-list.txt
@@ -27,6 +27,7 @@
 	     [ \--pretty | \--header ]
 	     [ \--bisect ]
 	     [ \--merge ]
+	     [ \--reverse ]
 	     [ \--walk-reflogs ]
 	     <commit>... [ \-- <paths>... ]
 
@@ -266,6 +267,10 @@
 	parent comes before all of its children, but otherwise things
 	are still ordered in the commit timestamp order.
 
+--reverse::
+
+	Output the commits in reverse order.
+
 Object Traversal
 ~~~~~~~~~~~~~~~~
 
diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index d0a2ad3..a8bf656 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -190,6 +190,13 @@
   and dereference the tag recursively until a non-tag object is
   found.
 
+* A colon, followed by a slash, followed by a text: this names
+  a commit whose commit message starts with the specified text.
+  This name returns the youngest matching commit which is
+  reachable from any ref.  If the commit message starts with a
+  '!', you have to repeat that;  the special sequence ':/!',
+  followed by something else than '!' is reserved for now.
+
 * A suffix ':' followed by a path; this names the blob or tree
   at the given path in the tree-ish object named by the part
   before the colon.
diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt
index 367646e..682313e 100644
--- a/Documentation/git-send-email.txt
+++ b/Documentation/git-send-email.txt
@@ -40,7 +40,8 @@
 	the first will be sent as replies to the first email sent.  When using
 	this, it is recommended that the first file given be an overview of the
 	entire patch series.
-	Default is --chain-reply-to
+	Default is the value of the 'sendemail.chainreplyto' configuration
+	value; if that is unspecified, default to --chain-reply-to.
 
 --compose::
 	Use $EDITOR to edit an introductory message for the
@@ -59,7 +60,8 @@
 	is not set, this will be prompted for.
 
 --no-signed-off-by-cc::
-	Do not add emails found in Signed-off-by: lines to the cc list.
+	Do not add emails found in Signed-off-by: or Cc: lines to the
+	cc list.
 
 --quiet::
 	Make git-send-email less verbose.  One line per email should be
@@ -101,6 +103,13 @@
 	Format of the file(s) specified in sendemail.aliasesfile. Must be
 	one of 'mutt', 'mailrc', 'pine', or 'gnus'.
 
+sendemail.bcc::
+	Email address (or alias) to always bcc.
+
+sendemail.chainreplyto::
+	Boolean value specifying the default to the '--chain_reply_to'
+	parameter.
+
 sendemail.smtpserver::
 	Default smtp server to use.
 
diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt
index 6ce6a39..a0d34e0 100644
--- a/Documentation/git-svn.txt
+++ b/Documentation/git-svn.txt
@@ -13,14 +13,13 @@
 -----------
 git-svn is a simple conduit for changesets between Subversion and git.
 It is not to be confused with gitlink:git-svnimport[1], which is
-read-only and geared towards tracking multiple branches.
+read-only.
 
 git-svn was originally designed for an individual developer who wants a
 bidirectional flow of changesets between a single branch in Subversion
 and an arbitrary number of branches in git.  Since its inception,
 git-svn has gained the ability to track multiple branches in a manner
-similar to git-svnimport; but it cannot (yet) automatically detect new
-branches and tags like git-svnimport does.
+similar to git-svnimport.
 
 git-svn is especially useful when it comes to tracking repositories
 not organized in the way Subversion developers recommend (trunk,
@@ -31,26 +30,88 @@
 --
 
 'init'::
-	Creates an empty git repository with additional metadata
-	directories for git-svn.  The Subversion URL must be specified
-	as a command-line argument.  Optionally, the target directory
-	to operate on can be specified as a second argument.  Normally
-	this command initializes the current directory.
+	Initializes an empty git repository with additional
+	metadata directories for git-svn.  The Subversion URL
+	may be specified as a command-line argument, or as full
+	URL arguments to -T/-t/-b.  Optionally, the target
+	directory to operate on can be specified as a second
+	argument.  Normally this command initializes the current
+	directory.
+
+-T<trunk_subdir>::
+--trunk=<trunk_subdir>::
+-t<tags_subdir>::
+--tags=<tags_subdir>::
+-b<branches_subdir>::
+--branches=<branches_subdir>::
+	These are optional command-line options for init.  Each of
+	these flags can point to a relative repository path
+	(--tags=project/tags') or a full url
+	(--tags=https://foo.org/project/tags)
+
+--no-metadata::
+	Set the 'noMetadata' option in the [svn-remote] config.
+--use-svm-props::
+	Set the 'useSvmProps' option in the [svn-remote] config.
+--use-svnsync-props::
+	Set the 'useSvnsyncProps' option in the [svn-remote] config.
+--rewrite-root=<URL>::
+	Set the 'rewriteRoot' option in the [svn-remote] config.
+--username=<USER>::
+	For transports that SVN handles authentication for (http,
+	https, and plain svn), specify the username.  For other
+	transports (eg svn+ssh://), you must include the username in
+	the URL, eg svn+ssh://foo@svn.bar.com/project
+
+--prefix=<prefix>::
+	This allows one to specify a prefix which is prepended
+	to the names of remotes if trunk/branches/tags are
+	specified.  The prefix does not automatically include a
+	trailing slash, so be sure you include one in the
+	argument if that is what you want.  This is useful if
+	you wish to track multiple projects that share a common
+	repository.
 
 'fetch'::
 
-Fetch unfetched revisions from the Subversion URL we are
-tracking.  refs/remotes/git-svn will be updated to the
-latest revision.
+	Fetch unfetched revisions from the Subversion remote we are
+	tracking.  The name of the [svn-remote "..."] section in the
+	.git/config file may be specified as an optional command-line
+	argument.
 
-Note: You should never attempt to modify the remotes/git-svn
-branch outside of git-svn.  Instead, create a branch from
-remotes/git-svn and work on that branch.  Use the 'dcommit'
-command (see below) to write git commits back to
-remotes/git-svn.
+'clone'::
+	Runs 'init' and 'fetch'.  It will automatically create a
+	directory based on the basename of the URL passed to it;
+	or if a second argument is passed; it will create a directory
+	and work within that.  It accepts all arguments that the
+	'init' and 'fetch' commands accept; with the exception of
+	'--fetch-all'.   After a repository is cloned, the 'fetch'
+	command will be able to update revisions without affecting
+	the working tree; and the 'rebase' command will be able
+	to update the working tree with the latest changes.
 
-See '<<fetch-args,Additional Fetch Arguments>>' if you are interested in
-manually joining branches on commit.
+'rebase'::
+	This fetches revisions from the SVN parent of the current HEAD
+	and rebases the current (uncommitted to SVN) work against it.
+
+This works similarly to 'svn update' or 'git-pull' except that
+it preserves linear history with 'git-rebase' instead of
+'git-merge' for ease of dcommit-ing with git-svn.
+
+This accepts all options that 'git-svn fetch' and 'git-rebase'
+accepts.  However '--fetch-all' only fetches from the current
+[svn-remote], and not all [svn-remote] definitions.
+
+Like 'git-rebase'; this requires that the working tree be clean
+and have no uncommitted changes.
++
+--
+-l;;
+--local;;
+	Do not fetch remotely; only run 'git-rebase' against the
+	last fetched commit from the upstream SVN.
+--
++
 
 'dcommit'::
 	Commit each diff from a specified head directly to the SVN
@@ -64,29 +125,40 @@
 	alternative to HEAD.
 	This is advantageous over 'set-tree' (below) because it produces
 	cleaner, more linear history.
+--
 
 'log'::
 	This should make it easy to look up svn log messages when svn
 	users refer to -r/--revision numbers.
++
+The following features from `svn log' are supported:
++
+--
+--revision=<n>[:<n>];;
+	is supported, non-numeric args are not:
+	HEAD, NEXT, BASE, PREV, etc ...
+-v/--verbose;;
+	it's not completely compatible with the --verbose
+	output in svn log, but reasonably close.
+--limit=<n>;;
+	is NOT the same as --max-count, doesn't count
+	merged/excluded commits
+--incremental;;
+	supported
+--
++
+New features:
++
+--
+--show-commit;;
+	shows the git commit sha1, as well
+--oneline;;
+	our version of --pretty=oneline
+--
++
+Any other arguments are passed directly to `git log'
 
-	The following features from `svn log' are supported:
-
-	--revision=<n>[:<n>] - is supported, non-numeric args are not:
-	                       HEAD, NEXT, BASE, PREV, etc ...
-	-v/--verbose         - it's not completely compatible with
-	                       the --verbose output in svn log, but
-			       reasonably close.
-	--limit=<n>          - is NOT the same as --max-count,
-	                       doesn't count merged/excluded commits
-	--incremental        - supported
-
-	New features:
-
-	--show-commit        - shows the git commit sha1, as well
-	--oneline            - our version of --pretty=oneline
-
-	Any other arguments are passed directly to `git log'
-
+--
 'set-tree'::
 	You should consider using 'dcommit' instead of this command.
 	Commit specified commit or tree objects to SVN.  This relies on
@@ -96,16 +168,6 @@
 	commit.  All merging is assumed to have taken place
 	independently of git-svn functions.
 
-'rebuild'::
-	Not a part of daily usage, but this is a useful command if
-	you've just cloned a repository (using gitlink:git-clone[1]) that was
-	tracked with git-svn.  Unfortunately, git-clone does not clone
-	git-svn metadata and the svn working tree that git-svn uses for
-	its operations.  This rebuilds the metadata so git-svn can
-	resume fetch operations.  A Subversion URL may be optionally
-	specified at the command-line if the directory/repository you're
-	tracking has moved or changed protocols.
-
 'show-ignore'::
 	Recursively finds and lists the svn:ignore property on
 	directories.  The output is suitable for appending to
@@ -122,53 +184,13 @@
 	repository (that has been init-ed with git-svn).
 	The -r<revision> option is required for this.
 
-'graft-branches'::
-	This command attempts to detect merges/branches from already
-	imported history.  Techniques used currently include regexes,
-	file copies, and tree-matches).  This command generates (or
-	modifies) the $GIT_DIR/info/grafts file.  This command is
-	considered experimental, and inherently flawed because
-	merge-tracking in SVN is inherently flawed and inconsistent
-	across different repositories.
-
-'multi-init'::
-	This command supports git-svnimport-like command-line syntax for
-	importing repositories that are laid out as recommended by the
-	SVN folks.  This is a bit more tolerant than the git-svnimport
-	command-line syntax and doesn't require the user to figure out
-	where the repository URL ends and where the repository path
-	begins.
-
--T<trunk_subdir>::
---trunk=<trunk_subdir>::
--t<tags_subdir>::
---tags=<tags_subdir>::
--b<branches_subdir>::
---branches=<branches_subdir>::
-	These are the command-line options for multi-init.  Each of
-	these flags can point to a relative repository path
-	(--tags=project/tags') or a full url
-	(--tags=https://foo.org/project/tags)
-
---prefix=<prefix>
-	This allows one to specify a prefix which is prepended to the
-	names of remotes.  The prefix does not automatically include a
-	trailing slash, so be sure you include one in the argument if
-	that is what you want.  This is useful if you wish to track
-	multiple projects that share a common repository.
-
-'multi-fetch'::
-	This runs fetch on all known SVN branches we're tracking.  This
-	will NOT discover new branches (unlike git-svnimport), so
-	multi-init will need to be re-run (it's idempotent).
-
 --
 
 OPTIONS
 -------
 --
 
---shared::
+--shared[={false|true|umask|group|all|world|everybody}]::
 --template=<template_directory>::
 	Only used with the 'init' command.
 	These are passed directly to gitlink:git-init[1].
@@ -176,14 +198,15 @@
 -r <ARG>::
 --revision <ARG>::
 
-Only used with the 'fetch' command.
+Used with the 'fetch' command.
 
-Takes any valid -r<argument> svn would accept and passes it
-directly to svn. -r<ARG1>:<ARG2> ranges and "{" DATE "}" syntax
-is also supported.  This is passed directly to svn, see svn
-documentation for more details.
+This allows revision ranges for partial/cauterized history
+to be supported.  $NUMBER, $NUMBER1:$NUMBER2 (numeric ranges),
+$NUMBER:HEAD, and BASE:$NUMBER are all supported.
 
-This can allow you to make partial mirrors when running fetch.
+This can allow you to make partial mirrors when running fetch;
+but is generally not recommended because history will be skipped
+and lost.
 
 -::
 --stdin::
@@ -252,16 +275,18 @@
 	Make git-svn less verbose.
 
 --repack[=<n>]::
---repack-flags=<flags>
-	These should help keep disk usage sane for large fetches
-	with many revisions.
+--repack-flags=<flags>::
 
-	--repack takes an optional argument for the number of revisions
-	to fetch before repacking.  This defaults to repacking every
-	1000 commits fetched if no argument is specified.
+These should help keep disk usage sane for large fetches
+with many revisions.
 
-	--repack-flags are passed directly to gitlink:git-repack[1].
+--repack takes an optional argument for the number of revisions
+to fetch before repacking.  This defaults to repacking every
+1000 commits fetched if no argument is specified.
 
+--repack-flags are passed directly to gitlink:git-repack[1].
+
+[verse]
 config key: svn.repack
 config key: svn.repackflags
 
@@ -270,7 +295,7 @@
 -s<strategy>::
 --strategy=<strategy>::
 
-These are only used with the 'dcommit' command.
+These are only used with the 'dcommit' and 'rebase' commands.
 
 Passed directly to git-rebase when using 'dcommit' if a
 'git-reset' cannot be used (see dcommit).
@@ -289,121 +314,121 @@
 ----------------
 --
 
--b<refname>::
---branch <refname>::
-Used with 'fetch', 'dcommit' or 'set-tree'.
-
-This can be used to join arbitrary git branches to remotes/git-svn
-on new commits where the tree object is equivalent.
-
-When used with different GIT_SVN_ID values, tags and branches in
-SVN can be tracked this way, as can some merges where the heads
-end up having completely equivalent content.  This can even be
-used to track branches across multiple SVN _repositories_.
-
-This option may be specified multiple times, once for each
-branch.
-
-config key: svn.branch
-
 -i<GIT_SVN_ID>::
 --id <GIT_SVN_ID>::
 
-This sets GIT_SVN_ID (instead of using the environment).  See the
-section on
-'<<tracking-multiple-repos,Tracking Multiple Repositories or Branches>>'
-for more information on using GIT_SVN_ID.
+This sets GIT_SVN_ID (instead of using the environment).  This
+allows the user to override the default refname to fetch from
+when tracking a single URL.  The 'log' and 'dcommit' commands
+no longer require this switch as an argument.
+
+-R<remote name>::
+--svn-remote <remote name>::
+	Specify the [svn-remote "<remote name>"] section to use,
+	this allows SVN multiple repositories to be tracked.
+	Default: "svn"
 
 --follow-parent::
 	This is especially helpful when we're tracking a directory
 	that has been moved around within the repository, or if we
 	started tracking a branch and never tracked the trunk it was
-	descended from.
+	descended from. This feature is enabled by default, use
+	--no-follow-parent to disable it.
 
 config key: svn.followparent
 
---no-metadata::
-	This gets rid of the git-svn-id: lines at the end of every commit.
+--
+CONFIG FILE-ONLY OPTIONS
+------------------------
+--
 
-	With this, you lose the ability to use the rebuild command.  If
-	you ever lose your .git/svn/git-svn/.rev_db file, you won't be
-	able to fetch again, either.  This is fine for one-shot imports.
+svn.noMetadata::
+svn-remote.<name>.noMetadata::
 
-	The 'git-svn log' command will not work on repositories using this,
-	either.
+This gets rid of the git-svn-id: lines at the end of every commit.
 
-config key: svn.nometadata
+If you lose your .git/svn/git-svn/.rev_db file, git-svn will not
+be able to rebuild it and you won't be able to fetch again,
+either.  This is fine for one-shot imports.
+
+The 'git-svn log' command will not work on repositories using
+this, either.  Using this conflicts with the 'useSvmProps'
+option for (hopefully) obvious reasons.
+
+svn.useSvmProps::
+svn-remote.<name>.useSvmProps::
+
+This allows git-svn to re-map repository URLs and UUIDs from
+mirrors created using SVN::Mirror (or svk) for metadata.
+
+If an SVN revision has a property, "svm:headrev", it is likely
+that the revision was created by SVN::Mirror (also used by SVK).
+The property contains a repository UUID and a revision.  We want
+to make it look like we are mirroring the original URL, so
+introduce a helper function that returns the original identity
+URL and UUID, and use it when generating metadata in commit
+messages.
+
+svn.useSvnsyncProps::
+svn-remote.<name>.useSvnsyncprops::
+	Similar to the useSvmProps option; this is for users
+	of the svnsync(1) command distributed with SVN 1.4.x and
+	later.
+
+svn-remote.<name>.rewriteRoot::
+	This allows users to create repositories from alternate
+	URLs.  For example, an administrator could run git-svn on the
+	server locally (accessing via file://) but wish to distribute
+	the repository with a public http:// or svn:// URL in the
+	metadata so users of it will see the public URL.
+
+Since the noMetadata, rewriteRoot, useSvnsyncProps and useSvmProps
+options all affect the metadata generated and used by git-svn; they
+*must* be set in the configuration file before any history is imported
+and these settings should never be changed once they are set.
+
+Additionally, only one of these four options can be used per-svn-remote
+section because they affect the 'git-svn-id:' metadata line.
 
 --
 
-COMPATIBILITY OPTIONS
----------------------
---
-
---upgrade::
-Only used with the 'rebuild' command.
-
-Run this if you used an old version of git-svn that used
-"git-svn-HEAD" instead of "remotes/git-svn" as the branch
-for tracking the remote.
-
---ignore-nodate::
-Only used with the 'fetch' command.
-
-By default git-svn will crash if it tries to import a revision
-from SVN which has '(no date)' listed as the date of the revision.
-This is repository corruption on SVN's part, plain and simple.
-But sometimes you really need those revisions anyway.
-
-If supplied git-svn will convert '(no date)' entries to the UNIX
-epoch (midnight on Jan. 1, 1970).  Yes, that's probably very wrong.
-SVN was very wrong.
-
---
-
-Basic Examples
-~~~~~~~~~~~~~~
+BASIC EXAMPLES
+--------------
 
 Tracking and contributing to a the trunk of a Subversion-managed project:
 
 ------------------------------------------------------------------------
-# Initialize a repo (like git init):
-	git-svn init http://svn.foo.org/project/trunk
-# Fetch remote revisions:
-	git-svn fetch
-# Create your own branch to hack on:
-	git checkout -b my-branch remotes/git-svn
-# Do some work, and then commit your new changes to SVN, as well as
-# automatically updating your working HEAD:
+# Clone a repo (like git clone):
+	git-svn clone http://svn.foo.org/project/trunk
+# Enter the newly cloned directory:
+	cd trunk
+# You should be on master branch, double-check with git-branch
+	git branch
+# Do some work and commit locally to git:
+	git commit ...
+# Something is committed to SVN, rebase your local changes against the
+# latest changes in SVN:
+	git-svn rebase
+# Now commit your changes (that were committed previously using git) to SVN,
+# as well as automatically updating your working HEAD:
 	git-svn dcommit
-# Something is committed to SVN, rebase the latest into your branch:
-	git-svn fetch && git rebase remotes/git-svn
 # Append svn:ignore settings to the default git exclude file:
 	git-svn show-ignore >> .git/info/exclude
 ------------------------------------------------------------------------
 
 Tracking and contributing to an entire Subversion-managed project
 (complete with a trunk, tags and branches):
-See also:
-'<<tracking-multiple-repos,Tracking Multiple Repositories or Branches>>'
 
 ------------------------------------------------------------------------
-# Initialize a repo (like git init):
-	git-svn multi-init http://svn.foo.org/project \
-		-T trunk -b branches -t tags
-# Fetch remote revisions:
-	git-svn multi-fetch
-# Create your own branch of trunk to hack on:
-	git checkout -b my-trunk remotes/trunk
-# Do some work, and then commit your new changes to SVN, as well as
-# automatically updating your working HEAD:
-	git-svn dcommit -i trunk
-# Something has been committed to trunk, rebase the latest into your branch:
-	git-svn multi-fetch && git rebase remotes/trunk
-# Append svn:ignore settings of trunk to the default git exclude file:
-	git-svn show-ignore -i trunk >> .git/info/exclude
-# Check for new branches and tags (no arguments are needed):
-	git-svn multi-init
+# Clone a repo (like git clone):
+	git-svn clone http://svn.foo.org/project -T trunk -b branches -t tags
+# View all branches and tags you have cloned:
+	git branch -r
+# Reset your master to trunk (or any other branch, replacing 'trunk'
+# with the appropriate name):
+	git reset --hard remotes/trunk
+# You may only dcommit to one branch/tag/trunk at a time.  The usage
+# of dcommit/rebase/show-ignore should be the same as above.
 ------------------------------------------------------------------------
 
 REBASE VS. PULL/MERGE
@@ -416,7 +441,7 @@
 
 If you use 'git-svn set-tree A..B' to commit several diffs and you do
 not have the latest remotes/git-svn merged into my-branch, you should
-use 'git rebase' to update your work branch instead of 'git pull' or
+use 'git-svn rebase' to update your work branch instead of 'git pull' or
 'git merge'.  'pull/merge' can cause non-linear history to be flattened
 when committing into SVN, which can lead to merge commits reversing
 previous commits in SVN.
@@ -426,67 +451,49 @@
 Merge tracking in Subversion is lacking and doing branched development
 with Subversion is cumbersome as a result.  git-svn does not do
 automated merge/branch tracking by default and leaves it entirely up to
-the user on the git side.
-
-[[tracking-multiple-repos]]
-TRACKING MULTIPLE REPOSITORIES OR BRANCHES
-------------------------------------------
-Because git-svn does not care about relationships between different
-branches or directories in a Subversion repository, git-svn has a simple
-hack to allow it to track an arbitrary number of related _or_ unrelated
-SVN repositories via one git repository.  Simply use the --id/-i flag or
-set the GIT_SVN_ID environment variable to a name other other than
-"git-svn" (the default) and git-svn will ignore the contents of the
-$GIT_DIR/svn/git-svn directory and instead do all of its work in
-$GIT_DIR/svn/$GIT_SVN_ID for that invocation.  The interface branch will
-be remotes/$GIT_SVN_ID, instead of remotes/git-svn.  Any
-remotes/$GIT_SVN_ID branch should never be modified by the user outside
-of git-svn commands.
-
-[[fetch-args]]
-ADDITIONAL FETCH ARGUMENTS
---------------------------
-This is for advanced users, most users should ignore this section.
-
-Unfetched SVN revisions may be imported as children of existing commits
-by specifying additional arguments to 'fetch'.  Additional parents may
-optionally be specified in the form of sha1 hex sums at the
-command-line.  Unfetched SVN revisions may also be tied to particular
-git commits with the following syntax:
-
-------------------------------------------------
-	svn_revision_number=git_commit_sha1
-------------------------------------------------
-
-This allows you to tie unfetched SVN revision 375 to your current HEAD:
-
-------------------------------------------------
-	git-svn fetch 375=$(git-rev-parse HEAD)
-------------------------------------------------
-
-If you're tracking a directory that has moved, or otherwise been
-branched or tagged off of another directory in the repository and you
-care about the full history of the project, then you can use
-the --follow-parent option.
-
-------------------------------------------------
-	git-svn fetch --follow-parent
-------------------------------------------------
+the user on the git side.  git-svn does however follow copy
+history of the directory that it is tracking, however (much like
+how 'svn log' works).
 
 BUGS
 ----
 
-We ignore all SVN properties except svn:executable.  Too difficult to
-map them since we rely heavily on git write-tree being _exactly_ the
-same on both the SVN and git working trees and I prefer not to clutter
-working trees with metadata files.
+We ignore all SVN properties except svn:executable.  Any unhandled
+properties are logged to $GIT_DIR/svn/<refname>/unhandled.log
 
 Renamed and copied directories are not detected by git and hence not
 tracked when committing to SVN.  I do not plan on adding support for
 this as it's quite difficult and time-consuming to get working for all
-the possible corner cases (git doesn't do it, either).  Renamed and
-copied files are fully supported if they're similar enough for git to
-detect them.
+the possible corner cases (git doesn't do it, either).  Committing
+renamed and copied files are fully supported if they're similar enough
+for git to detect them.
+
+CONFIGURATION
+-------------
+
+git-svn stores [svn-remote] configuration information in the
+repository .git/config file.  It is similar the core git
+[remote] sections except 'fetch' keys do not accept glob
+arguments; but they are instead handled by the 'branches'
+and 'tags' keys.  Since some SVN repositories are oddly
+configured with multiple projects glob expansions such those
+listed below are allowed:
+
+------------------------------------------------------------------------
+[svn-remote "project-a"]
+	url = http://server.org/svn
+	branches = branches/*/project-a:refs/remotes/project-a/branches/*
+	tags = tags/*/project-a:refs/remotes/project-a/tags/*
+	trunk = trunk/project-a:refs/remotes/project-a/trunk
+------------------------------------------------------------------------
+
+Keep in mind that the '*' (asterisk) wildcard of the local ref
+(left of the ':') *must* be the farthest right path component;
+however the remote wildcard may be anywhere as long as it's own
+independent path componet (surrounded by '/' or EOL).   This
+type of configuration is not automatically created by 'init' and
+should be manually entered with a text-editor or using
+gitlink:git-config[1]
 
 SEE ALSO
 --------
diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt
index b161c8b..cd5e014 100644
--- a/Documentation/git-update-index.txt
+++ b/Documentation/git-update-index.txt
@@ -295,6 +295,11 @@
 executable bit.   On such an unfortunate filesystem, you may
 need to use `git-update-index --chmod=`.
 
+Quite similarly, if `core.symlinks` configuration variable is set
+to 'false' (see gitlink:git-config[1]), symbolic links are checked out
+as plain files, and this command does not modify a recorded file mode
+from symbolic link to regular file.
+
 The command looks at `core.ignorestat` configuration variable.  See
 'Using "assume unchanged" bit' section above.
 
diff --git a/Documentation/git-upload-pack.txt b/Documentation/git-upload-pack.txt
index 9da062d..fd65192 100644
--- a/Documentation/git-upload-pack.txt
+++ b/Documentation/git-upload-pack.txt
@@ -8,7 +8,7 @@
 
 SYNOPSIS
 --------
-'git-upload-pack' <directory>
+'git-upload-pack' [--strict] [--timeout=<n>] <directory>
 
 DESCRIPTION
 -----------
@@ -23,6 +23,13 @@
 
 OPTIONS
 -------
+
+\--strict::
+	Do not try <directory>/.git/ if <directory> is no git directory.
+
+\--timeout=<n>::
+	Interrupt transfer after <n> seconds of inactivity.
+
 <directory>::
 	The repository to sync from.
 
diff --git a/Documentation/git.txt b/Documentation/git.txt
index e514588..dceacfa 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -35,6 +35,20 @@
 You are reading the documentation for the latest version of git.
 Documentation for older releases are available here:
 
+* link:v1.5.0.6/git.html[documentation for release 1.5.0.6]
+
+* link:v1.5.0.6/RelNotes-1.5.0.6.txt[release notes for 1.5.0.6]
+
+* link:v1.5.0.5/RelNotes-1.5.0.5.txt[release notes for 1.5.0.5]
+
+* link:v1.5.0.3/RelNotes-1.5.0.3.txt[release notes for 1.5.0.3]
+
+* link:v1.5.0.2/RelNotes-1.5.0.2.txt[release notes for 1.5.0.2]
+
+* link:v1.5.0.1/RelNotes-1.5.0.1.txt[release notes for 1.5.0.1]
+
+* link:v1.5.0/RelNotes-1.5.0.txt[release notes for 1.5.0]
+
 * link:v1.4.4.4/git.html[documentation for release 1.4.4.4]
 
 * link:v1.3.3/git.html[documentation for release 1.3.3]
diff --git a/Documentation/howto/revert-branch-rebase.txt b/Documentation/howto/revert-branch-rebase.txt
index d10476b..d88ec23 100644
--- a/Documentation/howto/revert-branch-rebase.txt
+++ b/Documentation/howto/revert-branch-rebase.txt
@@ -85,7 +85,7 @@
 
 ------------------------------------------------
 $ git checkout master
-$ git resolve master revert-c99 fast ;# this should be a fast forward
+$ git merge revert-c99 ;# this should be a fast forward
 Updating from 10d781b9caa4f71495c7b34963bef137216f86a8 to e3a693c...
  cache.h        |    8 ++++----
  commit.c       |    2 +-
@@ -95,13 +95,6 @@
  5 files changed, 8 insertions(+), 8 deletions(-)
 ------------------------------------------------
 
-The 'fast' in the above 'git resolve' is not a magic.  I knew this
-'resolve' would result in a fast forward merge, and if not, there is
-something very wrong (so I would do 'git reset' on the 'master' branch
-and examine the situation).  When a fast forward merge is done, the
-message parameter to 'git resolve' is discarded, because no new commit
-is created.  You could have said 'junk' or 'nothing' there as well.
-
 There is no need to redo the test at this point.  We fast forwarded
 and we know 'master' matches 'revert-c99' exactly.  In fact:
 
diff --git a/Documentation/howto/use-git-daemon.txt b/Documentation/howto/use-git-daemon.txt
new file mode 100644
index 0000000..1a1eb24
--- /dev/null
+++ b/Documentation/howto/use-git-daemon.txt
@@ -0,0 +1,52 @@
+How to use git-daemon
+
+Git can be run in inetd mode and in stand alone mode. But all you want is
+let a coworker pull from you, and therefore need to set up a git server
+real quick, right?
+
+Note that git-daemon is not really chatty at the moment, especially when
+things do not go according to plan (e.g. a socket could not be bound).
+
+Another word of warning: if you run
+
+	$ git ls-remote git://127.0.0.1/rule-the-world.git
+
+and you see a message like
+
+	fatal: The remote end hung up unexpectedly
+
+it only means that _something_ went wrong. To find out _what_ went wrong,
+you have to ask the server. (Git refuses to be more precise for your
+security only. Take off your shoes now. You have any coins in your pockets?
+Sorry, not allowed -- who knows what you planned to do with them?)
+
+With these two caveats, let's see an example:
+
+	$ git daemon --reuseaddr --verbose --base-path=/home/gitte/git \
+	  --export-all -- /home/gitte/git/rule-the-world.git
+
+(Of course, unless your user name is `gitte` _and_ your repository is in
+~/rule-the-world.git, you have to adjust the paths. If your repository is
+not bare, be aware that you have to type the path to the .git directory!)
+
+This invocation tries to reuse the address if it is already taken
+(this can save you some debugging, because otherwise killing and restarting
+git-daemon could just silently fail to bind to a socket).
+
+Also, it is (relatively) verbose when somebody actually connects to it.
+It also sets the base path, which means that all the projects which can be
+accessed using this daemon have to reside in or under that path.
+
+The option `--export-all` just means that you _don't_ have to create a
+file named `git-daemon-export-ok` in each exported repository. (Otherwise,
+git-daemon would complain loudly, and refuse to cooperate.)
+
+Last of all, the repository which should be exported is specified. It is
+a good practice to put the paths after a "--" separator.
+
+Now, test your daemon with
+
+	$ git ls-remote git://127.0.0.1/rule-the-world.git
+
+If this does not work, find out why, and submit a patch to this document.
+
diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt
index fb0b0b9..2fe6c31 100644
--- a/Documentation/pretty-formats.txt
+++ b/Documentation/pretty-formats.txt
@@ -77,9 +77,53 @@
 true parent commits, without taking grafts nor history
 simplification into account.
 
+	* 'format:'
++
+The 'format:' format allows you to specify which information
+you want to show. It works a little bit like printf format,
+with the notable exception that you get a newline with '%n'
+instead of '\n'.
+
+E.g, 'format:"The author of %h was %an, %ar%nThe title was >>%s<<"'
+would show something like this:
+
+The author of fe6e0ee was Junio C Hamano, 23 hours ago
+The title was >>t4119: test autocomputing -p<n> for traditional diff input.<<
+
+The placeholders are:
+
+- '%H': commit hash
+- '%h': abbreviated commit hash
+- '%T': tree hash
+- '%t': abbreviated tree hash
+- '%P': parent hashes
+- '%p': abbreviated parent hashes
+- '%an': author name
+- '%ae': author email
+- '%ad': author date
+- '%aD': author date, RFC2822 style
+- '%ar': author date, relative
+- '%at': author date, UNIX timestamp
+- '%cn': committer name
+- '%ce': committer email
+- '%cd': committer date
+- '%cD': committer date, RFC2822 style
+- '%cr': committer date, relative
+- '%ct': committer date, UNIX timestamp
+- '%e': encoding
+- '%s': subject
+- '%b': body
+- '%Cred': switch color to red
+- '%Cgreen': switch color to green
+- '%Cblue': switch color to blue
+- '%Creset': reset color
+- '%n': newline
+
+
 --encoding[=<encoding>]::
 	The commit objects record the encoding used for the log message
 	in their encoding header; this option can be used to tell the
 	command to re-code the commit log message in the encoding
 	preferred by the user.  For non plumbing commands this
 	defaults to UTF-8.
+
diff --git a/Documentation/technical/shallow.txt b/Documentation/technical/shallow.txt
new file mode 100644
index 0000000..559263a
--- /dev/null
+++ b/Documentation/technical/shallow.txt
@@ -0,0 +1,49 @@
+Def.: Shallow commits do have parents, but not in the shallow
+repo, and therefore grafts are introduced pretending that
+these commits have no parents.
+
+The basic idea is to write the SHA1s of shallow commits into
+$GIT_DIR/shallow, and handle its contents like the contents
+of $GIT_DIR/info/grafts (with the difference that shallow
+cannot contain parent information).
+
+This information is stored in a new file instead of grafts, or
+even the config, since the user should not touch that file
+at all (even throughout development of the shallow clone, it
+was never manually edited!).
+
+Each line contains exactly one SHA1. When read, a commit_graft
+will be constructed, which has nr_parent < 0 to make it easier
+to discern from user provided grafts.
+
+Since fsck-objects relies on the library to read the objects,
+it honours shallow commits automatically.
+
+There are some unfinished ends of the whole shallow business:
+
+- maybe we have to force non-thin packs when fetching into a
+  shallow repo (ATM they are forced non-thin).
+
+- A special handling of a shallow upstream is needed. At some
+  stage, upload-pack has to check if it sends a shallow commit,
+  and it should send that information early (or fail, if the
+  client does not support shallow repositories). There is no
+  support at all for this in this patch series.
+
+- Instead of locking $GIT_DIR/shallow at the start, just
+  the timestamp of it is noted, and when it comes to writing it,
+  a check is performed if the mtime is still the same, dying if
+  it is not.
+
+- It is unclear how "push into/from a shallow repo" should behave.
+
+- If you deepen a history, you'd want to get the tags of the
+  newly stored (but older!) commits. This does not work right now.
+
+To make a shallow clone, you can call "git-clone --depth 20 repo".
+The result contains only commit chains with a length of at most 20.
+It also writes an appropriate $GIT_DIR/shallow.
+
+You can deepen a shallow repository with "git-fetch --depth 20
+repo branch", which will fetch branch from repo, but stop at depth
+20, updating $GIT_DIR/shallow.
diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt
index 3ed9f84..574e9c0 100644
--- a/Documentation/user-manual.txt
+++ b/Documentation/user-manual.txt
@@ -84,7 +84,7 @@
   origin/master
   origin/next
   ...
-$ git branch checkout -b masterwork origin/master
+$ git checkout -b masterwork origin/master
 -----------------------------------------------
 
 Fetch a branch from a different repository, and give it a new
@@ -155,8 +155,8 @@
 ------------------------------------------------
 $ cat >~/.gitconfig <<\EOF
 [user]
-name = Your Name Comes Here
-email = you@yourdomain.example.com
+	name = Your Name Comes Here
+	email = you@yourdomain.example.com
 EOF
 ------------------------------------------------
 
@@ -195,7 +195,7 @@
 -----------------------------------------------
 $ git format-patch origin..HEAD # format a patch for each commit
 				# in HEAD but not in origin
-$ git-am mbox # import patches from the mailbox "mbox"
+$ git am mbox # import patches from the mailbox "mbox"
 -----------------------------------------------
 
 Fetch a branch in a different git repository, then merge into the
@@ -579,7 +579,7 @@
 
 -------------------------------------------------
 $ git remote add linux-nfs git://linux-nfs.org/pub/nfs-2.6.git
-$ git fetch
+$ git fetch linux-nfs
 * refs/remotes/linux-nfs/master: storing branch 'master' ...
   commit: bf81b46
 -------------------------------------------------
@@ -680,7 +680,7 @@
 run
 
 -------------------------------------------------
-$ git bisect-visualize
+$ git bisect visualize
 -------------------------------------------------
 
 which will run gitk and label the commit it chose with a marker that
@@ -765,7 +765,7 @@
 running
 
 -------------------------------------------------
-$ git-tag stable-1 1b2e1d63ff
+$ git tag stable-1 1b2e1d63ff
 -------------------------------------------------
 
 You can use stable-1 to refer to the commit 1b2e1d63ff.
@@ -909,7 +909,7 @@
 descendants:
 
 -------------------------------------------------
-$ git name-rev e05db0fd
+$ git name-rev --tags e05db0fd
 e05db0fd tags/v1.5.0-rc1^0~23
 -------------------------------------------------
 
@@ -918,7 +918,7 @@
 
 -------------------------------------------------
 $ git describe e05db0fd
-v1.5.0-rc0-ge05db0f
+v1.5.0-rc0-260-ge05db0f
 -------------------------------------------------
 
 but that may sometimes help you guess which tags might come after the
@@ -1861,7 +1861,7 @@
 
 The gitweb cgi script provides users an easy way to browse your
 project's files and history without having to install git; see the file
-gitweb/README in the git source tree for instructions on setting it up.
+gitweb/INSTALL in the git source tree for instructions on setting it up.
 
 Examples
 --------
@@ -2869,7 +2869,7 @@
 $ git-merge-index git-merge-one-file hello.c
 -------------------------------------------------
 
-and that is what higher level `git resolve` is implemented with.
+and that is what higher level `git merge -s resolve` is implemented with.
 
 How git stores objects efficiently: pack files
 ----------------------------------------------
diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN
index 7213309..601f30f 100755
--- a/GIT-VERSION-GEN
+++ b/GIT-VERSION-GEN
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.5.0.7.GIT
+DEF_VER=v1.5.1-rc3.GIT
 
 LF='
 '
diff --git a/Makefile b/Makefile
index eeb502f..b159ffd 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,8 @@
 # The default target of this Makefile is...
 all::
 
+# Define V=1 to have a more verbose compile.
+#
 # Define NO_OPENSSL environment variable if you do not have OpenSSL.
 # This also implies MOZILLA_SHA1.
 #
@@ -131,6 +133,7 @@
 bindir = $(prefix)/bin
 gitexecdir = $(bindir)
 template_dir = $(prefix)/share/git-core/templates/
+ETC_GITCONFIG = $(prefix)/etc/gitconfig
 # DESTDIR=
 
 # default configuration for gitweb
@@ -174,12 +177,12 @@
 SCRIPT_SH = \
 	git-bisect.sh git-checkout.sh \
 	git-clean.sh git-clone.sh git-commit.sh \
-	git-fetch.sh git-gc.sh \
+	git-fetch.sh \
 	git-ls-remote.sh \
-	git-merge-one-file.sh git-parse-remote.sh \
+	git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \
 	git-pull.sh git-rebase.sh \
 	git-repack.sh git-request-pull.sh git-reset.sh \
-	git-resolve.sh git-revert.sh git-sh-setup.sh \
+	git-sh-setup.sh \
 	git-tag.sh git-verify-tag.sh \
 	git-applymbox.sh git-applypatch.sh git-am.sh \
 	git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \
@@ -195,7 +198,7 @@
 
 SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
 	  $(patsubst %.perl,%,$(SCRIPT_PERL)) \
-	  git-cherry-pick git-status git-instaweb
+	  git-status git-instaweb
 
 # ... and all the rest that could be moved out of bindir to gitexecdir
 PROGRAMS = \
@@ -222,7 +225,7 @@
 BUILT_INS = \
 	git-format-patch$X git-show$X git-whatchanged$X git-cherry$X \
 	git-get-tar-commit-id$X git-init$X git-repo-config$X \
-	git-fsck-objects$X \
+	git-fsck-objects$X git-cherry-pick$X \
 	$(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS))
 
 # what 'all' will build and 'install' will install, in gitexecdir
@@ -269,7 +272,8 @@
 	revision.o pager.o tree-walk.o xdiff-interface.o \
 	write_or_die.o trace.o list-objects.o grep.o \
 	alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \
-	color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o
+	color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o \
+	convert.o
 
 BUILTIN_OBJS = \
 	builtin-add.o \
@@ -278,6 +282,7 @@
 	builtin-archive.o \
 	builtin-blame.o \
 	builtin-branch.o \
+	builtin-bundle.o \
 	builtin-cat-file.o \
 	builtin-checkout-index.o \
 	builtin-check-ref-format.o \
@@ -287,11 +292,12 @@
 	builtin-diff.o \
 	builtin-diff-files.o \
 	builtin-diff-index.o \
-	builtin-diff-stages.o \
 	builtin-diff-tree.o \
+	builtin-fetch--tool.o \
 	builtin-fmt-merge-msg.o \
 	builtin-for-each-ref.o \
 	builtin-fsck.o \
+	builtin-gc.o \
 	builtin-grep.o \
 	builtin-init-db.o \
 	builtin-log.o \
@@ -299,6 +305,7 @@
 	builtin-ls-tree.o \
 	builtin-mailinfo.o \
 	builtin-mailsplit.o \
+	builtin-merge-base.o \
 	builtin-merge-file.o \
 	builtin-mv.o \
 	builtin-name-rev.o \
@@ -312,6 +319,7 @@
 	builtin-rerere.o \
 	builtin-rev-list.o \
 	builtin-rev-parse.o \
+	builtin-revert.o \
 	builtin-rm.o \
 	builtin-runstatus.o \
 	builtin-shortlog.o \
@@ -600,9 +608,35 @@
 	export NO_PERL_MAKEMAKER
 endif
 
+QUIET_SUBDIR0  = $(MAKE) -C # space to separate -C and subdir
+QUIET_SUBDIR1  =
+
+ifneq ($(findstring $(MAKEFLAGS),w),w)
+PRINT_DIR = --no-print-directory
+else # "make -w"
+NO_SUBDIR = :
+endif
+
+ifneq ($(findstring $(MAKEFLAGS),s),s)
+ifndef V
+	QUIET_CC       = @echo '   ' CC $@;
+	QUIET_AR       = @echo '   ' AR $@;
+	QUIET_LINK     = @echo '   ' LINK $@;
+	QUIET_BUILT_IN = @echo '   ' BUILTIN $@;
+	QUIET_GEN      = @echo '   ' GEN $@;
+	QUIET_SUBDIR0  = @subdir=
+	QUIET_SUBDIR1  = ;$(NO_SUBDIR) echo '   ' SUBDIR $$subdir; \
+			 $(MAKE) $(PRINT_DIR) -C $$subdir
+	export V
+	export QUIET_GEN
+	export QUIET_BUILT_IN
+endif
+endif
+
 # Shell quote (do not use $(call) to accommodate ancient setups);
 
 SHA1_HEADER_SQ = $(subst ','\'',$(SHA1_HEADER))
+ETC_GITCONFIG_SQ = $(subst ','\'',$(ETC_GITCONFIG))
 
 DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
 bindir_SQ = $(subst ','\'',$(bindir))
@@ -615,7 +649,8 @@
 
 LIBS = $(GITLIBS) $(EXTLIBS)
 
-BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' $(COMPAT_CFLAGS)
+BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' \
+	-DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"' $(COMPAT_CFLAGS)
 LIB_OBJS += $(COMPAT_OBJS)
 
 ALL_CFLAGS += $(BASIC_CFLAGS)
@@ -632,44 +667,43 @@
 endif
 
 all::
-	$(MAKE) -C git-gui all
-	$(MAKE) -C perl PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all
-	$(MAKE) -C templates
+	$(QUIET_SUBDIR0)git-gui $(QUIET_SUBDIR1) all
+	$(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all
+	$(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1)
 
 strip: $(PROGRAMS) git$X
 	$(STRIP) $(STRIP_OPTS) $(PROGRAMS) git$X
 
 git$X: git.c common-cmds.h $(BUILTIN_OBJS) $(GITLIBS) GIT-CFLAGS
-	$(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \
+	$(QUIET_LINK)$(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \
 		$(ALL_CFLAGS) -o $@ $(filter %.c,$^) \
 		$(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS)
 
 help.o: common-cmds.h
 
 $(BUILT_INS): git$X
-	rm -f $@ && ln git$X $@
+	$(QUIET_BUILT_IN)rm -f $@ && ln git$X $@
 
 common-cmds.h: Documentation/git-*.txt
-	./generate-cmdlist.sh > $@+
-	mv $@+ $@
+	$(QUIET_GEN)./generate-cmdlist.sh > $@+ && mv $@+ $@
 
 $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
-	rm -f $@ $@+
+	$(QUIET_GEN)rm -f $@ $@+ && \
 	sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
 	    -e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \
 	    -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
 	    -e 's/@@NO_CURL@@/$(NO_CURL)/g' \
-	    $@.sh >$@+
-	chmod +x $@+
+	    $@.sh >$@+ && \
+	chmod +x $@+ && \
 	mv $@+ $@
 
 $(patsubst %.perl,%,$(SCRIPT_PERL)): perl/perl.mak
 
 perl/perl.mak: GIT-CFLAGS
-	$(MAKE) -C perl PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' $(@F)
+	$(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' $(@F)
 
 $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
-	rm -f $@ $@+
+	$(QUIET_GEN)rm -f $@ $@+ && \
 	INSTLIBDIR=`$(MAKE) -C perl -s --no-print-directory instlibdir` && \
 	sed -e '1{' \
 	    -e '	s|#!.*perl|#!$(PERL_PATH_SQ)|' \
@@ -680,20 +714,15 @@
 	    -e '}' \
 	    -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \
 	    -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
-	    $@.perl >$@+
-	chmod +x $@+
-	mv $@+ $@
-
-git-cherry-pick: git-revert
-	cp $< $@+
+	    $@.perl >$@+ && \
+	chmod +x $@+ && \
 	mv $@+ $@
 
 git-status: git-commit
-	cp $< $@+
-	mv $@+ $@
+	$(QUIET_GEN)cp $< $@+ && mv $@+ $@
 
 gitweb/gitweb.cgi: gitweb/gitweb.perl
-	rm -f $@ $@+
+	$(QUIET_GEN)rm -f $@ $@+ && \
 	sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \
 	    -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \
 	    -e 's|++GIT_BINDIR++|$(bindir)|g' \
@@ -711,12 +740,12 @@
 	    -e 's|++GITWEB_FAVICON++|$(GITWEB_FAVICON)|g' \
 	    -e 's|++GITWEB_SITE_HEADER++|$(GITWEB_SITE_HEADER)|g' \
 	    -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' \
-	    $< >$@+
-	chmod +x $@+
+	    $< >$@+ && \
+	chmod +x $@+ && \
 	mv $@+ $@
 
 git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css
-	rm -f $@ $@+
+	$(QUIET_GEN)rm -f $@ $@+ && \
 	sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
 	    -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
 	    -e 's/@@NO_CURL@@/$(NO_CURL)/g' \
@@ -724,15 +753,15 @@
 	    -e '/@@GITWEB_CGI@@/d' \
 	    -e '/@@GITWEB_CSS@@/r gitweb/gitweb.css' \
 	    -e '/@@GITWEB_CSS@@/d' \
-	    $@.sh > $@+
-	chmod +x $@+
+	    $@.sh > $@+ && \
+	chmod +x $@+ && \
 	mv $@+ $@
 
 configure: configure.ac
-	rm -f $@ $<+
+	$(QUIET_GEN)rm -f $@ $<+ && \
 	sed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
-	    $< > $<+
-	autoconf -o $@ $<+
+	    $< > $<+ && \
+	autoconf -o $@ $<+ && \
 	rm -f $<+
 
 # These can record GIT_VERSION
@@ -742,25 +771,25 @@
 	: GIT-VERSION-FILE
 
 %.o: %.c GIT-CFLAGS
-	$(CC) -o $*.o -c $(ALL_CFLAGS) $<
+	$(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $<
 %.o: %.S
-	$(CC) -o $*.o -c $(ALL_CFLAGS) $<
+	$(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $<
 
 exec_cmd.o: exec_cmd.c GIT-CFLAGS
-	$(CC) -o $*.o -c $(ALL_CFLAGS) '-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' $<
+	$(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) '-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' $<
 builtin-init-db.o: builtin-init-db.c GIT-CFLAGS
-	$(CC) -o $*.o -c $(ALL_CFLAGS) -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' $<
+	$(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' $<
 
 http.o: http.c GIT-CFLAGS
-	$(CC) -o $*.o -c $(ALL_CFLAGS) -DGIT_USER_AGENT='"git/$(GIT_VERSION)"' $<
+	$(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DGIT_USER_AGENT='"git/$(GIT_VERSION)"' $<
 
 ifdef NO_EXPAT
 http-fetch.o: http-fetch.c http.h GIT-CFLAGS
-	$(CC) -o $*.o -c $(ALL_CFLAGS) -DNO_EXPAT $<
+	$(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DNO_EXPAT $<
 endif
 
 git-%$X: %.o $(GITLIBS)
-	$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
+	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
 
 ssh-pull.o: ssh-fetch.c
 ssh-push.o: ssh-upload.c
@@ -774,11 +803,11 @@
 
 http.o http-fetch.o http-push.o: http.h
 git-http-fetch$X: fetch.o http.o http-fetch.o $(GITLIBS)
-	$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
+	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
 		$(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
 
 git-http-push$X: revision.o http.o http-push.o $(GITLIBS)
-	$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
+	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
 		$(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
 
 $(LIB_OBJS) $(BUILTIN_OBJS) fetch.o: $(LIB_H)
@@ -786,7 +815,7 @@
 $(DIFF_OBJS): diffcore.h
 
 $(LIB_FILE): $(LIB_OBJS)
-	rm -f $@ && $(AR) rcs $@ $(LIB_OBJS)
+	$(QUIET_AR)rm -f $@ && $(AR) rcs $@ $(LIB_OBJS)
 
 XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \
 	xdiff/xmerge.o
@@ -794,7 +823,7 @@
 	xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h
 
 $(XDIFF_LIB): $(XDIFF_OBJS)
-	rm -f $@ && $(AR) rcs $@ $(XDIFF_OBJS)
+	$(QUIET_AR)rm -f $@ && $(AR) rcs $@ $(XDIFF_OBJS)
 
 
 perl/Makefile: perl/Git.pm perl/Makefile.PL GIT-CFLAGS
@@ -831,7 +860,7 @@
 
 export NO_SVN_TESTS
 
-test: all
+test: all test-chmtime$X
 	$(MAKE) -C t/ all
 
 test-date$X: test-date.c date.o ctype.o
@@ -846,6 +875,9 @@
 test-sha1$X: test-sha1.o $(GITLIBS)
 	$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
 
+test-chmtime$X: test-chmtime.c
+	$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $<
+
 check-sha1:: test-sha1$X
 	./test-sha1.sh
 
@@ -931,7 +963,7 @@
 
 clean:
 	rm -f *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o xdiff/*.o \
-		$(LIB_FILE) $(XDIFF_LIB)
+		test-chmtime$X $(LIB_FILE) $(XDIFF_LIB)
 	rm -f $(ALL_PROGRAMS) $(BUILT_INS) git$X
 	rm -f *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags
 	rm -rf autom4te.cache
@@ -959,7 +991,7 @@
 		git-merge-octopus | git-merge-ours | git-merge-recursive | \
 		git-merge-resolve | git-merge-stupid | \
 		git-add--interactive | git-fsck-objects | git-init-db | \
-		git-repo-config | \
+		git-repo-config | git-fetch--tool | \
 		git-ssh-pull | git-ssh-push ) continue ;; \
 		esac ; \
 		test -f "Documentation/$$v.txt" || \
diff --git a/README b/README
index 441167c..548142c 100644
--- a/README
+++ b/README
@@ -38,3 +38,9 @@
 to the list, send an email with just "subscribe git" in the body to
 majordomo@vger.kernel.org. The mailing list archives are available at
 http://marc.theaimsgroup.com/?l=git and other archival sites.
+
+The messages titled "A note from the maintainer", "What's in
+git.git (stable)" and "What's cooking in git.git (topics)" and
+the discussion following them on the mailing list give a good
+reference for project status, development direction and
+remaining tasks.
diff --git a/RelNotes b/RelNotes
index 1126dfe..d5e055d 120000
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes-1.5.0.7.txt
\ No newline at end of file
+Documentation/RelNotes-1.5.1.txt
\ No newline at end of file
diff --git a/archive-tar.c b/archive-tar.c
index 7d52a06..d9c30d3 100644
--- a/archive-tar.c
+++ b/archive-tar.c
@@ -262,7 +262,7 @@
 	static struct strbuf path;
 	int filenamelen = strlen(filename);
 	void *buffer;
-	char type[20];
+	enum object_type type;
 	unsigned long size;
 
 	if (!path.alloc) {
@@ -283,7 +283,7 @@
 		buffer = NULL;
 		size = 0;
 	} else {
-		buffer = read_sha1_file(sha1, type, &size);
+		buffer = read_sha1_file(sha1, &type, &size);
 		if (!buffer)
 			die("cannot read %s", sha1_to_hex(sha1));
 	}
diff --git a/archive-zip.c b/archive-zip.c
index f31b8ed..7c49848 100644
--- a/archive-zip.c
+++ b/archive-zip.c
@@ -167,7 +167,7 @@
 	int pathlen;
 	unsigned char *out;
 	char *path;
-	char type[20];
+	enum object_type type;
 	void *buffer = NULL;
 	void *deflated = NULL;
 
@@ -195,7 +195,7 @@
 		if (S_ISREG(mode) && zlib_compression_level != 0)
 			method = 8;
 		result = 0;
-		buffer = read_sha1_file(sha1, type, &size);
+		buffer = read_sha1_file(sha1, &type, &size);
 		if (!buffer)
 			die("cannot read %s", sha1_to_hex(sha1));
 		crc = crc32(crc, buffer, size);
diff --git a/blob.c b/blob.c
index 9776bea..0a9ea41 100644
--- a/blob.c
+++ b/blob.c
@@ -30,18 +30,18 @@
 
 int parse_blob(struct blob *item)
 {
-        char type[20];
+        enum object_type type;
         void *buffer;
         unsigned long size;
 	int ret;
 
         if (item->object.parsed)
                 return 0;
-        buffer = read_sha1_file(item->object.sha1, type, &size);
+        buffer = read_sha1_file(item->object.sha1, &type, &size);
         if (!buffer)
                 return error("Could not read %s",
                              sha1_to_hex(item->object.sha1));
-        if (strcmp(type, blob_type))
+        if (type != OBJ_BLOB)
                 return error("Object %s not a blob",
                              sha1_to_hex(item->object.sha1));
 	ret = parse_blob_buffer(item, buffer, size);
diff --git a/builtin-add.c b/builtin-add.c
index 87e16aa..9fcf514 100644
--- a/builtin-add.c
+++ b/builtin-add.c
@@ -12,6 +12,8 @@
 static const char builtin_add_usage[] =
 "git-add [-n] [-v] [-f] [--interactive | -i] [--] <filepattern>...";
 
+static const char *excludes_file;
+
 static void prune_directory(struct dir_struct *dir, const char **pathspec, int prefix)
 {
 	char *seen;
@@ -67,6 +69,8 @@
 	path = git_path("info/exclude");
 	if (!access(path, R_OK))
 		add_excludes_from_file(dir, path);
+	if (!access(excludes_file, R_OK))
+		add_excludes_from_file(dir, excludes_file);
 
 	/*
 	 * Calculate common prefix for the pathspec, and
@@ -88,6 +92,18 @@
 		prune_directory(dir, pathspec, baselen);
 }
 
+static int git_add_config(const char *var, const char *value)
+{
+	if (!strcmp(var, "core.excludesfile")) {
+		if (!value)
+			die("core.excludesfile without value");
+		excludes_file = xstrdup(value);
+		return 0;
+	}
+
+	return git_default_config(var, value);
+}
+
 static struct lock_file lock_file;
 
 static const char ignore_warning[] =
@@ -115,7 +131,7 @@
 		exit(1);
 	}
 
-	git_config(git_default_config);
+	git_config(git_add_config);
 
 	newfd = hold_lock_file_for_update(&lock_file, get_index_file(), 1);
 
diff --git a/builtin-apply.c b/builtin-apply.c
index bec95d6..27a182b 100644
--- a/builtin-apply.c
+++ b/builtin-apply.c
@@ -28,6 +28,7 @@
 
 static int unidiff_zero;
 static int p_value = 1;
+static int p_value_known;
 static int check_index;
 static int write_index;
 static int cached;
@@ -144,6 +145,7 @@
 	unsigned long deflate_origlen;
 	int lines_added, lines_deleted;
 	int score;
+	unsigned int is_toplevel_relative:1;
 	unsigned int inaccurate_eof:1;
 	unsigned int is_binary:1;
 	unsigned int is_copy:1;
@@ -238,7 +240,7 @@
 	return 1;
 }
 
-static char * find_name(const char *line, char *def, int p_value, int terminate)
+static char *find_name(const char *line, char *def, int p_value, int terminate)
 {
 	int len;
 	const char *start = line;
@@ -311,11 +313,54 @@
 	return name;
 }
 
+static int count_slashes(const char *cp)
+{
+	int cnt = 0;
+	char ch;
+
+	while ((ch = *cp++))
+		if (ch == '/')
+			cnt++;
+	return cnt;
+}
+
+/*
+ * Given the string after "--- " or "+++ ", guess the appropriate
+ * p_value for the given patch.
+ */
+static int guess_p_value(const char *nameline)
+{
+	char *name, *cp;
+	int val = -1;
+
+	if (is_dev_null(nameline))
+		return -1;
+	name = find_name(nameline, NULL, 0, TERM_SPACE | TERM_TAB);
+	if (!name)
+		return -1;
+	cp = strchr(name, '/');
+	if (!cp)
+		val = 0;
+	else if (prefix) {
+		/*
+		 * Does it begin with "a/$our-prefix" and such?  Then this is
+		 * very likely to apply to our directory.
+		 */
+		if (!strncmp(name, prefix, prefix_length))
+			val = count_slashes(prefix);
+		else {
+			cp++;
+			if (!strncmp(cp, prefix, prefix_length))
+				val = count_slashes(prefix) + 1;
+		}
+	}
+	free(name);
+	return val;
+}
+
 /*
  * Get the name etc info from the --/+++ lines of a traditional patch header
  *
- * NOTE! This hardcodes "-p1" behaviour in filename detection.
- *
  * FIXME! The end-of-filename heuristics are kind of screwy. For existing
  * files, we can happily check the index for a match, but for creating a
  * new file we should try to match whatever "patch" does. I have no idea.
@@ -326,6 +371,16 @@
 
 	first += 4;	/* skip "--- " */
 	second += 4;	/* skip "+++ " */
+	if (!p_value_known) {
+		int p, q;
+		p = guess_p_value(first);
+		q = guess_p_value(second);
+		if (p < 0) p = q;
+		if (0 <= p && p == q) {
+			p_value = p;
+			p_value_known = 1;
+		}
+	}
 	if (is_dev_null(first)) {
 		patch->is_new = 1;
 		patch->is_delete = 0;
@@ -787,6 +842,7 @@
 {
 	unsigned long offset, len;
 
+	patch->is_toplevel_relative = 0;
 	patch->is_rename = patch->is_copy = 0;
 	patch->is_new = patch->is_delete = -1;
 	patch->old_mode = patch->new_mode = 0;
@@ -831,6 +887,7 @@
 					die("git diff header lacks filename information (line %d)", linenr);
 				patch->old_name = patch->new_name = patch->def_name;
 			}
+			patch->is_toplevel_relative = 1;
 			*hdrsize = git_hdr_len;
 			return offset;
 		}
@@ -1129,11 +1186,11 @@
 
 	*status_p = 0;
 
-	if (!strncmp(buffer, "delta ", 6)) {
+	if (!prefixcmp(buffer, "delta ")) {
 		patch_method = BINARY_DELTA_DEFLATED;
 		origlen = strtoul(buffer + 6, NULL, 10);
 	}
-	else if (!strncmp(buffer, "literal ", 8)) {
+	else if (!prefixcmp(buffer, "literal ")) {
 		patch_method = BINARY_LITERAL_DEFLATED;
 		origlen = strtoul(buffer + 8, NULL, 10);
 	}
@@ -1393,28 +1450,39 @@
 	free(qname);
 }
 
-static int read_old_data(struct stat *st, const char *path, void *buf, unsigned long size)
+static int read_old_data(struct stat *st, const char *path, char **buf_p, unsigned long *alloc_p, unsigned long *size_p)
 {
 	int fd;
 	unsigned long got;
+	unsigned long nsize;
+	char *nbuf;
+	unsigned long size = *size_p;
+	char *buf = *buf_p;
 
 	switch (st->st_mode & S_IFMT) {
 	case S_IFLNK:
-		return readlink(path, buf, size);
+		return readlink(path, buf, size) != size;
 	case S_IFREG:
 		fd = open(path, O_RDONLY);
 		if (fd < 0)
 			return error("unable to open %s", path);
 		got = 0;
 		for (;;) {
-			int ret = xread(fd, (char *) buf + got, size - got);
+			int ret = xread(fd, buf + got, size - got);
 			if (ret <= 0)
 				break;
 			got += ret;
 		}
 		close(fd);
-		return got;
-
+		nsize = got;
+		nbuf = buf;
+		if (convert_to_git(path, &nbuf, &nsize)) {
+			free(buf);
+			*buf_p = nbuf;
+			*alloc_p = nsize;
+			*size_p = nsize;
+		}
+		return got != size;
 	default:
 		return -1;
 	}
@@ -1656,6 +1724,8 @@
 			/* Ignore it, we already handled it */
 			break;
 		default:
+			if (apply_verbosely)
+				error("invalid start of line: '%c'", first);
 			return -1;
 		}
 		patch += len;
@@ -1753,6 +1823,9 @@
 		}
 	}
 
+	if (offset && apply_verbosely)
+		error("while searching for:\n%.*s", oldsize, oldlines);
+
 	free(old);
 	free(new);
 	return offset;
@@ -1839,11 +1912,11 @@
 
 	if (has_sha1_file(sha1)) {
 		/* We already have the postimage */
-		char type[10];
+		enum object_type type;
 		unsigned long size;
 
 		free(desc->buffer);
-		desc->buffer = read_sha1_file(sha1, type, &size);
+		desc->buffer = read_sha1_file(sha1, &type, &size);
 		if (!desc->buffer)
 			return error("the necessary postimage %s for "
 				     "'%s' cannot be read",
@@ -1899,8 +1972,8 @@
 	buf = NULL;
 	if (cached) {
 		if (ce) {
-			char type[20];
-			buf = read_sha1_file(ce->sha1, type, &size);
+			enum object_type type;
+			buf = read_sha1_file(ce->sha1, &type, &size);
 			if (!buf)
 				return error("read of %s failed",
 					     patch->old_name);
@@ -1908,10 +1981,10 @@
 		}
 	}
 	else if (patch->old_name) {
-		size = st->st_size;
+		size = xsize_t(st->st_size);
 		alloc = size + 8192;
 		buf = xmalloc(alloc);
-		if (read_old_data(st, patch->old_name, buf, alloc) != size)
+		if (read_old_data(st, patch->old_name, &buf, &alloc, &size))
 			return error("read of %s failed", patch->old_name);
 	}
 
@@ -2233,7 +2306,7 @@
 	}
 }
 
-static void remove_file(struct patch *patch)
+static void remove_file(struct patch *patch, int rmdir_empty)
 {
 	if (write_index) {
 		if (remove_file_from_cache(patch->old_name) < 0)
@@ -2241,7 +2314,7 @@
 		cache_tree_invalidate_path(active_cache_tree, patch->old_name);
 	}
 	if (!cached) {
-		if (!unlink(patch->old_name)) {
+		if (!unlink(patch->old_name) && rmdir_empty) {
 			char *name = xstrdup(patch->old_name);
 			char *end = strrchr(name, '/');
 			while (end) {
@@ -2282,16 +2355,27 @@
 
 static int try_create_file(const char *path, unsigned int mode, const char *buf, unsigned long size)
 {
-	int fd;
+	int fd, converted;
+	char *nbuf;
+	unsigned long nsize;
 
-	if (S_ISLNK(mode))
+	if (has_symlinks && S_ISLNK(mode))
 		/* Although buf:size is counted string, it also is NUL
 		 * terminated.
 		 */
 		return symlink(buf, path);
+
 	fd = open(path, O_CREAT | O_EXCL | O_WRONLY, (mode & 0100) ? 0777 : 0666);
 	if (fd < 0)
 		return -1;
+
+	nsize = size;
+	nbuf = (char *) buf;
+	converted = convert_to_working_tree(path, &nbuf, &nsize);
+	if (converted) {
+		buf = nbuf;
+		size = nsize;
+	}
 	while (size) {
 		int written = xwrite(fd, buf, size);
 		if (written < 0)
@@ -2303,6 +2387,8 @@
 	}
 	if (close(fd) < 0)
 		die("closing file %s: %s", path, strerror(errno));
+	if (converted)
+		free(nbuf);
 	return 0;
 }
 
@@ -2374,7 +2460,7 @@
 {
 	if (patch->is_delete > 0) {
 		if (phase == 0)
-			remove_file(patch);
+			remove_file(patch, 1);
 		return;
 	}
 	if (patch->is_new > 0 || patch->is_copy) {
@@ -2387,7 +2473,7 @@
 	 * thing: remove the old, write the new
 	 */
 	if (phase == 0)
-		remove_file(patch);
+		remove_file(patch, 0);
 	if (phase == 1)
 		create_file(patch);
 }
@@ -2509,6 +2595,32 @@
 	return 1;
 }
 
+static void prefix_one(char **name)
+{
+	char *old_name = *name;
+	if (!old_name)
+		return;
+	*name = xstrdup(prefix_filename(prefix, prefix_length, *name));
+	free(old_name);
+}
+
+static void prefix_patches(struct patch *p)
+{
+	if (!prefix || p->is_toplevel_relative)
+		return;
+	for ( ; p; p = p->next) {
+		if (p->new_name == p->old_name) {
+			char *prefixed = p->new_name;
+			prefix_one(&prefixed);
+			p->new_name = p->old_name = prefixed;
+		}
+		else {
+			prefix_one(&p->new_name);
+			prefix_one(&p->old_name);
+		}
+	}
+}
+
 static int apply_patch(int fd, const char *filename, int inaccurate_eof)
 {
 	unsigned long offset, size;
@@ -2531,11 +2643,14 @@
 			break;
 		if (apply_in_reverse)
 			reverse_patches(patch);
+		if (prefix)
+			prefix_patches(patch);
 		if (use_patch(patch)) {
 			patch_stats(patch);
 			*listp = patch;
 			listp = &patch->next;
-		} else {
+		}
+		else {
 			/* perhaps free it a bit better? */
 			free(patch);
 			skipped_patch++;
@@ -2596,9 +2711,16 @@
 	int read_stdin = 1;
 	int inaccurate_eof = 0;
 	int errs = 0;
+	int is_not_gitdir = 0;
 
 	const char *whitespace_option = NULL;
 
+	prefix = setup_git_directory_gently(&is_not_gitdir);
+	prefix_length = prefix ? strlen(prefix) : 0;
+	git_config(git_apply_config);
+	if (apply_default_whitespace)
+		parse_whitespace_option(apply_default_whitespace);
+
 	for (i = 1; i < argc; i++) {
 		const char *arg = argv[i];
 		char *end;
@@ -2609,15 +2731,16 @@
 			read_stdin = 0;
 			continue;
 		}
-		if (!strncmp(arg, "--exclude=", 10)) {
+		if (!prefixcmp(arg, "--exclude=")) {
 			struct excludes *x = xmalloc(sizeof(*x));
 			x->path = arg + 10;
 			x->next = excludes;
 			excludes = x;
 			continue;
 		}
-		if (!strncmp(arg, "-p", 2)) {
+		if (!prefixcmp(arg, "-p")) {
 			p_value = atoi(arg + 2);
+			p_value_known = 1;
 			continue;
 		}
 		if (!strcmp(arg, "--no-add")) {
@@ -2649,10 +2772,14 @@
 			continue;
 		}
 		if (!strcmp(arg, "--index")) {
+			if (is_not_gitdir)
+				die("--index outside a repository");
 			check_index = 1;
 			continue;
 		}
 		if (!strcmp(arg, "--cached")) {
+			if (is_not_gitdir)
+				die("--cached outside a repository");
 			check_index = 1;
 			cached = 1;
 			continue;
@@ -2670,13 +2797,13 @@
 			line_termination = 0;
 			continue;
 		}
-		if (!strncmp(arg, "-C", 2)) {
+		if (!prefixcmp(arg, "-C")) {
 			p_context = strtoul(arg + 2, &end, 0);
 			if (*end != '\0')
 				die("unrecognized context count '%s'", arg + 2);
 			continue;
 		}
-		if (!strncmp(arg, "--whitespace=", 13)) {
+		if (!prefixcmp(arg, "--whitespace=")) {
 			whitespace_option = arg + 13;
 			parse_whitespace_option(arg + 13);
 			continue;
@@ -2693,7 +2820,7 @@
 			apply = apply_with_reject = apply_verbosely = 1;
 			continue;
 		}
-		if (!strcmp(arg, "--verbose")) {
+		if (!strcmp(arg, "-v") || !strcmp(arg, "--verbose")) {
 			apply_verbosely = 1;
 			continue;
 		}
@@ -2701,14 +2828,6 @@
 			inaccurate_eof = 1;
 			continue;
 		}
-
-		if (check_index && prefix_length < 0) {
-			prefix = setup_git_directory();
-			prefix_length = prefix ? strlen(prefix) : 0;
-			git_config(git_apply_config);
-			if (!whitespace_option && apply_default_whitespace)
-				parse_whitespace_option(apply_default_whitespace);
-		}
 		if (0 < prefix_length)
 			arg = prefix_filename(prefix, prefix_length, arg);
 
diff --git a/builtin-archive.c b/builtin-archive.c
index 5265764..2fae885 100644
--- a/builtin-archive.c
+++ b/builtin-archive.c
@@ -35,7 +35,7 @@
 
 	for (i = 1; i < argc; i++) {
 		const char *arg = argv[i];
-		if (!strncmp("--exec=", arg, 7)) {
+		if (!prefixcmp(arg, "--exec=")) {
 			if (exec_at)
 				die("multiple --exec specified");
 			exec = arg + 7;
@@ -62,7 +62,7 @@
 	if (buf[len-1] == '\n')
 		buf[--len] = 0;
 	if (strcmp(buf, "ACK")) {
-		if (len > 5 && !strncmp(buf, "NACK ", 5))
+		if (len > 5 && !prefixcmp(buf, "NACK "))
 			die("git-archive: NACK %s", buf + 5);
 		die("git-archive: protocol error");
 	}
@@ -166,11 +166,11 @@
 			verbose = 1;
 			continue;
 		}
-		if (!strncmp(arg, "--format=", 9)) {
+		if (!prefixcmp(arg, "--format=")) {
 			format = arg + 9;
 			continue;
 		}
-		if (!strncmp(arg, "--prefix=", 9)) {
+		if (!prefixcmp(arg, "--prefix=")) {
 			base = arg + 9;
 			continue;
 		}
@@ -218,7 +218,7 @@
 		if (!strcmp(arg, "--"))
 			no_more_options = 1;
 		if (!no_more_options) {
-			if (!strncmp(arg, "--remote=", 9)) {
+			if (!prefixcmp(arg, "--remote=")) {
 				if (remote)
 					die("Multiple --remote specified");
 				remote = arg + 9;
diff --git a/builtin-blame.c b/builtin-blame.c
index 1a752b9..60ec535 100644
--- a/builtin-blame.c
+++ b/builtin-blame.c
@@ -87,9 +87,9 @@
 static char *fill_origin_blob(struct origin *o, mmfile_t *file)
 {
 	if (!o->file.ptr) {
-		char type[10];
+		enum object_type type;
 		num_read_blob++;
-		file->ptr = read_sha1_file(o->blob_sha1, type,
+		file->ptr = read_sha1_file(o->blob_sha1, &type,
 					   (unsigned long *)(&(file->size)));
 		o->file = *file;
 	}
@@ -180,16 +180,15 @@
 	int *lineno;
 };
 
-static int cmp_suspect(struct origin *a, struct origin *b)
+static inline int same_suspect(struct origin *a, struct origin *b)
 {
-	int cmp = hashcmp(a->commit->object.sha1, b->commit->object.sha1);
-	if (cmp)
-		return cmp;
-	return strcmp(a->path, b->path);
+	if (a == b)
+		return 1;
+	if (a->commit != b->commit)
+		return 0;
+	return !strcmp(a->path, b->path);
 }
 
-#define cmp_suspect(a, b) ( ((a)==(b)) ? 0 : cmp_suspect(a,b) )
-
 static void sanity_check_refcnt(struct scoreboard *);
 
 /*
@@ -202,7 +201,7 @@
 	struct blame_entry *ent, *next;
 
 	for (ent = sb->ent; ent && (next = ent->next); ent = next) {
-		if (!cmp_suspect(ent->suspect, next->suspect) &&
+		if (same_suspect(ent->suspect, next->suspect) &&
 		    ent->guilty == next->guilty &&
 		    ent->s_lno + ent->num_lines == next->s_lno) {
 			ent->num_lines += next->num_lines;
@@ -263,7 +262,6 @@
 static int fill_blob_sha1(struct origin *origin)
 {
 	unsigned mode;
-	char type[10];
 
 	if (!is_null_sha1(origin->blob_sha1))
 		return 0;
@@ -271,8 +269,7 @@
 			   origin->path,
 			   origin->blob_sha1, &mode))
 		goto error_out;
-	if (sha1_object_info(origin->blob_sha1, type, NULL) ||
-	    strcmp(type, blob_type))
+	if (sha1_object_info(origin->blob_sha1, NULL) != OBJ_BLOB)
 		goto error_out;
 	return 0;
  error_out:
@@ -777,7 +774,7 @@
 	int last_in_target = -1;
 
 	for (e = sb->ent; e; e = e->next) {
-		if (e->guilty || cmp_suspect(e->suspect, target))
+		if (e->guilty || !same_suspect(e->suspect, target))
 			continue;
 		if (last_in_target < e->s_lno + e->num_lines)
 			last_in_target = e->s_lno + e->num_lines;
@@ -797,7 +794,7 @@
 	struct blame_entry *e;
 
 	for (e = sb->ent; e; e = e->next) {
-		if (e->guilty || cmp_suspect(e->suspect, target))
+		if (e->guilty || !same_suspect(e->suspect, target))
 			continue;
 		if (same <= e->s_lno)
 			continue;
@@ -972,7 +969,7 @@
 	while (made_progress) {
 		made_progress = 0;
 		for (e = sb->ent; e; e = e->next) {
-			if (e->guilty || cmp_suspect(e->suspect, target))
+			if (e->guilty || !same_suspect(e->suspect, target))
 				continue;
 			find_copy_in_blob(sb, e, parent, split, &file_p);
 			if (split[1].suspect &&
@@ -1004,12 +1001,12 @@
 	struct blame_list *blame_list = NULL;
 
 	for (e = sb->ent, num_ents = 0; e; e = e->next)
-		if (!e->guilty && !cmp_suspect(e->suspect, target))
+		if (!e->guilty && same_suspect(e->suspect, target))
 			num_ents++;
 	if (num_ents) {
 		blame_list = xcalloc(num_ents, sizeof(struct blame_list));
 		for (e = sb->ent, i = 0; e; e = e->next)
-			if (!e->guilty && !cmp_suspect(e->suspect, target))
+			if (!e->guilty && same_suspect(e->suspect, target))
 				blame_list[i++].ent = e;
 	}
 	*num_ents_p = num_ents;
@@ -1139,7 +1136,7 @@
 		origin->file.ptr = NULL;
 	}
 	for (e = sb->ent; e; e = e->next) {
-		if (cmp_suspect(e->suspect, origin))
+		if (!same_suspect(e->suspect, origin))
 			continue;
 		origin_incref(porigin);
 		origin_decref(e->suspect);
@@ -1246,26 +1243,26 @@
  */
 struct commit_info
 {
-	char *author;
-	char *author_mail;
+	const char *author;
+	const char *author_mail;
 	unsigned long author_time;
-	char *author_tz;
+	const char *author_tz;
 
 	/* filled only when asked for details */
-	char *committer;
-	char *committer_mail;
+	const char *committer;
+	const char *committer_mail;
 	unsigned long committer_time;
-	char *committer_tz;
+	const char *committer_tz;
 
-	char *summary;
+	const char *summary;
 };
 
 /*
  * Parse author/committer line in the commit object buffer
  */
 static void get_ac_line(const char *inbuf, const char *what,
-			int bufsz, char *person, char **mail,
-			unsigned long *time, char **tz)
+			int bufsz, char *person, const char **mail,
+			unsigned long *time, const char **tz)
 {
 	int len;
 	char *tmp, *endp;
@@ -1282,7 +1279,7 @@
 	if (bufsz <= len) {
 	error_out:
 		/* Ugh */
-		person = *mail = *tz = "(unknown)";
+		*mail = *tz = "(unknown)";
 		*time = 0;
 		return;
 	}
@@ -1322,10 +1319,10 @@
 	 * we now need to populate them for output.
 	 */
 	if (!commit->buffer) {
-		char type[20];
+		enum object_type type;
 		unsigned long size;
 		commit->buffer =
-			read_sha1_file(commit->object.sha1, type, &size);
+			read_sha1_file(commit->object.sha1, &type, &size);
 	}
 	ret->author = author_buf;
 	get_ac_line(commit->buffer, "\nauthor ",
@@ -1445,7 +1442,7 @@
 
 		/* Take responsibility for the remaining entries */
 		for (ent = sb->ent; ent; ent = ent->next)
-			if (!cmp_suspect(ent->suspect, suspect))
+			if (same_suspect(ent->suspect, suspect))
 				found_guilty_entry(ent);
 		origin_decref(suspect);
 
@@ -1965,7 +1962,7 @@
 				die("Cannot lstat %s", path);
 			read_from = path;
 		}
-		fin_size = st.st_size;
+		fin_size = xsize_t(st.st_size);
 		buf = xmalloc(fin_size+1);
 		mode = canon_mode(st.st_mode);
 		switch (st.st_mode & S_IFMT) {
@@ -2006,7 +2003,7 @@
 	buf[fin_size] = 0;
 	origin->file.ptr = buf;
 	origin->file.size = fin_size;
-	pretend_sha1_file(buf, fin_size, blob_type, origin->blob_sha1);
+	pretend_sha1_file(buf, fin_size, OBJ_BLOB, origin->blob_sha1);
 	commit->util = origin;
 
 	/*
@@ -2065,9 +2062,10 @@
 	int i, seen_dashdash, unk, opt;
 	long bottom, top, lno;
 	int output_option = 0;
+	int show_stats = 0;
 	const char *revs_file = NULL;
 	const char *final_commit_name = NULL;
-	char type[10];
+	enum object_type type;
 	const char *bottomtop = NULL;
 	const char *contents_from = NULL;
 
@@ -2086,6 +2084,8 @@
 			blank_boundary = 1;
 		else if (!strcmp("--root", arg))
 			show_root = 1;
+		else if (!strcmp(arg, "--show-stats"))
+			show_stats = 1;
 		else if (!strcmp("-c", arg))
 			output_option |= OUTPUT_ANNOTATE_COMPAT;
 		else if (!strcmp("-t", arg))
@@ -2094,17 +2094,17 @@
 			output_option |= OUTPUT_LONG_OBJECT_NAME;
 		else if (!strcmp("-S", arg) && ++i < argc)
 			revs_file = argv[i];
-		else if (!strncmp("-M", arg, 2)) {
+		else if (!prefixcmp(arg, "-M")) {
 			opt |= PICKAXE_BLAME_MOVE;
 			blame_move_score = parse_score(arg+2);
 		}
-		else if (!strncmp("-C", arg, 2)) {
+		else if (!prefixcmp(arg, "-C")) {
 			if (opt & PICKAXE_BLAME_COPY)
 				opt |= PICKAXE_BLAME_COPY_HARDER;
 			opt |= PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE;
 			blame_copy_score = parse_score(arg+2);
 		}
-		else if (!strncmp("-L", arg, 2)) {
+		else if (!prefixcmp(arg, "-L")) {
 			if (!arg[2]) {
 				if (++i >= argc)
 					usage(blame_usage);
@@ -2299,7 +2299,7 @@
 		if (fill_blob_sha1(o))
 			die("no such path %s in %s", path, final_commit_name);
 
-		sb.final_buf = read_sha1_file(o->blob_sha1, type,
+		sb.final_buf = read_sha1_file(o->blob_sha1, &type,
 					      &sb.final_buf_size);
 	}
 	num_read_blob++;
@@ -2351,7 +2351,7 @@
 		ent = e;
 	}
 
-	if (DEBUG) {
+	if (show_stats) {
 		printf("num read blob: %d\n", num_read_blob);
 		printf("num get patch: %d\n", num_get_patch);
 		printf("num commits: %d\n", num_commits);
diff --git a/builtin-branch.c b/builtin-branch.c
index 2d8d61b..a4494ee 100644
--- a/builtin-branch.c
+++ b/builtin-branch.c
@@ -12,7 +12,7 @@
 #include "builtin.h"
 
 static const char builtin_branch_usage[] =
-  "git-branch [-r] (-d | -D) <branchname> | [-l] [-f] <branchname> [<start-point>] | (-m | -M) [<oldbranch>] <newbranch> | [--color | --no-color] [-r | -a] [-v [--abbrev=<length>]]";
+  "git-branch [-r] (-d | -D) <branchname> | [--track | --no-track] [-l] [-f] <branchname> [<start-point>] | (-m | -M) [<oldbranch>] <newbranch> | [--color | --no-color] [-r | -a] [-v [--abbrev=<length> | --no-abbrev]]";
 
 #define REF_UNKNOWN_TYPE    0x00
 #define REF_LOCAL_BRANCH    0x01
@@ -22,6 +22,8 @@
 static const char *head;
 static unsigned char head_sha1[20];
 
+static int branch_track_remotes;
+
 static int branch_use_color;
 static char branch_colors[][COLOR_MAXLEN] = {
 	"\033[m",	/* reset */
@@ -59,11 +61,14 @@
 		branch_use_color = git_config_colorbool(var, value);
 		return 0;
 	}
-	if (!strncmp(var, "color.branch.", 13)) {
+	if (!prefixcmp(var, "color.branch.")) {
 		int slot = parse_branch_color_slot(var, 13);
 		color_parse(value, var, branch_colors[slot]);
 		return 0;
 	}
+	if (!strcmp(var, "branch.autosetupmerge"))
+		branch_track_remotes = git_config_bool(var, value);
+
 	return git_default_config(var, value);
 }
 
@@ -134,7 +139,7 @@
 		 */
 
 		if (!force &&
-		    !in_merge_bases(rev, head_rev)) {
+		    !in_merge_bases(rev, &head_rev, 1)) {
 			error("The branch '%s' is not a strict subset of "
 				"your current HEAD.\n"
 				"If you are sure you want to delete it, "
@@ -178,13 +183,13 @@
 	int len;
 
 	/* Detect kind */
-	if (!strncmp(refname, "refs/heads/", 11)) {
+	if (!prefixcmp(refname, "refs/heads/")) {
 		kind = REF_LOCAL_BRANCH;
 		refname += 11;
-	} else if (!strncmp(refname, "refs/remotes/", 13)) {
+	} else if (!prefixcmp(refname, "refs/remotes/")) {
 		kind = REF_REMOTE_BRANCH;
 		refname += 13;
-	} else if (!strncmp(refname, "refs/tags/", 10)) {
+	} else if (!prefixcmp(refname, "refs/tags/")) {
 		kind = REF_TAG;
 		refname += 10;
 	}
@@ -289,12 +294,13 @@
 	detached = (detached && (kinds & REF_LOCAL_BRANCH));
 	if (detached) {
 		struct ref_item item;
-		item.name = "(no branch)";
+		item.name = xstrdup("(no branch)");
 		item.kind = REF_LOCAL_BRANCH;
 		hashcpy(item.sha1, head_sha1);
 		if (strlen(item.name) > ref_list.maxwidth)
 			      ref_list.maxwidth = strlen(item.name);
 		print_ref_item(&item, ref_list.maxwidth, verbose, abbrev, 1);
+		free(item.name);
 	}
 
 	for (i = 0; i < ref_list.index; i++) {
@@ -308,14 +314,119 @@
 	free_ref_list(&ref_list);
 }
 
+static char *config_repo;
+static char *config_remote;
+static const char *start_ref;
+static int start_len;
+static int base_len;
+
+static int get_remote_branch_name(const char *value)
+{
+	const char *colon;
+	const char *end;
+
+	if (*value == '+')
+		value++;
+
+	colon = strchr(value, ':');
+	if (!colon)
+		return 0;
+
+	end = value + strlen(value);
+
+	/* Try an exact match first.  */
+	if (!strcmp(colon + 1, start_ref)) {
+		/* Truncate the value before the colon.  */
+		nfasprintf(&config_repo, "%.*s", colon - value, value);
+		return 1;
+	}
+
+	/* Try with a wildcard match now.  */
+	if (end - value > 2 && end[-2] == '/' && end[-1] == '*' &&
+	    colon - value > 2 && colon[-2] == '/' && colon[-1] == '*' &&
+	    (end - 2) - (colon + 1) == base_len &&
+	    !strncmp(colon + 1, start_ref, base_len)) {
+		/* Replace the star with the remote branch name.  */
+		nfasprintf(&config_repo, "%.*s%s",
+			   (colon - 2) - value, value,
+			   start_ref + base_len);
+		return 1;
+	}
+
+	return 0;
+}
+
+static int get_remote_config(const char *key, const char *value)
+{
+	const char *var;
+	if (prefixcmp(key, "remote."))
+		return 0;
+
+	var = strrchr(key, '.');
+	if (var == key + 6)
+		return 0;
+
+	if (!strcmp(var, ".fetch") && get_remote_branch_name(value))
+		nfasprintf(&config_remote, "%.*s", var - (key + 7), key + 7);
+
+	return 0;
+}
+
+static void set_branch_merge(const char *name, const char *config_remote,
+			     const char *config_repo)
+{
+	char key[1024];
+	if (sizeof(key) <=
+	    snprintf(key, sizeof(key), "branch.%s.remote", name))
+		die("what a long branch name you have!");
+	git_config_set(key, config_remote);
+
+	/*
+	 * We do not have to check if we have enough space for
+	 * the 'merge' key, since it's shorter than the
+	 * previous 'remote' key, which we already checked.
+	 */
+	snprintf(key, sizeof(key), "branch.%s.merge", name);
+	git_config_set(key, config_repo);
+}
+
+static void set_branch_defaults(const char *name, const char *real_ref)
+{
+	const char *slash = strrchr(real_ref, '/');
+
+	if (!slash)
+		return;
+
+	start_ref = real_ref;
+	start_len = strlen(real_ref);
+	base_len = slash - real_ref;
+	git_config(get_remote_config);
+	if (!config_repo && !config_remote &&
+	    !prefixcmp(real_ref, "refs/heads/")) {
+		set_branch_merge(name, ".", real_ref);
+		printf("Branch %s set up to track local branch %s.\n",
+		       name, real_ref);
+	}
+
+	if (config_repo && config_remote) {
+		set_branch_merge(name, config_remote, config_repo);
+		printf("Branch %s set up to track remote branch %s.\n",
+		       name, real_ref);
+	}
+
+	if (config_repo)
+		free(config_repo);
+	if (config_remote)
+		free(config_remote);
+}
+
 static void create_branch(const char *name, const char *start_name,
-			  unsigned char *start_sha1,
-			  int force, int reflog)
+			  int force, int reflog, int track)
 {
 	struct ref_lock *lock;
 	struct commit *commit;
 	unsigned char sha1[20];
-	char ref[PATH_MAX], msg[PATH_MAX + 20];
+	char *real_ref, ref[PATH_MAX], msg[PATH_MAX + 20];
 	int forcing = 0;
 
 	snprintf(ref, sizeof ref, "refs/heads/%s", name);
@@ -330,12 +441,23 @@
 		forcing = 1;
 	}
 
-	if (start_sha1)
-		/* detached HEAD */
-		hashcpy(sha1, start_sha1);
-	else if (get_sha1(start_name, sha1))
+	real_ref = NULL;
+	if (get_sha1(start_name, sha1))
 		die("Not a valid object name: '%s'.", start_name);
 
+	switch (dwim_ref(start_name, strlen(start_name), sha1, &real_ref)) {
+	case 0:
+		/* Not branching from any existing branch */
+		real_ref = NULL;
+		break;
+	case 1:
+		/* Unique completion -- good */
+		break;
+	default:
+		die("Ambiguous object name: '%s'.", start_name);
+		break;
+	}
+
 	if ((commit = lookup_commit_reference(sha1)) == NULL)
 		die("Not a valid branch point: '%s'.", start_name);
 	hashcpy(sha1, commit->object.sha1);
@@ -354,8 +476,17 @@
 		snprintf(msg, sizeof msg, "branch: Created from %s",
 			 start_name);
 
+	/* When branching off a remote branch, set up so that git-pull
+	   automatically merges from there.  So far, this is only done for
+	   remotes registered via .git/config.  */
+	if (real_ref && track)
+		set_branch_defaults(name, real_ref);
+
 	if (write_ref_sha1(lock, sha1, msg) < 0)
 		die("Failed to write ref: %s.", strerror(errno));
+
+	if (real_ref)
+		free(real_ref);
 }
 
 static void rename_branch(const char *oldname, const char *newname, int force)
@@ -397,11 +528,12 @@
 	int delete = 0, force_delete = 0, force_create = 0;
 	int rename = 0, force_rename = 0;
 	int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0;
-	int reflog = 0;
+	int reflog = 0, track;
 	int kinds = REF_LOCAL_BRANCH;
 	int i;
 
 	git_config(git_branch_config);
+	track = branch_track_remotes;
 
 	for (i = 1; i < argc; i++) {
 		const char *arg = argv[i];
@@ -412,6 +544,14 @@
 			i++;
 			break;
 		}
+		if (!strcmp(arg, "--track")) {
+			track = 1;
+			continue;
+		}
+		if (!strcmp(arg, "--no-track")) {
+			track = 0;
+			continue;
+		}
 		if (!strcmp(arg, "-d")) {
 			delete = 1;
 			continue;
@@ -446,8 +586,16 @@
 			reflog = 1;
 			continue;
 		}
-		if (!strncmp(arg, "--abbrev=", 9)) {
-			abbrev = atoi(arg+9);
+		if (!prefixcmp(arg, "--no-abbrev")) {
+			abbrev = 0;
+			continue;
+		}
+		if (!prefixcmp(arg, "--abbrev=")) {
+			abbrev = strtoul(arg + 9, NULL, 10);
+			if (abbrev < MINIMUM_ABBREV)
+				abbrev = MINIMUM_ABBREV;
+			else if (abbrev > 40)
+				abbrev = 40;
 			continue;
 		}
 		if (!strcmp(arg, "-v")) {
@@ -476,7 +624,7 @@
 		detached = 1;
 	}
 	else {
-		if (strncmp(head, "refs/heads/", 11))
+		if (prefixcmp(head, "refs/heads/"))
 			die("HEAD not found below refs/heads!");
 		head += 11;
 	}
@@ -489,10 +637,9 @@
 		rename_branch(head, argv[i], force_rename);
 	else if (rename && (i == argc - 2))
 		rename_branch(argv[i], argv[i + 1], force_rename);
-	else if (i == argc - 1)
-		create_branch(argv[i], head, head_sha1, force_create, reflog);
-	else if (i == argc - 2)
-		create_branch(argv[i], argv[i+1], NULL, force_create, reflog);
+	else if (i == argc - 1 || i == argc - 2)
+		create_branch(argv[i], (i == argc - 2) ? argv[i+1] : head,
+			      force_create, reflog, track);
 	else
 		usage(builtin_branch_usage);
 
diff --git a/builtin-bundle.c b/builtin-bundle.c
new file mode 100644
index 0000000..d1635a0
--- /dev/null
+++ b/builtin-bundle.c
@@ -0,0 +1,380 @@
+#include "cache.h"
+#include "object.h"
+#include "commit.h"
+#include "diff.h"
+#include "revision.h"
+#include "list-objects.h"
+#include "run-command.h"
+
+/*
+ * Basic handler for bundle files to connect repositories via sneakernet.
+ * Invocation must include action.
+ * This function can create a bundle or provide information on an existing
+ * bundle supporting git-fetch, git-pull, and git-ls-remote
+ */
+
+static const char *bundle_usage="git-bundle (create <bundle> <git-rev-list args> | verify <bundle> | list-heads <bundle> [refname]... | unbundle <bundle> [refname]... )";
+
+static const char bundle_signature[] = "# v2 git bundle\n";
+
+struct ref_list {
+	unsigned int nr, alloc;
+	struct ref_list_entry {
+		unsigned char sha1[20];
+		char *name;
+	} *list;
+};
+
+static void add_to_ref_list(const unsigned char *sha1, const char *name,
+		struct ref_list *list)
+{
+	if (list->nr + 1 >= list->alloc) {
+		list->alloc = alloc_nr(list->nr + 1);
+		list->list = xrealloc(list->list,
+				list->alloc * sizeof(list->list[0]));
+	}
+	memcpy(list->list[list->nr].sha1, sha1, 20);
+	list->list[list->nr].name = xstrdup(name);
+	list->nr++;
+}
+
+struct bundle_header {
+	struct ref_list prerequisites;
+	struct ref_list references;
+};
+
+/* this function returns the length of the string */
+static int read_string(int fd, char *buffer, int size)
+{
+	int i;
+	for (i = 0; i < size - 1; i++) {
+		int count = xread(fd, buffer + i, 1);
+		if (count < 0)
+			return error("Read error: %s", strerror(errno));
+		if (count == 0) {
+			i--;
+			break;
+		}
+		if (buffer[i] == '\n')
+			break;
+	}
+	buffer[i + 1] = '\0';
+	return i + 1;
+}
+
+/* returns an fd */
+static int read_header(const char *path, struct bundle_header *header) {
+	char buffer[1024];
+	int fd = open(path, O_RDONLY);
+
+	if (fd < 0)
+		return error("could not open '%s'", path);
+	if (read_string(fd, buffer, sizeof(buffer)) < 0 ||
+			strcmp(buffer, bundle_signature)) {
+		close(fd);
+		return error("'%s' does not look like a v2 bundle file", path);
+	}
+	while (read_string(fd, buffer, sizeof(buffer)) > 0
+			&& buffer[0] != '\n') {
+		int is_prereq = buffer[0] == '-';
+		int offset = is_prereq ? 1 : 0;
+		int len = strlen(buffer);
+		unsigned char sha1[20];
+		struct ref_list *list = is_prereq ? &header->prerequisites
+			: &header->references;
+		char delim;
+
+		if (buffer[len - 1] == '\n')
+			buffer[len - 1] = '\0';
+		if (get_sha1_hex(buffer + offset, sha1)) {
+			warning("unrecognized header: %s", buffer);
+			continue;
+		}
+		delim = buffer[40 + offset];
+		if (!isspace(delim) && (delim != '\0' || !is_prereq))
+			die ("invalid header: %s", buffer);
+		add_to_ref_list(sha1, isspace(delim) ?
+				buffer + 41 + offset : "", list);
+	}
+	return fd;
+}
+
+static int list_refs(struct ref_list *r, int argc, const char **argv)
+{
+	int i;
+
+	for (i = 0; i < r->nr; i++) {
+		if (argc > 1) {
+			int j;
+			for (j = 1; j < argc; j++)
+				if (!strcmp(r->list[i].name, argv[j]))
+					break;
+			if (j == argc)
+				continue;
+		}
+		printf("%s %s\n", sha1_to_hex(r->list[i].sha1),
+				r->list[i].name);
+	}
+	return 0;
+}
+
+#define PREREQ_MARK (1u<<16)
+
+static int verify_bundle(struct bundle_header *header, int verbose)
+{
+	/*
+	 * Do fast check, then if any prereqs are missing then go line by line
+	 * to be verbose about the errors
+	 */
+	struct ref_list *p = &header->prerequisites;
+	struct rev_info revs;
+	const char *argv[] = {NULL, "--all"};
+	struct object_array refs;
+	struct commit *commit;
+	int i, ret = 0, req_nr;
+	const char *message = "Repository lacks these prerequisite commits:";
+
+	init_revisions(&revs, NULL);
+	for (i = 0; i < p->nr; i++) {
+		struct ref_list_entry *e = p->list + i;
+		struct object *o = parse_object(e->sha1);
+		if (o) {
+			o->flags |= PREREQ_MARK;
+			add_pending_object(&revs, o, e->name);
+			continue;
+		}
+		if (++ret == 1)
+			error(message);
+		error("%s %s", sha1_to_hex(e->sha1), e->name);
+	}
+	if (revs.pending.nr != p->nr)
+		return ret;
+	req_nr = revs.pending.nr;
+	setup_revisions(2, argv, &revs, NULL);
+
+	memset(&refs, 0, sizeof(struct object_array));
+	for (i = 0; i < revs.pending.nr; i++) {
+		struct object_array_entry *e = revs.pending.objects + i;
+		add_object_array(e->item, e->name, &refs);
+	}
+
+	prepare_revision_walk(&revs);
+
+	i = req_nr;
+	while (i && (commit = get_revision(&revs)))
+		if (commit->object.flags & PREREQ_MARK)
+			i--;
+
+	for (i = 0; i < req_nr; i++)
+		if (!(refs.objects[i].item->flags & SHOWN)) {
+			if (++ret == 1)
+				error(message);
+			error("%s %s", sha1_to_hex(refs.objects[i].item->sha1),
+				refs.objects[i].name);
+		}
+
+	for (i = 0; i < refs.nr; i++)
+		clear_commit_marks((struct commit *)refs.objects[i].item, -1);
+
+	if (verbose) {
+		struct ref_list *r;
+
+		r = &header->references;
+		printf("The bundle contains %d ref%s\n",
+		       r->nr, (1 < r->nr) ? "s" : "");
+		list_refs(r, 0, NULL);
+		r = &header->prerequisites;
+		printf("The bundle requires these %d ref%s\n",
+		       r->nr, (1 < r->nr) ? "s" : "");
+		list_refs(r, 0, NULL);
+	}
+	return ret;
+}
+
+static int list_heads(struct bundle_header *header, int argc, const char **argv)
+{
+	return list_refs(&header->references, argc, argv);
+}
+
+static int create_bundle(struct bundle_header *header, const char *path,
+		int argc, const char **argv)
+{
+	int bundle_fd = -1;
+	const char **argv_boundary = xmalloc((argc + 4) * sizeof(const char *));
+	const char **argv_pack = xmalloc(5 * sizeof(const char *));
+	int i, ref_count = 0;
+	char buffer[1024];
+	struct rev_info revs;
+	struct child_process rls;
+
+	bundle_fd = (!strcmp(path, "-") ? 1 :
+			open(path, O_CREAT | O_EXCL | O_WRONLY, 0666));
+	if (bundle_fd < 0)
+		return error("Could not create '%s': %s", path, strerror(errno));
+
+	/* write signature */
+	write_or_die(bundle_fd, bundle_signature, strlen(bundle_signature));
+
+	/* init revs to list objects for pack-objects later */
+	save_commit_buffer = 0;
+	init_revisions(&revs, NULL);
+
+	/* write prerequisites */
+	memcpy(argv_boundary + 3, argv + 1, argc * sizeof(const char *));
+	argv_boundary[0] = "rev-list";
+	argv_boundary[1] = "--boundary";
+	argv_boundary[2] = "--pretty=oneline";
+	argv_boundary[argc + 2] = NULL;
+	memset(&rls, 0, sizeof(rls));
+	rls.argv = argv_boundary;
+	rls.out = -1;
+	rls.git_cmd = 1;
+	if (start_command(&rls))
+		return -1;
+	while ((i = read_string(rls.out, buffer, sizeof(buffer))) > 0) {
+		unsigned char sha1[20];
+		if (buffer[0] == '-') {
+			write_or_die(bundle_fd, buffer, i);
+			if (!get_sha1_hex(buffer + 1, sha1)) {
+				struct object *object = parse_object(sha1);
+				object->flags |= UNINTERESTING;
+				add_pending_object(&revs, object, buffer);
+			}
+		} else if (!get_sha1_hex(buffer, sha1)) {
+			struct object *object = parse_object(sha1);
+			object->flags |= SHOWN;
+		}
+	}
+	if (finish_command(&rls))
+		return error("rev-list died");
+
+	/* write references */
+	argc = setup_revisions(argc, argv, &revs, NULL);
+	if (argc > 1)
+		return error("unrecognized argument: %s'", argv[1]);
+
+	for (i = 0; i < revs.pending.nr; i++) {
+		struct object_array_entry *e = revs.pending.objects + i;
+		unsigned char sha1[20];
+		char *ref;
+
+		if (e->item->flags & UNINTERESTING)
+			continue;
+		if (dwim_ref(e->name, strlen(e->name), sha1, &ref) != 1)
+			continue;
+		/*
+		 * Make sure the refs we wrote out is correct; --max-count and
+		 * other limiting options could have prevented all the tips
+		 * from getting output.
+		 */
+		if (!(e->item->flags & SHOWN)) {
+			warning("ref '%s' is excluded by the rev-list options",
+				e->name);
+			continue;
+		}
+		ref_count++;
+		write_or_die(bundle_fd, sha1_to_hex(e->item->sha1), 40);
+		write_or_die(bundle_fd, " ", 1);
+		write_or_die(bundle_fd, ref, strlen(ref));
+		write_or_die(bundle_fd, "\n", 1);
+		free(ref);
+	}
+	if (!ref_count)
+		die ("Refusing to create empty bundle.");
+
+	/* end header */
+	write_or_die(bundle_fd, "\n", 1);
+
+	/* write pack */
+	argv_pack[0] = "pack-objects";
+	argv_pack[1] = "--all-progress";
+	argv_pack[2] = "--stdout";
+	argv_pack[3] = "--thin";
+	argv_pack[4] = NULL;
+	memset(&rls, 0, sizeof(rls));
+	rls.argv = argv_pack;
+	rls.in = -1;
+	rls.out = bundle_fd;
+	rls.git_cmd = 1;
+	if (start_command(&rls))
+		return error("Could not spawn pack-objects");
+	for (i = 0; i < revs.pending.nr; i++) {
+		struct object *object = revs.pending.objects[i].item;
+		if (object->flags & UNINTERESTING)
+			write(rls.in, "^", 1);
+		write(rls.in, sha1_to_hex(object->sha1), 40);
+		write(rls.in, "\n", 1);
+	}
+	if (finish_command(&rls))
+		return error ("pack-objects died");
+	return 0;
+}
+
+static int unbundle(struct bundle_header *header, int bundle_fd,
+		int argc, const char **argv)
+{
+	const char *argv_index_pack[] = {"index-pack",
+		"--fix-thin", "--stdin", NULL};
+	struct child_process ip;
+
+	if (verify_bundle(header, 0))
+		return -1;
+	memset(&ip, 0, sizeof(ip));
+	ip.argv = argv_index_pack;
+	ip.in = bundle_fd;
+	ip.no_stdout = 1;
+	ip.git_cmd = 1;
+	if (run_command(&ip))
+		return error("index-pack died");
+	return list_heads(header, argc, argv);
+}
+
+int cmd_bundle(int argc, const char **argv, const char *prefix)
+{
+	struct bundle_header header;
+	int nongit = 0;
+	const char *cmd, *bundle_file;
+	int bundle_fd = -1;
+	char buffer[PATH_MAX];
+
+	if (argc < 3)
+		usage(bundle_usage);
+
+	cmd = argv[1];
+	bundle_file = argv[2];
+	argc -= 2;
+	argv += 2;
+
+	prefix = setup_git_directory_gently(&nongit);
+	if (prefix && bundle_file[0] != '/') {
+		snprintf(buffer, sizeof(buffer), "%s/%s", prefix, bundle_file);
+		bundle_file = buffer;
+	}
+
+	memset(&header, 0, sizeof(header));
+	if (strcmp(cmd, "create") &&
+			(bundle_fd = read_header(bundle_file, &header)) < 0)
+		return 1;
+
+	if (!strcmp(cmd, "verify")) {
+		close(bundle_fd);
+		if (verify_bundle(&header, 1))
+			return 1;
+		fprintf(stderr, "%s is okay\n", bundle_file);
+		return 0;
+	}
+	if (!strcmp(cmd, "list-heads")) {
+		close(bundle_fd);
+		return !!list_heads(&header, argc, argv);
+	}
+	if (!strcmp(cmd, "create")) {
+		if (nongit)
+			die("Need a repository to create a bundle.");
+		return !!create_bundle(&header, bundle_file, argc, argv);
+	} else if (!strcmp(cmd, "unbundle")) {
+		if (nongit)
+			die("Need a repository to unbundle.");
+		return !!unbundle(&header, bundle_fd, argc, argv);
+	} else
+		usage(bundle_usage);
+}
diff --git a/builtin-cat-file.c b/builtin-cat-file.c
index 6c16bfa..d61d3d5 100644
--- a/builtin-cat-file.c
+++ b/builtin-cat-file.c
@@ -79,7 +79,7 @@
 int cmd_cat_file(int argc, const char **argv, const char *prefix)
 {
 	unsigned char sha1[20];
-	char type[20];
+	enum object_type type;
 	void *buf;
 	unsigned long size;
 	int opt;
@@ -100,14 +100,16 @@
 	buf = NULL;
 	switch (opt) {
 	case 't':
-		if (!sha1_object_info(sha1, type, NULL)) {
-			printf("%s\n", type);
+		type = sha1_object_info(sha1, NULL);
+		if (type > 0) {
+			printf("%s\n", typename(type));
 			return 0;
 		}
 		break;
 
 	case 's':
-		if (!sha1_object_info(sha1, type, &size)) {
+		type = sha1_object_info(sha1, &size);
+		if (type > 0) {
 			printf("%lu\n", size);
 			return 0;
 		}
@@ -117,17 +119,18 @@
 		return !has_sha1_file(sha1);
 
 	case 'p':
-		if (sha1_object_info(sha1, type, NULL))
+		type = sha1_object_info(sha1, NULL);
+		if (type < 0)
 			die("Not a valid object name %s", argv[2]);
 
 		/* custom pretty-print here */
-		if (!strcmp(type, tree_type))
+		if (type == OBJ_TREE)
 			return cmd_ls_tree(2, argv + 1, NULL);
 
-		buf = read_sha1_file(sha1, type, &size);
+		buf = read_sha1_file(sha1, &type, &size);
 		if (!buf)
 			die("Cannot read object %s", argv[2]);
-		if (!strcmp(type, tag_type)) {
+		if (type == OBJ_TAG) {
 			pprint_tag(sha1, buf, size);
 			return 0;
 		}
diff --git a/builtin-checkout-index.c b/builtin-checkout-index.c
index b097c88..afe4b0e 100644
--- a/builtin-checkout-index.c
+++ b/builtin-checkout-index.c
@@ -223,12 +223,12 @@
 			to_tempfile = 1;
 			continue;
 		}
-		if (!strncmp(arg, "--prefix=", 9)) {
+		if (!prefixcmp(arg, "--prefix=")) {
 			state.base_dir = arg+9;
 			state.base_dir_len = strlen(state.base_dir);
 			continue;
 		}
-		if (!strncmp(arg, "--stage=", 8)) {
+		if (!prefixcmp(arg, "--stage=")) {
 			if (!strcmp(arg + 8, "all")) {
 				to_tempfile = 1;
 				checkout_stage = CHECKOUT_ALL;
diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c
index 2a818a0..4a8d8d8 100644
--- a/builtin-commit-tree.c
+++ b/builtin-commit-tree.c
@@ -45,15 +45,14 @@
 	memcpy(buf + size, one_line, len);
 }
 
-static void check_valid(unsigned char *sha1, const char *expect)
+static void check_valid(unsigned char *sha1, enum object_type expect)
 {
-	char type[20];
-
-	if (sha1_object_info(sha1, type, NULL))
+	enum object_type type = sha1_object_info(sha1, NULL);
+	if (type < 0)
 		die("%s is not a valid object", sha1_to_hex(sha1));
-	if (expect && strcmp(type, expect))
+	if (type != expect)
 		die("%s is not a valid '%s' object", sha1_to_hex(sha1),
-		    expect);
+		    typename(expect));
 }
 
 /*
@@ -101,7 +100,7 @@
 	if (get_sha1(argv[1], tree_sha1))
 		die("Not a valid object name %s", argv[1]);
 
-	check_valid(tree_sha1, tree_type);
+	check_valid(tree_sha1, OBJ_TREE);
 	for (i = 2; i < argc; i += 2) {
 		const char *a, *b;
 		a = argv[i]; b = argv[i+1];
@@ -112,7 +111,7 @@
 			die("Too many parents (%d max)", MAXPARENT);
 		if (get_sha1(b, parent_sha1[parents]))
 			die("Not a valid object name %s", b);
-		check_valid(parent_sha1[parents], commit_type);
+		check_valid(parent_sha1[parents], OBJ_COMMIT);
 		if (new_parent(parents))
 			parents++;
 	}
diff --git a/builtin-config.c b/builtin-config.c
index 0f9051d..dfa403b 100644
--- a/builtin-config.c
+++ b/builtin-config.c
@@ -2,7 +2,7 @@
 #include "cache.h"
 
 static const char git_config_set_usage[] =
-"git-config [ --global ] [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --add | --unset | --unset-all] name [value [value_regex]] | --rename-section old_name new_name | --list";
+"git-config [ --global ] [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --add | --unset | --unset-all] name [value [value_regex]] | --rename-section old_name new_name | --remove-section name | --list";
 
 static char *key;
 static regex_t *key_regexp;
@@ -64,7 +64,7 @@
 	int ret = -1;
 	char *tl;
 	char *global = NULL, *repo_config = NULL;
-	const char *local;
+	const char *system_wide = NULL, *local;
 
 	local = getenv(CONFIG_ENVIRONMENT);
 	if (!local) {
@@ -74,6 +74,7 @@
 			local = repo_config = xstrdup(git_path("config"));
 		if (home)
 			global = xstrdup(mkpath("%s/.gitconfig", home));
+		system_wide = ETC_GITCONFIG;
 	}
 
 	key = xstrdup(key_);
@@ -103,11 +104,15 @@
 		}
 	}
 
+	if (do_all && system_wide)
+		git_config_from_file(show_config, system_wide);
 	if (do_all && global)
 		git_config_from_file(show_config, global);
 	git_config_from_file(show_config, local);
 	if (!do_all && !seen && global)
 		git_config_from_file(show_config, global);
+	if (!do_all && !seen && system_wide)
+		git_config_from_file(show_config, system_wide);
 
 	free(key);
 	if (regexp) {
@@ -147,7 +152,10 @@
 			} else {
 				die("$HOME not set");
 			}
-		} else if (!strcmp(argv[1], "--rename-section")) {
+		}
+		else if (!strcmp(argv[1], "--system"))
+			setenv("GIT_CONFIG", ETC_GITCONFIG, 1);
+		else if (!strcmp(argv[1], "--rename-section")) {
 			int ret;
 			if (argc != 4)
 				usage(git_config_set_usage);
@@ -159,7 +167,21 @@
 				return 1;
 			}
 			return 0;
-		} else
+		}
+		else if (!strcmp(argv[1], "--remove-section")) {
+			int ret;
+			if (argc != 3)
+				usage(git_config_set_usage);
+			ret = git_config_rename_section(argv[2], NULL);
+			if (ret < 0)
+				return ret;
+			if (ret == 0) {
+				fprintf(stderr, "No such section!\n");
+				return 1;
+			}
+			return 0;
+		}
+		else
 			break;
 		argc--;
 		argv++;
diff --git a/builtin-count-objects.c b/builtin-count-objects.c
index f5b22bb..6263d8a 100644
--- a/builtin-count-objects.c
+++ b/builtin-count-objects.c
@@ -44,7 +44,7 @@
 			if (lstat(path, &st) || !S_ISREG(st.st_mode))
 				bad = 1;
 			else
-				(*loose_size) += st.st_blocks;
+				(*loose_size) += xsize_t(st.st_blocks);
 		}
 		if (bad) {
 			if (verbose) {
diff --git a/builtin-describe.c b/builtin-describe.c
index bcc6456..165917e 100644
--- a/builtin-describe.c
+++ b/builtin-describe.c
@@ -52,7 +52,7 @@
 	 * If --tags, then any tags are used.
 	 * Otherwise only annotated tags are used.
 	 */
-	if (!strncmp(path, "refs/tags/", 10)) {
+	if (!prefixcmp(path, "refs/tags/")) {
 		if (object->type == OBJ_TAG)
 			prio = 2;
 		else
@@ -254,12 +254,12 @@
 			all = 1;
 		else if (!strcmp(arg, "--tags"))
 			tags = 1;
-		else if (!strncmp(arg, "--abbrev=", 9)) {
+		else if (!prefixcmp(arg, "--abbrev=")) {
 			abbrev = strtoul(arg + 9, NULL, 10);
 			if (abbrev != 0 && (abbrev < MINIMUM_ABBREV || 40 < abbrev))
 				abbrev = DEFAULT_ABBREV;
 		}
-		else if (!strncmp(arg, "--candidates=", 13)) {
+		else if (!prefixcmp(arg, "--candidates=")) {
 			max_candidates = strtoul(arg + 13, NULL, 10);
 			if (max_candidates < 1)
 				max_candidates = 1;
diff --git a/builtin-diff-files.c b/builtin-diff-files.c
index 5d4a5c5..6ba5077 100644
--- a/builtin-diff-files.c
+++ b/builtin-diff-files.c
@@ -10,42 +10,26 @@
 #include "builtin.h"
 
 static const char diff_files_usage[] =
-"git-diff-files [-q] [-0/-1/2/3 |-c|--cc] [<common diff options>] [<path>...]"
+"git-diff-files [-q] [-0/-1/2/3 |-c|--cc|-n|--no-index] [<common diff options>] [<path>...]"
 COMMON_DIFF_OPTIONS_HELP;
 
 int cmd_diff_files(int argc, const char **argv, const char *prefix)
 {
 	struct rev_info rev;
-	int silent = 0;
+	int nongit = 0;
+	int result;
 
+	prefix = setup_git_directory_gently(&nongit);
 	init_revisions(&rev, prefix);
 	git_config(git_default_config); /* no "diff" UI options */
 	rev.abbrev = 0;
 
-	argc = setup_revisions(argc, argv, &rev, NULL);
-	while (1 < argc && argv[1][0] == '-') {
-		if (!strcmp(argv[1], "--base"))
-			rev.max_count = 1;
-		else if (!strcmp(argv[1], "--ours"))
-			rev.max_count = 2;
-		else if (!strcmp(argv[1], "--theirs"))
-			rev.max_count = 3;
-		else if (!strcmp(argv[1], "-q"))
-			silent = 1;
-		else
-			usage(diff_files_usage);
-		argv++; argc--;
-	}
+	if (!setup_diff_no_index(&rev, argc, argv, nongit, prefix))
+		argc = 0;
+	else
+		argc = setup_revisions(argc, argv, &rev, NULL);
 	if (!rev.diffopt.output_format)
 		rev.diffopt.output_format = DIFF_FORMAT_RAW;
-
-	/*
-	 * Make sure there are NO revision (i.e. pending object) parameter,
-	 * rev.max_count is reasonable (0 <= n <= 3),
-	 * there is no other revision filtering parameters.
-	 */
-	if (rev.pending.nr ||
-	    rev.min_age != -1 || rev.max_age != -1)
-		usage(diff_files_usage);
-	return run_diff_files(&rev, silent);
+	result = run_diff_files_cmd(&rev, argc, argv);
+	return rev.diffopt.exit_with_status ? rev.diffopt.has_changes: result;
 }
diff --git a/builtin-diff-index.c b/builtin-diff-index.c
index 95a3db1..d90eba9 100644
--- a/builtin-diff-index.c
+++ b/builtin-diff-index.c
@@ -14,6 +14,7 @@
 	struct rev_info rev;
 	int cached = 0;
 	int i;
+	int result;
 
 	init_revisions(&rev, prefix);
 	git_config(git_default_config); /* no "diff" UI options */
@@ -38,5 +39,10 @@
 	if (rev.pending.nr != 1 ||
 	    rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1)
 		usage(diff_cache_usage);
-	return run_diff_index(&rev, cached);
+	if (read_cache() < 0) {
+		perror("read_cache");
+		return -1;
+	}
+	result = run_diff_index(&rev, cached);
+	return rev.diffopt.exit_with_status ? rev.diffopt.has_changes: result;
 }
diff --git a/builtin-diff-stages.c b/builtin-diff-stages.c
deleted file mode 100644
index 70bb898..0000000
--- a/builtin-diff-stages.c
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (c) 2005 Junio C Hamano
- */
-
-#include "cache.h"
-#include "diff.h"
-#include "builtin.h"
-
-static struct diff_options diff_options;
-
-static const char diff_stages_usage[] =
-"git-diff-stages [<common diff options>] <stage1> <stage2> [<path>...]"
-COMMON_DIFF_OPTIONS_HELP;
-
-static void diff_stages(int stage1, int stage2, const char **pathspec)
-{
-	int i = 0;
-	while (i < active_nr) {
-		struct cache_entry *ce, *stages[4] = { NULL, };
-		struct cache_entry *one, *two;
-		const char *name;
-		int len, skip;
-
-		ce = active_cache[i];
-		skip = !ce_path_match(ce, pathspec);
-		len = ce_namelen(ce);
-		name = ce->name;
-		for (;;) {
-			int stage = ce_stage(ce);
-			stages[stage] = ce;
-			if (active_nr <= ++i)
-				break;
-			ce = active_cache[i];
-			if (ce_namelen(ce) != len ||
-			    memcmp(name, ce->name, len))
-				break;
-		}
-		one = stages[stage1];
-		two = stages[stage2];
-
-		if (skip || (!one && !two))
-			continue;
-		if (!one)
-			diff_addremove(&diff_options, '+', ntohl(two->ce_mode),
-				       two->sha1, name, NULL);
-		else if (!two)
-			diff_addremove(&diff_options, '-', ntohl(one->ce_mode),
-				       one->sha1, name, NULL);
-		else if (hashcmp(one->sha1, two->sha1) ||
-			 (one->ce_mode != two->ce_mode) ||
-			 diff_options.find_copies_harder)
-			diff_change(&diff_options,
-				    ntohl(one->ce_mode), ntohl(two->ce_mode),
-				    one->sha1, two->sha1, name, NULL);
-	}
-}
-
-int cmd_diff_stages(int ac, const char **av, const char *prefix)
-{
-	int stage1, stage2;
-	const char **pathspec = NULL;
-
-	git_config(git_default_config); /* no "diff" UI options */
-	read_cache();
-	diff_setup(&diff_options);
-	while (1 < ac && av[1][0] == '-') {
-		const char *arg = av[1];
-		if (!strcmp(arg, "-r"))
-			; /* as usual */
-		else {
-			int diff_opt_cnt;
-			diff_opt_cnt = diff_opt_parse(&diff_options,
-						      av+1, ac-1);
-			if (diff_opt_cnt < 0)
-				usage(diff_stages_usage);
-			else if (diff_opt_cnt) {
-				av += diff_opt_cnt;
-				ac -= diff_opt_cnt;
-				continue;
-			}
-			else
-				usage(diff_stages_usage);
-		}
-		ac--; av++;
-	}
-
-	if (!diff_options.output_format)
-		diff_options.output_format = DIFF_FORMAT_RAW;
-
-	if (ac < 3 ||
-	    sscanf(av[1], "%d", &stage1) != 1 ||
-	    ! (0 <= stage1 && stage1 <= 3) ||
-	    sscanf(av[2], "%d", &stage2) != 1 ||
-	    ! (0 <= stage2 && stage2 <= 3))
-		usage(diff_stages_usage);
-
-	av += 3; /* The rest from av[0] are for paths restriction. */
-	pathspec = get_pathspec(prefix, av);
-
-	if (diff_setup_done(&diff_options) < 0)
-		usage(diff_stages_usage);
-
-	diff_stages(stage1, stage2, pathspec);
-	diffcore_std(&diff_options);
-	diff_flush(&diff_options);
-	return 0;
-}
diff --git a/builtin-diff-tree.c b/builtin-diff-tree.c
index 24cb2d7f..0b591c8 100644
--- a/builtin-diff-tree.c
+++ b/builtin-diff-tree.c
@@ -118,7 +118,8 @@
 	}
 
 	if (!read_stdin)
-		return 0;
+		return opt->diffopt.exit_with_status ?
+		    opt->diffopt.has_changes: 0;
 
 	if (opt->diffopt.detect_rename)
 		opt->diffopt.setup |= (DIFF_SETUP_USE_SIZE_CACHE |
@@ -133,5 +134,5 @@
 		else
 			diff_tree_stdin(line);
 	}
-	return 0;
+	return opt->diffopt.exit_with_status ? opt->diffopt.has_changes: 0;
 }
diff --git a/builtin-diff.c b/builtin-diff.c
index c387ebb..21d13f0 100644
--- a/builtin-diff.c
+++ b/builtin-diff.c
@@ -25,40 +25,6 @@
 static const char builtin_diff_usage[] =
 "git-diff <options> <rev>{0,2} -- <path>*";
 
-static int builtin_diff_files(struct rev_info *revs,
-			      int argc, const char **argv)
-{
-	int silent = 0;
-	while (1 < argc) {
-		const char *arg = argv[1];
-		if (!strcmp(arg, "--base"))
-			revs->max_count = 1;
-		else if (!strcmp(arg, "--ours"))
-			revs->max_count = 2;
-		else if (!strcmp(arg, "--theirs"))
-			revs->max_count = 3;
-		else if (!strcmp(arg, "-q"))
-			silent = 1;
-		else
-			usage(builtin_diff_usage);
-		argv++; argc--;
-	}
-	/*
-	 * Make sure there are NO revision (i.e. pending object) parameter,
-	 * specified rev.max_count is reasonable (0 <= n <= 3), and
-	 * there is no other revision filtering parameter.
-	 */
-	if (revs->pending.nr ||
-	    revs->min_age != -1 ||
-	    revs->max_age != -1 ||
-	    3 < revs->max_count)
-		usage(builtin_diff_usage);
-	if (revs->max_count < 0 &&
-	    (revs->diffopt.output_format & DIFF_FORMAT_PATCH))
-		revs->combine_merges = revs->dense_combined_merges = 1;
-	return run_diff_files(revs, silent);
-}
-
 static void stuff_change(struct diff_options *opt,
 			 unsigned old_mode, unsigned new_mode,
 			 const unsigned char *old_sha1,
@@ -151,6 +117,10 @@
 	    revs->max_count != -1 || revs->min_age != -1 ||
 	    revs->max_age != -1)
 		usage(builtin_diff_usage);
+	if (read_cache() < 0) {
+		perror("read_cache");
+		return -1;
+	}
 	return run_diff_index(revs, cached);
 }
 
@@ -219,6 +189,8 @@
 	int ents = 0, blobs = 0, paths = 0;
 	const char *path = NULL;
 	struct blobinfo blob[2];
+	int nongit = 0;
+	int result = 0;
 
 	/*
 	 * We could get N tree-ish in the rev.pending_objects list.
@@ -240,10 +212,14 @@
 	 * Other cases are errors.
 	 */
 
+	prefix = setup_git_directory_gently(&nongit);
 	git_config(git_diff_ui_config);
 	init_revisions(&rev, prefix);
 
-	argc = setup_revisions(argc, argv, &rev, NULL);
+	if (!setup_diff_no_index(&rev, argc, argv, nongit, prefix))
+		argc = 0;
+	else
+		argc = setup_revisions(argc, argv, &rev, NULL);
 	if (!rev.diffopt.output_format) {
 		rev.diffopt.output_format = DIFF_FORMAT_PATCH;
 		if (diff_setup_done(&rev.diffopt) < 0)
@@ -261,6 +237,8 @@
 				break;
 			else if (!strcmp(arg, "--cached")) {
 				add_head(&rev);
+				if (!rev.pending.nr)
+					die("No HEAD commit to compare with (yet)");
 				break;
 			}
 		}
@@ -315,17 +293,17 @@
 	if (!ents) {
 		switch (blobs) {
 		case 0:
-			return builtin_diff_files(&rev, argc, argv);
+			result = run_diff_files_cmd(&rev, argc, argv);
 			break;
 		case 1:
 			if (paths != 1)
 				usage(builtin_diff_usage);
-			return builtin_diff_b_f(&rev, argc, argv, blob, path);
+			result = builtin_diff_b_f(&rev, argc, argv, blob, path);
 			break;
 		case 2:
 			if (paths)
 				usage(builtin_diff_usage);
-			return builtin_diff_blobs(&rev, argc, argv, blob);
+			result = builtin_diff_blobs(&rev, argc, argv, blob);
 			break;
 		default:
 			usage(builtin_diff_usage);
@@ -334,19 +312,21 @@
 	else if (blobs)
 		usage(builtin_diff_usage);
 	else if (ents == 1)
-		return builtin_diff_index(&rev, argc, argv);
+		result = builtin_diff_index(&rev, argc, argv);
 	else if (ents == 2)
-		return builtin_diff_tree(&rev, argc, argv, ent);
+		result = builtin_diff_tree(&rev, argc, argv, ent);
 	else if ((ents == 3) && (ent[0].item->flags & UNINTERESTING)) {
 		/* diff A...B where there is one sane merge base between
 		 * A and B.  We have ent[0] == merge-base, ent[1] == A,
 		 * and ent[2] == B.  Show diff between the base and B.
 		 */
 		ent[1] = ent[2];
-		return builtin_diff_tree(&rev, argc, argv, ent);
+		result = builtin_diff_tree(&rev, argc, argv, ent);
 	}
 	else
-		return builtin_diff_combined(&rev, argc, argv,
+		result = builtin_diff_combined(&rev, argc, argv,
 					     ent, ents);
-	usage(builtin_diff_usage);
+	if (rev.diffopt.exit_with_status)
+		result = rev.diffopt.has_changes;
+	return result;
 }
diff --git a/builtin-fetch--tool.c b/builtin-fetch--tool.c
new file mode 100644
index 0000000..e9d6764
--- /dev/null
+++ b/builtin-fetch--tool.c
@@ -0,0 +1,505 @@
+#include "cache.h"
+#include "refs.h"
+#include "commit.h"
+
+#define CHUNK_SIZE 1024
+
+static char *get_stdin(void)
+{
+	int offset = 0;
+	char *data = xmalloc(CHUNK_SIZE);
+
+	while (1) {
+		int cnt = xread(0, data + offset, CHUNK_SIZE);
+		if (cnt < 0)
+			die("error reading standard input: %s",
+			    strerror(errno));
+		if (cnt == 0) {
+			data[offset] = 0;
+			break;
+		}
+		offset += cnt;
+		data = xrealloc(data, offset + CHUNK_SIZE);
+	}
+	return data;
+}
+
+static void show_new(enum object_type type, unsigned char *sha1_new)
+{
+	fprintf(stderr, "  %s: %s\n", typename(type),
+		find_unique_abbrev(sha1_new, DEFAULT_ABBREV));
+}
+
+static int update_ref(const char *action,
+		      const char *refname,
+		      unsigned char *sha1,
+		      unsigned char *oldval)
+{
+	int len;
+	char msg[1024];
+	char *rla = getenv("GIT_REFLOG_ACTION");
+	static struct ref_lock *lock;
+
+	if (!rla)
+		rla = "(reflog update)";
+	len = snprintf(msg, sizeof(msg), "%s: %s", rla, action);
+	if (sizeof(msg) <= len)
+		die("insanely long action");
+	lock = lock_any_ref_for_update(refname, oldval);
+	if (!lock)
+		return 1;
+	if (write_ref_sha1(lock, sha1, msg) < 0)
+		return 1;
+	return 0;
+}
+
+static int update_local_ref(const char *name,
+			    const char *new_head,
+			    const char *note,
+			    int verbose, int force)
+{
+	unsigned char sha1_old[20], sha1_new[20];
+	char oldh[41], newh[41];
+	struct commit *current, *updated;
+	enum object_type type;
+
+	if (get_sha1_hex(new_head, sha1_new))
+		die("malformed object name %s", new_head);
+
+	type = sha1_object_info(sha1_new, NULL);
+	if (type < 0)
+		die("object %s not found", new_head);
+
+	if (!*name) {
+		/* Not storing */
+		if (verbose) {
+			fprintf(stderr, "* fetched %s\n", note);
+			show_new(type, sha1_new);
+		}
+		return 0;
+	}
+
+	if (get_sha1(name, sha1_old)) {
+		char *msg;
+	just_store:
+		/* new ref */
+		if (!strncmp(name, "refs/tags/", 10))
+			msg = "storing tag";
+		else
+			msg = "storing head";
+		fprintf(stderr, "* %s: storing %s\n",
+			name, note);
+		show_new(type, sha1_new);
+		return update_ref(msg, name, sha1_new, NULL);
+	}
+
+	if (!hashcmp(sha1_old, sha1_new)) {
+		if (verbose) {
+			fprintf(stderr, "* %s: same as %s\n", name, note);
+			show_new(type, sha1_new);
+		}
+		return 0;
+	}
+
+	if (!strncmp(name, "refs/tags/", 10)) {
+		fprintf(stderr, "* %s: updating with %s\n", name, note);
+		show_new(type, sha1_new);
+		return update_ref("updating tag", name, sha1_new, NULL);
+	}
+
+	current = lookup_commit_reference(sha1_old);
+	updated = lookup_commit_reference(sha1_new);
+	if (!current || !updated)
+		goto just_store;
+
+	strcpy(oldh, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
+	strcpy(newh, find_unique_abbrev(sha1_new, DEFAULT_ABBREV));
+
+	if (in_merge_bases(current, &updated, 1)) {
+		fprintf(stderr, "* %s: fast forward to %s\n",
+			name, note);
+		fprintf(stderr, "  old..new: %s..%s\n", oldh, newh);
+		return update_ref("fast forward", name, sha1_new, sha1_old);
+	}
+	if (!force) {
+		fprintf(stderr,
+			"* %s: not updating to non-fast forward %s\n",
+			name, note);
+		fprintf(stderr,
+			"  old...new: %s...%s\n", oldh, newh);
+		return 1;
+	}
+	fprintf(stderr,
+		"* %s: forcing update to non-fast forward %s\n",
+		name, note);
+	fprintf(stderr, "  old...new: %s...%s\n", oldh, newh);
+	return update_ref("forced-update", name, sha1_new, sha1_old);
+}
+
+static int append_fetch_head(FILE *fp,
+			     const char *head, const char *remote,
+			     const char *remote_name, const char *remote_nick,
+			     const char *local_name, int not_for_merge,
+			     int verbose, int force)
+{
+	struct commit *commit;
+	int remote_len, i, note_len;
+	unsigned char sha1[20];
+	char note[1024];
+	const char *what, *kind;
+
+	if (get_sha1(head, sha1))
+		return error("Not a valid object name: %s", head);
+	commit = lookup_commit_reference(sha1);
+	if (!commit)
+		not_for_merge = 1;
+
+	if (!strcmp(remote_name, "HEAD")) {
+		kind = "";
+		what = "";
+	}
+	else if (!strncmp(remote_name, "refs/heads/", 11)) {
+		kind = "branch";
+		what = remote_name + 11;
+	}
+	else if (!strncmp(remote_name, "refs/tags/", 10)) {
+		kind = "tag";
+		what = remote_name + 10;
+	}
+	else if (!strncmp(remote_name, "refs/remotes/", 13)) {
+		kind = "remote branch";
+		what = remote_name + 13;
+	}
+	else {
+		kind = "";
+		what = remote_name;
+	}
+
+	remote_len = strlen(remote);
+	for (i = remote_len - 1; remote[i] == '/' && 0 <= i; i--)
+		;
+	remote_len = i + 1;
+	if (4 < i && !strncmp(".git", remote + i - 3, 4))
+		remote_len = i - 3;
+
+	note_len = 0;
+	if (*what) {
+		if (*kind)
+			note_len += sprintf(note + note_len, "%s ", kind);
+		note_len += sprintf(note + note_len, "'%s' of ", what);
+	}
+	note_len += sprintf(note + note_len, "%.*s", remote_len, remote);
+	fprintf(fp, "%s\t%s\t%s\n",
+		sha1_to_hex(commit ? commit->object.sha1 : sha1),
+		not_for_merge ? "not-for-merge" : "",
+		note);
+	return update_local_ref(local_name, head, note, verbose, force);
+}
+
+static char *keep;
+static void remove_keep(void)
+{
+	if (keep && *keep)
+		unlink(keep);
+}
+
+static void remove_keep_on_signal(int signo)
+{
+	remove_keep();
+	signal(SIGINT, SIG_DFL);
+	raise(signo);
+}
+
+static char *find_local_name(const char *remote_name, const char *refs,
+			     int *force_p, int *not_for_merge_p)
+{
+	const char *ref = refs;
+	int len = strlen(remote_name);
+
+	while (ref) {
+		const char *next;
+		int single_force, not_for_merge;
+
+		while (*ref == '\n')
+			ref++;
+		if (!*ref)
+			break;
+		next = strchr(ref, '\n');
+
+		single_force = not_for_merge = 0;
+		if (*ref == '+') {
+			single_force = 1;
+			ref++;
+		}
+		if (*ref == '.') {
+			not_for_merge = 1;
+			ref++;
+			if (*ref == '+') {
+				single_force = 1;
+				ref++;
+			}
+		}
+		if (!strncmp(remote_name, ref, len) && ref[len] == ':') {
+			const char *local_part = ref + len + 1;
+			char *ret;
+			int retlen;
+
+			if (!next)
+				retlen = strlen(local_part);
+			else
+				retlen = next - local_part;
+			ret = xmalloc(retlen + 1);
+			memcpy(ret, local_part, retlen);
+			ret[retlen] = 0;
+			*force_p = single_force;
+			*not_for_merge_p = not_for_merge;
+			return ret;
+		}
+		ref = next;
+	}
+	return NULL;
+}
+
+static int fetch_native_store(FILE *fp,
+			      const char *remote,
+			      const char *remote_nick,
+			      const char *refs,
+			      int verbose, int force)
+{
+	char buffer[1024];
+	int err = 0;
+
+	signal(SIGINT, remove_keep_on_signal);
+	atexit(remove_keep);
+
+	while (fgets(buffer, sizeof(buffer), stdin)) {
+		int len;
+		char *cp;
+		char *local_name;
+		int single_force, not_for_merge;
+
+		for (cp = buffer; *cp && !isspace(*cp); cp++)
+			;
+		if (*cp)
+			*cp++ = 0;
+		len = strlen(cp);
+		if (len && cp[len-1] == '\n')
+			cp[--len] = 0;
+		if (!strcmp(buffer, "failed"))
+			die("Fetch failure: %s", remote);
+		if (!strcmp(buffer, "pack"))
+			continue;
+		if (!strcmp(buffer, "keep")) {
+			char *od = get_object_directory();
+			int len = strlen(od) + strlen(cp) + 50;
+			keep = xmalloc(len);
+			sprintf(keep, "%s/pack/pack-%s.keep", od, cp);
+			continue;
+		}
+
+		local_name = find_local_name(cp, refs,
+					     &single_force, &not_for_merge);
+		if (!local_name)
+			continue;
+		err |= append_fetch_head(fp,
+					 buffer, remote, cp, remote_nick,
+					 local_name, not_for_merge,
+					 verbose, force || single_force);
+	}
+	return err;
+}
+
+static int parse_reflist(const char *reflist)
+{
+	const char *ref;
+
+	printf("refs='");
+	for (ref = reflist; ref; ) {
+		const char *next;
+		while (*ref && isspace(*ref))
+			ref++;
+		if (!*ref)
+			break;
+		for (next = ref; *next && !isspace(*next); next++)
+			;
+		printf("\n%.*s", (int)(next - ref), ref);
+		ref = next;
+	}
+	printf("'\n");
+
+	printf("rref='");
+	for (ref = reflist; ref; ) {
+		const char *next, *colon;
+		while (*ref && isspace(*ref))
+			ref++;
+		if (!*ref)
+			break;
+		for (next = ref; *next && !isspace(*next); next++)
+			;
+		if (*ref == '.')
+			ref++;
+		if (*ref == '+')
+			ref++;
+		colon = strchr(ref, ':');
+		putchar('\n');
+		printf("%.*s", (int)((colon ? colon : next) - ref), ref);
+		ref = next;
+	}
+	printf("'\n");
+	return 0;
+}
+
+static int expand_refs_wildcard(const char *ls_remote_result, int numrefs,
+				const char **refs)
+{
+	int i, matchlen, replacelen;
+	int found_one = 0;
+	const char *remote = *refs++;
+	numrefs--;
+
+	if (numrefs == 0) {
+		fprintf(stderr, "Nothing specified for fetching with remote.%s.fetch\n",
+			remote);
+		printf("empty\n");
+	}
+
+	for (i = 0; i < numrefs; i++) {
+		const char *ref = refs[i];
+		const char *lref = ref;
+		const char *colon;
+		const char *tail;
+		const char *ls;
+		const char *next;
+
+		if (*lref == '+')
+			lref++;
+		colon = strchr(lref, ':');
+		tail = lref + strlen(lref);
+		if (!(colon &&
+		      2 < colon - lref &&
+		      colon[-1] == '*' &&
+		      colon[-2] == '/' &&
+		      2 < tail - (colon + 1) &&
+		      tail[-1] == '*' &&
+		      tail[-2] == '/')) {
+			/* not a glob */
+			if (!found_one++)
+				printf("explicit\n");
+			printf("%s\n", ref);
+			continue;
+		}
+
+		/* glob */
+		if (!found_one++)
+			printf("glob\n");
+
+		/* lref to colon-2 is remote hierarchy name;
+		 * colon+1 to tail-2 is local.
+		 */
+		matchlen = (colon-1) - lref;
+		replacelen = (tail-1) - (colon+1);
+		for (ls = ls_remote_result; ls; ls = next) {
+			const char *eol;
+			unsigned char sha1[20];
+			int namelen;
+
+			while (*ls && isspace(*ls))
+				ls++;
+			next = strchr(ls, '\n');
+			eol = !next ? (ls + strlen(ls)) : next;
+			if (!memcmp("^{}", eol-3, 3))
+				continue;
+			if (eol - ls < 40)
+				continue;
+			if (get_sha1_hex(ls, sha1))
+				continue;
+			ls += 40;
+			while (ls < eol && isspace(*ls))
+				ls++;
+			/* ls to next (or eol) is the name.
+			 * is it identical to lref to colon-2?
+			 */
+			if ((eol - ls) <= matchlen ||
+			    strncmp(ls, lref, matchlen))
+				continue;
+
+			/* Yes, it is a match */
+			namelen = eol - ls;
+			if (lref != ref)
+				putchar('+');
+			printf("%.*s:%.*s%.*s\n",
+			       namelen, ls,
+			       replacelen, colon + 1,
+			       namelen - matchlen, ls + matchlen);
+		}
+	}
+	return 0;
+}
+
+int cmd_fetch__tool(int argc, const char **argv, const char *prefix)
+{
+	int verbose = 0;
+	int force = 0;
+
+	while (1 < argc) {
+		const char *arg = argv[1];
+		if (!strcmp("-v", arg))
+			verbose = 1;
+		else if (!strcmp("-f", arg))
+			force = 1;
+		else
+			break;
+		argc--;
+		argv++;
+	}
+
+	if (argc <= 1)
+		return error("Missing subcommand");
+
+	if (!strcmp("append-fetch-head", argv[1])) {
+		int result;
+		FILE *fp;
+
+		if (argc != 8)
+			return error("append-fetch-head takes 6 args");
+		fp = fopen(git_path("FETCH_HEAD"), "a");
+		result = append_fetch_head(fp, argv[2], argv[3],
+					   argv[4], argv[5],
+					   argv[6], !!argv[7][0],
+					   verbose, force);
+		fclose(fp);
+		return result;
+	}
+	if (!strcmp("native-store", argv[1])) {
+		int result;
+		FILE *fp;
+
+		if (argc != 5)
+			return error("fetch-native-store takes 3 args");
+		fp = fopen(git_path("FETCH_HEAD"), "a");
+		result = fetch_native_store(fp, argv[2], argv[3], argv[4],
+					    verbose, force);
+		fclose(fp);
+		return result;
+	}
+	if (!strcmp("parse-reflist", argv[1])) {
+		const char *reflist;
+		if (argc != 3)
+			return error("parse-reflist takes 1 arg");
+		reflist = argv[2];
+		if (!strcmp(reflist, "-"))
+			reflist = get_stdin();
+		return parse_reflist(reflist);
+	}
+	if (!strcmp("expand-refs-wildcard", argv[1])) {
+		const char *reflist;
+		if (argc < 4)
+			return error("expand-refs-wildcard takes at least 2 args");
+		reflist = argv[2];
+		if (!strcmp(reflist, "-"))
+			reflist = get_stdin();
+		return expand_refs_wildcard(reflist, argc - 3, argv + 3);
+	}
+
+	return error("Unknown subcommand: %s", argv[1]);
+}
diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c
index 5be6fb4..5c145d2 100644
--- a/builtin-fmt-merge-msg.c
+++ b/builtin-fmt-merge-msg.c
@@ -81,7 +81,7 @@
 	if (len < 43 || line[40] != '\t')
 		return 1;
 
-	if (!strncmp(line + 41, "not-for-merge", 13))
+	if (!prefixcmp(line + 41, "not-for-merge"))
 		return 0;
 
 	if (line[41] != '\t')
@@ -119,15 +119,15 @@
 	if (pulling_head) {
 		origin = xstrdup(src);
 		src_data->head_status |= 1;
-	} else if (!strncmp(line, "branch ", 7)) {
+	} else if (!prefixcmp(line, "branch ")) {
 		origin = xstrdup(line + 7);
 		append_to_list(&src_data->branch, origin, NULL);
 		src_data->head_status |= 2;
-	} else if (!strncmp(line, "tag ", 4)) {
+	} else if (!prefixcmp(line, "tag ")) {
 		origin = line;
 		append_to_list(&src_data->tag, xstrdup(origin + 4), NULL);
 		src_data->head_status |= 2;
-	} else if (!strncmp(line, "remote branch ", 14)) {
+	} else if (!prefixcmp(line, "remote branch ")) {
 		origin = xstrdup(line + 14);
 		append_to_list(&src_data->r_branch, origin, NULL);
 		src_data->head_status |= 2;
@@ -282,7 +282,7 @@
 	current_branch = resolve_ref("HEAD", head_sha1, 1, NULL);
 	if (!current_branch)
 		die("No current branch");
-	if (!strncmp(current_branch, "refs/heads/", 11))
+	if (!prefixcmp(current_branch, "refs/heads/"))
 		current_branch += 11;
 
 	while (fgets(line, sizeof(line), in)) {
diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c
index 16c785f..2b21842 100644
--- a/builtin-for-each-ref.c
+++ b/builtin-for-each-ref.c
@@ -173,8 +173,8 @@
  */
 static void *get_obj(const unsigned char *sha1, struct object **obj, unsigned long *sz, int *eaten)
 {
-	char type[20];
-	void *buf = read_sha1_file(sha1, type, sz);
+	enum object_type type;
+	void *buf = read_sha1_file(sha1, &type, sz);
 
 	if (buf)
 		*obj = parse_object_buffer(sha1, type, *sz, buf, eaten);
@@ -196,7 +196,7 @@
 		if (deref)
 			name++;
 		if (!strcmp(name, "objecttype"))
-			v->s = type_names[obj->type];
+			v->s = typename(obj->type);
 		else if (!strcmp(name, "objectsize")) {
 			char *s = xmalloc(40);
 			sprintf(s, "%lu", sz);
@@ -301,7 +301,7 @@
 	return "";
 }
 
-static char *copy_line(const char *buf)
+static const char *copy_line(const char *buf)
 {
 	const char *eol = strchr(buf, '\n');
 	char *line;
@@ -315,7 +315,7 @@
 	return line;
 }
 
-static char *copy_name(const char *buf)
+static const char *copy_name(const char *buf)
 {
 	const char *eol = strchr(buf, '\n');
 	const char *eoname = strstr(buf, " <");
@@ -330,7 +330,7 @@
 	return line;
 }
 
-static char *copy_email(const char *buf)
+static const char *copy_email(const char *buf)
 {
 	const char *email = strchr(buf, '<');
 	const char *eoemail = strchr(email, '>');
@@ -814,7 +814,7 @@
 			i++;
 			break;
 		}
-		if (!strncmp(arg, "--format=", 9)) {
+		if (!prefixcmp(arg, "--format=")) {
 			if (format)
 				die("more than one --format?");
 			format = arg + 9;
@@ -844,7 +844,7 @@
 			quote_style = QUOTE_TCL;
 			continue;
 		}
-		if (!strncmp(arg, "--count=", 8)) {
+		if (!prefixcmp(arg, "--count=")) {
 			if (maxcount)
 				die("more than one --count?");
 			maxcount = atoi(arg + 8);
@@ -852,7 +852,7 @@
 				die("The number %s did not parse", arg);
 			continue;
 		}
-		if (!strncmp(arg, "--sort=", 7)) {
+		if (!prefixcmp(arg, "--sort=")) {
 			struct ref_sort *s = xcalloc(1, sizeof(*s));
 			int len;
 
diff --git a/builtin-fsck.c b/builtin-fsck.c
index 6da3814..21f1f9e 100644
--- a/builtin-fsck.c
+++ b/builtin-fsck.c
@@ -18,6 +18,9 @@
 static int check_strict;
 static int keep_cache_objects;
 static unsigned char head_sha1[20];
+static int errors_found;
+#define ERROR_OBJECT 01
+#define ERROR_REACHABLE 02
 
 #ifdef NO_D_INO_IN_DIRENT
 #define SORT_DIRENT 0
@@ -40,6 +43,7 @@
 {
 	va_list params;
 	va_start(params, err);
+	errors_found |= ERROR_OBJECT;
 	objreport(obj, "error", err, params);
 	va_end(params);
 	return -1;
@@ -67,9 +71,10 @@
 	 * do a full fsck
 	 */
 	if (!obj->parsed) {
-		if (has_sha1_file(obj->sha1))
+		if (has_sha1_pack(obj->sha1, NULL))
 			return; /* it is in pack - forget about it */
 		printf("missing %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1));
+		errors_found |= ERROR_REACHABLE;
 		return;
 	}
 
@@ -88,6 +93,7 @@
 			       typename(obj->type), sha1_to_hex(obj->sha1));
 			printf("              to %7s %s\n",
 			       typename(ref->type), sha1_to_hex(ref->sha1));
+			errors_found |= ERROR_REACHABLE;
 		}
 	}
 }
@@ -221,8 +227,7 @@
 	const char *o_name;
 	const unsigned char *o_sha1;
 
-	desc.buf = item->buffer;
-	desc.size = item->size;
+	init_tree_desc(&desc, item->buffer, item->size);
 
 	o_mode = 0;
 	o_name = NULL;
@@ -236,7 +241,7 @@
 
 		if (strchr(name, '/'))
 			has_full_path = 1;
-		has_zero_pad |= *(char *)desc.buf == '0';
+		has_zero_pad |= *(char *)desc.buffer == '0';
 		update_tree_entry(&desc);
 
 		switch (mode) {
@@ -346,8 +351,11 @@
 static int fsck_sha1(unsigned char *sha1)
 {
 	struct object *obj = parse_object(sha1);
-	if (!obj)
-		return error("%s: object corrupt or missing", sha1_to_hex(sha1));
+	if (!obj) {
+		errors_found |= ERROR_OBJECT;
+		return error("%s: object corrupt or missing",
+			     sha1_to_hex(sha1));
+	}
 	if (obj->flags & SEEN)
 		return 0;
 	obj->flags |= SEEN;
@@ -359,8 +367,10 @@
 		return fsck_commit((struct commit *) obj);
 	if (obj->type == OBJ_TAG)
 		return fsck_tag((struct tag *) obj);
+
 	/* By now, parse_object() would've returned NULL instead. */
-	return objerror(obj, "unknown type '%d' (internal fsck error)", obj->type);
+	return objerror(obj, "unknown type '%d' (internal fsck error)",
+			obj->type);
 }
 
 /*
@@ -546,7 +556,7 @@
 
 	if (!head_points_at || !(flag & REF_ISSYMREF))
 		return error("HEAD is not a symbolic ref");
-	if (strncmp(head_points_at, "refs/heads/", 11))
+	if (prefixcmp(head_points_at, "refs/heads/"))
 		return error("HEAD points to something strange (%s)",
 			     head_points_at);
 	if (is_null_sha1(sha1))
@@ -576,11 +586,16 @@
 	return err;
 }
 
+static const char fsck_usage[] =
+"git-fsck [--tags] [--root] [[--unreachable] [--cache] [--full] "
+"[--strict] <head-sha1>*]";
+
 int cmd_fsck(int argc, char **argv, const char *prefix)
 {
 	int i, heads;
 
 	track_object_refs = 1;
+	errors_found = 0;
 
 	for (i = 1; i < argc; i++) {
 		const char *arg = argv[i];
@@ -610,7 +625,7 @@
 			continue;
 		}
 		if (*arg == '-')
-			usage("git-fsck [--tags] [--root] [[--unreachable] [--cache] [--full] [--strict] <head-sha1>*]");
+			usage(fsck_usage);
 	}
 
 	fsck_head_link();
@@ -632,7 +647,7 @@
 			verify_pack(p, 0);
 
 		for (p = packed_git; p; p = p->next) {
-			int num = num_packed_objects(p);
+			uint32_t i, num = num_packed_objects(p);
 			for (i = 0; i < num; i++) {
 				unsigned char sha1[20];
 				nth_packed_object_sha1(p, i, sha1);
@@ -690,5 +705,5 @@
 	}
 
 	check_connectivity();
-	return 0;
+	return errors_found;
 }
diff --git a/builtin-gc.c b/builtin-gc.c
new file mode 100644
index 0000000..3b1f8c2
--- /dev/null
+++ b/builtin-gc.c
@@ -0,0 +1,78 @@
+/*
+ * git gc builtin command
+ *
+ * Cleanup unreachable files and optimize the repository.
+ *
+ * Copyright (c) 2007 James Bowes
+ *
+ * Based on git-gc.sh, which is
+ *
+ * Copyright (c) 2006 Shawn O. Pearce
+ */
+
+#include "cache.h"
+#include "run-command.h"
+
+#define FAILED_RUN "failed to run %s"
+
+static const char builtin_gc_usage[] = "git-gc [--prune]";
+
+static int pack_refs = -1;
+
+static const char *argv_pack_refs[] = {"pack-refs", "--prune", NULL};
+static const char *argv_reflog[] = {"reflog", "expire", "--all", NULL};
+static const char *argv_repack[] = {"repack", "-a", "-d", "-l", NULL};
+static const char *argv_prune[] = {"prune", NULL};
+static const char *argv_rerere[] = {"rerere", "gc", NULL};
+
+static int gc_config(const char *var, const char *value)
+{
+	if (!strcmp(var, "gc.packrefs")) {
+		if (!strcmp(value, "notbare"))
+			pack_refs = -1;
+		else
+			pack_refs = git_config_bool(var, value);
+		return 0;
+	}
+	return git_default_config(var, value);
+}
+
+int cmd_gc(int argc, const char **argv, const char *prefix)
+{
+	int i;
+	int prune = 0;
+
+	git_config(gc_config);
+
+	if (pack_refs < 0)
+		pack_refs = !is_bare_repository();
+
+	for (i = 1; i < argc; i++) {
+		const char *arg = argv[i];
+		if (!strcmp(arg, "--prune")) {
+			prune = 1;
+			continue;
+		}
+		/* perhaps other parameters later... */
+		break;
+	}
+	if (i != argc)
+		usage(builtin_gc_usage);
+
+	if (pack_refs && run_command_v_opt(argv_pack_refs, RUN_GIT_CMD))
+		return error(FAILED_RUN, argv_pack_refs[0]);
+
+	if (run_command_v_opt(argv_reflog, RUN_GIT_CMD))
+		return error(FAILED_RUN, argv_reflog[0]);
+
+	if (run_command_v_opt(argv_repack, RUN_GIT_CMD))
+		return error(FAILED_RUN, argv_repack[0]);
+
+	if (prune && run_command_v_opt(argv_prune, RUN_GIT_CMD))
+		return error(FAILED_RUN, argv_prune[0]);
+
+	if (run_command_v_opt(argv_rerere, RUN_GIT_CMD))
+		return error(FAILED_RUN, argv_rerere[0]);
+
+	return 0;
+}
diff --git a/builtin-grep.c b/builtin-grep.c
index 2bfbdb7..981f3d4 100644
--- a/builtin-grep.c
+++ b/builtin-grep.c
@@ -84,11 +84,11 @@
 {
 	unsigned long size;
 	char *data;
-	char type[20];
+	enum object_type type;
 	char *to_free = NULL;
 	int hit;
 
-	data = read_sha1_file(sha1, type, &size);
+	data = read_sha1_file(sha1, &type, &size);
 	if (!data) {
 		error("'%s': unable to read %s", name, sha1_to_hex(sha1));
 		return 0;
@@ -122,6 +122,8 @@
 	struct stat st;
 	int i;
 	char *data;
+	size_t sz;
+
 	if (lstat(filename, &st) < 0) {
 	err_ret:
 		if (errno != ENOENT)
@@ -132,11 +134,12 @@
 		return 0; /* empty file -- no grep hit */
 	if (!S_ISREG(st.st_mode))
 		return 0;
+	sz = xsize_t(st.st_size);
 	i = open(filename, O_RDONLY);
 	if (i < 0)
 		goto err_ret;
-	data = xmalloc(st.st_size + 1);
-	if (st.st_size != read_in_full(i, data, st.st_size)) {
+	data = xmalloc(sz + 1);
+	if (st.st_size != read_in_full(i, data, sz)) {
 		error("'%s': short read %s", filename, strerror(errno));
 		close(i);
 		free(data);
@@ -145,11 +148,12 @@
 	close(i);
 	if (opt->relative && opt->prefix_length)
 		filename += opt->prefix_length;
-	i = grep_buffer(opt, filename, data, st.st_size);
+	i = grep_buffer(opt, filename, data, sz);
 	free(data);
 	return i;
 }
 
+#ifdef __unix__
 static int exec_grep(int argc, const char **argv)
 {
 	pid_t pid;
@@ -298,6 +302,7 @@
 	}
 	return hit;
 }
+#endif
 
 static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
 {
@@ -373,21 +378,23 @@
 			 * decide if we want to descend into "abc"
 			 * directory.
 			 */
-			strcpy(path_buf + len + entry.pathlen, "/");
+			strcpy(path_buf + len + tree_entry_len(entry.path, entry.sha1), "/");
 
 		if (!pathspec_matches(paths, down))
 			;
 		else if (S_ISREG(entry.mode))
 			hit |= grep_sha1(opt, entry.sha1, path_buf, tn_len);
 		else if (S_ISDIR(entry.mode)) {
-			char type[20];
+			enum object_type type;
 			struct tree_desc sub;
 			void *data;
-			data = read_sha1_file(entry.sha1, type, &sub.size);
+			unsigned long size;
+
+			data = read_sha1_file(entry.sha1, &type, &size);
 			if (!data)
 				die("unable to read tree (%s)",
 				    sha1_to_hex(entry.sha1));
-			sub.buf = data;
+			init_tree_desc(&sub, data, size);
 			hit |= grep_tree(opt, paths, &sub, tree_name, down);
 			free(data);
 		}
@@ -403,12 +410,13 @@
 	if (obj->type == OBJ_COMMIT || obj->type == OBJ_TREE) {
 		struct tree_desc tree;
 		void *data;
+		unsigned long size;
 		int hit;
 		data = read_object_with_reference(obj->sha1, tree_type,
-						  &tree.size, NULL);
+						  &size, NULL);
 		if (!data)
 			die("unable to read tree (%s)", sha1_to_hex(obj->sha1));
-		tree.buf = data;
+		init_tree_desc(&tree, data, size);
 		hit = grep_tree(opt, paths, &tree, name, "");
 		free(data);
 		return hit;
@@ -426,6 +434,19 @@
 static const char emsg_missing_argument[] =
 "option requires an argument -%s";
 
+static int strtoul_ui(char const *s, unsigned int *result)
+{
+	unsigned long ul;
+	char *p;
+
+	errno = 0;
+	ul = strtoul(s, &p, 10);
+	if (errno || *p || p == s || (unsigned int) ul != ul)
+		return -1;
+	*result = ul;
+	return 0;
+}
+
 int cmd_grep(int argc, const char **argv, const char *prefix)
 {
 	int hit = 0;
@@ -527,9 +548,9 @@
 			opt.word_regexp = 1;
 			continue;
 		}
-		if (!strncmp("-A", arg, 2) ||
-		    !strncmp("-B", arg, 2) ||
-		    !strncmp("-C", arg, 2) ||
+		if (!prefixcmp(arg, "-A") ||
+		    !prefixcmp(arg, "-B") ||
+		    !prefixcmp(arg, "-C") ||
 		    (arg[0] == '-' && '1' <= arg[1] && arg[1] <= '9')) {
 			unsigned num;
 			const char *scan;
@@ -548,7 +569,7 @@
 				scan = arg + 1;
 				break;
 			}
-			if (sscanf(scan, "%u", &num) != 1)
+			if (strtoul_ui(scan, &num))
 				die(emsg_invalid_context_len, scan);
 			switch (arg[1]) {
 			case 'A':
diff --git a/builtin-init-db.c b/builtin-init-db.c
index 12e43d0..4df9fd0 100644
--- a/builtin-init-db.c
+++ b/builtin-init-db.c
@@ -283,11 +283,11 @@
 
 	for (i = 1; i < argc; i++, argv++) {
 		const char *arg = argv[1];
-		if (!strncmp(arg, "--template=", 11))
+		if (!prefixcmp(arg, "--template="))
 			template_dir = arg+11;
 		else if (!strcmp(arg, "--shared"))
 			shared_repository = PERM_GROUP;
-		else if (!strncmp(arg, "--shared=", 9))
+		else if (!prefixcmp(arg, "--shared="))
 			shared_repository = git_config_perm("arg", arg+9);
 		else
 			usage(init_db_usage);
diff --git a/builtin-log.c b/builtin-log.c
index a5e4b62..71df957 100644
--- a/builtin-log.c
+++ b/builtin-log.c
@@ -32,10 +32,10 @@
 		rev->always_show_header = 0;
 	for (i = 1; i < argc; i++) {
 		const char *arg = argv[i];
-		if (!strncmp(arg, "--encoding=", 11)) {
+		if (!prefixcmp(arg, "--encoding=")) {
 			arg += 11;
 			if (strcmp(arg, "none"))
-				git_log_output_encoding = strdup(arg);
+				git_log_output_encoding = xstrdup(arg);
 			else
 				git_log_output_encoding = "";
 		}
@@ -89,8 +89,8 @@
 static int show_object(const unsigned char *sha1, int suppress_header)
 {
 	unsigned long size;
-	char type[20];
-	char *buf = read_sha1_file(sha1, type, &size);
+	enum object_type type;
+	char *buf = read_sha1_file(sha1, &type, &size);
 	int offset = 0;
 
 	if (!buf)
@@ -293,7 +293,7 @@
 
 		sol += 2;
 		/* strip [PATCH] or [PATCH blabla] */
-		if (!keep_subject && !strncmp(sol, "[PATCH", 6)) {
+		if (!keep_subject && !prefixcmp(sol, "[PATCH")) {
 			char *eos = strchr(sol + 6, ']');
 			if (eos) {
 				while (isspace(*eos))
@@ -448,7 +448,7 @@
 		else if (!strcmp(argv[i], "-n") ||
 				!strcmp(argv[i], "--numbered"))
 			numbered = 1;
-		else if (!strncmp(argv[i], "--start-number=", 15))
+		else if (!prefixcmp(argv[i], "--start-number="))
 			start_number = strtol(argv[i] + 15, NULL, 10);
 		else if (!strcmp(argv[i], "--start-number")) {
 			i++;
@@ -482,15 +482,27 @@
 			memcpy(add_signoff, committer, endpos - committer + 1);
 			add_signoff[endpos - committer + 1] = 0;
 		}
-		else if (!strcmp(argv[i], "--attach"))
+		else if (!strcmp(argv[i], "--attach")) {
 			rev.mime_boundary = git_version_string;
-		else if (!strncmp(argv[i], "--attach=", 9))
+			rev.no_inline = 1;
+		}
+		else if (!prefixcmp(argv[i], "--attach=")) {
 			rev.mime_boundary = argv[i] + 9;
+			rev.no_inline = 1;
+		}
+		else if (!strcmp(argv[i], "--inline")) {
+			rev.mime_boundary = git_version_string;
+			rev.no_inline = 0;
+		}
+		else if (!prefixcmp(argv[i], "--inline=")) {
+			rev.mime_boundary = argv[i] + 9;
+			rev.no_inline = 0;
+		}
 		else if (!strcmp(argv[i], "--ignore-if-in-upstream"))
 			ignore_if_in_upstream = 1;
 		else if (!strcmp(argv[i], "--thread"))
 			thread = 1;
-		else if (!strncmp(argv[i], "--in-reply-to=", 14))
+		else if (!prefixcmp(argv[i], "--in-reply-to="))
 			in_reply_to = argv[i] + 14;
 		else if (!strcmp(argv[i], "--in-reply-to")) {
 			i++;
@@ -498,7 +510,7 @@
 				die("Need a Message-Id for --in-reply-to");
 			in_reply_to = argv[i];
 		}
-		else if (!strncmp(argv[i], "--suffix=", 9))
+		else if (!prefixcmp(argv[i], "--suffix="))
 			fmt_patch_suffix = argv[i] + 9;
 		else
 			argv[j++] = argv[i];
diff --git a/builtin-ls-files.c b/builtin-ls-files.c
index ac89eb2..4e1d5af 100644
--- a/builtin-ls-files.c
+++ b/builtin-ls-files.c
@@ -406,7 +406,7 @@
 			add_exclude(argv[++i], "", 0, &dir.exclude_list[EXC_CMDL]);
 			continue;
 		}
-		if (!strncmp(arg, "--exclude=", 10)) {
+		if (!prefixcmp(arg, "--exclude=")) {
 			exc_given = 1;
 			add_exclude(arg+10, "", 0, &dir.exclude_list[EXC_CMDL]);
 			continue;
@@ -416,12 +416,12 @@
 			add_excludes_from_file(&dir, argv[++i]);
 			continue;
 		}
-		if (!strncmp(arg, "--exclude-from=", 15)) {
+		if (!prefixcmp(arg, "--exclude-from=")) {
 			exc_given = 1;
 			add_excludes_from_file(&dir, arg+15);
 			continue;
 		}
-		if (!strncmp(arg, "--exclude-per-directory=", 24)) {
+		if (!prefixcmp(arg, "--exclude-per-directory=")) {
 			exc_given = 1;
 			dir.exclude_per_dir = arg + 24;
 			continue;
@@ -434,7 +434,7 @@
 			error_unmatch = 1;
 			continue;
 		}
-		if (!strncmp(arg, "--abbrev=", 9)) {
+		if (!prefixcmp(arg, "--abbrev=")) {
 			abbrev = strtoul(arg+9, NULL, 10);
 			if (abbrev && abbrev < MINIMUM_ABBREV)
 				abbrev = MINIMUM_ABBREV;
diff --git a/builtin-ls-tree.c b/builtin-ls-tree.c
index 201defd..6472610 100644
--- a/builtin-ls-tree.c
+++ b/builtin-ls-tree.c
@@ -118,7 +118,7 @@
 				chomp_prefix = 0;
 				break;
 			}
-			if (!strncmp(argv[1]+2, "abbrev=",7)) {
+			if (!prefixcmp(argv[1]+2, "abbrev=")) {
 				abbrev = strtoul(argv[1]+9, NULL, 10);
 				if (abbrev && abbrev < MINIMUM_ABBREV)
 					abbrev = MINIMUM_ABBREV;
diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
index cf5ef29..c95e477 100644
--- a/builtin-mailinfo.c
+++ b/builtin-mailinfo.c
@@ -11,19 +11,22 @@
 static int keep_subject;
 static const char *metainfo_charset;
 static char line[1000];
-static char date[1000];
 static char name[1000];
 static char email[1000];
-static char subject[1000];
 
 static enum  {
 	TE_DONTCARE, TE_QP, TE_BASE64,
 } transfer_encoding;
-static char charset[256];
+static enum  {
+	TYPE_TEXT, TYPE_OTHER,
+} message_type;
 
-static char multipart_boundary[1000];
-static int multipart_boundary_len;
+static char charset[256];
 static int patch_lines;
+static char **p_hdr_data, **s_hdr_data;
+
+#define MAX_HDR_PARSED 10
+#define MAX_BOUNDARIES 5
 
 static char *sanity_check(char *name, char *email)
 {
@@ -137,15 +140,13 @@
 	return 1;
 }
 
-static int handle_date(char *line)
+static int handle_header(char *line, char *data, int ofs)
 {
-	strcpy(date, line);
-	return 0;
-}
+	if (!line || !data)
+		return 1;
 
-static int handle_subject(char *line)
-{
-	strcpy(subject, line);
+	strcpy(data, line+ofs);
+
 	return 0;
 }
 
@@ -177,17 +178,32 @@
 	return 1;
 }
 
-static int handle_subcontent_type(char *line)
+struct content_type {
+	char *boundary;
+	int boundary_len;
+};
+
+static struct content_type content[MAX_BOUNDARIES];
+
+static struct content_type *content_top = content;
+
+static int handle_content_type(char *line)
 {
-	/* We do not want to mess with boundary.  Note that we do not
-	 * handle nested multipart.
-	 */
-	if (strcasestr(line, "boundary=")) {
-		fprintf(stderr, "Not handling nested multipart message.\n");
-		exit(1);
+	char boundary[256];
+
+	if (strcasestr(line, "text/") == NULL)
+		 message_type = TYPE_OTHER;
+	if (slurp_attr(line, "boundary=", boundary + 2)) {
+		memcpy(boundary, "--", 2);
+		if (content_top++ >= &content[MAX_BOUNDARIES]) {
+			fprintf(stderr, "Too many boundaries to handle\n");
+			exit(1);
+		}
+		content_top->boundary_len = strlen(boundary);
+		content_top->boundary = xmalloc(content_top->boundary_len+1);
+		strcpy(content_top->boundary, boundary);
 	}
-	slurp_attr(line, "charset=", charset);
-	if (*charset) {
+	if (slurp_attr(line, "charset=", charset)) {
 		int i, c;
 		for (i = 0; (c = charset[i]) != 0; i++)
 			charset[i] = tolower(c);
@@ -195,17 +211,6 @@
 	return 0;
 }
 
-static int handle_content_type(char *line)
-{
-	*multipart_boundary = 0;
-	if (slurp_attr(line, "boundary=", multipart_boundary + 2)) {
-		memcpy(multipart_boundary, "--", 2);
-		multipart_boundary_len = strlen(multipart_boundary);
-	}
-	slurp_attr(line, "charset=", charset);
-	return 0;
-}
-
 static int handle_content_transfer_encoding(char *line)
 {
 	if (strcasestr(line, "base64"))
@@ -219,7 +224,7 @@
 
 static int is_multipart_boundary(const char *line)
 {
-	return (!memcmp(line, multipart_boundary, multipart_boundary_len));
+	return (!memcmp(line, content_top->boundary, content_top->boundary_len));
 }
 
 static int eatspace(char *line)
@@ -230,62 +235,6 @@
 	return len;
 }
 
-#define SEEN_FROM 01
-#define SEEN_DATE 02
-#define SEEN_SUBJECT 04
-#define SEEN_BOGUS_UNIX_FROM 010
-#define SEEN_PREFIX  020
-
-/* First lines of body can have From:, Date:, and Subject: or empty */
-static void handle_inbody_header(int *seen, char *line)
-{
-	if (*seen & SEEN_PREFIX)
-		return;
-	if (isspace(*line)) {
-		char *cp;
-		for (cp = line + 1; *cp; cp++) {
-			if (!isspace(*cp))
-				break;
-		}
-		if (!*cp)
-			return;
-	}
-	if (!memcmp(">From", line, 5) && isspace(line[5])) {
-		if (!(*seen & SEEN_BOGUS_UNIX_FROM)) {
-			*seen |= SEEN_BOGUS_UNIX_FROM;
-			return;
-		}
-	}
-	if (!memcmp("From:", line, 5) && isspace(line[5])) {
-		if (!(*seen & SEEN_FROM) && handle_from(line+6)) {
-			*seen |= SEEN_FROM;
-			return;
-		}
-	}
-	if (!memcmp("Date:", line, 5) && isspace(line[5])) {
-		if (!(*seen & SEEN_DATE)) {
-			handle_date(line+6);
-			*seen |= SEEN_DATE;
-			return;
-		}
-	}
-	if (!memcmp("Subject:", line, 8) && isspace(line[8])) {
-		if (!(*seen & SEEN_SUBJECT)) {
-			handle_subject(line+9);
-			*seen |= SEEN_SUBJECT;
-			return;
-		}
-	}
-	if (!memcmp("[PATCH]", line, 7) && isspace(line[7])) {
-		if (!(*seen & SEEN_SUBJECT)) {
-			handle_subject(line);
-			*seen |= SEEN_SUBJECT;
-			return;
-		}
-	}
-	*seen |= SEEN_PREFIX;
-}
-
 static char *cleanup_subject(char *subject)
 {
 	if (keep_subject)
@@ -296,7 +245,7 @@
 		switch (*subject) {
 		case 'r': case 'R':
 			if (!memcmp("e:", subject+1, 2)) {
-				subject +=3;
+				subject += 3;
 				continue;
 			}
 			break;
@@ -341,57 +290,62 @@
 }
 
 static void decode_header(char *it);
-typedef int (*header_fn_t)(char *);
-struct header_def {
-	const char *name;
-	header_fn_t func;
-	int namelen;
+static char *header[MAX_HDR_PARSED] = {
+	"From","Subject","Date",
 };
 
-static void check_header(char *line, struct header_def *header)
+static int check_header(char *line, char **hdr_data, int overwrite)
 {
 	int i;
 
-	if (header[0].namelen <= 0) {
-		for (i = 0; header[i].name; i++)
-			header[i].namelen = strlen(header[i].name);
-	}
-	for (i = 0; header[i].name; i++) {
-		int len = header[i].namelen;
-		if (!strncasecmp(line, header[i].name, len) &&
+	/* search for the interesting parts */
+	for (i = 0; header[i]; i++) {
+		int len = strlen(header[i]);
+		if ((!hdr_data[i] || overwrite) &&
+		    !strncasecmp(line, header[i], len) &&
 		    line[len] == ':' && isspace(line[len + 1])) {
 			/* Unwrap inline B and Q encoding, and optionally
 			 * normalize the meta information to utf8.
 			 */
 			decode_header(line + len + 2);
-			header[i].func(line + len + 2);
-			break;
+			hdr_data[i] = xmalloc(1000 * sizeof(char));
+			if (! handle_header(line, hdr_data[i], len + 2)) {
+				return 1;
+			}
 		}
 	}
-}
 
-static void check_subheader_line(char *line)
-{
-	static struct header_def header[] = {
-		{ "Content-Type", handle_subcontent_type },
-		{ "Content-Transfer-Encoding",
-		  handle_content_transfer_encoding },
-		{ NULL },
-	};
-	check_header(line, header);
-}
-static void check_header_line(char *line)
-{
-	static struct header_def header[] = {
-		{ "From", handle_from },
-		{ "Date", handle_date },
-		{ "Subject", handle_subject },
-		{ "Content-Type", handle_content_type },
-		{ "Content-Transfer-Encoding",
-		  handle_content_transfer_encoding },
-		{ NULL },
-	};
-	check_header(line, header);
+	/* Content stuff */
+	if (!strncasecmp(line, "Content-Type", 12) &&
+		line[12] == ':' && isspace(line[12 + 1])) {
+		decode_header(line + 12 + 2);
+		if (! handle_content_type(line)) {
+			return 1;
+		}
+	}
+	if (!strncasecmp(line, "Content-Transfer-Encoding", 25) &&
+		line[25] == ':' && isspace(line[25 + 1])) {
+		decode_header(line + 25 + 2);
+		if (! handle_content_transfer_encoding(line)) {
+			return 1;
+		}
+	}
+
+	/* for inbody stuff */
+	if (!memcmp(">From", line, 5) && isspace(line[5]))
+		return 1;
+	if (!memcmp("[PATCH]", line, 7) && isspace(line[7])) {
+		for (i = 0; header[i]; i++) {
+			if (!memcmp("Subject: ", header[i], 9)) {
+				if (! handle_header(line, hdr_data[i], 0)) {
+					return 1;
+				}
+			}
+		}
+	}
+
+	/* no match */
+	return 0;
 }
 
 static int is_rfc2822_header(char *line)
@@ -545,10 +499,10 @@
 	return 0;
 }
 
-static void convert_to_utf8(char *line, char *charset)
+static void convert_to_utf8(char *line, const char *charset)
 {
-	static char latin_one[] = "latin1";
-	char *input_charset = *charset ? charset : latin_one;
+	static const char latin_one[] = "latin1";
+	const char *input_charset = *charset ? charset : latin_one;
 	char *out = reencode_string(line, metainfo_charset, input_charset);
 
 	if (!out)
@@ -647,147 +601,254 @@
 	}
 }
 
+static int handle_filter(char *line);
+
+static int find_boundary(void)
+{
+	while(fgets(line, sizeof(line), fin) != NULL) {
+		if (is_multipart_boundary(line))
+			return 1;
+	}
+	return 0;
+}
+
+static int handle_boundary(void)
+{
+	char newline[]="\n";
+again:
+	if (!memcmp(line+content_top->boundary_len, "--", 2)) {
+		/* we hit an end boundary */
+		/* pop the current boundary off the stack */
+		free(content_top->boundary);
+
+		/* technically won't happen as is_multipart_boundary()
+		   will fail first.  But just in case..
+		 */
+		if (content_top-- < content) {
+			fprintf(stderr, "Detected mismatched boundaries, "
+					"can't recover\n");
+			exit(1);
+		}
+		handle_filter(newline);
+
+		/* skip to the next boundary */
+		if (!find_boundary())
+			return 0;
+		goto again;
+	}
+
+	/* set some defaults */
+	transfer_encoding = TE_DONTCARE;
+	charset[0] = 0;
+	message_type = TYPE_TEXT;
+
+	/* slurp in this section's info */
+	while (read_one_header_line(line, sizeof(line), fin))
+		check_header(line, p_hdr_data, 0);
+
+	/* eat the blank line after section info */
+	return (fgets(line, sizeof(line), fin) != NULL);
+}
+
+static inline int patchbreak(const char *line)
+{
+	/* Beginning of a "diff -" header? */
+	if (!memcmp("diff -", line, 6))
+		return 1;
+
+	/* CVS "Index: " line? */
+	if (!memcmp("Index: ", line, 7))
+		return 1;
+
+	/*
+	 * "--- <filename>" starts patches without headers
+	 * "---<sp>*" is a manual separator
+	 */
+	if (!memcmp("---", line, 3)) {
+		line += 3;
+		/* space followed by a filename? */
+		if (line[0] == ' ' && !isspace(line[1]))
+			return 1;
+		/* Just whitespace? */
+		for (;;) {
+			unsigned char c = *line++;
+			if (c == '\n')
+				return 1;
+			if (!isspace(c))
+				break;
+		}
+		return 0;
+	}
+	return 0;
+}
+
+
+static int handle_commit_msg(char *line)
+{
+	static int still_looking = 1;
+
+	if (!cmitmsg)
+		return 0;
+
+	if (still_looking) {
+		char *cp = line;
+		if (isspace(*line)) {
+			for (cp = line + 1; *cp; cp++) {
+				if (!isspace(*cp))
+					break;
+			}
+			if (!*cp)
+				return 0;
+		}
+		if ((still_looking = check_header(cp, s_hdr_data, 0)) != 0)
+			return 0;
+	}
+
+	/* normalize the log message to UTF-8. */
+	if (metainfo_charset)
+		convert_to_utf8(line, charset);
+
+	if (patchbreak(line)) {
+		fclose(cmitmsg);
+		cmitmsg = NULL;
+		return 1;
+	}
+
+	fputs(line, cmitmsg);
+	return 0;
+}
+
+static int handle_patch(char *line)
+{
+	fputs(line, patchfile);
+	patch_lines++;
+	return 0;
+}
+
+static int handle_filter(char *line)
+{
+	static int filter = 0;
+
+	/* filter tells us which part we left off on
+	 * a non-zero return indicates we hit a filter point
+	 */
+	switch (filter) {
+	case 0:
+		if (!handle_commit_msg(line))
+			break;
+		filter++;
+	case 1:
+		if (!handle_patch(line))
+			break;
+		filter++;
+	default:
+		return 1;
+	}
+
+	return 0;
+}
+
+static void handle_body(void)
+{
+	int rc = 0;
+	static char newline[2000];
+	static char *np = newline;
+
+	/* Skip up to the first boundary */
+	if (content_top->boundary) {
+		if (!find_boundary())
+			return;
+	}
+
+	do {
+		/* process any boundary lines */
+		if (content_top->boundary && is_multipart_boundary(line)) {
+			/* flush any leftover */
+			if ((transfer_encoding == TE_BASE64)  &&
+			    (np != newline)) {
+				handle_filter(newline);
+			}
+			if (!handle_boundary())
+				return;
+		}
+
+		/* Unwrap transfer encoding */
+		decode_transfer_encoding(line);
+
+		switch (transfer_encoding) {
+		case TE_BASE64:
+		{
+			char *op = line;
+
+			/* binary data most likely doesn't have newlines */
+			if (message_type != TYPE_TEXT) {
+				rc = handle_filter(line);
+				break;
+			}
+
+			/* this is a decoded line that may contain
+			 * multiple new lines.  Pass only one chunk
+			 * at a time to handle_filter()
+			 */
+
+			do {
+				while (*op != '\n' && *op != 0)
+					*np++ = *op++;
+				*np = *op;
+				if (*np != 0) {
+					/* should be sitting on a new line */
+					*(++np) = 0;
+					op++;
+					rc = handle_filter(newline);
+					np = newline;
+				}
+			} while (*op != 0);
+			/* the partial chunk is saved in newline and
+			 * will be appended by the next iteration of fgets
+			 */
+			break;
+		}
+		default:
+			rc = handle_filter(line);
+		}
+		if (rc)
+			/* nothing left to filter */
+			break;
+	} while (fgets(line, sizeof(line), fin));
+
+	return;
+}
+
 static void handle_info(void)
 {
 	char *sub;
+	char *hdr;
+	int i;
 
-	sub = cleanup_subject(subject);
-	cleanup_space(name);
-	cleanup_space(date);
-	cleanup_space(email);
-	cleanup_space(sub);
+	for (i = 0; header[i]; i++) {
 
-	fprintf(fout, "Author: %s\nEmail: %s\nSubject: %s\nDate: %s\n\n",
-	       name, email, sub, date);
-}
-
-/* We are inside message body and have read line[] already.
- * Spit out the commit log.
- */
-static int handle_commit_msg(int *seen)
-{
-	if (!cmitmsg)
-		return 0;
-	do {
-		if (!memcmp("diff -", line, 6) ||
-		    !memcmp("---", line, 3) ||
-		    !memcmp("Index: ", line, 7))
-			break;
-		if ((multipart_boundary[0] && is_multipart_boundary(line))) {
-			/* We come here when the first part had only
-			 * the commit message without any patch.  We
-			 * pretend we have not seen this line yet, and
-			 * go back to the loop.
-			 */
-			return 1;
-		}
-
-		/* Unwrap transfer encoding and optionally
-		 * normalize the log message to UTF-8.
-		 */
-		decode_transfer_encoding(line);
-		if (metainfo_charset)
-			convert_to_utf8(line, charset);
-
-		handle_inbody_header(seen, line);
-		if (!(*seen & SEEN_PREFIX))
+		/* only print inbody headers if we output a patch file */
+		if (patch_lines && s_hdr_data[i])
+			hdr = s_hdr_data[i];
+		else if (p_hdr_data[i])
+			hdr = p_hdr_data[i];
+		else
 			continue;
 
-		fputs(line, cmitmsg);
-	} while (fgets(line, sizeof(line), fin) != NULL);
-	fclose(cmitmsg);
-	cmitmsg = NULL;
-	return 0;
-}
-
-/* We have done the commit message and have the first
- * line of the patch in line[].
- */
-static void handle_patch(void)
-{
-	do {
-		if (multipart_boundary[0] && is_multipart_boundary(line))
-			break;
-		/* Only unwrap transfer encoding but otherwise do not
-		 * do anything.  We do *NOT* want UTF-8 conversion
-		 * here; we are dealing with the user payload.
-		 */
-		decode_transfer_encoding(line);
-		fputs(line, patchfile);
-		patch_lines++;
-	} while (fgets(line, sizeof(line), fin) != NULL);
-}
-
-/* multipart boundary and transfer encoding are set up for us, and we
- * are at the end of the sub header.  do equivalent of handle_body up
- * to the next boundary without closing patchfile --- we will expect
- * that the first part to contain commit message and a patch, and
- * handle other parts as pure patches.
- */
-static int handle_multipart_one_part(int *seen)
-{
-	int n = 0;
-
-	while (fgets(line, sizeof(line), fin) != NULL) {
-	again:
-		n++;
-		if (is_multipart_boundary(line))
-			break;
-		if (handle_commit_msg(seen))
-			goto again;
-		handle_patch();
-		break;
-	}
-	if (n == 0)
-		return -1;
-	return 0;
-}
-
-static void handle_multipart_body(void)
-{
-	int seen = 0;
-	int part_num = 0;
-
-	/* Skip up to the first boundary */
-	while (fgets(line, sizeof(line), fin) != NULL)
-		if (is_multipart_boundary(line)) {
-			part_num = 1;
-			break;
+		if (!memcmp(header[i], "Subject", 7)) {
+			sub = cleanup_subject(hdr);
+			cleanup_space(sub);
+			fprintf(fout, "Subject: %s\n", sub);
+		} else if (!memcmp(header[i], "From", 4)) {
+			handle_from(hdr);
+			fprintf(fout, "Author: %s\n", name);
+			fprintf(fout, "Email: %s\n", email);
+		} else {
+			cleanup_space(hdr);
+			fprintf(fout, "%s: %s\n", header[i], hdr);
 		}
-	if (!part_num)
-		return;
-	/* We are on boundary line.  Start slurping the subhead. */
-	while (1) {
-		int hdr = read_one_header_line(line, sizeof(line), fin);
-		if (!hdr) {
-			if (handle_multipart_one_part(&seen) < 0)
-				return;
-			/* Reset per part headers */
-			transfer_encoding = TE_DONTCARE;
-			charset[0] = 0;
-		}
-		else
-			check_subheader_line(line);
 	}
-	fclose(patchfile);
-	if (!patch_lines) {
-		fprintf(stderr, "No patch found\n");
-		exit(1);
-	}
-}
-
-/* Non multipart message */
-static void handle_body(void)
-{
-	int seen = 0;
-
-	handle_commit_msg(&seen);
-	handle_patch();
-	fclose(patchfile);
-	if (!patch_lines) {
-		fprintf(stderr, "No patch found\n");
-		exit(1);
-	}
+	fprintf(fout, "\n");
 }
 
 int mailinfo(FILE *in, FILE *out, int ks, const char *encoding,
@@ -809,18 +870,16 @@
 		fclose(cmitmsg);
 		return -1;
 	}
-	while (1) {
-		int hdr = read_one_header_line(line, sizeof(line), fin);
-		if (!hdr) {
-			if (multipart_boundary[0])
-				handle_multipart_body();
-			else
-				handle_body();
-			handle_info();
-			break;
-		}
-		check_header_line(line);
-	}
+
+	p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(char *));
+	s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(char *));
+
+	/* process the email header */
+	while (read_one_header_line(line, sizeof(line), fin))
+		check_header(line, p_hdr_data, 1);
+
+	handle_body();
+	handle_info();
 
 	return 0;
 }
@@ -847,7 +906,7 @@
 			metainfo_charset = def_charset;
 		else if (!strcmp(argv[1], "-n"))
 			metainfo_charset = NULL;
-		else if (!strncmp(argv[1], "--encoding=", 11))
+		else if (!prefixcmp(argv[1], "--encoding="))
 			metainfo_charset = argv[1] + 11;
 		else
 			usage(mailinfo_usage);
diff --git a/merge-base.c b/builtin-merge-base.c
similarity index 79%
rename from merge-base.c
rename to builtin-merge-base.c
index 385f4ba..e35d362 100644
--- a/merge-base.c
+++ b/builtin-merge-base.c
@@ -1,9 +1,7 @@
 #include "cache.h"
 #include "commit.h"
 
-static int show_all;
-
-static int merge_base(struct commit *rev1, struct commit *rev2)
+static int show_merge_base(struct commit *rev1, struct commit *rev2, int show_all)
 {
 	struct commit_list *result = get_merge_bases(rev1, rev2, 0);
 
@@ -23,16 +21,16 @@
 static const char merge_base_usage[] =
 "git-merge-base [--all] <commit-id> <commit-id>";
 
-int main(int argc, char **argv)
+int cmd_merge_base(int argc, const char **argv, const char *prefix)
 {
 	struct commit *rev1, *rev2;
 	unsigned char rev1key[20], rev2key[20];
+	int show_all = 0;
 
-	setup_git_directory();
 	git_config(git_default_config);
 
 	while (1 < argc && argv[1][0] == '-') {
-		char *arg = argv[1];
+		const char *arg = argv[1];
 		if (!strcmp(arg, "-a") || !strcmp(arg, "--all"))
 			show_all = 1;
 		else
@@ -49,5 +47,5 @@
 	rev2 = lookup_commit_reference(rev2key);
 	if (!rev1 || !rev2)
 		return 1;
-	return merge_base(rev1, rev2);
+	return show_merge_base(rev1, rev2, show_all);
 }
diff --git a/builtin-name-rev.c b/builtin-name-rev.c
index b4f15cc..c022224 100644
--- a/builtin-name-rev.c
+++ b/builtin-name-rev.c
@@ -5,7 +5,7 @@
 #include "refs.h"
 
 static const char name_rev_usage[] =
-	"git-name-rev [--tags] ( --all | --stdin | committish [committish...] )\n";
+	"git-name-rev [--tags | --refs=<pattern>] ( --all | --stdin | committish [committish...] )\n";
 
 typedef struct rev_name {
 	const char *tip_name;
@@ -57,13 +57,17 @@
 			parents;
 			parents = parents->next, parent_number++) {
 		if (parent_number > 1) {
-			char *new_name = xmalloc(strlen(tip_name)+8);
+			int len = strlen(tip_name);
+			char *new_name = xmalloc(len + 8);
 
+			if (len > 2 && !strcmp(tip_name + len - 2, "^0"))
+				len -= 2;
 			if (generation > 0)
-				sprintf(new_name, "%s~%d^%d", tip_name,
+				sprintf(new_name, "%.*s~%d^%d", len, tip_name,
 						generation, parent_number);
 			else
-				sprintf(new_name, "%s^%d", tip_name, parent_number);
+				sprintf(new_name, "%.*s^%d", len, tip_name,
+						parent_number);
 
 			name_rev(parents->item, new_name,
 				merge_traversals + 1 , 0, 0);
@@ -74,13 +78,21 @@
 	}
 }
 
+struct name_ref_data {
+	int tags_only;
+	const char *ref_filter;
+};
+
 static int name_ref(const char *path, const unsigned char *sha1, int flags, void *cb_data)
 {
 	struct object *o = parse_object(sha1);
-	int tags_only = *(int*)cb_data;
+	struct name_ref_data *data = cb_data;
 	int deref = 0;
 
-	if (tags_only && strncmp(path, "refs/tags/", 10))
+	if (data->tags_only && prefixcmp(path, "refs/tags/"))
+		return 0;
+
+	if (data->ref_filter && fnmatch(data->ref_filter, path, 0))
 		return 0;
 
 	while (o && o->type == OBJ_TAG) {
@@ -93,9 +105,9 @@
 	if (o && o->type == OBJ_COMMIT) {
 		struct commit *commit = (struct commit *)o;
 
-		if (!strncmp(path, "refs/heads/", 11))
+		if (!prefixcmp(path, "refs/heads/"))
 			path = path + 11;
-		else if (!strncmp(path, "refs/", 5))
+		else if (!prefixcmp(path, "refs/"))
 			path = path + 5;
 
 		name_rev(commit, xstrdup(path), 0, 0, deref);
@@ -119,17 +131,22 @@
 
 	if (!n->generation)
 		return n->tip_name;
+	else {
+		int len = strlen(n->tip_name);
+		if (len > 2 && !strcmp(n->tip_name + len - 2, "^0"))
+			len -= 2;
+		snprintf(buffer, sizeof(buffer), "%.*s~%d", len, n->tip_name,
+				n->generation);
 
-	snprintf(buffer, sizeof(buffer), "%s~%d", n->tip_name, n->generation);
-
-	return buffer;
+		return buffer;
+	}
 }
 
 int cmd_name_rev(int argc, const char **argv, const char *prefix)
 {
 	struct object_array revs = { 0, 0, NULL };
 	int as_is = 0, all = 0, transform_stdin = 0;
-	int tags_only = 0;
+	struct name_ref_data data = { 0, NULL };
 
 	git_config(git_default_config);
 
@@ -146,7 +163,10 @@
 				as_is = 1;
 				continue;
 			} else if (!strcmp(*argv, "--tags")) {
-				tags_only = 1;
+				data.tags_only = 1;
+				continue;
+			} else  if (!prefixcmp(*argv, "--refs=")) {
+				data.ref_filter = *argv + 7;
 				continue;
 			} else if (!strcmp(*argv, "--all")) {
 				if (argc > 1)
@@ -185,7 +205,7 @@
 		add_object_array((struct object *)commit, *argv, &revs);
 	}
 
-	for_each_ref(name_ref, &tags_only);
+	for_each_ref(name_ref, &data);
 
 	if (transform_stdin) {
 		char buffer[2048];
diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c
index 9713882..b5f9648 100644
--- a/builtin-pack-objects.c
+++ b/builtin-pack-objects.c
@@ -23,7 +23,7 @@
 struct object_entry {
 	unsigned char sha1[20];
 	unsigned long size;	/* uncompressed size */
-	unsigned long offset;	/* offset into the final pack file;
+	off_t offset;	/* offset into the final pack file;
 				 * nonzero if already written.
 				 */
 	unsigned int depth;	/* delta depth */
@@ -35,7 +35,7 @@
 #define in_pack_header_size delta_size	/* only when reusing pack data */
 	struct object_entry *delta;	/* delta base object */
 	struct packed_git *in_pack; 	/* already in pack */
-	unsigned int in_pack_offset;
+	off_t in_pack_offset;
 	struct object_entry *delta_child; /* deltified objects who bases me */
 	struct object_entry *delta_sibling; /* other deltified objects who
 					     * uses the same base as me
@@ -68,7 +68,7 @@
 
 static struct object_entry **sorted_by_sha, **sorted_by_type;
 static struct object_entry *objects;
-static int nr_objects, nr_alloc, nr_result;
+static uint32_t nr_objects, nr_alloc, nr_result;
 static const char *base_name;
 static unsigned char pack_file_sha1[20];
 static int progress = 1;
@@ -101,7 +101,7 @@
  * get the object sha1 from the main index.
  */
 struct revindex_entry {
-	unsigned int offset;
+	off_t offset;
 	unsigned int nr;
 };
 struct pack_revindex {
@@ -114,10 +114,8 @@
 /*
  * stats
  */
-static int written;
-static int written_delta;
-static int reused;
-static int reused_delta;
+static uint32_t written, written_delta;
+static uint32_t reused, reused_delta;
 
 static int pack_revindex_ix(struct packed_git *p)
 {
@@ -168,11 +166,12 @@
 	struct packed_git *p = rix->p;
 	int num_ent = num_packed_objects(p);
 	int i;
-	void *index = p->index_base + 256;
+	const char *index = p->index_data;
 
+	index += 4 * 256;
 	rix->revindex = xmalloc(sizeof(*rix->revindex) * (num_ent + 1));
 	for (i = 0; i < num_ent; i++) {
-		unsigned int hl = *((unsigned int *)((char *) index + 24*i));
+		uint32_t hl = *((uint32_t *)(index + 24 * i));
 		rix->revindex[i].offset = ntohl(hl);
 		rix->revindex[i].nr = i;
 	}
@@ -185,7 +184,7 @@
 }
 
 static struct revindex_entry * find_packed_object(struct packed_git *p,
-						  unsigned int ofs)
+						  off_t ofs)
 {
 	int num;
 	int lo, hi;
@@ -213,25 +212,24 @@
 	die("internal error: pack revindex corrupt");
 }
 
-static unsigned long find_packed_object_size(struct packed_git *p,
-					     unsigned long ofs)
+static off_t find_packed_object_size(struct packed_git *p, off_t ofs)
 {
 	struct revindex_entry *entry = find_packed_object(p, ofs);
 	return entry[1].offset - ofs;
 }
 
-static unsigned char *find_packed_object_name(struct packed_git *p,
-					      unsigned long ofs)
+static const unsigned char *find_packed_object_name(struct packed_git *p,
+						    off_t ofs)
 {
 	struct revindex_entry *entry = find_packed_object(p, ofs);
-	return (unsigned char *)(p->index_base + 256) + 24 * entry->nr + 4;
+	return ((unsigned char *)p->index_data) + 4 * 256 + 24 * entry->nr + 4;
 }
 
 static void *delta_against(void *buf, unsigned long size, struct object_entry *entry)
 {
 	unsigned long othersize, delta_size;
-	char type[10];
-	void *otherbuf = read_sha1_file(entry->delta->sha1, type, &othersize);
+	enum object_type type;
+	void *otherbuf = read_sha1_file(entry->delta->sha1, &type, &othersize);
 	void *delta_buf;
 
 	if (!otherbuf)
@@ -278,8 +276,8 @@
  */
 static int check_pack_inflate(struct packed_git *p,
 		struct pack_window **w_curs,
-		unsigned long offset,
-		unsigned long len,
+		off_t offset,
+		off_t len,
 		unsigned long expect)
 {
 	z_stream stream;
@@ -305,8 +303,8 @@
 static void copy_pack_data(struct sha1file *f,
 		struct packed_git *p,
 		struct pack_window **w_curs,
-		unsigned long offset,
-		unsigned long len)
+		off_t offset,
+		off_t len)
 {
 	unsigned char *in;
 	unsigned int avail;
@@ -314,7 +312,7 @@
 	while (len) {
 		in = use_pack(p, w_curs, offset, &avail);
 		if (avail > len)
-			avail = len;
+			avail = (unsigned int)len;
 		sha1write(f, in, avail);
 		offset += avail;
 		len -= avail;
@@ -371,14 +369,15 @@
 	return check_loose_inflate(map, mapsize, size);
 }
 
-static unsigned long write_object(struct sha1file *f,
+static off_t write_object(struct sha1file *f,
 				  struct object_entry *entry)
 {
 	unsigned long size;
-	char type[10];
+	enum object_type type;
 	void *buf;
 	unsigned char header[10];
-	unsigned hdrlen, datalen;
+	unsigned hdrlen;
+	off_t datalen;
 	enum object_type obj_type;
 	int to_reuse = 0;
 
@@ -416,7 +415,7 @@
 	}
 
 	if (!to_reuse) {
-		buf = read_sha1_file(entry->sha1, type, &size);
+		buf = read_sha1_file(entry->sha1, &type, &size);
 		if (!buf)
 			die("unable to read %s", sha1_to_hex(entry->sha1));
 		if (size != entry->size)
@@ -441,7 +440,7 @@
 			 * encoding of the relative offset for the delta
 			 * base from this object's position in the pack.
 			 */
-			unsigned long ofs = entry->offset - entry->delta->offset;
+			off_t ofs = entry->offset - entry->delta->offset;
 			unsigned pos = sizeof(header) - 1;
 			header[pos] = ofs & 127;
 			while (ofs >>= 7)
@@ -462,7 +461,7 @@
 	else {
 		struct packed_git *p = entry->in_pack;
 		struct pack_window *w_curs = NULL;
-		unsigned long offset;
+		off_t offset;
 
 		if (entry->delta) {
 			obj_type = (allow_ofs_delta && entry->delta->offset) ?
@@ -472,7 +471,7 @@
 		hdrlen = encode_header(obj_type, entry->size, header);
 		sha1write(f, header, hdrlen);
 		if (obj_type == OBJ_OFS_DELTA) {
-			unsigned long ofs = entry->offset - entry->delta->offset;
+			off_t ofs = entry->offset - entry->delta->offset;
 			unsigned pos = sizeof(header) - 1;
 			header[pos] = ofs & 127;
 			while (ofs >>= 7)
@@ -500,9 +499,9 @@
 	return hdrlen + datalen;
 }
 
-static unsigned long write_one(struct sha1file *f,
+static off_t write_one(struct sha1file *f,
 			       struct object_entry *e,
-			       unsigned long offset)
+			       off_t offset)
 {
 	if (e->offset || e->preferred_base)
 		/* offset starts from header size and cannot be zero
@@ -518,9 +517,9 @@
 
 static void write_pack_file(void)
 {
-	int i;
+	uint32_t i;
 	struct sha1file *f;
-	unsigned long offset;
+	off_t offset;
 	struct pack_header hdr;
 	unsigned last_percent = 999;
 	int do_progress = progress;
@@ -533,7 +532,7 @@
 		f = sha1create("%s-%s.%s", base_name,
 			       sha1_to_hex(object_list_sha1), "pack");
 	if (do_progress)
-		fprintf(stderr, "Writing %d objects.\n", nr_result);
+		fprintf(stderr, "Writing %u objects.\n", nr_result);
 
 	hdr.hdr_signature = htonl(PACK_SIGNATURE);
 	hdr.hdr_version = htonl(PACK_VERSION);
@@ -558,13 +557,13 @@
 		fputc('\n', stderr);
  done:
 	if (written != nr_result)
-		die("wrote %d objects while expecting %d", written, nr_result);
+		die("wrote %u objects while expecting %u", written, nr_result);
 	sha1close(f, pack_file_sha1, 1);
 }
 
 static void write_index_file(void)
 {
-	int i;
+	uint32_t i;
 	struct sha1file *f = sha1create("%s-%s.%s", base_name,
 					sha1_to_hex(object_list_sha1), "idx");
 	struct object_entry **list = sorted_by_sha;
@@ -633,7 +632,7 @@
 
 static void rehash_objects(void)
 {
-	int i;
+	uint32_t i;
 	struct object_entry *oe;
 
 	object_ix_hashsz = nr_objects * 3;
@@ -670,16 +669,16 @@
 
 static int add_object_entry(const unsigned char *sha1, unsigned hash, int exclude)
 {
-	unsigned int idx = nr_objects;
+	uint32_t idx = nr_objects;
 	struct object_entry *entry;
 	struct packed_git *p;
-	unsigned int found_offset = 0;
+	off_t found_offset = 0;
 	struct packed_git *found_pack = NULL;
 	int ix, status = 0;
 
 	if (!exclude) {
 		for (p = packed_git; p; p = p->next) {
-			unsigned long offset = find_pack_entry_one(sha1, p);
+			off_t offset = find_pack_entry_one(sha1, p);
 			if (offset) {
 				if (incremental)
 					return 0;
@@ -696,9 +695,8 @@
 		goto already_added;
 
 	if (idx >= nr_alloc) {
-		unsigned int needed = (idx + 1024) * 3 / 2;
-		objects = xrealloc(objects, needed * sizeof(*entry));
-		nr_alloc = needed;
+		nr_alloc = (idx + 1024) * 3 / 2;
+		objects = xrealloc(objects, nr_alloc * sizeof(*entry));
 	}
 	entry = objects + idx;
 	nr_objects = idx + 1;
@@ -718,7 +716,7 @@
 
  already_added:
 	if (progress_update) {
-		fprintf(stderr, "Counting objects...%d\r", nr_objects);
+		fprintf(stderr, "Counting objects...%u\r", nr_objects);
 		progress_update = 0;
 	}
 	if (exclude)
@@ -765,7 +763,7 @@
 	struct pbase_tree_cache *ent, *nent;
 	void *data;
 	unsigned long size;
-	char type[20];
+	enum object_type type;
 	int neigh;
 	int my_ix = pbase_tree_cache_ix(sha1);
 	int available_ix = -1;
@@ -792,10 +790,10 @@
 	/* Did not find one.  Either we got a bogus request or
 	 * we need to read and perhaps cache.
 	 */
-	data = read_sha1_file(sha1, type, &size);
+	data = read_sha1_file(sha1, &type, &size);
 	if (!data)
 		return NULL;
-	if (strcmp(type, tree_type)) {
+	if (type != OBJ_TREE) {
 		free(data);
 		return NULL;
 	}
@@ -854,19 +852,19 @@
 
 	while (tree_entry(tree,&entry)) {
 		unsigned long size;
-		char type[20];
+		enum object_type type;
 
-		if (entry.pathlen != cmplen ||
+		if (tree_entry_len(entry.path, entry.sha1) != cmplen ||
 		    memcmp(entry.path, name, cmplen) ||
 		    !has_sha1_file(entry.sha1) ||
-		    sha1_object_info(entry.sha1, type, &size))
+		    (type = sha1_object_info(entry.sha1, &size)) < 0)
 			continue;
 		if (name[cmplen] != '/') {
 			unsigned hash = name_hash(fullname);
 			add_object_entry(entry.sha1, hash, 1);
 			return;
 		}
-		if (!strcmp(type, tree_type)) {
+		if (type == OBJ_TREE) {
 			struct tree_desc sub;
 			struct pbase_tree_cache *tree;
 			const char *down = name+cmplen+1;
@@ -875,8 +873,7 @@
 			tree = pbase_tree_get(entry.sha1);
 			if (!tree)
 				return;
-			sub.buf = tree->tree_data;
-			sub.size = tree->tree_size;
+			init_tree_desc(&sub, tree->tree_data, tree->tree_size);
 
 			add_pbase_object(&sub, down, downlen, fullname);
 			pbase_tree_put(tree);
@@ -939,8 +936,7 @@
 		}
 		else {
 			struct tree_desc tree;
-			tree.buf = it->pcache.tree_data;
-			tree.size = it->pcache.tree_size;
+			init_tree_desc(&tree, it->pcache.tree_data, it->pcache.tree_size);
 			add_pbase_object(&tree, name, cmplen, name);
 		}
 	}
@@ -978,22 +974,20 @@
 
 static void check_object(struct object_entry *entry)
 {
-	char type[20];
-
 	if (entry->in_pack && !entry->preferred_base) {
 		struct packed_git *p = entry->in_pack;
 		struct pack_window *w_curs = NULL;
-		unsigned long left = p->pack_size - entry->in_pack_offset;
 		unsigned long size, used;
+		unsigned int avail;
 		unsigned char *buf;
 		struct object_entry *base_entry = NULL;
 
-		buf = use_pack(p, &w_curs, entry->in_pack_offset, NULL);
+		buf = use_pack(p, &w_curs, entry->in_pack_offset, &avail);
 
 		/* We want in_pack_type even if we do not reuse delta.
 		 * There is no point not reusing non-delta representations.
 		 */
-		used = unpack_object_header_gently(buf, left,
+		used = unpack_object_header_gently(buf, avail,
 						   &entry->in_pack_type, &size);
 
 		/* Check if it is delta, and the base is also an object
@@ -1001,8 +995,9 @@
 		 * delta.
 		 */
 		if (!no_reuse_delta) {
-			unsigned char c, *base_name;
-			unsigned long ofs;
+			unsigned char c;
+			const unsigned char *base_name;
+			off_t ofs;
 			unsigned long used_0;
 			/* there is at least 20 bytes left in the pack */
 			switch (entry->in_pack_type) {
@@ -1062,21 +1057,10 @@
 		/* Otherwise we would do the usual */
 	}
 
-	if (sha1_object_info(entry->sha1, type, &entry->size))
+	entry->type = sha1_object_info(entry->sha1, &entry->size);
+	if (entry->type < 0)
 		die("unable to get type of object %s",
 		    sha1_to_hex(entry->sha1));
-
-	if (!strcmp(type, commit_type)) {
-		entry->type = OBJ_COMMIT;
-	} else if (!strcmp(type, tree_type)) {
-		entry->type = OBJ_TREE;
-	} else if (!strcmp(type, blob_type)) {
-		entry->type = OBJ_BLOB;
-	} else if (!strcmp(type, tag_type)) {
-		entry->type = OBJ_TAG;
-	} else
-		die("unable to pack object %s of type %s",
-		    sha1_to_hex(entry->sha1), type);
 }
 
 static unsigned int check_delta_limit(struct object_entry *me, unsigned int n)
@@ -1094,7 +1078,7 @@
 
 static void get_object_details(void)
 {
-	int i;
+	uint32_t i;
 	struct object_entry *entry;
 
 	prepare_pack_ix();
@@ -1133,7 +1117,7 @@
 static struct object_entry **create_sorted_list(entry_sort_t sort)
 {
 	struct object_entry **list = xmalloc(nr_objects * sizeof(struct object_entry *));
-	int i;
+	uint32_t i;
 
 	for (i = 0; i < nr_objects; i++)
 		list[i] = objects + i;
@@ -1150,7 +1134,7 @@
 static struct object_entry **create_final_object_list(void)
 {
 	struct object_entry **list;
-	int i, j;
+	uint32_t i, j;
 
 	for (i = nr_result = 0; i < nr_objects; i++)
 		if (!objects[i].preferred_base)
@@ -1206,7 +1190,7 @@
 	struct object_entry *trg_entry = trg->entry;
 	struct object_entry *src_entry = src->entry;
 	unsigned long trg_size, src_size, delta_size, sizediff, max_size, sz;
-	char type[10];
+	enum object_type type;
 	void *delta_buf;
 
 	/* Don't bother doing diffs between different types */
@@ -1257,13 +1241,13 @@
 
 	/* Load data if not already done */
 	if (!trg->data) {
-		trg->data = read_sha1_file(trg_entry->sha1, type, &sz);
+		trg->data = read_sha1_file(trg_entry->sha1, &type, &sz);
 		if (sz != trg_size)
 			die("object %s inconsistent object length (%lu vs %lu)",
 			    sha1_to_hex(trg_entry->sha1), sz, trg_size);
 	}
 	if (!src->data) {
-		src->data = read_sha1_file(src_entry->sha1, type, &sz);
+		src->data = read_sha1_file(src_entry->sha1, &type, &sz);
 		if (sz != src_size)
 			die("object %s inconsistent object length (%lu vs %lu)",
 			    sha1_to_hex(src_entry->sha1), sz, src_size);
@@ -1292,20 +1276,20 @@
 
 static void find_deltas(struct object_entry **list, int window, int depth)
 {
-	int i, idx;
+	uint32_t i = nr_objects, idx = 0, processed = 0;
 	unsigned int array_size = window * sizeof(struct unpacked);
-	struct unpacked *array = xmalloc(array_size);
-	unsigned processed = 0;
+	struct unpacked *array;
 	unsigned last_percent = 999;
 
+	if (!nr_objects)
+		return;
+	array = xmalloc(array_size);
 	memset(array, 0, array_size);
-	i = nr_objects;
-	idx = 0;
 	if (progress)
-		fprintf(stderr, "Deltifying %d objects.\n", nr_result);
+		fprintf(stderr, "Deltifying %u objects.\n", nr_result);
 
-	while (--i >= 0) {
-		struct object_entry *entry = list[i];
+	do {
+		struct object_entry *entry = list[--i];
 		struct unpacked *n = array + idx;
 		int j;
 
@@ -1338,7 +1322,7 @@
 
 		j = window;
 		while (--j > 0) {
-			unsigned int other_idx = idx + j;
+			uint32_t other_idx = idx + j;
 			struct unpacked *m;
 			if (other_idx >= window)
 				other_idx -= window;
@@ -1358,7 +1342,7 @@
 		idx++;
 		if (idx >= window)
 			idx = 0;
-	}
+	} while (i > 0);
 
 	if (progress)
 		fputc('\n', stderr);
@@ -1399,7 +1383,7 @@
 	}
 
 	if (progress)
-		fprintf(stderr, "Reusing %d objects pack %s\n", nr_objects,
+		fprintf(stderr, "Reusing %u objects pack %s\n", nr_objects,
 			sha1_to_hex(sha1));
 
 	if (pack_to_stdout) {
@@ -1550,7 +1534,7 @@
 	struct object_entry **list;
 	int use_internal_rev_list = 0;
 	int thin = 0;
-	int i;
+	uint32_t i;
 	const char **rp_av;
 	int rp_ac_alloc = 64;
 	int rp_ac;
@@ -1582,14 +1566,14 @@
 			incremental = 1;
 			continue;
 		}
-		if (!strncmp("--window=", arg, 9)) {
+		if (!prefixcmp(arg, "--window=")) {
 			char *end;
 			window = strtoul(arg+9, &end, 0);
 			if (!arg[9] || *end)
 				usage(pack_usage);
 			continue;
 		}
-		if (!strncmp("--depth=", arg, 8)) {
+		if (!prefixcmp(arg, "--depth=")) {
 			char *end;
 			depth = strtoul(arg+8, &end, 0);
 			if (!arg[8] || *end)
@@ -1625,7 +1609,7 @@
 			continue;
 		}
 		if (!strcmp("--unpacked", arg) ||
-		    !strncmp("--unpacked=", arg, 11) ||
+		    !prefixcmp(arg, "--unpacked=") ||
 		    !strcmp("--reflog", arg) ||
 		    !strcmp("--all", arg)) {
 			use_internal_rev_list = 1;
@@ -1683,7 +1667,7 @@
 	}
 
 	if (progress)
-		fprintf(stderr, "Done counting %d objects.\n", nr_objects);
+		fprintf(stderr, "Done counting %u objects.\n", nr_objects);
 	sorted_by_sha = create_final_object_list();
 	if (non_empty && !nr_result)
 		return 0;
@@ -1696,7 +1680,7 @@
 	}
 	SHA1_Final(object_list_sha1, &ctx);
 	if (progress && (nr_objects != nr_result))
-		fprintf(stderr, "Result has %d objects.\n", nr_result);
+		fprintf(stderr, "Result has %u objects.\n", nr_result);
 
 	if (reuse_cached_pack(object_list_sha1))
 		;
@@ -1717,7 +1701,7 @@
 		}
 	}
 	if (progress)
-		fprintf(stderr, "Total %d (delta %d), reused %d (delta %d)\n",
+		fprintf(stderr, "Total %u (delta %u), reused %u (delta %u)\n",
 			written, written_delta, reused, reused_delta);
 	return 0;
 }
diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c
index 3de9b3e..d080e30 100644
--- a/builtin-pack-refs.c
+++ b/builtin-pack-refs.c
@@ -36,7 +36,7 @@
 	/* Do not pack the symbolic refs */
 	if ((flags & REF_ISSYMREF))
 		return 0;
-	is_tag_ref = !strncmp(path, "refs/tags/", 10);
+	is_tag_ref = !prefixcmp(path, "refs/tags/");
 
 	/* ALWAYS pack refs that were already packed or are tags */
 	if (!cb->all && !is_tag_ref && !(flags & REF_ISPACKED))
diff --git a/builtin-prune.c b/builtin-prune.c
index 6f0ba0d..44df59e 100644
--- a/builtin-prune.c
+++ b/builtin-prune.c
@@ -10,19 +10,12 @@
 
 static int prune_object(char *path, const char *filename, const unsigned char *sha1)
 {
-	char buf[20];
-	const char *type;
-
 	if (show_only) {
-		if (sha1_object_info(sha1, buf, NULL))
-			type = "unknown";
-		else
-			type = buf;
-		printf("%s %s\n", sha1_to_hex(sha1), type);
-		return 0;
-	}
-	unlink(mkpath("%s/%s", path, filename));
-	rmdir(path);
+		enum object_type type = sha1_object_info(sha1, NULL);
+		printf("%s %s\n", sha1_to_hex(sha1),
+		       (type > 0) ? typename(type) : "unknown");
+	} else
+		unlink(mkpath("%s/%s", path, filename));
 	return 0;
 }
 
@@ -65,6 +58,8 @@
 		}
 		fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name);
 	}
+	if (!show_only)
+		rmdir(path);
 	closedir(dir);
 	return 0;
 }
diff --git a/builtin-push.c b/builtin-push.c
index c45649e..70b1168 100644
--- a/builtin-push.c
+++ b/builtin-push.c
@@ -32,7 +32,7 @@
 	/* Ignore the "refs/" at the beginning of the refname */
 	ref += 5;
 
-	if (!strncmp(ref, "tags/", 5))
+	if (!prefixcmp(ref, "tags/"))
 		add_refspec(xstrdup(ref));
 	return 0;
 }
@@ -149,10 +149,10 @@
 		int is_refspec;
 		char *s, *p;
 
-		if (!strncmp("URL:", buffer, 4)) {
+		if (!prefixcmp(buffer, "URL:")) {
 			is_refspec = 0;
 			s = buffer + 4;
-		} else if (!strncmp("Push:", buffer, 5)) {
+		} else if (!prefixcmp(buffer, "Push:")) {
 			is_refspec = 1;
 			s = buffer + 5;
 		} else
@@ -195,7 +195,7 @@
 
 static int get_remote_config(const char* key, const char* value)
 {
-	if (!strncmp(key, "remote.", 7) &&
+	if (!prefixcmp(key, "remote.") &&
 	    !strncmp(key + 7, config_repo, config_repo_len)) {
 		if (!strcmp(key + 7 + config_repo_len, ".url")) {
 			if (config_current_uri < MAX_URI)
@@ -323,10 +323,10 @@
 		int dest_refspec_nr = refspec_nr;
 		const char **dest_refspec = refspec;
 		const char *dest = uri[i];
-		const char *sender = "git-send-pack";
-		if (!strncmp(dest, "http://", 7) ||
-		    !strncmp(dest, "https://", 8))
-			sender = "git-http-push";
+		const char *sender = "send-pack";
+		if (!prefixcmp(dest, "http://") ||
+		    !prefixcmp(dest, "https://"))
+			sender = "http-push";
 		else if (thin)
 			argv[dest_argc++] = "--thin";
 		argv[0] = sender;
@@ -336,7 +336,7 @@
 		argv[dest_argc] = NULL;
 		if (verbose)
 			fprintf(stderr, "Pushing to %s\n", dest);
-		err = run_command_v(argv);
+		err = run_command_v_opt(argv, RUN_GIT_CMD);
 		if (!err)
 			continue;
 		switch (err) {
@@ -373,7 +373,7 @@
 			verbose=1;
 			continue;
 		}
-		if (!strncmp(arg, "--repo=", 7)) {
+		if (!prefixcmp(arg, "--repo=")) {
 			repo = arg+7;
 			continue;
 		}
@@ -397,11 +397,11 @@
 			thin = 0;
 			continue;
 		}
-		if (!strncmp(arg, "--receive-pack=", 15)) {
+		if (!prefixcmp(arg, "--receive-pack=")) {
 			receivepack = arg;
 			continue;
 		}
-		if (!strncmp(arg, "--exec=", 7)) {
+		if (!prefixcmp(arg, "--exec=")) {
 			receivepack = arg;
 			continue;
 		}
diff --git a/builtin-read-tree.c b/builtin-read-tree.c
index 8ba436d..793eae0 100644
--- a/builtin-read-tree.c
+++ b/builtin-read-tree.c
@@ -55,8 +55,7 @@
 	int cnt;
 
 	hashcpy(it->sha1, tree->object.sha1);
-	desc.buf = tree->buffer;
-	desc.size = tree->size;
+	init_tree_desc(&desc, tree->buffer, tree->size);
 	cnt = 0;
 	while (tree_entry(&desc, &entry)) {
 		if (!S_ISDIR(entry.mode))
@@ -133,7 +132,7 @@
 		 *  entries and put the entries from the tree under the
 		 * given subdirectory.
 		 */
-		if (!strncmp(arg, "--prefix=", 9)) {
+		if (!prefixcmp(arg, "--prefix=")) {
 			if (stage || opts.merge || opts.prefix)
 				usage(read_tree_usage);
 			opts.prefix = arg + 9;
@@ -179,13 +178,13 @@
 			continue;
 		}
 
-		if (!strncmp(arg, "--exclude-per-directory=", 24)) {
+		if (!prefixcmp(arg, "--exclude-per-directory=")) {
 			struct dir_struct *dir;
 
 			if (opts.dir)
 				die("more than one --exclude-per-directory are given.");
 
-			dir = calloc(1, sizeof(*opts.dir));
+			dir = xcalloc(1, sizeof(*opts.dir));
 			dir->show_ignored = 1;
 			dir->exclude_per_dir = arg + 24;
 			opts.dir = dir;
diff --git a/builtin-reflog.c b/builtin-reflog.c
index 65b845b..4c39f1d 100644
--- a/builtin-reflog.c
+++ b/builtin-reflog.c
@@ -52,18 +52,18 @@
 	if (tree->object.flags & INCOMPLETE)
 		return 0;
 
-	desc.buf = tree->buffer;
-	desc.size = tree->size;
-	if (!desc.buf) {
-		char type[20];
-		void *data = read_sha1_file(sha1, type, &desc.size);
+	if (!tree->buffer) {
+		enum object_type type;
+		unsigned long size;
+		void *data = read_sha1_file(sha1, &type, &size);
 		if (!data) {
 			tree->object.flags |= INCOMPLETE;
 			return 0;
 		}
-		desc.buf = data;
 		tree->buffer = data;
+		tree->size = size;
 	}
+	init_tree_desc(&desc, tree->buffer, tree->size);
 	complete = 1;
 	while (tree_entry(&desc, &entry)) {
 		if (!has_sha1_file(entry.sha1) ||
@@ -215,8 +215,8 @@
 			old = lookup_commit_reference_gently(osha1, 1);
 		if (!new && !is_null_sha1(nsha1))
 			new = lookup_commit_reference_gently(nsha1, 1);
-		if ((old && !in_merge_bases(old, cb->ref_commit)) ||
-		    (new && !in_merge_bases(new, cb->ref_commit)))
+		if ((old && !in_merge_bases(old, &cb->ref_commit, 1)) ||
+		    (new && !in_merge_bases(new, &cb->ref_commit, 1)))
 			goto prune;
 	}
 
@@ -321,9 +321,9 @@
 		const char *arg = argv[i];
 		if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
 			cb.dry_run = 1;
-		else if (!strncmp(arg, "--expire=", 9))
+		else if (!prefixcmp(arg, "--expire="))
 			cb.expire_total = approxidate(arg + 9);
-		else if (!strncmp(arg, "--expire-unreachable=", 21))
+		else if (!prefixcmp(arg, "--expire-unreachable="))
 			cb.expire_unreachable = approxidate(arg + 21);
 		else if (!strcmp(arg, "--stale-fix"))
 			cb.stalefix = 1;
diff --git a/builtin-rerere.c b/builtin-rerere.c
index 004eda2..b463c07 100644
--- a/builtin-rerere.c
+++ b/builtin-rerere.c
@@ -112,11 +112,11 @@
 		SHA1_Init(&ctx);
 
 	while (fgets(buf, sizeof(buf), f)) {
-		if (!strncmp("<<<<<<< ", buf, 8))
+		if (!prefixcmp(buf, "<<<<<<< "))
 			hunk = 1;
-		else if (!strncmp("=======", buf, 7))
+		else if (!prefixcmp(buf, "======="))
 			hunk = 2;
-		else if (!strncmp(">>>>>>> ", buf, 8)) {
+		else if (!prefixcmp(buf, ">>>>>>> ")) {
 			hunk_no++;
 			hunk = 0;
 			if (memcmp(one->ptr, two->ptr, one->nr < two->nr ?
diff --git a/builtin-rev-list.c b/builtin-rev-list.c
index c2db5a5..51858e3 100644
--- a/builtin-rev-list.c
+++ b/builtin-rev-list.c
@@ -180,7 +180,7 @@
 			nr++;
 		p = p->next;
 	}
-	closest = 0;
+	closest = -1;
 	best = list;
 
 	for (p = list; p; p = p->next) {
diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c
index d53deaa..37addb2 100644
--- a/builtin-rev-parse.c
+++ b/builtin-rev-parse.c
@@ -233,7 +233,7 @@
 			}
 			continue;
 		}
-		if (!strncmp(arg,"-n",2)) {
+		if (!prefixcmp(arg, "-n")) {
 			if ((filter & DO_FLAGS) && (filter & DO_REVS))
 				show(arg);
 			continue;
@@ -274,7 +274,7 @@
 				continue;
 			}
 			if (!strcmp(arg, "--short") ||
-			    !strncmp(arg, "--short=", 8)) {
+			    !prefixcmp(arg, "--short=")) {
 				filter &= ~(DO_FLAGS|DO_NOREV);
 				verify = 1;
 				abbrev = DEFAULT_ABBREV;
@@ -352,19 +352,19 @@
 						: "false");
 				continue;
 			}
-			if (!strncmp(arg, "--since=", 8)) {
+			if (!prefixcmp(arg, "--since=")) {
 				show_datestring("--max-age=", arg+8);
 				continue;
 			}
-			if (!strncmp(arg, "--after=", 8)) {
+			if (!prefixcmp(arg, "--after=")) {
 				show_datestring("--max-age=", arg+8);
 				continue;
 			}
-			if (!strncmp(arg, "--before=", 9)) {
+			if (!prefixcmp(arg, "--before=")) {
 				show_datestring("--min-age=", arg+9);
 				continue;
 			}
-			if (!strncmp(arg, "--until=", 8)) {
+			if (!prefixcmp(arg, "--until=")) {
 				show_datestring("--min-age=", arg+8);
 				continue;
 			}
diff --git a/builtin-revert.c b/builtin-revert.c
new file mode 100644
index 0000000..4ba0ee6
--- /dev/null
+++ b/builtin-revert.c
@@ -0,0 +1,404 @@
+#include "cache.h"
+#include "builtin.h"
+#include "object.h"
+#include "commit.h"
+#include "tag.h"
+#include "wt-status.h"
+#include "run-command.h"
+#include "exec_cmd.h"
+#include "utf8.h"
+
+/*
+ * This implements the builtins revert and cherry-pick.
+ *
+ * Copyright (c) 2007 Johannes E. Schindelin
+ *
+ * Based on git-revert.sh, which is
+ *
+ * Copyright (c) 2005 Linus Torvalds
+ * Copyright (c) 2005 Junio C Hamano
+ */
+
+static const char *revert_usage = "git-revert [--edit | --no-edit] [-n] <commit-ish>";
+
+static const char *cherry_pick_usage = "git-cherry-pick [--edit] [-n] [-r] [-x] <commit-ish>";
+
+static int edit;
+static int replay;
+enum { REVERT, CHERRY_PICK } action;
+static int no_commit;
+static struct commit *commit;
+static int needed_deref;
+
+static const char *me;
+
+#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
+
+static void parse_options(int argc, const char **argv)
+{
+	const char *usage_str = action == REVERT ?
+		revert_usage : cherry_pick_usage;
+	unsigned char sha1[20];
+	const char *arg;
+	int i;
+
+	if (argc < 2)
+		usage(usage_str);
+
+	for (i = 1; i < argc - 1; i++) {
+		arg = argv[i];
+		if (!strcmp(arg, "-n") || !strcmp(arg, "--no-commit"))
+			no_commit = 1;
+		else if (!strcmp(arg, "-e") || !strcmp(arg, "--edit"))
+			edit = 1;
+		else if (!strcmp(arg, "--no-edit"))
+			edit = 0;
+		else if (!strcmp(arg, "-x") || !strcmp(arg, "--i-really-want-"
+				"to-expose-my-private-commit-object-name"))
+			replay = 0;
+		else if (strcmp(arg, "-r"))
+			usage(usage_str);
+	}
+
+	arg = argv[argc - 1];
+	if (get_sha1(arg, sha1))
+		die ("Cannot find '%s'", arg);
+	commit = (struct commit *)parse_object(sha1);
+	if (!commit)
+		die ("Could not find %s", sha1_to_hex(sha1));
+	if (commit->object.type == OBJ_TAG) {
+		commit = (struct commit *)
+			deref_tag((struct object *)commit, arg, strlen(arg));
+		needed_deref = 1;
+	}
+	if (commit->object.type != OBJ_COMMIT)
+		die ("'%s' does not point to a commit", arg);
+}
+
+static char *get_oneline(const char *message)
+{
+	char *result;
+	const char *p = message, *abbrev, *eol;
+	int abbrev_len, oneline_len;
+
+	if (!p)
+		die ("Could not read commit message of %s",
+				sha1_to_hex(commit->object.sha1));
+	while (*p && (*p != '\n' || p[1] != '\n'))
+		p++;
+
+	if (*p) {
+		p += 2;
+		for (eol = p + 1; *eol && *eol != '\n'; eol++)
+			; /* do nothing */
+	} else
+		eol = p;
+	abbrev = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);
+	abbrev_len = strlen(abbrev);
+	oneline_len = eol - p;
+	result = xmalloc(abbrev_len + 5 + oneline_len);
+	memcpy(result, abbrev, abbrev_len);
+	memcpy(result + abbrev_len, "... ", 4);
+	memcpy(result + abbrev_len + 4, p, oneline_len);
+	result[abbrev_len + 4 + oneline_len] = '\0';
+	return result;
+}
+
+char *get_encoding(const char *message)
+{
+	const char *p = message, *eol;
+
+	if (!p)
+		die ("Could not read commit message of %s",
+				sha1_to_hex(commit->object.sha1));
+	while (*p && *p != '\n') {
+		for (eol = p + 1; *eol && *eol != '\n'; eol++)
+			; /* do nothing */
+		if (!prefixcmp(p, "encoding ")) {
+			char *result = xmalloc(eol - 8 - p);
+			strlcpy(result, p + 9, eol - 8 - p);
+			return result;
+		}
+		p = eol;
+		if (*p == '\n')
+			p++;
+	}
+	return NULL;
+}
+
+struct lock_file msg_file;
+static int msg_fd;
+
+static void add_to_msg(const char *string)
+{
+	int len = strlen(string);
+	if (write_in_full(msg_fd, string, len) < 0)
+		die ("Could not write to .msg");
+}
+
+static void add_message_to_msg(const char *message)
+{
+	const char *p = message;
+	while (*p && (*p != '\n' || p[1] != '\n'))
+		p++;
+
+	if (!*p)
+		add_to_msg(sha1_to_hex(commit->object.sha1));
+
+	p += 2;
+	add_to_msg(p);
+	return;
+}
+
+static void set_author_ident_env(const char *message)
+{
+	const char *p = message;
+	if (!p)
+		die ("Could not read commit message of %s",
+				sha1_to_hex(commit->object.sha1));
+	while (*p && *p != '\n') {
+		const char *eol;
+
+		for (eol = p; *eol && *eol != '\n'; eol++)
+			; /* do nothing */
+		if (!prefixcmp(p, "author ")) {
+			char *line, *pend, *email, *timestamp;
+
+			p += 7;
+			line = xmalloc(eol + 1 - p);
+			memcpy(line, p, eol - p);
+			line[eol - p] = '\0';
+			email = strchr(line, '<');
+			if (!email)
+				die ("Could not extract author email from %s",
+					sha1_to_hex(commit->object.sha1));
+			if (email == line)
+				pend = line;
+			else
+				for (pend = email; pend != line + 1 &&
+						isspace(pend[-1]); pend--);
+					; /* do nothing */
+			*pend = '\0';
+			email++;
+			timestamp = strchr(email, '>');
+			if (!timestamp)
+				die ("Could not extract author email from %s",
+					sha1_to_hex(commit->object.sha1));
+			*timestamp = '\0';
+			for (timestamp++; *timestamp && isspace(*timestamp);
+					timestamp++)
+				; /* do nothing */
+			setenv("GIT_AUTHOR_NAME", line, 1);
+			setenv("GIT_AUTHOR_EMAIL", email, 1);
+			setenv("GIT_AUTHOR_DATE", timestamp, 1);
+			free(line);
+			return;
+		}
+		p = eol;
+		if (*p == '\n')
+			p++;
+	}
+	die ("No author information found in %s",
+			sha1_to_hex(commit->object.sha1));
+}
+
+static int merge_recursive(const char *base_sha1,
+		const char *head_sha1, const char *head_name,
+		const char *next_sha1, const char *next_name)
+{
+	char buffer[256];
+	const char *argv[6];
+
+	sprintf(buffer, "GITHEAD_%s", head_sha1);
+	setenv(buffer, head_name, 1);
+	sprintf(buffer, "GITHEAD_%s", next_sha1);
+	setenv(buffer, next_name, 1);
+
+	/*
+	 * This three way merge is an interesting one.  We are at
+	 * $head, and would want to apply the change between $commit
+	 * and $prev on top of us (when reverting), or the change between
+	 * $prev and $commit on top of us (when cherry-picking or replaying).
+	 */
+	argv[0] = "merge-recursive";
+	argv[1] = base_sha1;
+	argv[2] = "--";
+	argv[3] = head_sha1;
+	argv[4] = next_sha1;
+	argv[5] = NULL;
+
+	return run_command_v_opt(argv, RUN_COMMAND_NO_STDIN | RUN_GIT_CMD);
+}
+
+static int revert_or_cherry_pick(int argc, const char **argv)
+{
+	unsigned char head[20];
+	struct commit *base, *next;
+	int i;
+	char *oneline, *reencoded_message = NULL;
+	const char *message, *encoding;
+
+	git_config(git_default_config);
+	me = action == REVERT ? "revert" : "cherry-pick";
+	setenv(GIT_REFLOG_ACTION, me, 0);
+	parse_options(argc, argv);
+
+	/* this is copied from the shell script, but it's never triggered... */
+	if (action == REVERT && replay)
+		die("revert is incompatible with replay");
+
+	if (no_commit) {
+		/*
+		 * We do not intend to commit immediately.  We just want to
+		 * merge the differences in.
+		 */
+		if (write_tree(head, 0, NULL))
+			die ("Your index file is unmerged.");
+	} else {
+		struct wt_status s;
+
+		if (get_sha1("HEAD", head))
+			die ("You do not have a valid HEAD");
+		wt_status_prepare(&s);
+		if (s.commitable || s.workdir_dirty)
+			die ("Dirty index: cannot %s", me);
+		discard_cache();
+	}
+
+	if (!commit->parents)
+		die ("Cannot %s a root commit", me);
+	if (commit->parents->next)
+		die ("Cannot %s a multi-parent commit.", me);
+	if (!(message = commit->buffer))
+		die ("Cannot get commit message for %s",
+				sha1_to_hex(commit->object.sha1));
+
+	/*
+	 * "commit" is an existing commit.  We would want to apply
+	 * the difference it introduces since its first parent "prev"
+	 * on top of the current HEAD if we are cherry-pick.  Or the
+	 * reverse of it if we are revert.
+	 */
+
+	msg_fd = hold_lock_file_for_update(&msg_file, ".msg", 1);
+
+	encoding = get_encoding(message);
+	if (!encoding)
+		encoding = "utf-8";
+	if (!git_commit_encoding)
+		git_commit_encoding = "utf-8";
+	if ((reencoded_message = reencode_string(message,
+					git_commit_encoding, encoding)))
+		message = reencoded_message;
+
+	oneline = get_oneline(message);
+
+	if (action == REVERT) {
+		char *oneline_body = strchr(oneline, ' ');
+
+		base = commit;
+		next = commit->parents->item;
+		add_to_msg("Revert \"");
+		add_to_msg(oneline_body + 1);
+		add_to_msg("\"\n\nThis reverts commit ");
+		add_to_msg(sha1_to_hex(commit->object.sha1));
+		add_to_msg(".\n");
+	} else {
+		base = commit->parents->item;
+		next = commit;
+		set_author_ident_env(message);
+		add_message_to_msg(message);
+		if (!replay) {
+			add_to_msg("(cherry picked from commit ");
+			add_to_msg(sha1_to_hex(commit->object.sha1));
+			add_to_msg(")\n");
+		}
+	}
+	if (needed_deref) {
+		add_to_msg("(original 'git ");
+		add_to_msg(me);
+		add_to_msg("' arguments: ");
+		for (i = 0; i < argc; i++) {
+			if (i)
+				add_to_msg(" ");
+			add_to_msg(argv[i]);
+		}
+		add_to_msg(")\n");
+	}
+
+	if (merge_recursive(sha1_to_hex(base->object.sha1),
+				sha1_to_hex(head), "HEAD",
+				sha1_to_hex(next->object.sha1), oneline) ||
+			write_tree(head, 0, NULL)) {
+		const char *target = git_path("MERGE_MSG");
+		add_to_msg("\nConflicts:\n\n");
+		read_cache();
+		for (i = 0; i < active_nr;) {
+			struct cache_entry *ce = active_cache[i++];
+			if (ce_stage(ce)) {
+				add_to_msg("\t");
+				add_to_msg(ce->name);
+				add_to_msg("\n");
+				while (i < active_nr && !strcmp(ce->name,
+						active_cache[i]->name))
+					i++;
+			}
+		}
+		if (close(msg_fd) || commit_lock_file(&msg_file) < 0)
+			die ("Error wrapping up .msg");
+		unlink(target);
+		if (rename(".msg", target))
+			die ("Could not move .msg to %s", target);
+		fprintf(stderr, "Automatic %s failed.  "
+			"After resolving the conflicts,\n"
+			"mark the corrected paths with 'git-add <paths>'\n"
+			"and commit the result.\n", me);
+		if (action == CHERRY_PICK) {
+			fprintf(stderr, "When commiting, use the option "
+				"'-c %s' to retain authorship and message.\n",
+				find_unique_abbrev(commit->object.sha1,
+					DEFAULT_ABBREV));
+		}
+		exit(1);
+	}
+	if (close(msg_fd) || commit_lock_file(&msg_file) < 0)
+		die ("Error wrapping up .msg");
+	fprintf(stderr, "Finished one %s.\n", me);
+
+	/*
+	 *
+	 * If we are cherry-pick, and if the merge did not result in
+	 * hand-editing, we will hit this commit and inherit the original
+	 * author date and name.
+	 * If we are revert, or if our cherry-pick results in a hand merge,
+	 * we had better say that the current user is responsible for that.
+	 */
+
+	if (!no_commit) {
+		if (edit)
+			return execl_git_cmd("commit", "-n", "-F", ".msg",
+				"-e", NULL);
+		else
+			return execl_git_cmd("commit", "-n", "-F", ".msg",
+				NULL);
+	}
+	if (reencoded_message)
+		free(reencoded_message);
+
+	return 0;
+}
+
+int cmd_revert(int argc, const char **argv, const char *prefix)
+{
+	if (isatty(0))
+		edit = 1;
+	action = REVERT;
+	return revert_or_cherry_pick(argc, argv);
+}
+
+int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
+{
+	replay = 1;
+	action = CHERRY_PICK;
+	return revert_or_cherry_pick(argc, argv);
+}
diff --git a/builtin-rm.c b/builtin-rm.c
index 00dbe39..bf42003 100644
--- a/builtin-rm.c
+++ b/builtin-rm.c
@@ -89,20 +89,10 @@
 		if (ce_match_stat(ce, &st, 0))
 			errs = error("'%s' has local modifications "
 				     "(hint: try -f)", ce->name);
-		if (no_head)
-			continue;
-		/*
-		 * It is Ok to remove a newly added path, as long as
-		 * it is cache-clean.
-		 */
-		if (get_tree_entry(head, name, sha1, &mode))
-			continue;
-		/*
-		 * Otherwise make sure the version from the HEAD
-		 * matches the index.
-		 */
-		if (ce->ce_mode != create_ce_mode(mode) ||
-		    hashcmp(ce->sha1, sha1))
+		if (no_head
+		     || get_tree_entry(head, name, sha1, &mode)
+		     || ce->ce_mode != create_ce_mode(mode)
+		     || hashcmp(ce->sha1, sha1))
 			errs = error("'%s' has changes staged in the index "
 				     "(hint: try -f)", name);
 	}
diff --git a/builtin-shortlog.c b/builtin-shortlog.c
index edb4042..29343ae 100644
--- a/builtin-shortlog.c
+++ b/builtin-shortlog.c
@@ -124,7 +124,7 @@
 	else
 		free(buffer);
 
-	if (!strncmp(oneline, "[PATCH", 6)) {
+	if (!prefixcmp(oneline, "[PATCH")) {
 		char *eob = strchr(oneline, ']');
 
 		if (eob) {
@@ -179,7 +179,7 @@
 	while (fgets(buffer, sizeof(buffer), stdin) != NULL) {
 		char *bob;
 		if ((buffer[0] == 'A' || buffer[0] == 'a') &&
-				!strncmp(buffer + 1, "uthor: ", 7) &&
+				!prefixcmp(buffer + 1, "uthor: ") &&
 				(bob = strchr(buffer + 7, '<')) != NULL) {
 			char buffer2[1024], offset = 0;
 
@@ -217,20 +217,20 @@
 
 	prepare_revision_walk(rev);
 	while ((commit = get_revision(rev)) != NULL) {
-		char *author = NULL, *oneline, *buffer;
+		const char *author = NULL, *oneline, *buffer;
 		int authorlen = authorlen, onelinelen;
 
 		/* get author and oneline */
 		for (buffer = commit->buffer; buffer && *buffer != '\0' &&
 				*buffer != '\n'; ) {
-			char *eol = strchr(buffer, '\n');
+			const char *eol = strchr(buffer, '\n');
 
 			if (eol == NULL)
 				eol = buffer + strlen(buffer);
 			else
 				eol++;
 
-			if (!strncmp(buffer, "author ", 7)) {
+			if (!prefixcmp(buffer, "author ")) {
 				char *bracket = strchr(buffer, '<');
 
 				if (bracket == NULL || bracket > eol)
@@ -304,8 +304,11 @@
 	if (!access(".mailmap", R_OK))
 		read_mailmap(".mailmap");
 
-	if (rev.pending.nr == 0)
+	if (rev.pending.nr == 0) {
+		if (isatty(0))
+			fprintf(stderr, "(reading log to summarize from standard input)\n");
 		read_from_stdin(&list);
+	}
 	else
 		get_from_rev(&rev, &list);
 
diff --git a/builtin-show-branch.c b/builtin-show-branch.c
index 0d94e40..c892f1f 100644
--- a/builtin-show-branch.c
+++ b/builtin-show-branch.c
@@ -266,7 +266,7 @@
 				    pretty, sizeof(pretty), 0, NULL, NULL, 0);
 	else
 		strcpy(pretty, "(unavailable)");
-	if (!strncmp(pretty, "[PATCH] ", 8))
+	if (!prefixcmp(pretty, "[PATCH] "))
 		cp = pretty + 8;
 	else
 		cp = pretty;
@@ -378,7 +378,7 @@
 {
 	unsigned char tmp[20];
 	int ofs = 11;
-	if (strncmp(refname, "refs/heads/", ofs))
+	if (prefixcmp(refname, "refs/heads/"))
 		return 0;
 	/* If both heads/foo and tags/foo exists, get_sha1 would
 	 * get confused.
@@ -392,7 +392,7 @@
 {
 	unsigned char tmp[20];
 	int ofs = 13;
-	if (strncmp(refname, "refs/remotes/", ofs))
+	if (prefixcmp(refname, "refs/remotes/"))
 		return 0;
 	/* If both heads/foo and tags/foo exists, get_sha1 would
 	 * get confused.
@@ -404,7 +404,7 @@
 
 static int append_tag_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
 {
-	if (strncmp(refname, "refs/tags/", 10))
+	if (prefixcmp(refname, "refs/tags/"))
 		return 0;
 	return append_ref(refname + 5, sha1, 0);
 }
@@ -435,9 +435,9 @@
 		return 0;
 	if (fnmatch(match_ref_pattern, tail, 0))
 		return 0;
-	if (!strncmp("refs/heads/", refname, 11))
+	if (!prefixcmp(refname, "refs/heads/"))
 		return append_head_ref(refname, sha1, flag, cb_data);
-	if (!strncmp("refs/tags/", refname, 10))
+	if (!prefixcmp(refname, "refs/tags/"))
 		return append_tag_ref(refname, sha1, flag, cb_data);
 	return append_ref(refname, sha1, 0);
 }
@@ -462,11 +462,11 @@
 	if ((!head[0]) ||
 	    (head_sha1 && sha1 && hashcmp(head_sha1, sha1)))
 		return 0;
-	if (!strncmp(head, "refs/heads/", 11))
+	if (!prefixcmp(head, "refs/heads/"))
 		head += 11;
-	if (!strncmp(name, "refs/heads/", 11))
+	if (!prefixcmp(name, "refs/heads/"))
 		name += 11;
-	else if (!strncmp(name, "heads/", 6))
+	else if (!prefixcmp(name, "heads/"))
 		name += 6;
 	return !strcmp(head, name);
 }
@@ -635,7 +635,7 @@
 			with_current_branch = 1;
 		else if (!strcmp(arg, "--sha1-name"))
 			sha1_name = 1;
-		else if (!strncmp(arg, "--more=", 7))
+		else if (!prefixcmp(arg, "--more="))
 			extra = atoi(arg + 7);
 		else if (!strcmp(arg, "--merge-base"))
 			merge_base = 1;
@@ -652,9 +652,9 @@
 		else if (!strcmp(arg, "--reflog") || !strcmp(arg, "-g")) {
 			reflog = DEFAULT_REFLOG;
 		}
-		else if (!strncmp(arg, "--reflog=", 9))
+		else if (!prefixcmp(arg, "--reflog="))
 			parse_reflog_param(arg + 9, &reflog, &reflog_base);
-		else if (!strncmp(arg, "-g=", 3))
+		else if (!prefixcmp(arg, "-g="))
 			parse_reflog_param(arg + 3, &reflog, &reflog_base);
 		else
 			usage(show_branch_usage);
@@ -721,7 +721,8 @@
 		}
 
 		for (i = 0; i < reflog; i++) {
-			char *logmsg, *msg, *m;
+			char *logmsg, *m;
+			const char *msg;
 			unsigned long timestamp;
 			int tz;
 
diff --git a/builtin-show-ref.c b/builtin-show-ref.c
index 75211e6..9463ff0 100644
--- a/builtin-show-ref.c
+++ b/builtin-show-ref.c
@@ -28,8 +28,8 @@
 	if (tags_only || heads_only) {
 		int match;
 
-		match = heads_only && !strncmp(refname, "refs/heads/", 11);
-		match |= tags_only && !strncmp(refname, "refs/tags/", 10);
+		match = heads_only && !prefixcmp(refname, "refs/heads/");
+		match |= tags_only && !prefixcmp(refname, "refs/tags/");
 		if (!match)
 			return 0;
 	}
@@ -178,8 +178,8 @@
 			hash_only = 1;
 			continue;
 		}
-		if (!strncmp(arg, "--hash=", 7) ||
-		    (!strncmp(arg, "--abbrev", 8) &&
+		if (!prefixcmp(arg, "--hash=") ||
+		    (!prefixcmp(arg, "--abbrev") &&
 		     (arg[8] == '=' || arg[8] == '\0'))) {
 			if (arg[2] != 'h' && !arg[8])
 				/* --abbrev only */
@@ -215,7 +215,7 @@
 		}
 		if (!strcmp(arg, "--exclude-existing"))
 			return exclude_existing(NULL);
-		if (!strncmp(arg, "--exclude-existing=", 19))
+		if (!prefixcmp(arg, "--exclude-existing="))
 			return exclude_existing(arg + 19);
 		usage(show_ref_usage);
 	}
@@ -226,7 +226,7 @@
 		while (*pattern) {
 			unsigned char sha1[20];
 
-			if (!strncmp(*pattern, "refs/", 5) &&
+			if (!prefixcmp(*pattern, "refs/") &&
 			    resolve_ref(*pattern, sha1, 1, NULL)) {
 				if (!quiet)
 					show_one(*pattern, sha1);
diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c
index 8055dda..b04719e 100644
--- a/builtin-tar-tree.c
+++ b/builtin-tar-tree.c
@@ -31,7 +31,7 @@
 	nargv[nargc++] = "git-archive";
 	nargv[nargc++] = "--format=tar";
 
-	if (2 <= argc && !strncmp("--remote=", argv[1], 9)) {
+	if (2 <= argc && !prefixcmp(argv[1], "--remote=")) {
 		nargv[nargc++] = argv[1];
 		argv++;
 		argc--;
diff --git a/builtin-unpack-objects.c b/builtin-unpack-objects.c
index d351e02..3956c56 100644
--- a/builtin-unpack-objects.c
+++ b/builtin-unpack-objects.c
@@ -119,18 +119,18 @@
 
 static struct obj_info *obj_list;
 
-static void added_object(unsigned nr, const char *type, void *data,
-			 unsigned long size);
+static void added_object(unsigned nr, enum object_type type,
+			 void *data, unsigned long size);
 
-static void write_object(unsigned nr, void *buf, unsigned long size,
-			 const char *type)
+static void write_object(unsigned nr, enum object_type type,
+			 void *buf, unsigned long size)
 {
-	if (write_sha1_file(buf, size, type, obj_list[nr].sha1) < 0)
+	if (write_sha1_file(buf, size, typename(type), obj_list[nr].sha1) < 0)
 		die("failed to write object");
 	added_object(nr, type, buf, size);
 }
 
-static void resolve_delta(unsigned nr, const char *type,
+static void resolve_delta(unsigned nr, enum object_type type,
 			  void *base, unsigned long base_size,
 			  void *delta, unsigned long delta_size)
 {
@@ -143,12 +143,12 @@
 	if (!result)
 		die("failed to apply delta");
 	free(delta);
-	write_object(nr, result, result_size, type);
+	write_object(nr, type, result, result_size);
 	free(result);
 }
 
-static void added_object(unsigned nr, const char *type, void *data,
-			 unsigned long size)
+static void added_object(unsigned nr, enum object_type type,
+			 void *data, unsigned long size)
 {
 	struct delta_info **p = &delta_list;
 	struct delta_info *info;
@@ -167,33 +167,24 @@
 	}
 }
 
-static void unpack_non_delta_entry(enum object_type kind, unsigned long size,
+static void unpack_non_delta_entry(enum object_type type, unsigned long size,
 				   unsigned nr)
 {
 	void *buf = get_data(size);
-	const char *type;
 
-	switch (kind) {
-	case OBJ_COMMIT: type = commit_type; break;
-	case OBJ_TREE:   type = tree_type; break;
-	case OBJ_BLOB:   type = blob_type; break;
-	case OBJ_TAG:    type = tag_type; break;
-	default: die("bad type %d", kind);
-	}
 	if (!dry_run && buf)
-		write_object(nr, buf, size, type);
+		write_object(nr, type, buf, size);
 	free(buf);
 }
 
-static void unpack_delta_entry(enum object_type kind, unsigned long delta_size,
+static void unpack_delta_entry(enum object_type type, unsigned long delta_size,
 			       unsigned nr)
 {
 	void *delta_data, *base;
 	unsigned long base_size;
-	char type[20];
 	unsigned char base_sha1[20];
 
-	if (kind == OBJ_REF_DELTA) {
+	if (type == OBJ_REF_DELTA) {
 		hashcpy(base_sha1, fill(20));
 		use(20);
 		delta_data = get_data(delta_size);
@@ -255,7 +246,7 @@
 		}
 	}
 
-	base = read_sha1_file(base_sha1, type, &base_size);
+	base = read_sha1_file(base_sha1, &type, &base_size);
 	if (!base) {
 		error("failed to read delta-pack base object %s",
 		      sha1_to_hex(base_sha1));
@@ -369,7 +360,7 @@
 				recover = 1;
 				continue;
 			}
-			if (!strncmp(arg, "--pack_header=", 14)) {
+			if (!prefixcmp(arg, "--pack_header=")) {
 				struct pack_header *hdr;
 				char *c;
 
diff --git a/builtin-update-index.c b/builtin-update-index.c
index 772aaba..71cef63 100644
--- a/builtin-update-index.c
+++ b/builtin-update-index.c
@@ -109,11 +109,11 @@
 	ce->ce_flags = htons(namelen);
 	fill_stat_cache_info(ce, &st);
 
-	if (trust_executable_bit)
+	if (trust_executable_bit && has_symlinks)
 		ce->ce_mode = create_ce_mode(st.st_mode);
 	else {
-		/* If there is an existing entry, pick the mode bits
-		 * from it, otherwise assume unexecutable.
+		/* If there is an existing entry, pick the mode bits and type
+		 * from it, otherwise assume unexecutable regular file.
 		 */
 		struct cache_entry *ent;
 		int pos = cache_name_pos(path, namelen);
@@ -487,6 +487,7 @@
 	int prefix_length = prefix ? strlen(prefix) : 0;
 	char set_executable_bit = 0;
 	unsigned int refresh_flags = 0;
+	int lock_error = 0;
 	struct lock_file *lock_file;
 
 	git_config(git_default_config);
@@ -494,7 +495,9 @@
 	/* We can't free this memory, it becomes part of a linked list parsed atexit() */
 	lock_file = xcalloc(1, sizeof(struct lock_file));
 
-	newfd = hold_lock_file_for_update(lock_file, get_index_file(), 1);
+	newfd = hold_lock_file_for_update(lock_file, get_index_file(), 0);
+	if (newfd < 0)
+		lock_error = errno;
 
 	entries = read_cache();
 	if (entries < 0)
@@ -651,6 +654,12 @@
 
  finish:
 	if (active_cache_changed) {
+		if (newfd < 0) {
+			if (refresh_flags & REFRESH_QUIET)
+				exit(128);
+			die("unable to create '%s.lock': %s",
+			    get_index_file(), strerror(lock_error));
+		}
 		if (write_cache(newfd, active_cache, active_nr) ||
 		    close(newfd) || commit_lock_file(lock_file))
 			die("Unable to write new index file");
diff --git a/builtin-write-tree.c b/builtin-write-tree.c
index 50670dc..90fc1cf 100644
--- a/builtin-write-tree.c
+++ b/builtin-write-tree.c
@@ -70,7 +70,7 @@
 		const char *arg = argv[1];
 		if (!strcmp(arg, "--missing-ok"))
 			missing_ok = 1;
-		else if (!strncmp(arg, "--prefix=", 9))
+		else if (!prefixcmp(arg, "--prefix="))
 			prefix = arg + 9;
 		else
 			usage(write_tree_usage);
diff --git a/builtin.h b/builtin.h
index 5108fd2..af203e9 100644
--- a/builtin.h
+++ b/builtin.h
@@ -19,22 +19,25 @@
 extern int cmd_archive(int argc, const char **argv, const char *prefix);
 extern int cmd_blame(int argc, const char **argv, const char *prefix);
 extern int cmd_branch(int argc, const char **argv, const char *prefix);
+extern int cmd_bundle(int argc, const char **argv, const char *prefix);
 extern int cmd_cat_file(int argc, const char **argv, const char *prefix);
 extern int cmd_checkout_index(int argc, const char **argv, const char *prefix);
 extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);
 extern int cmd_cherry(int argc, const char **argv, const char *prefix);
+extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix);
 extern int cmd_commit_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_count_objects(int argc, const char **argv, const char *prefix);
 extern int cmd_describe(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_files(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_index(int argc, const char **argv, const char *prefix);
 extern int cmd_diff(int argc, const char **argv, const char *prefix);
-extern int cmd_diff_stages(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_tree(int argc, const char **argv, const char *prefix);
+extern int cmd_fetch__tool(int argc, const char **argv, const char *prefix);
 extern int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix);
 extern int cmd_for_each_ref(int argc, const char **argv, const char *prefix);
 extern int cmd_format_patch(int argc, const char **argv, const char *prefix);
 extern int cmd_fsck(int argc, const char **argv, const char *prefix);
+extern int cmd_gc(int argc, const char **argv, const char *prefix);
 extern int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix);
 extern int cmd_grep(int argc, const char **argv, const char *prefix);
 extern int cmd_help(int argc, const char **argv, const char *prefix);
@@ -45,6 +48,7 @@
 extern int cmd_ls_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_mailinfo(int argc, const char **argv, const char *prefix);
 extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);
+extern int cmd_merge_base(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_file(int argc, const char **argv, const char *prefix);
 extern int cmd_mv(int argc, const char **argv, const char *prefix);
 extern int cmd_name_rev(int argc, const char **argv, const char *prefix);
@@ -59,6 +63,7 @@
 extern int cmd_rerere(int argc, const char **argv, const char *prefix);
 extern int cmd_rev_list(int argc, const char **argv, const char *prefix);
 extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);
+extern int cmd_revert(int argc, const char **argv, const char *prefix);
 extern int cmd_rm(int argc, const char **argv, const char *prefix);
 extern int cmd_runstatus(int argc, const char **argv, const char *prefix);
 extern int cmd_shortlog(int argc, const char **argv, const char *prefix);
diff --git a/cache.h b/cache.h
index 04f8e63..384b260 100644
--- a/cache.h
+++ b/cache.h
@@ -108,7 +108,10 @@
 }
 static inline unsigned int ce_mode_from_stat(struct cache_entry *ce, unsigned int mode)
 {
-	extern int trust_executable_bit;
+	extern int trust_executable_bit, has_symlinks;
+	if (!has_symlinks && S_ISREG(mode) &&
+	    ce && S_ISLNK(ntohl(ce->ce_mode)))
+		return ce->ce_mode;
 	if (!trust_executable_bit && S_ISREG(mode)) {
 		if (ce && S_ISREG(ntohl(ce->ce_mode)))
 			return ce->ce_mode;
@@ -127,6 +130,19 @@
 extern struct cache_tree *active_cache_tree;
 extern int cache_errno;
 
+enum object_type {
+	OBJ_BAD = -1,
+	OBJ_NONE = 0,
+	OBJ_COMMIT = 1,
+	OBJ_TREE = 2,
+	OBJ_BLOB = 3,
+	OBJ_TAG = 4,
+	/* 5 for future expansion */
+	OBJ_OFS_DELTA = 6,
+	OBJ_REF_DELTA = 7,
+	OBJ_MAX,
+};
+
 #define GIT_DIR_ENVIRONMENT "GIT_DIR"
 #define DEFAULT_GIT_DIR_ENVIRONMENT ".git"
 #define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY"
@@ -177,7 +193,7 @@
 extern int ce_match_stat(struct cache_entry *ce, struct stat *st, int);
 extern int ce_modified(struct cache_entry *ce, struct stat *st, int);
 extern int ce_path_match(const struct cache_entry *ce, const char **pathspec);
-extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, const char *type);
+extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path);
 extern int read_pipe(int fd, char** return_buf, unsigned long* return_size);
 extern int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object);
 extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object);
@@ -202,6 +218,7 @@
 /* Environment bits from configuration mechanism */
 extern int use_legacy_headers;
 extern int trust_executable_bit;
+extern int has_symlinks;
 extern int assume_unchanged;
 extern int prefer_symlink_refs;
 extern int log_all_ref_updates;
@@ -211,6 +228,8 @@
 extern int zlib_compression_level;
 extern size_t packed_git_window_size;
 extern size_t packed_git_limit;
+extern size_t delta_base_cache_limit;
+extern int auto_crlf;
 
 #define GIT_REPO_VERSION 0
 extern int repository_format_version;
@@ -262,12 +281,11 @@
 char *enter_repo(char *path, int strict);
 
 /* Read and unpack a sha1 file into memory, write memory to a sha1 file */
-extern int sha1_object_info(const unsigned char *, char *, unsigned long *);
-extern void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, unsigned long *size);
-extern void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size);
-extern int hash_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *sha1);
+extern int sha1_object_info(const unsigned char *, unsigned long *);
+extern void * read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size);
+extern int hash_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *sha1);
 extern int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *return_sha1);
-extern int pretend_sha1_file(void *, unsigned long, const char *, unsigned char *);
+extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *);
 
 extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type);
 
@@ -284,18 +302,6 @@
 extern int has_pack_file(const unsigned char *sha1);
 extern int has_pack_index(const unsigned char *sha1);
 
-enum object_type {
-	OBJ_NONE = 0,
-	OBJ_COMMIT = 1,
-	OBJ_TREE = 2,
-	OBJ_BLOB = 3,
-	OBJ_TAG = 4,
-	/* 5 for future expansion */
-	OBJ_OFS_DELTA = 6,
-	OBJ_REF_DELTA = 7,
-	OBJ_BAD,
-};
-
 extern signed char hexval_table[256];
 static inline unsigned int hexval(unsigned int c)
 {
@@ -325,7 +331,8 @@
 					unsigned long *size,
 					unsigned char *sha1_ret);
 
-const char *show_date(unsigned long time, int timezone, int relative);
+enum date_mode { DATE_NORMAL = 0, DATE_RELATIVE, DATE_SHORT };
+const char *show_date(unsigned long time, int timezone, enum date_mode mode);
 const char *show_rfc2822_date(unsigned long time, int timezone);
 int parse_date(const char *date, char *buf, int bufsize);
 void datestamp(char *buf, int bufsize);
@@ -365,9 +372,11 @@
 extern struct packed_git {
 	struct packed_git *next;
 	struct pack_window *windows;
-	uint32_t *index_base;
+	const void *index_data;
 	off_t index_size;
 	off_t pack_size;
+	time_t mtime;
+	int index_version;
 	int pack_fd;
 	int pack_local;
 	unsigned char sha1[20];
@@ -376,7 +385,7 @@
 } *packed_git;
 
 struct pack_entry {
-	unsigned int offset;
+	off_t offset;
 	unsigned char sha1[20];
 	struct packed_git *p;
 };
@@ -405,7 +414,7 @@
 
 extern struct packed_git *parse_pack_index(unsigned char *sha1);
 extern struct packed_git *parse_pack_index_file(const unsigned char *sha1,
-						char *idx_path);
+						const char *idx_path);
 
 extern void prepare_packed_git(void);
 extern void reprepare_packed_git(void);
@@ -415,15 +424,15 @@
 					 struct packed_git *packs);
 
 extern void pack_report(void);
-extern unsigned char* use_pack(struct packed_git *, struct pack_window **, unsigned long, unsigned int *);
+extern unsigned char* use_pack(struct packed_git *, struct pack_window **, off_t, unsigned int *);
 extern void unuse_pack(struct pack_window **);
-extern struct packed_git *add_packed_git(char *, int, int);
-extern int num_packed_objects(const struct packed_git *p);
-extern int nth_packed_object_sha1(const struct packed_git *, int, unsigned char*);
-extern unsigned long find_pack_entry_one(const unsigned char *, struct packed_git *);
-extern void *unpack_entry(struct packed_git *, unsigned long, char *, unsigned long *);
+extern struct packed_git *add_packed_git(const char *, int, int);
+extern uint32_t num_packed_objects(const struct packed_git *p);
+extern int nth_packed_object_sha1(const struct packed_git *, uint32_t, unsigned char*);
+extern off_t find_pack_entry_one(const unsigned char *, struct packed_git *);
+extern void *unpack_entry(struct packed_git *, off_t, enum object_type *, unsigned long *);
 extern unsigned long unpack_object_header_gently(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep);
-extern void packed_object_info_detail(struct packed_git *, unsigned long, char *, unsigned long *, unsigned long *, unsigned int *, unsigned char *);
+extern const char *packed_object_info_detail(struct packed_git *, off_t, unsigned long *, unsigned long *, unsigned int *, unsigned char *);
 
 /* Dumb servers support */
 extern int update_server_info(int);
@@ -443,8 +452,8 @@
 extern char git_default_email[MAX_GITNAME];
 extern char git_default_name[MAX_GITNAME];
 
-extern char *git_commit_encoding;
-extern char *git_log_output_encoding;
+extern const char *git_commit_encoding;
+extern const char *git_log_output_encoding;
 
 extern int copy_fd(int ifd, int ofd);
 extern int read_in_full(int fd, void *buf, size_t count);
@@ -474,8 +483,13 @@
 extern void alloc_report(void);
 
 /* trace.c */
+extern int nfasprintf(char **str, const char *fmt, ...);
 extern int nfvasprintf(char **str, const char *fmt, va_list va);
 extern void trace_printf(const char *format, ...);
 extern void trace_argv_printf(const char **argv, int count, const char *format, ...);
 
+/* convert.c */
+extern int convert_to_git(const char *path, char **bufp, unsigned long *sizep);
+extern int convert_to_working_tree(const char *path, char **bufp, unsigned long *sizep);
+
 #endif /* CACHE_H */
diff --git a/combine-diff.c b/combine-diff.c
index 044633d..3a9b32f 100644
--- a/combine-diff.c
+++ b/combine-diff.c
@@ -92,14 +92,14 @@
 static char *grab_blob(const unsigned char *sha1, unsigned long *size)
 {
 	char *blob;
-	char type[20];
+	enum object_type type;
 	if (is_null_sha1(sha1)) {
 		/* deleted blob */
 		*size = 0;
 		return xcalloc(1, 1);
 	}
-	blob = read_sha1_file(sha1, type, size);
-	if (strcmp(type, blob_type))
+	blob = read_sha1_file(sha1, &type, size);
+	if (type != OBJ_BLOB)
 		die("object '%s' is not a blob!", sha1_to_hex(sha1));
 	return blob;
 }
@@ -684,7 +684,7 @@
 			goto deleted_file;
 
 		if (S_ISLNK(st.st_mode)) {
-			size_t len = st.st_size;
+			size_t len = xsize_t(st.st_size);
 			result_size = len;
 			result = xmalloc(len + 1);
 			if (result_size != readlink(elem->path, result, len)) {
@@ -697,10 +697,20 @@
 		}
 		else if (0 <= (fd = open(elem->path, O_RDONLY)) &&
 			 !fstat(fd, &st)) {
-			size_t len = st.st_size;
+			size_t len = xsize_t(st.st_size);
 			size_t sz = 0;
+			int is_file, i;
 
 			elem->mode = canon_mode(st.st_mode);
+			/* if symlinks don't work, assume symlink if all parents
+			 * are symlinks
+			 */
+			is_file = has_symlinks;
+			for (i = 0; !is_file && i < num_parent; i++)
+				is_file = !S_ISLNK(elem->parent[i].mode);
+			if (!is_file)
+				elem->mode = canon_mode(S_IFLNK);
+
 			result_size = len;
 			result = xmalloc(len + 1);
 			while (sz < len) {
diff --git a/commit.c b/commit.c
index 1fe23b6..754d1b8 100644
--- a/commit.c
+++ b/commit.c
@@ -3,6 +3,7 @@
 #include "commit.h"
 #include "pkt-line.h"
 #include "utf8.h"
+#include "interpolate.h"
 
 int save_commit_buffer = 1;
 
@@ -36,8 +37,11 @@
 	{ "full",	5,	CMIT_FMT_FULL },
 	{ "fuller",	5,	CMIT_FMT_FULLER },
 	{ "oneline",	1,	CMIT_FMT_ONELINE },
+	{ "format:",	7,	CMIT_FMT_USERFORMAT},
 };
 
+static char *user_format;
+
 enum cmit_fmt get_commit_format(const char *arg)
 {
 	int i;
@@ -46,6 +50,12 @@
 		return CMIT_FMT_DEFAULT;
 	if (*arg == '=')
 		arg++;
+	if (!prefixcmp(arg, "format:")) {
+		if (user_format)
+			free(user_format);
+		user_format = xstrdup(arg + 7);
+		return CMIT_FMT_USERFORMAT;
+	}
 	for (i = 0; i < ARRAY_SIZE(cmt_fmts); i++) {
 		if (!strncmp(arg, cmt_fmts[i].n, cmt_fmts[i].cmp_len) &&
 		    !strncmp(arg, cmt_fmts[i].n, strlen(arg)))
@@ -342,18 +352,18 @@
 
 int parse_commit(struct commit *item)
 {
-	char type[20];
+	enum object_type type;
 	void *buffer;
 	unsigned long size;
 	int ret;
 
 	if (item->object.parsed)
 		return 0;
-	buffer = read_sha1_file(item->object.sha1, type, &size);
+	buffer = read_sha1_file(item->object.sha1, &type, &size);
 	if (!buffer)
 		return error("Could not read %s",
 			     sha1_to_hex(item->object.sha1));
-	if (strcmp(type, commit_type)) {
+	if (type != OBJ_COMMIT) {
 		free(buffer);
 		return error("Object %s not a commit",
 			     sha1_to_hex(item->object.sha1));
@@ -641,7 +651,7 @@
 	}
 }
 
-static char *replace_encoding_header(char *buf, char *encoding)
+static char *replace_encoding_header(char *buf, const char *encoding)
 {
 	char *encoding_header = strstr(buf, "\nencoding ");
 	char *header_end = strstr(buf, "\n\n");
@@ -687,32 +697,216 @@
 }
 
 static char *logmsg_reencode(const struct commit *commit,
-			     char *output_encoding)
+			     const char *output_encoding)
 {
+	static const char *utf8 = "utf-8";
+	const char *use_encoding;
 	char *encoding;
 	char *out;
-	char *utf8 = "utf-8";
 
 	if (!*output_encoding)
 		return NULL;
 	encoding = get_header(commit, "encoding");
-	if (!encoding)
-		encoding = utf8;
-	if (!strcmp(encoding, output_encoding))
-		out = strdup(commit->buffer);
+	use_encoding = encoding ? encoding : utf8;
+	if (!strcmp(use_encoding, output_encoding))
+		out = xstrdup(commit->buffer);
 	else
 		out = reencode_string(commit->buffer,
-				      output_encoding, encoding);
+				      output_encoding, use_encoding);
 	if (out)
 		out = replace_encoding_header(out, output_encoding);
 
-	if (encoding != utf8)
-		free(encoding);
-	if (!out)
-		return NULL;
+	free(encoding);
 	return out;
 }
 
+static char *xstrndup(const char *text, int len)
+{
+	char *result = xmalloc(len + 1);
+	memcpy(result, text, len);
+	result[len] = '\0';
+	return result;
+}
+
+static void fill_person(struct interp *table, const char *msg, int len)
+{
+	int start, end, tz = 0;
+	unsigned long date;
+	char *ep;
+
+	/* parse name */
+	for (end = 0; end < len && msg[end] != '<'; end++)
+		; /* do nothing */
+	start = end + 1;
+	while (end > 0 && isspace(msg[end - 1]))
+		end--;
+	table[0].value = xstrndup(msg, end);
+
+	if (start >= len)
+		return;
+
+	/* parse email */
+	for (end = start + 1; end < len && msg[end] != '>'; end++)
+		; /* do nothing */
+
+	if (end >= len)
+		return;
+
+	table[1].value = xstrndup(msg + start, end - start);
+
+	/* parse date */
+	for (start = end + 1; start < len && isspace(msg[start]); start++)
+		; /* do nothing */
+	if (start >= len)
+		return;
+	date = strtoul(msg + start, &ep, 10);
+	if (msg + start == ep)
+		return;
+
+	table[5].value = xstrndup(msg + start, ep - (msg + start));
+
+	/* parse tz */
+	for (start = ep - msg + 1; start < len && isspace(msg[start]); start++)
+		; /* do nothing */
+	if (start + 1 < len) {
+		tz = strtoul(msg + start + 1, NULL, 10);
+		if (msg[start] == '-')
+			tz = -tz;
+	}
+
+	interp_set_entry(table, 2, show_date(date, tz, 0));
+	interp_set_entry(table, 3, show_rfc2822_date(date, tz));
+	interp_set_entry(table, 4, show_date(date, tz, 1));
+}
+
+static long format_commit_message(const struct commit *commit,
+		const char *msg, char *buf, unsigned long space)
+{
+	struct interp table[] = {
+		{ "%H" },	/* commit hash */
+		{ "%h" },	/* abbreviated commit hash */
+		{ "%T" },	/* tree hash */
+		{ "%t" },	/* abbreviated tree hash */
+		{ "%P" },	/* parent hashes */
+		{ "%p" },	/* abbreviated parent hashes */
+		{ "%an" },	/* author name */
+		{ "%ae" },	/* author email */
+		{ "%ad" },	/* author date */
+		{ "%aD" },	/* author date, RFC2822 style */
+		{ "%ar" },	/* author date, relative */
+		{ "%at" },	/* author date, UNIX timestamp */
+		{ "%cn" },	/* committer name */
+		{ "%ce" },	/* committer email */
+		{ "%cd" },	/* committer date */
+		{ "%cD" },	/* committer date, RFC2822 style */
+		{ "%cr" },	/* committer date, relative */
+		{ "%ct" },	/* committer date, UNIX timestamp */
+		{ "%e" },	/* encoding */
+		{ "%s" },	/* subject */
+		{ "%b" },	/* body */
+		{ "%Cred" },	/* red */
+		{ "%Cgreen" },	/* green */
+		{ "%Cblue" },	/* blue */
+		{ "%Creset" },	/* reset color */
+		{ "%n" }	/* newline */
+	};
+	enum interp_index {
+		IHASH = 0, IHASH_ABBREV,
+		ITREE, ITREE_ABBREV,
+		IPARENTS, IPARENTS_ABBREV,
+		IAUTHOR_NAME, IAUTHOR_EMAIL,
+		IAUTHOR_DATE, IAUTHOR_DATE_RFC2822, IAUTHOR_DATE_RELATIVE,
+		IAUTHOR_TIMESTAMP,
+		ICOMMITTER_NAME, ICOMMITTER_EMAIL,
+		ICOMMITTER_DATE, ICOMMITTER_DATE_RFC2822,
+		ICOMMITTER_DATE_RELATIVE, ICOMMITTER_TIMESTAMP,
+		IENCODING,
+		ISUBJECT,
+		IBODY,
+		IRED, IGREEN, IBLUE, IRESET_COLOR,
+		INEWLINE
+	};
+	struct commit_list *p;
+	char parents[1024];
+	int i;
+	enum { HEADER, SUBJECT, BODY } state;
+
+	if (INEWLINE + 1 != ARRAY_SIZE(table))
+		die("invalid interp table!");
+
+	/* these are independent of the commit */
+	interp_set_entry(table, IRED, "\033[31m");
+	interp_set_entry(table, IGREEN, "\033[32m");
+	interp_set_entry(table, IBLUE, "\033[34m");
+	interp_set_entry(table, IRESET_COLOR, "\033[m");
+	interp_set_entry(table, INEWLINE, "\n");
+
+	/* these depend on the commit */
+	if (!commit->object.parsed)
+		parse_object(commit->object.sha1);
+	interp_set_entry(table, IHASH, sha1_to_hex(commit->object.sha1));
+	interp_set_entry(table, IHASH_ABBREV,
+			find_unique_abbrev(commit->object.sha1,
+				DEFAULT_ABBREV));
+	interp_set_entry(table, ITREE, sha1_to_hex(commit->tree->object.sha1));
+	interp_set_entry(table, ITREE_ABBREV,
+			find_unique_abbrev(commit->tree->object.sha1,
+				DEFAULT_ABBREV));
+
+	parents[1] = 0;
+	for (i = 0, p = commit->parents;
+			p && i < sizeof(parents) - 1;
+			p = p->next)
+		i += snprintf(parents + i, sizeof(parents) - i - 1, " %s",
+			sha1_to_hex(p->item->object.sha1));
+	interp_set_entry(table, IPARENTS, parents + 1);
+
+	parents[1] = 0;
+	for (i = 0, p = commit->parents;
+			p && i < sizeof(parents) - 1;
+			p = p->next)
+		i += snprintf(parents + i, sizeof(parents) - i - 1, " %s",
+			find_unique_abbrev(p->item->object.sha1,
+				DEFAULT_ABBREV));
+	interp_set_entry(table, IPARENTS_ABBREV, parents + 1);
+
+	for (i = 0, state = HEADER; msg[i] && state < BODY; i++) {
+		int eol;
+		for (eol = i; msg[eol] && msg[eol] != '\n'; eol++)
+			; /* do nothing */
+
+		if (state == SUBJECT) {
+			table[ISUBJECT].value = xstrndup(msg + i, eol - i);
+			i = eol;
+		}
+		if (i == eol) {
+			state++;
+			/* strip empty lines */
+			while (msg[eol + 1] == '\n')
+				eol++;
+		} else if (!prefixcmp(msg + i, "author "))
+			fill_person(table + IAUTHOR_NAME,
+					msg + i + 7, eol - i - 7);
+		else if (!prefixcmp(msg + i, "committer "))
+			fill_person(table + ICOMMITTER_NAME,
+					msg + i + 10, eol - i - 10);
+		else if (!prefixcmp(msg + i, "encoding "))
+			table[IENCODING].value =
+				xstrndup(msg + i + 9, eol - i - 9);
+		i = eol;
+	}
+	if (msg[i])
+		table[IBODY].value = xstrdup(msg + i);
+	for (i = 0; i < ARRAY_SIZE(table); i++)
+		if (!table[i].value)
+			interp_set_entry(table, i, "<unknown>");
+
+	interpolate(buf, space, user_format, table, ARRAY_SIZE(table));
+	interp_clear_table(table, ARRAY_SIZE(table));
+
+	return strlen(buf);
+}
+
 unsigned long pretty_print_commit(enum cmit_fmt fmt,
 				  const struct commit *commit,
 				  unsigned long len,
@@ -728,7 +922,10 @@
 	const char *msg = commit->buffer;
 	int plain_non_ascii = 0;
 	char *reencoded;
-	char *encoding;
+	const char *encoding;
+
+	if (fmt == CMIT_FMT_USERFORMAT)
+		return format_commit_message(commit, msg, buf, space);
 
 	encoding = (git_log_output_encoding
 		    ? git_log_output_encoding
@@ -1190,14 +1387,17 @@
 	return result;
 }
 
-int in_merge_bases(struct commit *rev1, struct commit *rev2)
+int in_merge_bases(struct commit *commit, struct commit **reference, int num)
 {
 	struct commit_list *bases, *b;
 	int ret = 0;
 
-	bases = get_merge_bases(rev1, rev2, 1);
+	if (num == 1)
+		bases = get_merge_bases(commit, *reference, 1);
+	else
+		die("not yet");
 	for (b = bases; b; b = b->next) {
-		if (!hashcmp(rev1->object.sha1, b->item->object.sha1)) {
+		if (!hashcmp(commit->object.sha1, b->item->object.sha1)) {
 			ret = 1;
 			break;
 		}
diff --git a/commit.h b/commit.h
index 491b0c4..83507a0 100644
--- a/commit.h
+++ b/commit.h
@@ -47,6 +47,7 @@
 	CMIT_FMT_FULLER,
 	CMIT_FMT_ONELINE,
 	CMIT_FMT_EMAIL,
+	CMIT_FMT_USERFORMAT,
 
 	CMIT_FMT_UNSPECIFIED,
 };
@@ -114,5 +115,5 @@
 extern struct commit_list *get_shallow_commits(struct object_array *heads,
 		int depth, int shallow_flag, int not_shallow_flag);
 
-int in_merge_bases(struct commit *rev1, struct commit *rev2);
+int in_merge_bases(struct commit *, struct commit **, int);
 #endif /* COMMIT_H */
diff --git a/config.c b/config.c
index c938aa0..6479855 100644
--- a/config.c
+++ b/config.c
@@ -269,6 +269,11 @@
 		return 0;
 	}
 
+	if (!strcmp(var, "core.symlinks")) {
+		has_symlinks = git_config_bool(var, value);
+		return 0;
+	}
+
 	if (!strcmp(var, "core.bare")) {
 		is_bare_repository_cfg = git_config_bool(var, value);
 		return 0;
@@ -326,6 +331,20 @@
 		return 0;
 	}
 
+	if (!strcmp(var, "core.deltabasecachelimit")) {
+		delta_base_cache_limit = git_config_int(var, value);
+		return 0;
+	}
+
+	if (!strcmp(var, "core.autocrlf")) {
+		if (value && !strcasecmp(value, "input")) {
+			auto_crlf = -1;
+			return 0;
+		}
+		auto_crlf = git_config_bool(var, value);
+		return 0;
+	}
+
 	if (!strcmp(var, "user.name")) {
 		strlcpy(git_default_name, value, sizeof(git_default_name));
 		return 0;
@@ -337,12 +356,12 @@
 	}
 
 	if (!strcmp(var, "i18n.commitencoding")) {
-		git_commit_encoding = strdup(value);
+		git_commit_encoding = xstrdup(value);
 		return 0;
 	}
 
 	if (!strcmp(var, "i18n.logoutputencoding")) {
-		git_log_output_encoding = strdup(value);
+		git_log_output_encoding = xstrdup(value);
 		return 0;
 	}
 
@@ -385,6 +404,8 @@
 	 * config file otherwise. */
 	filename = getenv(CONFIG_ENVIRONMENT);
 	if (!filename) {
+		if (!access(ETC_GITCONFIG, R_OK))
+			ret += git_config_from_file(fn, ETC_GITCONFIG);
 		home = getenv("HOME");
 		filename = getenv(CONFIG_LOCAL_ENVIRONMENT);
 		if (!filename)
@@ -415,7 +436,7 @@
 	int do_not_match;
 	regex_t* value_regex;
 	int multi_replace;
-	off_t offset[MAX_MATCHES];
+	size_t offset[MAX_MATCHES];
 	enum { START, SECTION_SEEN, SECTION_END_SEEN, KEY_SEEN } state;
 	int seen;
 } store;
@@ -563,11 +584,11 @@
 	return 1;
 }
 
-static int find_beginning_of_line(const char* contents, int size,
-	int offset_, int* found_bracket)
+static ssize_t find_beginning_of_line(const char* contents, size_t size,
+	size_t offset_, int* found_bracket)
 {
-	int equal_offset = size, bracket_offset = size;
-	int offset;
+	size_t equal_offset = size, bracket_offset = size;
+	ssize_t offset;
 
 	for (offset = offset_-2; offset > 0 
 			&& contents[offset] != '\n'; offset--)
@@ -711,7 +732,8 @@
 	} else {
 		struct stat st;
 		char* contents;
-		int i, copy_begin, copy_end, new_line = 0;
+		size_t contents_sz, copy_begin, copy_end;
+		int i, new_line = 0;
 
 		if (value_regex == NULL)
 			store.value_regex = NULL;
@@ -768,7 +790,8 @@
 		}
 
 		fstat(in_fd, &st);
-		contents = xmmap(NULL, st.st_size, PROT_READ,
+		contents_sz = xsize_t(st.st_size);
+		contents = xmmap(NULL, contents_sz, PROT_READ,
 			MAP_PRIVATE, in_fd, 0);
 		close(in_fd);
 
@@ -777,12 +800,12 @@
 
 		for (i = 0, copy_begin = 0; i < store.seen; i++) {
 			if (store.offset[i] == 0) {
-				store.offset[i] = copy_end = st.st_size;
+				store.offset[i] = copy_end = contents_sz;
 			} else if (store.state != KEY_SEEN) {
 				copy_end = store.offset[i];
 			} else
 				copy_end = find_beginning_of_line(
-					contents, st.st_size,
+					contents, contents_sz,
 					store.offset[i]-2, &new_line);
 
 			/* write the first part of the config */
@@ -809,13 +832,13 @@
 		}
 
 		/* write the rest of the config */
-		if (copy_begin < st.st_size)
+		if (copy_begin < contents_sz)
 			if (write_in_full(fd, contents + copy_begin,
-					  st.st_size - copy_begin) <
-			    st.st_size - copy_begin)
+					  contents_sz - copy_begin) <
+			    contents_sz - copy_begin)
 				goto write_err_out;
 
-		munmap(contents, st.st_size);
+		munmap(contents, contents_sz);
 		unlink(config_filename);
 	}
 
@@ -843,9 +866,37 @@
 
 }
 
+static int section_name_match (const char *buf, const char *name)
+{
+	int i = 0, j = 0, dot = 0;
+	for (; buf[i] && buf[i] != ']'; i++) {
+		if (!dot && isspace(buf[i])) {
+			dot = 1;
+			if (name[j++] != '.')
+				break;
+			for (i++; isspace(buf[i]); i++)
+				; /* do nothing */
+			if (buf[i] != '"')
+				break;
+			continue;
+		}
+		if (buf[i] == '\\' && dot)
+			i++;
+		else if (buf[i] == '"' && dot) {
+			for (i++; isspace(buf[i]); i++)
+				; /* do_nothing */
+			break;
+		}
+		if (buf[i] != name[j++])
+			break;
+	}
+	return (buf[i] == ']' && name[j] == 0);
+}
+
+/* if new_name == NULL, the section is removed instead */
 int git_config_rename_section(const char *old_name, const char *new_name)
 {
-	int ret = 0;
+	int ret = 0, remove = 0;
 	char *config_filename;
 	struct lock_file *lock = xcalloc(sizeof(struct lock_file), 1);
 	int out_fd;
@@ -876,31 +927,12 @@
 			; /* do nothing */
 		if (buf[i] == '[') {
 			/* it's a section */
-			int j = 0, dot = 0;
-			for (i++; buf[i] && buf[i] != ']'; i++) {
-				if (!dot && isspace(buf[i])) {
-					dot = 1;
-					if (old_name[j++] != '.')
-						break;
-					for (i++; isspace(buf[i]); i++)
-						; /* do nothing */
-					if (buf[i] != '"')
-						break;
+			if (section_name_match (&buf[i+1], old_name)) {
+				ret++;
+				if (new_name == NULL) {
+					remove = 1;
 					continue;
 				}
-				if (buf[i] == '\\' && dot)
-					i++;
-				else if (buf[i] == '"' && dot) {
-					for (i++; isspace(buf[i]); i++)
-						; /* do_nothing */
-					break;
-				}
-				if (buf[i] != old_name[j++])
-					break;
-			}
-			if (buf[i] == ']' && old_name[j] == 0) {
-				/* old_name matches */
-				ret++;
 				store.baselen = strlen(new_name);
 				if (!store_write_section(out_fd, new_name)) {
 					ret = write_error();
@@ -908,7 +940,10 @@
 				}
 				continue;
 			}
+			remove = 0;
 		}
+		if (remove)
+			continue;
 		length = strlen(buf);
 		if (write_in_full(out_fd, buf, length) != length) {
 			ret = write_error();
diff --git a/configure.ac b/configure.ac
index 7cfb3a0..3a8e778 100644
--- a/configure.ac
+++ b/configure.ac
@@ -114,13 +114,32 @@
 [NO_EXPAT=YesPlease])
 AC_SUBST(NO_EXPAT)
 #
-# Define NEEDS_LIBICONV if linking with libc is not enough (Darwin).
+# Define NEEDS_LIBICONV if linking with libc is not enough (Darwin and
+# some Solaris installations).
 # Define NO_ICONV if neither libc nor libiconv support iconv.
-AC_CHECK_LIB([c], [iconv],
-	[NEEDS_LIBICONV=],
-	AC_CHECK_LIB([iconv], [iconv],
-		[NEEDS_LIBICONV=YesPlease],
-		[NO_ICONV=YesPlease]))
+AC_DEFUN([ICONVTEST_SRC], [
+#include <iconv.h>
+
+int main(void)
+{
+	iconv_open("", "");
+	return 0;
+}
+])
+AC_MSG_CHECKING([for iconv in -lc])
+AC_LINK_IFELSE(ICONVTEST_SRC,
+	[AC_MSG_RESULT([yes])
+	NEEDS_LIBICONV=],
+	[AC_MSG_RESULT([no])
+	old_LIBS="$LIBS"
+	LIBS="$LIBS -liconv"
+	AC_MSG_CHECKING([for iconv in -liconv])
+	AC_LINK_IFELSE(ICONVTEST_SRC,
+		[AC_MSG_RESULT([yes])
+		NEEDS_LIBICONV=YesPlease],
+		[AC_MSG_RESULT([no])
+		NO_ICONV=YesPlease])
+	LIBS="$old_LIBS"])
 AC_SUBST(NEEDS_LIBICONV)
 AC_SUBST(NO_ICONV)
 test -n "$NEEDS_LIBICONV" && LIBS="$LIBS -liconv"
diff --git a/connect.c b/connect.c
index 7844888..da89c9c 100644
--- a/connect.c
+++ b/connect.c
@@ -3,6 +3,7 @@
 #include "pkt-line.h"
 #include "quote.h"
 #include "refs.h"
+#include "run-command.h"
 
 static char *server_capabilities;
 
@@ -96,7 +97,7 @@
 		line[--len] = 0;
 	if (!strcmp(line, "NAK"))
 		return 0;
-	if (!strncmp(line, "ACK ", 4)) {
+	if (!prefixcmp(line, "ACK ")) {
 		if (!get_sha1_hex(line+4, result_sha1)) {
 			if (strstr(line+45, "continue"))
 				return 2;
@@ -196,8 +197,8 @@
 		 */
 		if (namelen != patlen &&
 		    patlen != namelen - 5 &&
-		    strncmp(name, "refs/heads/", 11) &&
-		    strncmp(name, "refs/tags/", 10)) {
+		    prefixcmp(name, "refs/heads/") &&
+		    prefixcmp(name, "refs/tags/")) {
 			/* We want to catch the case where only weak
 			 * matches are found and there are multiple
 			 * matches, and where more than one strong
@@ -416,6 +417,8 @@
 	if (colon) {
 		*colon = 0;
 		port = colon + 1;
+		if (!*port)
+			port = "<none>";
 	}
 
 	memset(&hints, 0, sizeof(hints));
@@ -424,7 +427,7 @@
 
 	gai = getaddrinfo(host, port, &hints, &ai);
 	if (gai)
-		die("Unable to look up %s (%s)", host, gai_strerror(gai));
+		die("Unable to look up %s (port %s) (%s)", host, port, gai_strerror(gai));
 
 	for (ai0 = ai; ai; ai = ai->ai_next) {
 		sockfd = socket(ai->ai_family,
@@ -598,8 +601,8 @@
 {
 	const char *port = STR(DEFAULT_GIT_PORT);
 	char *colon, *end;
-	int pipefd[2][2];
-	pid_t pid;
+	const char *argv[4];
+	struct child_process proxy;
 
 	if (host[0] == '[') {
 		end = strchr(host + 1, ']');
@@ -618,25 +621,18 @@
 		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");
-	}
-	if (pid < 0)
-		die("fork failed");
-	fd[0] = pipefd[0][0];
-	fd[1] = pipefd[1][1];
-	close(pipefd[0][1]);
-	close(pipefd[1][0]);
+	argv[0] = git_proxy_command;
+	argv[1] = host;
+	argv[2] = port;
+	argv[3] = NULL;
+	memset(&proxy, 0, sizeof(proxy));
+	proxy.argv = argv;
+	proxy.in = -1;
+	proxy.out = -1;
+	if (start_command(&proxy))
+		die("cannot start proxy %s", argv[0]);
+	fd[0] = proxy.out; /* read from proxy stdout */
+	fd[1] = proxy.in;  /* write to proxy stdin */
 }
 
 #define MAX_CMD_LEN 1024
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 5d3d402..7c03403 100755
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -269,7 +269,6 @@
 		cvsimport)        : import;;
 		cvsserver)        : daemon;;
 		daemon)           : daemon;;
-		diff-stages)      : nobody uses it;;
 		fast-import)      : import;;
 		fsck-objects)     : plumbing;;
 		fetch-pack)       : plumbing;;
@@ -298,7 +297,6 @@
 		reflog)           : plumbing;;
 		repo-config)      : plumbing;;
 		rerere)           : plumbing;;
-		resolve)          : dead dont use;;
 		rev-list)         : plumbing;;
 		rev-parse)        : plumbing;;
 		runstatus)        : plumbing;;
diff --git a/contrib/continuous/cidaemon b/contrib/continuous/cidaemon
new file mode 100644
index 0000000..4009a15
--- /dev/null
+++ b/contrib/continuous/cidaemon
@@ -0,0 +1,503 @@
+#!/usr/bin/perl
+#
+# A daemon that waits for update events sent by its companion
+# post-receive-cinotify hook, checks out a new copy of source,
+# compiles it, and emails the guilty parties if the compile
+# (and optionally test suite) fails.
+#
+# To use this daemon, configure it and run it.  It will disconnect
+# from your terminal and fork into the background.  The daemon must
+# have local filesystem access to the source repositories, as it
+# uses objects/info/alternates to avoid copying objects.
+#
+# Add its companion post-receive-cinotify hook as the post-receive
+# hook to each repository that the daemon should monitor.  Yes, a
+# single daemon can monitor more than one repository.
+#
+# To use multiple daemons on the same system, give them each a
+# unique queue file and tmpdir.
+#
+# Global Config
+# -------------
+# Reads from a Git style configuration file.  This will be
+# ~/.gitconfig by default but can be overridden by setting
+# the GIT_CONFIG_FILE environment variable before starting.
+#
+# cidaemon.smtpHost
+#   Hostname of the SMTP server the daemon will send email
+#   through.  Defaults to 'localhost'.
+#
+# cidaemon.smtpUser
+#   Username to authenticate to the SMTP server as.  This
+#   variable is optional; if it is not supplied then no
+#   authentication will be performed.
+#
+# cidaemon.smtpPassword
+#   Password to authenticate to the SMTP server as.  This
+#   variable is optional.  If not supplied but smtpUser was,
+#   the daemon prompts for the password before forking into
+#   the background.
+#
+# cidaemon.smtpAuth
+#   Type of authentication to perform with the SMTP server.
+#   If set to 'login' and smtpUser was defined, this will
+#   use the AUTH LOGIN command, which is suitable for use
+#   with at least one version of Microsoft Exchange Server.
+#   If not set the daemon will use whatever auth methods
+#   are supported by your version of Net::SMTP.
+#
+# cidaemon.email
+#   Email address that daemon generated emails will be sent
+#   from.  This should be a useful email address within your
+#   organization.  Required.
+#
+# cidaemon.name
+#   Human friendly name that the daemon will send emails as.
+#   Defaults to 'cidaemon'.
+#
+# cidaemon.scanDelay
+#   Number of seconds to sleep between polls of the queue file.
+#   Defaults to 60.
+#
+# cidaemon.recentCache
+#   Number of recent commit SHA-1s per repository to cache and
+#   skip building if they appear again.  This is useful to avoid
+#   rebuilding the same commit multiple times just because it was
+#   pushed into more than one branch.  Defaults to 100.
+#
+# cidaemon.tmpdir
+#   Scratch directory to create the builds within.  The daemon
+#   makes a new subdirectory for each build, then deletes it when
+#   the build has finished.  The pid file is also placed here.
+#   Defaults to '/tmp'.
+#
+# cidaemon.queue
+#   Path to the queue file that the post-receive-cinotify hook
+#   appends events to.  This file is polled by the daemon.  It
+#   must not be on an NFS mount (uses flock).  Required.
+#
+# cidaemon.nocc
+#   Perl regex patterns to match against author and committer
+#   lines.  If a pattern matches, that author or committer will
+#   not be notified of a build failure.
+#
+# Per Repository Config
+# ----------------------
+# Read from the source repository's config file.
+#
+# builder.command
+#   Shell command to execute the build.  This command must
+#   return 0 on "success" and non-zero on failure.  If you
+#   also want to run a test suite, make sure your command
+#   does that too.  Required.
+#
+# builder.queue
+#   Queue file to notify the cidaemon through.  Should match
+#   cidaemon.queue.  If not set the hook will not notify the
+#   cidaemon.
+#
+# builder.skip
+#   Perl regex patterns of refs that should not be sent to
+#   cidaemon.  Updates of these refs will be ignored.
+#
+# builder.newBranchBase
+#   Glob patterns of refs that should be used to form the
+#   'old' revions of a newly created ref.  This should set
+#   to be globs that match your 'mainline' branches.  This
+#   way a build failure of a brand new topic branch does not
+#   attempt to email everyone since the beginning of time;
+#   instead it only emails those authors of commits not in
+#   these 'mainline' branches.
+
+local $ENV{PATH} = join ':', qw(
+	/opt/git/bin
+	/usr/bin
+	/bin
+	);
+
+use strict;
+use warnings;
+use FindBin qw($RealBin);
+use File::Spec;
+use lib File::Spec->catfile($RealBin, '..', 'perl5');
+use Storable qw(retrieve nstore);
+use Fcntl ':flock';
+use POSIX qw(strftime);
+use Getopt::Long qw(:config no_auto_abbrev auto_help);
+
+sub git_config ($;$)
+{
+	my $var = shift;
+	my $required = shift || 0;
+	local *GIT;
+	open GIT, '-|','git','config','--get',$var;
+	my $r = <GIT>;
+	chop $r if $r;
+	close GIT;
+	die "error: $var not set.\n" if ($required && !$r);
+	return $r;
+}
+
+package EXCHANGE_NET_SMTP;
+
+# Microsoft Exchange Server requires an 'AUTH LOGIN'
+# style of authentication.  This is different from
+# the default supported by Net::SMTP so we subclass
+# and override the auth method to support that.
+
+use Net::SMTP;
+use Net::Cmd;
+use MIME::Base64 qw(encode_base64);
+our @ISA = qw(Net::SMTP);
+our $auth_type = ::git_config 'cidaemon.smtpAuth';
+
+sub new
+{
+	my $self = shift;
+	my $type = ref($self) || $self;
+	$type->SUPER::new(@_);
+}
+
+sub auth
+{
+	my $self = shift;
+	return $self->SUPER::auth(@_) unless $auth_type eq 'login';
+
+	my $user = encode_base64 shift, '';
+	my $pass = encode_base64 shift, '';
+	return 0 unless CMD_MORE == $self->command("AUTH LOGIN")->response;
+	return 0 unless CMD_MORE == $self->command($user)->response;
+	CMD_OK == $self->command($pass)->response;
+}
+
+package main;
+
+my ($debug_flag, %recent);
+
+my $ex_host = git_config('cidaemon.smtpHost') || 'localhost';
+my $ex_user = git_config('cidaemon.smtpUser');
+my $ex_pass = git_config('cidaemon.smtpPassword');
+
+my $ex_from_addr = git_config('cidaemon.email', 1);
+my $ex_from_name = git_config('cidaemon.name') || 'cidaemon';
+
+my $scan_delay = git_config('cidaemon.scanDelay') || 60;
+my $recent_size = git_config('cidaemon.recentCache') || 100;
+my $tmpdir = git_config('cidaemon.tmpdir') || '/tmp';
+my $queue_name = git_config('cidaemon.queue', 1);
+my $queue_lock = "$queue_name.lock";
+
+my @nocc_list;
+open GIT,'git config --get-all cidaemon.nocc|';
+while (<GIT>) {
+	chop;
+	push @nocc_list, $_;
+}
+close GIT;
+
+sub nocc_author ($)
+{
+	local $_ = shift;
+	foreach my $pat (@nocc_list) {
+		return 1 if /$pat/;
+	}
+	0;
+}
+
+sub input_echo ($)
+{
+	my $prompt = shift;
+
+	local $| = 1;
+	print $prompt;
+	my $input = <STDIN>;
+	chop $input;
+	return $input;
+}
+
+sub input_noecho ($)
+{
+	my $prompt = shift;
+
+	my $end = sub {system('stty','echo');print "\n";exit};
+	local $SIG{TERM} = $end;
+	local $SIG{INT} = $end;
+	system('stty','-echo');
+
+	local $| = 1;
+	print $prompt;
+	my $input = <STDIN>;
+	system('stty','echo');
+	print "\n";
+	chop $input;
+	return $input;
+}
+
+sub rfc2822_date ()
+{
+	 strftime("%a, %d %b %Y %H:%M:%S %Z", localtime);
+}
+
+sub send_email ($$$)
+{
+	my ($subj, $body, $to) = @_;
+	my $now = rfc2822_date;
+	my $to_str = '';
+	my @rcpt_to;
+	foreach (@$to) {
+		my $s = $_;
+		$s =~ s/^/"/;
+		$s =~ s/(\s+<)/"$1/;
+		$to_str .= ', ' if $to_str;
+		$to_str .= $s;
+		push @rcpt_to, $1 if $s =~ /<(.*)>/;
+	}
+	die "Nobody to send to.\n" unless @rcpt_to;
+	my $msg = <<EOF;
+From: "$ex_from_name" <$ex_from_addr>
+To: $to_str
+Date: $now
+Subject: $subj
+
+$body
+EOF
+
+	my $smtp = EXCHANGE_NET_SMTP->new(Host => $ex_host)
+		or die "Cannot connect to $ex_host: $!\n";
+	if ($ex_user && $ex_pass) {
+		$smtp->auth($ex_user,$ex_pass)
+			or die "$ex_host rejected $ex_user\n";
+	}
+	$smtp->mail($ex_from_addr)
+		or die "$ex_host rejected $ex_from_addr\n";
+	scalar($smtp->recipient(@rcpt_to, { SkipBad => 1 }))
+		or die "$ex_host did not accept any addresses.\n";
+	$smtp->data($msg)
+		or die "$ex_host rejected message data\n";
+	$smtp->quit;
+}
+
+sub pop_queue ()
+{
+	open LOCK, ">$queue_lock" or die "Can't open $queue_lock: $!";
+	flock LOCK, LOCK_EX;
+
+	my $queue = -f $queue_name ? retrieve $queue_name : [];
+	my $ent = shift @$queue;
+	nstore $queue, $queue_name;
+
+	flock LOCK, LOCK_UN;
+	close LOCK;
+	$ent;
+}
+
+sub git_exec (@)
+{
+	system('git',@_) == 0 or die "Cannot git " . join(' ', @_) . "\n";
+}
+
+sub git_val (@)
+{
+	open(C, '-|','git',@_);
+	my $r = <C>;
+	chop $r if $r;
+	close C;
+	$r;
+}
+
+sub do_build ($$)
+{
+	my ($git_dir, $new) = @_;
+
+	my $tmp = File::Spec->catfile($tmpdir, "builder$$");
+	system('rm','-rf',$tmp) == 0 or die "Cannot clear $tmp\n";
+	die "Cannot clear $tmp.\n" if -e $tmp;
+
+	my $result = 1;
+	eval {
+		my $command;
+		{
+			local $ENV{GIT_DIR} = $git_dir;
+			$command = git_val 'config','builder.command';
+		}
+		die "No builder.command for $git_dir.\n" unless $command;
+
+		git_exec 'clone','-n','-l','-s',$git_dir,$tmp;
+		chmod 0700, $tmp or die "Cannot lock $tmp\n";
+		chdir $tmp or die "Cannot enter $tmp\n";
+
+		git_exec 'update-ref','HEAD',$new;
+		git_exec 'read-tree','-m','-u','HEAD','HEAD';
+		system $command;
+		if ($? == -1) {
+			print STDERR "failed to execute '$command': $!\n";
+			$result = 1;
+		} elsif ($? & 127) {
+			my $sig = $? & 127;
+			print STDERR "'$command' died from signal $sig\n";
+			$result = 1;
+		} else {
+			my $r = $? >> 8;
+			print STDERR "'$command' exited with $r\n" if $r;
+			$result = $r;
+		}
+	};
+	if ($@) {
+		$result = 2;
+		print STDERR "$@\n";
+	}
+
+	chdir '/';
+	system('rm','-rf',$tmp);
+	rmdir $tmp;
+	$result;
+}
+
+sub build_failed ($$$$$)
+{
+	my ($git_dir, $ref, $old, $new, $msg) = @_;
+
+	$git_dir =~ m,/([^/]+)$,;
+	my $repo_name = $1;
+	$ref =~ s,^refs/(heads|tags)/,,;
+
+	my %authors;
+	my $shortlog;
+	my $revstr;
+	{
+		local $ENV{GIT_DIR} = $git_dir;
+		my @revs = ($new);
+		push @revs, '--not', @$old if @$old;
+		open LOG,'-|','git','rev-list','--pretty=raw',@revs;
+		while (<LOG>) {
+			if (s/^(author|committer) //) {
+				chomp;
+				s/>.*$/>/;
+				$authors{$_} = 1 unless nocc_author $_;
+			}
+		}
+		close LOG;
+		open LOG,'-|','git','shortlog',@revs;
+		$shortlog .= $_ while <LOG>;
+		close LOG;
+		$revstr = join(' ', @revs);
+	}
+
+	my @to = sort keys %authors;
+	unless (@to) {
+		print STDERR "error: No authors in $revstr\n";
+		return;
+	}
+
+	my $subject = "[$repo_name] $ref : Build Failed";
+	my $body = <<EOF;
+Project: $git_dir
+Branch:  $ref
+Commits: $revstr
+
+$shortlog
+Build Output:
+--------------------------------------------------------------
+$msg
+EOF
+	send_email($subject, $body, \@to);
+}
+
+sub run_build ($$$$)
+{
+	my ($git_dir, $ref, $old, $new) = @_;
+
+	if ($debug_flag) {
+		my @revs = ($new);
+		push @revs, '--not', @$old if @$old;
+		print "BUILDING $git_dir\n";
+		print "  BRANCH: $ref\n";
+		print "  COMMITS: ", join(' ', @revs), "\n";
+	}
+
+	local(*R, *W);
+	pipe R, W or die "cannot pipe builder: $!";
+
+	my $builder = fork();
+	if (!defined $builder) {
+		die "cannot fork builder: $!";
+	} elsif (0 == $builder) {
+		close R;
+		close STDIN;open(STDIN, '/dev/null');
+		open(STDOUT, '>&W');
+		open(STDERR, '>&W');
+		exit do_build $git_dir, $new;
+	} else {
+		close W;
+		my $out = '';
+		$out .= $_ while <R>;
+		close R;
+		waitpid $builder, 0;
+		build_failed $git_dir, $ref, $old, $new, $out if $?;
+	}
+
+	print "DONE\n\n" if $debug_flag;
+}
+
+sub daemon_loop ()
+{
+	my $run = 1;
+	my $stop_sub = sub {$run = 0};
+	$SIG{HUP} = $stop_sub;
+	$SIG{INT} = $stop_sub;
+	$SIG{TERM} = $stop_sub;
+
+	mkdir $tmpdir, 0755;
+	my $pidfile = File::Spec->catfile($tmpdir, "cidaemon.pid");
+	open(O, ">$pidfile"); print O "$$\n"; close O;
+
+	while ($run) {
+		my $ent = pop_queue;
+		if ($ent) {
+			my ($git_dir, $ref, $old, $new) = @$ent;
+
+			$ent = $recent{$git_dir};
+			$recent{$git_dir} = $ent = [[], {}] unless $ent;
+			my ($rec_arr, $rec_hash) = @$ent;
+			next if $rec_hash->{$new}++;
+			while (@$rec_arr >= $recent_size) {
+				my $to_kill = shift @$rec_arr;
+				delete $rec_hash->{$to_kill};
+			}
+			push @$rec_arr, $new;
+
+			run_build $git_dir, $ref, $old, $new;
+		} else {
+			sleep $scan_delay;
+		}
+	}
+
+	unlink $pidfile;
+}
+
+$debug_flag = 0;
+GetOptions(
+	'debug|d' => \$debug_flag,
+	'smtp-user=s' => \$ex_user,
+) or die "usage: $0 [--debug] [--smtp-user=user]\n";
+
+$ex_pass = input_noecho("$ex_user SMTP password: ")
+	if ($ex_user && !$ex_pass);
+
+if ($debug_flag) {
+	daemon_loop;
+	exit 0;
+}
+
+my $daemon = fork();
+if (!defined $daemon) {
+	die "cannot fork daemon: $!";
+} elsif (0 == $daemon) {
+	close STDIN;open(STDIN, '/dev/null');
+	close STDOUT;open(STDOUT, '>/dev/null');
+	close STDERR;open(STDERR, '>/dev/null');
+	daemon_loop;
+	exit 0;
+} else {
+	print "Daemon $daemon running in the background.\n";
+}
diff --git a/contrib/continuous/post-receive-cinotify b/contrib/continuous/post-receive-cinotify
new file mode 100644
index 0000000..b8f5a60
--- /dev/null
+++ b/contrib/continuous/post-receive-cinotify
@@ -0,0 +1,104 @@
+#!/usr/bin/perl
+#
+# A hook that notifies its companion cidaemon through a simple
+# queue file that a ref has been updated via a push (actually
+# by a receive-pack running on the server).
+#
+# See cidaemon for per-repository configuration details.
+#
+# To use this hook, add it as the post-receive hook, make it
+# executable, and set its configuration options.
+#
+
+local $ENV{PATH} = '/opt/git/bin';
+
+use strict;
+use warnings;
+use File::Spec;
+use Storable qw(retrieve nstore);
+use Fcntl ':flock';
+
+my $git_dir = File::Spec->rel2abs($ENV{GIT_DIR});
+my $queue_name = `git config --get builder.queue`;chop $queue_name;
+$queue_name =~ m,^([^\s]+)$,; $queue_name = $1; # untaint
+unless ($queue_name) {
+	1 while <STDIN>;
+	print STDERR "\nerror: builder.queue not set.  Not enqueing.\n\n";
+	exit;
+}
+my $queue_lock = "$queue_name.lock";
+
+my @skip;
+open S, "git config --get-all builder.skip|";
+while (<S>) {
+	chop;
+	push @skip, $_;
+}
+close S;
+
+my @new_branch_base;
+open S, "git config --get-all builder.newBranchBase|";
+while (<S>) {
+	chop;
+	push @new_branch_base, $_;
+}
+close S;
+
+sub skip ($)
+{
+	local $_ = shift;
+	foreach my $p (@skip) {
+		return 1 if /^$p/;
+	}
+	0;
+}
+
+open LOCK, ">$queue_lock" or die "Can't open $queue_lock: $!";
+flock LOCK, LOCK_EX;
+
+my $queue = -f $queue_name ? retrieve $queue_name : [];
+my %existing;
+foreach my $r (@$queue) {
+	my ($gd, $ref) = @$r;
+	$existing{$gd}{$ref} = $r;
+}
+
+my @new_branch_commits;
+my $loaded_new_branch_commits = 0;
+
+while (<STDIN>) {
+	chop;
+	my ($old, $new, $ref) = split / /, $_, 3;
+
+	next if $old eq $new;
+	next if $new =~ /^0{40}$/;
+	next if skip $ref;
+
+	my $r = $existing{$git_dir}{$ref};
+	if ($r) {
+		$r->[3] = $new;
+	} else {
+		if ($old =~ /^0{40}$/) {
+			if (!$loaded_new_branch_commits && @new_branch_base) {
+				open M,'-|','git','show-ref',@new_branch_base;
+				while (<M>) {
+					($_) = split / /, $_;
+					push @new_branch_commits, $_;
+				}
+				close M;
+				$loaded_new_branch_commits = 1;
+			}
+			$old = [@new_branch_commits];
+		} else {
+			$old = [$old];
+		}
+
+		$r = [$git_dir, $ref, $old, $new];
+		$existing{$git_dir}{$ref} = $r;
+		push @$queue, $r;
+	}
+}
+nstore $queue, $queue_name;
+
+flock LOCK, LOCK_UN;
+close LOCK;
diff --git a/contrib/emacs/Makefile b/contrib/emacs/Makefile
index 350846d..8554e39 100644
--- a/contrib/emacs/Makefile
+++ b/contrib/emacs/Makefile
@@ -2,7 +2,7 @@
 
 EMACS = emacs
 
-ELC = git.elc vc-git.elc
+ELC = git.elc vc-git.elc git-blame.elc
 INSTALL ?= install
 INSTALL_ELC = $(INSTALL) -m 644
 prefix ?= $(HOME)
@@ -15,6 +15,6 @@
 	$(INSTALL_ELC) $(ELC) $(emacsdir)
 
 %.elc: %.el
-	$(EMACS) --batch --eval '(byte-compile-file "$<")'
+	$(EMACS) -batch -f batch-byte-compile $<
 
 clean:; rm -f $(ELC)
diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el
index db87a37..2f9995e 100644
--- a/contrib/emacs/git.el
+++ b/contrib/emacs/git.el
@@ -1,6 +1,6 @@
 ;;; git.el --- A user interface for git
 
-;; Copyright (C) 2005, 2006 Alexandre Julliard <julliard@winehq.org>
+;; Copyright (C) 2005, 2006, 2007 Alexandre Julliard <julliard@winehq.org>
 
 ;; Version: 1.0
 
@@ -213,6 +213,23 @@
     (error "Failed to run \"git %s\":\n%s" (mapconcat (lambda (x) x) args " ") (buffer-string)))
   (message "Running git %s...done" (car args)))
 
+(defun git-run-hook (hook env &rest args)
+  "Run a git hook and display its output if any."
+  (let ((dir default-directory)
+        (hook-name (expand-file-name (concat ".git/hooks/" hook))))
+    (or (not (file-executable-p hook-name))
+        (let (status (buffer (get-buffer-create "*Git Hook Output*")))
+          (with-current-buffer buffer
+            (erase-buffer)
+            (cd dir)
+            (setq status
+                  (if env
+                      (apply #'call-process "env" nil (list buffer t) nil
+                             (append (git-get-env-strings env) (list hook-name) args))
+                    (apply #'call-process hook-name nil (list buffer t) nil args))))
+          (display-message-or-buffer buffer)
+          (eq 0 status)))))
+
 (defun git-get-string-sha1 (string)
   "Read a SHA1 from the specified string."
   (and string
@@ -246,6 +263,16 @@
              (locale-charset-to-coding-system repo-config))
       'utf-8)))
 
+(defun git-get-logoutput-coding-system ()
+  "Return the coding system used for git-log output."
+  (let ((repo-config (or (git-config "i18n.logoutputencoding")
+                         (git-config "i18n.commitencoding"))))
+    (or git-commits-coding-system
+        (and repo-config
+             (fboundp 'locale-charset-to-coding-system)
+             (locale-charset-to-coding-system repo-config))
+      'utf-8)))
+
 (defun git-escape-file-name (name)
   "Escape a file name if necessary."
   (if (string-match "[\n\t\"\\]" name)
@@ -389,6 +416,14 @@
           (push (match-string 0) heads))))
     (nreverse heads)))
 
+(defun git-get-commit-description (commit)
+  "Get a one-line description of COMMIT."
+  (let ((coding-system-for-read (git-get-logoutput-coding-system)))
+    (let ((descr (git-call-process-env-string nil "log" "--max-count=1" "--pretty=oneline" commit)))
+      (if (and descr (string-match "\\`\\([0-9a-f]\\{40\\}\\) *\\(.*\\)$" descr))
+          (concat (substring (match-string 1 descr) 0 10) " - " (match-string 2 descr))
+        descr))))
+
 ;;;; File info structure
 ;;;; ------------------------------------------------------------
 
@@ -556,7 +591,7 @@
   "Refresh the ewoc header and footer."
   (let ((branch (git-symbolic-ref "HEAD"))
         (head (if (git-empty-db-p) "Nothing committed yet"
-                (substring (git-rev-parse "HEAD") 0 10)))
+                (git-get-commit-description "HEAD")))
         (merge-heads (git-get-merge-heads)))
     (ewoc-set-hf status
                  (format "Directory:  %s\nBranch:     %s\nHead:       %s%s\n"
@@ -567,7 +602,7 @@
                          head
                          (if merge-heads
                              (concat "\nMerging:    "
-                                     (mapconcat (lambda (str) (substring str 0 10)) merge-heads " "))
+                                     (mapconcat (lambda (str) (git-get-commit-description str)) merge-heads "\n            "))
                            ""))
                  (if (ewoc-nth status 0) "" "    No changes."))))
 
@@ -590,6 +625,20 @@
     (when modified
       (apply #'git-run-command nil env "update-index" "--" (git-get-filenames modified)))))
 
+(defun git-run-pre-commit-hook ()
+  "Run the pre-commit hook if any."
+  (unless git-status (error "Not in git-status buffer."))
+  (let ((files (git-marked-files-state 'added 'deleted 'modified)))
+    (or (not files)
+        (not (file-executable-p ".git/hooks/pre-commit"))
+        (let ((index-file (make-temp-file "gitidx")))
+          (unwind-protect
+            (let ((head-tree (unless (git-empty-db-p) (git-rev-parse "HEAD^{tree}"))))
+              (git-read-tree head-tree index-file)
+              (git-update-index index-file files)
+              (git-run-hook "pre-commit" `(("GIT_INDEX_FILE" . ,index-file))))
+          (delete-file index-file))))))
+
 (defun git-do-commit ()
   "Perform the actual commit using the current buffer as log message."
   (interactive)
@@ -622,7 +671,8 @@
                               (git-run-command nil nil "rerere"))
                             (git-refresh-files)
                             (git-refresh-ewoc-hf git-status)
-                            (message "Committed %s." commit))
+                            (message "Committed %s." commit)
+                            (git-run-hook "post-commit" nil))
                         (message "Commit aborted."))))
                 (message "No files to commit.")))
           (delete-file index-file))))))
@@ -944,28 +994,29 @@
   "Commit the marked file(s), asking for a commit message."
   (interactive)
   (unless git-status (error "Not in git-status buffer."))
-  (let ((buffer (get-buffer-create "*git-commit*"))
-        (coding-system (git-get-commits-coding-system))
-        author-name author-email subject date)
-    (when (eq 0 (buffer-size buffer))
-      (when (file-readable-p ".dotest/info")
-        (with-temp-buffer
-          (insert-file-contents ".dotest/info")
-          (goto-char (point-min))
-          (when (re-search-forward "^Author: \\(.*\\)\nEmail: \\(.*\\)$" nil t)
-            (setq author-name (match-string 1))
-            (setq author-email (match-string 2)))
-          (goto-char (point-min))
-          (when (re-search-forward "^Subject: \\(.*\\)$" nil t)
-            (setq subject (match-string 1)))
-          (goto-char (point-min))
-          (when (re-search-forward "^Date: \\(.*\\)$" nil t)
-            (setq date (match-string 1)))))
-      (git-setup-log-buffer buffer author-name author-email subject date))
-    (log-edit #'git-do-commit nil #'git-log-edit-files buffer)
-    (setq font-lock-keywords (font-lock-compile-keywords git-log-edit-font-lock-keywords))
-    (setq buffer-file-coding-system coding-system)
-    (re-search-forward (regexp-quote (concat git-log-msg-separator "\n")) nil t)))
+  (when (git-run-pre-commit-hook)
+    (let ((buffer (get-buffer-create "*git-commit*"))
+          (coding-system (git-get-commits-coding-system))
+          author-name author-email subject date)
+      (when (eq 0 (buffer-size buffer))
+        (when (file-readable-p ".dotest/info")
+          (with-temp-buffer
+            (insert-file-contents ".dotest/info")
+            (goto-char (point-min))
+            (when (re-search-forward "^Author: \\(.*\\)\nEmail: \\(.*\\)$" nil t)
+              (setq author-name (match-string 1))
+              (setq author-email (match-string 2)))
+            (goto-char (point-min))
+            (when (re-search-forward "^Subject: \\(.*\\)$" nil t)
+              (setq subject (match-string 1)))
+            (goto-char (point-min))
+            (when (re-search-forward "^Date: \\(.*\\)$" nil t)
+              (setq date (match-string 1)))))
+        (git-setup-log-buffer buffer author-name author-email subject date))
+      (log-edit #'git-do-commit nil #'git-log-edit-files buffer)
+      (setq font-lock-keywords (font-lock-compile-keywords git-log-edit-font-lock-keywords))
+      (setq buffer-file-coding-system coding-system)
+      (re-search-forward (regexp-quote (concat git-log-msg-separator "\n")) nil t))))
 
 (defun git-find-file ()
   "Visit the current file in its own buffer."
diff --git a/git-gc.sh b/contrib/examples/git-gc.sh
similarity index 100%
rename from git-gc.sh
rename to contrib/examples/git-gc.sh
diff --git a/git-resolve.sh b/contrib/examples/git-resolve.sh
similarity index 100%
rename from git-resolve.sh
rename to contrib/examples/git-resolve.sh
diff --git a/contrib/hooks/post-receieve-email b/contrib/hooks/post-receieve-email
new file mode 100644
index 0000000..6516015
--- /dev/null
+++ b/contrib/hooks/post-receieve-email
@@ -0,0 +1,588 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Andy Parkins
+#
+# An example hook script to mail out commit update information.  This hook sends emails
+# listing new revisions to the repository introduced by the change being reported.  The
+# rule is that (for branch updates) each commit will appear on one email and one email
+# only.
+#
+# This hook is stored in the contrib/hooks directory.  Your distribution will have put
+# this somewhere standard.  You should make this script executable then link to it in
+# the repository you would like to use it in.  For example, on debian the hook is stored
+# in /usr/share/doc/git-core/contrib/hooks/post-receive-email:
+#
+#  chmod a+x post-receive-email
+#  cd /path/to/your/repository.git
+#  ln -sf /usr/share/doc/git-core/contrib/hooks/post-receive-email hooks/post-receive
+#
+# This hook script assumes it is enabled on the central repository of a project, with
+# all users pushing only to it and not between each other.  It will still work if you
+# don't operate in that style, but it would become possible for the email to be from
+# someone other than the person doing the push.
+#
+# Config
+# ------
+# hooks.mailinglist
+#   This is the list that all pushes will go to; leave it blank to not send
+#   emails for every ref update.
+# hooks.announcelist
+#   This is the list that all pushes of annotated tags will go to.  Leave it
+#   blank to default to the mailinglist field.  The announce emails lists the
+#   short log summary of the changes since the last annotated tag.
+# hook.envelopesender
+#   If set then the -f option is passed to sendmail to allow the envelope sender
+#   address to be set
+#
+# Notes
+# -----
+# All emails have their subjects prefixed with "[SCM]" to aid filtering.
+# All emails include the headers "X-Git-Refname", "X-Git-Oldrev",
+# "X-Git-Newrev", and "X-Git-Reftype" to enable fine tuned filtering and
+# give information for debugging.
+#
+
+# ---------------------------- Functions
+
+#
+# Top level email generation function.  This decides what type of update
+# this is and calls the appropriate body-generation routine after outputting
+# the common header
+#
+# Note this function doesn't actually generate any email output, that is taken
+# care of by the functions it calls:
+#  - generate_email_header
+#  - generate_create_XXXX_email
+#  - generate_update_XXXX_email
+#  - generate_delete_XXXX_email
+#  - generate_email_footer
+#
+generate_email()
+{
+	# --- Arguments
+	oldrev=$(git rev-parse $1)
+	newrev=$(git rev-parse $2)
+	refname="$3"
+
+	# --- Interpret
+	# 0000->1234 (create)
+	# 1234->2345 (update)
+	# 2345->0000 (delete)
+	if expr "$oldrev" : '0*$' >/dev/null
+	then
+		change_type="create"
+	else
+		if expr "$newrev" : '0*$' >/dev/null
+		then
+			change_type="delete"
+		else
+			change_type="update"
+		fi
+	fi
+
+	# --- Get the revision types
+	newrev_type=$(git cat-file -t $newrev 2> /dev/null)
+	oldrev_type=$(git cat-file -t "$oldrev" 2> /dev/null)
+	case "$change_type" in
+	create|update)
+		rev="$newrev"
+		rev_type="$newrev_type"
+		;;
+	delete)
+		rev="$oldrev"
+		rev_type="$oldrev_type"
+		;;
+	esac
+
+	# The revision type tells us what type the commit is, combined with
+	# the location of the ref we can decide between
+	#  - working branch
+	#  - tracking branch
+	#  - unannoted tag
+	#  - annotated tag
+	case "$refname","$rev_type" in
+		refs/tags/*,commit)
+			# un-annotated tag
+			refname_type="tag"
+			short_refname=${refname##refs/tags/}
+			;;
+		refs/tags/*,tag)
+			# annotated tag
+			refname_type="annotated tag"
+			short_refname=${refname##refs/tags/}
+			# change recipients
+			if [ -n "$announcerecipients" ]; then
+				recipients="$announcerecipients"
+			fi
+			;;
+		refs/heads/*,commit)
+			# branch
+			refname_type="branch"
+			short_refname=${refname##refs/heads/}
+			;;
+		refs/remotes/*,commit)
+			# tracking branch
+			refname_type="tracking branch"
+			short_refname=${refname##refs/remotes/}
+			echo >&2 "*** Push-update of tracking branch, $refname"
+			echo >&2 "***  - no email generated."
+			exit 0
+			;;
+		*)
+			# Anything else (is there anything else?)
+			echo >&2 "*** Unknown type of update to $refname ($rev_type)"
+			echo >&2 "***  - no email generated"
+			exit 1
+			;;
+	esac
+
+	# Check if we've got anyone to send to
+	if [ -z "$recipients" ]; then
+		echo >&2 "*** hooks.recipients is not set so no email will be sent"
+		echo >&2 "*** for $refname update $oldrev->$newrev"
+		exit 0
+	fi
+
+	# Email parameters
+	# The committer will be obtained from the latest existing rev; so
+	# for a deletion it will be the oldrev, for the others, then newrev
+	committer=$(git show --pretty=full -s $rev | sed -ne "s/^Commit: //p" |
+		sed -ne 's/\(.*\) </"\1" </p')
+	# The email subject will contain the best description of the ref
+	# that we can build from the parameters
+	describe=$(git describe $rev 2>/dev/null)
+	if [ -z "$describe" ]; then
+		describe=$rev
+	fi
+
+	generate_email_header
+
+	# Call the correct body generation function
+	fn_name=general
+	case "$refname_type" in
+	"tracking branch"|branch)
+		fn_name=branch
+		;;
+	"annotated tag")
+		fn_name=atag
+		;;
+	esac
+	generate_${change_type}_${fn_name}_email
+
+	generate_email_footer
+}
+
+generate_email_header()
+{
+	# --- Email (all stdout will be the email)
+	# Generate header
+	cat <<-EOF
+	From: $committer
+	To: $recipients
+	Subject: ${EMAILPREFIX}$projectdesc $refname_type, $short_refname, ${change_type}d. $describe
+	X-Git-Refname: $refname
+	X-Git-Reftype: $refname_type
+	X-Git-Oldrev: $oldrev
+	X-Git-Newrev: $newrev
+
+	This is an automated email from the git hooks/post-receive script. It was
+	generated because a ref change was pushed to the repository containing
+	the project "$projectdesc".
+
+	The $refname_type, $short_refname has been ${change_type}d
+	EOF
+}
+
+generate_email_footer()
+{
+	cat <<-EOF
+
+
+	hooks/post-receive
+	-- 
+	$projectdesc
+	EOF
+}
+
+# --------------- Branches
+
+#
+# Called for the creation of a branch
+#
+generate_create_branch_email()
+{
+	# This is a new branch and so oldrev is not valid
+	echo "        at  $newrev ($newrev_type)"
+	echo ""
+
+	echo $LOGBEGIN
+	# This shows all log entries that are not already covered by
+	# another ref - i.e. commits that are now accessible from this
+	# ref that were previously not accessible (see generate_update_branch_email
+	# for the explanation of this command)
+	git rev-parse --not --branches | grep -v $(git rev-parse $refname) |
+	git rev-list --pretty --stdin $newrev
+	echo $LOGEND
+}
+
+#
+# Called for the change of a pre-existing branch
+#
+generate_update_branch_email()
+{
+	# Consider this:
+	#   1 --- 2 --- O --- X --- 3 --- 4 --- N
+	#
+	# O is $oldrev for $refname
+	# N is $newrev for $refname
+	# X is a revision pointed to by some other ref, for which we may
+	#   assume that an email has already been generated.
+	# In this case we want to issue an email containing only revisions
+	# 3, 4, and N.  Given (almost) by
+	#
+	#  git-rev-list N ^O --not --all
+	#
+	# The reason for the "almost", is that the "--not --all" will take
+	# precedence over the "N", and effectively will translate to
+	#
+	#  git-rev-list N ^O ^X ^N
+	#
+	# So, we need to build up the list more carefully.  git-rev-parse will
+	# generate a list of revs that may be fed into git-rev-list.  We can get
+	# it to make the "--not --all" part and then filter out the "^N" with:
+	#
+	#  git-rev-parse --not --all | grep -v N
+	#
+	# Then, using the --stdin switch to git-rev-list we have effectively
+	# manufactured
+	#
+	#  git-rev-list N ^O ^X
+	#
+	# This leaves a problem when someone else updates the repository
+	# while this script is running.  Their new value of the ref we're working
+	# on would be included in the "--not --all" output; and as our $newrev
+	# would be an ancestor of that commit, it would exclude all of our
+	# commits.  What we really want is to exclude the current value of
+	# $refname from the --not list, rather than N itself.  So:
+	#
+	#  git-rev-parse --not --all | grep -v $(git-rev-parse $refname)
+	#
+	# Get's us to something pretty safe (apart from the small time between
+	# refname being read, and git-rev-parse running - for that, I give up)
+	#
+	#
+	# Next problem, consider this:
+	#   * --- B --- * --- O ($oldrev)
+	#          \
+	#           * --- X --- * --- N ($newrev)
+	#
+	# That is to say, there is no guarantee that oldrev is a strict subset of
+	# newrev (it would have required a --force, but that's allowed).  So, we
+	# can't simply say rev-list $oldrev..$newrev.  Instead we find the common
+	# base of the two revs and list from there.
+	#
+	# As above, we need to take into account the presence of X; if another
+	# branch is already in the repository and points at some of the revisions
+	# that we are about to output - we don't want them.  The solution is as
+	# before: git-rev-parse output filtered.
+	#
+	# Finally, tags:
+	#   1 --- 2 --- O --- T --- 3 --- 4 --- N
+	#
+	# Tags pushed into the repository generate nice shortlog emails that
+	# summarise the commits between them and the previous tag.  However,
+	# those emails don't include the full commit messages that we output
+	# for a branch update.  Therefore we still want to output revisions
+	# that have been output on a tag email.
+	#
+	# Luckily, git-rev-parse includes just the tool.  Instead of using "--all"
+	# we use "--branches"; this has the added benefit that "remotes/" will
+	# be ignored as well.
+
+	# List all of the revisions that were removed by this update, in a fast forward
+	# update, this list will be empty, because rev-list O ^N is empty.  For a non
+	# fast forward, O ^N is the list of removed revisions
+	fastforward=""
+	rev=""
+	for rev in $(git rev-list $newrev..$oldrev)
+	do
+		revtype=$(git cat-file -t "$rev")
+		echo "  discards  $rev ($revtype)"
+	done
+	if [ -z "$rev" ]; then
+		fast_forward=1
+	fi
+
+	# List all the revisions from baserev to newrev in a kind of
+	# "table-of-contents"; note this list can include revisions that have
+	# already had notification emails and is present to show the full detail
+	# of the change from rolling back the old revision to the base revision and
+	# then forward to the new revision
+	for rev in $(git rev-list $oldrev..$newrev)
+	do
+		revtype=$(git cat-file -t "$rev")
+		echo "       via  $rev ($revtype)"
+	done
+
+	if [ -z "$fastforward" ]; then
+		echo "      from  $oldrev ($oldrev_type)"
+	else
+		echo ""
+		echo "This update added new revisions after undoing old revisions.  That is to"
+		echo "say, the old revision is not a strict subset of the new revision.  This"
+		echo "situation occurs when you --force push a change and generate a"
+		echo "repository containing something like this:"
+		echo ""
+		echo " * -- * -- B -- O -- O -- O ($oldrev)"
+		echo "            \\"
+		echo "             N -- N -- N ($newrev)"
+		echo ""
+		echo "When this happens we assume that you've already had alert emails for all"
+		echo "of the O revisions, and so we here report only the revisions in the N"
+		echo "branch from the common base, B."
+	fi
+
+	echo ""
+	echo "Those revisions listed above that are new to this repository have"
+	echo "not appeared on any other notification email; so we list those"
+	echo "revisions in full, below."
+
+	echo ""
+	echo $LOGBEGIN
+	git rev-parse --not --branches | grep -v $(git rev-parse $refname) |
+	git rev-list --pretty --stdin $oldrev..$newrev
+
+	# XXX: Need a way of detecting whether git rev-list actually outputted
+	# anything, so that we can issue a "no new revisions added by this
+	# update" message
+
+	echo $LOGEND
+
+	# The diffstat is shown from the old revision to the new revision.  This
+	# is to show the truth of what happened in this change.  There's no point
+	# showing the stat from the base to the new revision because the base
+	# is effectively a random revision at this point - the user will be
+	# interested in what this revision changed - including the undoing of
+	# previous revisions in the case of non-fast forward updates.
+	echo ""
+	echo "Summary of changes:"
+	git diff-tree --stat --summary --find-copies-harder $oldrev..$newrev
+}
+
+#
+# Called for the deletion of a branch
+#
+generate_delete_branch_email()
+{
+	echo "       was  $oldrev"
+	echo ""
+	echo $LOGEND
+	git show -s --pretty=oneline $oldrev
+	echo $LOGEND
+}
+
+# --------------- Annotated tags
+
+#
+# Called for the creation of an annotated tag
+#
+generate_create_atag_email()
+{
+	echo "        at  $newrev ($newrev_type)"
+
+	generate_atag_email
+}
+
+#
+# Called for the update of an annotated tag (this is probably a rare event
+# and may not even be allowed)
+#
+generate_update_atag_email()
+{
+	echo "        to  $newrev ($newrev_type)"
+	echo "      from  $oldrev (which is now obsolete)"
+
+	generate_atag_email
+}
+
+#
+# Called when an annotated tag is created or changed
+#
+generate_atag_email()
+{
+	# Use git-for-each-ref to pull out the individual fields from the tag
+	eval $(git for-each-ref --shell --format='
+	tagobject=%(*objectname)
+	tagtype=%(*objecttype)
+	tagger=%(taggername)
+	tagged=%(taggerdate)' $refname
+	)
+
+	echo "   tagging  $tagobject ($tagtype)"
+	case "$tagtype" in
+	commit)
+		# If the tagged object is a commit, then we assume this is a
+		# release, and so we calculate which tag this tag is replacing
+		prevtag=$(git describe --abbrev=0 $newrev^ 2>/dev/null)
+
+		if [ -n "$prevtag" ]; then
+			echo "  replaces  $prevtag"
+		fi
+		;;
+	*)
+		echo "    length  $(git cat-file -s $tagobject) bytes"
+		;;
+	esac
+	echo " tagged by  $tagger"
+	echo "        on  $tagged"
+
+	echo ""
+	echo $LOGBEGIN
+
+	# Show the content of the tag message; this might contain a change log
+	# or release notes so is worth displaying.
+	git cat-file tag $newrev | sed -e '1,/^$/d'
+
+	echo ""
+	case "$tagtype" in
+	commit)
+		# Only commit tags make sense to have rev-list operations performed
+		# on them
+		if [ -n "$prevtag" ]; then
+			# Show changes since the previous release
+			git rev-list --pretty=short "$prevtag..$newrev" | git shortlog
+		else
+			# No previous tag, show all the changes since time began
+			git rev-list --pretty=short $newrev | git shortlog
+		fi
+		;;
+	*)
+		# XXX: Is there anything useful we can do for non-commit objects?
+		;;
+	esac
+
+	echo $LOGEND
+}
+
+#
+# Called for the deletion of an annotated tag
+#
+generate_delete_atag_email()
+{
+	echo "       was  $oldrev"
+	echo ""
+	echo $LOGEND
+	git show -s --pretty=oneline $oldrev
+	echo $LOGEND
+}
+
+# --------------- General references
+
+#
+# Called when any other type of reference is created (most likely a
+# non-annotated tag)
+#
+generate_create_general_email()
+{
+	echo "        at  $newrev ($newrev_type)"
+
+	generate_general_email
+}
+
+#
+# Called when any other type of reference is updated (most likely a
+# non-annotated tag)
+#
+generate_update_general_email()
+{
+	echo "        to  $newrev ($newrev_type)"
+	echo "      from  $oldrev"
+
+	generate_general_email
+}
+
+#
+# Called for creation or update of any other type of reference
+#
+generate_general_email()
+{
+	# Unannotated tags are more about marking a point than releasing a version;
+	# therefore we don't do the shortlog summary that we do for annotated tags
+	# above - we simply show that the point has been marked, and print the log
+	# message for the marked point for reference purposes
+	#
+	# Note this section also catches any other reference type (although there
+	# aren't any) and deals with them in the same way.
+
+	echo ""
+	if [ "$newrev_type" = "commit" ]; then
+		echo $LOGBEGIN
+		git show --no-color --root -s $newrev
+		echo $LOGEND
+	else
+		# What can we do here?  The tag marks an object that is not a commit,
+		# so there is no log for us to display.  It's probably not wise to
+		# output git-cat-file as it could be a binary blob.  We'll just say how
+		# big it is
+		echo "$newrev is a $newrev_type, and is $(git cat-file -s $newrev) bytes long."
+	fi
+}
+
+#
+# Called for the deletion of any other type of reference
+#
+generate_delete_general_email()
+{
+	echo "       was  $oldrev"
+	echo ""
+	echo $LOGEND
+	git show -s --pretty=oneline $oldrev
+	echo $LOGEND
+}
+
+# ---------------------------- main()
+
+# --- Constants
+EMAILPREFIX="[SCM] "
+LOGBEGIN="- Log -----------------------------------------------------------------"
+LOGEND="-----------------------------------------------------------------------"
+
+# --- Config
+# Set GIT_DIR either from the working directory, or from the environment
+# variable.
+GIT_DIR=$(git rev-parse --git-dir 2>/dev/null)
+if [ -z "$GIT_DIR" ]; then
+	echo >&2 "fatal: post-receive: GIT_DIR not set"
+	exit 1
+fi
+
+projectdesc=$(sed -e '1p' "$GIT_DIR/description")
+# Check if the description is unchanged from it's default, and shorten it to a
+# more manageable length if it is
+if expr "$projectdesc" : "Unnamed repository.*$" >/dev/null
+then
+	projectdesc="UNNAMED PROJECT"
+fi
+
+recipients=$(git repo-config hooks.mailinglist)
+announcerecipients=$(git repo-config hooks.announcelist)
+envelopesender=$(git-repo-config hooks.envelopesender)
+
+# --- Main loop
+# Allow dual mode: run from the command line just like the update hook, or if
+# no arguments are given then run as a hook script
+if [ -n "$1" -a -n "$2" -a -n "$3" ]; then
+	# Output to the terminal in command line mode - if someone wanted to
+	# resend an email; they could redirect the output to sendmail themselves
+	PAGER= generate_email $2 $3 $1
+else
+	if [ -n "$envelopesender" ]; then
+		envelopesender="-f '$envelopesender'"
+	fi
+
+	while read oldrev newrev refname
+	do
+		generate_email $oldrev $newrev $refname |
+		/usr/sbin/sendmail -t $envelopesender
+	done
+fi
diff --git a/contrib/workdir/git-new-workdir b/contrib/workdir/git-new-workdir
new file mode 100755
index 0000000..9877b98
--- /dev/null
+++ b/contrib/workdir/git-new-workdir
@@ -0,0 +1,57 @@
+#!/bin/sh
+
+usage () {
+	echo "usage:" $@
+	exit 127
+}
+
+die () {
+	echo $@
+	exit 128
+}
+
+if test $# -lt 2 || test $# -gt 3
+then
+	usage "$0 <repository> <new_workdir> [<branch>]"
+fi
+
+orig_git=$1
+new_workdir=$2
+branch=$3
+
+# want to make sure that what is pointed to has a .git directory ...
+test -d "$orig_git/.git" || die "\"$orig_git\" is not a git repository!"
+
+# don't link to a workdir
+if test -L "$orig_git/.git/config"
+then
+	die "\"$orig_git\" is a working directory only, please specify" \
+		"a complete repository."
+fi
+
+# make sure the the links use full paths
+orig_git=$(cd "$orig_git"; pwd)
+
+# create the workdir
+mkdir -p "$new_workdir/.git" || die "unable to create \"$new_workdir\"!"
+
+# create the links to the original repo.  explictly exclude index, HEAD and
+# logs/HEAD from the list since they are purely related to the current working
+# directory, and should not be shared.
+for x in config refs logs/refs objects info hooks packed-refs remotes rr-cache
+do
+	case $x in
+	*/*)
+		mkdir -p "$(dirname "$new_workdir/.git/$x")"
+		;;
+	esac
+	ln -s "$orig_git/.git/$x" "$new_workdir/.git/$x"
+done
+
+# now setup the workdir
+cd "$new_workdir"
+# copy the HEAD from the original repository as a default branch
+cp "$orig_git/.git/HEAD" .git/HEAD
+# checkout the branch (either the same as HEAD from the original repository, or
+# the one that was asked for)
+git checkout -f $branch
diff --git a/convert-objects.c b/convert-objects.c
index a630132..4809f91 100644
--- a/convert-objects.c
+++ b/convert-objects.c
@@ -132,7 +132,7 @@
 	unsigned long orig_size = size;
 
 	while (size) {
-		int len = 1+strlen(buffer);
+		size_t len = 1+strlen(buffer);
 
 		convert_binary_sha1((char *) buffer + len);
 
@@ -284,27 +284,27 @@
 static struct entry * convert_entry(unsigned char *sha1)
 {
 	struct entry *entry = lookup_entry(sha1);
-	char type[20];
+	enum object_type type;
 	void *buffer, *data;
 	unsigned long size;
 
 	if (entry->converted)
 		return entry;
-	data = read_sha1_file(sha1, type, &size);
+	data = read_sha1_file(sha1, &type, &size);
 	if (!data)
 		die("unable to read object %s", sha1_to_hex(sha1));
 
 	buffer = xmalloc(size);
 	memcpy(buffer, data, size);
 
-	if (!strcmp(type, blob_type)) {
+	if (type == OBJ_BLOB) {
 		write_sha1_file(buffer, size, blob_type, entry->new_sha1);
-	} else if (!strcmp(type, tree_type))
+	} else if (type == OBJ_TREE)
 		convert_tree(buffer, size, entry->new_sha1);
-	else if (!strcmp(type, commit_type))
+	else if (type == OBJ_COMMIT)
 		convert_commit(buffer, size, entry->new_sha1);
 	else
-		die("unknown object type '%s' in %s", type, sha1_to_hex(sha1));
+		die("unknown object type %d in %s", type, sha1_to_hex(sha1));
 	entry->converted = 1;
 	free(buffer);
 	free(data);
diff --git a/convert.c b/convert.c
new file mode 100644
index 0000000..898bfe3
--- /dev/null
+++ b/convert.c
@@ -0,0 +1,186 @@
+#include "cache.h"
+/*
+ * convert.c - convert a file when checking it out and checking it in.
+ *
+ * This should use the pathname to decide on whether it wants to do some
+ * more interesting conversions (automatic gzip/unzip, general format
+ * conversions etc etc), but by default it just does automatic CRLF<->LF
+ * translation when the "auto_crlf" option is set.
+ */
+
+struct text_stat {
+	/* CR, LF and CRLF counts */
+	unsigned cr, lf, crlf;
+
+	/* These are just approximations! */
+	unsigned printable, nonprintable;
+};
+
+static void gather_stats(const char *buf, unsigned long size, struct text_stat *stats)
+{
+	unsigned long i;
+
+	memset(stats, 0, sizeof(*stats));
+
+	for (i = 0; i < size; i++) {
+		unsigned char c = buf[i];
+		if (c == '\r') {
+			stats->cr++;
+			if (i+1 < size && buf[i+1] == '\n')
+				stats->crlf++;
+			continue;
+		}
+		if (c == '\n') {
+			stats->lf++;
+			continue;
+		}
+		if (c == 127)
+			/* DEL */
+			stats->nonprintable++;
+		else if (c < 32) {
+			switch (c) {
+				/* BS, HT, ESC and FF */
+			case '\b': case '\t': case '\033': case '\014':
+				stats->printable++;
+				break;
+			default:
+				stats->nonprintable++;
+			}
+		}
+		else
+			stats->printable++;
+	}
+}
+
+/*
+ * The same heuristics as diff.c::mmfile_is_binary()
+ */
+static int is_binary(unsigned long size, struct text_stat *stats)
+{
+
+	if ((stats->printable >> 7) < stats->nonprintable)
+		return 1;
+	/*
+	 * Other heuristics? Average line length might be relevant,
+	 * as might LF vs CR vs CRLF counts..
+	 *
+	 * NOTE! It might be normal to have a low ratio of CRLF to LF
+	 * (somebody starts with a LF-only file and edits it with an editor
+	 * that adds CRLF only to lines that are added..). But do  we
+	 * want to support CR-only? Probably not.
+	 */
+	return 0;
+}
+
+int convert_to_git(const char *path, char **bufp, unsigned long *sizep)
+{
+	char *buffer, *nbuf;
+	unsigned long size, nsize;
+	struct text_stat stats;
+
+	/*
+	 * FIXME! Other pluggable conversions should go here,
+	 * based on filename patterns. Right now we just do the
+	 * stupid auto-CRLF one.
+	 */
+	if (!auto_crlf)
+		return 0;
+
+	size = *sizep;
+	if (!size)
+		return 0;
+	buffer = *bufp;
+
+	gather_stats(buffer, size, &stats);
+
+	/* No CR? Nothing to convert, regardless. */
+	if (!stats.cr)
+		return 0;
+
+	/*
+	 * We're currently not going to even try to convert stuff
+	 * that has bare CR characters. Does anybody do that crazy
+	 * stuff?
+	 */
+	if (stats.cr != stats.crlf)
+		return 0;
+
+	/*
+	 * And add some heuristics for binary vs text, of course...
+	 */
+	if (is_binary(size, &stats))
+		return 0;
+
+	/*
+	 * Ok, allocate a new buffer, fill it in, and return true
+	 * to let the caller know that we switched buffers on it.
+	 */
+	nsize = size - stats.crlf;
+	nbuf = xmalloc(nsize);
+	*bufp = nbuf;
+	*sizep = nsize;
+	do {
+		unsigned char c = *buffer++;
+		if (c != '\r')
+			*nbuf++ = c;
+	} while (--size);
+
+	return 1;
+}
+
+int convert_to_working_tree(const char *path, char **bufp, unsigned long *sizep)
+{
+	char *buffer, *nbuf;
+	unsigned long size, nsize;
+	struct text_stat stats;
+	unsigned char last;
+
+	/*
+	 * FIXME! Other pluggable conversions should go here,
+	 * based on filename patterns. Right now we just do the
+	 * stupid auto-CRLF one.
+	 */
+	if (auto_crlf <= 0)
+		return 0;
+
+	size = *sizep;
+	if (!size)
+		return 0;
+	buffer = *bufp;
+
+	gather_stats(buffer, size, &stats);
+
+	/* No LF? Nothing to convert, regardless. */
+	if (!stats.lf)
+		return 0;
+
+	/* Was it already in CRLF format? */
+	if (stats.lf == stats.crlf)
+		return 0;
+
+	/* If we have any bare CR characters, we're not going to touch it */
+	if (stats.cr != stats.crlf)
+		return 0;
+
+	if (is_binary(size, &stats))
+		return 0;
+
+	/*
+	 * Ok, allocate a new buffer, fill it in, and return true
+	 * to let the caller know that we switched buffers on it.
+	 */
+	nsize = size + stats.lf - stats.crlf;
+	nbuf = xmalloc(nsize);
+	*bufp = nbuf;
+	*sizep = nsize;
+	last = 0;
+	do {
+		unsigned char c = *buffer++;
+		if (c == '\n' && last != '\r')
+			*nbuf++ = '\r';
+		*nbuf++ = c;
+		last = c;
+	} while (--size);
+
+	return 1;
+}
diff --git a/daemon.c b/daemon.c
index 66f8d6f..e74ecac 100644
--- a/daemon.c
+++ b/daemon.c
@@ -286,7 +286,7 @@
 
 static int git_daemon_config(const char *var, const char *value)
 {
-	if (!strncmp(var, "daemon.", 7) &&
+	if (!prefixcmp(var, "daemon.") &&
 	    !strcmp(var + 7, service_looking_at->config_name)) {
 		service_enabled = git_config_bool(var, value);
 		return 0;
@@ -562,7 +562,7 @@
 	for (i = 0; i < ARRAY_SIZE(daemon_service); i++) {
 		struct daemon_service *s = &(daemon_service[i]);
 		int namelen = strlen(s->name);
-		if (!strncmp("git-", line, 4) &&
+		if (!prefixcmp(line, "git-") &&
 		    !strncmp(s->name, line + 4, namelen) &&
 		    line[namelen + 4] == ' ') {
 			/*
@@ -1011,7 +1011,7 @@
 	for (i = 1; i < argc; i++) {
 		char *arg = argv[i];
 
-		if (!strncmp(arg, "--listen=", 9)) {
+		if (!prefixcmp(arg, "--listen=")) {
 		    char *p = arg + 9;
 		    char *ph = listen_addr = xmalloc(strlen(arg + 9) + 1);
 		    while (*p)
@@ -1019,7 +1019,7 @@
 		    *ph = 0;
 		    continue;
 		}
-		if (!strncmp(arg, "--port=", 7)) {
+		if (!prefixcmp(arg, "--port=")) {
 			char *end;
 			unsigned long n;
 			n = strtoul(arg+7, &end, 0);
@@ -1045,11 +1045,11 @@
 			export_all_trees = 1;
 			continue;
 		}
-		if (!strncmp(arg, "--timeout=", 10)) {
+		if (!prefixcmp(arg, "--timeout=")) {
 			timeout = atoi(arg+10);
 			continue;
 		}
-		if (!strncmp(arg, "--init-timeout=", 15)) {
+		if (!prefixcmp(arg, "--init-timeout=")) {
 			init_timeout = atoi(arg+15);
 			continue;
 		}
@@ -1057,11 +1057,11 @@
 			strict_paths = 1;
 			continue;
 		}
-		if (!strncmp(arg, "--base-path=", 12)) {
+		if (!prefixcmp(arg, "--base-path=")) {
 			base_path = arg+12;
 			continue;
 		}
-		if (!strncmp(arg, "--interpolated-path=", 20)) {
+		if (!prefixcmp(arg, "--interpolated-path=")) {
 			interpolated_path = arg+20;
 			continue;
 		}
@@ -1073,11 +1073,11 @@
 			user_path = "";
 			continue;
 		}
-		if (!strncmp(arg, "--user-path=", 12)) {
+		if (!prefixcmp(arg, "--user-path=")) {
 			user_path = arg + 12;
 			continue;
 		}
-		if (!strncmp(arg, "--pid-file=", 11)) {
+		if (!prefixcmp(arg, "--pid-file=")) {
 			pid_file = arg + 11;
 			continue;
 		}
@@ -1086,27 +1086,27 @@
 			log_syslog = 1;
 			continue;
 		}
-		if (!strncmp(arg, "--user=", 7)) {
+		if (!prefixcmp(arg, "--user=")) {
 			user_name = arg + 7;
 			continue;
 		}
-		if (!strncmp(arg, "--group=", 8)) {
+		if (!prefixcmp(arg, "--group=")) {
 			group_name = arg + 8;
 			continue;
 		}
-		if (!strncmp(arg, "--enable=", 9)) {
+		if (!prefixcmp(arg, "--enable=")) {
 			enable_service(arg + 9, 1);
 			continue;
 		}
-		if (!strncmp(arg, "--disable=", 10)) {
+		if (!prefixcmp(arg, "--disable=")) {
 			enable_service(arg + 10, 0);
 			continue;
 		}
-		if (!strncmp(arg, "--allow-override=", 17)) {
+		if (!prefixcmp(arg, "--allow-override=")) {
 			make_service_overridable(arg + 17, 1);
 			continue;
 		}
-		if (!strncmp(arg, "--forbid-override=", 18)) {
+		if (!prefixcmp(arg, "--forbid-override=")) {
 			make_service_overridable(arg + 18, 0);
 			continue;
 		}
diff --git a/date.c b/date.c
index 542c004..0ceccbe 100644
--- a/date.c
+++ b/date.c
@@ -55,12 +55,12 @@
 	return gmtime(&t);
 }
 
-const char *show_date(unsigned long time, int tz, int relative)
+const char *show_date(unsigned long time, int tz, enum date_mode mode)
 {
 	struct tm *tm;
 	static char timebuf[200];
 
-	if (relative) {
+	if (mode == DATE_RELATIVE) {
 		unsigned long diff;
 		struct timeval now;
 		gettimeofday(&now, NULL);
@@ -105,12 +105,16 @@
 	tm = time_to_tm(time, tz);
 	if (!tm)
 		return NULL;
-	sprintf(timebuf, "%.3s %.3s %d %02d:%02d:%02d %d %+05d",
-		weekday_names[tm->tm_wday],
-		month_names[tm->tm_mon],
-		tm->tm_mday,
-		tm->tm_hour, tm->tm_min, tm->tm_sec,
-		tm->tm_year + 1900, tz);
+	if (mode == DATE_SHORT)
+		sprintf(timebuf, "%04d-%02d-%02d", tm->tm_year + 1900,
+				tm->tm_mon + 1, tm->tm_mday);
+	else
+		sprintf(timebuf, "%.3s %.3s %d %02d:%02d:%02d %d %+05d",
+				weekday_names[tm->tm_wday],
+				month_names[tm->tm_mon],
+				tm->tm_mday,
+				tm->tm_hour, tm->tm_min, tm->tm_sec,
+				tm->tm_year + 1900, tz);
 	return timebuf;
 }
 
diff --git a/diff-lib.c b/diff-lib.c
index 60c0fa6..5c5b05b 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -8,11 +8,308 @@
 #include "diffcore.h"
 #include "revision.h"
 #include "cache-tree.h"
+#include "path-list.h"
 
 /*
  * diff-files
  */
 
+static int read_directory(const char *path, struct path_list *list)
+{
+	DIR *dir;
+	struct dirent *e;
+
+	if (!(dir = opendir(path)))
+		return error("Could not open directory %s", path);
+
+	while ((e = readdir(dir)))
+		if (strcmp(".", e->d_name) && strcmp("..", e->d_name))
+			path_list_insert(xstrdup(e->d_name), list);
+
+	closedir(dir);
+	return 0;
+}
+
+static int get_mode(const char *path, int *mode)
+{
+	struct stat st;
+
+	if (!path || !strcmp(path, "/dev/null"))
+		*mode = 0;
+	else if (!strcmp(path, "-"))
+		*mode = ntohl(create_ce_mode(0666));
+	else if (stat(path, &st))
+		return error("Could not access '%s'", path);
+	else
+		*mode = st.st_mode;
+	return 0;
+}
+
+static int queue_diff(struct diff_options *o,
+		const char *name1, const char *name2)
+{
+	int mode1 = 0, mode2 = 0;
+
+	if (get_mode(name1, &mode1) || get_mode(name2, &mode2))
+		return -1;
+
+	if (mode1 && mode2 && S_ISDIR(mode1) != S_ISDIR(mode2))
+		return error("file/directory conflict: %s, %s", name1, name2);
+
+	if (S_ISDIR(mode1) || S_ISDIR(mode2)) {
+		char buffer1[PATH_MAX], buffer2[PATH_MAX];
+		struct path_list p1 = {NULL, 0, 0, 1}, p2 = {NULL, 0, 0, 1};
+		int len1 = 0, len2 = 0, i1, i2, ret = 0;
+
+		if (name1 && read_directory(name1, &p1))
+			return -1;
+		if (name2 && read_directory(name2, &p2)) {
+			path_list_clear(&p1, 0);
+			return -1;
+		}
+
+		if (name1) {
+			len1 = strlen(name1);
+			if (len1 > 0 && name1[len1 - 1] == '/')
+				len1--;
+			memcpy(buffer1, name1, len1);
+			buffer1[len1++] = '/';
+		}
+
+		if (name2) {
+			len2 = strlen(name2);
+			if (len2 > 0 && name2[len2 - 1] == '/')
+				len2--;
+			memcpy(buffer2, name2, len2);
+			buffer2[len2++] = '/';
+		}
+
+		for (i1 = i2 = 0; !ret && (i1 < p1.nr || i2 < p2.nr); ) {
+			const char *n1, *n2;
+			int comp;
+
+			if (i1 == p1.nr)
+				comp = 1;
+			else if (i2 == p2.nr)
+				comp = -1;
+			else
+				comp = strcmp(p1.items[i1].path,
+					p2.items[i2].path);
+
+			if (comp > 0)
+				n1 = NULL;
+			else {
+				n1 = buffer1;
+				strncpy(buffer1 + len1, p1.items[i1++].path,
+						PATH_MAX - len1);
+			}
+
+			if (comp < 0)
+				n2 = NULL;
+			else {
+				n2 = buffer2;
+				strncpy(buffer2 + len2, p2.items[i2++].path,
+						PATH_MAX - len2);
+			}
+
+			ret = queue_diff(o, n1, n2);
+		}
+		path_list_clear(&p1, 0);
+		path_list_clear(&p2, 0);
+
+		return ret;
+	} else {
+		struct diff_filespec *d1, *d2;
+
+		if (o->reverse_diff) {
+			unsigned tmp;
+			const char *tmp_c;
+			tmp = mode1; mode1 = mode2; mode2 = tmp;
+			tmp_c = name1; name1 = name2; name2 = tmp_c;
+		}
+
+		if (!name1)
+			name1 = "/dev/null";
+		if (!name2)
+			name2 = "/dev/null";
+		d1 = alloc_filespec(name1);
+		d2 = alloc_filespec(name2);
+		fill_filespec(d1, null_sha1, mode1);
+		fill_filespec(d2, null_sha1, mode2);
+
+		diff_queue(&diff_queued_diff, d1, d2);
+		return 0;
+	}
+}
+
+static int is_in_index(const char *path)
+{
+	int len = strlen(path);
+	int pos = cache_name_pos(path, len);
+	char c;
+
+	if (pos < 0)
+		return 0;
+	if (strncmp(active_cache[pos]->name, path, len))
+		return 0;
+	c = active_cache[pos]->name[len];
+	return c == '\0' || c == '/';
+}
+
+static int handle_diff_files_args(struct rev_info *revs,
+		int argc, const char **argv, int *silent)
+{
+	*silent = 0;
+
+	/* revs->max_count == -2 means --no-index */
+	while (1 < argc && argv[1][0] == '-') {
+		if (!strcmp(argv[1], "--base"))
+			revs->max_count = 1;
+		else if (!strcmp(argv[1], "--ours"))
+			revs->max_count = 2;
+		else if (!strcmp(argv[1], "--theirs"))
+			revs->max_count = 3;
+		else if (!strcmp(argv[1], "-n") ||
+				!strcmp(argv[1], "--no-index")) {
+			revs->max_count = -2;
+			revs->diffopt.exit_with_status = 1;
+		}
+		else if (!strcmp(argv[1], "-q"))
+			*silent = 1;
+		else
+			return error("invalid option: %s", argv[1]);
+		argv++; argc--;
+	}
+
+	if (revs->max_count == -1 && revs->diffopt.nr_paths == 2) {
+		/*
+		 * If two files are specified, and at least one is untracked,
+		 * default to no-index.
+		 */
+		read_cache();
+		if (!is_in_index(revs->diffopt.paths[0]) ||
+					!is_in_index(revs->diffopt.paths[1]))
+			revs->max_count = -2;
+	}
+
+	/*
+	 * Make sure there are NO revision (i.e. pending object) parameter,
+	 * rev.max_count is reasonable (0 <= n <= 3),
+	 * there is no other revision filtering parameters.
+	 */
+	if (revs->pending.nr || revs->max_count > 3 ||
+	    revs->min_age != -1 || revs->max_age != -1)
+		return error("no revision allowed with diff-files");
+
+	if (revs->max_count == -1 &&
+	    (revs->diffopt.output_format & DIFF_FORMAT_PATCH))
+		revs->combine_merges = revs->dense_combined_merges = 1;
+
+	return 0;
+}
+
+static int is_outside_repo(const char *path, int nongit, const char *prefix)
+{
+	int i;
+	if (nongit || !strcmp(path, "-") || path[0] == '/')
+		return 1;
+	if (prefixcmp(path, "../"))
+		return 0;
+	if (!prefix)
+		return 1;
+	for (i = strlen(prefix); !prefixcmp(path, "../"); ) {
+		while (i > 0 && prefix[i - 1] != '/')
+			i--;
+		if (--i < 0)
+			return 1;
+		path += 3;
+	}
+	return 0;
+}
+
+int setup_diff_no_index(struct rev_info *revs,
+		int argc, const char ** argv, int nongit, const char *prefix)
+{
+	int i;
+	for (i = 1; i < argc; i++)
+		if (argv[i][0] != '-' || argv[i][1] == '\0')
+			break;
+		else if (!strcmp(argv[i], "--")) {
+			i++;
+			break;
+		} else if (i < argc - 3 && !strcmp(argv[i], "--no-index")) {
+			i = argc - 3;
+			revs->diffopt.exit_with_status = 1;
+			break;
+		}
+	if (argc != i + 2 || (!is_outside_repo(argv[i + 1], nongit, prefix) &&
+				!is_outside_repo(argv[i], nongit, prefix)))
+		return -1;
+
+	diff_setup(&revs->diffopt);
+	for (i = 1; i < argc - 2; )
+		if (!strcmp(argv[i], "--no-index"))
+			i++;
+		else {
+			int j = diff_opt_parse(&revs->diffopt,
+					argv + i, argc - i);
+			if (!j)
+				die("invalid diff option/value: %s", argv[i]);
+			i += j;
+		}
+
+	if (prefix) {
+		int len = strlen(prefix);
+
+		revs->diffopt.paths = xcalloc(2, sizeof(char*));
+		for (i = 0; i < 2; i++) {
+			const char *p = argv[argc - 2 + i];
+			/*
+			 * stdin should be spelled as '-'; if you have
+			 * path that is '-', spell it as ./-.
+			 */
+			p = (strcmp(p, "-")
+			     ? xstrdup(prefix_filename(prefix, len, p))
+			     : p);
+			revs->diffopt.paths[i] = p;
+		}
+	}
+	else
+		revs->diffopt.paths = argv + argc - 2;
+	revs->diffopt.nr_paths = 2;
+	revs->max_count = -2;
+	return 0;
+}
+
+int run_diff_files_cmd(struct rev_info *revs, int argc, const char **argv)
+{
+	int silent_on_removed;
+
+	if (handle_diff_files_args(revs, argc, argv, &silent_on_removed))
+		return -1;
+
+	if (revs->max_count == -2) {
+		if (revs->diffopt.nr_paths != 2)
+			return error("need two files/directories with --no-index");
+		if (queue_diff(&revs->diffopt, revs->diffopt.paths[0],
+				revs->diffopt.paths[1]))
+			return -1;
+		diffcore_std(&revs->diffopt);
+		diff_flush(&revs->diffopt);
+		/*
+		 * The return code for --no-index imitates diff(1):
+		 * 0 = no changes, 1 = changes, else error
+		 */
+		return revs->diffopt.found_changes;
+	}
+
+	if (read_cache() < 0) {
+		perror("read_cache");
+		return -1;
+	}
+	return run_diff_files(revs, silent_on_removed);
+}
+
 int run_diff_files(struct rev_info *revs, int silent_on_removed)
 {
 	int entries, i;
@@ -20,17 +317,16 @@
 
 	if (diff_unmerged_stage < 0)
 		diff_unmerged_stage = 2;
-	entries = read_cache();
-	if (entries < 0) {
-		perror("read_cache");
-		return -1;
-	}
+	entries = active_nr;
 	for (i = 0; i < entries; i++) {
 		struct stat st;
 		unsigned int oldmode, newmode;
 		struct cache_entry *ce = active_cache[i];
 		int changed;
 
+		if (revs->diffopt.quiet && revs->diffopt.has_changes)
+			break;
+
 		if (!ce_path_match(ce, revs->prune_data))
 			continue;
 
@@ -134,6 +430,9 @@
 		    S_ISREG(newmode) && S_ISREG(oldmode) &&
 		    ((newmode ^ oldmode) == 0111))
 			newmode = oldmode;
+		else if (!has_symlinks &&
+		    S_ISREG(newmode) && S_ISLNK(oldmode))
+			newmode = oldmode;
 		diff_change(&revs->diffopt, oldmode, newmode,
 			    ce->sha1, (changed ? null_sha1 : ce->sha1),
 			    ce->name, NULL);
@@ -269,6 +568,9 @@
 		struct cache_entry *ce = *ac;
 		int same = (entries > 1) && ce_same_name(ce, ac[1]);
 
+		if (revs->diffopt.quiet && revs->diffopt.has_changes)
+			break;
+
 		if (!ce_path_match(ce, pathspec))
 			goto skip_entry;
 
@@ -362,10 +664,6 @@
 	if (!revs->ignore_merges)
 		match_missing = 1;
 
-	if (read_cache() < 0) {
-		perror("read_cache");
-		return -1;
-	}
 	mark_merge_entries();
 
 	ent = revs->pending.objects[0].item;
diff --git a/diff.c b/diff.c
index b8a90e9..d8f9242 100644
--- a/diff.c
+++ b/diff.c
@@ -77,7 +77,7 @@
 			diff_detect_rename_default = DIFF_DETECT_RENAME;
 		return 0;
 	}
-	if (!strncmp(var, "diff.color.", 11) || !strncmp(var, "color.diff.", 11)) {
+	if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) {
 		int slot = parse_diff_color_slot(var, 11);
 		color_parse(value, var, diff_colors[slot]);
 		return 0;
@@ -184,44 +184,61 @@
 	}
 }
 
-static void copy_file(int prefix, const char *data, int size)
+static void copy_file(int prefix, const char *data, int size,
+		const char *set, const char *reset)
 {
 	int ch, nl_just_seen = 1;
 	while (0 < size--) {
 		ch = *data++;
-		if (nl_just_seen)
+		if (nl_just_seen) {
+			fputs(set, stdout);
 			putchar(prefix);
-		putchar(ch);
-		if (ch == '\n')
+		}
+		if (ch == '\n') {
 			nl_just_seen = 1;
-		else
+			fputs(reset, stdout);
+		} else
 			nl_just_seen = 0;
+		putchar(ch);
 	}
 	if (!nl_just_seen)
-		printf("\n\\ No newline at end of file\n");
+		printf("%s\n\\ No newline at end of file\n", reset);
 }
 
 static void emit_rewrite_diff(const char *name_a,
 			      const char *name_b,
 			      struct diff_filespec *one,
-			      struct diff_filespec *two)
+			      struct diff_filespec *two,
+			      int color_diff)
 {
 	int lc_a, lc_b;
+	const char *name_a_tab, *name_b_tab;
+	const char *metainfo = diff_get_color(color_diff, DIFF_METAINFO);
+	const char *fraginfo = diff_get_color(color_diff, DIFF_FRAGINFO);
+	const char *old = diff_get_color(color_diff, DIFF_FILE_OLD);
+	const char *new = diff_get_color(color_diff, DIFF_FILE_NEW);
+	const char *reset = diff_get_color(color_diff, DIFF_RESET);
+
+	name_a += (*name_a == '/');
+	name_b += (*name_b == '/');
+	name_a_tab = strchr(name_a, ' ') ? "\t" : "";
+	name_b_tab = strchr(name_b, ' ') ? "\t" : "";
+
 	diff_populate_filespec(one, 0);
 	diff_populate_filespec(two, 0);
 	lc_a = count_lines(one->data, one->size);
 	lc_b = count_lines(two->data, two->size);
-	name_a += (*name_a == '/');
-	name_b += (*name_b == '/');
-	printf("--- a/%s\n+++ b/%s\n@@ -", name_a, name_b);
+	printf("%s--- a/%s%s%s\n%s+++ b/%s%s%s\n%s@@ -",
+	       metainfo, name_a, name_a_tab, reset,
+	       metainfo, name_b, name_b_tab, reset, fraginfo);
 	print_line_count(lc_a);
 	printf(" +");
 	print_line_count(lc_b);
-	printf(" @@\n");
+	printf(" @@%s\n", reset);
 	if (lc_a)
-		copy_file('-', one->data, one->size);
+		copy_file('-', one->data, one->size, old, reset);
 	if (lc_b)
-		copy_file('+', two->data, two->size);
+		copy_file('+', two->data, two->size, new, reset);
 }
 
 static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
@@ -365,6 +382,7 @@
 	int nparents, color_diff;
 	const char **label_path;
 	struct diff_words_data *diff_words;
+	int *found_changesp;
 };
 
 static void free_diff_words_data(struct emit_callback *ecbdata)
@@ -400,22 +418,16 @@
 	puts(reset);
 }
 
-static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len)
+static void emit_line_with_ws(int nparents,
+		const char *set, const char *reset, const char *ws,
+		const char *line, int len)
 {
-	int col0 = ecbdata->nparents;
+	int col0 = nparents;
 	int last_tab_in_indent = -1;
 	int last_space_in_indent = -1;
 	int i;
 	int tail = len;
 	int need_highlight_leading_space = 0;
-	const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE);
-	const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW);
-
-	if (!*ws) {
-		emit_line(set, reset, line, len);
-		return;
-	}
-
 	/* The line is a newly added line.  Does it have funny leading
 	 * whitespaces?  In indent, SP should never precede a TAB.
 	 */
@@ -470,6 +482,18 @@
 		emit_line(set, reset, line + i, len - i);
 }
 
+static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len)
+{
+	const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE);
+	const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW);
+
+	if (!*ws)
+		emit_line(set, reset, line, len);
+	else
+		emit_line_with_ws(ecbdata->nparents, set, reset, ws,
+				line, len);
+}
+
 static void fn_out_consume(void *priv, char *line, unsigned long len)
 {
 	int i;
@@ -478,9 +502,18 @@
 	const char *set = diff_get_color(ecbdata->color_diff, DIFF_METAINFO);
 	const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
 
+	*(ecbdata->found_changesp) = 1;
+
 	if (ecbdata->label_path[0]) {
-		printf("%s--- %s%s\n", set, ecbdata->label_path[0], reset);
-		printf("%s+++ %s%s\n", set, ecbdata->label_path[1], reset);
+		const char *name_a_tab, *name_b_tab;
+
+		name_a_tab = strchr(ecbdata->label_path[0], ' ') ? "\t" : "";
+		name_b_tab = strchr(ecbdata->label_path[1], ' ') ? "\t" : "";
+
+		printf("%s--- %s%s%s\n",
+		       set, ecbdata->label_path[0], reset, name_a_tab);
+		printf("%s+++ %s%s%s\n",
+		       set, ecbdata->label_path[1], reset, name_b_tab);
 		ecbdata->label_path[0] = ecbdata->label_path[1] = NULL;
 	}
 
@@ -872,30 +905,44 @@
 struct checkdiff_t {
 	struct xdiff_emit_state xm;
 	const char *filename;
-	int lineno;
+	int lineno, color_diff;
 };
 
 static void checkdiff_consume(void *priv, char *line, unsigned long len)
 {
 	struct checkdiff_t *data = priv;
+	const char *ws = diff_get_color(data->color_diff, DIFF_WHITESPACE);
+	const char *reset = diff_get_color(data->color_diff, DIFF_RESET);
+	const char *set = diff_get_color(data->color_diff, DIFF_FILE_NEW);
 
 	if (line[0] == '+') {
-		int i, spaces = 0;
+		int i, spaces = 0, space_before_tab = 0, white_space_at_end = 0;
 
 		/* check space before tab */
 		for (i = 1; i < len && (line[i] == ' ' || line[i] == '\t'); i++)
 			if (line[i] == ' ')
 				spaces++;
 		if (line[i - 1] == '\t' && spaces)
-			printf("%s:%d: space before tab:%.*s\n",
-				data->filename, data->lineno, (int)len, line);
+			space_before_tab = 1;
 
 		/* check white space at line end */
 		if (line[len - 1] == '\n')
 			len--;
 		if (isspace(line[len - 1]))
-			printf("%s:%d: white space at end: %.*s\n",
-				data->filename, data->lineno, (int)len, line);
+			white_space_at_end = 1;
+
+		if (space_before_tab || white_space_at_end) {
+			printf("%s:%d: %s", data->filename, data->lineno, ws);
+			if (space_before_tab) {
+				printf("space before tab");
+				if (white_space_at_end)
+					putchar(',');
+			}
+			if (white_space_at_end)
+				printf("white space at end");
+			printf(":%s ", reset);
+			emit_line_with_ws(1, set, reset, ws, line, len);
+		}
 
 		data->lineno++;
 	} else if (line[0] == ' ')
@@ -1052,7 +1099,9 @@
 		if ((one->mode ^ two->mode) & S_IFMT)
 			goto free_ab_and_return;
 		if (complete_rewrite) {
-			emit_rewrite_diff(name_a, name_b, one, two);
+			emit_rewrite_diff(name_a, name_b, one, two,
+					o->color_diff);
+			o->found_changes = 1;
 			goto free_ab_and_return;
 		}
 	}
@@ -1070,6 +1119,7 @@
 		else
 			printf("Binary files %s and %s differ\n",
 			       lbl[0], lbl[1]);
+		o->found_changes = 1;
 	}
 	else {
 		/* Crazy xdl interfaces.. */
@@ -1082,14 +1132,15 @@
 		memset(&ecbdata, 0, sizeof(ecbdata));
 		ecbdata.label_path = lbl;
 		ecbdata.color_diff = o->color_diff;
+		ecbdata.found_changesp = &o->found_changes;
 		xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
 		xecfg.ctxlen = o->context;
 		xecfg.flags = XDL_EMIT_FUNCNAMES;
 		if (!diffopts)
 			;
-		else if (!strncmp(diffopts, "--unified=", 10))
+		else if (!prefixcmp(diffopts, "--unified="))
 			xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10);
-		else if (!strncmp(diffopts, "-u", 2))
+		else if (!prefixcmp(diffopts, "-u"))
 			xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10);
 		ecb.outf = xdiff_outf;
 		ecb.priv = &ecbdata;
@@ -1153,7 +1204,7 @@
 
 static void builtin_checkdiff(const char *name_a, const char *name_b,
 			     struct diff_filespec *one,
-			     struct diff_filespec *two)
+			     struct diff_filespec *two, struct diff_options *o)
 {
 	mmfile_t mf1, mf2;
 	struct checkdiff_t data;
@@ -1165,6 +1216,7 @@
 	data.xm.consume = checkdiff_consume;
 	data.filename = name_b ? name_b : name_a;
 	data.lineno = 0;
+	data.color_diff = o->color_diff;
 
 	if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
 		die("unable to read files to diff");
@@ -1312,6 +1364,32 @@
 	return e;
 }
 
+static int populate_from_stdin(struct diff_filespec *s)
+{
+#define INCREMENT 1024
+	char *buf;
+	unsigned long size;
+	int got;
+
+	size = 0;
+	buf = NULL;
+	while (1) {
+		buf = xrealloc(buf, size + INCREMENT);
+		got = xread(0, buf + size, INCREMENT);
+		if (!got)
+			break; /* EOF */
+		if (got < 0)
+			return error("error while reading from stdin %s",
+				     strerror(errno));
+		size += got;
+	}
+	s->should_munmap = 0;
+	s->data = buf;
+	s->size = size;
+	s->should_free = 1;
+	return 0;
+}
+
 /*
  * While doing rename detection and pickaxe operation, we may need to
  * grab the data for the blob (or file) for our own in-core comparison.
@@ -1334,6 +1412,12 @@
 	    reuse_worktree_file(s->path, s->sha1, 0)) {
 		struct stat st;
 		int fd;
+		char *buf;
+		unsigned long size;
+
+		if (!strcmp(s->path, "-"))
+			return populate_from_stdin(s);
+
 		if (lstat(s->path, &st) < 0) {
 			if (errno == ENOENT) {
 			err_empty:
@@ -1344,7 +1428,7 @@
 				return err;
 			}
 		}
-		s->size = st.st_size;
+		s->size = xsize_t(st.st_size);
 		if (!s->size)
 			goto empty;
 		if (size_only)
@@ -1366,10 +1450,22 @@
 		s->data = xmmap(NULL, s->size, PROT_READ, MAP_PRIVATE, fd, 0);
 		close(fd);
 		s->should_munmap = 1;
-		/* FIXME! CRLF -> LF conversion goes here, based on "s->path" */
+
+		/*
+		 * Convert from working tree format to canonical git format
+		 */
+		buf = s->data;
+		size = s->size;
+		if (convert_to_git(s->path, &buf, &size)) {
+			munmap(s->data, s->size);
+			s->should_munmap = 0;
+			s->data = buf;
+			s->size = size;
+			s->should_free = 1;
+		}
 	}
 	else {
-		char type[20];
+		enum object_type type;
 		struct sha1_size_cache *e;
 
 		if (size_only) {
@@ -1378,11 +1474,12 @@
 				s->size = e->size;
 				return 0;
 			}
-			if (!sha1_object_info(s->sha1, type, &s->size))
+			type = sha1_object_info(s->sha1, &s->size);
+			if (type < 0)
 				locate_size_cache(s->sha1, 0, s->size);
 		}
 		else {
-			s->data = read_sha1_file(s->sha1, type, &s->size);
+			s->data = read_sha1_file(s->sha1, &type, &s->size);
 			s->should_free = 1;
 		}
 	}
@@ -1447,12 +1544,13 @@
 		if (S_ISLNK(st.st_mode)) {
 			int ret;
 			char buf[PATH_MAX + 1]; /* ought to be SYMLINK_MAX */
+			size_t sz = xsize_t(st.st_size);
 			if (sizeof(buf) <= st.st_size)
 				die("symlink too long: %s", name);
-			ret = readlink(name, buf, st.st_size);
+			ret = readlink(name, buf, sz);
 			if (ret < 0)
 				die("readlink(%s)", name);
-			prep_temp_blob(temp, buf, st.st_size,
+			prep_temp_blob(temp, buf, sz,
 				       (one->sha1_valid ?
 					one->sha1 : null_sha1),
 				       (one->sha1_valid ?
@@ -1621,6 +1719,10 @@
 	if (DIFF_FILE_VALID(one)) {
 		if (!one->sha1_valid) {
 			struct stat st;
+			if (!strcmp(one->path, "-")) {
+				hashcpy(one->sha1, null_sha1);
+				return;
+			}
 			if (lstat(one->path, &st) < 0)
 				die("stat %s", one->path);
 			if (index_path(one->sha1, one->path, &st, 0))
@@ -1775,7 +1877,7 @@
 	diff_fill_sha1_info(p->one);
 	diff_fill_sha1_info(p->two);
 
-	builtin_checkdiff(name, other, p->one, p->two);
+	builtin_checkdiff(name, other, p->one, p->two, o);
 }
 
 void diff_setup(struct diff_options *options)
@@ -1856,6 +1958,23 @@
 	if (options->abbrev <= 0 || 40 < options->abbrev)
 		options->abbrev = 40; /* full */
 
+	/*
+	 * It does not make sense to show the first hit we happened
+	 * to have found.  It does not make sense not to return with
+	 * exit code in such a case either.
+	 */
+	if (options->quiet) {
+		options->output_format = DIFF_FORMAT_NO_OUTPUT;
+		options->exit_with_status = 1;
+	}
+
+	/*
+	 * If we postprocess in diffcore, we cannot simply return
+	 * upon the first hit.  We need to run diff as usual.
+	 */
+	if (options->pickaxe || options->filter)
+		options->quiet = 0;
+
 	return 0;
 }
 
@@ -1924,7 +2043,7 @@
 	else if (!strcmp(arg, "--shortstat")) {
 		options->output_format |= DIFF_FORMAT_SHORTSTAT;
 	}
-	else if (!strncmp(arg, "--stat", 6)) {
+	else if (!prefixcmp(arg, "--stat")) {
 		char *end;
 		int width = options->stat_width;
 		int name_width = options->stat_name_width;
@@ -1933,9 +2052,9 @@
 
 		switch (*arg) {
 		case '-':
-			if (!strncmp(arg, "-width=", 7))
+			if (!prefixcmp(arg, "-width="))
 				width = strtoul(arg + 7, &end, 10);
-			else if (!strncmp(arg, "-name-width=", 12))
+			else if (!prefixcmp(arg, "-name-width="))
 				name_width = strtoul(arg + 12, &end, 10);
 			break;
 		case '=':
@@ -1960,7 +2079,7 @@
 	}
 	else if (!strcmp(arg, "-z"))
 		options->line_termination = 0;
-	else if (!strncmp(arg, "-l", 2))
+	else if (!prefixcmp(arg, "-l"))
 		options->rename_limit = strtoul(arg+2, NULL, 10);
 	else if (!strcmp(arg, "--full-index"))
 		options->full_index = 1;
@@ -1977,31 +2096,31 @@
 		options->output_format |= DIFF_FORMAT_NAME_STATUS;
 	else if (!strcmp(arg, "-R"))
 		options->reverse_diff = 1;
-	else if (!strncmp(arg, "-S", 2))
+	else if (!prefixcmp(arg, "-S"))
 		options->pickaxe = arg + 2;
 	else if (!strcmp(arg, "-s")) {
 		options->output_format |= DIFF_FORMAT_NO_OUTPUT;
 	}
-	else if (!strncmp(arg, "-O", 2))
+	else if (!prefixcmp(arg, "-O"))
 		options->orderfile = arg + 2;
-	else if (!strncmp(arg, "--diff-filter=", 14))
+	else if (!prefixcmp(arg, "--diff-filter="))
 		options->filter = arg + 14;
 	else if (!strcmp(arg, "--pickaxe-all"))
 		options->pickaxe_opts = DIFF_PICKAXE_ALL;
 	else if (!strcmp(arg, "--pickaxe-regex"))
 		options->pickaxe_opts = DIFF_PICKAXE_REGEX;
-	else if (!strncmp(arg, "-B", 2)) {
+	else if (!prefixcmp(arg, "-B")) {
 		if ((options->break_opt =
 		     diff_scoreopt_parse(arg)) == -1)
 			return -1;
 	}
-	else if (!strncmp(arg, "-M", 2)) {
+	else if (!prefixcmp(arg, "-M")) {
 		if ((options->rename_score =
 		     diff_scoreopt_parse(arg)) == -1)
 			return -1;
 		options->detect_rename = DIFF_DETECT_RENAME;
 	}
-	else if (!strncmp(arg, "-C", 2)) {
+	else if (!prefixcmp(arg, "-C")) {
 		if ((options->rename_score =
 		     diff_scoreopt_parse(arg)) == -1)
 			return -1;
@@ -2011,7 +2130,7 @@
 		options->find_copies_harder = 1;
 	else if (!strcmp(arg, "--abbrev"))
 		options->abbrev = DEFAULT_ABBREV;
-	else if (!strncmp(arg, "--abbrev=", 9)) {
+	else if (!prefixcmp(arg, "--abbrev=")) {
 		options->abbrev = strtoul(arg + 9, NULL, 10);
 		if (options->abbrev < MINIMUM_ABBREV)
 			options->abbrev = MINIMUM_ABBREV;
@@ -2026,10 +2145,16 @@
 		options->xdl_opts |= XDF_IGNORE_WHITESPACE;
 	else if (!strcmp(arg, "-b") || !strcmp(arg, "--ignore-space-change"))
 		options->xdl_opts |= XDF_IGNORE_WHITESPACE_CHANGE;
+	else if (!strcmp(arg, "--ignore-space-at-eol"))
+		options->xdl_opts |= XDF_IGNORE_WHITESPACE_AT_EOL;
 	else if (!strcmp(arg, "--color-words"))
 		options->color_diff = options->color_diff_words = 1;
 	else if (!strcmp(arg, "--no-renames"))
 		options->detect_rename = 0;
+	else if (!strcmp(arg, "--exit-code"))
+		options->exit_with_status = 1;
+	else if (!strcmp(arg, "--quiet"))
+		options->quiet = 1;
 	else
 		return 0;
 	return 1;
@@ -2068,7 +2193,7 @@
 	/* user says num divided by scale and we say internally that
 	 * is MAX_SCORE * num / scale.
 	 */
-	return (num >= scale) ? MAX_SCORE : (MAX_SCORE * num / scale);
+	return (int)((num >= scale) ? MAX_SCORE : (MAX_SCORE * num / scale));
 }
 
 int diff_scoreopt_parse(const char *opt)
@@ -2392,7 +2517,8 @@
 				p->status = DIFF_STATUS_RENAMED;
 		}
 		else if (hashcmp(p->one->sha1, p->two->sha1) ||
-			 p->one->mode != p->two->mode)
+			 p->one->mode != p->two->mode ||
+			 is_null_sha1(p->one->sha1))
 			p->status = DIFF_STATUS_MODIFIED;
 		else {
 			/* This is a "no-change" entry and should not
@@ -2518,7 +2644,7 @@
 	int new_len;
 
 	/* Ignore line numbers when computing the SHA1 of the patch */
-	if (!strncmp(line, "@@ -", 4))
+	if (!prefixcmp(line, "@@ -"))
 		return;
 
 	new_len = remove_space(line, len);
@@ -2793,6 +2919,8 @@
 
 void diffcore_std(struct diff_options *options)
 {
+	if (options->quiet)
+		return;
 	if (options->break_opt != -1)
 		diffcore_break(options->break_opt);
 	if (options->detect_rename)
@@ -2805,18 +2933,11 @@
 		diffcore_order(options->orderfile);
 	diff_resolve_rename_copy();
 	diffcore_apply_filter(options->filter);
+
+	options->has_changes = !!diff_queued_diff.nr;
 }
 
 
-void diffcore_std_no_resolve(struct diff_options *options)
-{
-	if (options->pickaxe)
-		diffcore_pickaxe(options->pickaxe, options->pickaxe_opts);
-	if (options->orderfile)
-		diffcore_order(options->orderfile);
-	diffcore_apply_filter(options->filter);
-}
-
 void diff_addremove(struct diff_options *options,
 		    int addremove, unsigned mode,
 		    const unsigned char *sha1,
@@ -2852,6 +2973,7 @@
 		fill_filespec(two, sha1, mode);
 
 	diff_queue(&diff_queued_diff, one, two);
+	options->has_changes = 1;
 }
 
 void diff_change(struct diff_options *options,
@@ -2877,6 +2999,7 @@
 	fill_filespec(two, new_sha1, new_mode);
 
 	diff_queue(&diff_queued_diff, one, two);
+	options->has_changes = 1;
 }
 
 void diff_unmerge(struct diff_options *options,
diff --git a/diff.h b/diff.h
index eece65d..a0d2ce1 100644
--- a/diff.h
+++ b/diff.h
@@ -56,7 +56,10 @@
 		 silent_on_remove:1,
 		 find_copies_harder:1,
 		 color_diff:1,
-		 color_diff_words:1;
+		 color_diff_words:1,
+		 has_changes:1,
+		 quiet:1,
+		 exit_with_status:1;
 	int context;
 	int break_opt;
 	int detect_rename;
@@ -75,6 +78,9 @@
 	int stat_width;
 	int stat_name_width;
 
+	/* this is set by diffcore for DIFF_FORMAT_PATCH */
+	int found_changes;
+
 	int nr_paths;
 	const char **paths;
 	int *pathlens;
@@ -167,8 +173,6 @@
 
 extern void diffcore_std(struct diff_options *);
 
-extern void diffcore_std_no_resolve(struct diff_options *);
-
 #define COMMON_DIFF_OPTIONS_HELP \
 "\ncommon diff options:\n" \
 "  -z            output diff-raw with lines terminated with NUL.\n" \
@@ -219,6 +223,9 @@
 extern const char *diff_unique_abbrev(const unsigned char *, int);
 
 extern int run_diff_files(struct rev_info *revs, int silent_on_removed);
+extern int setup_diff_no_index(struct rev_info *revs,
+		int argc, const char ** argv, int nongit, const char *prefix);
+extern int run_diff_files_cmd(struct rev_info *revs, int argc, const char **argv);
 
 extern int run_diff_index(struct rev_info *revs, int cached);
 
diff --git a/diffcore-break.c b/diffcore-break.c
index acb18db..9c19b8c 100644
--- a/diffcore-break.c
+++ b/diffcore-break.c
@@ -89,7 +89,7 @@
 	 * merge the surviving pair together if the score is
 	 * less than the minimum, after rename/copy runs.
 	 */
-	*merge_score_p = src_removed * MAX_SCORE / src->size;
+	*merge_score_p = (int)(src_removed * MAX_SCORE / src->size);
 
 	/* Extent of damage, which counts both inserts and
 	 * deletes.
diff --git a/diffcore-order.c b/diffcore-order.c
index 7ad0946..2a4bd82 100644
--- a/diffcore-order.c
+++ b/diffcore-order.c
@@ -14,6 +14,7 @@
 	void *map;
 	char *cp, *endp;
 	struct stat st;
+	size_t sz;
 
 	if (order)
 		return;
@@ -25,11 +26,12 @@
 		close(fd);
 		return;
 	}
-	map = mmap(NULL, st.st_size, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
+	sz = xsize_t(st.st_size);
+	map = mmap(NULL, sz, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
 	close(fd);
 	if (map == MAP_FAILED)
 		return;
-	endp = (char *) map + st.st_size;
+	endp = (char *) map + sz;
 	for (pass = 0; pass < 2; pass++) {
 		cnt = 0;
 		cp = map;
diff --git a/diffcore-rename.c b/diffcore-rename.c
index 91fa2be..7903041 100644
--- a/diffcore-rename.c
+++ b/diffcore-rename.c
@@ -172,7 +172,8 @@
 		return 0; /* error but caught downstream */
 
 
-	delta_limit = base_size * (MAX_SCORE-minimum_score) / MAX_SCORE;
+	delta_limit = (unsigned long)
+		(base_size * (MAX_SCORE-minimum_score) / MAX_SCORE);
 	if (diffcore_count_changes(src->data, src->size,
 				   dst->data, dst->size,
 				   &src->cnt_data, &dst->cnt_data,
@@ -186,7 +187,7 @@
 	if (!dst->size)
 		score = 0; /* should not happen */
 	else
-		score = src_copied * MAX_SCORE / max_size;
+		score = (int)(src_copied * MAX_SCORE / max_size);
 	return score;
 }
 
@@ -297,7 +298,7 @@
 				struct diff_filespec *one = rename_src[j].one;
 				if (!is_exact_match(one, two, contents_too))
 					continue;
-				record_rename_pair(i, j, MAX_SCORE);
+				record_rename_pair(i, j, (int)MAX_SCORE);
 				rename_count++;
 				break; /* we are done with this entry */
 			}
diff --git a/dir.c b/dir.c
index 32b57f0..b48e19d 100644
--- a/dir.c
+++ b/dir.c
@@ -130,13 +130,13 @@
 {
 	struct stat st;
 	int fd, i;
-	long size;
+	size_t size;
 	char *buf, *entry;
 
 	fd = open(fname, O_RDONLY);
 	if (fd < 0 || fstat(fd, &st) < 0)
 		goto err;
-	size = st.st_size;
+	size = xsize_t(st.st_size);
 	if (size == 0) {
 		close(fd);
 		return 0;
diff --git a/entry.c b/entry.c
index c2641dd..d72f811 100644
--- a/entry.c
+++ b/entry.c
@@ -68,16 +68,19 @@
 	void *new;
 	unsigned long size;
 	long wrote;
-	char type[20];
+	enum object_type type;
 
-	new = read_sha1_file(ce->sha1, type, &size);
-	if (!new || strcmp(type, blob_type)) {
+	new = read_sha1_file(ce->sha1, &type, &size);
+	if (!new || type != OBJ_BLOB) {
 		if (new)
 			free(new);
 		return error("git-checkout-index: unable to read sha1 file of %s (%s)",
 			path, sha1_to_hex(ce->sha1));
 	}
 	switch (ntohl(ce->ce_mode) & S_IFMT) {
+		char *buf;
+		unsigned long nsize;
+
 	case S_IFREG:
 		if (to_tempfile) {
 			strcpy(path, ".merge_file_XXXXXX");
@@ -89,7 +92,18 @@
 			return error("git-checkout-index: unable to create file %s (%s)",
 				path, strerror(errno));
 		}
-		/* FIXME: LF -> CRLF conversion goes here, based on "ce->name" */
+
+		/*
+		 * Convert from git internal format to working tree format
+		 */
+		buf = new;
+		nsize = size;
+		if (convert_to_working_tree(ce->name, &buf, &nsize)) {
+			free(new);
+			new = buf;
+			size = nsize;
+		}
+
 		wrote = write_in_full(fd, new, size);
 		close(fd);
 		free(new);
@@ -97,9 +111,12 @@
 			return error("git-checkout-index: unable to write file %s", path);
 		break;
 	case S_IFLNK:
-		if (to_tempfile) {
-			strcpy(path, ".merge_link_XXXXXX");
-			fd = mkstemp(path);
+		if (to_tempfile || !has_symlinks) {
+			if (to_tempfile) {
+				strcpy(path, ".merge_link_XXXXXX");
+				fd = mkstemp(path);
+			} else
+				fd = create_file(path, 0666);
 			if (fd < 0) {
 				free(new);
 				return error("git-checkout-index: unable to create "
diff --git a/environment.c b/environment.c
index 54c22f8..2231659 100644
--- a/environment.c
+++ b/environment.c
@@ -13,21 +13,24 @@
 char git_default_name[MAX_GITNAME];
 int use_legacy_headers = 1;
 int trust_executable_bit = 1;
+int has_symlinks = 1;
 int assume_unchanged;
 int prefer_symlink_refs;
 int is_bare_repository_cfg = -1; /* unspecified */
 int log_all_ref_updates = -1; /* unspecified */
 int warn_ambiguous_refs = 1;
 int repository_format_version;
-char *git_commit_encoding;
-char *git_log_output_encoding;
+const char *git_commit_encoding;
+const char *git_log_output_encoding;
 int shared_repository = PERM_UMASK;
 const char *apply_default_whitespace;
 int zlib_compression_level = Z_DEFAULT_COMPRESSION;
 size_t packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE;
 size_t packed_git_limit = DEFAULT_PACKED_GIT_LIMIT;
+size_t delta_base_cache_limit = 16 * 1024 * 1024;
 int pager_in_use;
 int pager_use_color = 1;
+int auto_crlf = 0;	/* 1: both ways, -1: only when adding git objects */
 
 static const char *git_dir;
 static char *git_object_dir, *git_index_file, *git_refs_dir, *git_graft_file;
diff --git a/exec_cmd.c b/exec_cmd.c
index 3996bce..9b74ed2 100644
--- a/exec_cmd.c
+++ b/exec_cmd.c
@@ -56,7 +56,7 @@
 			len = strlen(git_command);
 
 			/* Trivial cleanup */
-			while (!strncmp(exec_dir, "./", 2)) {
+			while (!prefixcmp(exec_dir, "./")) {
 				exec_dir += 2;
 				while (*exec_dir == '/')
 					exec_dir++;
diff --git a/fast-import.c b/fast-import.c
index ac37145..cdd629d 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -133,10 +133,6 @@
 #define PACK_ID_BITS 16
 #define MAX_PACK_ID ((1<<PACK_ID_BITS)-1)
 
-#ifndef PRIuMAX
-#define PRIuMAX "llu"
-#endif
-
 struct object_entry
 {
 	struct object_entry *next;
@@ -253,7 +249,7 @@
 
 /* Configured limits on output */
 static unsigned long max_depth = 10;
-static unsigned long max_packsize = (1LL << 32) - 1;
+static off_t max_packsize = (1LL << 32) - 1;
 static int force_update;
 
 /* Stats and misc. counters */
@@ -634,7 +630,7 @@
 	int pack_fd;
 
 	snprintf(tmpfile, sizeof(tmpfile),
-		"%s/pack_XXXXXX", get_object_directory());
+		"%s/tmp_pack_XXXXXX", get_object_directory());
 	pack_fd = mkstemp(tmpfile);
 	if (pack_fd < 0)
 		die("Can't create %s: %s", tmpfile, strerror(errno));
@@ -734,7 +730,7 @@
 	}
 
 	snprintf(tmpfile, sizeof(tmpfile),
-		"%s/index_XXXXXX", get_object_directory());
+		"%s/tmp_idx_XXXXXX", get_object_directory());
 	idx_fd = mkstemp(tmpfile);
 	if (idx_fd < 0)
 		die("Can't create %s: %s", tmpfile, strerror(errno));
@@ -757,7 +753,7 @@
 static char *keep_pack(char *curr_index_name)
 {
 	static char name[PATH_MAX];
-	static char *keep_msg = "fast-import";
+	static const char *keep_msg = "fast-import";
 	int keep_fd;
 
 	chmod(pack_data->pack_name, 0444);
@@ -893,7 +889,7 @@
 	SHA_CTX c;
 	z_stream s;
 
-	hdrlen = sprintf((char*)hdr,"%s %lu", type_names[type],
+	hdrlen = sprintf((char*)hdr,"%s %lu", typename(type),
 		(unsigned long)datlen) + 1;
 	SHA1_Init(&c);
 	SHA1_Update(&c, hdr, hdrlen);
@@ -1010,11 +1006,11 @@
 	struct object_entry *oe,
 	unsigned long *sizep)
 {
-	static char type[20];
+	enum object_type type;
 	struct packed_git *p = all_packs[oe->pack_id];
 	if (p == pack_data)
 		p->pack_size = pack_size + 20;
-	return unpack_entry(p, oe->offset, type, sizep);
+	return unpack_entry(p, oe->offset, &type, sizep);
 }
 
 static const char *get_mode(const char *str, uint16_t *modep)
@@ -1051,9 +1047,9 @@
 		t->delta_depth = 0;
 		buf = gfi_unpack_entry(myoe, &size);
 	} else {
-		char type[20];
-		buf = read_sha1_file(sha1, type, &size);
-		if (!buf || strcmp(type, tree_type))
+		enum object_type type;
+		buf = read_sha1_file(sha1, &type, &size);
+		if (!buf || type != OBJ_TREE)
 			die("Can't load tree %s", sha1_to_hex(sha1));
 	}
 
@@ -1070,7 +1066,7 @@
 		if (!c)
 			die("Corrupt mode in %s", sha1_to_hex(sha1));
 		e->versions[0].mode = e->versions[1].mode;
-		e->name = to_atom(c, (unsigned short)strlen(c));
+		e->name = to_atom(c, strlen(c));
 		c += e->name->str_len + 1;
 		hashcpy(e->versions[0].sha1, (unsigned char*)c);
 		hashcpy(e->versions[1].sha1, (unsigned char*)c);
@@ -1231,7 +1227,7 @@
 	if (t->entry_count == t->entry_capacity)
 		root->tree = t = grow_tree_content(t, t->entry_count);
 	e = new_tree_entry();
-	e->name = to_atom(p, (unsigned short)n);
+	e->name = to_atom(p, n);
 	e->versions[0].mode = 0;
 	hashclr(e->versions[0].sha1);
 	t->entries[t->entry_count++] = e;
@@ -1314,9 +1310,9 @@
 			return error("Branch %s is missing commits.", b->name);
 		}
 
-		if (!in_merge_bases(old_cmit, new_cmit)) {
+		if (!in_merge_bases(old_cmit, &new_cmit, 1)) {
 			unlock_ref(lock);
-			warn("Not updating %s"
+			warning("Not updating %s"
 				" (new tip %s does not contain %s)",
 				b->name, sha1_to_hex(b->sha1), sha1_to_hex(old_sha1));
 			return -1;
@@ -1375,16 +1371,33 @@
 
 static void dump_marks(void)
 {
-	if (mark_file)
-	{
-		FILE *f = fopen(mark_file, "w");
-		if (f) {
-			dump_marks_helper(f, 0, marks);
-			fclose(f);
-		} else
-			failure |= error("Unable to write marks file %s: %s",
-				mark_file, strerror(errno));
+	static struct lock_file mark_lock;
+	int mark_fd;
+	FILE *f;
+
+	if (!mark_file)
+		return;
+
+	mark_fd = hold_lock_file_for_update(&mark_lock, mark_file, 0);
+	if (mark_fd < 0) {
+		failure |= error("Unable to write marks file %s: %s",
+			mark_file, strerror(errno));
+		return;
 	}
+
+	f = fdopen(mark_fd, "w");
+	if (!f) {
+		rollback_lock_file(&mark_lock);
+		failure |= error("Unable to write marks file %s: %s",
+			mark_file, strerror(errno));
+		return;
+	}
+
+	dump_marks_helper(f, 0, marks);
+	fclose(f);
+	if (commit_lock_file(&mark_lock))
+		failure |= error("Unable to write marks file %s: %s",
+			mark_file, strerror(errno));
 }
 
 static void read_next_command(void)
@@ -1394,7 +1407,7 @@
 
 static void cmd_mark(void)
 {
-	if (!strncmp("mark :", command_buf.buf, 6)) {
+	if (!prefixcmp(command_buf.buf, "mark :")) {
 		next_mark = strtoumax(command_buf.buf + 6, NULL, 10);
 		read_next_command();
 	}
@@ -1407,10 +1420,10 @@
 	size_t length;
 	char *buffer;
 
-	if (strncmp("data ", command_buf.buf, 5))
+	if (prefixcmp(command_buf.buf, "data "))
 		die("Expected 'data n' command, found: %s", command_buf.buf);
 
-	if (!strncmp("<<", command_buf.buf + 5, 2)) {
+	if (!prefixcmp(command_buf.buf + 5, "<<")) {
 		char *term = xstrdup(command_buf.buf + 5 + 2);
 		size_t sz = 8192, term_len = command_buf.len - 5 - 2;
 		length = 0;
@@ -1531,7 +1544,7 @@
 {
 	while (cur_active_branches
 		&& cur_active_branches >= max_active_branches) {
-		unsigned long min_commit = ULONG_MAX;
+		uintmax_t min_commit = ULONG_MAX;
 		struct branch *e, *l = NULL, *p = NULL;
 
 		for (e = active_branches; e; e = e->active_next_branch) {
@@ -1579,7 +1592,6 @@
 	struct object_entry *oe = oe;
 	unsigned char sha1[20];
 	uint16_t mode, inline_data = 0;
-	char type[20];
 
 	p = get_mode(p, &mode);
 	if (!p)
@@ -1601,7 +1613,7 @@
 		oe = find_mark(strtoumax(p + 1, &x, 10));
 		hashcpy(sha1, oe->sha1);
 		p = x;
-	} else if (!strncmp("inline", p, 6)) {
+	} else if (!prefixcmp(p, "inline")) {
 		inline_data = 1;
 		p += 6;
 	} else {
@@ -1632,13 +1644,14 @@
 	} else if (oe) {
 		if (oe->type != OBJ_BLOB)
 			die("Not a blob (actually a %s): %s",
-				command_buf.buf, type_names[oe->type]);
+				command_buf.buf, typename(oe->type));
 	} else {
-		if (sha1_object_info(sha1, type, NULL))
+		enum object_type type = sha1_object_info(sha1, NULL);
+		if (type < 0)
 			die("Blob not found: %s", command_buf.buf);
-		if (strcmp(blob_type, type))
+		if (type != OBJ_BLOB)
 			die("Not a blob (actually a %s): %s",
-				command_buf.buf, type);
+			    typename(type), command_buf.buf);
 	}
 
 	tree_content_set(&b->branch_tree, p, sha1, S_IFREG | mode);
@@ -1674,7 +1687,7 @@
 	const char *from;
 	struct branch *s;
 
-	if (strncmp("from ", command_buf.buf, 5))
+	if (prefixcmp(command_buf.buf, "from "))
 		return;
 
 	if (b->branch_tree.tree) {
@@ -1717,7 +1730,7 @@
 			char *buf;
 
 			buf = read_object_with_reference(b->sha1,
-				type_names[OBJ_COMMIT], &size, b->sha1);
+				commit_type, &size, b->sha1);
 			if (!buf || size < 46)
 				die("Not a valid commit: %s", from);
 			if (memcmp("tree ", buf, 5)
@@ -1740,7 +1753,7 @@
 	struct branch *s;
 
 	*count = 0;
-	while (!strncmp("merge ", command_buf.buf, 6)) {
+	while (!prefixcmp(command_buf.buf, "merge ")) {
 		from = strchr(command_buf.buf, ' ') + 1;
 		n = xmalloc(sizeof(*n));
 		s = lookup_branch(from);
@@ -1755,7 +1768,7 @@
 		} else if (!get_sha1(from, n->sha1)) {
 			unsigned long size;
 			char *buf = read_object_with_reference(n->sha1,
-				type_names[OBJ_COMMIT], &size, n->sha1);
+				commit_type, &size, n->sha1);
 			if (!buf || size < 46)
 				die("Not a valid commit: %s", from);
 			free(buf);
@@ -1793,11 +1806,11 @@
 
 	read_next_command();
 	cmd_mark();
-	if (!strncmp("author ", command_buf.buf, 7)) {
+	if (!prefixcmp(command_buf.buf, "author ")) {
 		author = parse_ident(command_buf.buf + 7);
 		read_next_command();
 	}
-	if (!strncmp("committer ", command_buf.buf, 10)) {
+	if (!prefixcmp(command_buf.buf, "committer ")) {
 		committer = parse_ident(command_buf.buf + 10);
 		read_next_command();
 	}
@@ -1818,9 +1831,9 @@
 	for (;;) {
 		if (1 == command_buf.len)
 			break;
-		else if (!strncmp("M ", command_buf.buf, 2))
+		else if (!prefixcmp(command_buf.buf, "M "))
 			file_change_m(b);
-		else if (!strncmp("D ", command_buf.buf, 2))
+		else if (!prefixcmp(command_buf.buf, "D "))
 			file_change_d(b);
 		else if (!strcmp("deleteall", command_buf.buf))
 			file_change_deleteall(b);
@@ -1890,7 +1903,7 @@
 	read_next_command();
 
 	/* from ... */
-	if (strncmp("from ", command_buf.buf, 5))
+	if (prefixcmp(command_buf.buf, "from "))
 		die("Expected from command, got %s", command_buf.buf);
 	from = strchr(command_buf.buf, ' ') + 1;
 	s = lookup_branch(from);
@@ -1908,7 +1921,7 @@
 		char *buf;
 
 		buf = read_object_with_reference(sha1,
-			type_names[OBJ_COMMIT], &size, sha1);
+			commit_type, &size, sha1);
 		if (!buf || size < 46)
 			die("Not a valid commit: %s", from);
 		free(buf);
@@ -1917,7 +1930,7 @@
 	read_next_command();
 
 	/* tagger ... */
-	if (strncmp("tagger ", command_buf.buf, 7))
+	if (prefixcmp(command_buf.buf, "tagger "))
 		die("Expected tagger command, got %s", command_buf.buf);
 	tagger = parse_ident(command_buf.buf + 7);
 
@@ -1929,7 +1942,7 @@
 	size_dbuf(&new_data, 67+strlen(t->name)+strlen(tagger)+msglen);
 	sp = new_data.buffer;
 	sp += sprintf(sp, "object %s\n", sha1_to_hex(sha1));
-	sp += sprintf(sp, "type %s\n", type_names[OBJ_COMMIT]);
+	sp += sprintf(sp, "type %s\n", commit_type);
 	sp += sprintf(sp, "tag %s\n", t->name);
 	sp += sprintf(sp, "tagger %s\n", tagger);
 	*sp++ = '\n';
@@ -1980,6 +1993,40 @@
 	read_next_command();
 }
 
+static void import_marks(const char *input_file)
+{
+	char line[512];
+	FILE *f = fopen(input_file, "r");
+	if (!f)
+		die("cannot read %s: %s", input_file, strerror(errno));
+	while (fgets(line, sizeof(line), f)) {
+		uintmax_t mark;
+		char *end;
+		unsigned char sha1[20];
+		struct object_entry *e;
+
+		end = strchr(line, '\n');
+		if (line[0] != ':' || !end)
+			die("corrupt mark line: %s", line);
+		*end = 0;
+		mark = strtoumax(line + 1, &end, 10);
+		if (!mark || end == line + 1
+			|| *end != ' ' || get_sha1(end + 1, sha1))
+			die("corrupt mark line: %s", line);
+		e = find_object(sha1);
+		if (!e) {
+			enum object_type type = sha1_object_info(sha1, NULL);
+			if (type < 0)
+				die("object not found: %s", sha1_to_hex(sha1));
+			e = insert_object(sha1);
+			e->type = type;
+			e->pack_id = MAX_PACK_ID;
+		}
+		insert_mark(mark, e);
+	}
+	fclose(f);
+}
+
 static const char fast_import_usage[] =
 "git-fast-import [--date-format=f] [--max-pack-size=n] [--depth=n] [--active-branches=n] [--export-marks=marks.file]";
 
@@ -1988,13 +2035,19 @@
 	int i, show_stats = 1;
 
 	git_config(git_default_config);
+	alloc_objects(object_entry_alloc);
+	strbuf_init(&command_buf);
+	atom_table = xcalloc(atom_table_sz, sizeof(struct atom_str*));
+	branch_table = xcalloc(branch_table_sz, sizeof(struct branch*));
+	avail_tree_table = xcalloc(avail_tree_table_sz, sizeof(struct avail_tree_content*));
+	marks = pool_calloc(1, sizeof(struct mark_set));
 
 	for (i = 1; i < argc; i++) {
 		const char *a = argv[i];
 
 		if (*a != '-' || !strcmp(a, "--"))
 			break;
-		else if (!strncmp(a, "--date-format=", 14)) {
+		else if (!prefixcmp(a, "--date-format=")) {
 			const char *fmt = a + 14;
 			if (!strcmp(fmt, "raw"))
 				whenspec = WHENSPEC_RAW;
@@ -2005,15 +2058,17 @@
 			else
 				die("unknown --date-format argument %s", fmt);
 		}
-		else if (!strncmp(a, "--max-pack-size=", 16))
+		else if (!prefixcmp(a, "--max-pack-size="))
 			max_packsize = strtoumax(a + 16, NULL, 0) * 1024 * 1024;
-		else if (!strncmp(a, "--depth=", 8))
+		else if (!prefixcmp(a, "--depth="))
 			max_depth = strtoul(a + 8, NULL, 0);
-		else if (!strncmp(a, "--active-branches=", 18))
+		else if (!prefixcmp(a, "--active-branches="))
 			max_active_branches = strtoul(a + 18, NULL, 0);
-		else if (!strncmp(a, "--export-marks=", 15))
+		else if (!prefixcmp(a, "--import-marks="))
+			import_marks(a + 15);
+		else if (!prefixcmp(a, "--export-marks="))
 			mark_file = a + 15;
-		else if (!strncmp(a, "--export-pack-edges=", 20)) {
+		else if (!prefixcmp(a, "--export-pack-edges=")) {
 			if (pack_edges)
 				fclose(pack_edges);
 			pack_edges = fopen(a + 20, "a");
@@ -2031,14 +2086,6 @@
 	if (i != argc)
 		usage(fast_import_usage);
 
-	alloc_objects(object_entry_alloc);
-	strbuf_init(&command_buf);
-
-	atom_table = xcalloc(atom_table_sz, sizeof(struct atom_str*));
-	branch_table = xcalloc(branch_table_sz, sizeof(struct branch*));
-	avail_tree_table = xcalloc(avail_tree_table_sz, sizeof(struct avail_tree_content*));
-	marks = pool_calloc(1, sizeof(struct mark_set));
-
 	start_packfile();
 	for (;;) {
 		read_next_command();
@@ -2046,11 +2093,11 @@
 			break;
 		else if (!strcmp("blob", command_buf.buf))
 			cmd_new_blob();
-		else if (!strncmp("commit ", command_buf.buf, 7))
+		else if (!prefixcmp(command_buf.buf, "commit "))
 			cmd_new_commit();
-		else if (!strncmp("tag ", command_buf.buf, 4))
+		else if (!prefixcmp(command_buf.buf, "tag "))
 			cmd_new_tag();
-		else if (!strncmp("reset ", command_buf.buf, 6))
+		else if (!prefixcmp(command_buf.buf, "reset "))
 			cmd_reset_branch();
 		else if (!strcmp("checkpoint", command_buf.buf))
 			cmd_checkpoint();
diff --git a/fetch-pack.c b/fetch-pack.c
index c787106..06f4aec 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -15,8 +15,9 @@
 static int verbose;
 static int fetch_all;
 static int depth;
+static int no_progress;
 static const char fetch_pack_usage[] =
-"git-fetch-pack [--all] [--quiet|-q] [--keep|-k] [--thin] [--upload-pack=<git-upload-pack>] [--depth=<n>] [-v] [<host>:]<directory> [<refs>...]";
+"git-fetch-pack [--all] [--quiet|-q] [--keep|-k] [--thin] [--upload-pack=<git-upload-pack>] [--depth=<n>] [--no-progress] [-v] [<host>:]<directory> [<refs>...]";
 static const char *uploadpack = "git-upload-pack";
 
 #define COMPLETE	(1U << 0)
@@ -173,12 +174,13 @@
 		}
 
 		if (!fetching)
-			packet_write(fd[1], "want %s%s%s%s%s%s\n",
+			packet_write(fd[1], "want %s%s%s%s%s%s%s\n",
 				     sha1_to_hex(remote),
 				     (multi_ack ? " multi_ack" : ""),
 				     (use_sideband == 2 ? " side-band-64k" : ""),
 				     (use_sideband == 1 ? " side-band" : ""),
 				     (use_thin_pack ? " thin-pack" : ""),
+				     (no_progress ? " no-progress" : ""),
 				     " ofs-delta");
 		else
 			packet_write(fd[1], "want %s\n", sha1_to_hex(remote));
@@ -198,13 +200,13 @@
 		int len;
 
 		while ((len = packet_read_line(fd[0], line, sizeof(line)))) {
-			if (!strncmp("shallow ", line, 8)) {
+			if (!prefixcmp(line, "shallow ")) {
 				if (get_sha1_hex(line + 8, sha1))
 					die("invalid shallow line: %s", line);
 				register_shallow(sha1);
 				continue;
 			}
-			if (!strncmp("unshallow ", line, 10)) {
+			if (!prefixcmp(line, "unshallow ")) {
 				if (get_sha1_hex(line + 10, sha1))
 					die("invalid unshallow line: %s", line);
 				if (!lookup_object(sha1))
@@ -346,7 +348,7 @@
 		    check_ref_format(ref->name + 5))
 			; /* trash */
 		else if (fetch_all &&
-			 (!depth || strncmp(ref->name, "refs/tags/", 10) )) {
+			 (!depth || prefixcmp(ref->name, "refs/tags/") )) {
 			*newtail = ref;
 			ref->next = NULL;
 			newtail = &ref->next;
@@ -521,7 +523,7 @@
 	if (do_keep) {
 		*av++ = "index-pack";
 		*av++ = "--stdin";
-		if (!quiet)
+		if (!quiet && !no_progress)
 			*av++ = "-v";
 		if (use_thin_pack)
 			*av++ = "--fix-thin";
@@ -683,11 +685,11 @@
 		char *arg = argv[i];
 
 		if (*arg == '-') {
-			if (!strncmp("--upload-pack=", arg, 14)) {
+			if (!prefixcmp(arg, "--upload-pack=")) {
 				uploadpack = arg + 14;
 				continue;
 			}
-			if (!strncmp("--exec=", arg, 7)) {
+			if (!prefixcmp(arg, "--exec=")) {
 				uploadpack = arg + 7;
 				continue;
 			}
@@ -712,12 +714,16 @@
 				verbose = 1;
 				continue;
 			}
-			if (!strncmp("--depth=", arg, 8)) {
+			if (!prefixcmp(arg, "--depth=")) {
 				depth = strtol(arg + 8, NULL, 0);
 				if (stat(git_path("shallow"), &st))
 					st.st_mtime = 0;
 				continue;
 			}
+			if (!strcmp("--no-progress", arg)) {
+				no_progress = 1;
+				continue;
+			}
 			usage(fetch_pack_usage);
 		}
 		dest = arg;
diff --git a/fetch.c b/fetch.c
index f69be82..8e29d31 100644
--- a/fetch.c
+++ b/fetch.c
@@ -42,8 +42,7 @@
 	if (parse_tree(tree))
 		return -1;
 
-	desc.buf = tree->buffer;
-	desc.size = tree->size;
+	init_tree_desc(&desc, tree->buffer, tree->size);
 	while (tree_entry(&desc, &entry)) {
 		struct object *obj = NULL;
 
diff --git a/git-am.sh b/git-am.sh
index 2c73d11..e69ecbf 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -290,6 +290,10 @@
 		git-mailinfo $keep $utf8 "$dotest/msg" "$dotest/patch" \
 			<"$dotest/$msgnum" >"$dotest/info" ||
 			stop_here $this
+		test -s $dotest/patch || {
+			echo "Patch is empty.  Was is split wrong?"
+			stop_here $this
+		}
 		git-stripspace < "$dotest/msg" > "$dotest/msg-clean"
 		;;
 	esac
@@ -404,12 +408,10 @@
 		# trust what the user has in the index file and the
 		# working tree.
 		resolved=
-		changed="$(git-diff-index --cached --name-only HEAD)"
-		if test '' = "$changed"
-		then
+		git-diff-index --quiet --cached HEAD && {
 			echo "No changes - did you forget to use 'git add'?"
 			stop_here_user_resolve $this
-		fi
+		}
 		unmerged=$(git-ls-files -u)
 		if test -n "$unmerged"
 		then
@@ -431,13 +433,11 @@
 		then
 		    # Applying the patch to an earlier tree and merging the
 		    # result may have produced the same tree as ours.
-		    changed="$(git-diff-index --cached --name-only HEAD)"
-		    if test '' = "$changed"
-		    then
-			    echo No changes -- Patch already applied.
-			    go_next
-			    continue
-		    fi
+		    git-diff-index --quiet --cached HEAD && {
+			echo No changes -- Patch already applied.
+			go_next
+			continue
+		    }
 		    # clear apply_status -- we have successfully merged.
 		    apply_status=0
 		fi
diff --git a/git-applymbox.sh b/git-applymbox.sh
index 1f68599..3efd6a7 100755
--- a/git-applymbox.sh
+++ b/git-applymbox.sh
@@ -77,6 +77,10 @@
     *)
 	    git-mailinfo $keep_subject $utf8 \
 		.dotest/msg .dotest/patch <$i >.dotest/info || exit 1
+	    test -s .dotest/patch || {
+		echo "Patch is empty.  Was is split wrong?"
+		exit 1
+	    }
 	    git-stripspace < .dotest/msg > .dotest/msg-clean
 	    ;;
     esac
diff --git a/git-archimport.perl b/git-archimport.perl
index 0fcb156..c1e7c1d 100755
--- a/git-archimport.perl
+++ b/git-archimport.perl
@@ -89,7 +89,11 @@
 # values associated with keys:
 #   =1 - Arch version / git 'branch' detected via abrowse on a limit
 #   >1 - Arch version / git 'branch' of an auxiliary branch we've merged
-my %arch_branches = map { $_ => 1 } @ARGV;
+my %arch_branches = map { my $branch = $_; $branch =~ s/:[^:]*$//; $branch => 1 } @ARGV;
+
+# $branch_name_map:
+# maps arch branches to git branch names
+my %branch_name_map = map { m/^(.*):([^:]*)$/; $1 => $2 } grep { m/:/ } @ARGV;
 
 $ENV{'TMPDIR'} = $opt_t if $opt_t; # $ENV{TMPDIR} will affect tempdir() calls:
 my $tmp = tempdir('git-archimport-XXXXXX', TMPDIR => 1, CLEANUP => 1);
@@ -104,6 +108,7 @@
     closedir DIR
 }
 
+my $default_archive;		# default Arch archive
 my %reachable = ();             # Arch repositories we can access
 my %unreachable = ();           # Arch repositories we can't access :<
 my @psets  = ();                # the collection
@@ -303,7 +308,34 @@
     return $ret;
 }
 
-*git_branchname = $opt_o ? *old_style_branchname : *tree_dirname;
+*git_default_branchname = $opt_o ? *old_style_branchname : *tree_dirname;
+
+# retrieve default archive, since $branch_name_map keys might not include it
+sub get_default_archive {
+    if (!defined $default_archive) {
+        $default_archive = safe_pipe_capture($TLA,'my-default-archive');
+        chomp $default_archive;
+    }
+    return $default_archive;
+}
+
+sub git_branchname {
+    my $revision = shift;
+    my $name = extract_versionname($revision);
+
+    if (exists $branch_name_map{$name}) {
+	return $branch_name_map{$name};
+
+    } elsif ($name =~ m#^([^/]*)/(.*)$#
+	     && $1 eq get_default_archive()
+	     && exists $branch_name_map{$2}) {
+	# the names given in the command-line lacked the archive.
+	return $branch_name_map{$2};
+
+    } else {
+	return git_default_branchname($revision);
+    }
+}
 
 sub process_patchset_accurate {
     my $ps = shift;
@@ -333,19 +365,23 @@
         if ($ps->{tag} && (my $branchpoint = eval { ptag($ps->{tag}) })) {
             
             # find where we are supposed to branch from
-            system('git-checkout','-f','-b',$ps->{branch},
-                            $branchpoint) == 0 or die "$! $?\n";
-            
+	    if (! -e "$git_dir/refs/heads/$ps->{branch}") {
+		system('git-branch',$ps->{branch},$branchpoint) == 0 or die "$! $?\n";
+
+		# We trust Arch with the fact that this is just a tag,
+		# and it does not affect the state of the tree, so
+		# we just tag and move on.  If the user really wants us
+		# to consolidate more branches into one, don't tag because
+		# the tag name would be already taken.
+		tag($ps->{id}, $branchpoint);
+		ptag($ps->{id}, $branchpoint);
+		print " * Tagged $ps->{id} at $branchpoint\n";
+	    }
+	    system('git-checkout','-f',$ps->{branch}) == 0 or die "$! $?\n";
+
             # remove any old stuff that got leftover:
             my $rm = safe_pipe_capture('git-ls-files','--others','-z');
             rmtree(split(/\0/,$rm)) if $rm;
-
-            # If we trust Arch with the fact that this is just 
-            # a tag, and it does not affect the state of the tree
-            # then we just tag and move on
-            tag($ps->{id}, $branchpoint);
-            ptag($ps->{id}, $branchpoint);
-            print " * Tagged $ps->{id} at $branchpoint\n";
             return 0;
         } else {
             warn "Tagging from unknown id unsupported\n" if $ps->{tag};
@@ -385,14 +421,19 @@
                 unless $branchpoint;
             
             # find where we are supposed to branch from
-            system('git-checkout','-b',$ps->{branch},$branchpoint);
+	    if (! -e "$git_dir/refs/heads/$ps->{branch}") {
+		system('git-branch',$ps->{branch},$branchpoint) == 0 or die "$! $?\n";
 
-            # If we trust Arch with the fact that this is just 
-            # a tag, and it does not affect the state of the tree
-            # then we just tag and move on
-            tag($ps->{id}, $branchpoint);
-            ptag($ps->{id}, $branchpoint);
-            print " * Tagged $ps->{id} at $branchpoint\n";
+		# We trust Arch with the fact that this is just a tag,
+		# and it does not affect the state of the tree, so
+		# we just tag and move on.  If the user really wants us
+		# to consolidate more branches into one, don't tag because
+		# the tag name would be already taken.
+		tag($ps->{id}, $branchpoint);
+		ptag($ps->{id}, $branchpoint);
+		print " * Tagged $ps->{id} at $branchpoint\n";
+            }
+            system('git-checkout',$ps->{branch}) == 0 or die "$! $?\n";
             return 0;
         } 
         die $! if $?;
@@ -830,8 +871,9 @@
     if ($opt_o) {
         $tag =~ s|/|--|g;
     } else {
-        # don't use subdirs for tags yet, it could screw up other porcelains
-        $tag =~ s|/|,|g;
+	my $patchname = $tag;
+	$patchname =~ s/.*--//;
+        $tag = git_branchname ($tag) . '--' . $patchname;
     }
     
     if ($commit) {
diff --git a/git-bisect.sh b/git-bisect.sh
index b1c3a6b..11313a7 100755
--- a/git-bisect.sh
+++ b/git-bisect.sh
@@ -1,14 +1,15 @@
 #!/bin/sh
 
-USAGE='[start|bad|good|next|reset|visualize|replay|log]'
+USAGE='[start|bad|good|next|reset|visualize|replay|log|run]'
 LONG_USAGE='git bisect start [<pathspec>]	reset bisect state and start bisection.
 git bisect bad [<rev>]		mark <rev> a known-bad revision.
 git bisect good [<rev>...]	mark <rev>... known-good revisions.
 git bisect next			find next bisection to test and check it out.
 git bisect reset [<branch>]	finish bisection search and go back to branch.
 git bisect visualize            show bisect status in gitk.
-git bisect replay <logfile>	replay bisection log
-git bisect log			show bisect log.'
+git bisect replay <logfile>	replay bisection log.
+git bisect log			show bisect log.
+git bisect run <cmd>... 	use <cmd>... to automatically bisect.'
 
 . git-sh-setup
 require_work_tree
@@ -49,7 +50,7 @@
 	head=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD) ||
 	die "Bad HEAD - I need a symbolic ref"
 	case "$head" in
-	refs/heads/bisect*)
+	refs/heads/bisect)
 		if [ -s "$GIT_DIR/head-name" ]; then
 		    branch=`cat "$GIT_DIR/head-name"`
 		else
@@ -85,7 +86,7 @@
 	0)
 		rev=$(git-rev-parse --verify HEAD) ;;
 	1)
-		rev=$(git-rev-parse --verify "$1") ;;
+		rev=$(git-rev-parse --verify "$1^{commit}") ;;
 	*)
 		usage ;;
 	esac || exit
@@ -104,7 +105,7 @@
 	esac
 	for rev in $revs
 	do
-		rev=$(git-rev-parse --verify "$rev") || exit
+		rev=$(git-rev-parse --verify "$rev^{commit}") || exit
 		echo "$rev" >"$GIT_DIR/refs/bisect/good-$rev"
 		echo "# good: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
 		echo "git-bisect good $rev" >>"$GIT_DIR/BISECT_LOG"
@@ -122,7 +123,15 @@
 	case "$next_ok,$1" in
 	no,) false ;;
 	no,fail)
-	    echo >&2 'You need to give me at least one good and one bad revisions.'
+	    THEN=''
+	    test -d "$GIT_DIR/refs/bisect" || {
+		echo >&2 'You need to start by "git bisect start".'
+		THEN='then '
+	    }
+	    echo >&2 'You '$THEN'need to give me at least one good' \
+		'and one bad revisions.'
+	    echo >&2 '(You can use "git bisect bad" and' \
+		'"git bisect good" for that.)'
 	    exit 1 ;;
 	*)
 	    true ;;
@@ -140,7 +149,7 @@
 	bad=$(git-rev-parse --verify refs/bisect/bad) &&
 	good=$(git-rev-parse --sq --revs-only --not \
 		$(cd "$GIT_DIR" && ls refs/bisect/good-*)) &&
-	rev=$(eval "git-rev-list --bisect $good $bad -- $(cat $GIT_DIR/BISECT_NAMES)") || exit
+	rev=$(eval "git-rev-list --bisect $good $bad -- $(cat "$GIT_DIR/BISECT_NAMES")") || exit
 	if [ -z "$rev" ]; then
 	    echo "$bad was both good and bad"
 	    exit 1
@@ -172,7 +181,7 @@
 	   else
 	       branch=master
 	   fi ;;
-	1) test -f "$GIT_DIR/refs/heads/$1" || {
+	1) git-show-ref --verify --quiet -- "refs/heads/$1" || {
 	       echo >&2 "$1 does not seem to be a valid branch"
 	       exit 1
 	   }
@@ -185,6 +194,7 @@
 		rm -f "$GIT_DIR/refs/heads/bisect" "$GIT_DIR/head-name"
 		rm -f "$GIT_DIR/BISECT_LOG"
 		rm -f "$GIT_DIR/BISECT_NAMES"
+		rm -f "$GIT_DIR/BISECT_RUN"
 	fi
 }
 
@@ -220,6 +230,52 @@
 	bisect_auto_next
 }
 
+bisect_run () {
+    bisect_next_check fail
+
+    while true
+    do
+      echo "running $@"
+      "$@"
+      res=$?
+
+      # Check for really bad run error.
+      if [ $res -lt 0 -o $res -ge 128 ]; then
+	  echo >&2 "bisect run failed:"
+	  echo >&2 "exit code $res from '$@' is < 0 or >= 128"
+	  exit $res
+      fi
+
+      # Use "bisect_good" or "bisect_bad"
+      # depending on run success or failure.
+      if [ $res -gt 0 ]; then
+	  next_bisect='bisect_bad'
+      else
+	  next_bisect='bisect_good'
+      fi
+
+      # We have to use a subshell because bisect_good or
+      # bisect_bad functions can exit.
+      ( $next_bisect > "$GIT_DIR/BISECT_RUN" )
+      res=$?
+
+      cat "$GIT_DIR/BISECT_RUN"
+
+      if [ $res -ne 0 ]; then
+	  echo >&2 "bisect run failed:"
+	  echo >&2 "$next_bisect exited with error code $res"
+	  exit $res
+      fi
+
+      if grep "is first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null; then
+	  echo "bisect run success"
+	  exit 0;
+      fi
+
+    done
+}
+
+
 case "$#" in
 0)
     usage ;;
@@ -244,6 +300,8 @@
 	bisect_replay "$@" ;;
     log)
 	cat "$GIT_DIR/BISECT_LOG" ;;
+    run)
+        bisect_run "$@" ;;
     *)
         usage ;;
     esac
diff --git a/git-checkout.sh b/git-checkout.sh
index 83b2639..a7390e8 100755
--- a/git-checkout.sh
+++ b/git-checkout.sh
@@ -12,6 +12,7 @@
 new_name=
 force=
 branch=
+track=
 newbranch=
 newbranch_log=
 merge=
@@ -33,7 +34,10 @@
 			die "git checkout: we do not like '$newbranch' as a branch name."
 		;;
 	"-l")
-		newbranch_log=1
+		newbranch_log=-l
+		;;
+	"--track"|"--no-track")
+		track="$arg"
 		;;
 	"-f")
 		force=1
@@ -85,6 +89,11 @@
     esac
 done
 
+case "$newbranch,$track" in
+,--*)
+	die "git checkout: --track and --no-track require -b"
+esac
+
 case "$force$merge" in
 11)
 	die "git checkout: -f and -m are incompatible"
@@ -154,6 +163,13 @@
 detached=
 detach_warn=
 
+describe_detached_head () {
+	test -n "$quiet" || {
+		printf >&2 "$1 "
+		GIT_PAGER= git log >&2 -1 --pretty=oneline --abbrev-commit "$2"
+	}
+}
+
 if test -z "$branch$newbranch" && test "$new" != "$old"
 then
 	detached="$new"
@@ -164,9 +180,9 @@
 (now or later) by using -b with the checkout command again. Example:
   git checkout -b <new_branch_name>"
 	fi
-elif test -z "$oldbranch" && test -z "$quiet"
+elif test -z "$oldbranch"
 then
-	echo >&2 "Previous HEAD position was $old"
+	describe_detached_head 'Previous HEAD position was' "$old"
 fi
 
 if [ "X$old" = X ]
@@ -235,18 +251,19 @@
 #
 if [ "$?" -eq 0 ]; then
 	if [ "$newbranch" ]; then
-		if [ "$newbranch_log" ]; then
-			mkdir -p $(dirname "$GIT_DIR/logs/refs/heads/$newbranch")
-			touch "$GIT_DIR/logs/refs/heads/$newbranch"
-		fi
-		git-update-ref -m "checkout: Created from $new_name" "refs/heads/$newbranch" $new || exit
+		git-branch $track $newbranch_log "$newbranch" "$new_name" || exit
 		branch="$newbranch"
 	fi
 	if test -n "$branch"
 	then
 		GIT_DIR="$GIT_DIR" git-symbolic-ref -m "checkout: moving to $branch" HEAD "refs/heads/$branch"
-		if test -z "$quiet"
+		if test -n "$quiet"
 		then
+			true	# nothing
+		elif test "refs/heads/$branch" = "$oldbranch"
+		then
+			echo >&2 "Already on branch \"$branch\""
+		else
 			echo >&2 "Switched to${newbranch:+ a new} branch \"$branch\""
 		fi
 	elif test -n "$detached"
@@ -265,6 +282,7 @@
 		then
 			echo >&2 "$detach_warn"
 		fi
+		describe_detached_head 'HEAD is now at' HEAD
 	fi
 	rm -f "$GIT_DIR/MERGE_HEAD"
 else
diff --git a/git-clone.sh b/git-clone.sh
index 1bd54de..513b574 100755
--- a/git-clone.sh
+++ b/git-clone.sh
@@ -42,6 +42,7 @@
 	http_fetch "$1/info/refs" "$clone_tmp/refs" ||
 		die "Cannot get remote repository information.
 Perhaps git-update-server-info needs to be run there?"
+	test "z$quiet" = z && v=-v || v=
 	while read sha1 refname
 	do
 		name=`expr "z$refname" : 'zrefs/\(.*\)'` &&
@@ -59,7 +60,7 @@
 		else
 			tname=$name
 		fi
-		git-http-fetch -v -a -w "$tname" "$name" "$1/" || exit 1
+		git-http-fetch $v -a -w "$tname" "$name" "$1" || exit 1
 	done <"$clone_tmp/refs"
 	rm -fr "$clone_tmp"
 	http_fetch "$1/HEAD" "$GIT_DIR/REMOTE_HEAD" ||
@@ -79,6 +80,8 @@
 origin_override=
 use_separate_remote=t
 depth=
+no_progress=
+test -t 1 || no_progress=--no-progress
 while
 	case "$#,$1" in
 	0,*) break ;;
@@ -290,8 +293,8 @@
 		;;
 	*)
 		case "$upload_pack" in
-		'') git-fetch-pack --all -k $quiet $depth "$repo" ;;
-		*) git-fetch-pack --all -k $quiet "$upload_pack" $depth "$repo" ;;
+		'') git-fetch-pack --all -k $quiet $depth $no_progress "$repo";;
+		*) git-fetch-pack --all -k $quiet "$upload_pack" $depth $no_progress "$repo" ;;
 		esac >"$GIT_DIR/CLONE_HEAD" ||
 			die "fetch-pack from '$repo' failed."
 		;;
@@ -393,7 +396,7 @@
 
 	case "$no_checkout" in
 	'')
-		test "z$quiet" = z && v=-v || v=
+		test "z$quiet" = z -a "z$no_progress" = z && v=-v || v=
 		git-read-tree -m -u $v HEAD HEAD
 	esac
 fi
diff --git a/git-commit.sh b/git-commit.sh
index fdaedc0..292cf96 100755
--- a/git-commit.sh
+++ b/git-commit.sh
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Linus Torvalds
 # Copyright (c) 2006 Junio C Hamano
 
-USAGE='[-a] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit> | --amend] [-u] [-e] [--author <author>] [[-i | -o] <path>...]'
+USAGE='[-a | --interactive] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit> | --amend] [-u] [-e] [--author <author>] [[-i | -o] <path>...]'
 SUBDIRECTORY_OK=Yes
 . git-sh-setup
 require_work_tree
@@ -13,10 +13,10 @@
 case "$0" in
 *status)
 	status_only=t
-	unmerged_ok_if_status=--unmerged ;;
+	;;
 *commit)
 	status_only=
-	unmerged_ok_if_status= ;;
+	;;
 esac
 
 refuse_partial () {
@@ -71,6 +71,7 @@
 
 all=
 also=
+interactive=
 only=
 logfile=
 use_commit=
@@ -131,6 +132,11 @@
 		also=t
 		shift
 		;;
+	--int|--inte|--inter|--intera|--interac|--interact|--interacti|\
+	--interactiv|--interactive)
+		interactive=t
+		shift
+		;;
 	-o|--o|--on|--onl|--only)
 		only=t
 		shift
@@ -304,12 +310,14 @@
 	;;
 esac
 unset only
-case "$all,$also,$#" in
-t,t,*)
-	die "Cannot use -a and -i at the same time." ;;
+case "$all,$interactive,$also,$#" in
+*t,*t,*)
+	die "Cannot use -a, --interactive or -i at the same time." ;;
 t,,[1-9]*)
 	die "Paths with -a does not make sense." ;;
-,t,0)
+,t,[1-9]*)
+	die "Paths with --interactive does not make sense." ;;
+,,t,0)
 	die "No paths with -i does not make sense." ;;
 esac
 
@@ -344,6 +352,9 @@
 	) || exit
 	;;
 ,)
+	if test "$interactive" = t; then
+		git add --interactive || exit
+	fi
 	case "$#" in
 	0)
 		;; # commit as-is
@@ -393,16 +404,17 @@
 	USE_INDEX="$THIS_INDEX"
 fi
 
-GIT_INDEX_FILE="$USE_INDEX" \
-	git-update-index -q $unmerged_ok_if_status --refresh || exit
-
-################################################################
-# If the request is status, just show it and exit.
-
-case "$0" in
-*status)
+case "$status_only" in
+t)
+	# This will silently fail in a read-only repository, which is
+	# what we want.
+	GIT_INDEX_FILE="$USE_INDEX" git-update-index -q --unmerged --refresh
 	run_status
 	exit $?
+	;;
+'')
+	GIT_INDEX_FILE="$USE_INDEX" git-update-index -q --refresh || exit
+	;;
 esac
 
 ################################################################
diff --git a/git-compat-util.h b/git-compat-util.h
index 309240b..139fc19 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -1,6 +1,8 @@
 #ifndef GIT_COMPAT_UTIL_H
 #define GIT_COMPAT_UTIL_H
 
+#define _FILE_OFFSET_BITS 64
+
 #ifndef FLEX_ARRAY
 #if defined(__GNUC__) && (__GNUC__ < 3)
 #define FLEX_ARRAY 0
@@ -68,6 +70,10 @@
 #define PATH_MAX 4096
 #endif
 
+#ifndef PRIuMAX
+#define PRIuMAX "llu"
+#endif
+
 #ifdef __GNUC__
 #define NORETURN __attribute__((__noreturn__))
 #else
@@ -81,7 +87,7 @@
 extern void usage(const char *err) NORETURN;
 extern void die(const char *err, ...) NORETURN __attribute__((format (printf, 1, 2)));
 extern int error(const char *err, ...) __attribute__((format (printf, 1, 2)));
-extern void warn(const char *err, ...) __attribute__((format (printf, 1, 2)));
+extern void warning(const char *err, ...) __attribute__((format (printf, 1, 2)));
 
 extern void set_usage_routine(void (*routine)(const char *err) NORETURN);
 extern void set_die_routine(void (*routine)(const char *err, va_list params) NORETURN);
@@ -252,6 +258,11 @@
 	}
 }
 
+static inline size_t xsize_t(off_t len)
+{
+	return (size_t)len;
+}
+
 static inline int has_extension(const char *filename, const char *ext)
 {
 	size_t len = strlen(filename);
@@ -285,4 +296,9 @@
 	return x;
 }
 
+static inline int prefixcmp(const char *str, const char *prefix)
+{
+	return strncmp(str, prefix, strlen(prefix));
+}
+
 #endif
diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl
index 32a4883..67224b4 100755
--- a/git-cvsexportcommit.perl
+++ b/git-cvsexportcommit.perl
@@ -15,14 +15,21 @@
     die "GIT_DIR is not defined or is unreadable";
 }
 
-our ($opt_h, $opt_P, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m );
+our ($opt_h, $opt_P, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m, $opt_d);
 
-getopts('hPpvcfam:');
+getopts('hPpvcfam:d:');
 
 $opt_h && usage();
 
 die "Need at least one commit identifier!" unless @ARGV;
 
+my @cvs;
+if ($opt_d) {
+	@cvs = ('cvs', '-d', $opt_d);
+} else {
+	@cvs = ('cvs');
+}
+
 # setup a tempdir
 our ($tmpdir, $tmpdirname) = tempdir('git-cvsapplycommit-XXXXXX',
 				     TMPDIR => 1,
@@ -160,7 +167,7 @@
 	my $p = $1;
 	next if (grep { $_ eq $p } @dirs);
     }
-    my @status = grep(m/^File/,  safe_pipe_capture('cvs', '-q', 'status' ,$f));
+    my @status = grep(m/^File/,  safe_pipe_capture(@cvs, '-q', 'status' ,$f));
     if (@status > 1) { warn 'Strange! cvs status returned more than one line?'};
     if (-d dirname $f and $status[0] !~ m/Status: Unknown$/
 	and $status[0] !~ m/^File: no file /) {
@@ -173,7 +180,7 @@
 foreach my $f (@files) {
     next if grep { $_ eq $f } @afiles;
     # TODO:we need to handle removed in cvs
-    my @status = grep(m/^File/,  safe_pipe_capture('cvs', '-q', 'status' ,$f));
+    my @status = grep(m/^File/,  safe_pipe_capture(@cvs, '-q', 'status' ,$f));
     if (@status > 1) { warn 'Strange! cvs status returned more than one line?'};
     unless ($status[0] =~ m/Status: Up-to-date$/) {
 	$dirty = 1;
@@ -194,7 +201,7 @@
 print "Patch applied successfully. Adding new files and directories to CVS\n";
 my $dirtypatch = 0;
 foreach my $d (@dirs) {
-    if (system('cvs','add',$d)) {
+    if (system(@cvs,'add',$d)) {
 	$dirtypatch = 1;
 	warn "Failed to cvs add directory $d -- you may need to do it manually";
     }
@@ -202,9 +209,9 @@
 
 foreach my $f (@afiles) {
     if (grep { $_ eq $f } @bfiles) {
-      system('cvs', 'add','-kb',$f);
+      system(@cvs, 'add','-kb',$f);
     } else {
-      system('cvs', 'add', $f);
+      system(@cvs, 'add', $f);
     }
     if ($?) {
 	$dirtypatch = 1;
@@ -213,7 +220,7 @@
 }
 
 foreach my $f (@dfiles) {
-    system('cvs', 'rm', '-f', $f);
+    system(@cvs, 'rm', '-f', $f);
     if ($?) {
 	$dirtypatch = 1;
 	warn "Failed to cvs rm -f $f -- you may need to do it manually";
@@ -223,7 +230,7 @@
 print "Commit to CVS\n";
 print "Patch title (first comment line): $title\n";
 my @commitfiles = map { unless (m/\s/) { '\''.$_.'\''; } else { $_; }; } (@files);
-my $cmd = "cvs commit -F .msg @commitfiles";
+my $cmd = join(' ', @cvs)." commit -F .msg @commitfiles";
 
 if ($dirtypatch) {
     print "NOTE: One or more hunks failed to apply cleanly.\n";
@@ -236,7 +243,7 @@
 
 if ($opt_c) {
     print "Autocommit\n  $cmd\n";
-    print safe_pipe_capture('cvs', 'commit', '-F', '.msg', @files);
+    print safe_pipe_capture(@cvs, 'commit', '-F', '.msg', @files);
     if ($?) {
 	die "Exiting: The commit did not succeed";
     }
diff --git a/git-cvsserver.perl b/git-cvsserver.perl
index 9371788..68aa752 100755
--- a/git-cvsserver.perl
+++ b/git-cvsserver.perl
@@ -374,7 +374,8 @@
 
         print "Checked-in $dirpart\n";
         print "$filename\n";
-        print "/$filepart/0///\n";
+        my $kopts = kopts_from_path($filepart);
+        print "/$filepart/0//$kopts/\n";
 
         $addcount++;
     }
@@ -455,7 +456,8 @@
 
         print "Checked-in $dirpart\n";
         print "$filename\n";
-        print "/$filepart/-1.$wrev///\n";
+        my $kopts = kopts_from_path($filepart);
+        print "/$filepart/-1.$wrev//$kopts/\n";
 
         $rmcount++;
     }
@@ -726,7 +728,8 @@
        print $state->{CVSROOT} . "/$module/" . ( defined ( $git->{dir} ) and $git->{dir} ne "./" ? $git->{dir} . "/" : "" ) . "$git->{name}\n";
 
         # this is an "entries" line
-        print "/$git->{name}/1.$git->{revision}///\n";
+        my $kopts = kopts_from_path($git->{name});
+        print "/$git->{name}/1.$git->{revision}//$kopts/\n";
         # permissions
         print "u=$git->{mode},g=$git->{mode},o=$git->{mode}\n";
 
@@ -917,8 +920,9 @@
 		print $state->{CVSROOT} . "/$state->{module}/$filename\n";
 
 		# this is an "entries" line
-		$log->debug("/$filepart/1.$meta->{revision}///");
-		print "/$filepart/1.$meta->{revision}///\n";
+		my $kopts = kopts_from_path($filepart);
+		$log->debug("/$filepart/1.$meta->{revision}//$kopts/");
+		print "/$filepart/1.$meta->{revision}//$kopts/\n";
 
 		# permissions
 		$log->debug("SEND : u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}");
@@ -943,6 +947,7 @@
 
             # we need to merge with the local changes ( M=successful merge, C=conflict merge )
             $log->info("Merging $file_local, $file_old, $file_new");
+            print "M Merging differences between 1.$oldmeta->{revision} and 1.$meta->{revision} into $filename\n";
 
             $log->debug("Temporary directory for merge is $dir");
 
@@ -953,29 +958,32 @@
             {
                 $log->info("Merged successfully");
                 print "M M $filename\n";
-                $log->debug("Update-existing $dirpart");
+                $log->debug("Merged $dirpart");
 
                 # Don't want to actually _DO_ the update if -n specified
                 unless ( $state->{globaloptions}{-n} )
                 {
-                    print "Update-existing $dirpart\n";
+                    print "Merged $dirpart\n";
                     $log->debug($state->{CVSROOT} . "/$state->{module}/$filename");
                     print $state->{CVSROOT} . "/$state->{module}/$filename\n";
-                    $log->debug("/$filepart/1.$meta->{revision}///");
-                    print "/$filepart/1.$meta->{revision}///\n";
+                    my $kopts = kopts_from_path($filepart);
+                    $log->debug("/$filepart/1.$meta->{revision}//$kopts/");
+                    print "/$filepart/1.$meta->{revision}//$kopts/\n";
                 }
             }
             elsif ( $return == 1 )
             {
                 $log->info("Merged with conflicts");
+                print "E cvs update: conflicts found in $filename\n";
                 print "M C $filename\n";
 
                 # Don't want to actually _DO_ the update if -n specified
                 unless ( $state->{globaloptions}{-n} )
                 {
-                    print "Update-existing $dirpart\n";
+                    print "Merged $dirpart\n";
                     print $state->{CVSROOT} . "/$state->{module}/$filename\n";
-                    print "/$filepart/1.$meta->{revision}/+//\n";
+                    my $kopts = kopts_from_path($filepart);
+                    print "/$filepart/1.$meta->{revision}/+/$kopts/\n";
                 }
             }
             else
@@ -1031,37 +1039,37 @@
         exit;
     }
 
-    my $lockfile = "$state->{CVSROOT}/refs/heads/$state->{module}.lock";
-    unless ( sysopen(LOCKFILE,$lockfile,O_EXCL|O_CREAT|O_WRONLY) )
-    {
-        $log->warn("lockfile '$lockfile' already exists, please try again");
-        print "error 1 Lock file '$lockfile' already exists, please try again\n";
-        exit;
-    }
-
     # Grab a handle to the SQLite db and do any necessary updates
     my $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log);
     $updater->update();
 
     my $tmpdir = tempdir ( DIR => $TEMP_DIR );
     my ( undef, $file_index ) = tempfile ( DIR => $TEMP_DIR, OPEN => 0 );
-    $log->info("Lock successful, basing commit on '$tmpdir', index file is '$file_index'");
+    $log->info("Lockless commit start, basing commit on '$tmpdir', index file is '$file_index'");
 
     $ENV{GIT_DIR} = $state->{CVSROOT} . "/";
     $ENV{GIT_INDEX_FILE} = $file_index;
 
+    # Remember where the head was at the beginning.
+    my $parenthash = `git show-ref -s refs/heads/$state->{module}`;
+    chomp $parenthash;
+    if ($parenthash !~ /^[0-9a-f]{40}$/) {
+	    print "error 1 pserver cannot find the current HEAD of module";
+	    exit;
+    }
+
     chdir $tmpdir;
 
     # populate the temporary index based
-    system("git-read-tree", $state->{module});
+    system("git-read-tree", $parenthash);
     unless ($? == 0)
     {
 	die "Error running git-read-tree $state->{module} $file_index $!";
     }
     $log->info("Created index '$file_index' with for head $state->{module} - exit status $?");
 
-
     my @committedfiles = ();
+    my %oldmeta;
 
     # foreach file specified on the command line ...
     foreach my $filename ( @{$state->{args}} )
@@ -1072,6 +1080,7 @@
         next unless ( exists $state->{entries}{$filename}{modified_filename} or not $state->{entries}{$filename}{unchanged} );
 
         my $meta = $updater->getmeta($filename);
+	$oldmeta{$filename} = $meta;
 
         my $wrev = revparse($filename);
 
@@ -1095,8 +1104,6 @@
         {
             # fail everything if an up to date check fails
             print "error 1 Up to date check failed for $filename\n";
-            close LOCKFILE;
-            unlink($lockfile);
             chdir "/";
             exit;
         }
@@ -1139,16 +1146,12 @@
     {
         print "E No files to commit\n";
         print "ok\n";
-        close LOCKFILE;
-        unlink($lockfile);
         chdir "/";
         return;
     }
 
     my $treehash = `git-write-tree`;
-    my $parenthash = `cat $ENV{GIT_DIR}refs/heads/$state->{module}`;
     chomp $treehash;
-    chomp $parenthash;
 
     $log->debug("Treehash : $treehash, Parenthash : $parenthash");
 
@@ -1159,19 +1162,36 @@
     close $msg_fh;
 
     my $commithash = `git-commit-tree $treehash -p $parenthash < $msg_filename`;
+    chomp($commithash);
     $log->info("Commit hash : $commithash");
 
     unless ( $commithash =~ /[a-zA-Z0-9]{40}/ )
     {
         $log->warn("Commit failed (Invalid commit hash)");
         print "error 1 Commit failed (unknown reason)\n";
-        close LOCKFILE;
-        unlink($lockfile);
         chdir "/";
         exit;
     }
 
-    print LOCKFILE $commithash;
+	# Check that this is allowed, just as we would with a receive-pack
+	my @cmd = ( $ENV{GIT_DIR}.'hooks/update', "refs/heads/$state->{module}",
+			$parenthash, $commithash );
+	if( -x $cmd[0] ) {
+		unless( system( @cmd ) == 0 )
+		{
+			$log->warn("Commit failed (update hook declined to update ref)");
+			print "error 1 Commit failed (update hook declined)\n";
+			chdir "/";
+			exit;
+		}
+	}
+
+	if (system(qw(git update-ref -m), "cvsserver ci",
+			"refs/heads/$state->{module}", $commithash, $parenthash)) {
+		$log->warn("update-ref for $state->{module} failed.");
+		print "error 1 Cannot commit -- update first\n";
+		exit;
+	}
 
     $updater->update();
 
@@ -1189,23 +1209,26 @@
 
         $log->debug("Checked-in $dirpart : $filename");
 
+	print "M $state->{CVSROOT}/$state->{module}/$filename,v  <--  $dirpart$filepart\n";
         if ( defined $meta->{filehash} && $meta->{filehash} eq "deleted" )
         {
+            print "M new revision: delete; previous revision: 1.$oldmeta{$filename}{revision}\n";
             print "Remove-entry $dirpart\n";
             print "$filename\n";
         } else {
+            if ($meta->{revision} == 1) {
+	        print "M initial revision: 1.1\n";
+            } else {
+	        print "M new revision: 1.$meta->{revision}; previous revision: 1.$oldmeta{$filename}{revision}\n";
+            }
             print "Checked-in $dirpart\n";
             print "$filename\n";
-            print "/$filepart/1.$meta->{revision}///\n";
+            my $kopts = kopts_from_path($filepart);
+            print "/$filepart/1.$meta->{revision}//$kopts/\n";
         }
     }
 
-    close LOCKFILE;
-    my $reffile = "$ENV{GIT_DIR}refs/heads/$state->{module}";
-    unlink($reffile);
-    rename($lockfile, $reffile);
     chdir "/";
-
     print "ok\n";
 }
 
@@ -1283,7 +1306,7 @@
         }
         if ( defined($meta->{revision}) )
         {
-            print "M Repository revision:\t1." . $meta->{revision} . "\t$state->{repository}/$filename,v\n";
+            print "M Repository revision:\t1." . $meta->{revision} . "\t$state->{CVSROOT}/$state->{module}/$filename,v\n";
             print "M Sticky Tag:\t\t(none)\n";
             print "M Sticky Date:\t\t(none)\n";
             print "M Sticky Options:\t\t(none)\n";
@@ -1882,6 +1905,28 @@
     return $filename;
 }
 
+# Given a path, this function returns a string containing the kopts
+# that should go into that path's Entries line.  For example, a binary
+# file should get -kb.
+sub kopts_from_path
+{
+	my ($path) = @_;
+
+	# Once it exists, the git attributes system should be used to look up
+	# what attributes apply to this path.
+
+	# Until then, take the setting from the config file
+    unless ( defined ( $cfg->{gitcvs}{allbinary} ) and $cfg->{gitcvs}{allbinary} =~ /^\s*(1|true|yes)\s*$/i )
+    {
+		# Return "" to give no special treatment to any path
+		return "";
+    } else {
+		# Alternatively, to have all files treated as if they are binary (which
+		# is more like git itself), always return the "-kb" option
+		return "-kb";
+    }
+}
+
 package GITCVS::log;
 
 ####
diff --git a/git-fetch.sh b/git-fetch.sh
index ca984e7..fd70696 100755
--- a/git-fetch.sh
+++ b/git-fetch.sh
@@ -24,6 +24,8 @@
 exec=
 keep=
 shallow_depth=
+no_progress=
+test -t 1 || no_progress=--no-progress
 while case "$#" in 0) break ;; esac
 do
 	case "$1" in
@@ -107,133 +109,11 @@
 	die "Cannot get the repository state from $remote"
 
 append_fetch_head () {
-    head_="$1"
-    remote_="$2"
-    remote_name_="$3"
-    remote_nick_="$4"
-    local_name_="$5"
-    case "$6" in
-    t) not_for_merge_='not-for-merge' ;;
-    '') not_for_merge_= ;;
-    esac
-
-    # remote-nick is the URL given on the command line (or a shorthand)
-    # remote-name is the $GIT_DIR relative refs/ path we computed
-    # for this refspec.
-
-    # the $note_ variable will be fed to git-fmt-merge-msg for further
-    # processing.
-    case "$remote_name_" in
-    HEAD)
-	note_= ;;
-    refs/heads/*)
-	note_="$(expr "$remote_name_" : 'refs/heads/\(.*\)')"
-	note_="branch '$note_' of " ;;
-    refs/tags/*)
-	note_="$(expr "$remote_name_" : 'refs/tags/\(.*\)')"
-	note_="tag '$note_' of " ;;
-    refs/remotes/*)
-	note_="$(expr "$remote_name_" : 'refs/remotes/\(.*\)')"
-	note_="remote branch '$note_' of " ;;
-    *)
-	note_="$remote_name of " ;;
-    esac
-    remote_1_=$(expr "z$remote_" : 'z\(.*\)\.git/*$') &&
-	remote_="$remote_1_"
-    note_="$note_$remote_"
-
-    # 2.6.11-tree tag would not be happy to be fed to resolve.
-    if git-cat-file commit "$head_" >/dev/null 2>&1
-    then
-	headc_=$(git-rev-parse --verify "$head_^0") || exit
-	echo "$headc_	$not_for_merge_	$note_" >>"$GIT_DIR/FETCH_HEAD"
-    else
-	echo "$head_	not-for-merge	$note_" >>"$GIT_DIR/FETCH_HEAD"
-    fi
-
-    update_local_ref "$local_name_" "$head_" "$note_"
-}
-
-update_local_ref () {
-    # If we are storing the head locally make sure that it is
-    # a fast forward (aka "reverse push").
-
-    label_=$(git-cat-file -t $2)
-    newshort_=$(git-rev-parse --short $2)
-    if test -z "$1" ; then
-	[ "$verbose" ] && echo >&2 "* fetched $3"
-	[ "$verbose" ] && echo >&2 "  $label_: $newshort_"
-	return 0
-    fi
-    oldshort_=$(git show-ref --hash --abbrev "$1" 2>/dev/null)
-
-    case "$1" in
-    refs/tags/*)
-	# Tags need not be pointing at commits so there
-	# is no way to guarantee "fast-forward" anyway.
-	if test -n "$oldshort_"
-	then
-		if now_=$(git show-ref --hash "$1") && test "$now_" = "$2"
-		then
-			[ "$verbose" ] && echo >&2 "* $1: same as $3"
-			[ "$verbose" ] && echo >&2 "  $label_: $newshort_" ||:
-		else
-			echo >&2 "* $1: updating with $3"
-			echo >&2 "  $label_: $newshort_"
-			git-update-ref -m "$GIT_REFLOG_ACTION: updating tag" "$1" "$2"
-		fi
-	else
-		echo >&2 "* $1: storing $3"
-		echo >&2 "  $label_: $newshort_"
-		git-update-ref -m "$GIT_REFLOG_ACTION: storing tag" "$1" "$2"
-	fi
-	;;
-
-    refs/heads/* | refs/remotes/*)
-	# $1 is the ref being updated.
-	# $2 is the new value for the ref.
-	local=$(git-rev-parse --verify "$1^0" 2>/dev/null)
-	if test "$local"
-	then
-	    # Require fast-forward.
-	    mb=$(git-merge-base "$local" "$2") &&
-	    case "$2,$mb" in
-	    $local,*)
-	        if test -n "$verbose"
-		then
-			echo >&2 "* $1: same as $3"
-			echo >&2 "  $label_: $newshort_"
-		fi
-		;;
-	    *,$local)
-		echo >&2 "* $1: fast forward to $3"
-		echo >&2 "  old..new: $oldshort_..$newshort_"
-		git-update-ref -m "$GIT_REFLOG_ACTION: fast-forward" "$1" "$2" "$local"
-		;;
-	    *)
-		false
-		;;
-	    esac || {
-		case ",$force,$single_force," in
-		*,t,*)
-			echo >&2 "* $1: forcing update to non-fast forward $3"
-			echo >&2 "  old...new: $oldshort_...$newshort_"
-			git-update-ref -m "$GIT_REFLOG_ACTION: forced-update" "$1" "$2" "$local"
-			;;
-		*)
-			echo >&2 "* $1: not updating to non-fast forward $3"
-			echo >&2 "  old...new: $oldshort_...$newshort_"
-			exit 1
-			;;
-		esac
-	    }
-	else
-	    echo >&2 "* $1: storing $3"
-	    echo >&2 "  $label_: $newshort_"
-	    git-update-ref -m "$GIT_REFLOG_ACTION: storing head" "$1" "$2"
-	fi
-	;;
-    esac
+	flags=
+	test -n "$verbose" && flags="$flags$LF-v"
+	test -n "$force$single_force" && flags="$flags$LF-f"
+	GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION" \
+		git-fetch--tool $flags append-fetch-head "$@"
 }
 
 # updating the current HEAD with git-fetch in a bare
@@ -243,6 +123,15 @@
 	orig_head=$(git-rev-parse --verify HEAD 2>/dev/null)
 fi
 
+# Allow --notags from remote.$1.tagopt
+case "$tags$no_tags" in
+'')
+	case "$(git-config --get "remote.$1.tagopt")" in
+	--no-tags)
+		no_tags=t ;;
+	esac
+esac
+
 # If --tags (and later --heads or --all) is specified, then we are
 # not talking about defaults stored in Pull: line of remotes or
 # branches file, and just fetch those and refspecs explicitly given.
@@ -268,7 +157,40 @@
 	fi
 fi
 
-fetch_main () {
+fetch_all_at_once () {
+
+  eval=$(echo "$1" | git-fetch--tool parse-reflist "-")
+  eval "$eval"
+
+    ( : subshell because we muck with IFS
+      IFS=" 	$LF"
+      (
+	if test "$remote" = . ; then
+	    git-show-ref $rref || echo failed "$remote"
+	elif test -f "$remote" ; then
+	    test -n "$shallow_depth" &&
+		die "shallow clone with bundle is not supported"
+	    git-bundle unbundle "$remote" $rref ||
+	    echo failed "$remote"
+	else
+	  git-fetch-pack --thin $exec $keep $shallow_depth $no_progress \
+		"$remote" $rref ||
+	  echo failed "$remote"
+	fi
+      ) |
+      (
+	flags=
+	test -n "$verbose" && flags="$flags -v"
+	test -n "$force" && flags="$flags -f"
+	GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION" \
+		git-fetch--tool $flags native-store \
+			"$remote" "$remote_nick" "$refs"
+      )
+    ) || exit
+
+}
+
+fetch_per_ref () {
   reflist="$1"
   refs=
   rref=
@@ -326,7 +248,7 @@
 	  expr "z$head" : "z$_x40\$" >/dev/null ||
 		die "No such ref $remote_name at $remote"
 	  echo >&2 "Fetching $remote_name from $remote using $proto"
-	  git-http-fetch -v -a "$head" "$remote/" || exit
+	  git-http-fetch -v -a "$head" "$remote" || exit
 	  ;;
       rsync://*)
 	  test -n "$shallow_depth" &&
@@ -360,9 +282,6 @@
 	      rsync_slurped_objects=t
 	  }
 	  ;;
-      *)
-	  # We will do git native transport with just one call later.
-	  continue ;;
       esac
 
       append_fetch_head "$head" "$remote" \
@@ -370,72 +289,17 @@
 
   done
 
-  case "$remote" in
-  http://* | https://* | ftp://* | rsync://* )
-      ;; # we are already done.
-  *)
-    ( : subshell because we muck with IFS
-      IFS=" 	$LF"
-      (
-	  git-fetch-pack --thin $exec $keep $shallow_depth "$remote" $rref ||
-	  echo failed "$remote"
-      ) |
-      (
-	trap '
-		if test -n "$keepfile" && test -f "$keepfile"
-		then
-			rm -f "$keepfile"
-		fi
-	' 0
+}
 
-        keepfile=
-	while read sha1 remote_name
-	do
-	  case "$sha1" in
-	  failed)
-		  echo >&2 "Fetch failure: $remote"
-		  exit 1 ;;
-	  # special line coming from index-pack with the pack name
-	  pack)
-		  continue ;;
-	  keep)
-		  keepfile="$GIT_OBJECT_DIRECTORY/pack/pack-$remote_name.keep"
-		  continue ;;
-	  esac
-	  found=
-	  single_force=
-	  for ref in $refs
-	  do
-	      case "$ref" in
-	      +$remote_name:*)
-		  single_force=t
-		  not_for_merge=
-		  found="$ref"
-		  break ;;
-	      .+$remote_name:*)
-		  single_force=t
-		  not_for_merge=t
-		  found="$ref"
-		  break ;;
-	      .$remote_name:*)
-		  not_for_merge=t
-		  found="$ref"
-		  break ;;
-	      $remote_name:*)
-		  not_for_merge=
-		  found="$ref"
-		  break ;;
-	      esac
-	  done
-	  local_name=$(expr "z$found" : 'z[^:]*:\(.*\)')
-	  append_fetch_head "$sha1" "$remote" \
-		  "$remote_name" "$remote_nick" "$local_name" \
-		  "$not_for_merge" || exit
-        done
-      )
-    ) || exit ;;
-  esac
-
+fetch_main () {
+	case "$remote" in
+	http://* | https://* | ftp://* | rsync://* )
+		fetch_per_ref "$@"
+		;;
+	*)
+		fetch_all_at_once "$@"
+		;;
+	esac
 }
 
 fetch_main "$reflist" || exit
diff --git a/git-gui/Makefile b/git-gui/Makefile
index d74fca2..b82789e 100644
--- a/git-gui/Makefile
+++ b/git-gui/Makefile
@@ -28,6 +28,11 @@
 	QUIET_BUILT_IN = @echo '   ' BUILTIN $@;
 endif
 
+ifeq ($(findstring $(MAKEFLAGS),s),s)
+QUIET_GEN =
+QUIET_BUILT_IN =
+endif
+
 DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
 gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
 SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
diff --git a/git-ls-remote.sh b/git-ls-remote.sh
index 8ea5c5e..a6ed99a 100755
--- a/git-ls-remote.sh
+++ b/git-ls-remote.sh
@@ -89,8 +89,13 @@
 	;;
 
 * )
-	git-peek-remote $exec "$peek_repo" ||
+	if test -f "$peek_repo" ; then
+		git bundle list-heads "$peek_repo" ||
 		echo "failed	slurping"
+	else
+		git-peek-remote $exec "$peek_repo" ||
+		echo "failed	slurping"
+	fi
 	;;
 esac |
 sort -t '	' -k 2 |
diff --git a/git-merge-ours.sh b/git-merge-ours.sh
index 4f3d053..2b6a5c0 100755
--- a/git-merge-ours.sh
+++ b/git-merge-ours.sh
@@ -9,6 +9,6 @@
 # because the current index is what we will be committing as the
 # merge result.
 
-test "$(git-diff-index --cached --name-status HEAD)" = "" || exit 2
+git-diff-index --quiet --cached HEAD || exit 2
 
 exit 0
diff --git a/git-merge.sh b/git-merge.sh
index 8759c5a..fa45891 100755
--- a/git-merge.sh
+++ b/git-merge.sh
@@ -108,6 +108,10 @@
 		git-show-ref -q --verify "refs/heads/$truname" 2>/dev/null
 	then
 		echo "$rh		branch '$truname' (early part) of ."
+	elif test "$remote" = "FETCH_HEAD" -a -r "$GIT_DIR/FETCH_HEAD"
+	then
+		sed -e 's/	not-for-merge	/		/' -e 1q \
+			"$GIT_DIR/FETCH_HEAD"
 	else
 		echo "$rh		commit '$remote'"
 	fi
diff --git a/git-mergetool.sh b/git-mergetool.sh
new file mode 100755
index 0000000..e62351b
--- /dev/null
+++ b/git-mergetool.sh
@@ -0,0 +1,365 @@
+#!/bin/sh
+#
+# This program resolves merge conflicts in git
+#
+# Copyright (c) 2006 Theodore Y. Ts'o
+#
+# This file is licensed under the GPL v2, or a later version
+# at the discretion of Junio C Hammano.
+#
+
+USAGE='[--tool=tool] [file to merge] ...'
+SUBDIRECTORY_OK=Yes
+. git-sh-setup
+require_work_tree
+
+# Returns true if the mode reflects a symlink
+is_symlink () {
+    test "$1" = 120000
+}
+
+local_present () {
+    test -n "$local_mode"
+}
+
+remote_present () {
+    test -n "$remote_mode"
+}
+
+base_present () {
+    test -n "$base_mode"
+}
+
+cleanup_temp_files () {
+    if test "$1" = --save-backup ; then
+	mv -- "$BACKUP" "$path.orig"
+	rm -f -- "$LOCAL" "$REMOTE" "$BASE"
+    else
+	rm -f -- "$LOCAL" "$REMOTE" "$BASE" "$BACKUP"
+    fi
+}
+
+describe_file () {
+    mode="$1"
+    branch="$2"
+    file="$3"
+
+    printf "  {%s}: " "$branch"
+    if test -z "$mode"; then
+	echo "deleted"
+    elif is_symlink "$mode" ; then
+	echo "a symbolic link -> '$(cat "$file")'"
+    else
+	if base_present; then
+	    echo "modified"
+	else
+	    echo "created"
+	fi
+    fi
+}
+
+
+resolve_symlink_merge () {
+    while true; do
+	printf "Use (l)ocal or (r)emote, or (a)bort? "
+	read ans
+	case "$ans" in
+	    [lL]*)
+		git-checkout-index -f --stage=2 -- "$path"
+		git-add -- "$path"
+		cleanup_temp_files --save-backup
+		return
+		;;
+	    [rR]*)
+		git-checkout-index -f --stage=3 -- "$path"
+		git-add -- "$path"
+		cleanup_temp_files --save-backup
+		return
+		;;
+	    [aA]*)
+		exit 1
+		;;
+	    esac
+	done
+}
+
+resolve_deleted_merge () {
+    while true; do
+	if base_present; then
+	    printf "Use (m)odified or (d)eleted file, or (a)bort? "
+	else
+	    printf "Use (c)reated or (d)eleted file, or (a)bort? "
+	fi
+	read ans
+	case "$ans" in
+	    [mMcC]*)
+		git-add -- "$path"
+		cleanup_temp_files --save-backup
+		return
+		;;
+	    [dD]*)
+		git-rm -- "$path" > /dev/null
+		cleanup_temp_files
+		return
+		;;
+	    [aA]*)
+		exit 1
+		;;
+	    esac
+	done
+}
+
+check_unchanged () {
+    if test "$path" -nt "$BACKUP" ; then
+	status=0;
+    else
+	while true; do
+	    echo "$path seems unchanged."
+	    printf "Was the merge successful? [y/n] "
+	    read answer < /dev/tty
+	    case "$answer" in
+		y*|Y*) status=0; break ;;
+		n*|N*) status=1; break ;;
+	    esac
+	done
+    fi
+}
+
+save_backup () {
+    if test "$status" -eq 0; then
+	mv -- "$BACKUP" "$path.orig"
+    fi
+}
+
+remove_backup () {
+    if test "$status" -eq 0; then
+	rm "$BACKUP"
+    fi
+}
+
+merge_file () {
+    path="$1"
+
+    f=`git-ls-files -u -- "$path"`
+    if test -z "$f" ; then
+	if test ! -f "$path" ; then
+	    echo "$path: file not found"
+	else
+	    echo "$path: file does not need merging"
+	fi
+	exit 1
+    fi
+
+    BACKUP="$path.BACKUP.$$"
+    LOCAL="$path.LOCAL.$$"
+    REMOTE="$path.REMOTE.$$"
+    BASE="$path.BASE.$$"
+
+    mv -- "$path" "$BACKUP"
+    cp -- "$BACKUP" "$path"
+
+    base_mode=`git ls-files -u -- "$path" | awk '{if ($3==1) print $1;}'`
+    local_mode=`git ls-files -u -- "$path" | awk '{if ($3==2) print $1;}'`
+    remote_mode=`git ls-files -u -- "$path" | awk '{if ($3==3) print $1;}'`
+
+    base_present   && git cat-file blob ":1:$path" > "$BASE" 2>/dev/null
+    local_present  && git cat-file blob ":2:$path" > "$LOCAL" 2>/dev/null
+    remote_present && git cat-file blob ":3:$path" > "$REMOTE" 2>/dev/null
+
+    if test -z "$local_mode" -o -z "$remote_mode"; then
+	echo "Deleted merge conflict for '$path':"
+	describe_file "$local_mode" "local" "$LOCAL"
+	describe_file "$remote_mode" "remote" "$REMOTE"
+	resolve_deleted_merge
+	return
+    fi
+
+    if is_symlink "$local_mode" || is_symlink "$remote_mode"; then
+	echo "Symbolic link merge conflict for '$path':"
+	describe_file "$local_mode" "local" "$LOCAL"
+	describe_file "$remote_mode" "remote" "$REMOTE"
+	resolve_symlink_merge
+	return
+    fi
+
+    echo "Normal merge conflict for '$path':"
+    describe_file "$local_mode" "local" "$LOCAL"
+    describe_file "$remote_mode" "remote" "$REMOTE"
+    printf "Hit return to start merge resolution tool (%s): " "$merge_tool"
+    read ans
+
+    case "$merge_tool" in
+	kdiff3)
+	    if base_present ; then
+		(kdiff3 --auto --L1 "$path (Base)" -L2 "$path (Local)" --L3 "$path (Remote)" \
+		    -o "$path" -- "$BASE" "$LOCAL" "$REMOTE" > /dev/null 2>&1)
+	    else
+		(kdiff3 --auto -L1 "$path (Local)" --L2 "$path (Remote)" \
+		    -o "$path" -- "$LOCAL" "$REMOTE" > /dev/null 2>&1)
+	    fi
+	    status=$?
+	    remove_backup
+	    ;;
+	tkdiff)
+	    if base_present ; then
+		tkdiff -a "$BASE" -o "$path" -- "$LOCAL" "$REMOTE"
+	    else
+		tkdiff -o "$path" -- "$LOCAL" "$REMOTE"
+	    fi
+	    status=$?
+	    save_backup
+	    ;;
+	meld|vimdiff)
+	    touch "$BACKUP"
+	    $merge_tool -- "$LOCAL" "$path" "$REMOTE"
+	    check_unchanged
+	    save_backup
+	    ;;
+	xxdiff)
+	    touch "$BACKUP"
+	    if base_present ; then
+		xxdiff -X --show-merged-pane \
+		    -R 'Accel.SaveAsMerged: "Ctrl-S"' \
+		    -R 'Accel.Search: "Ctrl+F"' \
+		    -R 'Accel.SearchForward: "Ctrl-G"' \
+		    --merged-file "$path" -- "$LOCAL" "$BASE" "$REMOTE"
+	    else
+		xxdiff -X --show-merged-pane \
+		    -R 'Accel.SaveAsMerged: "Ctrl-S"' \
+		    -R 'Accel.Search: "Ctrl+F"' \
+		    -R 'Accel.SearchForward: "Ctrl-G"' \
+		    --merged-file "$path" -- "$LOCAL" "$REMOTE"
+	    fi
+	    check_unchanged
+	    save_backup
+	    ;;
+	opendiff)
+	    touch "$BACKUP"
+	    if base_present; then
+		opendiff "$LOCAL" "$REMOTE" -ancestor "$BASE" -merge "$path" | cat
+	    else
+		opendiff "$LOCAL" "$REMOTE" -merge "$path" | cat
+	    fi
+	    check_unchanged
+	    save_backup
+	    ;;
+	emerge)
+	    if base_present ; then
+		emacs -f emerge-files-with-ancestor-command "$LOCAL" "$REMOTE" "$BASE" "$path"
+	    else
+		emacs -f emerge-files-command "$LOCAL" "$REMOTE" "$path"
+	    fi
+	    status=$?
+	    save_backup
+	    ;;
+    esac
+    if test "$status" -ne 0; then
+	echo "merge of $path failed" 1>&2
+	mv -- "$BACKUP" "$path"
+	exit 1
+    fi
+    git add -- "$path"
+    cleanup_temp_files
+}
+
+while case $# in 0) break ;; esac
+do
+    case "$1" in
+	-t|--tool*)
+	    case "$#,$1" in
+		*,*=*)
+		    merge_tool=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+		    ;;
+		1,*)
+		    usage ;;
+		*)
+		    merge_tool="$2"
+		    shift ;;
+	    esac
+	    ;;
+	--)
+	    break
+	    ;;
+	-*)
+	    usage
+	    ;;
+	*)
+	    break
+	    ;;
+    esac
+    shift
+done
+
+if test -z "$merge_tool"; then
+    merge_tool=`git-config merge.tool`
+    case "$merge_tool" in
+	kdiff3 | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | "")
+	    ;; # happy
+	*)
+	    echo >&2 "git config option merge.tool set to unknown tool: $merge_tool"
+	    echo >&2 "Resetting to default..."
+	    unset merge_tool
+	    ;;
+    esac
+fi
+
+if test -z "$merge_tool" ; then
+    if type kdiff3 >/dev/null 2>&1 && test -n "$DISPLAY"; then
+	merge_tool="kdiff3";
+    elif type tkdiff >/dev/null 2>&1 && test -n "$DISPLAY"; then
+	merge_tool=tkdiff
+    elif type xxdiff >/dev/null 2>&1 && test -n "$DISPLAY"; then
+	merge_tool=xxdiff
+    elif type meld >/dev/null 2>&1 && test -n "$DISPLAY"; then
+	merge_tool=meld
+    elif type opendiff >/dev/null 2>&1; then
+	merge_tool=opendiff
+    elif type emacs >/dev/null 2>&1; then
+	merge_tool=emerge
+    elif type vimdiff >/dev/null 2>&1; then
+	merge_tool=vimdiff
+    else
+	echo "No available merge resolution programs available."
+	exit 1
+    fi
+fi
+
+case "$merge_tool" in
+    kdiff3|tkdiff|meld|xxdiff|vimdiff|opendiff)
+	if ! type "$merge_tool" > /dev/null 2>&1; then
+	    echo "The merge tool $merge_tool is not available"
+	    exit 1
+	fi
+	;;
+    emerge)
+	if ! type "emacs" > /dev/null 2>&1; then
+	    echo "Emacs is not available"
+	    exit 1
+	fi
+	;;
+    *)
+	echo "Unknown merge tool: $merge_tool"
+	exit 1
+	;;
+esac
+
+if test $# -eq 0 ; then
+	files=`git ls-files -u | sed -e 's/^[^	]*	//' | sort -u`
+	if test -z "$files" ; then
+		echo "No files need merging"
+		exit 0
+	fi
+	echo Merging the files: $files
+	git ls-files -u | sed -e 's/^[^	]*	//' | sort -u | while read i
+	do
+		printf "\n"
+		merge_file "$i" < /dev/tty > /dev/tty
+	done
+else
+	while test $# -gt 0; do
+		printf "\n"
+		merge_file "$1"
+		shift
+	done
+fi
+exit 0
diff --git a/git-parse-remote.sh b/git-parse-remote.sh
index 5208ee6..437b0c3 100755
--- a/git-parse-remote.sh
+++ b/git-parse-remote.sh
@@ -9,6 +9,9 @@
 	*/*)
 		echo ''
 		;;
+	.)
+		echo self
+		;;
 	*)
 		if test "$(git-config --get "remote.$1.url")"
 		then
@@ -31,6 +34,9 @@
 	'')
 		echo "$1"
 		;;
+	self)
+		echo "$1"
+		;;
 	config)
 		git-config --get "remote.$1.url"
 		;;
@@ -57,7 +63,7 @@
 get_remote_default_refs_for_push () {
 	data_source=$(get_data_source "$1")
 	case "$data_source" in
-	'' | branches)
+	'' | branches | self)
 		;; # no default push mapping, just send matching refs.
 	config)
 		git-config --get-all "remote.$1.push" ;;
@@ -81,51 +87,8 @@
 # is to help prevent randomly "globbed" ref from being chosen as
 # a merge candidate
 expand_refs_wildcard () {
-	remote="$1"
-	shift
-	first_one=yes
-	if test "$#" = 0
-	then
-		echo empty
-		echo >&2 "Nothing specified for fetching with remote.$remote.fetch"
-	fi
-	for ref
-	do
-		lref=${ref#'+'}
-		# a non glob pattern is given back as-is.
-		expr "z$lref" : 'zrefs/.*/\*:refs/.*/\*$' >/dev/null || {
-			if test -n "$first_one"
-			then
-				echo "explicit"
-				first_one=
-			fi
-			echo "$ref"
-			continue
-		}
-
-		# glob
-		if test -n "$first_one"
-		then
-			echo "glob"
-			first_one=
-		fi
-		from=`expr "z$lref" : 'z\(refs/.*/\)\*:refs/.*/\*$'`
-		to=`expr "z$lref" : 'zrefs/.*/\*:\(refs/.*/\)\*$'`
-		local_force=
-		test "z$lref" = "z$ref" || local_force='+'
-		echo "$ls_remote_result" |
-		sed -e '/\^{}$/d' |
-		(
-			IFS='	'
-			while read sha1 name
-			do
-				# ignore the ones that do not start with $from
-				mapped=${name#"$from"}
-				test "z$name" = "z$mapped" && continue
-				echo "${local_force}${name}:${to}${mapped}"
-			done
-		)
-	done
+	echo "$ls_remote_result" |
+	git fetch--tool expand-refs-wildcard "-" "$@"
 }
 
 # Subroutine to canonicalize remote:local notation.
@@ -206,6 +169,10 @@
 	case "$data_source" in
 	'')
 		echo "HEAD:" ;;
+	self)
+	        canon_refs_list_for_fetch -d "$1" \
+			$(git-for-each-ref --format='%(refname):')
+		;;
 	config)
 		canon_refs_list_for_fetch -d "$1" \
 			$(git-config --get-all "remote.$1.fetch") ;;
@@ -220,7 +187,7 @@
 					}' "$GIT_DIR/remotes/$1")
 		;;
 	*)
-		die "internal error: get-remote-default-ref-for-push $1" ;;
+		die "internal error: get-remote-default-ref-for-fetch $1" ;;
 	esac
 }
 
diff --git a/git-quiltimport.sh b/git-quiltimport.sh
index 671a5ff..edccd82 100755
--- a/git-quiltimport.sh
+++ b/git-quiltimport.sh
@@ -73,6 +73,10 @@
 for patch_name in $(cat "$QUILT_PATCHES/series" | grep -v '^#'); do
 	echo $patch_name
 	(cat $QUILT_PATCHES/$patch_name | git-mailinfo "$tmp_msg" "$tmp_patch" > "$tmp_info") || exit 3
+	test -s $dotest/patch || {
+		echo "Patch is empty.  Was is split wrong?"
+		stop_here $this
+	}
 
 	# Parse the author information
 	export GIT_AUTHOR_NAME=$(sed -ne 's/Author: //p' "$tmp_info")
@@ -111,7 +115,7 @@
 	if [ -z "$dry_run" ] ; then
 		git-apply --index -C1 "$tmp_patch" &&
 		tree=$(git-write-tree) &&
-		commit=$((echo "$SUBJECT"; echo; cat "$tmp_msg") | git-commit-tree $tree -p $commit) &&
+		commit=$( (echo "$SUBJECT"; echo; cat "$tmp_msg") | git-commit-tree $tree -p $commit) &&
 		git-update-ref -m "quiltimport: $patch_name" HEAD $commit || exit 4
 	fi
 done
diff --git a/git-rebase.sh b/git-rebase.sh
index b51d19d..1d96f32 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -59,7 +59,7 @@
 		die "$RESOLVEMSG"
 	fi
 
-	if test -n "`git-diff-index HEAD`"
+	if ! git-diff-index --quiet HEAD
 	then
 		if ! git-commit -C "`cat $dotest/current`"
 		then
@@ -124,13 +124,11 @@
 do
 	case "$1" in
 	--continue)
-		diff=$(git-diff-files)
-		case "$diff" in
-		?*)	echo "You must edit all merge conflicts and then"
+		git-diff-files --quiet || {
+			echo "You must edit all merge conflicts and then"
 			echo "mark them as resolved using git update-index"
 			exit 1
-			;;
-		esac
+		}
 		if test -d "$dotest"
 		then
 			prev_head="`cat $dotest/prev_head`"
@@ -265,6 +263,10 @@
 upstream=`git rev-parse --verify "${upstream_name}^0"` ||
     die "invalid upstream $upstream_name"
 
+# Make sure the branch to rebase onto is valid.
+onto_name=${newbase-"$upstream_name"}
+onto=$(git-rev-parse --verify "${onto_name}^0") || exit
+
 # If a hook exists, give it a chance to interrupt
 if test -x "$GIT_DIR/hooks/pre-rebase"
 then
@@ -291,10 +293,6 @@
 esac
 branch=$(git-rev-parse --verify "${branch_name}^0") || exit
 
-# Make sure the branch to rebase onto is valid.
-onto_name=${newbase-"$upstream_name"}
-onto=$(git-rev-parse --verify "${onto_name}^0") || exit
-
 # Now we are rebasing commits $upstream..$branch on top of $onto
 
 # Check if we are already based on $onto, but this should be
diff --git a/git-remote.perl b/git-remote.perl
index 670bafb..52013fe 100755
--- a/git-remote.perl
+++ b/git-remote.perl
@@ -15,6 +15,10 @@
 		$hash->{$name}{'FETCH'} ||= [];
 		push @{$hash->{$name}{'FETCH'}}, $value;
 	}
+	elsif ($what eq 'push') {
+		$hash->{$name}{'PUSH'} ||= [];
+		push @{$hash->{$name}{'PUSH'}}, $value;
+	}
 	if (!exists $hash->{$name}{'SOURCE'}) {
 		$hash->{$name}{'SOURCE'} = 'config';
 	}
@@ -44,7 +48,8 @@
 			}
 		}
 		elsif (/^Push:\s*(.*)$/) {
-			; # later
+			$it->{'PUSH'} ||= [];
+			push @{$it->{'PUSH'}}, $1;
 		}
 		elsif (/^Pull:\s*(.*)$/) {
 			$it->{'FETCH'} ||= [];
@@ -250,6 +255,15 @@
 	if ($info->{'LS_REMOTE'}) {
 		show_mapping($name, $info);
 	}
+	if ($info->{'PUSH'}) {
+		my @pushed = map {
+			s|^refs/heads/||;
+			s|:refs/heads/|:|;
+			$_;
+		} @{$info->{'PUSH'}};
+		print "  Local branch(es) pushed with 'git push'\n";
+		print "    @pushed\n";
+	}
 }
 
 sub add_remote {
@@ -274,6 +288,31 @@
 	}
 }
 
+sub update_remote {
+	my ($name) = @_;
+
+        my $conf = $git->config("remotes." . $name);
+	if (defined($conf)) {
+		@remotes = split(' ', $conf);
+	} elsif ($name eq 'default') {
+		undef @remotes;
+		for (sort keys %$remote) {
+			my $do_fetch = $git->config_boolean("remote." . $_ .
+						    ".skipDefaultUpdate");
+			if (!defined($do_fetch) || $do_fetch ne "true") {
+				push @remotes, $_;
+			}
+		}
+	} else {
+		print STDERR "Remote group $name does not exists.\n";
+		exit(1);
+	}
+	for (@remotes) {
+		print "Updating $_\n";
+		$git->command('fetch', "$_");
+	}
+}
+
 sub add_usage {
 	print STDERR "Usage: git remote add [-f] [-t track]* [-m master] <name> <url>\n";
 	exit(1);
@@ -303,6 +342,15 @@
 		show_remote($ARGV[$i], $ls_remote);
 	}
 }
+elsif ($ARGV[0] eq 'update') {
+	if (@ARGV <= 1) {
+		update_remote("default");
+		exit(1);
+	}
+	for ($i = 1; $i < @ARGV; $i++) {
+		update_remote($ARGV[$i]);
+	}
+}
 elsif ($ARGV[0] eq 'prune') {
 	my $ls_remote = 1;
 	my $i;
@@ -360,5 +408,6 @@
 	print STDERR "       git remote add <name> <url>\n";
 	print STDERR "       git remote show <name>\n";
 	print STDERR "       git remote prune <name>\n";
+	print STDERR "       git remote update [group]\n";
 	exit(1);
 }
diff --git a/git-revert.sh b/git-revert.sh
deleted file mode 100755
index 49f0032..0000000
--- a/git-revert.sh
+++ /dev/null
@@ -1,197 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005 Linus Torvalds
-# Copyright (c) 2005 Junio C Hamano
-#
-
-case "$0" in
-*-revert* )
-	test -t 0 && edit=-e
-	replay=
-	me=revert
-	USAGE='[--edit | --no-edit] [-n] <commit-ish>' ;;
-*-cherry-pick* )
-	replay=t
-	edit=
-	me=cherry-pick
-	USAGE='[--edit] [-n] [-r] [-x] <commit-ish>'  ;;
-* )
-	echo >&2 "What are you talking about?"
-	exit 1 ;;
-esac
-
-SUBDIRECTORY_OK=Yes ;# we will cd up
-. git-sh-setup
-require_work_tree
-cd_to_toplevel
-
-no_commit=
-while case "$#" in 0) break ;; esac
-do
-	case "$1" in
-	-n|--n|--no|--no-|--no-c|--no-co|--no-com|--no-comm|\
-	    --no-commi|--no-commit)
-		no_commit=t
-		;;
-	-e|--e|--ed|--edi|--edit)
-		edit=-e
-		;;
-	--n|--no|--no-|--no-e|--no-ed|--no-edi|--no-edit)
-		edit=
-		;;
-	-r)
-		: no-op ;;
-	-x|--i-really-want-to-expose-my-private-commit-object-name)
-		replay=
-		;;
-	-*)
-		usage
-		;;
-	*)
-		break
-		;;
-	esac
-	shift
-done
-
-set_reflog_action "$me"
-
-test "$me,$replay" = "revert,t" && usage
-
-case "$no_commit" in
-t)
-	# We do not intend to commit immediately.  We just want to
-	# merge the differences in.
-	head=$(git-write-tree) ||
-		die "Your index file is unmerged."
-	;;
-*)
-	head=$(git-rev-parse --verify HEAD) ||
-		die "You do not have a valid HEAD"
-	files=$(git-diff-index --cached --name-only $head) || exit
-	if [ "$files" ]; then
-		die "Dirty index: cannot $me (dirty: $files)"
-	fi
-	;;
-esac
-
-rev=$(git-rev-parse --verify "$@") &&
-commit=$(git-rev-parse --verify "$rev^0") ||
-	die "Not a single commit $@"
-prev=$(git-rev-parse --verify "$commit^1" 2>/dev/null) ||
-	die "Cannot run $me a root commit"
-git-rev-parse --verify "$commit^2" >/dev/null 2>&1 &&
-	die "Cannot run $me a multi-parent commit."
-
-encoding=$(git config i18n.commitencoding || echo UTF-8)
-
-# "commit" is an existing commit.  We would want to apply
-# the difference it introduces since its first parent "prev"
-# on top of the current HEAD if we are cherry-pick.  Or the
-# reverse of it if we are revert.
-
-case "$me" in
-revert)
-	git show -s --pretty=oneline --encoding="$encoding" $commit |
-	sed -e '
-		s/^[^ ]* /Revert "/
-		s/$/"/
-	'
-	echo
-	echo "This reverts commit $commit."
-	test "$rev" = "$commit" ||
-	echo "(original 'git revert' arguments: $@)"
-	base=$commit next=$prev
-	;;
-
-cherry-pick)
-	pick_author_script='
-	/^author /{
-		s/'\''/'\''\\'\'\''/g
-		h
-		s/^author \([^<]*\) <[^>]*> .*$/\1/
-		s/'\''/'\''\'\'\''/g
-		s/.*/GIT_AUTHOR_NAME='\''&'\''/p
-
-		g
-		s/^author [^<]* <\([^>]*\)> .*$/\1/
-		s/'\''/'\''\'\'\''/g
-		s/.*/GIT_AUTHOR_EMAIL='\''&'\''/p
-
-		g
-		s/^author [^<]* <[^>]*> \(.*\)$/\1/
-		s/'\''/'\''\'\'\''/g
-		s/.*/GIT_AUTHOR_DATE='\''&'\''/p
-
-		q
-	}'
-
-	logmsg=`git show -s --pretty=raw --encoding="$encoding" "$commit"`
-	set_author_env=`echo "$logmsg" |
-	LANG=C LC_ALL=C sed -ne "$pick_author_script"`
-	eval "$set_author_env"
-	export GIT_AUTHOR_NAME
-	export GIT_AUTHOR_EMAIL
-	export GIT_AUTHOR_DATE
-
-	echo "$logmsg" |
-	sed -e '1,/^$/d' -e 's/^    //'
-	case "$replay" in
-	'')
-		echo "(cherry picked from commit $commit)"
-		test "$rev" = "$commit" ||
-		echo "(original 'git cherry-pick' arguments: $@)"
-		;;
-	esac
-	base=$prev next=$commit
-	;;
-
-esac >.msg
-
-eval GITHEAD_$head=HEAD
-eval GITHEAD_$next='`git show -s \
-	--pretty=oneline --encoding="$encoding" "$commit" |
-	sed -e "s/^[^ ]* //"`'
-export GITHEAD_$head GITHEAD_$next
-
-# This three way merge is an interesting one.  We are at
-# $head, and would want to apply the change between $commit
-# and $prev on top of us (when reverting), or the change between
-# $prev and $commit on top of us (when cherry-picking or replaying).
-
-git-merge-recursive $base -- $head $next &&
-result=$(git-write-tree 2>/dev/null) || {
-	mv -f .msg "$GIT_DIR/MERGE_MSG"
-	{
-	    echo '
-Conflicts:
-'
-		git ls-files --unmerged |
-		sed -e 's/^[^	]*	/	/' |
-		uniq
-	} >>"$GIT_DIR/MERGE_MSG"
-	echo >&2 "Automatic $me failed.  After resolving the conflicts,"
-	echo >&2 "mark the corrected paths with 'git-add <paths>'"
-	echo >&2 "and commit the result."
-	case "$me" in
-	cherry-pick)
-		echo >&2 "You may choose to use the following when making"
-		echo >&2 "the commit:"
-		echo >&2 "$set_author_env"
-	esac
-	exit 1
-}
-echo >&2 "Finished one $me."
-
-# If we are cherry-pick, and if the merge did not result in
-# hand-editing, we will hit this commit and inherit the original
-# author date and name.
-# If we are revert, or if our cherry-pick results in a hand merge,
-# we had better say that the current user is responsible for that.
-
-case "$no_commit" in
-'')
-	git-commit -n -F .msg $edit
-	rm -f .msg
-	;;
-esac
diff --git a/git-send-email.perl b/git-send-email.perl
index 6a285bf..ae50990 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -34,6 +34,53 @@
 }
 package main;
 
+
+sub usage {
+	print <<EOT;
+git-send-email [options] <file | directory>...
+Options:
+   --from         Specify the "From:" line of the email to be sent.
+
+   --to           Specify the primary "To:" line of the email.
+
+   --cc           Specify an initial "Cc:" list for the entire series
+                  of emails.
+
+   --bcc          Specify a list of email addresses that should be Bcc:
+		  on all the emails.
+
+   --compose      Use \$EDITOR to edit an introductory message for the
+                  patch series.
+
+   --subject      Specify the initial "Subject:" line.
+                  Only necessary if --compose is also set.  If --compose
+		  is not set, this will be prompted for.
+
+   --in-reply-to  Specify the first "In-Reply-To:" header line.
+                  Only used if --compose is also set.  If --compose is not
+		  set, this will be prompted for.
+
+   --chain-reply-to If set, the replies will all be to the previous
+                  email sent, rather than to the first email sent.
+                  Defaults to on.
+
+   --no-signed-off-cc Suppress the automatic addition of email addresses
+                 that appear in Signed-off-by: or Cc: lines to the cc:
+                 list.  Note: Using this option is not recommended.
+
+   --smtp-server  If set, specifies the outgoing SMTP server to use.
+                  Defaults to localhost.
+
+   --suppress-from Suppress sending emails to yourself if your address
+                  appears in a From: line.
+
+   --quiet	  Make git-send-email less verbose.  One line per email
+                  should be all that is output.
+
+EOT
+	exit(1);
+}
+
 # most mail servers generate the Date: header, but not all...
 sub format_2822_time {
 	my ($time) = @_;
@@ -102,6 +149,16 @@
 	$term = new FakeTerm "$@: going non-interactive";
 }
 
+my $def_chain = $repo->config_boolean('sendemail.chainreplyto');
+if ($def_chain and $def_chain eq 'false') {
+    $chain_reply_to = 0;
+}
+
+@bcclist = $repo->config('sendemail.bcc');
+if (!@bcclist or !$bcclist[0]) {
+    @bcclist = ();
+}
+
 # Begin by accumulating all the variables (defined above), that we will end up
 # needing, first, from the command line:
 
@@ -120,6 +177,10 @@
 		    "dry-run" => \$dry_run,
 	 );
 
+unless ($rc) {
+    usage();
+}
+
 # Verify the user input
 
 foreach my $entry (@to) {
@@ -311,50 +372,8 @@
 		print $_,"\n" for (@files);
 	}
 } else {
-	print <<EOT;
-git-send-email [options] <file | directory> [... file | directory ]
-Options:
-   --from         Specify the "From:" line of the email to be sent.
-
-   --to           Specify the primary "To:" line of the email.
-
-   --cc           Specify an initial "Cc:" list for the entire series
-                  of emails.
-
-   --bcc          Specify a list of email addresses that should be Bcc:
-		  on all the emails.
-
-   --compose      Use \$EDITOR to edit an introductory message for the
-                  patch series.
-
-   --subject      Specify the initial "Subject:" line.
-                  Only necessary if --compose is also set.  If --compose
-		  is not set, this will be prompted for.
-
-   --in-reply-to  Specify the first "In-Reply-To:" header line.
-                  Only used if --compose is also set.  If --compose is not
-		  set, this will be prompted for.
-
-   --chain-reply-to If set, the replies will all be to the previous
-                  email sent, rather than to the first email sent.
-                  Defaults to on.
-
-   --no-signed-off-cc Suppress the automatic addition of email addresses
-                 that appear in a Signed-off-by: line, to the cc: list.
-		 Note: Using this option is not recommended.
-
-   --smtp-server  If set, specifies the outgoing SMTP server to use.
-                  Defaults to localhost.
-
-  --suppress-from Suppress sending emails to yourself if your address
-                  appears in a From: line.
-
-   --quiet	Make git-send-email less verbose.  One line per email should be
-		all that is output.
-
-Error: Please specify a file or a directory on the command line.
-EOT
-	exit(1);
+	print STDERR "\nNo patch files specified!\n\n";
+	usage();
 }
 
 # Variables we set as part of the loop over files
@@ -553,8 +572,8 @@
 			}
 		} else {
 			$message .=  $_;
-			if (/^Signed-off-by: (.*)$/i && !$no_signed_off_cc) {
-				my $c = $1;
+			if (/^(Signed-off-by|Cc): (.*)$/i && !$no_signed_off_cc) {
+				my $c = $2;
 				chomp $c;
 				push @cc, $c;
 				printf("(sob) Adding cc: %s from line '%s'\n",
diff --git a/git-svn.perl b/git-svn.perl
index d792a62..d307d43 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -4,33 +4,21 @@
 use warnings;
 use strict;
 use vars qw/	$AUTHOR $VERSION
-		$SVN_URL $SVN_INFO $SVN_WC $SVN_UUID
-		$GIT_SVN_INDEX $GIT_SVN
-		$GIT_DIR $GIT_SVN_DIR $REVDB/;
+		$sha1 $sha1_short $_revision
+		$_q $_authors %users/;
 $AUTHOR = 'Eric Wong <normalperson@yhbt.net>';
 $VERSION = '@@GIT_VERSION@@';
 
-use Cwd qw/abs_path/;
-$GIT_DIR = abs_path($ENV{GIT_DIR} || '.git');
-$ENV{GIT_DIR} = $GIT_DIR;
+my $git_dir_user_set = 1 if defined $ENV{GIT_DIR};
+$ENV{GIT_DIR} ||= '.git';
+$Git::SVN::default_repo_id = 'svn';
+$Git::SVN::default_ref_id = $ENV{GIT_SVN_ID} || 'git-svn';
+$Git::SVN::Ra::_log_window_size = 100;
 
-my $LC_ALL = $ENV{LC_ALL};
-my $TZ = $ENV{TZ};
-# make sure the svn binary gives consistent output between locales and TZs:
+$Git::SVN::Log::TZ = $ENV{TZ};
 $ENV{TZ} = 'UTC';
-$ENV{LC_ALL} = 'C';
 $| = 1; # unbuffer STDOUT
 
-# properties that we do not log:
-my %SKIP = ( 'svn:wc:ra_dav:version-url' => 1,
-             'svn:special' => 1,
-             'svn:executable' => 1,
-             'svn:entry:committed-rev' => 1,
-             'svn:entry:last-author' => 1,
-             'svn:entry:uuid' => 1,
-             'svn:entry:committed-date' => 1,
-);
-
 sub fatal (@) { print STDERR @_; exit 1 }
 require SVN::Core; # use()-ing this causes segfaults for me... *shrug*
 require SVN::Ra;
@@ -38,120 +26,130 @@
 if ($SVN::Core::VERSION lt '1.1.0') {
 	fatal "Need SVN::Core 1.1.0 or better (got $SVN::Core::VERSION)\n";
 }
+push @Git::SVN::Ra::ISA, 'SVN::Ra';
 push @SVN::Git::Editor::ISA, 'SVN::Delta::Editor';
 push @SVN::Git::Fetcher::ISA, 'SVN::Delta::Editor';
-*SVN::Git::Fetcher::process_rm = *process_rm;
 use Carp qw/croak/;
 use IO::File qw//;
 use File::Basename qw/dirname basename/;
 use File::Path qw/mkpath/;
 use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev pass_through/;
-use POSIX qw/strftime/;
 use IPC::Open3;
-use Memoize;
-use Git qw/command command_oneline command_noisy
-           command_output_pipe command_input_pipe command_close_pipe/;
-memoize('revisions_eq');
-memoize('cmt_metadata');
-memoize('get_commit_time');
+use Git;
+
+BEGIN {
+	my $s;
+	foreach (qw/command command_oneline command_noisy command_output_pipe
+	            command_input_pipe command_close_pipe/) {
+		$s .= "*SVN::Git::Editor::$_ = *SVN::Git::Fetcher::$_ = ".
+		      "*Git::SVN::Migration::$_ = ".
+		      "*Git::SVN::Log::$_ = *Git::SVN::$_ = *$_ = *Git::$_; ";
+	}
+	eval $s;
+}
 
 my ($SVN);
 
-my $_optimize_commits = 1 unless $ENV{GIT_SVN_NO_OPTIMIZE_COMMITS};
-my $sha1 = qr/[a-f\d]{40}/;
-my $sha1_short = qr/[a-f\d]{4,40}/;
-my $_esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/;
-my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit,
-	$_find_copies_harder, $_l, $_cp_similarity, $_cp_remote,
-	$_repack, $_repack_nr, $_repack_flags, $_q,
-	$_message, $_file, $_follow_parent, $_no_metadata,
-	$_template, $_shared, $_no_default_regex, $_no_graft_copy,
-	$_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit,
-	$_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m,
-	$_merge, $_strategy, $_dry_run, $_ignore_nodate, $_non_recursive,
-	$_username, $_config_dir, $_no_auth_cache,
-	$_pager, $_color, $_prefix);
-my (@_branch_from, %tree_map, %users, %rusers, %equiv);
-my ($_svn_can_do_switch);
-my @repo_path_split_cache;
-
-my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext,
-		'branch|b=s' => \@_branch_from,
-		'follow-parent|follow' => \$_follow_parent,
-		'branch-all-refs|B' => \$_branch_all_refs,
+$sha1 = qr/[a-f\d]{40}/;
+$sha1_short = qr/[a-f\d]{4,40}/;
+my ($_stdin, $_help, $_edit,
+	$_message, $_file,
+	$_template, $_shared,
+	$_version, $_fetch_all,
+	$_merge, $_strategy, $_dry_run, $_local,
+	$_prefix, $_no_checkout, $_verbose);
+$Git::SVN::_follow_parent = 1;
+my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username,
+                    'config-dir=s' => \$Git::SVN::Ra::config_dir,
+                    'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache );
+my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent,
 		'authors-file|A=s' => \$_authors,
-		'repack:i' => \$_repack,
-		'no-metadata' => \$_no_metadata,
+		'repack:i' => \$Git::SVN::_repack,
+		'noMetadata' => \$Git::SVN::_no_metadata,
+		'useSvmProps' => \$Git::SVN::_use_svm_props,
+		'useSvnsyncProps' => \$Git::SVN::_use_svnsync_props,
+		'log-window-size=i' => \$Git::SVN::Ra::_log_window_size,
+		'no-checkout' => \$_no_checkout,
 		'quiet|q' => \$_q,
-		'username=s' => \$_username,
-		'config-dir=s' => \$_config_dir,
-		'no-auth-cache' => \$_no_auth_cache,
-		'ignore-nodate' => \$_ignore_nodate,
-		'repack-flags|repack-args|repack-opts=s' => \$_repack_flags);
+		'repack-flags|repack-args|repack-opts=s' =>
+		   \$Git::SVN::_repack_flags,
+		%remote_opts );
 
 my ($_trunk, $_tags, $_branches);
-my %multi_opts = ( 'trunk|T=s' => \$_trunk,
-		'tags|t=s' => \$_tags,
-		'branches|b=s' => \$_branches );
-my %init_opts = ( 'template=s' => \$_template, 'shared' => \$_shared );
+my %icv;
+my %init_opts = ( 'template=s' => \$_template, 'shared:s' => \$_shared,
+                  'trunk|T=s' => \$_trunk, 'tags|t=s' => \$_tags,
+                  'branches|b=s' => \$_branches, 'prefix=s' => \$_prefix,
+		  'no-metadata' => sub { $icv{noMetadata} = 1 },
+		  'use-svm-props' => sub { $icv{useSvmProps} = 1 },
+		  'use-svnsync-props' => sub { $icv{useSvnsyncProps} = 1 },
+		  'rewrite-root=s' => sub { $icv{rewriteRoot} = $_[1] },
+                  %remote_opts );
 my %cmt_opts = ( 'edit|e' => \$_edit,
-		'rmdir' => \$_rmdir,
-		'find-copies-harder' => \$_find_copies_harder,
-		'l=i' => \$_l,
-		'copy-similarity|C=i'=> \$_cp_similarity
+		'rmdir' => \$SVN::Git::Editor::_rmdir,
+		'find-copies-harder' => \$SVN::Git::Editor::_find_copies_harder,
+		'l=i' => \$SVN::Git::Editor::_rename_limit,
+		'copy-similarity|C=i'=> \$SVN::Git::Editor::_cp_similarity
 );
 
 my %cmd = (
 	fetch => [ \&cmd_fetch, "Download new revisions from SVN",
-			{ 'revision|r=s' => \$_revision, %fc_opts } ],
-	init => [ \&init, "Initialize a repo for tracking" .
+			{ 'revision|r=s' => \$_revision,
+			  'fetch-all|all' => \$_fetch_all,
+			   %fc_opts } ],
+	clone => [ \&cmd_clone, "Initialize and fetch revisions",
+			{ 'revision|r=s' => \$_revision,
+			   %fc_opts, %init_opts } ],
+	init => [ \&cmd_init, "Initialize a repo for tracking" .
 			  " (requires URL argument)",
 			  \%init_opts ],
-	dcommit => [ \&dcommit, 'Commit several diffs to merge with upstream',
+	'multi-init' => [ \&cmd_multi_init,
+	                  "Deprecated alias for ".
+			  "'$0 init -T<trunk> -b<branches> -t<tags>'",
+			  \%init_opts ],
+	dcommit => [ \&cmd_dcommit,
+	             'Commit several diffs to merge with upstream',
 			{ 'merge|m|M' => \$_merge,
 			  'strategy|s=s' => \$_strategy,
-			  'dry-run|n' => \$_dry_run,
-			%cmt_opts, %fc_opts } ],
-	'set-tree' => [ \&commit, "Set an SVN repository to a git tree-ish",
-			{	'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ],
-	'show-ignore' => [ \&show_ignore, "Show svn:ignore listings",
-			{ 'revision|r=i' => \$_revision } ],
-	rebuild => [ \&rebuild, "Rebuild git-svn metadata (after git clone)",
-			{ 'no-ignore-externals' => \$_no_ignore_ext,
-			  'copy-remote|remote=s' => \$_cp_remote,
-			  'upgrade' => \$_upgrade } ],
-	'graft-branches' => [ \&graft_branches,
-			'Detect merges/branches from already imported history',
-			{ 'merge-rx|m' => \@_opt_m,
-			  'branch|b=s' => \@_branch_from,
-			  'branch-all-refs|B' => \$_branch_all_refs,
-			  'no-default-regex' => \$_no_default_regex,
-			  'no-graft-copy' => \$_no_graft_copy } ],
-	'multi-init' => [ \&multi_init,
-			'Initialize multiple trees (like git-svnimport)',
-			{ %multi_opts, %init_opts,
-			 'revision|r=i' => \$_revision,
-			 'username=s' => \$_username,
-			 'config-dir=s' => \$_config_dir,
-			 'no-auth-cache' => \$_no_auth_cache,
-			 'prefix=s' => \$_prefix,
-			} ],
-	'multi-fetch' => [ \&multi_fetch,
-			'Fetch multiple trees (like git-svnimport)',
-			\%fc_opts ],
-	'log' => [ \&show_log, 'Show commit logs',
-			{ 'limit=i' => \$_limit,
-			  'revision|r=s' => \$_revision,
 			  'verbose|v' => \$_verbose,
-			  'incremental' => \$_incremental,
-			  'oneline' => \$_oneline,
-			  'show-commit' => \$_show_commit,
-			  'non-recursive' => \$_non_recursive,
+			  'dry-run|n' => \$_dry_run,
+			  'fetch-all|all' => \$_fetch_all,
+			%cmt_opts, %fc_opts } ],
+	'set-tree' => [ \&cmd_set_tree,
+	                "Set an SVN repository to a git tree-ish",
+			{ 'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ],
+	'show-ignore' => [ \&cmd_show_ignore, "Show svn:ignore listings",
+			{ 'revision|r=i' => \$_revision } ],
+	'multi-fetch' => [ \&cmd_multi_fetch,
+	                   "Deprecated alias for $0 fetch --all",
+			   { 'revision|r=s' => \$_revision, %fc_opts } ],
+	'migrate' => [ sub { },
+	               # no-op, we automatically run this anyways,
+	               'Migrate configuration/metadata/layout from
+		        previous versions of git-svn',
+                       { 'minimize' => \$Git::SVN::Migration::_minimize,
+			 %remote_opts } ],
+	'log' => [ \&Git::SVN::Log::cmd_show_log, 'Show commit logs',
+			{ 'limit=i' => \$Git::SVN::Log::limit,
+			  'revision|r=s' => \$_revision,
+			  'verbose|v' => \$Git::SVN::Log::verbose,
+			  'incremental' => \$Git::SVN::Log::incremental,
+			  'oneline' => \$Git::SVN::Log::oneline,
+			  'show-commit' => \$Git::SVN::Log::show_commit,
+			  'non-recursive' => \$Git::SVN::Log::non_recursive,
 			  'authors-file|A=s' => \$_authors,
-			  'color' => \$_color,
-			  'pager=s' => \$_pager,
+			  'color' => \$Git::SVN::Log::color,
+			  'pager=s' => \$Git::SVN::Log::pager,
 			} ],
-	'commit-diff' => [ \&commit_diff, 'Commit a diff between two trees',
+	'rebase' => [ \&cmd_rebase, "Fetch and rebase your working directory",
+			{ 'merge|m|M' => \$_merge,
+			  'verbose|v' => \$_verbose,
+			  'strategy|s=s' => \$_strategy,
+			  'local|l' => \$_local,
+			  'fetch-all|all' => \$_fetch_all,
+			  %fc_opts } ],
+	'commit-diff' => [ \&cmd_commit_diff,
+	                   'Commit a diff between two trees',
 			{ 'message|m=s' => \$_message,
 			  'file|F=s' => \$_file,
 			  'revision|r=s' => \$_revision,
@@ -170,20 +168,50 @@
 my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd);
 
 read_repo_config(\%opts);
-my $rv = GetOptions(%opts, 'help|H|h' => \$_help,
-				'version|V' => \$_version,
-				'id|i=s' => \$GIT_SVN);
+my $rv = GetOptions(%opts, 'help|H|h' => \$_help, 'version|V' => \$_version,
+                    'minimize-connections' => \$Git::SVN::Migration::_minimize,
+                    'id|i=s' => \$Git::SVN::default_ref_id,
+                    'svn-remote|remote|R=s' => sub {
+                       $Git::SVN::no_reuse_existing = 1;
+                       $Git::SVN::default_repo_id = $_[1] });
 exit 1 if (!$rv && $cmd ne 'log');
 
-set_default_vals();
 usage(0) if $_help;
 version() if $_version;
 usage(1) unless defined $cmd;
-init_vars();
 load_authors() if $_authors;
-load_all_refs() if $_branch_all_refs;
-migration_check() unless $cmd =~ /^(?:init|rebuild|multi-init|commit-diff)$/;
-$cmd{$cmd}->[0]->(@ARGV);
+
+# make sure we're always running
+unless ($cmd =~ /(?:clone|init|multi-init)$/) {
+	unless (-d $ENV{GIT_DIR}) {
+		if ($git_dir_user_set) {
+			die "GIT_DIR=$ENV{GIT_DIR} explicitly set, ",
+			    "but it is not a directory\n";
+		}
+		my $git_dir = delete $ENV{GIT_DIR};
+		chomp(my $cdup = command_oneline(qw/rev-parse --show-cdup/));
+		unless (length $cdup) {
+			die "Already at toplevel, but $git_dir ",
+			    "not found '$cdup'\n";
+		}
+		chdir $cdup or die "Unable to chdir up to '$cdup'\n";
+		unless (-d $git_dir) {
+			die "$git_dir still not found after going to ",
+			    "'$cdup'\n";
+		}
+		$ENV{GIT_DIR} = $git_dir;
+	}
+}
+unless ($cmd =~ /^(?:clone|init|multi-init|commit-diff)$/) {
+	Git::SVN::Migration::migration_check();
+}
+Git::SVN::init_vars();
+eval {
+	Git::SVN::verify_remotes_sanity();
+	$cmd{$cmd}->[0]->(@ARGV);
+};
+fatal $@ if $@;
+post_fetch_checkout();
 exit 0;
 
 ####################### primary functions ######################
@@ -198,6 +226,7 @@
 
 	foreach (sort keys %cmd) {
 		next if $cmd && $cmd ne $_;
+		next if /^multi-/; # don't show deprecated commands
 		print $fd '  ',pack('A17',$_),$cmd{$_}->[1],"\n";
 		foreach (keys %{$cmd{$_}->[2]}) {
 			# prints out arguments as they should be passed:
@@ -221,173 +250,79 @@
 	exit 0;
 }
 
-sub rebuild {
-	if (!verify_ref("refs/remotes/$GIT_SVN^0")) {
-		copy_remote_ref();
-	}
-	$SVN_URL = shift or undef;
-	my $newest_rev = 0;
-	if ($_upgrade) {
-		command_noisy('update-ref',"refs/remotes/$GIT_SVN","
-		              $GIT_SVN-HEAD");
-	} else {
-		check_upgrade_needed();
-	}
-
-	my ($rev_list, $ctx) = command_output_pipe("rev-list",
-	                                           "refs/remotes/$GIT_SVN");
-	my $latest;
-	while (<$rev_list>) {
-		chomp;
-		my $c = $_;
-		croak "Non-SHA1: $c\n" unless $c =~ /^$sha1$/o;
-		my @commit = grep(/^git-svn-id: /,
-		                  command(qw/cat-file commit/, $c));
-		next if (!@commit); # skip merges
-		my ($url, $rev, $uuid) = extract_metadata($commit[$#commit]);
-		if (!defined $rev || !$uuid) {
-			croak "Unable to extract revision or UUID from ",
-				"$c, $commit[$#commit]\n";
-		}
-
-		# if we merged or otherwise started elsewhere, this is
-		# how we break out of it
-		next if (defined $SVN_UUID && ($uuid ne $SVN_UUID));
-		next if (defined $SVN_URL && defined $url && ($url ne $SVN_URL));
-
-		unless (defined $latest) {
-			if (!$SVN_URL && !$url) {
-				croak "SVN repository location required: $url\n";
-			}
-			$SVN_URL ||= $url;
-			$SVN_UUID ||= $uuid;
-			setup_git_svn();
-			$latest = $rev;
-		}
-		revdb_set($REVDB, $rev, $c);
-		print "r$rev = $c\n";
-		$newest_rev = $rev if ($rev > $newest_rev);
-	}
-	command_close_pipe($rev_list, $ctx);
-}
-
-sub init {
-	my $url = shift or die "SVN repository location required " .
-				"as a command-line argument\n";
-	$url =~ s!/+$!!; # strip trailing slash
-
-	if (my $repo_path = shift) {
-		unless (-d $repo_path) {
-			mkpath([$repo_path]);
-		}
-		$GIT_DIR = $ENV{GIT_DIR} = $repo_path . "/.git";
-		init_vars();
-	}
-
-	$SVN_URL = $url;
-	unless (-d $GIT_DIR) {
+sub do_git_init_db {
+	unless (-d $ENV{GIT_DIR}) {
 		my @init_db = ('init');
 		push @init_db, "--template=$_template" if defined $_template;
-		push @init_db, "--shared" if defined $_shared;
+		if (defined $_shared) {
+			if ($_shared =~ /[a-z]/) {
+				push @init_db, "--shared=$_shared";
+			} else {
+				push @init_db, "--shared";
+			}
+		}
 		command_noisy(@init_db);
 	}
-	setup_git_svn();
+	my $set;
+	my $pfx = "svn-remote.$Git::SVN::default_repo_id";
+	foreach my $i (keys %icv) {
+		die "'$set' and '$i' cannot both be set\n" if $set;
+		next unless defined $icv{$i};
+		command_noisy('config', "$pfx.$i", $icv{$i});
+		$set = $i;
+	}
+}
+
+sub init_subdir {
+	my $repo_path = shift or return;
+	mkpath([$repo_path]) unless -d $repo_path;
+	chdir $repo_path or die "Couldn't chdir to $repo_path: $!\n";
+	$ENV{GIT_DIR} = '.git';
+}
+
+sub cmd_clone {
+	my ($url, $path) = @_;
+	if (!defined $path &&
+	    (defined $_trunk || defined $_branches || defined $_tags) &&
+	    $url !~ m#^[a-z\+]+://#) {
+		$path = $url;
+	}
+	$path = basename($url) if !defined $path || !length $path;
+	cmd_init($url, $path);
+	Git::SVN::fetch_all($Git::SVN::default_repo_id);
+}
+
+sub cmd_init {
+	if (defined $_trunk || defined $_branches || defined $_tags) {
+		return cmd_multi_init(@_);
+	}
+	my $url = shift or die "SVN repository location required ",
+	                       "as a command-line argument\n";
+	init_subdir(@_);
+	do_git_init_db();
+
+	Git::SVN->init($url);
 }
 
 sub cmd_fetch {
-	fetch_child_id($GIT_SVN, @_);
+	if (grep /^\d+=./, @_) {
+		die "'<rev>=<commit>' fetch arguments are ",
+		    "no longer supported.\n";
+	}
+	my ($remote) = @_;
+	if (@_ > 1) {
+		die "Usage: $0 fetch [--all] [svn-remote]\n";
+	}
+	$remote ||= $Git::SVN::default_repo_id;
+	if ($_fetch_all) {
+		cmd_multi_fetch();
+	} else {
+		Git::SVN::fetch_all($remote, Git::SVN::read_all_remotes());
+	}
 }
 
-sub fetch {
-	check_upgrade_needed();
-	$SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url");
-	my $ret = fetch_lib(@_);
-	if ($ret->{commit} && !verify_ref('refs/heads/master^0')) {
-		command_noisy(qw(update-ref refs/heads/master),$ret->{commit});
-	}
-	return $ret;
-}
-
-sub fetch_lib {
-	my (@parents) = @_;
-	$SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url");
-	$SVN ||= libsvn_connect($SVN_URL);
-	my ($last_rev, $last_commit) = svn_grab_base_rev();
-	my ($base, $head) = libsvn_parse_revision($last_rev);
-	if ($base > $head) {
-		return { revision => $last_rev, commit => $last_commit }
-	}
-	my $index = set_index($GIT_SVN_INDEX);
-
-	# limit ourselves and also fork() since get_log won't release memory
-	# after processing a revision and SVN stuff seems to leak
-	my $inc = 1000;
-	my ($min, $max) = ($base, $head < $base+$inc ? $head : $base+$inc);
-	read_uuid();
-	if (defined $last_commit) {
-		unless (-e $GIT_SVN_INDEX) {
-			command_noisy('read-tree', $last_commit);
-		}
-		my $x = command_oneline('write-tree');
-		my ($y) = (command(qw/cat-file commit/, $last_commit)
-							=~ /^tree ($sha1)/m);
-		if ($y ne $x) {
-			unlink $GIT_SVN_INDEX or croak $!;
-			command_noisy('read-tree', $last_commit);
-		}
-		$x = command_oneline('write-tree');
-		if ($y ne $x) {
-			print STDERR "trees ($last_commit) $y != $x\n",
-				 "Something is seriously wrong...\n";
-		}
-	}
-	while (1) {
-		# fork, because using SVN::Pool with get_log() still doesn't
-		# seem to help enough to keep memory usage down.
-		defined(my $pid = fork) or croak $!;
-		if (!$pid) {
-			$SVN::Error::handler = \&libsvn_skip_unknown_revs;
-
-			# Yes I'm perfectly aware that the fourth argument
-			# below is the limit revisions number.  Unfortunately
-			# performance sucks with it enabled, so it's much
-			# faster to fetch revision ranges instead of relying
-			# on the limiter.
-			libsvn_get_log(libsvn_dup_ra($SVN), [''],
-					$min, $max, 0, 1, 1,
-				sub {
-					my $log_msg;
-					if ($last_commit) {
-						$log_msg = libsvn_fetch(
-							$last_commit, @_);
-						$last_commit = git_commit(
-							$log_msg,
-							$last_commit,
-							@parents);
-					} else {
-						$log_msg = libsvn_new_tree(@_);
-						$last_commit = git_commit(
-							$log_msg, @parents);
-					}
-				});
-			exit 0;
-		}
-		waitpid $pid, 0;
-		croak $? if $?;
-		($last_rev, $last_commit) = svn_grab_base_rev();
-		last if ($max >= $head);
-		$min = $max + 1;
-		$max += $inc;
-		$max = $head if ($max > $head);
-		$SVN = libsvn_connect($SVN_URL);
-	}
-	restore_index($index);
-	return { revision => $last_rev, commit => $last_commit };
-}
-
-sub commit {
+sub cmd_set_tree {
 	my (@commits) = @_;
-	check_upgrade_needed();
 	if ($_stdin || !@commits) {
 		print "Reading from stdin...\n";
 		@commits = ();
@@ -405,702 +340,287 @@
 		} elsif (scalar @tmp > 1) {
 			push @revs, reverse(command('rev-list',@tmp));
 		} else {
-			die "Failed to rev-parse $c\n";
+			fatal "Failed to rev-parse $c\n";
 		}
 	}
-	commit_lib(@revs);
+	my $gs = Git::SVN->new;
+	my ($r_last, $cmt_last) = $gs->last_rev_commit;
+	$gs->fetch;
+	if (defined $gs->{last_rev} && $r_last != $gs->{last_rev}) {
+		fatal "There are new revisions that were fetched ",
+		      "and need to be merged (or acknowledged) ",
+		      "before committing.\nlast rev: $r_last\n",
+		      " current: $gs->{last_rev}\n";
+	}
+	$gs->set_tree($_) foreach @revs;
 	print "Done committing ",scalar @revs," revisions to SVN\n";
 }
 
-sub commit_lib {
-	my (@revs) = @_;
-	my ($r_last, $cmt_last) = svn_grab_base_rev();
-	defined $r_last or die "Must have an existing revision to commit\n";
-	my $fetched = fetch();
-	if ($r_last != $fetched->{revision}) {
-		print STDERR "There are new revisions that were fetched ",
-				"and need to be merged (or acknowledged) ",
-				"before committing.\n",
-				"last rev: $r_last\n",
-				" current: $fetched->{revision}\n";
-		exit 1;
+sub cmd_dcommit {
+	my $head = shift;
+	$head ||= 'HEAD';
+	my @refs;
+	my ($url, $rev, $uuid) = working_head_info($head, \@refs);
+	my $c = $refs[-1];
+	unless (defined $url && defined $rev && defined $uuid) {
+		die "Unable to determine upstream SVN information from ",
+		    "$head history\n";
 	}
-	read_uuid();
-	my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : ();
-	my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$";
-
-	my $repo;
-	set_svn_commit_env();
-	foreach my $c (@revs) {
-		my $log_msg = get_commit_message($c, $commit_msg);
-
-		# fork for each commit because there's a memory leak I
-		# can't track down... (it's probably in the SVN code)
-		defined(my $pid = open my $fh, '-|') or croak $!;
-		if (!$pid) {
-			my $ed = SVN::Git::Editor->new(
-					{	r => $r_last,
-						ra => libsvn_dup_ra($SVN),
-						c => $c,
-						svn_path => $SVN->{svn_path},
-					},
-					$SVN->get_commit_editor(
-						$log_msg->{msg},
-						sub {
-							libsvn_commit_cb(
-								@_, $c,
-								$log_msg->{msg},
-								$r_last,
-								$cmt_last)
-						},
-						@lock)
-					);
-			my $mods = libsvn_checkout_tree($cmt_last, $c, $ed);
-			if (@$mods == 0) {
-				print "No changes\nr$r_last = $cmt_last\n";
-				$ed->abort_edit;
-			} else {
-				$ed->close_edit;
-			}
-			exit 0;
-		}
-		my ($r_new, $cmt_new, $no);
-		while (<$fh>) {
-			print $_;
-			chomp;
-			if (/^r(\d+) = ($sha1)$/o) {
-				($r_new, $cmt_new) = ($1, $2);
-			} elsif ($_ eq 'No changes') {
-				$no = 1;
-			}
-		}
-		close $fh or exit 1;
-		if (! defined $r_new && ! defined $cmt_new) {
-			unless ($no) {
-				die "Failed to parse revision information\n";
-			}
-		} else {
-			($r_last, $cmt_last) = ($r_new, $cmt_new);
-		}
-	}
-	$ENV{LC_ALL} = 'C';
-	unlink $commit_msg;
-}
-
-sub dcommit {
-	my $head = shift || 'HEAD';
-	my $gs = "refs/remotes/$GIT_SVN";
-	my @refs = command(qw/rev-list --no-merges/, "$gs..$head");
+	my $gs = Git::SVN->find_by_url($url);
 	my $last_rev;
-	foreach my $d (reverse @refs) {
+	foreach my $d (@refs) {
 		if (!verify_ref("$d~1")) {
-			die "Commit $d\n",
-			    "has no parent commit, and therefore ",
-			    "nothing to diff against.\n",
-			    "You should be working from a repository ",
-			    "originally created by git-svn\n";
+			fatal "Commit $d\n",
+			      "has no parent commit, and therefore ",
+			      "nothing to diff against.\n",
+			      "You should be working from a repository ",
+			      "originally created by git-svn\n";
 		}
 		unless (defined $last_rev) {
 			(undef, $last_rev, undef) = cmt_metadata("$d~1");
 			unless (defined $last_rev) {
-				die "Unable to extract revision information ",
-				    "from commit $d~1\n";
+				fatal "Unable to extract revision information ",
+				      "from commit $d~1\n";
 			}
 		}
 		if ($_dry_run) {
 			print "diff-tree $d~1 $d\n";
 		} else {
-			if (my $r = commit_diff("$d~1", $d, undef, $last_rev)) {
-				$last_rev = $r;
-			} # else: no changes, same $last_rev
+			my %ed_opts = ( r => $last_rev,
+			                log => get_commit_entry($d)->{log},
+			                ra => Git::SVN::Ra->new($url),
+			                tree_a => "$d~1",
+			                tree_b => $d,
+			                editor_cb => sub {
+			                       print "Committed r$_[0]\n";
+			                       $last_rev = $_[0]; },
+			                svn_path => '');
+			if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) {
+				print "No changes\n$d~1 == $d\n";
+			}
 		}
 	}
 	return if $_dry_run;
-	fetch();
-	my @diff = command('diff-tree', 'HEAD', $gs, '--');
+	unless ($gs) {
+		warn "Could not determine fetch information for $url\n",
+		     "Will not attempt to fetch and rebase commits.\n",
+		     "This probably means you have useSvmProps and should\n",
+		     "now resync your SVN::Mirror repository.\n";
+		return;
+	}
+	$_fetch_all ? $gs->fetch_all : $gs->fetch;
+	# we always want to rebase against the current HEAD, not any
+	# head that was passed to us
+	my @diff = command('diff-tree', 'HEAD', $gs->refname, '--');
 	my @finish;
 	if (@diff) {
-		@finish = qw/rebase/;
-		push @finish, qw/--merge/ if $_merge;
-		push @finish, "--strategy=$_strategy" if $_strategy;
-		print STDERR "W: HEAD and $gs differ, using @finish:\n", @diff;
+		@finish = rebase_cmd();
+		print STDERR "W: HEAD and ", $gs->refname, " differ, ",
+		             "using @finish:\n", "@diff";
 	} else {
-		print "No changes between current HEAD and $gs\n",
-		      "Resetting to the latest $gs\n";
+		print "No changes between current HEAD and ",
+		      $gs->refname, "\nResetting to the latest ",
+		      $gs->refname, "\n";
 		@finish = qw/reset --mixed/;
 	}
-	command_noisy(@finish, $gs);
+	command_noisy(@finish, $gs->refname);
 }
 
-sub show_ignore {
-	$SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url");
-	my $repo;
-	$SVN ||= libsvn_connect($SVN_URL);
-	my $r = defined $_revision ? $_revision : $SVN->get_latest_revnum;
-	libsvn_traverse_ignore(\*STDOUT, '', $r);
+sub cmd_rebase {
+	command_noisy(qw/update-index --refresh/);
+	my $url = (working_head_info('HEAD'))[0];
+	if (!defined $url) {
+		die "Unable to determine upstream SVN information from ",
+		    "working tree history\n";
+	}
+
+	my $gs = Git::SVN->find_by_url($url);
+	unless ($gs) {
+		die "Unable to determine remote information from URL: $url\n";
+	}
+	if (command(qw/diff-index HEAD --/)) {
+		print STDERR "Cannot rebase with uncommited changes:\n";
+		command_noisy('status');
+		exit 1;
+	}
+	unless ($_local) {
+		$_fetch_all ? $gs->fetch_all : $gs->fetch;
+	}
+	command_noisy(rebase_cmd(), $gs->refname);
 }
 
-sub graft_branches {
-	my $gr_file = "$GIT_DIR/info/grafts";
-	my ($grafts, $comments) = read_grafts($gr_file);
-	my $gr_sha1;
-
-	if (%$grafts) {
-		# temporarily disable our grafts file to make this idempotent
-		chomp($gr_sha1 = command(qw/hash-object -w/,$gr_file));
-		rename $gr_file, "$gr_file~$gr_sha1" or croak $!;
-	}
-
-	my $l_map = read_url_paths();
-	my @re = map { qr/$_/is } @_opt_m if @_opt_m;
-	unless ($_no_default_regex) {
-		push @re, (qr/\b(?:merge|merging|merged)\s+with\s+([\w\.\-]+)/i,
-			qr/\b(?:merge|merging|merged)\s+([\w\.\-]+)/i,
-			qr/\b(?:from|of)\s+([\w\.\-]+)/i );
-	}
-	foreach my $u (keys %$l_map) {
-		if (@re) {
-			foreach my $p (keys %{$l_map->{$u}}) {
-				graft_merge_msg($grafts,$l_map,$u,$p,@re);
-			}
-		}
-		unless ($_no_graft_copy) {
-			graft_file_copy_lib($grafts,$l_map,$u);
-		}
-	}
-	graft_tree_joins($grafts);
-
-	write_grafts($grafts, $comments, $gr_file);
-	unlink "$gr_file~$gr_sha1" if $gr_sha1;
+sub cmd_show_ignore {
+	my $url = (::working_head_info('HEAD'))[0];
+	my $gs = Git::SVN->find_by_url($url) || Git::SVN->new;
+	my $r = (defined $_revision ? $_revision : $gs->ra->get_latest_revnum);
+	$gs->traverse_ignore(\*STDOUT, $gs->{path}, $r);
 }
 
-sub multi_init {
+sub cmd_multi_init {
 	my $url = shift;
 	unless (defined $_trunk || defined $_branches || defined $_tags) {
 		usage(1);
 	}
-	if (defined $_trunk) {
-		my $trunk_url = complete_svn_url($url, $_trunk);
-		my $ch_id;
-		if ($GIT_SVN eq 'git-svn') {
-			$ch_id = 1;
-			$GIT_SVN = $ENV{GIT_SVN_ID} = 'trunk';
-		}
-		init_vars();
-		unless (-d $GIT_SVN_DIR) {
-			if ($ch_id) {
-				print "GIT_SVN_ID set to 'trunk' for ",
-				      "$trunk_url ($_trunk)\n";
-			}
-			init($trunk_url);
-			command_noisy('config', 'svn.trunk', $trunk_url);
-		}
-	}
 	$_prefix = '' unless defined $_prefix;
-	complete_url_ls_init($url, $_branches, '--branches/-b', $_prefix);
-	complete_url_ls_init($url, $_tags, '--tags/-t', $_prefix . 'tags/');
-}
-
-sub multi_fetch {
-	# try to do trunk first, since branches/tags
-	# may be descended from it.
-	if (-e "$GIT_DIR/svn/trunk/info/url") {
-		fetch_child_id('trunk', @_);
+	if (defined $url) {
+		$url =~ s#/+$##;
+		init_subdir(@_);
 	}
-	rec_fetch('', "$GIT_DIR/svn", @_);
-}
-
-sub show_log {
-	my (@args) = @_;
-	my ($r_min, $r_max);
-	my $r_last = -1; # prevent dupes
-	rload_authors() if $_authors;
-	if (defined $TZ) {
-		$ENV{TZ} = $TZ;
-	} else {
-		delete $ENV{TZ};
-	}
-	if (defined $_revision) {
-		if ($_revision =~ /^(\d+):(\d+)$/) {
-			($r_min, $r_max) = ($1, $2);
-		} elsif ($_revision =~ /^\d+$/) {
-			$r_min = $r_max = $_revision;
-		} else {
-			print STDERR "-r$_revision is not supported, use ",
-				"standard \'git log\' arguments instead\n";
-			exit 1;
+	do_git_init_db();
+	if (defined $_trunk) {
+		my $trunk_ref = $_prefix . 'trunk';
+		# try both old-style and new-style lookups:
+		my $gs_trunk = eval { Git::SVN->new($trunk_ref) };
+		unless ($gs_trunk) {
+			my ($trunk_url, $trunk_path) =
+			                      complete_svn_url($url, $_trunk);
+			$gs_trunk = Git::SVN->init($trunk_url, $trunk_path,
+						   undef, $trunk_ref);
 		}
 	}
-
-	config_pager();
-	@args = (git_svn_log_cmd($r_min, $r_max), @args);
-	my $log = command_output_pipe(@args);
-	run_pager();
-	my (@k, $c, $d);
-
-	while (<$log>) {
-		if (/^${_esc_color}commit ($sha1_short)/o) {
-			my $cmt = $1;
-			if ($c && cmt_showable($c) && $c->{r} != $r_last) {
-				$r_last = $c->{r};
-				process_commit($c, $r_min, $r_max, \@k) or
-								goto out;
-			}
-			$d = undef;
-			$c = { c => $cmt };
-		} elsif (/^${_esc_color}author (.+) (\d+) ([\-\+]?\d+)$/) {
-			get_author_info($c, $1, $2, $3);
-		} elsif (/^${_esc_color}(?:tree|parent|committer) /) {
-			# ignore
-		} elsif (/^${_esc_color}:\d{6} \d{6} $sha1_short/o) {
-			push @{$c->{raw}}, $_;
-		} elsif (/^${_esc_color}[ACRMDT]\t/) {
-			# we could add $SVN->{svn_path} here, but that requires
-			# remote access at the moment (repo_path_split)...
-			s#^(${_esc_color})([ACRMDT])\t#$1   $2 #;
-			push @{$c->{changed}}, $_;
-		} elsif (/^${_esc_color}diff /) {
-			$d = 1;
-			push @{$c->{diff}}, $_;
-		} elsif ($d) {
-			push @{$c->{diff}}, $_;
-		} elsif (/^${_esc_color}    (git-svn-id:.+)$/) {
-			($c->{url}, $c->{r}, undef) = extract_metadata($1);
-		} elsif (s/^${_esc_color}    //) {
-			push @{$c->{l}}, $_;
-		}
-	}
-	if ($c && defined $c->{r} && $c->{r} != $r_last) {
-		$r_last = $c->{r};
-		process_commit($c, $r_min, $r_max, \@k);
-	}
-	if (@k) {
-		my $swap = $r_max;
-		$r_max = $r_min;
-		$r_min = $swap;
-		process_commit($_, $r_min, $r_max) foreach reverse @k;
-	}
-out:
-	close $log;
-	print '-' x72,"\n" unless $_incremental || $_oneline;
+	return unless defined $_branches || defined $_tags;
+	my $ra = $url ? Git::SVN::Ra->new($url) : undef;
+	complete_url_ls_init($ra, $_branches, '--branches/-b', $_prefix);
+	complete_url_ls_init($ra, $_tags, '--tags/-t', $_prefix . 'tags/');
 }
 
-sub commit_diff_usage {
-	print STDERR "Usage: $0 commit-diff <tree-ish> <tree-ish> [<URL>]\n";
-	exit 1
+sub cmd_multi_fetch {
+	my $remotes = Git::SVN::read_all_remotes();
+	foreach my $repo_id (sort keys %$remotes) {
+		if ($remotes->{$repo_id}->{url}) {
+			Git::SVN::fetch_all($repo_id, $remotes);
+		}
+	}
 }
 
-sub commit_diff {
-	my $ta = shift or commit_diff_usage();
-	my $tb = shift or commit_diff_usage();
-	if (!eval { $SVN_URL = shift || file_to_s("$GIT_SVN_DIR/info/url") }) {
-		print STDERR "Needed URL or usable git-svn id command-line\n";
-		commit_diff_usage();
-	}
-	my $r = shift;
-	unless (defined $r) {
-		if (defined $_revision) {
-			$r = $_revision
-		} else {
-			die "-r|--revision is a required argument\n";
+# this command is special because it requires no metadata
+sub cmd_commit_diff {
+	my ($ta, $tb, $url) = @_;
+	my $usage = "Usage: $0 commit-diff -r<revision> ".
+	            "<tree-ish> <tree-ish> [<URL>]\n";
+	fatal($usage) if (!defined $ta || !defined $tb);
+	my $svn_path;
+	if (!defined $url) {
+		my $gs = eval { Git::SVN->new };
+		if (!$gs) {
+			fatal("Needed URL or usable git-svn --id in ",
+			      "the command-line\n", $usage);
 		}
+		$url = $gs->{url};
+		$svn_path = $gs->{path};
+	}
+	unless (defined $_revision) {
+		fatal("-r|--revision is a required argument\n", $usage);
 	}
 	if (defined $_message && defined $_file) {
-		print STDERR "Both --message/-m and --file/-F specified ",
-				"for the commit message.\n",
-				"I have no idea what you mean\n";
-		exit 1;
+		fatal("Both --message/-m and --file/-F specified ",
+		      "for the commit message.\n",
+		      "I have no idea what you mean\n");
 	}
 	if (defined $_file) {
 		$_message = file_to_s($_file);
 	} else {
-		$_message ||= get_commit_message($tb,
-					"$GIT_DIR/.svn-commit.tmp.$$")->{msg};
+		$_message ||= get_commit_entry($tb)->{log};
 	}
-	$SVN ||= libsvn_connect($SVN_URL);
+	my $ra ||= Git::SVN::Ra->new($url);
+	$svn_path ||= $ra->{svn_path};
+	my $r = $_revision;
 	if ($r eq 'HEAD') {
-		$r = $SVN->get_latest_revnum;
+		$r = $ra->get_latest_revnum;
 	} elsif ($r !~ /^\d+$/) {
 		die "revision argument: $r not understood by git-svn\n";
 	}
-	my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : ();
-	my $rev_committed;
-	my $ed = SVN::Git::Editor->new({	r => $r,
-						ra => libsvn_dup_ra($SVN),
-						c => $tb,
-						svn_path => $SVN->{svn_path}
-					},
-				$SVN->get_commit_editor($_message,
-					sub {
-						$rev_committed = $_[0];
-						print "Committed $_[0]\n";
-					}, @lock)
-				);
-	eval {
-		my $mods = libsvn_checkout_tree($ta, $tb, $ed);
-		if (@$mods == 0) {
-			print "No changes\n$ta == $tb\n";
-			$ed->abort_edit;
-		} else {
-			$ed->close_edit;
-		}
-	};
-	fatal "$@\n" if $@;
-	$_message = $_file = undef;
-	return $rev_committed;
+	my %ed_opts = ( r => $r,
+	                log => $_message,
+	                ra => $ra,
+	                tree_a => $ta,
+	                tree_b => $tb,
+	                editor_cb => sub { print "Committed r$_[0]\n" },
+	                svn_path => $svn_path );
+	if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) {
+		print "No changes\n$ta == $tb\n";
+	}
 }
 
 ########################### utility functions #########################
 
-sub cmt_showable {
-	my ($c) = @_;
-	return 1 if defined $c->{r};
-	if ($c->{l} && $c->{l}->[-1] eq "...\n" &&
-				$c->{a_raw} =~ /\@([a-f\d\-]+)>$/) {
-		my @msg = command(qw/cat-file commit/, $c->{c});
-		shift @msg while ($msg[0] ne "\n");
-		shift @msg;
-		@{$c->{l}} = grep !/^git-svn-id: /, @msg;
-
-		(undef, $c->{r}, undef) = extract_metadata(
-				(grep(/^git-svn-id: /, @msg))[-1]);
-	}
-	return defined $c->{r};
+sub rebase_cmd {
+	my @cmd = qw/rebase/;
+	push @cmd, '-v' if $_verbose;
+	push @cmd, qw/--merge/ if $_merge;
+	push @cmd, "--strategy=$_strategy" if $_strategy;
+	@cmd;
 }
 
-sub log_use_color {
-	return 1 if $_color;
-	my ($dc, $dcvar);
-	$dcvar = 'color.diff';
-	$dc = `git-config --get $dcvar`;
-	if ($dc eq '') {
-		# nothing at all; fallback to "diff.color"
-		$dcvar = 'diff.color';
-		$dc = `git-config --get $dcvar`;
-	}
-	chomp($dc);
-	if ($dc eq 'auto') {
-		my $pc;
-		$pc = `git-config --get color.pager`;
-		if ($pc eq '') {
-			# does not have it -- fallback to pager.color
-			$pc = `git-config --bool --get pager.color`;
-		}
-		else {
-			$pc = `git-config --bool --get color.pager`;
-			if ($?) {
-				$pc = 'false';
-			}
-		}
-		chomp($pc);
-		if (-t *STDOUT || (defined $_pager && $pc eq 'true')) {
-			return ($ENV{TERM} && $ENV{TERM} ne 'dumb');
-		}
-		return 0;
-	}
-	return 0 if $dc eq 'never';
-	return 1 if $dc eq 'always';
-	chomp($dc = `git-config --bool --get $dcvar`);
-	return ($dc eq 'true');
-}
+sub post_fetch_checkout {
+	return if $_no_checkout;
+	my $gs = $Git::SVN::_head or return;
+	return if verify_ref('refs/heads/master^0');
 
-sub git_svn_log_cmd {
-	my ($r_min, $r_max) = @_;
-	my @cmd = (qw/log --abbrev-commit --pretty=raw
-			--default/, "refs/remotes/$GIT_SVN");
-	push @cmd, '-r' unless $_non_recursive;
-	push @cmd, qw/--raw --name-status/ if $_verbose;
-	push @cmd, '--color' if log_use_color();
-	return @cmd unless defined $r_max;
-	if ($r_max == $r_min) {
-		push @cmd, '--max-count=1';
-		if (my $c = revdb_get($REVDB, $r_max)) {
-			push @cmd, $c;
-		}
-	} else {
-		my ($c_min, $c_max);
-		$c_max = revdb_get($REVDB, $r_max);
-		$c_min = revdb_get($REVDB, $r_min);
-		if (defined $c_min && defined $c_max) {
-			if ($r_max > $r_max) {
-				push @cmd, "$c_min..$c_max";
-			} else {
-				push @cmd, "$c_max..$c_min";
-			}
-		} elsif ($r_max > $r_min) {
-			push @cmd, $c_max;
-		} else {
-			push @cmd, $c_min;
-		}
-	}
-	return @cmd;
-}
+	my $valid_head = verify_ref('HEAD^0');
+	command_noisy(qw(update-ref refs/heads/master), $gs->refname);
+	return if ($valid_head || !verify_ref('HEAD^0'));
 
-sub fetch_child_id {
-	my $id = shift;
-	print "Fetching $id\n";
-	my $ref = "$GIT_DIR/refs/remotes/$id";
-	defined(my $pid = open my $fh, '-|') or croak $!;
-	if (!$pid) {
-		$GIT_SVN = $ENV{GIT_SVN_ID} = $id;
-		init_vars();
-		fetch(@_);
-		exit 0;
-	}
-	while (<$fh>) {
-		print $_;
-		check_repack() if (/^r\d+ = $sha1/o);
-	}
-	close $fh or croak $?;
-}
+	return if $ENV{GIT_DIR} !~ m#^(?:.*/)?\.git$#;
+	my $index = $ENV{GIT_INDEX_FILE} || "$ENV{GIT_DIR}/index";
+	return if -f $index;
 
-sub rec_fetch {
-	my ($pfx, $p, @args) = @_;
-	my @dir;
-	foreach (sort <$p/*>) {
-		if (-r "$_/info/url") {
-			$pfx .= '/' if $pfx && $pfx !~ m!/$!;
-			my $id = $pfx . basename $_;
-			next if $id eq 'trunk';
-			fetch_child_id($id, @args);
-		} elsif (-d $_) {
-			push @dir, $_;
-		}
-	}
-	foreach (@dir) {
-		my $x = $_;
-		$x =~ s!^\Q$GIT_DIR\E/svn/!!;
-		rec_fetch($x, $_);
-	}
+	chomp(my $bare = `git config --bool --get core.bare`);
+	return if $bare eq 'true';
+	return if command_oneline(qw/rev-parse --is-inside-git-dir/) eq 'true';
+	command_noisy(qw/read-tree -m -u -v HEAD HEAD/);
+	print STDERR "Checked out HEAD:\n  ",
+	             $gs->full_url, " r", $gs->last_rev, "\n";
 }
 
 sub complete_svn_url {
 	my ($url, $path) = @_;
 	$path =~ s#/+$##;
-	$url =~ s#/+$## if $url;
 	if ($path !~ m#^[a-z\+]+://#) {
-		$path = '/' . $path if ($path !~ m#^/#);
 		if (!defined $url || $url !~ m#^[a-z\+]+://#) {
 			fatal("E: '$path' is not a complete URL ",
 			      "and a separate URL is not specified\n");
 		}
-		$path = $url . $path;
+		return ($url, $path);
 	}
-	return $path;
+	return ($path, '');
 }
 
 sub complete_url_ls_init {
-	my ($url, $path, $switch, $pfx) = @_;
-	unless ($path) {
+	my ($ra, $repo_path, $switch, $pfx) = @_;
+	unless ($repo_path) {
 		print STDERR "W: $switch not specified\n";
 		return;
 	}
-	my $full_url = complete_svn_url($url, $path);
-	my @ls = libsvn_ls_fullurl($full_url);
-	defined(my $pid = fork) or croak $!;
-	if (!$pid) {
-		foreach my $u (map { "$full_url/$_" } (grep m!/$!, @ls)) {
-			$u =~ s#/+$##;
-			if ($u !~ m!\Q$full_url\E/(.+)$!) {
-				print STDERR "W: Unrecognized URL: $u\n";
-				die "This should never happen\n";
-			}
-			# don't try to init already existing refs
-			my $id = $pfx.$1;
-			$GIT_SVN = $ENV{GIT_SVN_ID} = $id;
-			init_vars();
-			unless (-d $GIT_SVN_DIR) {
-				print "init $u => $id\n";
-				init($u);
-			}
+	$repo_path =~ s#/+$##;
+	if ($repo_path =~ m#^[a-z\+]+://#) {
+		$ra = Git::SVN::Ra->new($repo_path);
+		$repo_path = '';
+	} else {
+		$repo_path =~ s#^/+##;
+		unless ($ra) {
+			fatal("E: '$repo_path' is not a complete URL ",
+			      "and a separate URL is not specified\n");
 		}
-		exit 0;
 	}
-	waitpid $pid, 0;
-	croak $? if $?;
+	my $url = $ra->{url};
+	my $gs = Git::SVN->init($url, undef, undef, undef, 1);
+	my $k = "svn-remote.$gs->{repo_id}.url";
+	my $orig_url = eval { command_oneline(qw/config --get/, $k) };
+	if ($orig_url && ($orig_url ne $gs->{url})) {
+		die "$k already set: $orig_url\n",
+		    "wanted to set to: $gs->{url}\n";
+	}
+	command_oneline('config', $k, $gs->{url}) unless $orig_url;
+	my $remote_path = "$ra->{svn_path}/$repo_path/*";
+	$remote_path =~ s#/+#/#g;
+	$remote_path =~ s#^/##g;
 	my ($n) = ($switch =~ /^--(\w+)/);
-	command_noisy('config', "svn.$n", $full_url);
-}
-
-sub common_prefix {
-	my $paths = shift;
-	my %common;
-	foreach (@$paths) {
-		my @tmp = split m#/#, $_;
-		my $p = '';
-		while (my $x = shift @tmp) {
-			$p .= "/$x";
-			$common{$p} ||= 0;
-			$common{$p}++;
-		}
+	if (length $pfx && $pfx !~ m#/$#) {
+		die "--prefix='$pfx' must have a trailing slash '/'\n";
 	}
-	foreach (sort {length $b <=> length $a} keys %common) {
-		if ($common{$_} == @$paths) {
-			return $_;
-		}
-	}
-	return '';
-}
-
-# grafts set here are 'stronger' in that they're based on actual tree
-# matches, and won't be deleted from merge-base checking in write_grafts()
-sub graft_tree_joins {
-	my $grafts = shift;
-	map_tree_joins() if (@_branch_from && !%tree_map);
-	return unless %tree_map;
-
-	git_svn_each(sub {
-		my $i = shift;
-		my @args = (qw/rev-list --pretty=raw/, "refs/remotes/$i");
-		my ($fh, $ctx) = command_output_pipe(@args);
-		while (<$fh>) {
-			next unless /^commit ($sha1)$/o;
-			my $c = $1;
-			my ($t) = (<$fh> =~ /^tree ($sha1)$/o);
-			next unless $tree_map{$t};
-
-			my $l;
-			do {
-				$l = readline $fh;
-			} until ($l =~ /^committer (?:.+) (\d+) ([\-\+]?\d+)$/);
-
-			my ($s, $tz) = ($1, $2);
-			if ($tz =~ s/^\+//) {
-				$s += tz_to_s_offset($tz);
-			} elsif ($tz =~ s/^\-//) {
-				$s -= tz_to_s_offset($tz);
-			}
-
-			my ($url_a, $r_a, $uuid_a) = cmt_metadata($c);
-
-			foreach my $p (@{$tree_map{$t}}) {
-				next if $p eq $c;
-				my $mb = eval { command('merge-base', $c, $p) };
-				next unless ($@ || $?);
-				if (defined $r_a) {
-					# see if SVN says it's a relative
-					my ($url_b, $r_b, $uuid_b) =
-							cmt_metadata($p);
-					next if (defined $url_b &&
-							defined $url_a &&
-							($url_a eq $url_b) &&
-							($uuid_a eq $uuid_b));
-					if ($uuid_a eq $uuid_b) {
-						if ($r_b < $r_a) {
-							$grafts->{$c}->{$p} = 2;
-							next;
-						} elsif ($r_b > $r_a) {
-							$grafts->{$p}->{$c} = 2;
-							next;
-						}
-					}
-				}
-				my $ct = get_commit_time($p);
-				if ($ct < $s) {
-					$grafts->{$c}->{$p} = 2;
-				} elsif ($ct > $s) {
-					$grafts->{$p}->{$c} = 2;
-				}
-				# what should we do when $ct == $s ?
-			}
-		}
-		command_close_pipe($fh, $ctx);
-	});
-}
-
-sub graft_file_copy_lib {
-	my ($grafts, $l_map, $u) = @_;
-	my $tree_paths = $l_map->{$u};
-	my $pfx = common_prefix([keys %$tree_paths]);
-	my ($repo, $path) = repo_path_split($u.$pfx);
-	$SVN = libsvn_connect($repo);
-
-	my ($base, $head) = libsvn_parse_revision();
-	my $inc = 1000;
-	my ($min, $max) = ($base, $head < $base+$inc ? $head : $base+$inc);
-	my $eh = $SVN::Error::handler;
-	$SVN::Error::handler = \&libsvn_skip_unknown_revs;
-	while (1) {
-		my $pool = SVN::Pool->new;
-		libsvn_get_log(libsvn_dup_ra($SVN), [$path],
-		               $min, $max, 0, 2, 1,
-			sub {
-				libsvn_graft_file_copies($grafts, $tree_paths,
-							$path, @_);
-			}, $pool);
-		$pool->clear;
-		last if ($max >= $head);
-		$min = $max + 1;
-		$max += $inc;
-		$max = $head if ($max > $head);
-	}
-	$SVN::Error::handler = $eh;
-}
-
-sub process_merge_msg_matches {
-	my ($grafts, $l_map, $u, $p, $c, @matches) = @_;
-	my (@strong, @weak);
-	foreach (@matches) {
-		# merging with ourselves is not interesting
-		next if $_ eq $p;
-		if ($l_map->{$u}->{$_}) {
-			push @strong, $_;
-		} else {
-			push @weak, $_;
-		}
-	}
-	foreach my $w (@weak) {
-		last if @strong;
-		# no exact match, use branch name as regexp.
-		my $re = qr/\Q$w\E/i;
-		foreach (keys %{$l_map->{$u}}) {
-			if (/$re/) {
-				push @strong, $l_map->{$u}->{$_};
-				last;
-			}
-		}
-		last if @strong;
-		$w = basename($w);
-		$re = qr/\Q$w\E/i;
-		foreach (keys %{$l_map->{$u}}) {
-			if (/$re/) {
-				push @strong, $l_map->{$u}->{$_};
-				last;
-			}
-		}
-	}
-	my ($rev) = ($c->{m} =~ /^git-svn-id:\s(?:\S+?)\@(\d+)
-					\s(?:[a-f\d\-]+)$/xsm);
-	unless (defined $rev) {
-		($rev) = ($c->{m} =~/^git-svn-id:\s(\d+)
-					\@(?:[a-f\d\-]+)/xsm);
-		return unless defined $rev;
-	}
-	foreach my $m (@strong) {
-		my ($r0, $s0) = find_rev_before($rev, $m, 1);
-		$grafts->{$c->{c}}->{$s0} = 1 if defined $s0;
-	}
-}
-
-sub graft_merge_msg {
-	my ($grafts, $l_map, $u, $p, @re) = @_;
-
-	my $x = $l_map->{$u}->{$p};
-	my $rl = rev_list_raw("refs/remotes/$x");
-	while (my $c = next_rev_list_entry($rl)) {
-		foreach my $re (@re) {
-			my (@br) = ($c->{m} =~ /$re/g);
-			next unless @br;
-			process_merge_msg_matches($grafts,$l_map,$u,$p,$c,@br);
-		}
-	}
-}
-
-sub read_uuid {
-	return if $SVN_UUID;
-	my $pool = SVN::Pool->new;
-	$SVN_UUID = $SVN->get_uuid($pool);
-	$pool->clear;
+	command_noisy('config', "svn-remote.$gs->{repo_id}.$n",
+				"$remote_path:refs/remotes/$pfx*");
 }
 
 sub verify_ref {
@@ -1109,37 +629,9 @@
 	                       { STDERR => 0 }); };
 }
 
-sub repo_path_split {
-	my $full_url = shift;
-	$full_url =~ s#/+$##;
-
-	foreach (@repo_path_split_cache) {
-		if ($full_url =~ s#$_##) {
-			my $u = $1;
-			$full_url =~ s#^/+##;
-			return ($u, $full_url);
-		}
-	}
-	my $tmp = libsvn_connect($full_url);
-	return ($tmp->{repos_root}, $tmp->{svn_path});
-}
-
-sub setup_git_svn {
-	defined $SVN_URL or croak "SVN repository location required\n";
-	unless (-d $GIT_DIR) {
-		croak "GIT_DIR=$GIT_DIR does not exist!\n";
-	}
-	mkpath([$GIT_SVN_DIR]);
-	mkpath(["$GIT_SVN_DIR/info"]);
-	open my $fh, '>>',$REVDB or croak $!;
-	close $fh;
-	s_to_file($SVN_URL,"$GIT_SVN_DIR/info/url");
-
-}
-
 sub get_tree_from_treeish {
 	my ($treeish) = @_;
-	croak "Not a sha1: $treeish\n" unless $treeish =~ /^$sha1$/o;
+	# $treeish can be a symbolic ref, too:
 	my $type = command_oneline(qw/cat-file -t/, $treeish);
 	my $expected;
 	while ($type eq 'tag') {
@@ -1148,7 +640,7 @@
 	if ($type eq 'commit') {
 		$expected = (grep /^tree /, command(qw/cat-file commit/,
 		                                    $treeish))[0];
-		($expected) = ($expected =~ /^tree ($sha1)$/);
+		($expected) = ($expected =~ /^tree ($sha1)$/o);
 		die "Unable to get tree from $treeish\n" unless $expected;
 	} elsif ($type eq 'tree') {
 		$expected = $treeish;
@@ -1158,146 +650,44 @@
 	return $expected;
 }
 
-sub get_diff {
-	my ($from, $treeish) = @_;
-	print "diff-tree $from $treeish\n";
-	my @diff_tree = qw(diff-tree -z -r);
-	if ($_cp_similarity) {
-		push @diff_tree, "-C$_cp_similarity";
-	} else {
-		push @diff_tree, '-C';
-	}
-	push @diff_tree, '--find-copies-harder' if $_find_copies_harder;
-	push @diff_tree, "-l$_l" if defined $_l;
-	push @diff_tree, $from, $treeish;
-	my ($diff_fh, $ctx) = command_output_pipe(@diff_tree);
-	local $/ = "\0";
-	my $state = 'meta';
-	my @mods;
-	while (<$diff_fh>) {
-		chomp $_; # this gets rid of the trailing "\0"
-		if ($state eq 'meta' && /^:(\d{6})\s(\d{6})\s
-					$sha1\s($sha1)\s([MTCRAD])\d*$/xo) {
-			push @mods, {	mode_a => $1, mode_b => $2,
-					sha1_b => $3, chg => $4 };
-			if ($4 =~ /^(?:C|R)$/) {
-				$state = 'file_a';
-			} else {
-				$state = 'file_b';
-			}
-		} elsif ($state eq 'file_a') {
-			my $x = $mods[$#mods] or croak "Empty array\n";
-			if ($x->{chg} !~ /^(?:C|R)$/) {
-				croak "Error parsing $_, $x->{chg}\n";
-			}
-			$x->{file_a} = $_;
-			$state = 'file_b';
-		} elsif ($state eq 'file_b') {
-			my $x = $mods[$#mods] or croak "Empty array\n";
-			if (exists $x->{file_a} && $x->{chg} !~ /^(?:C|R)$/) {
-				croak "Error parsing $_, $x->{chg}\n";
-			}
-			if (!exists $x->{file_a} && $x->{chg} =~ /^(?:C|R)$/) {
-				croak "Error parsing $_, $x->{chg}\n";
-			}
-			$x->{file_b} = $_;
-			$state = 'meta';
-		} else {
-			croak "Error parsing $_\n";
-		}
-	}
-	command_close_pipe($diff_fh, $ctx);
-	return \@mods;
-}
+sub get_commit_entry {
+	my ($treeish) = shift;
+	my %log_entry = ( log => '', tree => get_tree_from_treeish($treeish) );
+	my $commit_editmsg = "$ENV{GIT_DIR}/COMMIT_EDITMSG";
+	my $commit_msg = "$ENV{GIT_DIR}/COMMIT_MSG";
+	open my $log_fh, '>', $commit_editmsg or croak $!;
 
-sub libsvn_checkout_tree {
-	my ($from, $treeish, $ed) = @_;
-	my $mods = get_diff($from, $treeish);
-	return $mods unless (scalar @$mods);
-	my %o = ( D => 1, R => 0, C => -1, A => 3, M => 3, T => 3 );
-	foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {
-		my $f = $m->{chg};
-		if (defined $o{$f}) {
-			$ed->$f($m, $_q);
-		} else {
-			croak "Invalid change type: $f\n";
-		}
-	}
-	$ed->rmdirs($_q) if $_rmdir;
-	return $mods;
-}
-
-sub get_commit_message {
-	my ($commit, $commit_msg) = (@_);
-	my %log_msg = ( msg => '' );
-	open my $msg, '>', $commit_msg or croak $!;
-
-	my $type = command_oneline(qw/cat-file -t/, $commit);
+	my $type = command_oneline(qw/cat-file -t/, $treeish);
 	if ($type eq 'commit' || $type eq 'tag') {
 		my ($msg_fh, $ctx) = command_output_pipe('cat-file',
-		                                         $type, $commit);
+		                                         $type, $treeish);
 		my $in_msg = 0;
 		while (<$msg_fh>) {
 			if (!$in_msg) {
 				$in_msg = 1 if (/^\s*$/);
 			} elsif (/^git-svn-id: /) {
-				# skip this, we regenerate the correct one
-				# on re-fetch anyways
+				# skip this for now, we regenerate the
+				# correct one on re-fetch anyways
+				# TODO: set *:merge properties or like...
 			} else {
-				print $msg $_ or croak $!;
+				print $log_fh $_ or croak $!;
 			}
 		}
 		command_close_pipe($msg_fh, $ctx);
 	}
-	close $msg or croak $!;
+	close $log_fh or croak $!;
 
 	if ($_edit || ($type eq 'tree')) {
 		my $editor = $ENV{VISUAL} || $ENV{EDITOR} || 'vi';
-		system($editor, $commit_msg);
+		# TODO: strip out spaces, comments, like git-commit.sh
+		system($editor, $commit_editmsg);
 	}
-
-	# file_to_s removes all trailing newlines, so just use chomp() here:
-	open $msg, '<', $commit_msg or croak $!;
-	{ local $/; chomp($log_msg{msg} = <$msg>); }
-	close $msg or croak $!;
-
-	return \%log_msg;
-}
-
-sub set_svn_commit_env {
-	if (defined $LC_ALL) {
-		$ENV{LC_ALL} = $LC_ALL;
-	} else {
-		delete $ENV{LC_ALL};
-	}
-}
-
-sub rev_list_raw {
-	my ($fh, $c) = command_output_pipe(qw/rev-list --pretty=raw/, @_);
-	return { fh => $fh, ctx => $c, t => { } };
-}
-
-sub next_rev_list_entry {
-	my $rl = shift;
-	my $fh = $rl->{fh};
-	my $x = $rl->{t};
-	while (<$fh>) {
-		if (/^commit ($sha1)$/o) {
-			if ($x->{c}) {
-				$rl->{t} = { c => $1 };
-				return $x;
-			} else {
-				$x->{c} = $1;
-			}
-		} elsif (/^parent ($sha1)$/o) {
-			$x->{p}->{$1} = 1;
-		} elsif (s/^    //) {
-			$x->{m} ||= '';
-			$x->{m} .= $_;
-		}
-	}
-	command_close_pipe($fh, $rl->{ctx});
-	return ($x != $rl->{t}) ? $x : undef;
+	rename $commit_editmsg, $commit_msg or croak $!;
+	open $log_fh, '<', $commit_msg or croak $!;
+	{ local $/; chomp($log_entry{log} = <$log_fh>); }
+	close $log_fh or croak $!;
+	unlink $commit_msg;
+	\%log_entry;
 }
 
 sub s_to_file {
@@ -1318,289 +708,35 @@
 	return $ret;
 }
 
-sub assert_revision_unknown {
-	my $r = shift;
-	if (my $c = revdb_get($REVDB, $r)) {
-		croak "$r = $c already exists! Why are we refetching it?";
-	}
-}
-
-sub git_commit {
-	my ($log_msg, @parents) = @_;
-	assert_revision_unknown($log_msg->{revision});
-	map_tree_joins() if (@_branch_from && !%tree_map);
-
-	my (@tmp_parents, @exec_parents, %seen_parent);
-	if (my $lparents = $log_msg->{parents}) {
-		@tmp_parents = @$lparents
-	}
-	# commit parents can be conditionally bound to a particular
-	# svn revision via: "svn_revno=commit_sha1", filter them out here:
-	foreach my $p (@parents) {
-		next unless defined $p;
-		if ($p =~ /^(\d+)=($sha1_short)$/o) {
-			if ($1 == $log_msg->{revision}) {
-				push @tmp_parents, $2;
-			}
-		} else {
-			push @tmp_parents, $p if $p =~ /$sha1_short/o;
-		}
-	}
-	my $tree = $log_msg->{tree};
-	if (!defined $tree) {
-		my $index = set_index($GIT_SVN_INDEX);
-		$tree = command_oneline('write-tree');
-		croak $? if $?;
-		restore_index($index);
-	}
-	# just in case we clobber the existing ref, we still want that ref
-	# as our parent:
-	if (my $cur = verify_ref("refs/remotes/$GIT_SVN^0")) {
-		chomp $cur;
-		push @tmp_parents, $cur;
-	}
-
-	if (exists $tree_map{$tree}) {
-		foreach my $p (@{$tree_map{$tree}}) {
-			my $skip;
-			foreach (@tmp_parents) {
-				# see if a common parent is found
-				my $mb = eval { command('merge-base', $_, $p) };
-				next if ($@ || $?);
-				$skip = 1;
-				last;
-			}
-			next if $skip;
-			my ($url_p, $r_p, $uuid_p) = cmt_metadata($p);
-			next if (($SVN_UUID eq $uuid_p) &&
-						($log_msg->{revision} > $r_p));
-			next if (defined $url_p && defined $SVN_URL &&
-						($SVN_UUID eq $uuid_p) &&
-						($url_p eq $SVN_URL));
-			push @tmp_parents, $p;
-		}
-	}
-	foreach (@tmp_parents) {
-		next if $seen_parent{$_};
-		$seen_parent{$_} = 1;
-		push @exec_parents, $_;
-		# MAXPARENT is defined to 16 in commit-tree.c:
-		last if @exec_parents > 16;
-	}
-
-	set_commit_env($log_msg);
-	my @exec = ('git-commit-tree', $tree);
-	push @exec, '-p', $_  foreach @exec_parents;
-	defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec))
-								or croak $!;
-	print $msg_fh $log_msg->{msg} or croak $!;
-	unless ($_no_metadata) {
-		print $msg_fh "\ngit-svn-id: $SVN_URL\@$log_msg->{revision}",
-					" $SVN_UUID\n" or croak $!;
-	}
-	$msg_fh->flush == 0 or croak $!;
-	close $msg_fh or croak $!;
-	chomp(my $commit = do { local $/; <$out_fh> });
-	close $out_fh or croak $!;
-	waitpid $pid, 0;
-	croak $? if $?;
-	if ($commit !~ /^$sha1$/o) {
-		die "Failed to commit, invalid sha1: $commit\n";
-	}
-	command_noisy('update-ref',"refs/remotes/$GIT_SVN",$commit);
-	revdb_set($REVDB, $log_msg->{revision}, $commit);
-
-	# this output is read via pipe, do not change:
-	print "r$log_msg->{revision} = $commit\n";
-	return $commit;
-}
-
-sub check_repack {
-	if ($_repack && (--$_repack_nr == 0)) {
-		$_repack_nr = $_repack;
-		# repack doesn't use any arguments with spaces in them, does it?
-		command_noisy('repack', split(/\s+/, $_repack_flags));
-	}
-}
-
-sub set_commit_env {
-	my ($log_msg) = @_;
-	my $author = $log_msg->{author};
-	if (!defined $author || length $author == 0) {
-		$author = '(no author)';
-	}
-	my ($name,$email) = defined $users{$author} ?  @{$users{$author}}
-				: ($author,"$author\@$SVN_UUID");
-	$ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name;
-	$ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email;
-	$ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_msg->{date};
-}
-
-sub check_upgrade_needed {
-	if (!-r $REVDB) {
-		-d $GIT_SVN_DIR or mkpath([$GIT_SVN_DIR]);
-		open my $fh, '>>',$REVDB or croak $!;
-		close $fh;
-	}
-	return unless eval {
-		command([qw/rev-parse --verify/,"$GIT_SVN-HEAD^0"],
-		        {STDERR => 0});
-	};
-	my $head = eval { command('rev-parse',"refs/remotes/$GIT_SVN") };
-	if ($@ || !$head) {
-		print STDERR "Please run: $0 rebuild --upgrade\n";
-		exit 1;
-	}
-}
-
-# fills %tree_map with a reverse mapping of trees to commits.  Useful
-# for finding parents to commit on.
-sub map_tree_joins {
-	my %seen;
-	foreach my $br (@_branch_from) {
-		my $pipe = command_output_pipe(qw/rev-list
-		                            --topo-order --pretty=raw/, $br);
-		while (<$pipe>) {
-			if (/^commit ($sha1)$/o) {
-				my $commit = $1;
-
-				# if we've seen a commit,
-				# we've seen its parents
-				last if $seen{$commit};
-				my ($tree) = (<$pipe> =~ /^tree ($sha1)$/o);
-				unless (defined $tree) {
-					die "Failed to parse commit $commit\n";
-				}
-				push @{$tree_map{$tree}}, $commit;
-				$seen{$commit} = 1;
-			}
-		}
-		close $pipe;
-	}
-}
-
-sub load_all_refs {
-	if (@_branch_from) {
-		print STDERR '--branch|-b parameters are ignored when ',
-			"--branch-all-refs|-B is passed\n";
-	}
-
-	# don't worry about rev-list on non-commit objects/tags,
-	# it shouldn't blow up if a ref is a blob or tree...
-	@_branch_from = command(qw/rev-parse --symbolic --all/);
-}
-
 # '<svn username> = real-name <email address>' mapping based on git-svnimport:
 sub load_authors {
 	open my $authors, '<', $_authors or die "Can't open $_authors $!\n";
+	my $log = $cmd eq 'log';
 	while (<$authors>) {
 		chomp;
 		next unless /^(\S+?|\(no author\))\s*=\s*(.+?)\s*<(.+)>\s*$/;
 		my ($user, $name, $email) = ($1, $2, $3);
-		$users{$user} = [$name, $email];
+		if ($log) {
+			$Git::SVN::Log::rusers{"$name <$email>"} = $user;
+		} else {
+			$users{$user} = [$name, $email];
+		}
 	}
 	close $authors or croak $!;
 }
 
-sub rload_authors {
-	open my $authors, '<', $_authors or die "Can't open $_authors $!\n";
-	while (<$authors>) {
-		chomp;
-		next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/;
-		my ($user, $name, $email) = ($1, $2, $3);
-		$rusers{"$name <$email>"} = $user;
-	}
-	close $authors or croak $!;
-}
-
-sub git_svn_each {
-	my $sub = shift;
-	foreach (command(qw/rev-parse --symbolic --all/)) {
-		next unless s#^refs/remotes/##;
-		chomp $_;
-		next unless -f "$GIT_DIR/svn/$_/info/url";
-		&$sub($_);
-	}
-}
-
-sub migrate_revdb {
-	git_svn_each(sub {
-		my $id = shift;
-		defined(my $pid = fork) or croak $!;
-		if (!$pid) {
-			$GIT_SVN = $ENV{GIT_SVN_ID} = $id;
-			init_vars();
-			exit 0 if -r $REVDB;
-			print "Upgrading svn => git mapping...\n";
-			-d $GIT_SVN_DIR or mkpath([$GIT_SVN_DIR]);
-			open my $fh, '>>',$REVDB or croak $!;
-			close $fh;
-			rebuild();
-			print "Done upgrading. You may now delete the ",
-				"deprecated $GIT_SVN_DIR/revs directory\n";
-			exit 0;
-		}
-		waitpid $pid, 0;
-		croak $? if $?;
-	});
-}
-
-sub migration_check {
-	migrate_revdb() unless (-e $REVDB);
-	return if (-d "$GIT_DIR/svn" || !-d $GIT_DIR);
-	print "Upgrading repository...\n";
-	unless (-d "$GIT_DIR/svn") {
-		mkdir "$GIT_DIR/svn" or croak $!;
-	}
-	print "Data from a previous version of git-svn exists, but\n\t",
-				"$GIT_SVN_DIR\n\t(required for this version ",
-				"($VERSION) of git-svn) does not.\n";
-
-	foreach my $x (command(qw/rev-parse --symbolic --all/)) {
-		next unless $x =~ s#^refs/remotes/##;
-		chomp $x;
-		next unless -f "$GIT_DIR/$x/info/url";
-		my $u = eval { file_to_s("$GIT_DIR/$x/info/url") };
-		next unless $u;
-		my $dn = dirname("$GIT_DIR/svn/$x");
-		mkpath([$dn]) unless -d $dn;
-		rename "$GIT_DIR/$x", "$GIT_DIR/svn/$x" or croak "$!: $x";
-	}
-	migrate_revdb() if (-d $GIT_SVN_DIR && !-w $REVDB);
-	print "Done upgrading.\n";
-}
-
-sub find_rev_before {
-	my ($r, $id, $eq_ok) = @_;
-	my $f = "$GIT_DIR/svn/$id/.rev_db";
-	return (undef,undef) unless -r $f;
-	--$r unless $eq_ok;
-	while ($r > 0) {
-		if (my $c = revdb_get($f, $r)) {
-			return ($r, $c);
-		}
-		--$r;
-	}
-	return (undef, undef);
-}
-
-sub init_vars {
-	$GIT_SVN ||= $ENV{GIT_SVN_ID} || 'git-svn';
-	$GIT_SVN_DIR = "$GIT_DIR/svn/$GIT_SVN";
-	$REVDB = "$GIT_SVN_DIR/.rev_db";
-	$GIT_SVN_INDEX = "$GIT_SVN_DIR/index";
-	$SVN_URL = undef;
-	$SVN_WC = "$GIT_SVN_DIR/tree";
-	%tree_map = ();
-}
-
 # convert GetOpt::Long specs for use by git-config
 sub read_repo_config {
-	return unless -d $GIT_DIR;
+	return unless -d $ENV{GIT_DIR};
 	my $opts = shift;
+	my @config_only;
 	foreach my $o (keys %$opts) {
+		# if we have mixedCase and a long option-only, then
+		# it's a config-only variable that we don't need for
+		# the command-line.
+		push @config_only, $o if ($o =~ /[A-Z]/ && $o =~ /^[a-z]+$/i);
 		my $v = $opts->{$o};
-		my ($key) = ($o =~ /^([a-z\-]+)/);
+		my ($key) = ($o =~ /^([a-zA-Z\-]+)/);
 		$key =~ s/-//g;
 		my $arg = 'git-config';
 		$arg .= ' --int' if ($o =~ /[:=]i$/);
@@ -1615,127 +751,7 @@
 			}
 		}
 	}
-}
-
-sub set_default_vals {
-	if (defined $_repack) {
-		$_repack = 1000 if ($_repack <= 0);
-		$_repack_nr = $_repack;
-		$_repack_flags ||= '-d';
-	}
-}
-
-sub read_grafts {
-	my $gr_file = shift;
-	my ($grafts, $comments) = ({}, {});
-	if (open my $fh, '<', $gr_file) {
-		my @tmp;
-		while (<$fh>) {
-			if (/^($sha1)\s+/) {
-				my $c = $1;
-				if (@tmp) {
-					@{$comments->{$c}} = @tmp;
-					@tmp = ();
-				}
-				foreach my $p (split /\s+/, $_) {
-					$grafts->{$c}->{$p} = 1;
-				}
-			} else {
-				push @tmp, $_;
-			}
-		}
-		close $fh or croak $!;
-		@{$comments->{'END'}} = @tmp if @tmp;
-	}
-	return ($grafts, $comments);
-}
-
-sub write_grafts {
-	my ($grafts, $comments, $gr_file) = @_;
-
-	open my $fh, '>', $gr_file or croak $!;
-	foreach my $c (sort keys %$grafts) {
-		if ($comments->{$c}) {
-			print $fh $_ foreach @{$comments->{$c}};
-		}
-		my $p = $grafts->{$c};
-		my %x; # real parents
-		delete $p->{$c}; # commits are not self-reproducing...
-		my $ch = command_output_pipe(qw/cat-file commit/, $c);
-		while (<$ch>) {
-			if (/^parent ($sha1)/) {
-				$x{$1} = $p->{$1} = 1;
-			} else {
-				last unless /^\S/;
-			}
-		}
-		close $ch; # breaking the pipe
-
-		# if real parents are the only ones in the grafts, drop it
-		next if join(' ',sort keys %$p) eq join(' ',sort keys %x);
-
-		my (@ip, @jp, $mb);
-		my %del = %x;
-		@ip = @jp = keys %$p;
-		foreach my $i (@ip) {
-			next if $del{$i} || $p->{$i} == 2;
-			foreach my $j (@jp) {
-				next if $i eq $j || $del{$j} || $p->{$j} == 2;
-				$mb = eval { command('merge-base', $i, $j) };
-				next unless $mb;
-				chomp $mb;
-				next if $x{$mb};
-				if ($mb eq $j) {
-					delete $p->{$i};
-					$del{$i} = 1;
-				} elsif ($mb eq $i) {
-					delete $p->{$j};
-					$del{$j} = 1;
-				}
-			}
-		}
-
-		# if real parents are the only ones in the grafts, drop it
-		next if join(' ',sort keys %$p) eq join(' ',sort keys %x);
-
-		print $fh $c, ' ', join(' ', sort keys %$p),"\n";
-	}
-	if ($comments->{'END'}) {
-		print $fh $_ foreach @{$comments->{'END'}};
-	}
-	close $fh or croak $!;
-}
-
-sub read_url_paths_all {
-	my ($l_map, $pfx, $p) = @_;
-	my @dir;
-	foreach (<$p/*>) {
-		if (-r "$_/info/url") {
-			$pfx .= '/' if $pfx && $pfx !~ m!/$!;
-			my $id = $pfx . basename $_;
-			my $url = file_to_s("$_/info/url");
-			my ($u, $p) = repo_path_split($url);
-			$l_map->{$u}->{$p} = $id;
-		} elsif (-d $_) {
-			push @dir, $_;
-		}
-	}
-	foreach (@dir) {
-		my $x = $_;
-		$x =~ s!^\Q$GIT_DIR\E/svn/!!o;
-		read_url_paths_all($l_map, $x, $_);
-	}
-}
-
-# this one only gets ids that have been imported, not new ones
-sub read_url_paths {
-	my $l_map = {};
-	git_svn_each(sub { my $x = shift;
-			my $url = file_to_s("$GIT_DIR/svn/$x/info/url");
-			my ($u, $p) = repo_path_split($url);
-			$l_map->{$u}->{$p} = $x;
-			});
-	return $l_map;
+	delete @$opts{@config_only} if @config_only;
 }
 
 sub extract_metadata {
@@ -1755,164 +771,1348 @@
 		command(qw/cat-file commit/, shift)))[-1]);
 }
 
-sub get_commit_time {
-	my $cmt = shift;
-	my $fh = command_output_pipe(qw/rev-list --pretty=raw -n1/, $cmt);
+sub working_head_info {
+	my ($head, $refs) = @_;
+	my ($url, $rev, $uuid);
+	my ($fh, $ctx) = command_output_pipe('rev-list', $head);
 	while (<$fh>) {
-		/^committer\s(?:.+) (\d+) ([\-\+]?\d+)$/ or next;
-		my ($s, $tz) = ($1, $2);
-		if ($tz =~ s/^\+//) {
-			$s += tz_to_s_offset($tz);
-		} elsif ($tz =~ s/^\-//) {
-			$s -= tz_to_s_offset($tz);
+		chomp;
+		($url, $rev, $uuid) = cmt_metadata($_);
+		last if (defined $url && defined $rev && defined $uuid);
+		unshift @$refs, $_ if $refs;
+	}
+	close $fh; # break the pipe
+	($url, $rev, $uuid);
+}
+
+package Git::SVN;
+use strict;
+use warnings;
+use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent
+            $_repack $_repack_flags $_use_svm_props $_head
+            $_use_svnsync_props $no_reuse_existing/;
+use Carp qw/croak/;
+use File::Path qw/mkpath/;
+use File::Copy qw/copy/;
+use IPC::Open3;
+
+my $_repack_nr;
+# properties that we do not log:
+my %SKIP_PROP;
+BEGIN {
+	%SKIP_PROP = map { $_ => 1 } qw/svn:wc:ra_dav:version-url
+	                                svn:special svn:executable
+	                                svn:entry:committed-rev
+	                                svn:entry:last-author
+	                                svn:entry:uuid
+	                                svn:entry:committed-date/;
+
+	# some options are read globally, but can be overridden locally
+	# per [svn-remote "..."] section.  Command-line options will *NOT*
+	# override options set in an [svn-remote "..."] section
+	my $e;
+	foreach (qw/follow_parent no_metadata use_svm_props
+	            use_svnsync_props/) {
+		my $key = $_;
+		$key =~ tr/_//d;
+		$e .= "sub $_ {
+			my (\$self) = \@_;
+			return \$self->{-$_} if exists \$self->{-$_};
+			my \$k = \"svn-remote.\$self->{repo_id}\.$key\";
+			eval { command_oneline(qw/config --get/, \$k) };
+			if (\$@) {
+				\$self->{-$_} = \$Git::SVN::_$_;
+			} else {
+				my \$v = command_oneline(qw/config --bool/,\$k);
+				\$self->{-$_} = \$v eq 'false' ? 0 : 1;
+			}
+			return \$self->{-$_} }\n";
+	}
+	$e .= "1;\n";
+	eval $e or die $@;
+}
+
+my %LOCKFILES;
+END { unlink keys %LOCKFILES if %LOCKFILES }
+
+sub resolve_local_globs {
+	my ($url, $fetch, $glob_spec) = @_;
+	return unless defined $glob_spec;
+	my $ref = $glob_spec->{ref};
+	my $path = $glob_spec->{path};
+	foreach (command(qw#for-each-ref --format=%(refname) refs/remotes#)) {
+		next unless m#^refs/remotes/$ref->{regex}$#;
+		my $p = $1;
+		my $pathname = $path->full_path($p);
+		my $refname = $ref->full_path($p);
+		if (my $existing = $fetch->{$pathname}) {
+			if ($existing ne $refname) {
+				die "Refspec conflict:\n",
+				    "existing: refs/remotes/$existing\n",
+				    " globbed: refs/remotes/$refname\n";
+			}
+			my $u = (::cmt_metadata("refs/remotes/$refname"))[0];
+			$u =~ s!^\Q$url\E(/|$)!! or die
+			  "refs/remotes/$refname: '$url' not found in '$u'\n";
+			if ($pathname ne $u) {
+				warn "W: Refspec glob conflict ",
+				     "(ref: refs/remotes/$refname):\n",
+				     "expected path: $pathname\n",
+				     "    real path: $u\n",
+				     "Continuing ahead with $u\n";
+				next;
+			}
+		} else {
+			$fetch->{$pathname} = $refname;
 		}
-		close $fh;
-		return $s;
-	}
-	die "Can't get commit time for commit: $cmt\n";
-}
-
-sub tz_to_s_offset {
-	my ($tz) = @_;
-	$tz =~ s/(\d\d)$//;
-	return ($1 * 60) + ($tz * 3600);
-}
-
-# adapted from pager.c
-sub config_pager {
-	$_pager ||= $ENV{GIT_PAGER} || $ENV{PAGER};
-	if (!defined $_pager) {
-		$_pager = 'less';
-	} elsif (length $_pager == 0 || $_pager eq 'cat') {
-		$_pager = undef;
 	}
 }
 
-sub run_pager {
-	return unless -t *STDOUT;
-	pipe my $rfd, my $wfd or return;
-	defined(my $pid = fork) or croak $!;
-	if (!$pid) {
-		open STDOUT, '>&', $wfd or croak $!;
-		return;
+sub parse_revision_argument {
+	my ($base, $head) = @_;
+	if (!defined $::_revision || $::_revision eq 'BASE:HEAD') {
+		return ($base, $head);
 	}
-	open STDIN, '<&', $rfd or croak $!;
-	$ENV{LESS} ||= 'FRSX';
-	exec $_pager or croak "Can't run pager: $! ($_pager)\n";
+	return ($1, $2) if ($::_revision =~ /^(\d+):(\d+)$/);
+	return ($::_revision, $::_revision) if ($::_revision =~ /^\d+$/);
+	return ($head, $head) if ($::_revision eq 'HEAD');
+	return ($base, $1) if ($::_revision =~ /^BASE:(\d+)$/);
+	return ($1, $head) if ($::_revision =~ /^(\d+):HEAD$/);
+	die "revision argument: $::_revision not understood by git-svn\n";
 }
 
-sub get_author_info {
-	my ($dest, $author, $t, $tz) = @_;
-	$author =~ s/(?:^\s*|\s*$)//g;
-	$dest->{a_raw} = $author;
-	my $_a;
-	if ($_authors) {
-		$_a = $rusers{$author} || undef;
+sub fetch_all {
+	my ($repo_id, $remotes) = @_;
+	if (ref $repo_id) {
+		my $gs = $repo_id;
+		$repo_id = undef;
+		$repo_id = $gs->{repo_id};
 	}
-	if (!$_a) {
-		($_a) = ($author =~ /<([^>]+)\@[^>]+>$/);
-	}
-	$dest->{t} = $t;
-	$dest->{tz} = $tz;
-	$dest->{a} = $_a;
-	# Date::Parse isn't in the standard Perl distro :(
-	if ($tz =~ s/^\+//) {
-		$t += tz_to_s_offset($tz);
-	} elsif ($tz =~ s/^\-//) {
-		$t -= tz_to_s_offset($tz);
-	}
-	$dest->{t_utc} = $t;
-}
+	$remotes ||= read_all_remotes();
+	my $remote = $remotes->{$repo_id} or
+	             die "[svn-remote \"$repo_id\"] unknown\n";
+	my $fetch = $remote->{fetch};
+	my $url = $remote->{url} or die "svn-remote.$repo_id.url not defined\n";
+	my (@gs, @globs);
+	my $ra = Git::SVN::Ra->new($url);
+	my $uuid = $ra->get_uuid;
+	my $head = $ra->get_latest_revnum;
+	my $base = defined $fetch ? $head : 0;
 
-sub process_commit {
-	my ($c, $r_min, $r_max, $defer) = @_;
-	if (defined $r_min && defined $r_max) {
-		if ($r_min == $c->{r} && $r_min == $r_max) {
-			show_commit($c);
-			return 0;
+	# read the max revs for wildcard expansion (branches/*, tags/*)
+	foreach my $t (qw/branches tags/) {
+		defined $remote->{$t} or next;
+		push @globs, $remote->{$t};
+		my $max_rev = eval { tmp_config(qw/--int --get/,
+		                         "svn-remote.$repo_id.${t}-maxRev") };
+		if (defined $max_rev && ($max_rev < $base)) {
+			$base = $max_rev;
+		} elsif (!defined $max_rev) {
+			$base = 0;
 		}
-		return 1 if $r_min == $r_max;
-		if ($r_min < $r_max) {
-			# we need to reverse the print order
-			return 0 if (defined $_limit && --$_limit < 0);
-			push @$defer, $c;
+	}
+
+	if ($fetch) {
+		foreach my $p (sort keys %$fetch) {
+			my $gs = Git::SVN->new($fetch->{$p}, $repo_id, $p);
+			my $lr = $gs->rev_db_max;
+			if (defined $lr) {
+				$base = $lr if ($lr < $base);
+			}
+			push @gs, $gs;
+		}
+	}
+
+	($base, $head) = parse_revision_argument($base, $head);
+	$ra->gs_fetch_loop_common($base, $head, \@gs, \@globs);
+}
+
+sub read_all_remotes {
+	my $r = {};
+	foreach (grep { s/^svn-remote\.// } command(qw/config -l/)) {
+		if (m!^(.+)\.fetch=\s*(.*)\s*:\s*refs/remotes/(.+)\s*$!) {
+			$r->{$1}->{fetch}->{$2} = $3;
+		} elsif (m!^(.+)\.url=\s*(.*)\s*$!) {
+			$r->{$1}->{url} = $2;
+		} elsif (m!^(.+)\.(branches|tags)=
+		           (.*):refs/remotes/(.+)\s*$/!x) {
+			my ($p, $g) = ($3, $4);
+			my $rs = $r->{$1}->{$2} = {
+			                  t => $2,
+					  remote => $1,
+			                  path => Git::SVN::GlobSpec->new($p),
+			                  ref => Git::SVN::GlobSpec->new($g) };
+			if (length($rs->{ref}->{right}) != 0) {
+				die "The '*' glob character must be the last ",
+				    "character of '$g'\n";
+			}
+		}
+	}
+	$r;
+}
+
+sub init_vars {
+	if (defined $_repack) {
+		$_repack = 1000 if ($_repack <= 0);
+		$_repack_nr = $_repack;
+		$_repack_flags ||= '-d';
+	}
+}
+
+sub verify_remotes_sanity {
+	return unless -d $ENV{GIT_DIR};
+	my %seen;
+	foreach (command(qw/config -l/)) {
+		if (m!^svn-remote\.(?:.+)\.fetch=.*:refs/remotes/(\S+)\s*$!) {
+			if ($seen{$1}) {
+				die "Remote ref refs/remote/$1 is tracked by",
+				    "\n  \"$_\"\nand\n  \"$seen{$1}\"\n",
+				    "Please resolve this ambiguity in ",
+				    "your git configuration file before ",
+				    "continuing\n";
+			}
+			$seen{$1} = $_;
+		}
+	}
+}
+
+# we allow more chars than remotes2config.sh...
+sub sanitize_remote_name {
+	my ($name) = @_;
+	$name =~ tr{A-Za-z0-9:,/+-}{.}c;
+	$name;
+}
+
+sub find_existing_remote {
+	my ($url, $remotes) = @_;
+	return undef if $no_reuse_existing;
+	my $existing;
+	foreach my $repo_id (keys %$remotes) {
+		my $u = $remotes->{$repo_id}->{url} or next;
+		next if $u ne $url;
+		$existing = $repo_id;
+		last;
+	}
+	$existing;
+}
+
+sub init_remote_config {
+	my ($self, $url, $no_write) = @_;
+	$url =~ s!/+$!!; # strip trailing slash
+	my $r = read_all_remotes();
+	my $existing = find_existing_remote($url, $r);
+	if ($existing) {
+		unless ($no_write) {
+			print STDERR "Using existing ",
+				     "[svn-remote \"$existing\"]\n";
+		}
+		$self->{repo_id} = $existing;
+	} else {
+		my $min_url = Git::SVN::Ra->new($url)->minimize_url;
+		$existing = find_existing_remote($min_url, $r);
+		if ($existing) {
+			unless ($no_write) {
+				print STDERR "Using existing ",
+					     "[svn-remote \"$existing\"]\n";
+			}
+			$self->{repo_id} = $existing;
+		}
+		if ($min_url ne $url) {
+			unless ($no_write) {
+				print STDERR "Using higher level of URL: ",
+					     "$url => $min_url\n";
+			}
+			my $old_path = $self->{path};
+			$self->{path} = $url;
+			$self->{path} =~ s!^\Q$min_url\E(/|$)!!;
+			if (length $old_path) {
+				$self->{path} .= "/$old_path";
+			}
+			$url = $min_url;
+		}
+	}
+	my $orig_url;
+	if (!$existing) {
+		# verify that we aren't overwriting anything:
+		$orig_url = eval {
+			command_oneline('config', '--get',
+					"svn-remote.$self->{repo_id}.url")
+		};
+		if ($orig_url && ($orig_url ne $url)) {
+			die "svn-remote.$self->{repo_id}.url already set: ",
+			    "$orig_url\nwanted to set to: $url\n";
+		}
+	}
+	my ($xrepo_id, $xpath) = find_ref($self->refname);
+	if (defined $xpath) {
+		die "svn-remote.$xrepo_id.fetch already set to track ",
+		    "$xpath:refs/remotes/", $self->refname, "\n";
+	}
+	unless ($no_write) {
+		command_noisy('config',
+			      "svn-remote.$self->{repo_id}.url", $url);
+		command_noisy('config', '--add',
+			      "svn-remote.$self->{repo_id}.fetch",
+			      "$self->{path}:".$self->refname);
+	}
+	$self->{url} = $url;
+}
+
+sub find_by_url { # repos_root and, path are optional
+	my ($class, $full_url, $repos_root, $path) = @_;
+	return undef unless defined $full_url;
+	my $remotes = read_all_remotes();
+	if (defined $full_url && defined $repos_root && !defined $path) {
+		$path = $full_url;
+		$path =~ s#^\Q$repos_root\E(?:/|$)##;
+	}
+	foreach my $repo_id (keys %$remotes) {
+		my $u = $remotes->{$repo_id}->{url} or next;
+		next if defined $repos_root && $repos_root ne $u;
+
+		my $fetch = $remotes->{$repo_id}->{fetch} || {};
+		foreach (qw/branches tags/) {
+			resolve_local_globs($u, $fetch,
+			                    $remotes->{$repo_id}->{$_});
+		}
+		my $p = $path;
+		unless (defined $p) {
+			$p = $full_url;
+			$p =~ s#^\Q$u\E(?:/|$)## or next;
+		}
+		foreach my $f (keys %$fetch) {
+			next if $f ne $p;
+			return Git::SVN->new($fetch->{$f}, $repo_id, $f);
+		}
+	}
+	undef;
+}
+
+sub init {
+	my ($class, $url, $path, $repo_id, $ref_id, $no_write) = @_;
+	my $self = _new($class, $repo_id, $ref_id, $path);
+	if (defined $url) {
+		$self->init_remote_config($url, $no_write);
+	}
+	$self;
+}
+
+sub find_ref {
+	my ($ref_id) = @_;
+	foreach (command(qw/config -l/)) {
+		next unless m!^svn-remote\.(.+)\.fetch=
+		              \s*(.*)\s*:\s*refs/remotes/(.+)\s*$!x;
+		my ($repo_id, $path, $ref) = ($1, $2, $3);
+		if ($ref eq $ref_id) {
+			$path = '' if ($path =~ m#^\./?#);
+			return ($repo_id, $path);
+		}
+	}
+	(undef, undef, undef);
+}
+
+sub new {
+	my ($class, $ref_id, $repo_id, $path) = @_;
+	if (defined $ref_id && !defined $repo_id && !defined $path) {
+		($repo_id, $path) = find_ref($ref_id);
+		if (!defined $repo_id) {
+			die "Could not find a \"svn-remote.*.fetch\" key ",
+			    "in the repository configuration matching: ",
+			    "refs/remotes/$ref_id\n";
+		}
+	}
+	my $self = _new($class, $repo_id, $ref_id, $path);
+	if (!defined $self->{path} || !length $self->{path}) {
+		my $fetch = command_oneline('config', '--get',
+		                            "svn-remote.$repo_id.fetch",
+		                            ":refs/remotes/$ref_id\$") or
+		     die "Failed to read \"svn-remote.$repo_id.fetch\" ",
+		         "\":refs/remotes/$ref_id\$\" in config\n";
+		($self->{path}, undef) = split(/\s*:\s*/, $fetch);
+	}
+	$self->{url} = command_oneline('config', '--get',
+	                               "svn-remote.$repo_id.url") or
+                  die "Failed to read \"svn-remote.$repo_id.url\" in config\n";
+	$self->rebuild;
+	$self;
+}
+
+sub refname { "refs/remotes/$_[0]->{ref_id}" }
+
+sub svm_uuid {
+	my ($self) = @_;
+	return $self->{svm}->{uuid} if $self->svm;
+	$self->ra;
+	unless ($self->{svm}) {
+		die "SVM UUID not cached, and reading remotely failed\n";
+	}
+	$self->{svm}->{uuid};
+}
+
+sub svm {
+	my ($self) = @_;
+	return $self->{svm} if $self->{svm};
+	my $svm;
+	# see if we have it in our config, first:
+	eval {
+		my $section = "svn-remote.$self->{repo_id}";
+		$svm = {
+		  source => tmp_config('--get', "$section.svm-source"),
+		  uuid => tmp_config('--get', "$section.svm-uuid"),
+		  replace => tmp_config('--get', "$section.svm-replace"),
+		}
+	};
+	if ($svm && $svm->{source} && $svm->{uuid} && $svm->{replace}) {
+		$self->{svm} = $svm;
+	}
+	$self->{svm};
+}
+
+sub _set_svm_vars {
+	my ($self, $ra) = @_;
+	return $ra if $self->svm;
+
+	my @err = ( "useSvmProps set, but failed to read SVM properties\n",
+		    "(svm:source, svm:uuid) ",
+		    "from the following URLs:\n" );
+	sub read_svm_props {
+		my ($self, $ra, $path, $r) = @_;
+		my $props = ($ra->get_dir($path, $r))[2];
+		my $src = $props->{'svm:source'};
+		my $uuid = $props->{'svm:uuid'};
+		return undef if (!$src || !$uuid);
+
+		chomp($src, $uuid);
+
+		$uuid =~ m{^[0-9a-f\-]{30,}$}
+		    or die "doesn't look right - svm:uuid is '$uuid'\n";
+
+		# the '!' is used to mark the repos_root!/relative/path
+		$src =~ s{/?!/?}{/};
+		$src =~ s{/+$}{}; # no trailing slashes please
+		# username is of no interest
+		$src =~ s{(^[a-z\+]*://)[^/@]*@}{$1};
+
+		my $replace = $ra->{url};
+		$replace .= "/$path" if length $path;
+
+		my $section = "svn-remote.$self->{repo_id}";
+		tmp_config("$section.svm-source", $src);
+		tmp_config("$section.svm-replace", $replace);
+		tmp_config("$section.svm-uuid", $uuid);
+		$self->{svm} = {
+			source => $src,
+			uuid => $uuid,
+			replace => $replace
+		};
+	}
+
+	my $r = $ra->get_latest_revnum;
+	my $path = $self->{path};
+	my %tried;
+	while (length $path) {
+		unless ($tried{"$self->{url}/$path"}) {
+			return $ra if $self->read_svm_props($ra, $path, $r);
+			$tried{"$self->{url}/$path"} = 1;
+		}
+		$path =~ s#/?[^/]+$##;
+	}
+	die "Path: '$path' should be ''\n" if $path ne '';
+	return $ra if $self->read_svm_props($ra, $path, $r);
+	$tried{"$self->{url}/$path"} = 1;
+
+	if ($ra->{repos_root} eq $self->{url}) {
+		die @err, (map { "  $_\n" } keys %tried), "\n";
+	}
+
+	# nope, make sure we're connected to the repository root:
+	my $ok;
+	my @tried_b;
+	$path = $ra->{svn_path};
+	$ra = Git::SVN::Ra->new($ra->{repos_root});
+	while (length $path) {
+		unless ($tried{"$ra->{url}/$path"}) {
+			$ok = $self->read_svm_props($ra, $path, $r);
+			last if $ok;
+			$tried{"$ra->{url}/$path"} = 1;
+		}
+		$path =~ s#/?[^/]+$##;
+	}
+	die "Path: '$path' should be ''\n" if $path ne '';
+	$ok ||= $self->read_svm_props($ra, $path, $r);
+	$tried{"$ra->{url}/$path"} = 1;
+	if (!$ok) {
+		die @err, (map { "  $_\n" } keys %tried), "\n";
+	}
+	Git::SVN::Ra->new($self->{url});
+}
+
+sub svnsync {
+	my ($self) = @_;
+	return $self->{svnsync} if $self->{svnsync};
+
+	if ($self->no_metadata) {
+		die "Can't have both 'noMetadata' and ",
+		    "'useSvnsyncProps' options set!\n";
+	}
+	if ($self->rewrite_root) {
+		die "Can't have both 'useSvnsyncProps' and 'rewriteRoot' ",
+		    "options set!\n";
+	}
+
+	my $svnsync;
+	# see if we have it in our config, first:
+	eval {
+		my $section = "svn-remote.$self->{repo_id}";
+		$svnsync = {
+		  url => tmp_config('--get', "$section.svnsync-url"),
+		  uuid => tmp_config('--get', "$section.svnsync-uuid"),
+		}
+	};
+	if ($svnsync && $svnsync->{url} && $svnsync->{uuid}) {
+		return $self->{svnsync} = $svnsync;
+	}
+
+	my $err = "useSvnsyncProps set, but failed to read " .
+	          "svnsync property: svn:sync-from-";
+	my $rp = $self->ra->rev_proplist(0);
+
+	my $url = $rp->{'svn:sync-from-url'} or die $err . "url\n";
+	$url =~ m{^[a-z\+]+://} or
+	           die "doesn't look right - svn:sync-from-url is '$url'\n";
+
+	my $uuid = $rp->{'svn:sync-from-uuid'} or die $err . "uuid\n";
+	$uuid =~ m{^[0-9a-f\-]{30,}$} or
+	           die "doesn't look right - svn:sync-from-uuid is '$uuid'\n";
+
+	my $section = "svn-remote.$self->{repo_id}";
+	tmp_config('--add', "$section.svnsync-uuid", $uuid);
+	tmp_config('--add', "$section.svnsync-url", $url);
+	return $self->{svnsync} = { url => $url, uuid => $uuid };
+}
+
+# this allows us to memoize our SVN::Ra UUID locally and avoid a
+# remote lookup (useful for 'git svn log').
+sub ra_uuid {
+	my ($self) = @_;
+	unless ($self->{ra_uuid}) {
+		my $key = "svn-remote.$self->{repo_id}.uuid";
+		my $uuid = eval { tmp_config('--get', $key) };
+		if (!$@ && $uuid && $uuid =~ /^([a-f\d\-]{30,})$/) {
+			$self->{ra_uuid} = $uuid;
+		} else {
+			die "ra_uuid called without URL\n" unless $self->{url};
+			$self->{ra_uuid} = $self->ra->get_uuid;
+			tmp_config('--add', $key, $self->{ra_uuid});
+		}
+	}
+	$self->{ra_uuid};
+}
+
+sub ra {
+	my ($self) = shift;
+	my $ra = Git::SVN::Ra->new($self->{url});
+	if ($self->use_svm_props && !$self->{svm}) {
+		if ($self->no_metadata) {
+			die "Can't have both 'noMetadata' and ",
+			    "'useSvmProps' options set!\n";
+		} elsif ($self->use_svnsync_props) {
+			die "Can't have both 'useSvnsyncProps' and ",
+			    "'useSvmProps' options set!\n";
+		}
+		$ra = $self->_set_svm_vars($ra);
+		$self->{-want_revprops} = 1;
+	}
+	$ra;
+}
+
+sub rel_path {
+	my ($self) = @_;
+	my $repos_root = $self->ra->{repos_root};
+	return $self->{path} if ($self->{url} eq $repos_root);
+	my $url = $self->{url} .
+	          (length $self->{path} ? "/$self->{path}" : $self->{path});
+	$url =~ s!^\Q$repos_root\E(?:/+|$)!!g;
+	$url;
+}
+
+sub traverse_ignore {
+	my ($self, $fh, $path, $r) = @_;
+	$path =~ s#^/+##g;
+	my $ra = $self->ra;
+	my ($dirent, undef, $props) = $ra->get_dir($path, $r);
+	my $p = $path;
+	$p =~ s#^\Q$self->{path}\E(/|$)##;
+	print $fh length $p ? "\n# $p\n" : "\n# /\n";
+	if (my $s = $props->{'svn:ignore'}) {
+		$s =~ s/[\r\n]+/\n/g;
+		chomp $s;
+		if (length $p == 0) {
+			$s =~ s#\n#\n/$p#g;
+			print $fh "/$s\n";
+		} else {
+			$s =~ s#\n#\n/$p/#g;
+			print $fh "/$p/$s\n";
+		}
+	}
+	foreach (sort keys %$dirent) {
+		next if $dirent->{$_}->kind != $SVN::Node::dir;
+		$self->traverse_ignore($fh, "$path/$_", $r);
+	}
+}
+
+sub last_rev { ($_[0]->last_rev_commit)[0] }
+sub last_commit { ($_[0]->last_rev_commit)[1] }
+
+# returns the newest SVN revision number and newest commit SHA1
+sub last_rev_commit {
+	my ($self) = @_;
+	if (defined $self->{last_rev} && defined $self->{last_commit}) {
+		return ($self->{last_rev}, $self->{last_commit});
+	}
+	my $c = ::verify_ref($self->refname.'^0');
+	if ($c && !$self->use_svm_props && !$self->no_metadata) {
+		my $rev = (::cmt_metadata($c))[1];
+		if (defined $rev) {
+			($self->{last_rev}, $self->{last_commit}) = ($rev, $c);
+			return ($rev, $c);
+		}
+	}
+	my $db_path = $self->db_path;
+	unless (-e $db_path) {
+		($self->{last_rev}, $self->{last_commit}) = (undef, undef);
+		return (undef, undef);
+	}
+	my $offset = -41; # from tail
+	my $rl;
+	open my $fh, '<', $db_path or croak "$db_path not readable: $!\n";
+	sysseek($fh, $offset, 2); # don't care for errors
+	sysread($fh, $rl, 41) == 41 or return (undef, undef);
+	chomp $rl;
+	while (('0' x40) eq $rl && sysseek($fh, 0, 1) != 0) {
+		$offset -= 41;
+		sysseek($fh, $offset, 2); # don't care for errors
+		sysread($fh, $rl, 41) == 41 or return (undef, undef);
+		chomp $rl;
+	}
+	if ($c && $c ne $rl) {
+		die "$db_path and ", $self->refname,
+		    " inconsistent!:\n$c != $rl\n";
+	}
+	my $rev = sysseek($fh, 0, 1) or croak $!;
+	$rev =  ($rev - 41) / 41;
+	close $fh or croak $!;
+	($self->{last_rev}, $self->{last_commit}) = ($rev, $c);
+	return ($rev, $c);
+}
+
+sub get_fetch_range {
+	my ($self, $min, $max) = @_;
+	$max ||= $self->ra->get_latest_revnum;
+	$min ||= $self->rev_db_max;
+	(++$min, $max);
+}
+
+sub tmp_config {
+	my (@args) = @_;
+	my $old_def_config = "$ENV{GIT_DIR}/svn/config";
+	my $config = "$ENV{GIT_DIR}/svn/.metadata";
+	if (-e $old_def_config && ! -e $config) {
+		rename $old_def_config, $config or
+		       die "Failed rename $old_def_config => $config: $!\n";
+	}
+	my $old_config = $ENV{GIT_CONFIG};
+	$ENV{GIT_CONFIG} = $config;
+	$@ = undef;
+	my @ret = eval {
+		unless (-f $config) {
+			mkfile($config);
+			open my $fh, '>', $config or
+			    die "Can't open $config: $!\n";
+			print $fh "; This file is used internally by ",
+			          "git-svn\n" or die
+				  "Couldn't write to $config: $!\n";
+			print $fh "; You should not have to edit it\n" or
+			      die "Couldn't write to $config: $!\n";
+			close $fh or die "Couldn't close $config: $!\n";
+		}
+		command('config', @args);
+	};
+	my $err = $@;
+	if (defined $old_config) {
+		$ENV{GIT_CONFIG} = $old_config;
+	} else {
+		delete $ENV{GIT_CONFIG};
+	}
+	die $err if $err;
+	wantarray ? @ret : $ret[0];
+}
+
+sub tmp_index_do {
+	my ($self, $sub) = @_;
+	my $old_index = $ENV{GIT_INDEX_FILE};
+	$ENV{GIT_INDEX_FILE} = $self->{index};
+	$@ = undef;
+	my @ret = eval {
+		my ($dir, $base) = ($self->{index} =~ m#^(.*?)/?([^/]+)$#);
+		mkpath([$dir]) unless -d $dir;
+		&$sub;
+	};
+	my $err = $@;
+	if (defined $old_index) {
+		$ENV{GIT_INDEX_FILE} = $old_index;
+	} else {
+		delete $ENV{GIT_INDEX_FILE};
+	}
+	die $err if $err;
+	wantarray ? @ret : $ret[0];
+}
+
+sub assert_index_clean {
+	my ($self, $treeish) = @_;
+
+	$self->tmp_index_do(sub {
+		command_noisy('read-tree', $treeish) unless -e $self->{index};
+		my $x = command_oneline('write-tree');
+		my ($y) = (command(qw/cat-file commit/, $treeish) =~
+		           /^tree ($::sha1)/mo);
+		return if $y eq $x;
+
+		warn "Index mismatch: $y != $x\nrereading $treeish\n";
+		unlink $self->{index} or die "unlink $self->{index}: $!\n";
+		command_noisy('read-tree', $treeish);
+		$x = command_oneline('write-tree');
+		if ($y ne $x) {
+			::fatal "trees ($treeish) $y != $x\n",
+			        "Something is seriously wrong...\n";
+		}
+	});
+}
+
+sub get_commit_parents {
+	my ($self, $log_entry) = @_;
+	my (%seen, @ret, @tmp);
+	# legacy support for 'set-tree'; this is only used by set_tree_cb:
+	if (my $ip = $self->{inject_parents}) {
+		if (my $commit = delete $ip->{$log_entry->{revision}}) {
+			push @tmp, $commit;
+		}
+	}
+	if (my $cur = ::verify_ref($self->refname.'^0')) {
+		push @tmp, $cur;
+	}
+	push @tmp, $_ foreach (@{$log_entry->{parents}}, @tmp);
+	while (my $p = shift @tmp) {
+		next if $seen{$p};
+		$seen{$p} = 1;
+		push @ret, $p;
+		# MAXPARENT is defined to 16 in commit-tree.c:
+		last if @ret >= 16;
+	}
+	if (@tmp) {
+		die "r$log_entry->{revision}: No room for parents:\n\t",
+		    join("\n\t", @tmp), "\n";
+	}
+	@ret;
+}
+
+sub rewrite_root {
+	my ($self) = @_;
+	return $self->{-rewrite_root} if exists $self->{-rewrite_root};
+	my $k = "svn-remote.$self->{repo_id}.rewriteRoot";
+	my $rwr = eval { command_oneline(qw/config --get/, $k) };
+	if ($rwr) {
+		$rwr =~ s#/+$##;
+		if ($rwr !~ m#^[a-z\+]+://#) {
+			die "$rwr is not a valid URL (key: $k)\n";
+		}
+	}
+	$self->{-rewrite_root} = $rwr;
+}
+
+sub metadata_url {
+	my ($self) = @_;
+	($self->rewrite_root || $self->{url}) .
+	   (length $self->{path} ? '/' . $self->{path} : '');
+}
+
+sub full_url {
+	my ($self) = @_;
+	$self->{url} . (length $self->{path} ? '/' . $self->{path} : '');
+}
+
+sub do_git_commit {
+	my ($self, $log_entry) = @_;
+	my $lr = $self->last_rev;
+	if (defined $lr && $lr >= $log_entry->{revision}) {
+		die "Last fetched revision of ", $self->refname,
+		    " was r$lr, but we are about to fetch: ",
+		    "r$log_entry->{revision}!\n";
+	}
+	if (my $c = $self->rev_db_get($log_entry->{revision})) {
+		croak "$log_entry->{revision} = $c already exists! ",
+		      "Why are we refetching it?\n";
+	}
+	$ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $log_entry->{name};
+	$ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} =
+	                                                  $log_entry->{email};
+	$ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_entry->{date};
+
+	my $tree = $log_entry->{tree};
+	if (!defined $tree) {
+		$tree = $self->tmp_index_do(sub {
+		                            command_oneline('write-tree') });
+	}
+	die "Tree is not a valid sha1: $tree\n" if $tree !~ /^$::sha1$/o;
+
+	my @exec = ('git-commit-tree', $tree);
+	foreach ($self->get_commit_parents($log_entry)) {
+		push @exec, '-p', $_;
+	}
+	defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec))
+	                                                           or croak $!;
+	print $msg_fh $log_entry->{log} or croak $!;
+	unless ($self->no_metadata) {
+		print $msg_fh "\ngit-svn-id: $log_entry->{metadata}\n"
+		              or croak $!;
+	}
+	$msg_fh->flush == 0 or croak $!;
+	close $msg_fh or croak $!;
+	chomp(my $commit = do { local $/; <$out_fh> });
+	close $out_fh or croak $!;
+	waitpid $pid, 0;
+	croak $? if $?;
+	if ($commit !~ /^$::sha1$/o) {
+		die "Failed to commit, invalid sha1: $commit\n";
+	}
+
+	$self->rev_db_set($log_entry->{revision}, $commit, 1);
+
+	$self->{last_rev} = $log_entry->{revision};
+	$self->{last_commit} = $commit;
+	print "r$log_entry->{revision}";
+	if (defined $log_entry->{svm_revision}) {
+		 print " (\@$log_entry->{svm_revision})";
+		 $self->rev_db_set($log_entry->{svm_revision}, $commit,
+		                   0, $self->svm_uuid);
+	}
+	print " = $commit ($self->{ref_id})\n";
+	if (defined $_repack && (--$_repack_nr == 0)) {
+		$_repack_nr = $_repack;
+		# repack doesn't use any arguments with spaces in them, does it?
+		print "Running git repack $_repack_flags ...\n";
+		command_noisy('repack', split(/\s+/, $_repack_flags));
+		print "Done repacking\n";
+	}
+	return $commit;
+}
+
+sub match_paths {
+	my ($self, $paths, $r) = @_;
+	return 1 if $self->{path} eq '';
+	if (my $path = $paths->{"/$self->{path}"}) {
+		return ($path->{action} eq 'D') ? 0 : 1;
+	}
+	$self->{path_regex} ||= qr/^\/\Q$self->{path}\E\//;
+	if (grep /$self->{path_regex}/, keys %$paths) {
+		return 1;
+	}
+	my $c = '';
+	foreach (split m#/#, $self->{path}) {
+		$c .= "/$_";
+		next unless ($paths->{$c} &&
+		             ($paths->{$c}->{action} =~ /^[AR]$/));
+		if ($self->ra->check_path($self->{path}, $r) ==
+		    $SVN::Node::dir) {
 			return 1;
 		}
-		if ($r_min != $r_max) {
-			return 1 if ($r_min < $c->{r});
-			return 1 if ($r_max > $c->{r});
-		}
 	}
-	return 0 if (defined $_limit && --$_limit < 0);
-	show_commit($c);
-	return 1;
+	return 0;
 }
 
-sub show_commit {
-	my $c = shift;
-	if ($_oneline) {
-		my $x = "\n";
-		if (my $l = $c->{l}) {
-			while ($l->[0] =~ /^\s*$/) { shift @$l }
-			$x = $l->[0];
-		}
-		$_l_fmt ||= 'A' . length($c->{r});
-		print 'r',pack($_l_fmt, $c->{r}),' | ';
-		print "$c->{c} | " if $_show_commit;
-		print $x;
-	} else {
-		show_commit_normal($c);
+sub find_parent_branch {
+	my ($self, $paths, $rev) = @_;
+	return undef unless $self->follow_parent;
+	unless (defined $paths) {
+		my $err_handler = $SVN::Error::handler;
+		$SVN::Error::handler = \&Git::SVN::Ra::skip_unknown_revs;
+		$self->ra->get_log([$self->{path}], $rev, $rev, 0, 1, 1, sub {
+		                   $paths =
+				      Git::SVN::Ra::dup_changed_paths($_[0]) });
+		$SVN::Error::handler = $err_handler;
 	}
-}
+	return undef unless defined $paths;
 
-sub show_commit_changed_paths {
-	my ($c) = @_;
-	return unless $c->{changed};
-	print "Changed paths:\n", @{$c->{changed}};
-}
-
-sub show_commit_normal {
-	my ($c) = @_;
-	print '-' x72, "\nr$c->{r} | ";
-	print "$c->{c} | " if $_show_commit;
-	print "$c->{a} | ", strftime("%Y-%m-%d %H:%M:%S %z (%a, %d %b %Y)",
-				 localtime($c->{t_utc})), ' | ';
-	my $nr_line = 0;
-
-	if (my $l = $c->{l}) {
-		while ($l->[$#$l] eq "\n" && $#$l > 0
-		                          && $l->[($#$l - 1)] eq "\n") {
-			pop @$l;
-		}
-		$nr_line = scalar @$l;
-		if (!$nr_line) {
-			print "1 line\n\n\n";
+	# look for a parent from another branch:
+	my @b_path_components = split m#/#, $self->rel_path;
+	my @a_path_components;
+	my $i;
+	while (@b_path_components) {
+		$i = $paths->{'/'.join('/', @b_path_components)};
+		last if $i && defined $i->{copyfrom_path};
+		unshift(@a_path_components, pop(@b_path_components));
+	}
+	return undef unless defined $i && defined $i->{copyfrom_path};
+	my $branch_from = $i->{copyfrom_path};
+	if (@a_path_components) {
+		print STDERR "branch_from: $branch_from => ";
+		$branch_from .= '/'.join('/', @a_path_components);
+		print STDERR $branch_from, "\n";
+	}
+	my $r = $i->{copyfrom_rev};
+	my $repos_root = $self->ra->{repos_root};
+	my $url = $self->ra->{url};
+	my $new_url = $repos_root . $branch_from;
+	print STDERR  "Found possible branch point: ",
+	              "$new_url => ", $self->full_url, ", $r\n";
+	$branch_from =~ s#^/##;
+	my $gs = Git::SVN->find_by_url($new_url, $repos_root, $branch_from);
+	unless ($gs) {
+		my $ref_id = $self->{ref_id};
+		$ref_id =~ s/\@\d+$//;
+		$ref_id .= "\@$r";
+		# just grow a tail if we're not unique enough :x
+		$ref_id .= '-' while find_ref($ref_id);
+		print STDERR "Initializing parent: $ref_id\n";
+		$gs = Git::SVN->init($new_url, '', $ref_id, $ref_id, 1);
+	}
+	my ($r0, $parent) = $gs->find_rev_before($r, 1);
+	if (!defined $r0 || !defined $parent) {
+		$gs->fetch(0, $r);
+		($r0, $parent) = $gs->last_rev_commit;
+	}
+	if (defined $r0 && defined $parent) {
+		print STDERR "Found branch parent: ($self->{ref_id}) $parent\n";
+		my $ed;
+		if ($self->ra->can_do_switch) {
+			$self->assert_index_clean($parent);
+			print STDERR "Following parent with do_switch\n";
+			# do_switch works with svn/trunk >= r22312, but that
+			# is not included with SVN 1.4.3 (the latest version
+			# at the moment), so we can't rely on it
+			$self->{last_commit} = $parent;
+			$ed = SVN::Git::Fetcher->new($self);
+			$gs->ra->gs_do_switch($r0, $rev, $gs,
+					      $self->full_url, $ed)
+			  or die "SVN connection failed somewhere...\n";
 		} else {
-			if ($nr_line == 1) {
-				$nr_line = '1 line';
-			} else {
-				$nr_line .= ' lines';
+			print STDERR "Following parent with do_update\n";
+			$ed = SVN::Git::Fetcher->new($self);
+			$self->ra->gs_do_update($rev, $rev, $self, $ed)
+			  or die "SVN connection failed somewhere...\n";
+		}
+		print STDERR "Successfully followed parent\n";
+		return $self->make_log_entry($rev, [$parent], $ed);
+	}
+	return undef;
+}
+
+sub do_fetch {
+	my ($self, $paths, $rev) = @_;
+	my $ed;
+	my ($last_rev, @parents);
+	if (my $lc = $self->last_commit) {
+		# we can have a branch that was deleted, then re-added
+		# under the same name but copied from another path, in
+		# which case we'll have multiple parents (we don't
+		# want to break the original ref, nor lose copypath info):
+		if (my $log_entry = $self->find_parent_branch($paths, $rev)) {
+			push @{$log_entry->{parents}}, $lc;
+			return $log_entry;
+		}
+		$ed = SVN::Git::Fetcher->new($self);
+		$last_rev = $self->{last_rev};
+		$ed->{c} = $lc;
+		@parents = ($lc);
+	} else {
+		$last_rev = $rev;
+		if (my $log_entry = $self->find_parent_branch($paths, $rev)) {
+			return $log_entry;
+		}
+		$ed = SVN::Git::Fetcher->new($self);
+	}
+	unless ($self->ra->gs_do_update($last_rev, $rev, $self, $ed)) {
+		die "SVN connection failed somewhere...\n";
+	}
+	$self->make_log_entry($rev, \@parents, $ed);
+}
+
+sub get_untracked {
+	my ($self, $ed) = @_;
+	my @out;
+	my $h = $ed->{empty};
+	foreach (sort keys %$h) {
+		my $act = $h->{$_} ? '+empty_dir' : '-empty_dir';
+		push @out, "  $act: " . uri_encode($_);
+		warn "W: $act: $_\n";
+	}
+	foreach my $t (qw/dir_prop file_prop/) {
+		$h = $ed->{$t} or next;
+		foreach my $path (sort keys %$h) {
+			my $ppath = $path eq '' ? '.' : $path;
+			foreach my $prop (sort keys %{$h->{$path}}) {
+				next if $SKIP_PROP{$prop};
+				my $v = $h->{$path}->{$prop};
+				my $t_ppath_prop = "$t: " .
+				                    uri_encode($ppath) . ' ' .
+				                    uri_encode($prop);
+				if (defined $v) {
+					push @out, "  +$t_ppath_prop " .
+					           uri_encode($v);
+				} else {
+					push @out, "  -$t_ppath_prop";
+				}
 			}
-			print $nr_line, "\n";
-			show_commit_changed_paths($c);
-			print "\n";
-			print $_ foreach @$l;
+		}
+	}
+	foreach my $t (qw/absent_file absent_directory/) {
+		$h = $ed->{$t} or next;
+		foreach my $parent (sort keys %$h) {
+			foreach my $path (sort @{$h->{$parent}}) {
+				push @out, "  $t: " .
+				           uri_encode("$parent/$path");
+				warn "W: $t: $parent/$path ",
+				     "Insufficient permissions?\n";
+			}
+		}
+	}
+	\@out;
+}
+
+sub parse_svn_date {
+	my $date = shift || return '+0000 1970-01-01 00:00:00';
+	my ($Y,$m,$d,$H,$M,$S) = ($date =~ /^(\d{4})\-(\d\d)\-(\d\d)T
+	                                    (\d\d)\:(\d\d)\:(\d\d).\d+Z$/x) or
+	                                 croak "Unable to parse date: $date\n";
+	"+0000 $Y-$m-$d $H:$M:$S";
+}
+
+sub check_author {
+	my ($author) = @_;
+	if (!defined $author || length $author == 0) {
+		$author = '(no author)';
+	}
+	if (defined $::_authors && ! defined $::users{$author}) {
+		die "Author: $author not defined in $::_authors file\n";
+	}
+	$author;
+}
+
+sub make_log_entry {
+	my ($self, $rev, $parents, $ed) = @_;
+	my $untracked = $self->get_untracked($ed);
+
+	open my $un, '>>', "$self->{dir}/unhandled.log" or croak $!;
+	print $un "r$rev\n" or croak $!;
+	print $un $_, "\n" foreach @$untracked;
+	my %log_entry = ( parents => $parents || [], revision => $rev,
+	                  log => '');
+
+	my $headrev;
+	my $logged = delete $self->{logged_rev_props};
+	if (!$logged || $self->{-want_revprops}) {
+		my $rp = $self->ra->rev_proplist($rev);
+		foreach (sort keys %$rp) {
+			my $v = $rp->{$_};
+			if (/^svn:(author|date|log)$/) {
+				$log_entry{$1} = $v;
+			} elsif ($_ eq 'svm:headrev') {
+				$headrev = $v;
+			} else {
+				print $un "  rev_prop: ", uri_encode($_), ' ',
+					  uri_encode($v), "\n";
+			}
 		}
 	} else {
-		print "1 line\n";
-		show_commit_changed_paths($c);
-		print "\n";
-
+		map { $log_entry{$_} = $logged->{$_} } keys %$logged;
 	}
-	foreach my $x (qw/raw diff/) {
-		if ($c->{$x}) {
-			print "\n";
-			print $_ foreach @{$c->{$x}}
+	close $un or croak $!;
+
+	$log_entry{date} = parse_svn_date($log_entry{date});
+	$log_entry{log} .= "\n";
+	my $author = $log_entry{author} = check_author($log_entry{author});
+	my ($name, $email) = defined $::users{$author} ? @{$::users{$author}}
+	                                               : ($author, undef);
+	if (defined $headrev && $self->use_svm_props) {
+		if ($self->rewrite_root) {
+			die "Can't have both 'useSvmProps' and 'rewriteRoot' ",
+			    "options set!\n";
 		}
+		my ($uuid, $r) = $headrev =~ m{^([a-f\d\-]{30,}):(\d+)$};
+		# we don't want "SVM: initializing mirror for junk" ...
+		return undef if $r == 0;
+		my $svm = $self->svm;
+		if ($uuid ne $svm->{uuid}) {
+			die "UUID mismatch on SVM path:\n",
+			    "expected: $svm->{uuid}\n",
+			    "     got: $uuid\n";
+		}
+		my $full_url = $self->full_url;
+		$full_url =~ s#^\Q$svm->{replace}\E(/|$)#$svm->{source}$1# or
+		             die "Failed to replace '$svm->{replace}' with ",
+		                 "'$svm->{source}' in $full_url\n";
+		# throw away username for storing in records
+		remove_username($full_url);
+		$log_entry{metadata} = "$full_url\@$r $uuid";
+		$log_entry{svm_revision} = $r;
+		$email ||= "$author\@$uuid"
+	} elsif ($self->use_svnsync_props) {
+		my $full_url = $self->svnsync->{url};
+		$full_url .= "/$self->{path}" if length $self->{path};
+		my $uuid = $self->svnsync->{uuid};
+		$log_entry{metadata} = "$full_url\@$rev $uuid";
+		$email ||= "$author\@$uuid"
+	} else {
+		$log_entry{metadata} = $self->metadata_url. "\@$rev " .
+		                       $self->ra->get_uuid;
+		$email ||= "$author\@" . $self->ra->get_uuid;
+	}
+	$log_entry{name} = $name;
+	$log_entry{email} = $email;
+	\%log_entry;
+}
+
+sub fetch {
+	my ($self, $min_rev, $max_rev, @parents) = @_;
+	my ($last_rev, $last_commit) = $self->last_rev_commit;
+	my ($base, $head) = $self->get_fetch_range($min_rev, $max_rev);
+	$self->ra->gs_fetch_loop_common($base, $head, [$self]);
+}
+
+sub set_tree_cb {
+	my ($self, $log_entry, $tree, $rev, $date, $author) = @_;
+	$self->{inject_parents} = { $rev => $tree };
+	$self->fetch(undef, undef);
+}
+
+sub set_tree {
+	my ($self, $tree) = (shift, shift);
+	my $log_entry = ::get_commit_entry($tree);
+	unless ($self->{last_rev}) {
+		fatal("Must have an existing revision to commit\n");
+	}
+	my %ed_opts = ( r => $self->{last_rev},
+	                log => $log_entry->{log},
+	                ra => $self->ra,
+	                tree_a => $self->{last_commit},
+	                tree_b => $tree,
+	                editor_cb => sub {
+			       $self->set_tree_cb($log_entry, $tree, @_) },
+	                svn_path => $self->{path} );
+	if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) {
+		print "No changes\nr$self->{last_rev} = $tree\n";
 	}
 }
 
-sub _simple_prompt {
+sub rebuild {
+	my ($self) = @_;
+	my $db_path = $self->db_path;
+	return if (-e $db_path && ! -z $db_path);
+	return unless ::verify_ref($self->refname.'^0');
+	if (-f $self->{db_root}) {
+		rename $self->{db_root}, $db_path or die
+		     "rename $self->{db_root} => $db_path failed: $!\n";
+		my ($dir, $base) = ($db_path =~ m#^(.*?)/?([^/]+)$#);
+		symlink $base, $self->{db_root} or die
+		     "symlink $base => $self->{db_root} failed: $!\n";
+		return;
+	}
+	print "Rebuilding $db_path ...\n";
+	my ($rev_list, $ctx) = command_output_pipe("rev-list", $self->refname);
+	my $latest;
+	my $full_url = $self->full_url;
+	remove_username($full_url);
+	my $svn_uuid;
+	while (<$rev_list>) {
+		chomp;
+		my $c = $_;
+		die "Non-SHA1: $c\n" unless $c =~ /^$::sha1$/o;
+		my ($url, $rev, $uuid) = ::cmt_metadata($c);
+		remove_username($url);
+
+		# ignore merges (from set-tree)
+		next if (!defined $rev || !$uuid);
+
+		# if we merged or otherwise started elsewhere, this is
+		# how we break out of it
+		if ((defined $svn_uuid && ($uuid ne $svn_uuid)) ||
+		    ($full_url && $url && ($url ne $full_url))) {
+			next;
+		}
+		$latest ||= $rev;
+		$svn_uuid ||= $uuid;
+
+		$self->rev_db_set($rev, $c);
+		print "r$rev = $c\n";
+	}
+	command_close_pipe($rev_list, $ctx);
+	print "Done rebuilding $db_path\n";
+}
+
+# rev_db:
+# Tie::File seems to be prone to offset errors if revisions get sparse,
+# it's not that fast, either.  Tie::File is also not in Perl 5.6.  So
+# one of my favorite modules is out :<  Next up would be one of the DBM
+# modules, but I'm not sure which is most portable...  So I'll just
+# go with something that's plain-text, but still capable of
+# being randomly accessed.  So here's my ultra-simple fixed-width
+# database.  All records are 40 characters + "\n", so it's easy to seek
+# to a revision: (41 * rev) is the byte offset.
+# A record of 40 0s denotes an empty revision.
+# And yes, it's still pretty fast (faster than Tie::File).
+# These files are disposable unless noMetadata or useSvmProps is set
+
+sub _rev_db_set {
+	my ($fh, $rev, $commit) = @_;
+	my $offset = $rev * 41;
+	# assume that append is the common case:
+	seek $fh, 0, 2 or croak $!;
+	my $pos = tell $fh;
+	if ($pos < $offset) {
+		for (1 .. (($offset - $pos) / 41)) {
+			print $fh (('0' x 40),"\n") or croak $!;
+		}
+	}
+	seek $fh, $offset, 0 or croak $!;
+	print $fh $commit,"\n" or croak $!;
+}
+
+sub mkfile {
+	my ($path) = @_;
+	unless (-e $path) {
+		my ($dir, $base) = ($path =~ m#^(.*?)/?([^/]+)$#);
+		mkpath([$dir]) unless -d $dir;
+		open my $fh, '>>', $path or die "Couldn't create $path: $!\n";
+		close $fh or die "Couldn't close (create) $path: $!\n";
+	}
+}
+
+sub rev_db_set {
+	my ($self, $rev, $commit, $update_ref, $uuid) = @_;
+	length $commit == 40 or die "arg3 must be a full SHA1 hexsum\n";
+	my $db = $self->db_path($uuid);
+	my $db_lock = "$db.lock";
+	my $sig;
+	if ($update_ref) {
+		$SIG{INT} = $SIG{HUP} = $SIG{TERM} = $SIG{ALRM} = $SIG{PIPE} =
+		            $SIG{USR1} = $SIG{USR2} = sub { $sig = $_[0] };
+	}
+	mkfile($db);
+
+	$LOCKFILES{$db_lock} = 1;
+	my $sync;
+	# both of these options make our .rev_db file very, very important
+	# and we can't afford to lose it because rebuild() won't work
+	if ($self->use_svm_props || $self->no_metadata) {
+		$sync = 1;
+		copy($db, $db_lock) or die "rev_db_set(@_): ",
+					   "Failed to copy: ",
+					   "$db => $db_lock ($!)\n";
+	} else {
+		rename $db, $db_lock or die "rev_db_set(@_): ",
+					    "Failed to rename: ",
+					    "$db => $db_lock ($!)\n";
+	}
+	open my $fh, '+<', $db_lock or die "Couldn't open $db_lock: $!\n";
+	_rev_db_set($fh, $rev, $commit);
+	if ($sync) {
+		$fh->flush or die "Couldn't flush $db_lock: $!\n";
+		$fh->sync or die "Couldn't sync $db_lock: $!\n";
+	}
+	close $fh or croak $!;
+	if ($update_ref) {
+		$_head = $self;
+		command_noisy('update-ref', '-m', "r$rev",
+		              $self->refname, $commit);
+	}
+	rename $db_lock, $db or die "rev_db_set(@_): ", "Failed to rename: ",
+	                            "$db_lock => $db ($!)\n";
+	delete $LOCKFILES{$db_lock};
+	if ($update_ref) {
+		$SIG{INT} = $SIG{HUP} = $SIG{TERM} = $SIG{ALRM} = $SIG{PIPE} =
+		            $SIG{USR1} = $SIG{USR2} = 'DEFAULT';
+		kill $sig, $$ if defined $sig;
+	}
+}
+
+sub rev_db_max {
+	my ($self) = @_;
+	$self->rebuild;
+	my $db_path = $self->db_path;
+	my @stat = stat $db_path or return 0;
+	($stat[7] % 41) == 0 or die "$db_path inconsistent size: $stat[7]\n";
+	my $max = $stat[7] / 41;
+	(($max > 0) ? $max - 1 : 0);
+}
+
+sub rev_db_get {
+	my ($self, $rev, $uuid) = @_;
+	my $ret;
+	my $offset = $rev * 41;
+	my $db_path = $self->db_path($uuid);
+	return undef unless -e $db_path;
+	open my $fh, '<', $db_path or croak $!;
+	if (sysseek($fh, $offset, 0) == $offset) {
+		my $read = sysread($fh, $ret, 40);
+		$ret = undef if ($read != 40 || $ret eq ('0'x40));
+	}
+	close $fh or croak $!;
+	$ret;
+}
+
+sub find_rev_before {
+	my ($self, $rev, $eq_ok) = @_;
+	--$rev unless $eq_ok;
+	while ($rev > 0) {
+		if (my $c = $self->rev_db_get($rev)) {
+			return ($rev, $c);
+		}
+		--$rev;
+	}
+	return (undef, undef);
+}
+
+sub _new {
+	my ($class, $repo_id, $ref_id, $path) = @_;
+	unless (defined $repo_id && length $repo_id) {
+		$repo_id = $Git::SVN::default_repo_id;
+	}
+	unless (defined $ref_id && length $ref_id) {
+		$_[2] = $ref_id = $Git::SVN::default_ref_id;
+	}
+	$_[1] = $repo_id = sanitize_remote_name($repo_id);
+	my $dir = "$ENV{GIT_DIR}/svn/$ref_id";
+	$_[3] = $path = '' unless (defined $path);
+	mkpath(["$ENV{GIT_DIR}/svn"]);
+	bless {
+		ref_id => $ref_id, dir => $dir, index => "$dir/index",
+	        path => $path, config => "$ENV{GIT_DIR}/svn/config",
+	        db_root => "$dir/.rev_db", repo_id => $repo_id }, $class;
+}
+
+sub db_path {
+	my ($self, $uuid) = @_;
+	$uuid ||= $self->ra_uuid;
+	"$self->{db_root}.$uuid";
+}
+
+sub uri_encode {
+	my ($f) = @_;
+	$f =~ s#([^a-zA-Z0-9\*!\:_\./\-])#uc sprintf("%%%02x",ord($1))#eg;
+	$f
+}
+
+sub remove_username {
+	$_[0] =~ s{^([^:]*://)[^@]+@}{$1};
+}
+
+package Git::SVN::Prompt;
+use strict;
+use warnings;
+require SVN::Core;
+use vars qw/$_no_auth_cache $_username/;
+
+sub simple {
 	my ($cred, $realm, $default_username, $may_save, $pool) = @_;
 	$may_save = undef if $_no_auth_cache;
 	$default_username = $_username if defined $_username;
@@ -1923,7 +2123,7 @@
 		}
 		$cred->username($default_username);
 	} else {
-		_username_prompt($cred, $realm, $may_save, $pool);
+		username($cred, $realm, $may_save, $pool);
 	}
 	$cred->password(_read_password("Password for '" .
 	                               $cred->username . "': ", $realm));
@@ -1931,7 +2131,7 @@
 	$SVN::_Core::SVN_NO_ERROR;
 }
 
-sub _ssl_server_trust_prompt {
+sub ssl_server_trust {
 	my ($cred, $realm, $failures, $cert_info, $may_save, $pool) = @_;
 	$may_save = undef if $_no_auth_cache;
 	print STDERR "Error validating server certificate for '$realm':\n";
@@ -1980,7 +2180,7 @@
 	$SVN::_Core::SVN_NO_ERROR;
 }
 
-sub _ssl_client_cert_prompt {
+sub ssl_client_cert {
 	my ($cred, $realm, $may_save, $pool) = @_;
 	$may_save = undef if $_no_auth_cache;
 	print STDERR "Client certificate filename: ";
@@ -1991,7 +2191,7 @@
 	$SVN::_Core::SVN_NO_ERROR;
 }
 
-sub _ssl_client_cert_pw_prompt {
+sub ssl_client_cert_pw {
 	my ($cred, $realm, $may_save, $pool) = @_;
 	$may_save = undef if $_no_auth_cache;
 	$cred->password(_read_password("Password: ", $realm));
@@ -1999,7 +2199,7 @@
 	$SVN::_Core::SVN_NO_ERROR;
 }
 
-sub _username_prompt {
+sub username {
 	my ($cred, $realm, $may_save, $pool) = @_;
 	$may_save = undef if $_no_auth_cache;
 	if (defined $realm && length $realm) {
@@ -2035,546 +2235,7 @@
 	$password;
 }
 
-sub libsvn_connect {
-	my ($url) = @_;
-	SVN::_Core::svn_config_ensure($_config_dir, undef);
-	my ($baton, $callbacks) = SVN::Core::auth_open_helper([
-	    SVN::Client::get_simple_provider(),
-	    SVN::Client::get_ssl_server_trust_file_provider(),
-	    SVN::Client::get_simple_prompt_provider(
-	      \&_simple_prompt, 2),
-	    SVN::Client::get_ssl_client_cert_prompt_provider(
-	      \&_ssl_client_cert_prompt, 2),
-	    SVN::Client::get_ssl_client_cert_pw_prompt_provider(
-	      \&_ssl_client_cert_pw_prompt, 2),
-	    SVN::Client::get_username_provider(),
-	    SVN::Client::get_ssl_server_trust_prompt_provider(
-	      \&_ssl_server_trust_prompt),
-	    SVN::Client::get_username_prompt_provider(
-	      \&_username_prompt, 2),
-	  ]);
-	my $config = SVN::Core::config_get_config($_config_dir);
-	my $ra = SVN::Ra->new(url => $url, auth => $baton,
-	                      config => $config,
-	                      pool => SVN::Pool->new,
-	                      auth_provider_callbacks => $callbacks);
-	$ra->{svn_path} = $url;
-	$ra->{repos_root} = $ra->get_repos_root;
-	$ra->{svn_path} =~ s#^\Q$ra->{repos_root}\E/*##;
-	push @repo_path_split_cache, qr/^(\Q$ra->{repos_root}\E)/;
-	return $ra;
-}
-
-sub libsvn_can_do_switch {
-	unless (defined $_svn_can_do_switch) {
-		my $pool = SVN::Pool->new;
-		my $rep = eval {
-			$SVN->do_switch(1, '', 0, $SVN->{url},
-			                SVN::Delta::Editor->new, $pool);
-		};
-		if ($@) {
-			$_svn_can_do_switch = 0;
-		} else {
-			$rep->abort_report($pool);
-			$_svn_can_do_switch = 1;
-		}
-		$pool->clear;
-	}
-	$_svn_can_do_switch;
-}
-
-sub libsvn_dup_ra {
-	my ($ra) = @_;
-	SVN::Ra->new(map { $_ => $ra->{$_} } qw/config url
-	             auth auth_provider_callbacks repos_root svn_path/);
-}
-
-sub uri_encode {
-	my ($f) = @_;
-	$f =~ s#([^a-zA-Z0-9\*!\:_\./\-])#uc sprintf("%%%02x",ord($1))#eg;
-	$f
-}
-
-sub uri_decode {
-	my ($f) = @_;
-	$f =~ tr/+/ /;
-	$f =~ s/%([A-F0-9]{2})/chr hex($1)/ge;
-	$f
-}
-
-sub libsvn_log_entry {
-	my ($rev, $author, $date, $msg, $parents, $untracked) = @_;
-	my ($Y,$m,$d,$H,$M,$S) = ($date =~ /^(\d{4})\-(\d\d)\-(\d\d)T
-					 (\d\d)\:(\d\d)\:(\d\d).\d+Z$/x)
-				or die "Unable to parse date: $date\n";
-	if (defined $author && length $author > 0 &&
-	    defined $_authors && ! defined $users{$author}) {
-		die "Author: $author not defined in $_authors file\n";
-	}
-	$msg = '' if ($rev == 0 && !defined $msg);
-
-	open my $un, '>>', "$GIT_SVN_DIR/unhandled.log" or croak $!;
-	my $h;
-	print $un "r$rev\n" or croak $!;
-	$h = $untracked->{empty};
-	foreach (sort keys %$h) {
-		my $act = $h->{$_} ? '+empty_dir' : '-empty_dir';
-		print $un "  $act: ", uri_encode($_), "\n" or croak $!;
-		warn "W: $act: $_\n";
-	}
-	foreach my $t (qw/dir_prop file_prop/) {
-		$h = $untracked->{$t} or next;
-		foreach my $path (sort keys %$h) {
-			my $ppath = $path eq '' ? '.' : $path;
-			foreach my $prop (sort keys %{$h->{$path}}) {
-				next if $SKIP{$prop};
-				my $v = $h->{$path}->{$prop};
-				if (defined $v) {
-					print $un "  +$t: ",
-						  uri_encode($ppath), ' ',
-						  uri_encode($prop), ' ',
-						  uri_encode($v), "\n"
-						  or croak $!;
-				} else {
-					print $un "  -$t: ",
-						  uri_encode($ppath), ' ',
-						  uri_encode($prop), "\n"
-						  or croak $!;
-				}
-			}
-		}
-	}
-	foreach my $t (qw/absent_file absent_directory/) {
-		$h = $untracked->{$t} or next;
-		foreach my $parent (sort keys %$h) {
-			foreach my $path (sort @{$h->{$parent}}) {
-				print $un "  $t: ",
-				      uri_encode("$parent/$path"), "\n"
-				      or croak $!;
-				warn "W: $t: $parent/$path ",
-				     "Insufficient permissions?\n";
-			}
-		}
-	}
-
-	# revprops (make this optional? it's an extra network trip...)
-	my $pool = SVN::Pool->new;
-	my $rp = $SVN->rev_proplist($rev, $pool);
-	foreach (sort keys %$rp) {
-		next if /^svn:(?:author|date|log)$/;
-		print $un "  rev_prop: ", uri_encode($_), ' ',
-		          uri_encode($rp->{$_}), "\n";
-	}
-	$pool->clear;
-	close $un or croak $!;
-
-	{ revision => $rev, date => "+0000 $Y-$m-$d $H:$M:$S",
-	  author => $author, msg => $msg."\n", parents => $parents || [],
-	  revprops => $rp }
-}
-
-sub process_rm {
-	my ($gui, $last_commit, $f, $q) = @_;
-	# remove entire directories.
-	if (command('ls-tree',$last_commit,'--',$f) =~ /^040000 tree/) {
-		my ($ls, $ctx) = command_output_pipe(qw/ls-tree
-		                                     -r --name-only -z/,
-				                     $last_commit,'--',$f);
-		local $/ = "\0";
-		while (<$ls>) {
-			print $gui '0 ',0 x 40,"\t",$_ or croak $!;
-			print "\tD\t$_\n" unless $q;
-		}
-		print "\tD\t$f/\n" unless $q;
-		command_close_pipe($ls, $ctx);
-		return $SVN::Node::dir;
-	} else {
-		print $gui '0 ',0 x 40,"\t",$f,"\0" or croak $!;
-		print "\tD\t$f\n" unless $q;
-		return $SVN::Node::file;
-	}
-}
-
-sub libsvn_fetch {
-	my ($last_commit, $paths, $rev, $author, $date, $msg) = @_;
-	my $pool = SVN::Pool->new;
-	my $ed = SVN::Git::Fetcher->new({ c => $last_commit, q => $_q });
-	my $reporter = $SVN->do_update($rev, '', 1, $ed, $pool);
-	my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : ();
-	my (undef, $last_rev, undef) = cmt_metadata($last_commit);
-	$reporter->set_path('', $last_rev, 0, @lock, $pool);
-	$reporter->finish_report($pool);
-	$pool->clear;
-	unless ($ed->{git_commit_ok}) {
-		die "SVN connection failed somewhere...\n";
-	}
-	libsvn_log_entry($rev, $author, $date, $msg, [$last_commit], $ed);
-}
-
-sub svn_grab_base_rev {
-	my $c = eval { command_oneline([qw/rev-parse --verify/,
-	                                "refs/remotes/$GIT_SVN^0"],
-				        { STDERR => 0 }) };
-	if (defined $c && length $c) {
-		my ($url, $rev, $uuid) = cmt_metadata($c);
-		return ($rev, $c) if defined $rev;
-	}
-	if ($_no_metadata) {
-		my $offset = -41; # from tail
-		my $rl;
-		open my $fh, '<', $REVDB or
-			die "--no-metadata specified and $REVDB not readable\n";
-		seek $fh, $offset, 2;
-		$rl = readline $fh;
-		defined $rl or return (undef, undef);
-		chomp $rl;
-		while ($c ne $rl && tell $fh != 0) {
-			$offset -= 41;
-			seek $fh, $offset, 2;
-			$rl = readline $fh;
-			defined $rl or return (undef, undef);
-			chomp $rl;
-		}
-		my $rev = tell $fh;
-		croak $! if ($rev < -1);
-		$rev =  ($rev - 41) / 41;
-		close $fh or croak $!;
-		return ($rev, $c);
-	}
-	return (undef, undef);
-}
-
-sub libsvn_parse_revision {
-	my $base = shift;
-	my $head = $SVN->get_latest_revnum();
-	if (!defined $_revision || $_revision eq 'BASE:HEAD') {
-		return ($base + 1, $head) if (defined $base);
-		return (0, $head);
-	}
-	return ($1, $2) if ($_revision =~ /^(\d+):(\d+)$/);
-	return ($_revision, $_revision) if ($_revision =~ /^\d+$/);
-	if ($_revision =~ /^BASE:(\d+)$/) {
-		return ($base + 1, $1) if (defined $base);
-		return (0, $head);
-	}
-	return ($1, $head) if ($_revision =~ /^(\d+):HEAD$/);
-	die "revision argument: $_revision not understood by git-svn\n",
-		"Try using the command-line svn client instead\n";
-}
-
-sub libsvn_traverse_ignore {
-	my ($fh, $path, $r) = @_;
-	$path =~ s#^/+##g;
-	my $pool = SVN::Pool->new;
-	my ($dirent, undef, $props) = $SVN->get_dir($path, $r, $pool);
-	my $p = $path;
-	$p =~ s#^\Q$SVN->{svn_path}\E/##;
-	print $fh length $p ? "\n# $p\n" : "\n# /\n";
-	if (my $s = $props->{'svn:ignore'}) {
-		$s =~ s/[\r\n]+/\n/g;
-		chomp $s;
-		if (length $p == 0) {
-			$s =~ s#\n#\n/$p#g;
-			print $fh "/$s\n";
-		} else {
-			$s =~ s#\n#\n/$p/#g;
-			print $fh "/$p/$s\n";
-		}
-	}
-	foreach (sort keys %$dirent) {
-		next if $dirent->{$_}->kind != $SVN::Node::dir;
-		libsvn_traverse_ignore($fh, "$path/$_", $r);
-	}
-	$pool->clear;
-}
-
-sub revisions_eq {
-	my ($path, $r0, $r1) = @_;
-	return 1 if $r0 == $r1;
-	my $nr = 0;
-	# should be OK to use Pool here (r1 - r0) should be small
-	my $pool = SVN::Pool->new;
-	libsvn_get_log($SVN, [$path], $r0, $r1,
-			0, 0, 1, sub {$nr++}, $pool);
-	$pool->clear;
-	return 0 if ($nr > 1);
-	return 1;
-}
-
-sub libsvn_find_parent_branch {
-	my ($paths, $rev, $author, $date, $msg) = @_;
-	my $svn_path = '/'.$SVN->{svn_path};
-
-	# look for a parent from another branch:
-	my $i = $paths->{$svn_path} or return;
-	my $branch_from = $i->copyfrom_path or return;
-	my $r = $i->copyfrom_rev;
-	print STDERR  "Found possible branch point: ",
-				"$branch_from => $svn_path, $r\n";
-	$branch_from =~ s#^/##;
-	my $l_map = {};
-	read_url_paths_all($l_map, '', "$GIT_DIR/svn");
-	my $url = $SVN->{repos_root};
-	defined $l_map->{$url} or return;
-	my $id = $l_map->{$url}->{$branch_from};
-	if (!defined $id && $_follow_parent) {
-		print STDERR "Following parent: $branch_from\@$r\n";
-		# auto create a new branch and follow it
-		$id = basename($branch_from);
-		$id .= '@'.$r if -r "$GIT_DIR/svn/$id";
-		while (-r "$GIT_DIR/svn/$id") {
-			# just grow a tail if we're not unique enough :x
-			$id .= '-';
-		}
-	}
-	return unless defined $id;
-
-	my ($r0, $parent) = find_rev_before($r,$id,1);
-	if ($_follow_parent && (!defined $r0 || !defined $parent)) {
-		defined(my $pid = fork) or croak $!;
-		if (!$pid) {
-			$GIT_SVN = $ENV{GIT_SVN_ID} = $id;
-			init_vars();
-			$SVN_URL = "$url/$branch_from";
-			$SVN = undef;
-			setup_git_svn();
-			# we can't assume SVN_URL exists at r+1:
-			$_revision = "0:$r";
-			fetch_lib();
-			exit 0;
-		}
-		waitpid $pid, 0;
-		croak $? if $?;
-		($r0, $parent) = find_rev_before($r,$id,1);
-	}
-	return unless (defined $r0 && defined $parent);
-	if (revisions_eq($branch_from, $r0, $r)) {
-		unlink $GIT_SVN_INDEX;
-		print STDERR "Found branch parent: ($GIT_SVN) $parent\n";
-		command_noisy('read-tree', $parent);
-		unless (libsvn_can_do_switch()) {
-			return _libsvn_new_tree($paths, $rev, $author, $date,
-			                        $msg, [$parent]);
-		}
-		# do_switch works with svn/trunk >= r22312, but that is not
-		# included with SVN 1.4.2 (the latest version at the moment),
-		# so we can't rely on it.
-		my $ra = libsvn_connect("$url/$branch_from");
-		my $ed = SVN::Git::Fetcher->new({c => $parent, q => $_q });
-		my $pool = SVN::Pool->new;
-		my $reporter = $ra->do_switch($rev, '', 1, $SVN->{url},
-		                              $ed, $pool);
-		my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : ();
-		$reporter->set_path('', $r0, 0, @lock, $pool);
-		$reporter->finish_report($pool);
-		$pool->clear;
-		unless ($ed->{git_commit_ok}) {
-			die "SVN connection failed somewhere...\n";
-		}
-		return libsvn_log_entry($rev, $author, $date, $msg, [$parent]);
-	}
-	print STDERR "Nope, branch point not imported or unknown\n";
-	return undef;
-}
-
-sub libsvn_get_log {
-	my ($ra, @args) = @_;
-	$args[4]-- if $args[4] && ! $_follow_parent;
-	if ($SVN::Core::VERSION le '1.2.0') {
-		splice(@args, 3, 1);
-	}
-	$ra->get_log(@args);
-}
-
-sub libsvn_new_tree {
-	if (my $log_entry = libsvn_find_parent_branch(@_)) {
-		return $log_entry;
-	}
-	my ($paths, $rev, $author, $date, $msg) = @_; # $pool is last
-	_libsvn_new_tree($paths, $rev, $author, $date, $msg, []);
-}
-
-sub _libsvn_new_tree {
-	my ($paths, $rev, $author, $date, $msg, $parents) = @_;
-	my $pool = SVN::Pool->new;
-	my $ed = SVN::Git::Fetcher->new({q => $_q});
-	my $reporter = $SVN->do_update($rev, '', 1, $ed, $pool);
-	my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : ();
-	$reporter->set_path('', $rev, 1, @lock, $pool);
-	$reporter->finish_report($pool);
-	$pool->clear;
-	unless ($ed->{git_commit_ok}) {
-		die "SVN connection failed somewhere...\n";
-	}
-	libsvn_log_entry($rev, $author, $date, $msg, $parents, $ed);
-}
-
-sub find_graft_path_commit {
-	my ($tree_paths, $p1, $r1) = @_;
-	foreach my $x (keys %$tree_paths) {
-		next unless ($p1 =~ /^\Q$x\E/);
-		my $i = $tree_paths->{$x};
-		my ($r0, $parent) = find_rev_before($r1,$i,1);
-		return $parent if (defined $r0 && $r0 == $r1);
-		print STDERR "r$r1 of $i not imported\n";
-		next;
-	}
-	return undef;
-}
-
-sub find_graft_path_parents {
-	my ($grafts, $tree_paths, $c, $p0, $r0) = @_;
-	foreach my $x (keys %$tree_paths) {
-		next unless ($p0 =~ /^\Q$x\E/);
-		my $i = $tree_paths->{$x};
-		my ($r, $parent) = find_rev_before($r0, $i, 1);
-		if (defined $r && defined $parent && revisions_eq($x,$r,$r0)) {
-			my ($url_b, undef, $uuid_b) = cmt_metadata($c);
-			my ($url_a, undef, $uuid_a) = cmt_metadata($parent);
-			next if ($url_a && $url_b && $url_a eq $url_b &&
-							$uuid_b eq $uuid_a);
-			$grafts->{$c}->{$parent} = 1;
-		}
-	}
-}
-
-sub libsvn_graft_file_copies {
-	my ($grafts, $tree_paths, $path, $paths, $rev) = @_;
-	foreach (keys %$paths) {
-		my $i = $paths->{$_};
-		my ($m, $p0, $r0) = ($i->action, $i->copyfrom_path,
-					$i->copyfrom_rev);
-		next unless (defined $p0 && defined $r0);
-
-		my $p1 = $_;
-		$p1 =~ s#^/##;
-		$p0 =~ s#^/##;
-		my $c = find_graft_path_commit($tree_paths, $p1, $rev);
-		next unless $c;
-		find_graft_path_parents($grafts, $tree_paths, $c, $p0, $r0);
-	}
-}
-
-sub set_index {
-	my $old = $ENV{GIT_INDEX_FILE};
-	$ENV{GIT_INDEX_FILE} = shift;
-	return $old;
-}
-
-sub restore_index {
-	my ($old) = @_;
-	if (defined $old) {
-		$ENV{GIT_INDEX_FILE} = $old;
-	} else {
-		delete $ENV{GIT_INDEX_FILE};
-	}
-}
-
-sub libsvn_commit_cb {
-	my ($rev, $date, $committer, $c, $msg, $r_last, $cmt_last) = @_;
-	if ($_optimize_commits && $rev == ($r_last + 1)) {
-		my $log = libsvn_log_entry($rev,$committer,$date,$msg);
-		$log->{tree} = get_tree_from_treeish($c);
-		my $cmt = git_commit($log, $cmt_last, $c);
-		my @diff = command('diff-tree', $cmt, $c);
-		if (@diff) {
-			print STDERR "Trees differ: $cmt $c\n",
-					join('',@diff),"\n";
-			exit 1;
-		}
-	} else {
-		fetch("$rev=$c");
-	}
-}
-
-sub libsvn_ls_fullurl {
-	my $fullurl = shift;
-	my $ra = libsvn_connect($fullurl);
-	my @ret;
-	my $pool = SVN::Pool->new;
-	my $r = defined $_revision ? $_revision : $ra->get_latest_revnum;
-	my ($dirent, undef, undef) = $ra->get_dir('', $r, $pool);
-	foreach my $d (sort keys %$dirent) {
-		if ($dirent->{$d}->kind == $SVN::Node::dir) {
-			push @ret, "$d/"; # add '/' for compat with cli svn
-		}
-	}
-	$pool->clear;
-	return @ret;
-}
-
-
-sub libsvn_skip_unknown_revs {
-	my $err = shift;
-	my $errno = $err->apr_err();
-	# Maybe the branch we're tracking didn't
-	# exist when the repo started, so it's
-	# not an error if it doesn't, just continue
-	#
-	# Wonderfully consistent library, eh?
-	# 160013 - svn:// and file://
-	# 175002 - http(s)://
-	# 175007 - http(s):// (this repo required authorization, too...)
-	#   More codes may be discovered later...
-	if ($errno == 175007 || $errno == 175002 || $errno == 160013) {
-		return;
-	}
-	croak "Error from SVN, ($errno): ", $err->expanded_message,"\n";
-};
-
-# Tie::File seems to be prone to offset errors if revisions get sparse,
-# it's not that fast, either.  Tie::File is also not in Perl 5.6.  So
-# one of my favorite modules is out :<  Next up would be one of the DBM
-# modules, but I'm not sure which is most portable...  So I'll just
-# go with something that's plain-text, but still capable of
-# being randomly accessed.  So here's my ultra-simple fixed-width
-# database.  All records are 40 characters + "\n", so it's easy to seek
-# to a revision: (41 * rev) is the byte offset.
-# A record of 40 0s denotes an empty revision.
-# And yes, it's still pretty fast (faster than Tie::File).
-sub revdb_set {
-	my ($file, $rev, $commit) = @_;
-	length $commit == 40 or croak "arg3 must be a full SHA1 hexsum\n";
-	open my $fh, '+<', $file or croak $!;
-	my $offset = $rev * 41;
-	# assume that append is the common case:
-	seek $fh, 0, 2 or croak $!;
-	my $pos = tell $fh;
-	if ($pos < $offset) {
-		print $fh (('0' x 40),"\n") x (($offset - $pos) / 41);
-	}
-	seek $fh, $offset, 0 or croak $!;
-	print $fh $commit,"\n";
-	close $fh or croak $!;
-}
-
-sub revdb_get {
-	my ($file, $rev) = @_;
-	my $ret;
-	my $offset = $rev * 41;
-	open my $fh, '<', $file or croak $!;
-	seek $fh, $offset, 0;
-	if (tell $fh == $offset) {
-		$ret = readline $fh;
-		if (defined $ret) {
-			chomp $ret;
-			$ret = undef if ($ret =~ /^0{40}$/);
-		}
-	}
-	close $fh or croak $!;
-	return $ret;
-}
-
-sub copy_remote_ref {
-	my $origin = $_cp_remote ? $_cp_remote : 'origin';
-	my $ref = "refs/remotes/$GIT_SVN";
-	if (command('ls-remote', $origin, $ref)) {
-		command_noisy('fetch', $origin, "$ref:$ref");
-	} elsif ($_cp_remote && !$_upgrade) {
-		die "Unable to find remote reference: ",
-				"refs/remotes/$GIT_SVN on $origin\n";
-	}
-}
+package main;
 
 {
 	my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file.
@@ -2594,27 +2255,28 @@
 use warnings;
 use Carp qw/croak/;
 use IO::File qw//;
-use Git qw/command command_oneline command_noisy
-           command_output_pipe command_input_pipe command_close_pipe/;
+use Digest::MD5;
 
 # file baton members: path, mode_a, mode_b, pool, fh, blob, base
 sub new {
 	my ($class, $git_svn) = @_;
 	my $self = SVN::Delta::Editor->new;
 	bless $self, $class;
-	$self->{c} = $git_svn->{c} if exists $git_svn->{c};
-	$self->{q} = $git_svn->{q};
+	$self->{c} = $git_svn->{last_commit} if exists $git_svn->{last_commit};
 	$self->{empty} = {};
 	$self->{dir_prop} = {};
 	$self->{file_prop} = {};
 	$self->{absent_dir} = {};
 	$self->{absent_file} = {};
-	($self->{gui}, $self->{ctx}) = command_input_pipe(
-	                                     qw/update-index -z --index-info/);
-	require Digest::MD5;
+	$self->{gii} = $git_svn->tmp_index_do(sub { Git::IndexInfo->new });
 	$self;
 }
 
+sub set_path_strip {
+	my ($self, $path) = @_;
+	$self->{path_strip} = qr/^\Q$path\E(\/|$)/ if length $path;
+}
+
 sub open_root {
 	{ path => '' };
 }
@@ -2624,16 +2286,46 @@
 	{ path => $path };
 }
 
+sub git_path {
+	my ($self, $path) = @_;
+	if ($self->{path_strip}) {
+		$path =~ s!$self->{path_strip}!! or
+		  die "Failed to strip path '$path' ($self->{path_strip})\n";
+	}
+	$path;
+}
+
 sub delete_entry {
 	my ($self, $path, $rev, $pb) = @_;
-	my $t = process_rm($self->{gui}, $self->{c}, $path, $self->{q});
-	$self->{empty}->{$path} = 0 if $t == $SVN::Node::dir;
+
+	my $gpath = $self->git_path($path);
+	return undef if ($gpath eq '');
+
+	# remove entire directories.
+	if (command('ls-tree', $self->{c}, '--', $gpath) =~ /^040000 tree/) {
+		my ($ls, $ctx) = command_output_pipe(qw/ls-tree
+		                                     -r --name-only -z/,
+				                     $self->{c}, '--', $gpath);
+		local $/ = "\0";
+		while (<$ls>) {
+			chomp;
+			$self->{gii}->remove($_);
+			print "\tD\t$_\n" unless $::_q;
+		}
+		print "\tD\t$gpath/\n" unless $::_q;
+		command_close_pipe($ls, $ctx);
+		$self->{empty}->{$path} = 0
+	} else {
+		$self->{gii}->remove($gpath);
+		print "\tD\t$gpath\n" unless $::_q;
+	}
 	undef;
 }
 
 sub open_file {
 	my ($self, $path, $pb, $rev) = @_;
-	my ($mode, $blob) = (command('ls-tree', $self->{c}, '--',$path)
+	my $gpath = $self->git_path($path);
+	my ($mode, $blob) = (command('ls-tree', $self->{c}, '--', $gpath)
 	                     =~ /^(\d{6}) blob ([a-f\d]{40})\t/);
 	unless (defined $mode && defined $blob) {
 		die "$path was not found in commit $self->{c} (r$rev)\n";
@@ -2732,7 +2424,7 @@
 sub close_file {
 	my ($self, $fb, $exp) = @_;
 	my $hash;
-	my $path = $fb->{path};
+	my $path = $self->git_path($fb->{path});
 	if (my $fh = $fb->{fh}) {
 		seek($fh, 0, 0) or croak $!;
 		my $md5 = Digest::MD5->new;
@@ -2760,65 +2452,163 @@
 		$hash = $fb->{blob} or die "no blob information\n";
 	}
 	$fb->{pool}->clear;
-	my $gui = $self->{gui};
-	print $gui "$fb->{mode_b} $hash\t$path\0" or croak $!;
-	print "\t$fb->{action}\t$path\n" if $fb->{action} && ! $self->{q};
+	$self->{gii}->update($fb->{mode_b}, $hash, $path) or croak $!;
+	print "\t$fb->{action}\t$path\n" if $fb->{action} && ! $::_q;
 	undef;
 }
 
 sub abort_edit {
 	my $self = shift;
-	eval { command_close_pipe($self->{gui}, $self->{ctx}) };
+	$self->{nr} = $self->{gii}->{nr};
+	delete $self->{gii};
 	$self->SUPER::abort_edit(@_);
 }
 
 sub close_edit {
 	my $self = shift;
-	command_close_pipe($self->{gui}, $self->{ctx});
 	$self->{git_commit_ok} = 1;
+	$self->{nr} = $self->{gii}->{nr};
+	delete $self->{gii};
 	$self->SUPER::close_edit(@_);
 }
 
 package SVN::Git::Editor;
-use vars qw/@ISA/;
+use vars qw/@ISA $_rmdir $_cp_similarity $_find_copies_harder $_rename_limit/;
 use strict;
 use warnings;
 use Carp qw/croak/;
 use IO::File;
-use Git qw/command command_oneline command_noisy
-           command_output_pipe command_input_pipe command_close_pipe/;
+use Digest::MD5;
 
 sub new {
-	my $class = shift;
-	my $git_svn = shift;
-	my $self = SVN::Delta::Editor->new(@_);
-	bless $self, $class;
-	foreach (qw/svn_path c r ra /) {
-		die "$_ required!\n" unless (defined $git_svn->{$_});
-		$self->{$_} = $git_svn->{$_};
+	my ($class, $opts) = @_;
+	foreach (qw/svn_path r ra tree_a tree_b log editor_cb/) {
+		die "$_ required!\n" unless (defined $opts->{$_});
 	}
-	$self->{pool} = SVN::Pool->new;
+
+	my $pool = SVN::Pool->new;
+	my $mods = generate_diff($opts->{tree_a}, $opts->{tree_b});
+	my $types = check_diff_paths($opts->{ra}, $opts->{svn_path},
+	                             $opts->{r}, $mods);
+
+	# $opts->{ra} functions should not be used after this:
+	my @ce  = $opts->{ra}->get_commit_editor($opts->{log},
+	                                        $opts->{editor_cb}, $pool);
+	my $self = SVN::Delta::Editor->new(@ce, $pool);
+	bless $self, $class;
+	foreach (qw/svn_path r tree_a tree_b/) {
+		$self->{$_} = $opts->{$_};
+	}
+	$self->{url} = $opts->{ra}->{url};
+	$self->{mods} = $mods;
+	$self->{types} = $types;
+	$self->{pool} = $pool;
 	$self->{bat} = { '' => $self->open_root($self->{r}, $self->{pool}) };
 	$self->{rm} = { };
-	require Digest::MD5;
+	$self->{path_prefix} = length $self->{svn_path} ?
+	                       "$self->{svn_path}/" : '';
 	return $self;
 }
 
+sub generate_diff {
+	my ($tree_a, $tree_b) = @_;
+	my @diff_tree = qw(diff-tree -z -r);
+	if ($_cp_similarity) {
+		push @diff_tree, "-C$_cp_similarity";
+	} else {
+		push @diff_tree, '-C';
+	}
+	push @diff_tree, '--find-copies-harder' if $_find_copies_harder;
+	push @diff_tree, "-l$_rename_limit" if defined $_rename_limit;
+	push @diff_tree, $tree_a, $tree_b;
+	my ($diff_fh, $ctx) = command_output_pipe(@diff_tree);
+	local $/ = "\0";
+	my $state = 'meta';
+	my @mods;
+	while (<$diff_fh>) {
+		chomp $_; # this gets rid of the trailing "\0"
+		if ($state eq 'meta' && /^:(\d{6})\s(\d{6})\s
+					$::sha1\s($::sha1)\s
+					([MTCRAD])\d*$/xo) {
+			push @mods, {	mode_a => $1, mode_b => $2,
+					sha1_b => $3, chg => $4 };
+			if ($4 =~ /^(?:C|R)$/) {
+				$state = 'file_a';
+			} else {
+				$state = 'file_b';
+			}
+		} elsif ($state eq 'file_a') {
+			my $x = $mods[$#mods] or croak "Empty array\n";
+			if ($x->{chg} !~ /^(?:C|R)$/) {
+				croak "Error parsing $_, $x->{chg}\n";
+			}
+			$x->{file_a} = $_;
+			$state = 'file_b';
+		} elsif ($state eq 'file_b') {
+			my $x = $mods[$#mods] or croak "Empty array\n";
+			if (exists $x->{file_a} && $x->{chg} !~ /^(?:C|R)$/) {
+				croak "Error parsing $_, $x->{chg}\n";
+			}
+			if (!exists $x->{file_a} && $x->{chg} =~ /^(?:C|R)$/) {
+				croak "Error parsing $_, $x->{chg}\n";
+			}
+			$x->{file_b} = $_;
+			$state = 'meta';
+		} else {
+			croak "Error parsing $_\n";
+		}
+	}
+	command_close_pipe($diff_fh, $ctx);
+	\@mods;
+}
+
+sub check_diff_paths {
+	my ($ra, $pfx, $rev, $mods) = @_;
+	my %types;
+	$pfx .= '/' if length $pfx;
+
+	sub type_diff_paths {
+		my ($ra, $types, $path, $rev) = @_;
+		my @p = split m#/+#, $path;
+		my $c = shift @p;
+		unless (defined $types->{$c}) {
+			$types->{$c} = $ra->check_path($c, $rev);
+		}
+		while (@p) {
+			$c .= '/' . shift @p;
+			next if defined $types->{$c};
+			$types->{$c} = $ra->check_path($c, $rev);
+		}
+	}
+
+	foreach my $m (@$mods) {
+		foreach my $f (qw/file_a file_b/) {
+			next unless defined $m->{$f};
+			my ($dir) = ($m->{$f} =~ m#^(.*?)/?(?:[^/]+)$#);
+			if (length $pfx.$dir && ! defined $types{$dir}) {
+				type_diff_paths($ra, \%types, $pfx.$dir, $rev);
+			}
+		}
+	}
+	\%types;
+}
+
 sub split_path {
 	return ($_[0] =~ m#^(.*?)/?([^/]+)$#);
 }
 
 sub repo_path {
-	(defined $_[1] && length $_[1]) ? $_[1] : ''
+	my ($self, $path) = @_;
+	$self->{path_prefix}.(defined $path ? $path : '');
 }
 
 sub url_path {
 	my ($self, $path) = @_;
-	$self->{ra}->{url} . '/' . $self->repo_path($path);
+	$self->{url} . '/' . $self->repo_path($path);
 }
 
 sub rmdirs {
-	my ($self, $q) = @_;
+	my ($self) = @_;
 	my $rm = $self->{rm};
 	delete $rm->{''}; # we never delete the url we're tracking
 	return unless %$rm;
@@ -2836,8 +2626,8 @@
 	delete $rm->{''}; # we never delete the url we're tracking
 	return unless %$rm;
 
-	my ($fh, $ctx) = command_output_pipe(
-	                           qw/ls-tree --name-only -r -z/, $self->{c});
+	my ($fh, $ctx) = command_output_pipe(qw/ls-tree --name-only -r -z/,
+	                                     $self->{tree_b});
 	local $/ = "\0";
 	while (<$fh>) {
 		chomp;
@@ -2856,7 +2646,7 @@
 	foreach my $d (sort { $b =~ tr#/#/# <=> $a =~ tr#/#/# } keys %$rm) {
 		$self->close_directory($bat->{$d}, $p);
 		my ($dn) = ($d =~ m#^(.*?)/?(?:[^/]+)$#);
-		print "\tD+\t$d/\n" unless $q;
+		print "\tD+\t$d/\n" unless $::_q;
 		$self->SUPER::delete_entry($d, $r, $bat->{$dn}, $p);
 		delete $bat->{$d};
 	}
@@ -2864,9 +2654,10 @@
 
 sub open_or_add_dir {
 	my ($self, $full_path, $baton) = @_;
-	my $p = SVN::Pool->new;
-	my $t = $self->{ra}->check_path($full_path, $self->{r}, $p);
-	$p->clear;
+	my $t = $self->{types}->{$full_path};
+	if (!defined $t) {
+		die "$full_path not known in r$self->{r} or we have a bug!\n";
+	}
 	if ($t == $SVN::Node::none) {
 		return $self->add_directory($full_path, $baton,
 						undef, -1, $self->{pool});
@@ -2883,9 +2674,9 @@
 sub ensure_path {
 	my ($self, $path) = @_;
 	my $bat = $self->{bat};
-	$path = $self->repo_path($path);
-	return $bat->{''} unless (length $path);
-	my @p = split m#/+#, $path;
+	my $repo_path = $self->repo_path($path);
+	return $bat->{''} unless (length $repo_path);
+	my @p = split m#/+#, $repo_path;
 	my $c = shift @p;
 	$bat->{$c} ||= $self->open_or_add_dir($c, $bat->{''});
 	while (@p) {
@@ -2897,23 +2688,23 @@
 }
 
 sub A {
-	my ($self, $m, $q) = @_;
+	my ($self, $m) = @_;
 	my ($dir, $file) = split_path($m->{file_b});
 	my $pbat = $self->ensure_path($dir);
 	my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
 					undef, -1);
-	print "\tA\t$m->{file_b}\n" unless $q;
+	print "\tA\t$m->{file_b}\n" unless $::_q;
 	$self->chg_file($fbat, $m);
 	$self->close_file($fbat,undef,$self->{pool});
 }
 
 sub C {
-	my ($self, $m, $q) = @_;
+	my ($self, $m) = @_;
 	my ($dir, $file) = split_path($m->{file_b});
 	my $pbat = $self->ensure_path($dir);
 	my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
 				$self->url_path($m->{file_a}), $self->{r});
-	print "\tC\t$m->{file_a} => $m->{file_b}\n" unless $q;
+	print "\tC\t$m->{file_a} => $m->{file_b}\n" unless $::_q;
 	$self->chg_file($fbat, $m);
 	$self->close_file($fbat,undef,$self->{pool});
 }
@@ -2927,12 +2718,12 @@
 }
 
 sub R {
-	my ($self, $m, $q) = @_;
+	my ($self, $m) = @_;
 	my ($dir, $file) = split_path($m->{file_b});
 	my $pbat = $self->ensure_path($dir);
 	my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
 				$self->url_path($m->{file_a}), $self->{r});
-	print "\tR\t$m->{file_a} => $m->{file_b}\n" unless $q;
+	print "\tR\t$m->{file_a} => $m->{file_b}\n" unless $::_q;
 	$self->chg_file($fbat, $m);
 	$self->close_file($fbat,undef,$self->{pool});
 
@@ -2942,12 +2733,12 @@
 }
 
 sub M {
-	my ($self, $m, $q) = @_;
+	my ($self, $m) = @_;
 	my ($dir, $file) = split_path($m->{file_b});
 	my $pbat = $self->ensure_path($dir);
 	my $fbat = $self->open_file($self->repo_path($m->{file_b}),
 				$pbat,$self->{r},$self->{pool});
-	print "\t$m->{chg}\t$m->{file_b}\n" unless $q;
+	print "\t$m->{chg}\t$m->{file_b}\n" unless $::_q;
 	$self->chg_file($fbat, $m);
 	$self->close_file($fbat,undef,$self->{pool});
 }
@@ -2998,10 +2789,10 @@
 }
 
 sub D {
-	my ($self, $m, $q) = @_;
+	my ($self, $m) = @_;
 	my ($dir, $file) = split_path($m->{file_b});
 	my $pbat = $self->ensure_path($dir);
-	print "\tD\t$m->{file_b}\n" unless $q;
+	print "\tD\t$m->{file_b}\n" unless $::_q;
 	$self->delete_entry($m->{file_b}, $pbat);
 }
 
@@ -3018,22 +2809,1115 @@
 sub abort_edit {
 	my ($self) = @_;
 	$self->SUPER::abort_edit($self->{pool});
+}
+
+sub DESTROY {
+	my $self = shift;
+	$self->SUPER::DESTROY(@_);
 	$self->{pool}->clear;
 }
 
+# this drives the editor
+sub apply_diff {
+	my ($self) = @_;
+	my $mods = $self->{mods};
+	my %o = ( D => 1, R => 0, C => -1, A => 3, M => 3, T => 3 );
+	foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {
+		my $f = $m->{chg};
+		if (defined $o{$f}) {
+			$self->$f($m);
+		} else {
+			fatal("Invalid change type: $f\n");
+		}
+	}
+	$self->rmdirs if $_rmdir;
+	if (@$mods == 0) {
+		$self->abort_edit;
+	} else {
+		$self->close_edit;
+	}
+	return scalar @$mods;
+}
+
+package Git::SVN::Ra;
+use vars qw/@ISA $config_dir $_log_window_size/;
+use strict;
+use warnings;
+my ($can_do_switch, %ignored_err, $RA);
+
+BEGIN {
+	# enforce temporary pool usage for some simple functions
+	my $e;
+	foreach (qw/get_latest_revnum get_uuid get_repos_root/) {
+		$e .= "sub $_ {
+			my \$self = shift;
+			my \$pool = SVN::Pool->new;
+			my \@ret = \$self->SUPER::$_(\@_,\$pool);
+			\$pool->clear;
+			wantarray ? \@ret : \$ret[0]; }\n";
+	}
+
+	# get_dir needs $pool held in cache for dirents to work,
+	# check_path is cacheable and rev_proplist is close enough
+	# for our purposes.
+	foreach (qw/check_path get_dir rev_proplist/) {
+		$e .= "my \%${_}_cache; my \$${_}_rev = 0; sub $_ {
+			my \$self = shift;
+			my \$r = pop;
+			my \$k = join(\"\\0\", \@_);
+			if (my \$x = \$${_}_cache{\$r}->{\$k}) {
+				return wantarray ? \@\$x : \$x->[0];
+			}
+			my \$pool = SVN::Pool->new;
+			my \@ret = \$self->SUPER::$_(\@_, \$r, \$pool);
+			if (\$r != \$${_}_rev) {
+				\%${_}_cache = ( pool => [] );
+				\$${_}_rev = \$r;
+			}
+			\$${_}_cache{\$r}->{\$k} = \\\@ret;
+			push \@{\$${_}_cache{pool}}, \$pool;
+			wantarray ? \@ret : \$ret[0]; }\n";
+	}
+	$e .= "\n1;";
+	eval $e or die $@;
+}
+
+sub new {
+	my ($class, $url) = @_;
+	$url =~ s!/+$!!;
+	return $RA if ($RA && $RA->{url} eq $url);
+	$RA->{pool}->clear if $RA;
+
+	SVN::_Core::svn_config_ensure($config_dir, undef);
+	my ($baton, $callbacks) = SVN::Core::auth_open_helper([
+	    SVN::Client::get_simple_provider(),
+	    SVN::Client::get_ssl_server_trust_file_provider(),
+	    SVN::Client::get_simple_prompt_provider(
+	      \&Git::SVN::Prompt::simple, 2),
+	    SVN::Client::get_ssl_client_cert_prompt_provider(
+	      \&Git::SVN::Prompt::ssl_client_cert, 2),
+	    SVN::Client::get_ssl_client_cert_pw_prompt_provider(
+	      \&Git::SVN::Prompt::ssl_client_cert_pw, 2),
+	    SVN::Client::get_username_provider(),
+	    SVN::Client::get_ssl_server_trust_prompt_provider(
+	      \&Git::SVN::Prompt::ssl_server_trust),
+	    SVN::Client::get_username_prompt_provider(
+	      \&Git::SVN::Prompt::username, 2),
+	  ]);
+	my $config = SVN::Core::config_get_config($config_dir);
+	my $self = SVN::Ra->new(url => $url, auth => $baton,
+	                      config => $config,
+			      pool => SVN::Pool->new,
+	                      auth_provider_callbacks => $callbacks);
+	$self->{svn_path} = $url;
+	$self->{repos_root} = $self->get_repos_root;
+	$self->{svn_path} =~ s#^\Q$self->{repos_root}\E(/|$)##;
+	$RA = bless $self, $class;
+}
+
+sub DESTROY {
+	# do not call the real DESTROY since we store ourselves in $RA
+}
+
+sub get_log {
+	my ($self, @args) = @_;
+	my $pool = SVN::Pool->new;
+	splice(@args, 3, 1) if ($SVN::Core::VERSION le '1.2.0');
+	my $ret = $self->SUPER::get_log(@args, $pool);
+	$pool->clear;
+	$ret;
+}
+
+sub get_commit_editor {
+	my ($self, $log, $cb, $pool) = @_;
+	my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : ();
+	$self->SUPER::get_commit_editor($log, $cb, @lock, $pool);
+}
+
+sub gs_do_update {
+	my ($self, $rev_a, $rev_b, $gs, $editor) = @_;
+	my $new = ($rev_a == $rev_b);
+	my $path = $gs->{path};
+
+	if ($new && -e $gs->{index}) {
+		unlink $gs->{index} or die
+		  "Couldn't unlink index: $gs->{index}: $!\n";
+	}
+	my $pool = SVN::Pool->new;
+	$editor->set_path_strip($path);
+	my (@pc) = split m#/#, $path;
+	my $reporter = $self->do_update($rev_b, (@pc ? shift @pc : ''),
+	                                1, $editor, $pool);
+	my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : ();
+
+	# Since we can't rely on svn_ra_reparent being available, we'll
+	# just have to do some magic with set_path to make it so
+	# we only want a partial path.
+	my $sp = '';
+	my $final = join('/', @pc);
+	while (@pc) {
+		$reporter->set_path($sp, $rev_b, 0, @lock, $pool);
+		$sp .= '/' if length $sp;
+		$sp .= shift @pc;
+	}
+	die "BUG: '$sp' != '$final'\n" if ($sp ne $final);
+
+	$reporter->set_path($sp, $rev_a, $new, @lock, $pool);
+
+	$reporter->finish_report($pool);
+	$pool->clear;
+	$editor->{git_commit_ok};
+}
+
+# this requires SVN 1.4.3 or later (do_switch didn't work before 1.4.3, and
+# svn_ra_reparent didn't work before 1.4)
+sub gs_do_switch {
+	my ($self, $rev_a, $rev_b, $gs, $url_b, $editor) = @_;
+	my $path = $gs->{path};
+	my $pool = SVN::Pool->new;
+
+	my $full_url = $self->{url};
+	my $old_url = $full_url;
+	$full_url .= "/$path" if length $path;
+	my ($ra, $reparented);
+	if ($old_url ne $full_url) {
+		if ($old_url !~ m#^svn(\+ssh)?://#) {
+			SVN::_Ra::svn_ra_reparent($self->{session}, $full_url,
+			                          $pool);
+			$self->{url} = $full_url;
+			$reparented = 1;
+		} else {
+			$ra = Git::SVN::Ra->new($full_url);
+		}
+	}
+	$ra ||= $self;
+	my $reporter = $ra->do_switch($rev_b, '', 1, $url_b, $editor, $pool);
+	my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : ();
+	$reporter->set_path('', $rev_a, 0, @lock, $pool);
+	$reporter->finish_report($pool);
+
+	if ($reparented) {
+		SVN::_Ra::svn_ra_reparent($self->{session}, $old_url, $pool);
+		$self->{url} = $old_url;
+	}
+
+	$pool->clear;
+	$editor->{git_commit_ok};
+}
+
+sub gs_fetch_loop_common {
+	my ($self, $base, $head, $gsv, $globs) = @_;
+	return if ($base > $head);
+	my $inc = $_log_window_size;
+	my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc);
+	my %common;
+	my $common_max = scalar @$gsv;
+
+	foreach my $gs (@$gsv) {
+		my @tmp = split m#/#, $gs->{path};
+		my $p = '';
+		foreach (@tmp) {
+			$p .= length($p) ? "/$_" : $_;
+			$common{$p} ||= 0;
+			$common{$p}++;
+		}
+	}
+	$globs ||= [];
+	$common_max += scalar @$globs;
+	foreach my $glob (@$globs) {
+		my @tmp = split m#/#, $glob->{path}->{left};
+		my $p = '';
+		foreach (@tmp) {
+			$p .= length($p) ? "/$_" : $_;
+			$common{$p} ||= 0;
+			$common{$p}++;
+		}
+	}
+
+	my $longest_path = '';
+	foreach (sort {length $b <=> length $a} keys %common) {
+		if ($common{$_} == $common_max) {
+			$longest_path = $_;
+			last;
+		}
+	}
+	while (1) {
+		my %revs;
+		my $err;
+		my $err_handler = $SVN::Error::handler;
+		$SVN::Error::handler = sub {
+			($err) = @_;
+			skip_unknown_revs($err);
+		};
+		sub _cb {
+			my ($paths, $r, $author, $date, $log) = @_;
+			[ dup_changed_paths($paths),
+			  { author => $author, date => $date, log => $log } ];
+		}
+		$self->get_log([$longest_path], $min, $max, 0, 1, 1,
+		               sub { $revs{$_[1]} = _cb(@_) });
+		if ($err && $max >= $head) {
+			print STDERR "Path '$longest_path' ",
+				     "was probably deleted:\n",
+				     $err->expanded_message,
+				     "\nWill attempt to follow ",
+				     "revisions r$min .. r$max ",
+				     "committed before the deletion\n";
+			my $hi = $max;
+			while (--$hi >= $min) {
+				my $ok;
+				$self->get_log([$longest_path], $min, $hi,
+				               0, 1, 1, sub {
+				               $ok ||= $_[1];
+				               $revs{$_[1]} = _cb(@_) });
+				if ($ok) {
+					print STDERR "r$min .. r$ok OK\n";
+					last;
+				}
+			}
+		}
+		$SVN::Error::handler = $err_handler;
+
+		my %exists = map { $_->{path} => $_ } @$gsv;
+		foreach my $r (sort {$a <=> $b} keys %revs) {
+			my ($paths, $logged) = @{$revs{$r}};
+
+			foreach my $gs ($self->match_globs(\%exists, $paths,
+			                                   $globs, $r)) {
+				if ($gs->rev_db_max >= $r) {
+					next;
+				}
+				next unless $gs->match_paths($paths, $r);
+				$gs->{logged_rev_props} = $logged;
+				if (my $last_commit = $gs->last_commit) {
+					$gs->assert_index_clean($last_commit);
+				}
+				my $log_entry = $gs->do_fetch($paths, $r);
+				if ($log_entry) {
+					$gs->do_git_commit($log_entry);
+				}
+			}
+			foreach my $g (@$globs) {
+				my $k = "svn-remote.$g->{remote}." .
+				        "$g->{t}-maxRev";
+				Git::SVN::tmp_config($k, $r);
+			}
+		}
+		# pre-fill the .rev_db since it'll eventually get filled in
+		# with '0' x40 if something new gets committed
+		foreach my $gs (@$gsv) {
+			next if defined $gs->rev_db_get($max);
+			$gs->rev_db_set($max, 0 x40);
+		}
+		foreach my $g (@$globs) {
+			my $k = "svn-remote.$g->{remote}.$g->{t}-maxRev";
+			Git::SVN::tmp_config($k, $max);
+		}
+		last if $max >= $head;
+		$min = $max + 1;
+		$max += $inc;
+		$max = $head if ($max > $head);
+	}
+}
+
+sub match_globs {
+	my ($self, $exists, $paths, $globs, $r) = @_;
+
+	sub get_dir_check {
+		my ($self, $exists, $g, $r) = @_;
+		my @x = eval { $self->get_dir($g->{path}->{left}, $r) };
+		return unless scalar @x == 3;
+		my $dirents = $x[0];
+		foreach my $de (keys %$dirents) {
+			next if $dirents->{$de}->kind != $SVN::Node::dir;
+			my $p = $g->{path}->full_path($de);
+			next if $exists->{$p};
+			next if (length $g->{path}->{right} &&
+				 ($self->check_path($p, $r) !=
+				  $SVN::Node::dir));
+			$exists->{$p} = Git::SVN->init($self->{url}, $p, undef,
+					 $g->{ref}->full_path($de), 1);
+		}
+	}
+	foreach my $g (@$globs) {
+		if (my $path = $paths->{"/$g->{path}->{left}"}) {
+			if ($path->{action} =~ /^[AR]$/) {
+				get_dir_check($self, $exists, $g, $r);
+			}
+		}
+		foreach (keys %$paths) {
+			if (/$g->{path}->{left_regex}/ &&
+			    !/$g->{path}->{regex}/) {
+				next if $paths->{$_}->{action} !~ /^[AR]$/;
+				get_dir_check($self, $exists, $g, $r);
+			}
+			next unless /$g->{path}->{regex}/;
+			my $p = $1;
+			my $pathname = $g->{path}->full_path($p);
+			next if $exists->{$pathname};
+			$exists->{$pathname} = Git::SVN->init(
+			                      $self->{url}, $pathname, undef,
+			                      $g->{ref}->full_path($p), 1);
+		}
+		my $c = '';
+		foreach (split m#/#, $g->{path}->{left}) {
+			$c .= "/$_";
+			next unless ($paths->{$c} &&
+			             ($paths->{$c}->{action} =~ /^[AR]$/));
+			get_dir_check($self, $exists, $g, $r);
+		}
+	}
+	values %$exists;
+}
+
+sub minimize_url {
+	my ($self) = @_;
+	return $self->{url} if ($self->{url} eq $self->{repos_root});
+	my $url = $self->{repos_root};
+	my @components = split(m!/!, $self->{svn_path});
+	my $c = '';
+	do {
+		$url .= "/$c" if length $c;
+		eval { (ref $self)->new($url)->get_latest_revnum };
+	} while ($@ && ($c = shift @components));
+	$url;
+}
+
+sub can_do_switch {
+	my $self = shift;
+	unless (defined $can_do_switch) {
+		my $pool = SVN::Pool->new;
+		my $rep = eval {
+			$self->do_switch(1, '', 0, $self->{url},
+			                 SVN::Delta::Editor->new, $pool);
+		};
+		if ($@) {
+			$can_do_switch = 0;
+		} else {
+			$rep->abort_report($pool);
+			$can_do_switch = 1;
+		}
+		$pool->clear;
+	}
+	$can_do_switch;
+}
+
+sub skip_unknown_revs {
+	my ($err) = @_;
+	my $errno = $err->apr_err();
+	# Maybe the branch we're tracking didn't
+	# exist when the repo started, so it's
+	# not an error if it doesn't, just continue
+	#
+	# Wonderfully consistent library, eh?
+	# 160013 - svn:// and file://
+	# 175002 - http(s)://
+	# 175007 - http(s):// (this repo required authorization, too...)
+	#   More codes may be discovered later...
+	if ($errno == 175007 || $errno == 175002 || $errno == 160013) {
+		my $err_key = $err->expanded_message;
+		# revision numbers change every time, filter them out
+		$err_key =~ s/\d+/\0/g;
+		$err_key = "$errno\0$err_key";
+		unless ($ignored_err{$err_key}) {
+			warn "W: Ignoring error from SVN, path probably ",
+			     "does not exist: ($errno): ",
+			     $err->expanded_message,"\n";
+			$ignored_err{$err_key} = 1;
+		}
+		return;
+	}
+	die "Error from SVN, ($errno): ", $err->expanded_message,"\n";
+}
+
+# svn_log_changed_path_t objects passed to get_log are likely to be
+# overwritten even if only the refs are copied to an external variable,
+# so we should dup the structures in their entirety.  Using an externally
+# passed pool (instead of our temporary and quickly cleared pool in
+# Git::SVN::Ra) does not help matters at all...
+sub dup_changed_paths {
+	my ($paths) = @_;
+	return undef unless $paths;
+	my %ret;
+	foreach my $p (keys %$paths) {
+		my $i = $paths->{$p};
+		my %s = map { $_ => $i->$_ }
+		              qw/copyfrom_path copyfrom_rev action/;
+		$ret{$p} = \%s;
+	}
+	\%ret;
+}
+
+package Git::SVN::Log;
+use strict;
+use warnings;
+use POSIX qw/strftime/;
+use vars qw/$TZ $limit $color $pager $non_recursive $verbose $oneline
+            %rusers $show_commit $incremental/;
+my $l_fmt;
+
+sub cmt_showable {
+	my ($c) = @_;
+	return 1 if defined $c->{r};
+	if ($c->{l} && $c->{l}->[-1] eq "...\n" &&
+				$c->{a_raw} =~ /\@([a-f\d\-]+)>$/) {
+		my @log = command(qw/cat-file commit/, $c->{c});
+		shift @log while ($log[0] ne "\n");
+		shift @log;
+		@{$c->{l}} = grep !/^git-svn-id: /, @log;
+
+		(undef, $c->{r}, undef) = ::extract_metadata(
+				(grep(/^git-svn-id: /, @log))[-1]);
+	}
+	return defined $c->{r};
+}
+
+sub log_use_color {
+	return 1 if $color;
+	my ($dc, $dcvar);
+	$dcvar = 'color.diff';
+	$dc = `git-config --get $dcvar`;
+	if ($dc eq '') {
+		# nothing at all; fallback to "diff.color"
+		$dcvar = 'diff.color';
+		$dc = `git-config --get $dcvar`;
+	}
+	chomp($dc);
+	if ($dc eq 'auto') {
+		my $pc;
+		$pc = `git-config --get color.pager`;
+		if ($pc eq '') {
+			# does not have it -- fallback to pager.color
+			$pc = `git-config --bool --get pager.color`;
+		}
+		else {
+			$pc = `git-config --bool --get color.pager`;
+			if ($?) {
+				$pc = 'false';
+			}
+		}
+		chomp($pc);
+		if (-t *STDOUT || (defined $pager && $pc eq 'true')) {
+			return ($ENV{TERM} && $ENV{TERM} ne 'dumb');
+		}
+		return 0;
+	}
+	return 0 if $dc eq 'never';
+	return 1 if $dc eq 'always';
+	chomp($dc = `git-config --bool --get $dcvar`);
+	return ($dc eq 'true');
+}
+
+sub git_svn_log_cmd {
+	my ($r_min, $r_max, @args) = @_;
+	my $head = 'HEAD';
+	foreach my $x (@args) {
+		last if $x eq '--';
+		next unless ::verify_ref("$x^0");
+		$head = $x;
+		last;
+	}
+
+	my $url = (::working_head_info($head))[0];
+	my $gs = Git::SVN->find_by_url($url) || Git::SVN->_new;
+	my @cmd = (qw/log --abbrev-commit --pretty=raw --default/,
+	           $gs->refname);
+	push @cmd, '-r' unless $non_recursive;
+	push @cmd, qw/--raw --name-status/ if $verbose;
+	push @cmd, '--color' if log_use_color();
+	return @cmd unless defined $r_max;
+	if ($r_max == $r_min) {
+		push @cmd, '--max-count=1';
+		if (my $c = $gs->rev_db_get($r_max)) {
+			push @cmd, $c;
+		}
+	} else {
+		my ($c_min, $c_max);
+		$c_max = $gs->rev_db_get($r_max);
+		$c_min = $gs->rev_db_get($r_min);
+		if (defined $c_min && defined $c_max) {
+			if ($r_max > $r_max) {
+				push @cmd, "$c_min..$c_max";
+			} else {
+				push @cmd, "$c_max..$c_min";
+			}
+		} elsif ($r_max > $r_min) {
+			push @cmd, $c_max;
+		} else {
+			push @cmd, $c_min;
+		}
+	}
+	return @cmd;
+}
+
+# adapted from pager.c
+sub config_pager {
+	$pager ||= $ENV{GIT_PAGER} || $ENV{PAGER};
+	if (!defined $pager) {
+		$pager = 'less';
+	} elsif (length $pager == 0 || $pager eq 'cat') {
+		$pager = undef;
+	}
+}
+
+sub run_pager {
+	return unless -t *STDOUT;
+	pipe my $rfd, my $wfd or return;
+	defined(my $pid = fork) or ::fatal "Can't fork: $!\n";
+	if (!$pid) {
+		open STDOUT, '>&', $wfd or
+		                     ::fatal "Can't redirect to stdout: $!\n";
+		return;
+	}
+	open STDIN, '<&', $rfd or ::fatal "Can't redirect stdin: $!\n";
+	$ENV{LESS} ||= 'FRSX';
+	exec $pager or ::fatal "Can't run pager: $! ($pager)\n";
+}
+
+sub tz_to_s_offset {
+	my ($tz) = @_;
+	$tz =~ s/(\d\d)$//;
+	return ($1 * 60) + ($tz * 3600);
+}
+
+sub get_author_info {
+	my ($dest, $author, $t, $tz) = @_;
+	$author =~ s/(?:^\s*|\s*$)//g;
+	$dest->{a_raw} = $author;
+	my $au;
+	if ($::_authors) {
+		$au = $rusers{$author} || undef;
+	}
+	if (!$au) {
+		($au) = ($author =~ /<([^>]+)\@[^>]+>$/);
+	}
+	$dest->{t} = $t;
+	$dest->{tz} = $tz;
+	$dest->{a} = $au;
+	# Date::Parse isn't in the standard Perl distro :(
+	if ($tz =~ s/^\+//) {
+		$t += tz_to_s_offset($tz);
+	} elsif ($tz =~ s/^\-//) {
+		$t -= tz_to_s_offset($tz);
+	}
+	$dest->{t_utc} = $t;
+}
+
+sub process_commit {
+	my ($c, $r_min, $r_max, $defer) = @_;
+	if (defined $r_min && defined $r_max) {
+		if ($r_min == $c->{r} && $r_min == $r_max) {
+			show_commit($c);
+			return 0;
+		}
+		return 1 if $r_min == $r_max;
+		if ($r_min < $r_max) {
+			# we need to reverse the print order
+			return 0 if (defined $limit && --$limit < 0);
+			push @$defer, $c;
+			return 1;
+		}
+		if ($r_min != $r_max) {
+			return 1 if ($r_min < $c->{r});
+			return 1 if ($r_max > $c->{r});
+		}
+	}
+	return 0 if (defined $limit && --$limit < 0);
+	show_commit($c);
+	return 1;
+}
+
+sub show_commit {
+	my $c = shift;
+	if ($oneline) {
+		my $x = "\n";
+		if (my $l = $c->{l}) {
+			while ($l->[0] =~ /^\s*$/) { shift @$l }
+			$x = $l->[0];
+		}
+		$l_fmt ||= 'A' . length($c->{r});
+		print 'r',pack($l_fmt, $c->{r}),' | ';
+		print "$c->{c} | " if $show_commit;
+		print $x;
+	} else {
+		show_commit_normal($c);
+	}
+}
+
+sub show_commit_changed_paths {
+	my ($c) = @_;
+	return unless $c->{changed};
+	print "Changed paths:\n", @{$c->{changed}};
+}
+
+sub show_commit_normal {
+	my ($c) = @_;
+	print '-' x72, "\nr$c->{r} | ";
+	print "$c->{c} | " if $show_commit;
+	print "$c->{a} | ", strftime("%Y-%m-%d %H:%M:%S %z (%a, %d %b %Y)",
+				 localtime($c->{t_utc})), ' | ';
+	my $nr_line = 0;
+
+	if (my $l = $c->{l}) {
+		while ($l->[$#$l] eq "\n" && $#$l > 0
+		                          && $l->[($#$l - 1)] eq "\n") {
+			pop @$l;
+		}
+		$nr_line = scalar @$l;
+		if (!$nr_line) {
+			print "1 line\n\n\n";
+		} else {
+			if ($nr_line == 1) {
+				$nr_line = '1 line';
+			} else {
+				$nr_line .= ' lines';
+			}
+			print $nr_line, "\n";
+			show_commit_changed_paths($c);
+			print "\n";
+			print $_ foreach @$l;
+		}
+	} else {
+		print "1 line\n";
+		show_commit_changed_paths($c);
+		print "\n";
+
+	}
+	foreach my $x (qw/raw stat diff/) {
+		if ($c->{$x}) {
+			print "\n";
+			print $_ foreach @{$c->{$x}}
+		}
+	}
+}
+
+sub cmd_show_log {
+	my (@args) = @_;
+	my ($r_min, $r_max);
+	my $r_last = -1; # prevent dupes
+	if (defined $TZ) {
+		$ENV{TZ} = $TZ;
+	} else {
+		delete $ENV{TZ};
+	}
+	if (defined $::_revision) {
+		if ($::_revision =~ /^(\d+):(\d+)$/) {
+			($r_min, $r_max) = ($1, $2);
+		} elsif ($::_revision =~ /^\d+$/) {
+			$r_min = $r_max = $::_revision;
+		} else {
+			::fatal "-r$::_revision is not supported, use ",
+				"standard \'git log\' arguments instead\n";
+		}
+	}
+
+	config_pager();
+	@args = (git_svn_log_cmd($r_min, $r_max, @args), @args);
+	my $log = command_output_pipe(@args);
+	run_pager();
+	my (@k, $c, $d, $stat);
+	my $esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/;
+	while (<$log>) {
+		if (/^${esc_color}commit ($::sha1_short)/o) {
+			my $cmt = $1;
+			if ($c && cmt_showable($c) && $c->{r} != $r_last) {
+				$r_last = $c->{r};
+				process_commit($c, $r_min, $r_max, \@k) or
+								goto out;
+			}
+			$d = undef;
+			$c = { c => $cmt };
+		} elsif (/^${esc_color}author (.+) (\d+) ([\-\+]?\d+)$/o) {
+			get_author_info($c, $1, $2, $3);
+		} elsif (/^${esc_color}(?:tree|parent|committer) /o) {
+			# ignore
+		} elsif (/^${esc_color}:\d{6} \d{6} $::sha1_short/o) {
+			push @{$c->{raw}}, $_;
+		} elsif (/^${esc_color}[ACRMDT]\t/) {
+			# we could add $SVN->{svn_path} here, but that requires
+			# remote access at the moment (repo_path_split)...
+			s#^(${esc_color})([ACRMDT])\t#$1   $2 #o;
+			push @{$c->{changed}}, $_;
+		} elsif (/^${esc_color}diff /o) {
+			$d = 1;
+			push @{$c->{diff}}, $_;
+		} elsif ($d) {
+			push @{$c->{diff}}, $_;
+		} elsif (/^\ .+\ \|\s*\d+\ $esc_color[\+\-]*
+		          $esc_color*[\+\-]*$esc_color$/x) {
+			$stat = 1;
+			push @{$c->{stat}}, $_;
+		} elsif ($stat && /^ \d+ files changed, \d+ insertions/) {
+			push @{$c->{stat}}, $_;
+			$stat = undef;
+		} elsif (/^${esc_color}    (git-svn-id:.+)$/o) {
+			($c->{url}, $c->{r}, undef) = ::extract_metadata($1);
+		} elsif (s/^${esc_color}    //o) {
+			push @{$c->{l}}, $_;
+		}
+	}
+	if ($c && defined $c->{r} && $c->{r} != $r_last) {
+		$r_last = $c->{r};
+		process_commit($c, $r_min, $r_max, \@k);
+	}
+	if (@k) {
+		my $swap = $r_max;
+		$r_max = $r_min;
+		$r_min = $swap;
+		process_commit($_, $r_min, $r_max) foreach reverse @k;
+	}
+out:
+	close $log;
+	print '-' x72,"\n" unless $incremental || $oneline;
+}
+
+package Git::SVN::Migration;
+# these version numbers do NOT correspond to actual version numbers
+# of git nor git-svn.  They are just relative.
+#
+# v0 layout: .git/$id/info/url, refs/heads/$id-HEAD
+#
+# v1 layout: .git/$id/info/url, refs/remotes/$id
+#
+# v2 layout: .git/svn/$id/info/url, refs/remotes/$id
+#
+# v3 layout: .git/svn/$id, refs/remotes/$id
+#            - info/url may remain for backwards compatibility
+#            - this is what we migrate up to this layout automatically,
+#            - this will be used by git svn init on single branches
+# v3.1 layout (auto migrated):
+#            - .rev_db => .rev_db.$UUID, .rev_db will remain as a symlink
+#              for backwards compatibility
+#
+# v4 layout: .git/svn/$repo_id/$id, refs/remotes/$repo_id/$id
+#            - this is only created for newly multi-init-ed
+#              repositories.  Similar in spirit to the
+#              --use-separate-remotes option in git-clone (now default)
+#            - we do not automatically migrate to this (following
+#              the example set by core git)
+use strict;
+use warnings;
+use Carp qw/croak/;
+use File::Path qw/mkpath/;
+use File::Basename qw/dirname basename/;
+use vars qw/$_minimize/;
+
+sub migrate_from_v0 {
+	my $git_dir = $ENV{GIT_DIR};
+	return undef unless -d $git_dir;
+	my ($fh, $ctx) = command_output_pipe(qw/rev-parse --symbolic --all/);
+	my $migrated = 0;
+	while (<$fh>) {
+		chomp;
+		my ($id, $orig_ref) = ($_, $_);
+		next unless $id =~ s#^refs/heads/(.+)-HEAD$#$1#;
+		next unless -f "$git_dir/$id/info/url";
+		my $new_ref = "refs/remotes/$id";
+		if (::verify_ref("$new_ref^0")) {
+			print STDERR "W: $orig_ref is probably an old ",
+			             "branch used by an ancient version of ",
+				     "git-svn.\n",
+				     "However, $new_ref also exists.\n",
+				     "We will not be able ",
+				     "to use this branch until this ",
+				     "ambiguity is resolved.\n";
+			next;
+		}
+		print STDERR "Migrating from v0 layout...\n" if !$migrated;
+		print STDERR "Renaming ref: $orig_ref => $new_ref\n";
+		command_noisy('update-ref', $new_ref, $orig_ref);
+		command_noisy('update-ref', '-d', $orig_ref, $orig_ref);
+		$migrated++;
+	}
+	command_close_pipe($fh, $ctx);
+	print STDERR "Done migrating from v0 layout...\n" if $migrated;
+	$migrated;
+}
+
+sub migrate_from_v1 {
+	my $git_dir = $ENV{GIT_DIR};
+	my $migrated = 0;
+	return $migrated unless -d $git_dir;
+	my $svn_dir = "$git_dir/svn";
+
+	# just in case somebody used 'svn' as their $id at some point...
+	return $migrated if -d $svn_dir && ! -f "$svn_dir/info/url";
+
+	print STDERR "Migrating from a git-svn v1 layout...\n";
+	mkpath([$svn_dir]);
+	print STDERR "Data from a previous version of git-svn exists, but\n\t",
+	             "$svn_dir\n\t(required for this version ",
+	             "($::VERSION) of git-svn) does not. exist\n";
+	my ($fh, $ctx) = command_output_pipe(qw/rev-parse --symbolic --all/);
+	while (<$fh>) {
+		my $x = $_;
+		next unless $x =~ s#^refs/remotes/##;
+		chomp $x;
+		next unless -f "$git_dir/$x/info/url";
+		my $u = eval { ::file_to_s("$git_dir/$x/info/url") };
+		next unless $u;
+		my $dn = dirname("$git_dir/svn/$x");
+		mkpath([$dn]) unless -d $dn;
+		if ($x eq 'svn') { # they used 'svn' as GIT_SVN_ID:
+			mkpath(["$git_dir/svn/svn"]);
+			print STDERR " - $git_dir/$x/info => ",
+			                "$git_dir/svn/$x/info\n";
+			rename "$git_dir/$x/info", "$git_dir/svn/$x/info" or
+			       croak "$!: $x";
+			# don't worry too much about these, they probably
+			# don't exist with repos this old (save for index,
+			# and we can easily regenerate that)
+			foreach my $f (qw/unhandled.log index .rev_db/) {
+				rename "$git_dir/$x/$f", "$git_dir/svn/$x/$f";
+			}
+		} else {
+			print STDERR " - $git_dir/$x => $git_dir/svn/$x\n";
+			rename "$git_dir/$x", "$git_dir/svn/$x" or
+			       croak "$!: $x";
+		}
+		$migrated++;
+	}
+	command_close_pipe($fh, $ctx);
+	print STDERR "Done migrating from a git-svn v1 layout\n";
+	$migrated;
+}
+
+sub read_old_urls {
+	my ($l_map, $pfx, $path) = @_;
+	my @dir;
+	foreach (<$path/*>) {
+		if (-r "$_/info/url") {
+			$pfx .= '/' if $pfx && $pfx !~ m!/$!;
+			my $ref_id = $pfx . basename $_;
+			my $url = ::file_to_s("$_/info/url");
+			$l_map->{$ref_id} = $url;
+		} elsif (-d $_) {
+			push @dir, $_;
+		}
+	}
+	foreach (@dir) {
+		my $x = $_;
+		$x =~ s!^\Q$ENV{GIT_DIR}\E/svn/!!o;
+		read_old_urls($l_map, $x, $_);
+	}
+}
+
+sub migrate_from_v2 {
+	my @cfg = command(qw/config -l/);
+	return if grep /^svn-remote\..+\.url=/, @cfg;
+	my %l_map;
+	read_old_urls(\%l_map, '', "$ENV{GIT_DIR}/svn");
+	my $migrated = 0;
+
+	foreach my $ref_id (sort keys %l_map) {
+		eval { Git::SVN->init($l_map{$ref_id}, '', undef, $ref_id) };
+		if ($@) {
+			Git::SVN->init($l_map{$ref_id}, '', $ref_id, $ref_id);
+		}
+		$migrated++;
+	}
+	$migrated;
+}
+
+sub minimize_connections {
+	my $r = Git::SVN::read_all_remotes();
+	my $new_urls = {};
+	my $root_repos = {};
+	foreach my $repo_id (keys %$r) {
+		my $url = $r->{$repo_id}->{url} or next;
+		my $fetch = $r->{$repo_id}->{fetch} or next;
+		my $ra = Git::SVN::Ra->new($url);
+
+		# skip existing cases where we already connect to the root
+		if (($ra->{url} eq $ra->{repos_root}) ||
+		    (Git::SVN::sanitize_remote_name($ra->{repos_root}) eq
+		     $repo_id)) {
+			$root_repos->{$ra->{url}} = $repo_id;
+			next;
+		}
+
+		my $root_ra = Git::SVN::Ra->new($ra->{repos_root});
+		my $root_path = $ra->{url};
+		$root_path =~ s#^\Q$ra->{repos_root}\E(/|$)##;
+		foreach my $path (keys %$fetch) {
+			my $ref_id = $fetch->{$path};
+			my $gs = Git::SVN->new($ref_id, $repo_id, $path);
+
+			# make sure we can read when connecting to
+			# a higher level of a repository
+			my ($last_rev, undef) = $gs->last_rev_commit;
+			if (!defined $last_rev) {
+				$last_rev = eval {
+					$root_ra->get_latest_revnum;
+				};
+				next if $@;
+			}
+			my $new = $root_path;
+			$new .= length $path ? "/$path" : '';
+			eval {
+				$root_ra->get_log([$new], $last_rev, $last_rev,
+			                          0, 0, 1, sub { });
+			};
+			next if $@;
+			$new_urls->{$ra->{repos_root}}->{$new} =
+			        { ref_id => $ref_id,
+				  old_repo_id => $repo_id,
+				  old_path => $path };
+		}
+	}
+
+	my @emptied;
+	foreach my $url (keys %$new_urls) {
+		# see if we can re-use an existing [svn-remote "repo_id"]
+		# instead of creating a(n ugly) new section:
+		my $repo_id = $root_repos->{$url} ||
+		              Git::SVN::sanitize_remote_name($url);
+
+		my $fetch = $new_urls->{$url};
+		foreach my $path (keys %$fetch) {
+			my $x = $fetch->{$path};
+			Git::SVN->init($url, $path, $repo_id, $x->{ref_id});
+			my $pfx = "svn-remote.$x->{old_repo_id}";
+
+			my $old_fetch = quotemeta("$x->{old_path}:".
+			                          "refs/remotes/$x->{ref_id}");
+			command_noisy(qw/config --unset/,
+			              "$pfx.fetch", '^'. $old_fetch . '$');
+			delete $r->{$x->{old_repo_id}}->
+			       {fetch}->{$x->{old_path}};
+			if (!keys %{$r->{$x->{old_repo_id}}->{fetch}}) {
+				command_noisy(qw/config --unset/,
+				              "$pfx.url");
+				push @emptied, $x->{old_repo_id}
+			}
+		}
+	}
+	if (@emptied) {
+		my $file = $ENV{GIT_CONFIG} || $ENV{GIT_CONFIG_LOCAL} ||
+		           "$ENV{GIT_DIR}/config";
+		print STDERR <<EOF;
+The following [svn-remote] sections in your config file ($file) are empty
+and can be safely removed:
+EOF
+		print STDERR "[svn-remote \"$_\"]\n" foreach @emptied;
+	}
+}
+
+sub migration_check {
+	migrate_from_v0();
+	migrate_from_v1();
+	migrate_from_v2();
+	minimize_connections() if $_minimize;
+}
+
+package Git::IndexInfo;
+use strict;
+use warnings;
+use Git qw/command_input_pipe command_close_pipe/;
+
+sub new {
+	my ($class) = @_;
+	my ($gui, $ctx) = command_input_pipe(qw/update-index -z --index-info/);
+	bless { gui => $gui, ctx => $ctx, nr => 0}, $class;
+}
+
+sub remove {
+	my ($self, $path) = @_;
+	if (print { $self->{gui} } '0 ', 0 x 40, "\t", $path, "\0") {
+		return ++$self->{nr};
+	}
+	undef;
+}
+
+sub update {
+	my ($self, $mode, $hash, $path) = @_;
+	if (print { $self->{gui} } $mode, ' ', $hash, "\t", $path, "\0") {
+		return ++$self->{nr};
+	}
+	undef;
+}
+
+sub DESTROY {
+	my ($self) = @_;
+	command_close_pipe($self->{gui}, $self->{ctx});
+}
+
+package Git::SVN::GlobSpec;
+use strict;
+use warnings;
+
+sub new {
+	my ($class, $glob) = @_;
+	my $re = $glob;
+	$re =~ s!/+$!!g; # no need for trailing slashes
+	my $nr = ($re =~ s!^(.*)\*(.*)$!\(\[^/\]+\)!g);
+	my ($left, $right) = ($1, $2);
+	if ($nr > 1) {
+		die "Only one '*' wildcard expansion ",
+		    "is supported (got $nr): '$glob'\n";
+	} elsif ($nr == 0) {
+		die "One '*' is needed for glob: '$glob'\n";
+	}
+	$re = quotemeta($left) . $re . quotemeta($right);
+	if (length $left && !($left =~ s!/+$!!g)) {
+		die "Missing trailing '/' on left side of: '$glob' ($left)\n";
+	}
+	if (length $right && !($right =~ s!^/+!!g)) {
+		die "Missing leading '/' on right side of: '$glob' ($right)\n";
+	}
+	my $left_re = qr/^\/\Q$left\E(\/|$)/;
+	bless { left => $left, right => $right, left_regex => $left_re,
+	        regex => qr/$re/, glob => $glob }, $class;
+}
+
+sub full_path {
+	my ($self, $path) = @_;
+	return (length $self->{left} ? "$self->{left}/" : '') .
+	       $path . (length $self->{right} ? "/$self->{right}" : '');
+}
+
 __END__
 
 Data structures:
 
-$log_msg hashref as returned by libsvn_log_entry()
+
+$remotes = { # returned by read_all_remotes()
+	'svn' => {
+		# svn-remote.svn.url=https://svn.musicpd.org
+		url => 'https://svn.musicpd.org',
+		# svn-remote.svn.fetch=mpd/trunk:trunk
+		fetch => {
+			'mpd/trunk' => 'trunk',
+		},
+		# svn-remote.svn.tags=mpd/tags/*:tags/*
+		tags => {
+			path => {
+				left => 'mpd/tags',
+				right => '',
+				regex => qr!mpd/tags/([^/]+)$!,
+				glob => 'tags/*',
+			},
+			ref => {
+				left => 'tags',
+				right => '',
+				regex => qr!tags/([^/]+)$!,
+				glob => 'tags/*',
+			},
+		}
+	}
+};
+
+$log_entry hashref as returned by libsvn_log_entry()
 {
-	msg => 'whitespace-formatted log entry
+	log => 'whitespace-formatted log entry
 ',						# trailing newline is preserved
 	revision => '8',			# integer
 	date => '2004-02-24T17:01:44.108345Z',	# commit date
 	author => 'committer name'
 };
 
+
+# this is generated by generate_diff();
 @mods = array of diff-index line hashes, each element represents one line
 	of diff-index output
 
diff --git a/git.c b/git.c
index 660b0a6..5b1bc2a 100644
--- a/git.c
+++ b/git.c
@@ -48,7 +48,7 @@
 		/*
 		 * Check remaining flags.
 		 */
-		if (!strncmp(cmd, "--exec-path", 11)) {
+		if (!prefixcmp(cmd, "--exec-path")) {
 			cmd += 11;
 			if (*cmd == '=')
 				git_set_exec_path(cmd + 1);
@@ -66,7 +66,7 @@
 			setenv(GIT_DIR_ENVIRONMENT, (*argv)[1], 1);
 			(*argv)++;
 			(*argc)--;
-		} else if (!strncmp(cmd, "--git-dir=", 10)) {
+		} else if (!prefixcmp(cmd, "--git-dir=")) {
 			setenv(GIT_DIR_ENVIRONMENT, cmd + 10, 1);
 		} else if (!strcmp(cmd, "--bare")) {
 			static char git_dir[PATH_MAX+1];
@@ -88,7 +88,7 @@
 
 static int git_alias_config(const char *var, const char *value)
 {
-	if (!strncmp(var, "alias.", 6) && !strcmp(var + 6, alias_command)) {
+	if (!prefixcmp(var, "alias.") && !strcmp(var + 6, alias_command)) {
 		alias_string = xstrdup(value);
 	}
 	return 0;
@@ -99,7 +99,7 @@
 	int src, dst, count = 0, size = 16;
 	char quoted = 0;
 
-	*argv = malloc(sizeof(char*) * size);
+	*argv = xmalloc(sizeof(char*) * size);
 
 	/* split alias_string */
 	(*argv)[count++] = cmdline;
@@ -229,26 +229,29 @@
 		{ "archive", cmd_archive, RUN_SETUP },
 		{ "blame", cmd_blame, RUN_SETUP },
 		{ "branch", cmd_branch, RUN_SETUP },
+		{ "bundle", cmd_bundle },
 		{ "cat-file", cmd_cat_file, RUN_SETUP },
 		{ "checkout-index", cmd_checkout_index, RUN_SETUP },
 		{ "check-ref-format", cmd_check_ref_format },
 		{ "cherry", cmd_cherry, RUN_SETUP },
+		{ "cherry-pick", cmd_cherry_pick, RUN_SETUP | NOT_BARE },
 		{ "commit-tree", cmd_commit_tree, RUN_SETUP },
 		{ "config", cmd_config },
 		{ "count-objects", cmd_count_objects, RUN_SETUP },
 		{ "describe", cmd_describe, RUN_SETUP },
-		{ "diff", cmd_diff, RUN_SETUP | USE_PAGER },
-		{ "diff-files", cmd_diff_files, RUN_SETUP },
+		{ "diff", cmd_diff, USE_PAGER },
+		{ "diff-files", cmd_diff_files },
 		{ "diff-index", cmd_diff_index, RUN_SETUP },
-		{ "diff-stages", cmd_diff_stages, RUN_SETUP },
 		{ "diff-tree", cmd_diff_tree, RUN_SETUP },
+		{ "fetch--tool", cmd_fetch__tool, RUN_SETUP },
 		{ "fmt-merge-msg", cmd_fmt_merge_msg, RUN_SETUP },
 		{ "for-each-ref", cmd_for_each_ref, RUN_SETUP },
 		{ "format-patch", cmd_format_patch, RUN_SETUP },
 		{ "fsck", cmd_fsck, RUN_SETUP },
 		{ "fsck-objects", cmd_fsck, RUN_SETUP },
+		{ "gc", cmd_gc, RUN_SETUP },
 		{ "get-tar-commit-id", cmd_get_tar_commit_id },
-		{ "grep", cmd_grep, RUN_SETUP },
+		{ "grep", cmd_grep, RUN_SETUP | USE_PAGER },
 		{ "help", cmd_help },
 		{ "init", cmd_init_db },
 		{ "init-db", cmd_init_db },
@@ -257,6 +260,7 @@
 		{ "ls-tree", cmd_ls_tree, RUN_SETUP },
 		{ "mailinfo", cmd_mailinfo },
 		{ "mailsplit", cmd_mailsplit },
+		{ "merge-base", cmd_merge_base, RUN_SETUP },
 		{ "merge-file", cmd_merge_file },
 		{ "mv", cmd_mv, RUN_SETUP | NOT_BARE },
 		{ "name-rev", cmd_name_rev, RUN_SETUP },
@@ -271,6 +275,7 @@
 		{ "rerere", cmd_rerere, RUN_SETUP },
 		{ "rev-list", cmd_rev_list, RUN_SETUP },
 		{ "rev-parse", cmd_rev_parse, RUN_SETUP },
+		{ "revert", cmd_revert, RUN_SETUP | NOT_BARE },
 		{ "rm", cmd_rm, RUN_SETUP | NOT_BARE },
 		{ "runstatus", cmd_runstatus, RUN_SETUP | NOT_BARE },
 		{ "shortlog", cmd_shortlog, RUN_SETUP | USE_PAGER },
@@ -348,7 +353,7 @@
 	 * So we just directly call the internal command handler, and
 	 * die if that one cannot handle it.
 	 */
-	if (!strncmp(cmd, "git-", 4)) {
+	if (!prefixcmp(cmd, "git-")) {
 		cmd += 4;
 		argv[0] = cmd;
 		handle_internal_command(argc, argv, envp);
@@ -360,7 +365,7 @@
 	argc--;
 	handle_options(&argv, &argc);
 	if (argc > 0) {
-		if (!strncmp(argv[0], "--", 2))
+		if (!prefixcmp(argv[0], "--"))
 			argv[0] += 2;
 	} else {
 		/* Default command: "help" */
diff --git a/gitk b/gitk
index 9ddff3e..db28d74 100755
--- a/gitk
+++ b/gitk
@@ -720,6 +720,7 @@
     bindkey <Key-Return> {findnext 0}
     bindkey ? findprev
     bindkey f nextfile
+    bindkey <F5> updatecommits
     bind . <Control-q> doquit
     bind . <Control-f> dofind
     bind . <Control-g> {findnext 0}
@@ -985,6 +986,7 @@
 <Ctrl-plus>	Increase font size
 <Ctrl-KP->	Decrease font size
 <Ctrl-minus>	Decrease font size
+<F5>		Update
 } \
 	    -justify left -bg white -border 2 -relief sunken
     pack $w.m -side top -fill both
@@ -1904,7 +1906,7 @@
     } else {
 	set gdtargs [list "-S$highlight_files"]
     }
-    set cmd [concat | git-diff-tree -r -s --stdin $gdtargs]
+    set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
     set filehighlight [open $cmd r+]
     fconfigure $filehighlight -blocking 0
     fileevent $filehighlight readable readfhighlight
@@ -1956,7 +1958,7 @@
     }
     if {[eof $filehighlight]} {
 	# strange...
-	puts "oops, git-diff-tree died"
+	puts "oops, git diff-tree died"
 	catch {close $filehighlight}
 	unset filehighlight
     }
diff --git a/gitweb/INSTALL b/gitweb/INSTALL
index 371407d..6328e26 100644
--- a/gitweb/INSTALL
+++ b/gitweb/INSTALL
@@ -103,9 +103,25 @@
 
   See the top of gitweb.perl file for examples of customizable options.
 
+Config file example
+~~~~~~~~~~~~~~~~~~~
 
-Gitweb repositories:
---------------------
+To enable blame, pickaxe search, and snapshot support, while allowing
+individual projects to turn them off, put the following in your
+GITWEB_CONFIG file:
+
+	$feature{'blame'}{'default'} = [1];
+	$feature{'blame'}{'override'} = 1;
+
+	$feature{'pickaxe'}{'default'} = [1];
+	$feature{'pickaxe'}{'override'} = 1;
+
+	$feature{'snapshot'}{'default'} = ['x-gzip', 'gz', 'gzip'];
+	$feature{'snapshot'}{'override'} = 1;
+
+
+Gitweb repositories
+-------------------
 
 - By default all git repositories under projectroot are visible and
   available to gitweb. List of projects is generated by default by
@@ -139,6 +155,31 @@
   show repository only if this file exists in its object database
   (if directory has the magic file $export_ok).
 
+Generating projects list using gitweb
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We assume that GITWEB_CONFIG has its default Makefile value, namely
+gitweb_config.perl. Put the following in gitweb_make_index.perl file:
+
+	$GITWEB_CONFIG = "gitweb_config.perl";
+	do $GITWEB_CONFIG if -e $GITWEB_CONFIG;
+
+	$projects_list = $projectroot;
+
+Then create the following script to get list of project in the format
+suitable for GITWEB_LIST build configuration variable (or
+$projects_list variable in gitweb config):
+
+	#!/bin/sh
+
+	export GITWEB_CONFIG="gitweb_make_index.perl"
+	export GATEWAY_INTERFACE="CGI/1.1"
+	export HTTP_ACCEPT="*/*"
+	export REQUEST_METHOD="GET"
+	export QUERY_STRING="a=project_index"
+
+	perl -- /var/www/cgi-bin/gitweb.cgi
+
 
 Requirements
 ------------
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 5214050..3786955 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -3154,7 +3154,7 @@
 	}
 	$ftype = git_get_type($hash);
 	if ($ftype !~ "blob") {
-		die_error("400 Bad Request", "Object is not a blob");
+		die_error('400 Bad Request', "Object is not a blob");
 	}
 	open ($fd, "-|", git_cmd(), "blame", '-p', '--',
 	      $file_name, $hash_base)
@@ -3220,7 +3220,7 @@
 			print "</td>\n";
 		}
 		open (my $dd, "-|", git_cmd(), "rev-parse", "$full_rev^")
-			or die_error("could not open git-rev-parse");
+			or die_error(undef, "Open git-rev-parse failed");
 		my $parent_commit = <$dd>;
 		close $dd;
 		chomp($parent_commit);
@@ -3622,7 +3622,7 @@
 	$name =~ s/\047/\047\\\047\047/g;
 	open my $fd, "-|",
 	"$git archive --format=tar --prefix=\'$name\'/ $hash | $command"
-		or die_error(undef, "Execute git-tar-tree failed.");
+		or die_error(undef, "Execute git-tar-tree failed");
 	binmode STDOUT, ':raw';
 	print <$fd>;
 	binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
@@ -3885,7 +3885,7 @@
 			# read raw output
 			open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
 				$hash_parent_base, $hash_base,
-				"--", $file_name
+				"--", (defined $file_parent ? $file_parent : ()), $file_name
 				or die_error(undef, "Open git-diff-tree failed");
 			@difftree = map { chomp; $_ } <$fd>;
 			close $fd
@@ -3935,7 +3935,7 @@
 		# open patch output
 		open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
 			'-p', $hash_parent_base, $hash_base,
-			"--", $file_name
+			"--", (defined $file_parent ? $file_parent : ()), $file_name
 			or die_error(undef, "Open git-diff-tree failed");
 	}
 
diff --git a/hash-object.c b/hash-object.c
index 5f89e64..18f5017 100644
--- a/hash-object.c
+++ b/hash-object.c
@@ -7,7 +7,7 @@
 #include "cache.h"
 #include "blob.h"
 
-static void hash_object(const char *path, const char *type, int write_object)
+static void hash_object(const char *path, enum object_type type, int write_object)
 {
 	int fd;
 	struct stat st;
@@ -15,7 +15,7 @@
 	fd = open(path, O_RDONLY);
 	if (fd < 0 ||
 	    fstat(fd, &st) < 0 ||
-	    index_fd(sha1, fd, &st, write_object, type))
+	    index_fd(sha1, fd, &st, write_object, type, path))
 		die(write_object
 		    ? "Unable to add %s to database"
 		    : "Unable to hash %s", path);
@@ -73,7 +73,7 @@
 			if (0 <= prefix_length)
 				arg = prefix_filename(prefix, prefix_length,
 						      arg);
-			hash_object(arg, type, write_object);
+			hash_object(arg, type_from_string(type), write_object);
 			no_more_flags = 1;
 		}
 	}
diff --git a/help.c b/help.c
index b667463..6a9af4d 100644
--- a/help.c
+++ b/help.c
@@ -31,12 +31,6 @@
 	return 80;
 }
 
-static void oom(void)
-{
-	fprintf(stderr, "git: out of memory\n");
-	exit(1);
-}
-
 static inline void mput_char(char c, unsigned int num)
 {
 	while(num--)
@@ -54,13 +48,9 @@
 	struct cmdname *ent;
 	if (cmdname_alloc <= cmdname_cnt) {
 		cmdname_alloc = cmdname_alloc + 200;
-		cmdname = realloc(cmdname, cmdname_alloc * sizeof(*cmdname));
-		if (!cmdname)
-			oom();
+		cmdname = xrealloc(cmdname, cmdname_alloc * sizeof(*cmdname));
 	}
-	ent = malloc(sizeof(*ent) + len);
-	if (!ent)
-		oom();
+	ent = xmalloc(sizeof(*ent) + len);
 	ent->len = len;
 	memcpy(ent->name, name, len);
 	ent->name[len] = 0;
@@ -130,7 +120,7 @@
 		struct stat st;
 		int entlen;
 
-		if (strncmp(de->d_name, "git-", 4))
+		if (prefixcmp(de->d_name, "git-"))
 			continue;
 		strcpy(path+dirlen, de->d_name);
 		if (stat(path, &st) || /* stat, not lstat */
@@ -179,7 +169,7 @@
 {
 	const char *page;
 
-	if (!strncmp(git_cmd, "git", 3))
+	if (!prefixcmp(git_cmd, "git"))
 		page = git_cmd;
 	else {
 		int page_len = strlen(git_cmd) + 4;
diff --git a/http-fetch.c b/http-fetch.c
index 9f790a0..557b403 100644
--- a/http-fetch.c
+++ b/http-fetch.c
@@ -16,8 +16,7 @@
 
 struct alt_base
 {
-	const char *base;
-	int path_len;
+	char *base;
 	int got_indices;
 	struct packed_git *packs;
 	struct alt_base *next;
@@ -158,12 +157,12 @@
 
 	SHA1_Init(&obj_req->c);
 
-	url = xmalloc(strlen(obj_req->repo->base) + 50);
-	obj_req->url = xmalloc(strlen(obj_req->repo->base) + 50);
+	url = xmalloc(strlen(obj_req->repo->base) + 51);
+	obj_req->url = xmalloc(strlen(obj_req->repo->base) + 51);
 	strcpy(url, obj_req->repo->base);
 	posn = url + strlen(obj_req->repo->base);
-	strcpy(posn, "objects/");
-	posn += 8;
+	strcpy(posn, "/objects/");
+	posn += 9;
 	memcpy(posn, hex, 2);
 	posn += 2;
 	*(posn++) = '/';
@@ -515,7 +514,6 @@
 			int serverlen = 0;
 			struct alt_base *newalt;
 			char *target = NULL;
-			char *path;
 			if (data[i] == '/') {
 				/* This counts
 				 * http://git.host/pub/scm/linux.git/
@@ -583,12 +581,6 @@
 				newalt->base = target;
 				newalt->got_indices = 0;
 				newalt->packs = NULL;
-				path = strstr(target, "//");
-				if (path) {
-					path = strchr(path+2, '/');
-					if (path)
-						newalt->path_len = strlen(path);
-				}
 
 				while (tail->next != NULL)
 					tail = tail->next;
@@ -717,8 +709,8 @@
 		case 'P':
 			i++;
 			if (i + 52 <= buffer.posn &&
-			    !strncmp(data + i, " pack-", 6) &&
-			    !strncmp(data + i + 46, ".pack\n", 6)) {
+			    !prefixcmp(data + i, " pack-") &&
+			    !prefixcmp(data + i + 46, ".pack\n")) {
 				get_sha1_hex(data + i + 6, sha1);
 				setup_index(repo, sha1);
 				i += 51;
@@ -938,14 +930,14 @@
 	int len, baselen, ch;
 
 	baselen = strlen(base);
-	len = baselen + 6; /* "refs/" + NUL */
+	len = baselen + 7; /* "/refs/" + NUL */
 	for (cp = ref; (ch = *cp) != 0; cp++, len++)
 		if (needs_quote(ch))
 			len += 2; /* extra two hex plus replacement % */
 	qref = xmalloc(len);
 	memcpy(qref, base, baselen);
-	memcpy(qref + baselen, "refs/", 5);
-	for (cp = ref, dp = qref + baselen + 5; (ch = *cp) != 0; cp++) {
+	memcpy(qref + baselen, "/refs/", 6);
+	for (cp = ref, dp = qref + baselen + 6; (ch = *cp) != 0; cp++) {
 		if (needs_quote(ch)) {
 			*dp++ = '%';
 			*dp++ = hex((ch >> 4) & 0xF);
@@ -999,7 +991,7 @@
 	const char **write_ref = NULL;
 	char **commit_id;
 	const char *url;
-	char *path;
+	char *s;
 	int arg = 1;
 	int rc = 0;
 
@@ -1044,16 +1036,13 @@
 	no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:");
 
 	alt = xmalloc(sizeof(*alt));
-	alt->base = url;
+	alt->base = xmalloc(strlen(url) + 1);
+	strcpy(alt->base, url);
+	for (s = alt->base + strlen(alt->base) - 1; *s == '/'; --s)
+		*s = 0;
 	alt->got_indices = 0;
 	alt->packs = NULL;
 	alt->next = NULL;
-	path = strstr(url, "//");
-	if (path) {
-		path = strchr(path+2, '/');
-		if (path)
-			alt->path_len = strlen(path);
-	}
 
 	if (pull(commits, commit_id, write_ref, url))
 		rc = 1;
diff --git a/http-push.c b/http-push.c
index 6af9aec..724720c 100644
--- a/http-push.c
+++ b/http-push.c
@@ -479,7 +479,7 @@
 	char *hex = sha1_to_hex(request->obj->sha1);
 	struct active_request_slot *slot;
 	char *posn;
-	char type[20];
+	enum object_type type;
 	char hdr[50];
 	void *unpacked;
 	unsigned long len;
@@ -487,8 +487,8 @@
 	ssize_t size;
 	z_stream stream;
 
-	unpacked = read_sha1_file(request->obj->sha1, type, &len);
-	hdrlen = sprintf(hdr, "%s %lu", type, len) + 1;
+	unpacked = read_sha1_file(request->obj->sha1, &type, &len);
+	hdrlen = sprintf(hdr, "%s %lu", typename(type), len) + 1;
 
 	/* Set it up */
 	memset(&stream, 0, sizeof(stream));
@@ -1060,8 +1060,8 @@
 		case 'P':
 			i++;
 			if (i + 52 < buffer.posn &&
-			    !strncmp(data + i, " pack-", 6) &&
-			    !strncmp(data + i + 46, ".pack\n", 6)) {
+			    !prefixcmp(data + i, " pack-") &&
+			    !prefixcmp(data + i + 46, ".pack\n")) {
 				get_sha1_hex(data + i + 6, sha1);
 				setup_index(sha1);
 				i += 51;
@@ -1206,11 +1206,11 @@
 			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))
+			if (!prefixcmp(ctx->cdata, "Second-"))
 				lock->timeout =
 					strtol(ctx->cdata + 7, NULL, 10);
 		} else if (!strcmp(ctx->name, DAV_ACTIVELOCK_TOKEN)) {
-			if (!strncmp(ctx->cdata, "opaquelocktoken:", 16)) {
+			if (!prefixcmp(ctx->cdata, "opaquelocktoken:")) {
 				lock->token = xmalloc(strlen(ctx->cdata) - 15);
 				strcpy(lock->token, ctx->cdata + 16);
 			}
@@ -1750,8 +1750,7 @@
 	me.elem = name;
 	me.elem_len = strlen(name);
 
-	desc.buf = tree->buffer;
-	desc.size = tree->size;
+	init_tree_desc(&desc, tree->buffer, tree->size);
 
 	while (tree_entry(&desc, &entry)) {
 		if (S_ISDIR(entry.mode))
@@ -2171,7 +2170,7 @@
 		return;
 
 	/* If it's a symref, set the refname; otherwise try for a sha1 */
-	if (!strncmp((char *)buffer.buffer, "ref: ", 5)) {
+	if (!prefixcmp((char *)buffer.buffer, "ref: ")) {
 		*symref = xmalloc(buffer.posn - 5);
 		memcpy(*symref, (char *)buffer.buffer + 5, buffer.posn - 6);
 		(*symref)[buffer.posn - 6] = '\0';
diff --git a/imap-send.c b/imap-send.c
index 3eaf025..84df2fa 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -1192,7 +1192,7 @@
 	char *p = msg->data;
 
 	while (1) {
-		if (!strncmp( "From ", p, 5 )) {
+		if (!prefixcmp(p, "From ")) {
 			count++;
 			p += 5;
 		}
@@ -1216,7 +1216,7 @@
 	data = &all_msgs->data[ *ofs ];
 	msg->len = all_msgs->len - *ofs;
 
-	if (msg->len < 5 || strncmp( data, "From ", 5 ))
+	if (msg->len < 5 || prefixcmp(data, "From "))
 		return 0;
 
 	p = strchr( data, '\n' );
@@ -1267,12 +1267,12 @@
 		imap_folder = xstrdup( val );
 	} else if (!strcmp( "host", key )) {
 		{
-			if (!strncmp( "imap:", val, 5 ))
+			if (!prefixcmp(val, "imap:"))
 				val += 5;
 			if (!server.port)
 				server.port = 143;
 		}
-		if (!strncmp( "//", val, 2 ))
+		if (!prefixcmp(val, "//"))
 			val += 2;
 		server.host = xstrdup( val );
 	}
diff --git a/index-pack.c b/index-pack.c
index f917744..3c768fb 100644
--- a/index-pack.c
+++ b/index-pack.c
@@ -139,7 +139,7 @@
 		if (!pack_name) {
 			static char tmpfile[PATH_MAX];
 			snprintf(tmpfile, sizeof(tmpfile),
-				 "%s/pack_XXXXXX", get_object_directory());
+				 "%s/tmp_pack_XXXXXX", get_object_directory());
 			output_fd = mkstemp(tmpfile);
 			pack_name = xstrdup(tmpfile);
 		} else
@@ -347,26 +347,19 @@
 static void sha1_object(const void *data, unsigned long size,
 			enum object_type type, unsigned char *sha1)
 {
-	SHA_CTX ctx;
-	char header[50];
-	int header_size;
-	const char *type_str;
-
-	switch (type) {
-	case OBJ_COMMIT: type_str = commit_type; break;
-	case OBJ_TREE:   type_str = tree_type; break;
-	case OBJ_BLOB:   type_str = blob_type; break;
-	case OBJ_TAG:    type_str = tag_type; break;
-	default:
-		die("bad type %d", type);
+	hash_sha1_file(data, size, typename(type), sha1);
+	if (has_sha1_file(sha1)) {
+		void *has_data;
+		enum object_type has_type;
+		unsigned long has_size;
+		has_data = read_sha1_file(sha1, &has_type, &has_size);
+		if (!has_data)
+			die("cannot read existing object %s", sha1_to_hex(sha1));
+		if (size != has_size || type != has_type ||
+		    memcmp(data, has_data, size) != 0)
+			die("SHA1 COLLISION FOUND WITH %s !", sha1_to_hex(sha1));
+		free(has_data);
 	}
-
-	header_size = sprintf(header, "%s %lu", type_str, size) + 1;
-
-	SHA1_Init(&ctx);
-	SHA1_Update(&ctx, header, header_size);
-	SHA1_Update(&ctx, data, size);
-	SHA1_Final(sha1, &ctx);
 }
 
 static void resolve_delta(struct object_entry *delta_obj, void *base_data,
@@ -463,7 +456,8 @@
 	/* If input_fd is a file, we should have reached its end now. */
 	if (fstat(input_fd, &st))
 		die("cannot fstat packfile: %s", strerror(errno));
-	if (S_ISREG(st.st_mode) && st.st_size != consumed_bytes)
+	if (S_ISREG(st.st_mode) &&
+			lseek(input_fd, 0, SEEK_CUR) - input_len != st.st_size)
 		die("pack has junk at the end");
 
 	if (!nr_deltas)
@@ -546,7 +540,7 @@
 	return size;
 }
 
-static void append_obj_to_pack(void *buf,
+static void append_obj_to_pack(const unsigned char *sha1, void *buf,
 			       unsigned long size, enum object_type type)
 {
 	struct object_entry *obj = &objects[nr_objects++];
@@ -564,7 +558,7 @@
 	write_or_die(output_fd, header, n);
 	obj[1].offset = obj[0].offset + n;
 	obj[1].offset += write_compressed(output_fd, buf, size);
-	sha1_object(buf, size, type, obj->sha1);
+	hashcpy(obj->sha1, sha1);
 }
 
 static int delta_pos_compare(const void *_a, const void *_b)
@@ -601,30 +595,25 @@
 		struct delta_entry *d = sorted_by_pos[i];
 		void *data;
 		unsigned long size;
-		char type[10];
-		enum object_type obj_type;
+		enum object_type type;
 		int j, first, last;
 
 		if (objects[d->obj_no].real_type != OBJ_REF_DELTA)
 			continue;
-		data = read_sha1_file(d->base.sha1, type, &size);
+		data = read_sha1_file(d->base.sha1, &type, &size);
 		if (!data)
 			continue;
-		if      (!strcmp(type, blob_type))   obj_type = OBJ_BLOB;
-		else if (!strcmp(type, tree_type))   obj_type = OBJ_TREE;
-		else if (!strcmp(type, commit_type)) obj_type = OBJ_COMMIT;
-		else if (!strcmp(type, tag_type))    obj_type = OBJ_TAG;
-		else die("base object %s is of type '%s'",
-			 sha1_to_hex(d->base.sha1), type);
 
 		find_delta_children(&d->base, &first, &last);
 		for (j = first; j <= last; j++) {
 			struct object_entry *child = objects + deltas[j].obj_no;
 			if (child->real_type == OBJ_REF_DELTA)
-				resolve_delta(child, data, size, obj_type);
+				resolve_delta(child, data, size, type);
 		}
 
-		append_obj_to_pack(data, size, obj_type);
+		if (check_sha1_signature(d->base.sha1, data, size, typename(type)))
+			die("local object %s is corrupt", sha1_to_hex(d->base.sha1));
+		append_obj_to_pack(d->base.sha1, data, size, type);
 		free(data);
 		if (verbose)
 			percent = display_progress(nr_resolved_deltas,
@@ -702,7 +691,7 @@
 	if (!index_name) {
 		static char tmpfile[PATH_MAX];
 		snprintf(tmpfile, sizeof(tmpfile),
-			 "%s/index_XXXXXX", get_object_directory());
+			 "%s/tmp_idx_XXXXXX", get_object_directory());
 		fd = mkstemp(tmpfile);
 		index_name = xstrdup(tmpfile);
 	} else {
@@ -759,7 +748,7 @@
 		  const char *keep_name, const char *keep_msg,
 		  unsigned char *sha1)
 {
-	char *report = "pack";
+	const char *report = "pack";
 	char name[PATH_MAX];
 	int err;
 
@@ -855,9 +844,9 @@
 				fix_thin_pack = 1;
 			} else if (!strcmp(arg, "--keep")) {
 				keep_msg = "";
-			} else if (!strncmp(arg, "--keep=", 7)) {
+			} else if (!prefixcmp(arg, "--keep=")) {
 				keep_msg = arg + 7;
-			} else if (!strncmp(arg, "--pack_header=", 14)) {
+			} else if (!prefixcmp(arg, "--pack_header=")) {
 				struct pack_header *hdr;
 				char *c;
 
diff --git a/interpolate.c b/interpolate.c
index f992ef7..fb30694 100644
--- a/interpolate.c
+++ b/interpolate.c
@@ -55,7 +55,7 @@
 	const char *src = orig;
 	char *dest = result;
 	int newlen = 0;
-	char *name, *value;
+	const char *name, *value;
 	int namelen, valuelen;
 	int i;
 	char c;
diff --git a/interpolate.h b/interpolate.h
index 190a180..16a26b9 100644
--- a/interpolate.h
+++ b/interpolate.h
@@ -12,7 +12,7 @@
  */
 
 struct interp {
-	char *name;
+	const char *name;
 	char *value;
 };
 
diff --git a/list-objects.c b/list-objects.c
index f1fa21c..2ba2c95 100644
--- a/list-objects.c
+++ b/list-objects.c
@@ -49,8 +49,7 @@
 	me.elem = name;
 	me.elem_len = strlen(name);
 
-	desc.buf = tree->buffer;
-	desc.size = tree->size;
+	init_tree_desc(&desc, tree->buffer, tree->size);
 
 	while (tree_entry(&desc, &entry)) {
 		if (S_ISDIR(entry.mode))
diff --git a/local-fetch.c b/local-fetch.c
index 7cfe8b3..4b650ef 100644
--- a/local-fetch.c
+++ b/local-fetch.c
@@ -64,9 +64,9 @@
 		}
 		/* If we got ENOENT there is no point continuing. */
 		if (errno == ENOENT) {
-			if (warn_if_not_exists)
-				fprintf(stderr, "does not exist %s\n", source);
-			return -1;
+			if (!warn_if_not_exists)
+				return -1;
+			return error("does not exist %s", source);
 		}
 	}
 	if (use_symlink) {
@@ -74,9 +74,8 @@
 		if (stat(source, &st)) {
 			if (!warn_if_not_exists && errno == ENOENT)
 				return -1;
-			fprintf(stderr, "cannot stat %s: %s\n", source,
-				strerror(errno));
-			return -1;
+			return error("cannot stat %s: %s", source,
+				     strerror(errno));
 		}
 		if (!symlink(source, dest)) {
 			pull_say("symlink %s\n", hex);
@@ -90,25 +89,21 @@
 		if (ifd < 0) {
 			if (!warn_if_not_exists && errno == ENOENT)
 				return -1;
-			fprintf(stderr, "cannot open %s\n", source);
-			return -1;
+			return error("cannot open %s", source);
 		}
 		ofd = open(dest, O_WRONLY | O_CREAT | O_EXCL, 0666);
 		if (ofd < 0) {
-			fprintf(stderr, "cannot open %s\n", dest);
 			close(ifd);
-			return -1;
+			return error("cannot open %s", dest);
 		}
 		status = copy_fd(ifd, ofd);
 		close(ofd);
 		if (status)
-			fprintf(stderr, "cannot write %s\n", dest);
-		else
-			pull_say("copy %s\n", hex);
-		return status;
+			return error("cannot write %s", dest);
+		pull_say("copy %s\n", hex);
+		return 0;
 	}
-	fprintf(stderr, "failed to copy %s with given copy methods.\n", hex);
-	return -1;
+	return error("failed to copy %s with given copy methods.", hex);
 }
 
 static int fetch_pack(const unsigned char *sha1)
@@ -181,13 +176,11 @@
 	ifd = open(filename, O_RDONLY);
 	if (ifd < 0) {
 		close(ifd);
-		fprintf(stderr, "cannot open %s\n", filename);
-		return -1;
+		return error("cannot open %s", filename);
 	}
 	if (read_in_full(ifd, hex, 40) != 40 || get_sha1_hex(hex, sha1)) {
 		close(ifd);
-		fprintf(stderr, "cannot read from %s\n", filename);
-		return -1;
+		return error("cannot read from %s", filename);
 	}
 	close(ifd);
 	pull_say("ref %s\n", sha1_to_hex(sha1));
diff --git a/log-tree.c b/log-tree.c
index ac86194..8797aa1 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -186,7 +186,7 @@
 			snprintf(subject_buffer, sizeof(subject_buffer) - 1,
 				 "%s"
 				 "MIME-Version: 1.0\n"
-				 "Content-Type: multipart/mixed;\n"
+				 "Content-Type: multipart/mixed;"
 				 " boundary=\"%s%s\"\n"
 				 "\n"
 				 "This is a multi-part message in MIME "
@@ -202,16 +202,18 @@
 
 			snprintf(buffer, sizeof(buffer) - 1,
 				 "--%s%s\n"
-				 "Content-Type: text/x-patch;\n"
+				 "Content-Type: text/x-patch;"
 				 " name=\"%s.diff\"\n"
 				 "Content-Transfer-Encoding: 8bit\n"
-				 "Content-Disposition: inline;\n"
+				 "Content-Disposition: %s;"
 				 " filename=\"%s.diff\"\n\n",
 				 mime_boundary_leader, opt->mime_boundary,
-				 sha1, sha1);
+				 sha1,
+				 opt->no_inline ? "attachment" : "inline",
+				 sha1);
 			opt->diffopt.stat_sep = buffer;
 		}
-	} else {
+	} else if (opt->commit_format != CMIT_FMT_USERFORMAT) {
 		fputs(diff_get_color(opt->diffopt.color_diff, DIFF_COMMIT),
 		      stdout);
 		if (opt->commit_format != CMIT_FMT_ONELINE)
diff --git a/merge-file.c b/merge-file.c
index 69dc1eb..748d15c 100644
--- a/merge-file.c
+++ b/merge-file.c
@@ -7,12 +7,12 @@
 {
 	void *buf;
 	unsigned long size;
-	char type[20];
+	enum object_type type;
 
-	buf = read_sha1_file(obj->object.sha1, type, &size);
+	buf = read_sha1_file(obj->object.sha1, &type, &size);
 	if (!buf)
 		return -1;
-	if (strcmp(type, blob_type))
+	if (type != OBJ_BLOB)
 		return -1;
 	f->ptr = buf;
 	f->size = size;
@@ -86,12 +86,12 @@
 	 * modified in the other branch!
 	 */
 	if (!our || !their) {
-		char type[20];
+		enum object_type type;
 		if (base)
 			return NULL;
 		if (!our)
 			our = their;
-		return read_sha1_file(our->object.sha1, type, size);
+		return read_sha1_file(our->object.sha1, &type, size);
 	}
 
 	if (fill_mmfile_blob(&f1, our) < 0)
diff --git a/merge-index.c b/merge-index.c
index 7027d78..5599fd3 100644
--- a/merge-index.c
+++ b/merge-index.c
@@ -1,30 +1,17 @@
 #include "cache.h"
+#include "run-command.h"
 
 static const char *pgm;
-static const char *arguments[8];
+static const char *arguments[9];
 static int one_shot, quiet;
 static int err;
 
 static void run_program(void)
 {
-	pid_t pid = fork();
-	int status;
-
-	if (pid < 0)
-		die("unable to fork");
-	if (!pid) {
-		execlp(pgm, arguments[0],
-			    arguments[1],
-			    arguments[2],
-			    arguments[3],
-			    arguments[4],
-			    arguments[5],
-			    arguments[6],
-			    arguments[7],
-			    NULL);
-		die("unable to execute '%s'", pgm);
-	}
-	if (waitpid(pid, &status, 0) < 0 || !WIFEXITED(status) || WEXITSTATUS(status)) {
+	struct child_process child;
+	memset(&child, 0, sizeof(child));
+	child.argv = arguments;
+	if (run_command(&child)) {
 		if (one_shot) {
 			err++;
 		} else {
@@ -49,6 +36,7 @@
 	arguments[5] = "";
 	arguments[6] = "";
 	arguments[7] = "";
+	arguments[8] = NULL;
 	found = 0;
 	do {
 		static char hexbuf[4][60];
diff --git a/merge-recursive.c b/merge-recursive.c
index 397a7ad..e1aebd7 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -278,8 +278,16 @@
 {
 	struct tree *result = NULL;
 
-	if (unmerged_index())
+	if (unmerged_index()) {
+		int i;
+		output(0, "There are unmerged index entries:");
+		for (i = 0; i < active_nr; i++) {
+			struct cache_entry *ce = active_cache[i];
+			if (ce_stage(ce))
+				output(0, "%d %.*s", ce_stage(ce), ce_namelen(ce), ce->name);
+		}
 		return NULL;
+	}
 
 	if (!active_cache_tree)
 		active_cache_tree = cache_tree();
@@ -560,17 +568,17 @@
 		update_wd = 0;
 
 	if (update_wd) {
-		char type[20];
+		enum object_type type;
 		void *buf;
 		unsigned long size;
 
-		buf = read_sha1_file(sha, type, &size);
+		buf = read_sha1_file(sha, &type, &size);
 		if (!buf)
 			die("cannot read object %s '%s'", sha1_to_hex(sha), path);
-		if (strcmp(type, blob_type) != 0)
+		if (type != OBJ_BLOB)
 			die("blob expected for %s '%s'", sha1_to_hex(sha), path);
 
-		if (S_ISREG(mode)) {
+		if (S_ISREG(mode) || (!has_symlinks && S_ISLNK(mode))) {
 			int fd;
 			if (mkdir_p(path, 0777))
 				die("failed to create path %s: %s", path, strerror(errno));
@@ -591,6 +599,7 @@
 			mkdir_p(path, 0777);
 			unlink(path);
 			symlink(lnk, path);
+			free(lnk);
 		} else
 			die("do not know what to do with %06o %s '%s'",
 			    mode, sha1_to_hex(sha), path);
@@ -620,7 +629,7 @@
 static void fill_mm(const unsigned char *sha1, mmfile_t *mm)
 {
 	unsigned long size;
-	char type[20];
+	enum object_type type;
 
 	if (!hashcmp(sha1, null_sha1)) {
 		mm->ptr = xstrdup("");
@@ -628,8 +637,8 @@
 		return;
 	}
 
-	mm->ptr = read_sha1_file(sha1, type, &size);
-	if (!mm->ptr || strcmp(type, blob_type))
+	mm->ptr = read_sha1_file(sha1, &type, &size);
+	if (!mm->ptr || type != OBJ_BLOB)
 		die("unable to read blob object %s", sha1_to_hex(sha1));
 	mm->size = size;
 }
@@ -734,8 +743,19 @@
 		       ren2_dst, branch1, dst_name2);
 		remove_file(0, ren2_dst, 0);
 	}
-	update_stages(dst_name1, NULL, ren1->pair->two, NULL, 1);
-	update_stages(dst_name2, NULL, NULL, ren2->pair->two, 1);
+	if (index_only) {
+		remove_file_from_cache(dst_name1);
+		remove_file_from_cache(dst_name2);
+		/*
+		 * Uncomment to leave the conflicting names in the resulting tree
+		 *
+		 * update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, dst_name1);
+		 * update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, dst_name2);
+		 */
+	} else {
+		update_stages(dst_name1, NULL, ren1->pair->two, NULL, 1);
+		update_stages(dst_name2, NULL, NULL, ren2->pair->two, 1);
+	}
 	while (delp--)
 		free(del[delp]);
 }
@@ -851,10 +871,16 @@
 			if (strcmp(ren1_dst, ren2_dst) != 0) {
 				clean_merge = 0;
 				output(1, "CONFLICT (rename/rename): "
-				       "Rename %s->%s in branch %s "
-				       "rename %s->%s in %s",
+				       "Rename \"%s\"->\"%s\" in branch \"%s\" "
+				       "rename \"%s\"->\"%s\" in \"%s\"%s",
 				       src, ren1_dst, branch1,
-				       src, ren2_dst, branch2);
+				       src, ren2_dst, branch2,
+				       index_only ? " (left unresolved)": "");
+				if (index_only) {
+					remove_file_from_cache(src);
+					update_file(0, ren1->pair->one->sha1,
+						    ren1->pair->one->mode, src);
+				}
 				conflict_rename_rename(ren1, branch1, ren2, branch2);
 			} else {
 				struct merge_file_info mfi;
@@ -1213,7 +1239,7 @@
 
 		tree->object.parsed = 1;
 		tree->object.type = OBJ_TREE;
-		pretend_sha1_file(NULL, 0, tree_type, tree->object.sha1);
+		pretend_sha1_file(NULL, 0, OBJ_TREE, tree->object.sha1);
 		merged_common_ancestors = make_virtual_commit(tree, "ancestor");
 	}
 
diff --git a/merge-tree.c b/merge-tree.c
index 692ede0..3b8d9e6 100644
--- a/merge-tree.c
+++ b/merge-tree.c
@@ -57,11 +57,11 @@
 
 static void *result(struct merge_list *entry, unsigned long *size)
 {
-	char type[20];
+	enum object_type type;
 	struct blob *base, *our, *their;
 
 	if (!entry->stage)
-		return read_sha1_file(entry->blob->object.sha1, type, size);
+		return read_sha1_file(entry->blob->object.sha1, &type, size);
 	base = NULL;
 	if (entry->stage == 1) {
 		base = entry->blob;
@@ -80,10 +80,10 @@
 
 static void *origin(struct merge_list *entry, unsigned long *size)
 {
-	char type[20];
+	enum object_type type;
 	while (entry) {
 		if (entry->stage == 2)
-			return read_sha1_file(entry->blob->object.sha1, type, size);
+			return read_sha1_file(entry->blob->object.sha1, &type, size);
 		entry = entry->link;
 	}
 	return NULL;
@@ -188,7 +188,7 @@
 
 static int unresolved_directory(const char *base, struct name_entry n[3])
 {
-	int baselen;
+	int baselen, pathlen;
 	char *newbase;
 	struct name_entry *p;
 	struct tree_desc t[3];
@@ -205,10 +205,11 @@
 	if (!S_ISDIR(p->mode))
 		return 0;
 	baselen = strlen(base);
-	newbase = xmalloc(baselen + p->pathlen + 2);
+	pathlen = tree_entry_len(p->path, p->sha1);
+	newbase = xmalloc(baselen + pathlen + 2);
 	memcpy(newbase, base, baselen);
-	memcpy(newbase + baselen, p->path, p->pathlen);
-	memcpy(newbase + baselen + p->pathlen, "/", 2);
+	memcpy(newbase + baselen, p->path, pathlen);
+	memcpy(newbase + baselen + pathlen, "/", 2);
 
 	buf0 = fill_tree_descriptor(t+0, n[0].sha1);
 	buf1 = fill_tree_descriptor(t+1, n[1].sha1);
diff --git a/mktag.c b/mktag.c
index 3448a5d..9310111 100644
--- a/mktag.c
+++ b/mktag.c
@@ -27,13 +27,13 @@
 static int verify_object(unsigned char *sha1, const char *expected_type)
 {
 	int ret = -1;
-	char type[100];
+	enum object_type type;
 	unsigned long size;
-	void *buffer = read_sha1_file(sha1, type, &size);
+	void *buffer = read_sha1_file(sha1, &type, &size);
 
 	if (buffer) {
-		if (!strcmp(type, expected_type))
-			ret = check_sha1_signature(sha1, buffer, size, type);
+		if (type == type_from_string(expected_type))
+			ret = check_sha1_signature(sha1, buffer, size, expected_type);
 		free(buffer);
 	}
 	return ret;
diff --git a/mktree.c b/mktree.c
index 56205d1..d86dde8 100644
--- a/mktree.c
+++ b/mktree.c
@@ -95,7 +95,7 @@
 		int len;
 		char *ptr, *ntr;
 		unsigned mode;
-		char type[20];
+		enum object_type type;
 		char *path;
 
 		read_line(&sb, stdin, line_termination);
@@ -115,11 +115,12 @@
 		    ntr[41] != '\t' ||
 		    get_sha1_hex(ntr + 1, sha1))
 			die("input format error: %s", sb.buf);
-		if (sha1_object_info(sha1, type, NULL))
+		type = sha1_object_info(sha1, NULL);
+		if (type < 0)
 			die("object %s unavailable", sha1_to_hex(sha1));
 		*ntr++ = 0; /* now at the beginning of SHA1 */
-		if (strcmp(ptr, type))
-			die("object type %s mismatch (%s)", ptr, type);
+		if (type != type_from_string(ptr))
+			die("object type %s mismatch (%s)", ptr, typename(type));
 		ntr += 41; /* at the beginning of name */
 		if (line_termination && ntr[0] == '"')
 			path = unquote_c_style(ntr, NULL);
diff --git a/object.c b/object.c
index de244e2..78a44a6 100644
--- a/object.c
+++ b/object.c
@@ -18,11 +18,31 @@
 	return obj_hash[idx];
 }
 
-const char *type_names[] = {
-	"none", "commit", "tree", "blob", "tag",
-	"bad type 5", "bad type 6", "delta", "bad",
+static const char *object_type_strings[] = {
+	NULL,		/* OBJ_NONE = 0 */
+	"commit",	/* OBJ_COMMIT = 1 */
+	"tree",		/* OBJ_TREE = 2 */
+	"blob",		/* OBJ_BLOB = 3 */
+	"tag",		/* OBJ_TAG = 4 */
 };
 
+const char *typename(unsigned int type)
+{
+	if (type >= ARRAY_SIZE(object_type_strings))
+		return NULL;
+	return object_type_strings[type];
+}
+
+int type_from_string(const char *str)
+{
+	int i;
+
+	for (i = 1; i < ARRAY_SIZE(object_type_strings); i++)
+		if (!strcmp(str, object_type_strings[i]))
+			return i;
+	die("invalid object type \"%s\"", str);
+}
+
 static unsigned int hash_obj(struct object *obj, unsigned int n)
 {
 	unsigned int hash = *(unsigned int *)obj->sha1;
@@ -100,24 +120,6 @@
 	nr_objs++;
 }
 
-struct object *lookup_object_type(const unsigned char *sha1, const char *type)
-{
-	if (!type) {
-		return lookup_unknown_object(sha1);
-	} else if (!strcmp(type, blob_type)) {
-		return &lookup_blob(sha1)->object;
-	} else if (!strcmp(type, tree_type)) {
-		return &lookup_tree(sha1)->object;
-	} else if (!strcmp(type, commit_type)) {
-		return &lookup_commit(sha1)->object;
-	} else if (!strcmp(type, tag_type)) {
-		return &lookup_tag(sha1)->object;
-	} else {
-		error("Unknown type %s", type);
-		return NULL;
-	}
-}
-
 union any_object {
 	struct object object;
 	struct commit commit;
@@ -138,23 +140,23 @@
 	return obj;
 }
 
-struct object *parse_object_buffer(const unsigned char *sha1, const char *type, unsigned long size, void *buffer, int *eaten_p)
+struct object *parse_object_buffer(const unsigned char *sha1, enum object_type type, unsigned long size, void *buffer, int *eaten_p)
 {
 	struct object *obj;
 	int eaten = 0;
 
-	if (!strcmp(type, blob_type)) {
+	if (type == OBJ_BLOB) {
 		struct blob *blob = lookup_blob(sha1);
 		parse_blob_buffer(blob, buffer, size);
 		obj = &blob->object;
-	} else if (!strcmp(type, tree_type)) {
+	} else if (type == OBJ_TREE) {
 		struct tree *tree = lookup_tree(sha1);
 		obj = &tree->object;
 		if (!tree->object.parsed) {
 			parse_tree_buffer(tree, buffer, size);
 			eaten = 1;
 		}
-	} else if (!strcmp(type, commit_type)) {
+	} else if (type == OBJ_COMMIT) {
 		struct commit *commit = lookup_commit(sha1);
 		parse_commit_buffer(commit, buffer, size);
 		if (!commit->buffer) {
@@ -162,7 +164,7 @@
 			eaten = 1;
 		}
 		obj = &commit->object;
-	} else if (!strcmp(type, tag_type)) {
+	} else if (type == OBJ_TAG) {
 		struct tag *tag = lookup_tag(sha1);
 		parse_tag_buffer(tag, buffer, size);
 		obj = &tag->object;
@@ -176,14 +178,16 @@
 struct object *parse_object(const unsigned char *sha1)
 {
 	unsigned long size;
-	char type[20];
+	enum object_type type;
 	int eaten;
-	void *buffer = read_sha1_file(sha1, type, &size);
+	void *buffer = read_sha1_file(sha1, &type, &size);
 
 	if (buffer) {
 		struct object *obj;
-		if (check_sha1_signature(sha1, buffer, size, type) < 0)
-			printf("sha1 mismatch %s\n", sha1_to_hex(sha1));
+		if (check_sha1_signature(sha1, buffer, size, typename(type)) < 0) {
+			error("sha1 mismatch %s\n", sha1_to_hex(sha1));
+			return NULL;
+		}
 
 		obj = parse_object_buffer(sha1, type, size, buffer, &eaten);
 		if (!eaten)
diff --git a/object.h b/object.h
index caee733..bdbf0fa 100644
--- a/object.h
+++ b/object.h
@@ -36,24 +36,17 @@
 };
 
 extern int track_object_refs;
-extern const char *type_names[9];
+
+extern const char *typename(unsigned int type);
+extern int type_from_string(const char *str);
 
 extern unsigned int get_max_object_index(void);
 extern struct object *get_indexed_object(unsigned int);
-
-static inline const char *typename(unsigned int type)
-{
-	return type_names[type > OBJ_BAD ? OBJ_BAD : type];
-}
-
 extern struct object_refs *lookup_object_refs(struct object *);
 
 /** Internal only **/
 struct object *lookup_object(const unsigned char *sha1);
 
-/** Returns the object, having looked it up as being the given type. **/
-struct object *lookup_object_type(const unsigned char *sha1, const char *type);
-
 void created_object(const unsigned char *sha1, struct object *obj);
 
 /** Returns the object, having parsed it to find out what it is. **/
@@ -63,7 +56,7 @@
  * parsing it.  eaten_p indicates if the object has a borrowed copy
  * of buffer and the caller should not free() it.
  */
-struct object *parse_object_buffer(const unsigned char *sha1, const char *type, unsigned long size, void *buffer, int *eaten_p);
+struct object *parse_object_buffer(const unsigned char *sha1, enum object_type type, unsigned long size, void *buffer, int *eaten_p);
 
 /** Returns the object, with potentially excess memory allocated. **/
 struct object *lookup_unknown_object(const unsigned  char *sha1);
diff --git a/pack-check.c b/pack-check.c
index 08a9fd8..d988322 100644
--- a/pack-check.c
+++ b/pack-check.c
@@ -4,12 +4,13 @@
 static int verify_packfile(struct packed_git *p,
 		struct pack_window **w_curs)
 {
-	unsigned long index_size = p->index_size;
-	void *index_base = p->index_base;
+	off_t index_size = p->index_size;
+	const unsigned char *index_base = p->index_data;
 	SHA_CTX ctx;
 	unsigned char sha1[20];
-	unsigned long offset = 0, pack_sig = p->pack_size - 20;
-	int nr_objects, err, i;
+	off_t offset = 0, pack_sig = p->pack_size - 20;
+	uint32_t nr_objects, i;
+	int err;
 
 	/* Note that the pack header checks are actually performed by
 	 * use_pack when it first opens the pack file.  If anything
@@ -23,14 +24,14 @@
 		unsigned char *in = use_pack(p, w_curs, offset, &remaining);
 		offset += remaining;
 		if (offset > pack_sig)
-			remaining -= offset - pack_sig;
+			remaining -= (unsigned int)(offset - pack_sig);
 		SHA1_Update(&ctx, in, remaining);
 	}
 	SHA1_Final(sha1, &ctx);
 	if (hashcmp(sha1, use_pack(p, w_curs, pack_sig, NULL)))
 		return error("Packfile %s SHA1 mismatch with itself",
 			     p->pack_name);
-	if (hashcmp(sha1, (unsigned char *)index_base + index_size - 40))
+	if (hashcmp(sha1, index_base + index_size - 40))
 		return error("Packfile %s SHA1 mismatch with idx",
 			     p->pack_name);
 	unuse_pack(w_curs);
@@ -40,24 +41,25 @@
 	 * we do not do scan-streaming check on the pack file.
 	 */
 	nr_objects = num_packed_objects(p);
-	for (i = err = 0; i < nr_objects; i++) {
+	for (i = 0, err = 0; i < nr_objects; i++) {
 		unsigned char sha1[20];
 		void *data;
-		char type[20];
-		unsigned long size, offset;
+		enum object_type type;
+		unsigned long size;
+		off_t offset;
 
 		if (nth_packed_object_sha1(p, i, sha1))
 			die("internal error pack-check nth-packed-object");
 		offset = find_pack_entry_one(sha1, p);
 		if (!offset)
 			die("internal error pack-check find-pack-entry-one");
-		data = unpack_entry(p, offset, type, &size);
+		data = unpack_entry(p, offset, &type, &size);
 		if (!data) {
 			err = error("cannot unpack %s from %s",
 				    sha1_to_hex(sha1), p->pack_name);
 			continue;
 		}
-		if (check_sha1_signature(sha1, data, size, type)) {
+		if (check_sha1_signature(sha1, data, size, typename(type))) {
 			err = error("packed %s from %s is corrupt",
 				    sha1_to_hex(sha1), p->pack_name);
 			free(data);
@@ -74,18 +76,17 @@
 
 static void show_pack_info(struct packed_git *p)
 {
-	int nr_objects, i;
-	unsigned int chain_histogram[MAX_CHAIN];
+	uint32_t nr_objects, i, chain_histogram[MAX_CHAIN];
 
 	nr_objects = num_packed_objects(p);
 	memset(chain_histogram, 0, sizeof(chain_histogram));
 
 	for (i = 0; i < nr_objects; i++) {
 		unsigned char sha1[20], base_sha1[20];
-		char type[20];
+		const char *type;
 		unsigned long size;
 		unsigned long store_size;
-		unsigned long offset;
+		off_t offset;
 		unsigned int delta_chain_length;
 
 		if (nth_packed_object_sha1(p, i, sha1))
@@ -94,14 +95,16 @@
 		if (!offset)
 			die("internal error pack-check find-pack-entry-one");
 
-		packed_object_info_detail(p, offset, type, &size, &store_size,
-					  &delta_chain_length,
-					  base_sha1);
+		type = packed_object_info_detail(p, offset, &size, &store_size,
+						 &delta_chain_length,
+						 base_sha1);
 		printf("%s ", sha1_to_hex(sha1));
 		if (!delta_chain_length)
-			printf("%-6s %lu %lu\n", type, size, offset);
+			printf("%-6s %lu %"PRIuMAX"\n",
+			       type, size, (uintmax_t)offset);
 		else {
-			printf("%-6s %lu %lu %u %s\n", type, size, offset,
+			printf("%-6s %lu %"PRIuMAX" %u %s\n",
+			       type, size, (uintmax_t)offset,
 			       delta_chain_length, sha1_to_hex(base_sha1));
 			if (delta_chain_length < MAX_CHAIN)
 				chain_histogram[delta_chain_length]++;
@@ -123,8 +126,8 @@
 
 int verify_pack(struct packed_git *p, int verbose)
 {
-	unsigned long index_size = p->index_size;
-	void *index_base = p->index_base;
+	off_t index_size = p->index_size;
+	const unsigned char *index_base = p->index_data;
 	SHA_CTX ctx;
 	unsigned char sha1[20];
 	int ret;
@@ -132,9 +135,9 @@
 	ret = 0;
 	/* Verify SHA1 sum of the index file */
 	SHA1_Init(&ctx);
-	SHA1_Update(&ctx, index_base, index_size - 20);
+	SHA1_Update(&ctx, index_base, (unsigned int)(index_size - 20));
 	SHA1_Final(sha1, &ctx);
-	if (hashcmp(sha1, (unsigned char *)index_base + index_size - 20))
+	if (hashcmp(sha1, index_base + index_size - 20))
 		ret = error("Packfile index for %s SHA1 mismatch",
 			    p->pack_name);
 
diff --git a/pack-redundant.c b/pack-redundant.c
index edb5524..40e579b 100644
--- a/pack-redundant.c
+++ b/pack-redundant.c
@@ -17,7 +17,7 @@
 
 struct llist_item {
 	struct llist_item *next;
-	unsigned char *sha1;
+	const unsigned char *sha1;
 };
 static struct llist {
 	struct llist_item *front;
@@ -104,9 +104,9 @@
 	return ret;
 }
 
-static inline struct llist_item * llist_insert(struct llist *list,
-					       struct llist_item *after,
-					       unsigned char *sha1)
+static inline struct llist_item *llist_insert(struct llist *list,
+					      struct llist_item *after,
+					       const unsigned char *sha1)
 {
 	struct llist_item *new = llist_item_get();
 	new->sha1 = sha1;
@@ -128,12 +128,14 @@
 	return new;
 }
 
-static inline struct llist_item *llist_insert_back(struct llist *list, unsigned char *sha1)
+static inline struct llist_item *llist_insert_back(struct llist *list,
+						   const unsigned char *sha1)
 {
 	return llist_insert(list, list->back, sha1);
 }
 
-static inline struct llist_item *llist_insert_sorted_unique(struct llist *list, unsigned char *sha1, struct llist_item *hint)
+static inline struct llist_item *llist_insert_sorted_unique(struct llist *list,
+			const unsigned char *sha1, struct llist_item *hint)
 {
 	struct llist_item *prev = NULL, *l;
 
@@ -246,12 +248,12 @@
 static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2)
 {
 	int p1_off, p2_off;
-	unsigned char *p1_base, *p2_base;
+	const unsigned char *p1_base, *p2_base;
 	struct llist_item *p1_hint = NULL, *p2_hint = NULL;
 
 	p1_off = p2_off = 256 * 4 + 4;
-	p1_base = (unsigned char *) p1->pack->index_base;
-	p2_base = (unsigned char *) p2->pack->index_base;
+	p1_base = p1->pack->index_data;
+	p2_base = p2->pack->index_data;
 
 	while (p1_off <= p1->pack->index_size - 3 * 20 &&
 	       p2_off <= p2->pack->index_size - 3 * 20)
@@ -351,11 +353,11 @@
 {
 	size_t ret = 0;
 	int p1_off, p2_off;
-	unsigned char *p1_base, *p2_base;
+	const unsigned char *p1_base, *p2_base;
 
 	p1_off = p2_off = 256 * 4 + 4;
-	p1_base = (unsigned char *)p1->index_base;
-	p2_base = (unsigned char *)p2->index_base;
+	p1_base = p1->index_data;
+	p2_base = p2->index_data;
 
 	while (p1_off <= p1->index_size - 3 * 20 &&
 	       p2_off <= p2->index_size - 3 * 20)
@@ -396,9 +398,9 @@
 	return ret;
 }
 
-static inline size_t pack_set_bytecount(struct pack_list *pl)
+static inline off_t pack_set_bytecount(struct pack_list *pl)
 {
-	size_t ret = 0;
+	off_t ret = 0;
 	while (pl) {
 		ret += pl->pack->pack_size;
 		ret += pl->pack->index_size;
@@ -413,7 +415,7 @@
 		*non_unique = NULL, *min_perm = NULL;
 	struct pll *perm, *perm_all, *perm_ok = NULL, *new_perm;
 	struct llist *missing;
-	size_t min_perm_size = (size_t)-1, perm_size;
+	off_t min_perm_size = 0, perm_size;
 	int n;
 
 	pl = local_packs;
@@ -461,7 +463,7 @@
 	perm = perm_ok;
 	while (perm) {
 		perm_size = pack_set_bytecount(perm->pl);
-		if (min_perm_size > perm_size) {
+		if (!min_perm_size || min_perm_size > perm_size) {
 			min_perm_size = perm_size;
 			min_perm = perm->pl;
 		}
@@ -534,7 +536,7 @@
 {
 	struct pack_list l;
 	size_t off;
-	unsigned char *base;
+	const unsigned char *base;
 
 	if (!p->pack_local && !(alt_odb || verbose))
 		return NULL;
@@ -543,7 +545,7 @@
 	llist_init(&l.all_objects);
 
 	off = 256 * 4 + 4;
-	base = (unsigned char *)p->index_base;
+	base = p->index_data;
 	while (off <= p->index_size - 3 * 20) {
 		llist_insert_back(l.all_objects, base + off);
 		off += 24;
diff --git a/pack.h b/pack.h
index deb427e..d4d412c 100644
--- a/pack.h
+++ b/pack.h
@@ -16,24 +16,15 @@
 };
 
 /*
- * Packed object index header
- *
- * struct pack_idx_header {
- * 	uint32_t idx_signature;
- *	uint32_t idx_version;
- * };
- *
- * Note: this header isn't active yet.  In future versions of git
- * we may change the index file format.  At that time we would start
- * the first four bytes of the new index format with this signature,
- * as all older git binaries would find this value illegal and abort
- * reading the file.
+ * The first four bytes of index formats later than version 1 should
+ * start with this signature, as all older git binaries would find this
+ * value illegal and abort reading the file.
  *
  * This is the case because the number of objects in a packfile
  * cannot exceed 1,431,660,000 as every object would need at least
- * 3 bytes of data and the overall packfile cannot exceed 4 GiB due
- * to the 32 bit offsets used by the index.  Clearly the signature
- * exceeds this maximum.
+ * 3 bytes of data and the overall packfile cannot exceed 4 GiB with
+ * version 1 of the index file due to the offsets limited to 32 bits.
+ * Clearly the signature exceeds this maximum.
  *
  * Very old git binaries will also compare the first 4 bytes to the
  * next 4 bytes in the index and abort with a "non-monotonic index"
@@ -43,6 +34,15 @@
  */
 #define PACK_IDX_SIGNATURE 0xff744f63	/* "\377tOc" */
 
+/*
+ * Packed object index header
+ */
+struct pack_idx_header {
+	uint32_t idx_signature;
+	uint32_t idx_version;
+};
+
+
 extern int verify_pack(struct packed_git *, int);
 
 #define PH_ERROR_EOF		(-1)
diff --git a/path.c b/path.c
index c5d25a4..6395cf2 100644
--- a/path.c
+++ b/path.c
@@ -252,7 +252,7 @@
 
 	if (access("objects", X_OK) == 0 && access("refs", X_OK) == 0 &&
 	    validate_headref("HEAD") == 0) {
-		putenv("GIT_DIR=.");
+		setenv("GIT_DIR", ".", 1);
 		check_repository_format();
 		return path;
 	}
diff --git a/peek-remote.c b/peek-remote.c
index ef3c76c..96bfac4 100644
--- a/peek-remote.c
+++ b/peek-remote.c
@@ -35,11 +35,11 @@
 		char *arg = argv[i];
 
 		if (*arg == '-') {
-			if (!strncmp("--upload-pack=", arg, 14)) {
+			if (!prefixcmp(arg, "--upload-pack=")) {
 				uploadpack = arg + 14;
 				continue;
 			}
-			if (!strncmp("--exec=", arg, 7)) {
+			if (!prefixcmp(arg, "--exec=")) {
 				uploadpack = arg + 7;
 				continue;
 			}
diff --git a/perl/Git.pm b/perl/Git.pm
index f2c156c..b5b1cf5 100644
--- a/perl/Git.pm
+++ b/perl/Git.pm
@@ -516,6 +516,36 @@
 }
 
 
+=item config_boolean ( VARIABLE )
+
+Retrieve the boolean configuration C<VARIABLE>.
+
+Must be called on a repository instance.
+
+This currently wraps command('config') so it is not so fast.
+
+=cut
+
+sub config_boolean {
+	my ($self, $var) = @_;
+	$self->repo_path()
+		or throw Error::Simple("not a repository");
+
+	try {
+		return $self->command_oneline('config', '--bool', '--get',
+					      $var);
+	} catch Git::Error::Command with {
+		my $E = shift;
+		if ($E->value() == 1) {
+			# Key not found.
+			return undef;
+		} else {
+			throw $E;
+		}
+	};
+}
+
+
 =item ident ( TYPE | IDENTSTR )
 
 =item ident_person ( TYPE | IDENTSTR | IDENTARRAY )
diff --git a/perl/Makefile b/perl/Makefile
index 099beda..17d004e 100644
--- a/perl/Makefile
+++ b/perl/Makefile
@@ -6,11 +6,15 @@
 PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
 prefix_SQ = $(subst ','\'',$(prefix))
 
+ifndef V
+	QUIET = @
+endif
+
 all install instlibdir: $(makfile)
-	$(MAKE) -f $(makfile) $@
+	$(QUIET)$(MAKE) -f $(makfile) $@
 
 clean:
-	test -f $(makfile) && $(MAKE) -f $(makfile) $@ || exit 0
+	$(QUIET)test -f $(makfile) && $(MAKE) -f $(makfile) $@ || exit 0
 	$(RM) ppport.h
 	$(RM) $(makfile)
 	$(RM) $(makfile).old
diff --git a/reachable.c b/reachable.c
index 01760d7..ff3dd34 100644
--- a/reachable.c
+++ b/reachable.c
@@ -42,8 +42,7 @@
 	me.elem = name;
 	me.elem_len = strlen(name);
 
-	desc.buf = tree->buffer;
-	desc.size = tree->size;
+	init_tree_desc(&desc, tree->buffer, tree->size);
 
 	while (tree_entry(&desc, &entry)) {
 		if (S_ISDIR(entry.mode))
diff --git a/read-cache.c b/read-cache.c
index 605b352..6339a27 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -59,20 +59,20 @@
 
 	if (fd >= 0) {
 		unsigned char sha1[20];
-		if (!index_fd(sha1, fd, st, 0, NULL))
+		if (!index_fd(sha1, fd, st, 0, OBJ_BLOB, ce->name))
 			match = hashcmp(sha1, ce->sha1);
 		/* index_fd() closed the file descriptor already */
 	}
 	return match;
 }
 
-static int ce_compare_link(struct cache_entry *ce, unsigned long expected_size)
+static int ce_compare_link(struct cache_entry *ce, size_t expected_size)
 {
 	int match = -1;
 	char *target;
 	void *buffer;
 	unsigned long size;
-	char type[10];
+	enum object_type type;
 	int len;
 
 	target = xmalloc(expected_size);
@@ -81,7 +81,7 @@
 		free(target);
 		return -1;
 	}
-	buffer = read_sha1_file(ce->sha1, type, &size);
+	buffer = read_sha1_file(ce->sha1, &type, &size);
 	if (!buffer) {
 		free(target);
 		return -1;
@@ -101,7 +101,7 @@
 			return DATA_CHANGED;
 		break;
 	case S_IFLNK:
-		if (ce_compare_link(ce, st->st_size))
+		if (ce_compare_link(ce, xsize_t(st->st_size)))
 			return DATA_CHANGED;
 		break;
 	default:
@@ -125,7 +125,9 @@
 			changed |= MODE_CHANGED;
 		break;
 	case S_IFLNK:
-		changed |= !S_ISLNK(st->st_mode) ? TYPE_CHANGED : 0;
+		if (!S_ISLNK(st->st_mode) &&
+		    (has_symlinks || !S_ISREG(st->st_mode)))
+			changed |= TYPE_CHANGED;
 		break;
 	default:
 		die("internal error: ce_mode is %o", ntohl(ce->ce_mode));
@@ -344,11 +346,11 @@
 	ce->ce_flags = htons(namelen);
 	fill_stat_cache_info(ce, &st);
 
-	if (trust_executable_bit)
+	if (trust_executable_bit && has_symlinks)
 		ce->ce_mode = create_ce_mode(st.st_mode);
 	else {
-		/* If there is an existing entry, pick the mode bits
-		 * from it, otherwise assume unexecutable.
+		/* If there is an existing entry, pick the mode bits and type
+		 * from it, otherwise assume unexecutable regular file.
 		 */
 		struct cache_entry *ent;
 		int pos = cache_name_pos(path, namelen);
@@ -795,7 +797,7 @@
 	}
 
 	if (!fstat(fd, &st)) {
-		cache_mmap_size = st.st_size;
+		cache_mmap_size = xsize_t(st.st_size);
 		errno = EINVAL;
 		if (cache_mmap_size >= sizeof(struct cache_header) + 20)
 			cache_mmap = xmmap(NULL, cache_mmap_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
diff --git a/receive-pack.c b/receive-pack.c
index ea6872e..26aa26b 100644
--- a/receive-pack.c
+++ b/receive-pack.c
@@ -67,18 +67,11 @@
 
 static struct command *commands;
 
-static char update_hook[] = "hooks/update";
+static const char pre_receive_hook[] = "hooks/pre-receive";
+static const char post_receive_hook[] = "hooks/post-receive";
 
-static int run_update_hook(const char *refname,
-			   char *old_hex, char *new_hex)
+static int hook_status(int code, const char *hook_name)
 {
-	int code;
-
-	if (access(update_hook, X_OK) < 0)
-		return 0;
-	code = run_command_opt(RUN_COMMAND_NO_STDIN
-		| RUN_COMMAND_STDOUT_TO_STDERR,
-		update_hook, refname, old_hex, new_hex, NULL);
 	switch (code) {
 	case 0:
 		return 0;
@@ -86,46 +79,105 @@
 		return error("hook fork failed");
 	case -ERR_RUN_COMMAND_EXEC:
 		return error("hook execute failed");
+	case -ERR_RUN_COMMAND_PIPE:
+		return error("hook pipe failed");
 	case -ERR_RUN_COMMAND_WAITPID:
 		return error("waitpid failed");
 	case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
 		return error("waitpid is confused");
 	case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
-		return error("%s died of signal", update_hook);
+		return error("%s died of signal", hook_name);
 	case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
-		return error("%s died strangely", update_hook);
+		return error("%s died strangely", hook_name);
 	default:
-		error("%s exited with error code %d", update_hook, -code);
+		error("%s exited with error code %d", hook_name, -code);
 		return -code;
 	}
 }
 
-static int update(struct command *cmd)
+static int run_hook(const char *hook_name)
+{
+	static char buf[sizeof(commands->old_sha1) * 2 + PATH_MAX + 4];
+	struct command *cmd;
+	struct child_process proc;
+	const char *argv[2];
+	int have_input = 0, code;
+
+	for (cmd = commands; !have_input && cmd; cmd = cmd->next) {
+		if (!cmd->error_string)
+			have_input = 1;
+	}
+
+	if (!have_input || access(hook_name, X_OK) < 0)
+		return 0;
+
+	argv[0] = hook_name;
+	argv[1] = NULL;
+
+	memset(&proc, 0, sizeof(proc));
+	proc.argv = argv;
+	proc.in = -1;
+	proc.stdout_to_stderr = 1;
+
+	code = start_command(&proc);
+	if (code)
+		return hook_status(code, hook_name);
+	for (cmd = commands; cmd; cmd = cmd->next) {
+		if (!cmd->error_string) {
+			size_t n = snprintf(buf, sizeof(buf), "%s %s %s\n",
+				sha1_to_hex(cmd->old_sha1),
+				sha1_to_hex(cmd->new_sha1),
+				cmd->ref_name);
+			if (write_in_full(proc.in, buf, n) != n)
+				break;
+		}
+	}
+	return hook_status(finish_command(&proc), hook_name);
+}
+
+static int run_update_hook(struct command *cmd)
+{
+	static const char update_hook[] = "hooks/update";
+	struct child_process proc;
+	const char *argv[5];
+
+	if (access(update_hook, X_OK) < 0)
+		return 0;
+
+	argv[0] = update_hook;
+	argv[1] = cmd->ref_name;
+	argv[2] = sha1_to_hex(cmd->old_sha1);
+	argv[3] = sha1_to_hex(cmd->new_sha1);
+	argv[4] = NULL;
+
+	memset(&proc, 0, sizeof(proc));
+	proc.argv = argv;
+	proc.no_stdin = 1;
+	proc.stdout_to_stderr = 1;
+
+	return hook_status(run_command(&proc), update_hook);
+}
+
+static const char *update(struct command *cmd)
 {
 	const char *name = cmd->ref_name;
 	unsigned char *old_sha1 = cmd->old_sha1;
 	unsigned char *new_sha1 = cmd->new_sha1;
-	char new_hex[41], old_hex[41];
 	struct ref_lock *lock;
 
-	cmd->error_string = NULL;
-	if (!strncmp(name, "refs/", 5) && check_ref_format(name + 5)) {
-		cmd->error_string = "funny refname";
-		return error("refusing to create funny ref '%s' locally",
-			     name);
+	if (!prefixcmp(name, "refs/") && check_ref_format(name + 5)) {
+		error("refusing to create funny ref '%s' locally", name);
+		return "funny refname";
 	}
 
-	strcpy(new_hex, sha1_to_hex(new_sha1));
-	strcpy(old_hex, sha1_to_hex(old_sha1));
-
 	if (!is_null_sha1(new_sha1) && !has_sha1_file(new_sha1)) {
-		cmd->error_string = "bad pack";
-		return error("unpack should have generated %s, "
-			     "but I can't find it!", new_hex);
+		error("unpack should have generated %s, "
+		      "but I can't find it!", sha1_to_hex(new_sha1));
+		return "bad pack";
 	}
 	if (deny_non_fast_forwards && !is_null_sha1(new_sha1) &&
 	    !is_null_sha1(old_sha1) &&
-	    !strncmp(name, "refs/heads/", 11)) {
+	    !prefixcmp(name, "refs/heads/")) {
 		struct commit *old_commit, *new_commit;
 		struct commit_list *bases, *ent;
 
@@ -136,35 +188,39 @@
 			if (!hashcmp(old_sha1, ent->item->object.sha1))
 				break;
 		free_commit_list(bases);
-		if (!ent)
-			return error("denying non-fast forward;"
-				     " you should pull first");
+		if (!ent) {
+			error("denying non-fast forward %s"
+			      " (you should pull first)", name);
+			return "non-fast forward";
+		}
 	}
-	if (run_update_hook(name, old_hex, new_hex)) {
-		cmd->error_string = "hook declined";
-		return error("hook declined to update %s", name);
+	if (run_update_hook(cmd)) {
+		error("hook declined to update %s", name);
+		return "hook declined";
 	}
 
 	if (is_null_sha1(new_sha1)) {
 		if (delete_ref(name, old_sha1)) {
-			cmd->error_string = "failed to delete";
-			return error("failed to delete %s", name);
+			error("failed to delete %s", name);
+			return "failed to delete";
 		}
-		fprintf(stderr, "%s: %s -> deleted\n", name, old_hex);
+		fprintf(stderr, "%s: %s -> deleted\n", name,
+			sha1_to_hex(old_sha1));
+		return NULL; /* good */
 	}
 	else {
 		lock = lock_any_ref_for_update(name, old_sha1);
 		if (!lock) {
-			cmd->error_string = "failed to lock";
-			return error("failed to lock %s", name);
+			error("failed to lock %s", name);
+			return "failed to lock";
 		}
 		if (write_ref_sha1(lock, new_sha1, "push")) {
-			cmd->error_string = "failed to write";
-			return -1; /* error() already called */
+			return "failed to write"; /* error() already called */
 		}
-		fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex);
+		fprintf(stderr, "%s: %s -> %s\n", name,
+			sha1_to_hex(old_sha1), sha1_to_hex(new_sha1));
+		return NULL; /* good */
 	}
-	return 0;
 }
 
 static char update_post_hook[] = "hooks/post-update";
@@ -175,14 +231,14 @@
 	int argc;
 	const char **argv;
 
-	if (access(update_post_hook, X_OK) < 0)
-		return;
-	for (argc = 1, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) {
+	for (argc = 0, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) {
 		if (cmd_p->error_string)
 			continue;
 		argc++;
 	}
-	argv = xmalloc(sizeof(*argv) * (1 + argc));
+	if (!argc || access(update_post_hook, X_OK) < 0)
+		return;
+	argv = xmalloc(sizeof(*argv) * (2 + argc));
 	argv[0] = update_post_hook;
 
 	for (argc = 1, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) {
@@ -199,19 +255,30 @@
 		| RUN_COMMAND_STDOUT_TO_STDERR);
 }
 
-/*
- * This gets called after(if) we've successfully
- * unpacked the data payload.
- */
-static void execute_commands(void)
+static void execute_commands(const char *unpacker_error)
 {
 	struct command *cmd = commands;
 
+	if (unpacker_error) {
+		while (cmd) {
+			cmd->error_string = "n/a (unpacker error)";
+			cmd = cmd->next;
+		}
+		return;
+	}
+
+	if (run_hook(pre_receive_hook)) {
+		while (cmd) {
+			cmd->error_string = "pre-receive hook declined";
+			cmd = cmd->next;
+		}
+		return;
+	}
+
 	while (cmd) {
-		update(cmd);
+		cmd->error_string = update(cmd);
 		cmd = cmd->next;
 	}
-	run_update_post_hook(commands);
 }
 
 static void read_head_info(void)
@@ -247,7 +314,7 @@
 		hashcpy(cmd->old_sha1, old_sha1);
 		hashcpy(cmd->new_sha1, new_sha1);
 		memcpy(cmd->ref_name, line + 82, len - 81);
-		cmd->error_string = "n/a (unpacker error)";
+		cmd->error_string = NULL;
 		cmd->next = NULL;
 		*p = cmd;
 		p = &cmd->next;
@@ -315,10 +382,10 @@
 		}
 	} else {
 		const char *keeper[6];
-		int fd[2], s, len, status;
-		pid_t pid;
+		int s, len, status;
 		char keep_arg[256];
 		char packname[46];
+		struct child_process ip;
 
 		s = sprintf(keep_arg, "--keep=receive-pack %i on ", getpid());
 		if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
@@ -330,20 +397,12 @@
 		keeper[3] = hdr_arg;
 		keeper[4] = keep_arg;
 		keeper[5] = NULL;
-
-		if (pipe(fd) < 0)
-			return "index-pack pipe failed";
-		pid = fork();
-		if (pid < 0)
+		memset(&ip, 0, sizeof(ip));
+		ip.argv = keeper;
+		ip.out = -1;
+		ip.git_cmd = 1;
+		if (start_command(&ip))
 			return "index-pack fork failed";
-		if (!pid) {
-			dup2(fd[1], 1);
-			close(fd[1]);
-			close(fd[0]);
-			execv_git_cmd(keeper);
-			die("execv of index-pack failed");
-		}
-		close(fd[1]);
 
 		/*
 		 * The first thing we expects from index-pack's output
@@ -353,9 +412,8 @@
 		 * later on.  If we don't get that then tough luck with it.
 		 */
 		for (len = 0;
-		     len < 46 && (s = xread(fd[0], packname+len, 46-len)) > 0;
+		     len < 46 && (s = xread(ip.out, packname+len, 46-len)) > 0;
 		     len += s);
-		close(fd[0]);
 		if (len == 46 && packname[45] == '\n' &&
 		    memcmp(packname, "keep\t", 5) == 0) {
 			char path[PATH_MAX];
@@ -365,14 +423,8 @@
 			pack_lockfile = xstrdup(path);
 		}
 
-		/* Then wrap our index-pack process. */
-		while (waitpid(pid, &status, 0) < 0)
-			if (errno != EINTR)
-				return "waitpid failed";
-		if (WIFEXITED(status)) {
-			int code = WEXITSTATUS(status);
-			if (code)
-				return "index-pack exited with error code";
+		status = finish_command(&ip);
+		if (!status) {
 			reprepare_packed_git();
 			return NULL;
 		}
@@ -450,12 +502,13 @@
 
 		if (!delete_only(commands))
 			unpack_status = unpack();
-		if (!unpack_status)
-			execute_commands();
+		execute_commands(unpack_status);
 		if (pack_lockfile)
 			unlink(pack_lockfile);
 		if (report_status)
 			report(unpack_status);
+		run_hook(post_receive_hook);
+		run_update_post_hook(commands);
 	}
 	return 0;
 }
diff --git a/refs.c b/refs.c
index 131e870..f471152 100644
--- a/refs.c
+++ b/refs.c
@@ -828,8 +828,8 @@
 		goto rollback;
 	}
 
-	if (!strncmp(oldref, "refs/heads/", 11) &&
-			!strncmp(newref, "refs/heads/", 11)) {
+	if (!prefixcmp(oldref, "refs/heads/") &&
+			!prefixcmp(newref, "refs/heads/")) {
 		char oldsection[1024], newsection[1024];
 
 		snprintf(oldsection, 1024, "branch.%s", oldref + 11);
@@ -894,8 +894,8 @@
 	log_file = git_path("logs/%s", ref_name);
 
 	if (log_all_ref_updates &&
-	    (!strncmp(ref_name, "refs/heads/", 11) ||
-	     !strncmp(ref_name, "refs/remotes/", 13) ||
+	    (!prefixcmp(ref_name, "refs/heads/") ||
+	     !prefixcmp(ref_name, "refs/remotes/") ||
 	     !strcmp(ref_name, "HEAD"))) {
 		if (safe_create_leading_directories(log_file) < 0)
 			return error("unable to create directory for %s",
@@ -980,6 +980,27 @@
 		unlock_ref(lock);
 		return -1;
 	}
+	if (strcmp(lock->orig_ref_name, "HEAD") != 0) {
+		/*
+		 * Special hack: If a branch is updated directly and HEAD
+		 * points to it (may happen on the remote side of a push
+		 * for example) then logically the HEAD reflog should be
+		 * updated too.
+		 * A generic solution implies reverse symref information,
+		 * but finding all symrefs pointing to the given branch
+		 * would be rather costly for this rare event (the direct
+		 * update of a branch) to be worth it.  So let's cheat and
+		 * check with HEAD only which should cover 99% of all usage
+		 * scenarios (even 100% of the default ones).
+		 */
+		unsigned char head_sha1[20];
+		int head_flag;
+		const char *head_ref;
+		head_ref = resolve_ref("HEAD", head_sha1, 1, &head_flag);
+		if (head_ref && (head_flag & REF_ISSYMREF) &&
+		    !strcmp(head_ref, lock->ref_name))
+			log_ref_write("HEAD", lock->old_sha1, sha1, logmsg);
+	}
 	if (commit_lock_file(lock->lk)) {
 		error("Couldn't set %s", lock->ref_name);
 		unlock_ref(lock);
@@ -1077,6 +1098,7 @@
 	unsigned long date;
 	unsigned char logged_sha1[20];
 	void *log_mapped;
+	size_t mapsz;
 
 	logfile = git_path("logs/%s", ref);
 	logfd = open(logfile, O_RDONLY, 0);
@@ -1085,7 +1107,8 @@
 	fstat(logfd, &st);
 	if (!st.st_size)
 		die("Log %s is empty.", logfile);
-	log_mapped = xmmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, logfd, 0);
+	mapsz = xsize_t(st.st_size);
+	log_mapped = xmmap(NULL, mapsz, PROT_READ, MAP_PRIVATE, logfd, 0);
 	logdata = log_mapped;
 	close(logfd);
 
@@ -1138,7 +1161,7 @@
 						logfile, show_rfc2822_date(date, tz));
 				}
 			}
-			munmap(log_mapped, st.st_size);
+			munmap(log_mapped, mapsz);
 			return 0;
 		}
 		lastrec = rec;
@@ -1157,7 +1180,7 @@
 		die("Log %s is corrupt.", logfile);
 	if (msg)
 		*msg = ref_msg(logdata, logend);
-	munmap(log_mapped, st.st_size);
+	munmap(log_mapped, mapsz);
 
 	if (cutoff_time)
 		*cutoff_time = date;
diff --git a/revision.c b/revision.c
index 76499dc..486393c 100644
--- a/revision.c
+++ b/revision.c
@@ -62,8 +62,7 @@
 	if (parse_tree(tree) < 0)
 		die("bad tree %s", sha1_to_hex(obj->sha1));
 
-	desc.buf = tree->buffer;
-	desc.size = tree->size;
+	init_tree_desc(&desc, tree->buffer, tree->size);
 	while (tree_entry(&desc, &entry)) {
 		if (S_ISDIR(entry.mode))
 			mark_tree_uninteresting(lookup_tree(entry.sha1));
@@ -213,6 +212,13 @@
 	return 1;
 }
 
+/*
+ * The goal is to get REV_TREE_NEW as the result only if the
+ * diff consists of all '+' (and no other changes), and
+ * REV_TREE_DIFFERENT otherwise (of course if the trees are
+ * the same we want REV_TREE_SAME).  That means that once we
+ * get to REV_TREE_DIFFERENT, we do not have to look any further.
+ */
 static int tree_difference = REV_TREE_SAME;
 
 static void file_add_remove(struct diff_options *options,
@@ -236,6 +242,8 @@
 		diff = REV_TREE_NEW;
 	}
 	tree_difference = diff;
+	if (tree_difference == REV_TREE_DIFFERENT)
+		options->has_changes = 1;
 }
 
 static void file_change(struct diff_options *options,
@@ -245,6 +253,7 @@
 		 const char *base, const char *path)
 {
 	tree_difference = REV_TREE_DIFFERENT;
+	options->has_changes = 1;
 }
 
 int rev_compare_tree(struct rev_info *revs, struct tree *t1, struct tree *t2)
@@ -254,6 +263,7 @@
 	if (!t2)
 		return REV_TREE_DIFFERENT;
 	tree_difference = REV_TREE_SAME;
+	revs->pruning.has_changes = 0;
 	if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "",
 			   &revs->pruning) < 0)
 		return REV_TREE_DIFFERENT;
@@ -264,24 +274,24 @@
 {
 	int retval;
 	void *tree;
+	unsigned long size;
 	struct tree_desc empty, real;
 
 	if (!t1)
 		return 0;
 
-	tree = read_object_with_reference(t1->object.sha1, tree_type, &real.size, NULL);
+	tree = read_object_with_reference(t1->object.sha1, tree_type, &size, NULL);
 	if (!tree)
 		return 0;
-	real.buf = tree;
+	init_tree_desc(&real, tree, size);
+	init_tree_desc(&empty, "", 0);
 
-	empty.buf = "";
-	empty.size = 0;
-
-	tree_difference = 0;
+	tree_difference = REV_TREE_SAME;
+	revs->pruning.has_changes = 0;
 	retval = diff_tree(&empty, &real, "", &revs->pruning);
 	free(tree);
 
-	return retval >= 0 && !tree_difference;
+	return retval >= 0 && (tree_difference == REV_TREE_SAME);
 }
 
 static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
@@ -350,6 +360,7 @@
 {
 	struct commit_list *parent = commit->parents;
 	unsigned left_flag;
+	int add, rest;
 
 	if (commit->object.flags & ADDED)
 		return;
@@ -395,18 +406,19 @@
 		return;
 
 	left_flag = (commit->object.flags & SYMMETRIC_LEFT);
-	parent = commit->parents;
-	while (parent) {
+
+	rest = !revs->first_parent_only;
+	for (parent = commit->parents, add = 1; parent; add = rest) {
 		struct commit *p = parent->item;
 
 		parent = parent->next;
-
 		parse_commit(p);
 		p->object.flags |= left_flag;
 		if (p->object.flags & SEEN)
 			continue;
 		p->object.flags |= SEEN;
-		insert_by_date(p, list);
+		if (add)
+			insert_by_date(p, list);
 	}
 }
 
@@ -437,36 +449,6 @@
 			continue;
 		p = &commit_list_insert(commit, p)->next;
 	}
-	if (revs->boundary) {
-		/* mark the ones that are on the result list first */
-		for (list = newlist; list; list = list->next) {
-			struct commit *commit = list->item;
-			commit->object.flags |= TMP_MARK;
-		}
-		for (list = newlist; list; list = list->next) {
-			struct commit *commit = list->item;
-			struct object *obj = &commit->object;
-			struct commit_list *parent;
-			if (obj->flags & UNINTERESTING)
-				continue;
-			for (parent = commit->parents;
-			     parent;
-			     parent = parent->next) {
-				struct commit *pcommit = parent->item;
-				if (!(pcommit->object.flags & UNINTERESTING))
-					continue;
-				pcommit->object.flags |= BOUNDARY;
-				if (pcommit->object.flags & TMP_MARK)
-					continue;
-				pcommit->object.flags |= TMP_MARK;
-				p = &commit_list_insert(pcommit, p)->next;
-			}
-		}
-		for (list = newlist; list; list = list->next) {
-			struct commit *commit = list->item;
-			commit->object.flags &= ~TMP_MARK;
-		}
-	}
 	revs->commits = newlist;
 }
 
@@ -482,7 +464,7 @@
 	struct all_refs_cb *cb = cb_data;
 	struct object *object = get_reference(cb->all_revs, path, sha1,
 					      cb->all_flags);
-	add_pending_object(cb->all_revs, object, "");
+	add_pending_object(cb->all_revs, object, path);
 	return 0;
 }
 
@@ -504,7 +486,7 @@
 			add_pending_object(cb->all_revs, o, "");
 		}
 		else if (!cb->warned_bad_reflog) {
-			warn("reflog of '%s' references pruned commits",
+			warning("reflog of '%s' references pruned commits",
 				cb->name_for_errormsg);
 			cb->warned_bad_reflog = 1;
 		}
@@ -575,6 +557,7 @@
 	revs->ignore_merges = 1;
 	revs->simplify_history = 1;
 	revs->pruning.recursive = 1;
+	revs->pruning.quiet = 1;
 	revs->pruning.add_remove = file_add_remove;
 	revs->pruning.change = file_change;
 	revs->lifo = 1;
@@ -815,11 +798,11 @@
 		const char *arg = argv[i];
 		if (*arg == '-') {
 			int opts;
-			if (!strncmp(arg, "--max-count=", 12)) {
+			if (!prefixcmp(arg, "--max-count=")) {
 				revs->max_count = atoi(arg + 12);
 				continue;
 			}
-			if (!strncmp(arg, "--skip=", 7)) {
+			if (!prefixcmp(arg, "--skip=")) {
 				revs->skip_count = atoi(arg + 7);
 				continue;
 			}
@@ -834,31 +817,31 @@
 				revs->max_count = atoi(argv[++i]);
 				continue;
 			}
-			if (!strncmp(arg,"-n",2)) {
+			if (!prefixcmp(arg, "-n")) {
 				revs->max_count = atoi(arg + 2);
 				continue;
 			}
-			if (!strncmp(arg, "--max-age=", 10)) {
+			if (!prefixcmp(arg, "--max-age=")) {
 				revs->max_age = atoi(arg + 10);
 				continue;
 			}
-			if (!strncmp(arg, "--since=", 8)) {
+			if (!prefixcmp(arg, "--since=")) {
 				revs->max_age = approxidate(arg + 8);
 				continue;
 			}
-			if (!strncmp(arg, "--after=", 8)) {
+			if (!prefixcmp(arg, "--after=")) {
 				revs->max_age = approxidate(arg + 8);
 				continue;
 			}
-			if (!strncmp(arg, "--min-age=", 10)) {
+			if (!prefixcmp(arg, "--min-age=")) {
 				revs->min_age = atoi(arg + 10);
 				continue;
 			}
-			if (!strncmp(arg, "--before=", 9)) {
+			if (!prefixcmp(arg, "--before=")) {
 				revs->min_age = approxidate(arg + 9);
 				continue;
 			}
-			if (!strncmp(arg, "--until=", 8)) {
+			if (!prefixcmp(arg, "--until=")) {
 				revs->min_age = approxidate(arg + 8);
 				continue;
 			}
@@ -866,6 +849,10 @@
 				handle_all(revs, flags);
 				continue;
 			}
+			if (!strcmp(arg, "--first-parent")) {
+				revs->first_parent_only = 1;
+				continue;
+			}
 			if (!strcmp(arg, "--reflog")) {
 				handle_reflog(revs, flags);
 				continue;
@@ -946,7 +933,7 @@
 				revs->num_ignore_packed = 0;
 				continue;
 			}
-			if (!strncmp(arg, "--unpacked=", 11)) {
+			if (!prefixcmp(arg, "--unpacked=")) {
 				revs->unpacked = 1;
 				add_ignore_packed(revs, arg+11);
 				continue;
@@ -982,7 +969,7 @@
 				revs->verbose_header = 1;
 				continue;
 			}
-			if (!strncmp(arg, "--pretty", 8)) {
+			if (!prefixcmp(arg, "--pretty")) {
 				revs->verbose_header = 1;
 				revs->commit_format = get_commit_format(arg+8);
 				continue;
@@ -1007,7 +994,7 @@
 				revs->abbrev = DEFAULT_ABBREV;
 				continue;
 			}
-			if (!strncmp(arg, "--abbrev=", 9)) {
+			if (!prefixcmp(arg, "--abbrev=")) {
 				revs->abbrev = strtoul(arg + 9, NULL, 10);
 				if (revs->abbrev < MINIMUM_ABBREV)
 					revs->abbrev = MINIMUM_ABBREV;
@@ -1036,15 +1023,15 @@
 			/*
 			 * Grepping the commit log
 			 */
-			if (!strncmp(arg, "--author=", 9)) {
+			if (!prefixcmp(arg, "--author=")) {
 				add_header_grep(revs, "author", arg+9);
 				continue;
 			}
-			if (!strncmp(arg, "--committer=", 12)) {
+			if (!prefixcmp(arg, "--committer=")) {
 				add_header_grep(revs, "committer", arg+12);
 				continue;
 			}
-			if (!strncmp(arg, "--grep=", 7)) {
+			if (!prefixcmp(arg, "--grep=")) {
 				add_message_grep(revs, arg+7);
 				continue;
 			}
@@ -1052,14 +1039,18 @@
 				all_match = 1;
 				continue;
 			}
-			if (!strncmp(arg, "--encoding=", 11)) {
+			if (!prefixcmp(arg, "--encoding=")) {
 				arg += 11;
 				if (strcmp(arg, "none"))
-					git_log_output_encoding = strdup(arg);
+					git_log_output_encoding = xstrdup(arg);
 				else
 					git_log_output_encoding = "";
 				continue;
 			}
+			if (!strcmp(arg, "--reverse")) {
+				revs->reverse ^= 1;
+				continue;
+			}
 
 			opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i);
 			if (opts > 0) {
@@ -1189,17 +1180,6 @@
 	}
 }
 
-static void mark_boundary_to_show(struct commit *commit)
-{
-	struct commit_list *p = commit->parents;
-	while (p) {
-		commit = p->item;
-		p = p->next;
-		if (commit->object.flags & BOUNDARY)
-			commit->object.flags |= BOUNDARY_SHOW;
-	}
-}
-
 static int commit_match(struct commit *commit, struct rev_info *opt)
 {
 	if (!opt->grep_filter)
@@ -1242,18 +1222,6 @@
 						    revs->ignore_packed))
 		    continue;
 
-		/* We want to show boundary commits only when their
-		 * children are shown.  When path-limiter is in effect,
-		 * rewrite_parents() drops some commits from getting shown,
-		 * and there is no point showing boundary parents that
-		 * are not shown.  After rewrite_parents() rewrites the
-		 * parents of a commit that is shown, we mark the boundary
-		 * parents with BOUNDARY_SHOW.
-		 */
-		if (commit->object.flags & BOUNDARY_SHOW) {
-			commit->object.flags |= SHOWN;
-			return commit;
-		}
 		if (commit->object.flags & UNINTERESTING)
 			continue;
 		if (revs->min_age != -1 && (commit->date > revs->min_age))
@@ -1276,35 +1244,136 @@
 			if (revs->parents)
 				rewrite_parents(revs, commit);
 		}
-		if (revs->boundary)
-			mark_boundary_to_show(commit);
-		commit->object.flags |= SHOWN;
 		return commit;
 	} while (revs->commits);
 	return NULL;
 }
 
+static void gc_boundary(struct object_array *array)
+{
+	unsigned nr = array->nr;
+	unsigned alloc = array->alloc;
+	struct object_array_entry *objects = array->objects;
+
+	if (alloc <= nr) {
+		unsigned i, j;
+		for (i = j = 0; i < nr; i++) {
+			if (objects[i].item->flags & SHOWN)
+				continue;
+			if (i != j)
+				objects[j] = objects[i];
+			j++;
+		}
+		for (i = j; i < nr; i++)
+			objects[i].item = NULL;
+		array->nr = j;
+	}
+}
+
 struct commit *get_revision(struct rev_info *revs)
 {
 	struct commit *c = NULL;
+	struct commit_list *l;
 
-	if (0 < revs->skip_count) {
-		while ((c = get_revision_1(revs)) != NULL) {
-			if (revs->skip_count-- <= 0)
+	if (revs->boundary == 2) {
+		unsigned i;
+		struct object_array *array = &revs->boundary_commits;
+		struct object_array_entry *objects = array->objects;
+		for (i = 0; i < array->nr; i++) {
+			c = (struct commit *)(objects[i].item);
+			if (!c)
+				continue;
+			if (!(c->object.flags & CHILD_SHOWN))
+				continue;
+			if (!(c->object.flags & SHOWN))
+				break;
+		}
+		if (array->nr <= i)
+			return NULL;
+
+		c->object.flags |= SHOWN | BOUNDARY;
+		return c;
+	}
+
+	if (revs->reverse) {
+		int limit = -1;
+
+		if (0 <= revs->max_count) {
+			limit = revs->max_count;
+			if (0 < revs->skip_count)
+				limit += revs->skip_count;
+		}
+		l = NULL;
+		while ((c = get_revision_1(revs))) {
+			commit_list_insert(c, &l);
+			if ((0 < limit) && !--limit)
+				break;
+		}
+		revs->commits = l;
+		revs->reverse = 0;
+		revs->max_count = -1;
+		c = NULL;
+	}
+
+	/*
+	 * Now pick up what they want to give us
+	 */
+	c = get_revision_1(revs);
+	if (c) {
+		while (0 < revs->skip_count) {
+			revs->skip_count--;
+			c = get_revision_1(revs);
+			if (!c)
 				break;
 		}
 	}
 
-	/* Check the max_count ... */
+	/*
+	 * Check the max_count.
+	 */
 	switch (revs->max_count) {
 	case -1:
 		break;
 	case 0:
-		return NULL;
+		c = NULL;
+		break;
 	default:
 		revs->max_count--;
 	}
+
 	if (c)
+		c->object.flags |= SHOWN;
+
+	if (!revs->boundary) {
 		return c;
-	return get_revision_1(revs);
+	}
+
+	if (!c) {
+		/*
+		 * get_revision_1() runs out the commits, and
+		 * we are done computing the boundaries.
+		 * switch to boundary commits output mode.
+		 */
+		revs->boundary = 2;
+		return get_revision(revs);
+	}
+
+	/*
+	 * boundary commits are the commits that are parents of the
+	 * ones we got from get_revision_1() but they themselves are
+	 * not returned from get_revision_1().  Before returning
+	 * 'c', we need to mark its parents that they could be boundaries.
+	 */
+
+	for (l = c->parents; l; l = l->next) {
+		struct object *p;
+		p = &(l->item->object);
+		if (p->flags & (CHILD_SHOWN | SHOWN))
+			continue;
+		p->flags |= CHILD_SHOWN;
+		gc_boundary(&revs->boundary_commits);
+		add_object_array(p, NULL, &revs->boundary_commits);
+	}
+
+	return c;
 }
diff --git a/revision.h b/revision.h
index d93481f..55e6b53 100644
--- a/revision.h
+++ b/revision.h
@@ -7,7 +7,7 @@
 #define SHOWN		(1u<<3)
 #define TMP_MARK	(1u<<4) /* for isolated cases; clean after use */
 #define BOUNDARY	(1u<<5)
-#define BOUNDARY_SHOW	(1u<<6)
+#define CHILD_SHOWN	(1u<<6)
 #define ADDED		(1u<<7)	/* Parents already parsed and added? */
 #define SYMMETRIC_LEFT	(1u<<8)
 
@@ -21,6 +21,9 @@
 	struct commit_list *commits;
 	struct object_array pending;
 
+	/* Parents of shown commits */
+	struct object_array boundary_commits;
+
 	/* Basic information */
 	const char *prefix;
 	void *prune_data;
@@ -40,9 +43,11 @@
 			edge_hint:1,
 			limited:1,
 			unpacked:1, /* see also ignore_packed below */
-			boundary:1,
+			boundary:2,
 			left_right:1,
-			parents:1;
+			parents:1,
+			reverse:1,
+			first_parent_only:1;
 
 	/* Diff flags */
 	unsigned int	diff:1,
@@ -73,6 +78,7 @@
 	const char	*add_signoff;
 	const char	*extra_headers;
 	const char	*log_reencode;
+	int		no_inline;
 
 	/* Filter by commit log message */
 	struct grep_opt	*grep_filter;
diff --git a/run-command.c b/run-command.c
index cfbad74..eff523e 100644
--- a/run-command.c
+++ b/run-command.c
@@ -2,30 +2,108 @@
 #include "run-command.h"
 #include "exec_cmd.h"
 
-int run_command_v_opt(const char **argv, int flags)
+static inline void close_pair(int fd[2])
 {
-	pid_t pid = fork();
+	close(fd[0]);
+	close(fd[1]);
+}
 
-	if (pid < 0)
-		return -ERR_RUN_COMMAND_FORK;
-	if (!pid) {
-		if (flags & RUN_COMMAND_NO_STDIN) {
-			int fd = open("/dev/null", O_RDWR);
-			dup2(fd, 0);
-			close(fd);
-		}
-		if (flags & RUN_COMMAND_STDOUT_TO_STDERR)
-			dup2(2, 1);
-		if (flags & RUN_GIT_CMD) {
-			execv_git_cmd(argv);
-		} else {
-			execvp(argv[0], (char *const*) argv);
-		}
-		die("exec %s failed.", argv[0]);
+static inline void dup_devnull(int to)
+{
+	int fd = open("/dev/null", O_RDWR);
+	dup2(fd, to);
+	close(fd);
+}
+
+int start_command(struct child_process *cmd)
+{
+	int need_in, need_out;
+	int fdin[2], fdout[2];
+
+	need_in = !cmd->no_stdin && cmd->in < 0;
+	if (need_in) {
+		if (pipe(fdin) < 0)
+			return -ERR_RUN_COMMAND_PIPE;
+		cmd->in = fdin[1];
+		cmd->close_in = 1;
 	}
+
+	need_out = !cmd->no_stdout
+		&& !cmd->stdout_to_stderr
+		&& cmd->out < 0;
+	if (need_out) {
+		if (pipe(fdout) < 0) {
+			if (need_in)
+				close_pair(fdin);
+			return -ERR_RUN_COMMAND_PIPE;
+		}
+		cmd->out = fdout[0];
+		cmd->close_out = 1;
+	}
+
+	cmd->pid = fork();
+	if (cmd->pid < 0) {
+		if (need_in)
+			close_pair(fdin);
+		if (need_out)
+			close_pair(fdout);
+		return -ERR_RUN_COMMAND_FORK;
+	}
+
+	if (!cmd->pid) {
+		if (cmd->no_stdin)
+			dup_devnull(0);
+		else if (need_in) {
+			dup2(fdin[0], 0);
+			close_pair(fdin);
+		} else if (cmd->in) {
+			dup2(cmd->in, 0);
+			close(cmd->in);
+		}
+
+		if (cmd->no_stdout)
+			dup_devnull(1);
+		else if (cmd->stdout_to_stderr)
+			dup2(2, 1);
+		else if (need_out) {
+			dup2(fdout[1], 1);
+			close_pair(fdout);
+		} else if (cmd->out > 1) {
+			dup2(cmd->out, 1);
+			close(cmd->out);
+		}
+
+		if (cmd->git_cmd) {
+			execv_git_cmd(cmd->argv);
+		} else {
+			execvp(cmd->argv[0], (char *const*) cmd->argv);
+		}
+		die("exec %s failed.", cmd->argv[0]);
+	}
+
+	if (need_in)
+		close(fdin[0]);
+	else if (cmd->in)
+		close(cmd->in);
+
+	if (need_out)
+		close(fdout[1]);
+	else if (cmd->out > 1)
+		close(cmd->out);
+
+	return 0;
+}
+
+int finish_command(struct child_process *cmd)
+{
+	if (cmd->close_in)
+		close(cmd->in);
+	if (cmd->close_out)
+		close(cmd->out);
+
 	for (;;) {
 		int status, code;
-		pid_t waiting = waitpid(pid, &status, 0);
+		pid_t waiting = waitpid(cmd->pid, &status, 0);
 
 		if (waiting < 0) {
 			if (errno == EINTR)
@@ -33,7 +111,7 @@
 			error("waitpid failed (%s)", strerror(errno));
 			return -ERR_RUN_COMMAND_WAITPID;
 		}
-		if (waiting != pid)
+		if (waiting != cmd->pid)
 			return -ERR_RUN_COMMAND_WAITPID_WRONG_PID;
 		if (WIFSIGNALED(status))
 			return -ERR_RUN_COMMAND_WAITPID_SIGNAL;
@@ -47,47 +125,21 @@
 	}
 }
 
-int run_command_v(const char **argv)
+int run_command(struct child_process *cmd)
 {
-	return run_command_v_opt(argv, 0);
+	int code = start_command(cmd);
+	if (code)
+		return code;
+	return finish_command(cmd);
 }
 
-static int run_command_va_opt(int opt, const char *cmd, va_list param)
+int run_command_v_opt(const char **argv, int opt)
 {
-	int argc;
-	const char *argv[MAX_RUN_COMMAND_ARGS];
-	const char *arg;
-
-	argv[0] = (char*) cmd;
-	argc = 1;
-	while (argc < MAX_RUN_COMMAND_ARGS) {
-		arg = argv[argc++] = va_arg(param, char *);
-		if (!arg)
-			break;
-	}
-	if (MAX_RUN_COMMAND_ARGS <= argc)
-		return error("too many args to run %s", cmd);
-	return run_command_v_opt(argv, opt);
-}
-
-int run_command_opt(int opt, const char *cmd, ...)
-{
-	va_list params;
-	int r;
-
-	va_start(params, cmd);
-	r = run_command_va_opt(opt, cmd, params);
-	va_end(params);
-	return r;
-}
-
-int run_command(const char *cmd, ...)
-{
-	va_list params;
-	int r;
-
-	va_start(params, cmd);
-	r = run_command_va_opt(0, cmd, params);
-	va_end(params);
-	return r;
+	struct child_process cmd;
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.argv = argv;
+	cmd.no_stdin = opt & RUN_COMMAND_NO_STDIN ? 1 : 0;
+	cmd.git_cmd = opt & RUN_GIT_CMD ? 1 : 0;
+	cmd.stdout_to_stderr = opt & RUN_COMMAND_STDOUT_TO_STDERR ? 1 : 0;
+	return run_command(&cmd);
 }
diff --git a/run-command.h b/run-command.h
index 59c4476..3680ef9 100644
--- a/run-command.h
+++ b/run-command.h
@@ -1,22 +1,36 @@
 #ifndef RUN_COMMAND_H
 #define RUN_COMMAND_H
 
-#define MAX_RUN_COMMAND_ARGS 256
 enum {
 	ERR_RUN_COMMAND_FORK = 10000,
 	ERR_RUN_COMMAND_EXEC,
+	ERR_RUN_COMMAND_PIPE,
 	ERR_RUN_COMMAND_WAITPID,
 	ERR_RUN_COMMAND_WAITPID_WRONG_PID,
 	ERR_RUN_COMMAND_WAITPID_SIGNAL,
 	ERR_RUN_COMMAND_WAITPID_NOEXIT,
 };
 
+struct child_process {
+	const char **argv;
+	pid_t pid;
+	int in;
+	int out;
+	unsigned close_in:1;
+	unsigned close_out:1;
+	unsigned no_stdin:1;
+	unsigned no_stdout:1;
+	unsigned git_cmd:1; /* if this is to be git sub-command */
+	unsigned stdout_to_stderr:1;
+};
+
+int start_command(struct child_process *);
+int finish_command(struct child_process *);
+int run_command(struct child_process *);
+
 #define RUN_COMMAND_NO_STDIN 1
 #define RUN_GIT_CMD	     2	/*If this is to be git sub-command */
 #define RUN_COMMAND_STDOUT_TO_STDERR 4
 int run_command_v_opt(const char **argv, int opt);
-int run_command_v(const char **argv);
-int run_command_opt(int opt, const char *cmd, ...);
-int run_command(const char *cmd, ...);
 
 #endif
diff --git a/send-pack.c b/send-pack.c
index 33e69db..d5b5162 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -3,7 +3,7 @@
 #include "tag.h"
 #include "refs.h"
 #include "pkt-line.h"
-#include "exec_cmd.h"
+#include "run-command.h"
 
 static const char send_pack_usage[] =
 "git-send-pack [--all] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
@@ -19,46 +19,35 @@
  */
 static int pack_objects(int fd, struct ref *refs)
 {
-	int pipe_fd[2];
-	pid_t pid;
+	/*
+	 * The child becomes pack-objects --revs; we feed
+	 * the revision parameters to it via its stdin and
+	 * let its stdout go back to the other end.
+	 */
+	const char *args[] = {
+		"pack-objects",
+		"--all-progress",
+		"--revs",
+		"--stdout",
+		NULL,
+		NULL,
+	};
+	struct child_process po;
 
-	if (pipe(pipe_fd) < 0)
-		return error("send-pack: pipe failed");
-	pid = fork();
-	if (pid < 0)
-		return error("send-pack: unable to fork git-pack-objects");
-	if (!pid) {
-		/*
-		 * The child becomes pack-objects --revs; we feed
-		 * the revision parameters to it via its stdin and
-		 * let its stdout go back to the other end.
-		 */
-		static const char *args[] = {
-			"pack-objects",
-			"--all-progress",
-			"--revs",
-			"--stdout",
-			NULL,
-			NULL,
-		};
-		if (use_thin_pack)
-			args[4] = "--thin";
-		dup2(pipe_fd[0], 0);
-		dup2(fd, 1);
-		close(pipe_fd[0]);
-		close(pipe_fd[1]);
-		close(fd);
-		execv_git_cmd(args);
-		die("git-pack-objects exec failed (%s)", strerror(errno));
-	}
+	if (use_thin_pack)
+		args[4] = "--thin";
+	memset(&po, 0, sizeof(po));
+	po.argv = args;
+	po.in = -1;
+	po.out = fd;
+	po.git_cmd = 1;
+	if (start_command(&po))
+		die("git-pack-objects failed (%s)", strerror(errno));
 
 	/*
 	 * We feed the pack-objects we just spawned with revision
 	 * parameters by writing to the pipe.
 	 */
-	close(pipe_fd[0]);
-	close(fd);
-
 	while (refs) {
 		char buf[42];
 
@@ -67,38 +56,23 @@
 			memcpy(buf + 1, sha1_to_hex(refs->old_sha1), 40);
 			buf[0] = '^';
 			buf[41] = '\n';
-			if (!write_or_whine(pipe_fd[1], buf, 42,
+			if (!write_or_whine(po.in, buf, 42,
 						"send-pack: send refs"))
 				break;
 		}
 		if (!is_null_sha1(refs->new_sha1)) {
 			memcpy(buf, sha1_to_hex(refs->new_sha1), 40);
 			buf[40] = '\n';
-			if (!write_or_whine(pipe_fd[1], buf, 41,
+			if (!write_or_whine(po.in, buf, 41,
 						"send-pack: send refs"))
 				break;
 		}
 		refs = refs->next;
 	}
-	close(pipe_fd[1]);
 
-	for (;;) {
-		int status, code;
-		pid_t waiting = waitpid(pid, &status, 0);
-
-		if (waiting < 0) {
-			if (errno == EINTR)
-				continue;
-			return error("waitpid failed (%s)", strerror(errno));
-		}
-		if ((waiting != pid) || WIFSIGNALED(status) ||
-		    !WIFEXITED(status))
-			return error("pack-objects died with strange error");
-		code = WEXITSTATUS(status);
-		if (code)
-			return -code;
-		return 0;
-	}
+	if (finish_command(&po))
+		return error("pack-objects died with strange error");
+	return 0;
 }
 
 static void unmark_and_free(struct commit_list *list, unsigned int mark)
@@ -379,11 +353,11 @@
 		char *arg = *argv;
 
 		if (*arg == '-') {
-			if (!strncmp(arg, "--receive-pack=", 15)) {
+			if (!prefixcmp(arg, "--receive-pack=")) {
 				receivepack = arg + 15;
 				continue;
 			}
-			if (!strncmp(arg, "--exec=", 7)) {
+			if (!prefixcmp(arg, "--exec=")) {
 				receivepack = arg + 7;
 				continue;
 			}
diff --git a/setup.c b/setup.c
index 76e5e69..a45ea83 100644
--- a/setup.c
+++ b/setup.c
@@ -251,7 +251,7 @@
 	offset++;
 	cwd[len++] = '/';
 	cwd[len] = 0;
-	inside_git_dir = !strncmp(cwd + offset, ".git/", 5);
+	inside_git_dir = !prefixcmp(cwd + offset, ".git/");
 	return cwd + offset;
 }
 
diff --git a/sha1_file.c b/sha1_file.c
index 2c87031..9c26038 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -349,6 +349,7 @@
 static void read_info_alternates(const char * relative_base, int depth)
 {
 	char *map;
+	size_t mapsz;
 	struct stat st;
 	char path[PATH_MAX];
 	int fd;
@@ -361,12 +362,13 @@
 		close(fd);
 		return;
 	}
-	map = xmmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+	mapsz = xsize_t(st.st_size);
+	map = xmmap(NULL, mapsz, PROT_READ, MAP_PRIVATE, fd, 0);
 	close(fd);
 
-	link_alt_odb_entries(map, map + st.st_size, '\n', relative_base, depth);
+	link_alt_odb_entries(map, map + mapsz, '\n', relative_base, depth);
 
-	munmap(map, st.st_size);
+	munmap(map, mapsz);
 }
 
 void prepare_alt_odb(void)
@@ -430,48 +432,50 @@
 		pack_mapped, peak_pack_mapped);
 }
 
-static int check_packed_git_idx(const char *path, unsigned long *idx_size_,
-				void **idx_map_)
+static int check_packed_git_idx(const char *path,  struct packed_git *p)
 {
 	void *idx_map;
-	uint32_t *index;
-	unsigned long idx_size;
-	int nr, i;
+	struct pack_idx_header *hdr;
+	size_t idx_size;
+	uint32_t nr, i, *index;
 	int fd = open(path, O_RDONLY);
 	struct stat st;
+
 	if (fd < 0)
 		return -1;
 	if (fstat(fd, &st)) {
 		close(fd);
 		return -1;
 	}
-	idx_size = st.st_size;
+	idx_size = xsize_t(st.st_size);
+	if (idx_size < 4 * 256 + 20 + 20) {
+		close(fd);
+		return error("index file %s is too small", path);
+	}
 	idx_map = xmmap(NULL, idx_size, PROT_READ, MAP_PRIVATE, fd, 0);
 	close(fd);
 
-	index = idx_map;
-	*idx_map_ = idx_map;
-	*idx_size_ = idx_size;
-
-	/* check index map */
-	if (idx_size < 4*256 + 20 + 20)
-		return error("index file %s is too small", path);
-
 	/* a future index format would start with this, as older git
 	 * binaries would fail the non-monotonic index check below.
 	 * give a nicer warning to the user if we can.
 	 */
-	if (index[0] == htonl(PACK_IDX_SIGNATURE))
+	hdr = idx_map;
+	if (hdr->idx_signature == htonl(PACK_IDX_SIGNATURE)) {
+		munmap(idx_map, idx_size);
 		return error("index file %s is a newer version"
 			" and is not supported by this binary"
 			" (try upgrading GIT to a newer version)",
 			path);
+	}
 
 	nr = 0;
+	index = idx_map;
 	for (i = 0; i < 256; i++) {
-		unsigned int n = ntohl(index[i]);
-		if (n < nr)
+		uint32_t n = ntohl(index[i]);
+		if (n < nr) {
+			munmap(idx_map, idx_size);
 			return error("non-monotonic index %s", path);
+		}
 		nr = n;
 	}
 
@@ -482,9 +486,14 @@
 	 *  - 20-byte SHA1 of the packfile
 	 *  - 20-byte SHA1 file checksum
 	 */
-	if (idx_size != 4*256 + nr * 24 + 20 + 20)
+	if (idx_size != 4*256 + nr * 24 + 20 + 20) {
+		munmap(idx_map, idx_size);
 		return error("wrong index file size in %s", path);
+	}
 
+	p->index_version = 1;
+	p->index_data = idx_map;
+	p->index_size = idx_size;
 	return 0;
 }
 
@@ -605,7 +614,7 @@
 		return error("end of packfile %s is unavailable", p->pack_name);
 	if (read_in_full(p->pack_fd, sha1, sizeof(sha1)) != sizeof(sha1))
 		return error("packfile %s signature is unavailable", p->pack_name);
-	idx_sha1 = ((unsigned char *)p->index_base) + p->index_size - 40;
+	idx_sha1 = ((unsigned char *)p->index_data) + p->index_size - 40;
 	if (hashcmp(sha1, idx_sha1))
 		return error("packfile %s does not match index", p->pack_name);
 	return 0;
@@ -622,7 +631,7 @@
 	return -1;
 }
 
-static int in_window(struct pack_window *win, unsigned long offset)
+static int in_window(struct pack_window *win, off_t offset)
 {
 	/* We must promise at least 20 bytes (one hash) after the
 	 * offset is available from this window, otherwise the offset
@@ -637,7 +646,7 @@
 
 unsigned char* use_pack(struct packed_git *p,
 		struct pack_window **w_cursor,
-		unsigned long offset,
+		off_t offset,
 		unsigned int *left)
 {
 	struct pack_window *win = *w_cursor;
@@ -662,11 +671,13 @@
 		}
 		if (!win) {
 			size_t window_align = packed_git_window_size / 2;
+			off_t len;
 			win = xcalloc(1, sizeof(*win));
 			win->offset = (offset / window_align) * window_align;
-			win->len = p->pack_size - win->offset;
-			if (win->len > packed_git_window_size)
-				win->len = packed_git_window_size;
+			len = p->pack_size - win->offset;
+			if (len > packed_git_window_size)
+				len = packed_git_window_size;
+			win->len = (size_t)len;
 			pack_mapped += win->len;
 			while (packed_git_limit < pack_mapped
 				&& unuse_one_window(p))
@@ -695,41 +706,41 @@
 	}
 	offset -= win->offset;
 	if (left)
-		*left = win->len - offset;
+		*left = win->len - xsize_t(offset);
 	return win->base + offset;
 }
 
-struct packed_git *add_packed_git(char *path, int path_len, int local)
+struct packed_git *add_packed_git(const char *path, int path_len, int local)
 {
 	struct stat st;
-	struct packed_git *p;
-	unsigned long idx_size;
-	void *idx_map;
-	unsigned char sha1[20];
+	struct packed_git *p = xmalloc(sizeof(*p) + path_len + 2);
 
-	if (check_packed_git_idx(path, &idx_size, &idx_map))
+	/*
+	 * Make sure a corresponding .pack file exists and that
+	 * the index looks sane.
+	 */
+	path_len -= strlen(".idx");
+	if (path_len < 1)
 		return NULL;
-
-	/* do we have a corresponding .pack file? */
-	strcpy(path + path_len - 4, ".pack");
-	if (stat(path, &st) || !S_ISREG(st.st_mode)) {
-		munmap(idx_map, idx_size);
+	memcpy(p->pack_name, path, path_len);
+	strcpy(p->pack_name + path_len, ".pack");
+	if (stat(p->pack_name, &st) || !S_ISREG(st.st_mode) ||
+	    check_packed_git_idx(path, p)) {
+		free(p);
 		return NULL;
 	}
+
 	/* ok, it looks sane as far as we can check without
 	 * actually mapping the pack file.
 	 */
-	p = xmalloc(sizeof(*p) + path_len + 2);
-	strcpy(p->pack_name, path);
-	p->index_size = idx_size;
 	p->pack_size = st.st_size;
-	p->index_base = idx_map;
 	p->next = NULL;
 	p->windows = NULL;
 	p->pack_fd = -1;
 	p->pack_local = local;
-	if ((path_len > 44) && !get_sha1_hex(path + path_len - 44, sha1))
-		hashcpy(p->sha1, sha1);
+	p->mtime = st.st_mtime;
+	if (path_len < 40 || get_sha1_hex(path + path_len - 40, p->sha1))
+		hashclr(p->sha1);
 	return p;
 }
 
@@ -739,23 +750,19 @@
 	return parse_pack_index_file(sha1, path);
 }
 
-struct packed_git *parse_pack_index_file(const unsigned char *sha1, char *idx_path)
+struct packed_git *parse_pack_index_file(const unsigned char *sha1,
+					 const char *idx_path)
 {
-	struct packed_git *p;
-	unsigned long idx_size;
-	void *idx_map;
-	char *path;
+	const char *path = sha1_pack_name(sha1);
+	struct packed_git *p = xmalloc(sizeof(*p) + strlen(path) + 2);
 
-	if (check_packed_git_idx(idx_path, &idx_size, &idx_map))
+	if (check_packed_git_idx(idx_path, p)) {
+		free(p);
 		return NULL;
+	}
 
-	path = sha1_pack_name(sha1);
-
-	p = xmalloc(sizeof(*p) + strlen(path) + 2);
 	strcpy(p->pack_name, path);
-	p->index_size = idx_size;
 	p->pack_size = 0;
-	p->index_base = idx_map;
 	p->next = NULL;
 	p->windows = NULL;
 	p->pack_fd = -1;
@@ -812,6 +819,60 @@
 	closedir(dir);
 }
 
+static int sort_pack(const void *a_, const void *b_)
+{
+	struct packed_git *a = *((struct packed_git **)a_);
+	struct packed_git *b = *((struct packed_git **)b_);
+	int st;
+
+	/*
+	 * Local packs tend to contain objects specific to our
+	 * variant of the project than remote ones.  In addition,
+	 * remote ones could be on a network mounted filesystem.
+	 * Favor local ones for these reasons.
+	 */
+	st = a->pack_local - b->pack_local;
+	if (st)
+		return -st;
+
+	/*
+	 * Younger packs tend to contain more recent objects,
+	 * and more recent objects tend to get accessed more
+	 * often.
+	 */
+	if (a->mtime < b->mtime)
+		return 1;
+	else if (a->mtime == b->mtime)
+		return 0;
+	return -1;
+}
+
+static void rearrange_packed_git(void)
+{
+	struct packed_git **ary, *p;
+	int i, n;
+
+	for (n = 0, p = packed_git; p; p = p->next)
+		n++;
+	if (n < 2)
+		return;
+
+	/* prepare an array of packed_git for easier sorting */
+	ary = xcalloc(n, sizeof(struct packed_git *));
+	for (n = 0, p = packed_git; p; p = p->next)
+		ary[n++] = p;
+
+	qsort(ary, n, sizeof(struct packed_git *), sort_pack);
+
+	/* link them back again */
+	for (i = 0; i < n - 1; i++)
+		ary[i]->next = ary[i + 1];
+	ary[n - 1]->next = NULL;
+	packed_git = ary[0];
+
+	free(ary);
+}
+
 static int prepare_packed_git_run_once = 0;
 void prepare_packed_git(void)
 {
@@ -826,6 +887,7 @@
 		prepare_packed_git_one(alt->base, 0);
 		alt->name[-1] = '/';
 	}
+	rearrange_packed_git();
 	prepare_packed_git_run_once = 1;
 }
 
@@ -871,9 +933,9 @@
 		 */
 		sha1_file_open_flag = 0;
 	}
-	map = xmmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+	*size = xsize_t(st.st_size);
+	map = xmmap(NULL, *size, PROT_READ, MAP_PRIVATE, fd, 0);
 	close(fd);
-	*size = st.st_size;
 	return map;
 }
 
@@ -952,30 +1014,54 @@
 
 	/* And generate the fake traditional header */
 	stream->total_out = 1 + snprintf(buffer, bufsiz, "%s %lu",
-					 type_names[type], size);
+					 typename(type), size);
 	return 0;
 }
 
-static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size)
+static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size, const unsigned char *sha1)
 {
 	int bytes = strlen(buffer) + 1;
 	unsigned char *buf = xmalloc(1+size);
 	unsigned long n;
+	int status = Z_OK;
 
 	n = stream->total_out - bytes;
 	if (n > size)
 		n = size;
 	memcpy(buf, (char *) buffer + bytes, n);
 	bytes = n;
-	if (bytes < size) {
+	if (bytes <= size) {
+		/*
+		 * The above condition must be (bytes <= size), not
+		 * (bytes < size).  In other words, even though we
+		 * expect no more output and set avail_out to zer0,
+		 * the input zlib stream may have bytes that express
+		 * "this concludes the stream", and we *do* want to
+		 * eat that input.
+		 *
+		 * Otherwise we would not be able to test that we
+		 * consumed all the input to reach the expected size;
+		 * we also want to check that zlib tells us that all
+		 * went well with status == Z_STREAM_END at the end.
+		 */
 		stream->next_out = buf + bytes;
 		stream->avail_out = size - bytes;
-		while (inflate(stream, Z_FINISH) == Z_OK)
-			/* nothing */;
+		while (status == Z_OK)
+			status = inflate(stream, Z_FINISH);
 	}
 	buf[size] = 0;
-	inflateEnd(stream);
-	return buf;
+	if (status == Z_STREAM_END && !stream->avail_in) {
+		inflateEnd(stream);
+		return buf;
+	}
+
+	if (status < 0)
+		error("corrupt loose object '%s'", sha1_to_hex(sha1));
+	else if (stream->avail_in)
+		error("garbage at end of loose object '%s'",
+		      sha1_to_hex(sha1));
+	free(buf);
+	return NULL;
 }
 
 /*
@@ -983,26 +1069,27 @@
  * too permissive for what we want to check. So do an anal
  * object header parse by hand.
  */
-static int parse_sha1_header(char *hdr, char *type, unsigned long *sizep)
+static int parse_sha1_header(const char *hdr, unsigned long *sizep)
 {
+	char type[10];
 	int i;
 	unsigned long size;
 
 	/*
 	 * The type can be at most ten bytes (including the 
 	 * terminating '\0' that we add), and is followed by
-	 * a space. 
+	 * a space.
 	 */
-	i = 10;
+	i = 0;
 	for (;;) {
 		char c = *hdr++;
 		if (c == ' ')
 			break;
-		if (!--i)
+		type[i++] = c;
+		if (i >= sizeof(type))
 			return -1;
-		*type++ = c;
 	}
-	*type = 0;
+	type[i] = 0;
 
 	/*
 	 * The length must follow immediately, and be in canonical
@@ -1025,31 +1112,30 @@
 	/*
 	 * The length must be followed by a zero byte
 	 */
-	return *hdr ? -1 : 0;
+	return *hdr ? -1 : type_from_string(type);
 }
 
-void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, unsigned long *size)
+static void *unpack_sha1_file(void *map, unsigned long mapsize, enum object_type *type, unsigned long *size, const unsigned char *sha1)
 {
 	int ret;
 	z_stream stream;
 	char hdr[8192];
 
 	ret = unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr));
-	if (ret < Z_OK || parse_sha1_header(hdr, type, size) < 0)
+	if (ret < Z_OK || (*type = parse_sha1_header(hdr, size)) < 0)
 		return NULL;
 
-	return unpack_sha1_rest(&stream, hdr, *size);
+	return unpack_sha1_rest(&stream, hdr, *size, sha1);
 }
 
-static unsigned long get_delta_base(struct packed_git *p,
+static off_t get_delta_base(struct packed_git *p,
 				    struct pack_window **w_curs,
-				    unsigned long offset,
-				    enum object_type kind,
-				    unsigned long delta_obj_offset,
-				    unsigned long *base_obj_offset)
+				    off_t *curpos,
+				    enum object_type type,
+				    off_t delta_obj_offset)
 {
-	unsigned char *base_info = use_pack(p, w_curs, offset, NULL);
-	unsigned long base_offset;
+	unsigned char *base_info = use_pack(p, w_curs, *curpos, NULL);
+	off_t base_offset;
 
 	/* use_pack() assured us we have [base_info, base_info + 20)
 	 * as a range that we can look at without walking off the
@@ -1057,7 +1143,7 @@
 	 * that is assured.  An OFS_DELTA longer than the hash size
 	 * is stupid, as then a REF_DELTA would be smaller to store.
 	 */
-	if (kind == OBJ_OFS_DELTA) {
+	if (type == OBJ_OFS_DELTA) {
 		unsigned used = 0;
 		unsigned char c = base_info[used++];
 		base_offset = c & 127;
@@ -1071,49 +1157,43 @@
 		base_offset = delta_obj_offset - base_offset;
 		if (base_offset >= delta_obj_offset)
 			die("delta base offset out of bound");
-		offset += used;
-	} else if (kind == OBJ_REF_DELTA) {
+		*curpos += used;
+	} else if (type == OBJ_REF_DELTA) {
 		/* The base entry _must_ be in the same pack */
 		base_offset = find_pack_entry_one(base_info, p);
 		if (!base_offset)
 			die("failed to find delta-pack base object %s",
 				sha1_to_hex(base_info));
-		offset += 20;
+		*curpos += 20;
 	} else
 		die("I am totally screwed");
-	*base_obj_offset = base_offset;
-	return offset;
+	return base_offset;
 }
 
 /* forward declaration for a mutually recursive function */
-static int packed_object_info(struct packed_git *p, unsigned long offset,
-			      char *type, unsigned long *sizep);
+static int packed_object_info(struct packed_git *p, off_t offset,
+			      unsigned long *sizep);
 
 static int packed_delta_info(struct packed_git *p,
 			     struct pack_window **w_curs,
-			     unsigned long offset,
-			     enum object_type kind,
-			     unsigned long obj_offset,
-			     char *type,
+			     off_t curpos,
+			     enum object_type type,
+			     off_t obj_offset,
 			     unsigned long *sizep)
 {
-	unsigned long base_offset;
+	off_t base_offset;
 
-	offset = get_delta_base(p, w_curs, offset, kind,
-		obj_offset, &base_offset);
+	base_offset = get_delta_base(p, w_curs, &curpos, type, obj_offset);
+	type = packed_object_info(p, base_offset, NULL);
 
 	/* We choose to only get the type of the base object and
 	 * ignore potentially corrupt pack file that expects the delta
 	 * based on a base with a wrong size.  This saves tons of
 	 * inflate() calls.
 	 */
-	if (packed_object_info(p, base_offset, type, NULL))
-		die("cannot get info for delta-pack base");
-
 	if (sizep) {
 		const unsigned char *data;
 		unsigned char delta_head[20], *in;
-		unsigned long result_size;
 		z_stream stream;
 		int st;
 
@@ -1123,10 +1203,10 @@
 
 		inflateInit(&stream);
 		do {
-			in = use_pack(p, w_curs, offset, &stream.avail_in);
+			in = use_pack(p, w_curs, curpos, &stream.avail_in);
 			stream.next_in = in;
 			st = inflate(&stream, Z_FINISH);
-			offset += stream.next_in - in;
+			curpos += stream.next_in - in;
 		} while ((st == Z_OK || st == Z_BUF_ERROR)
 			&& stream.total_out < sizeof(delta_head));
 		inflateEnd(&stream);
@@ -1143,21 +1223,21 @@
 		get_delta_hdr_size(&data, delta_head+sizeof(delta_head));
 
 		/* Read the result size */
-		result_size = get_delta_hdr_size(&data, delta_head+sizeof(delta_head));
-		*sizep = result_size;
+		*sizep = get_delta_hdr_size(&data, delta_head+sizeof(delta_head));
 	}
-	return 0;
+
+	return type;
 }
 
-static unsigned long unpack_object_header(struct packed_git *p,
-		struct pack_window **w_curs,
-		unsigned long offset,
-		enum object_type *type,
-		unsigned long *sizep)
+static int unpack_object_header(struct packed_git *p,
+				struct pack_window **w_curs,
+				off_t *curpos,
+				unsigned long *sizep)
 {
 	unsigned char *base;
 	unsigned int left;
 	unsigned long used;
+	enum object_type type;
 
 	/* use_pack() assures us we have [base, base + 20) available
 	 * as a range that we can look at at.  (Its actually the hash
@@ -1165,100 +1245,98 @@
 	 * the maximum deflated object size is 2^137, which is just
 	 * insane, so we know won't exceed what we have been given.
 	 */
-	base = use_pack(p, w_curs, offset, &left);
-	used = unpack_object_header_gently(base, left, type, sizep);
+	base = use_pack(p, w_curs, *curpos, &left);
+	used = unpack_object_header_gently(base, left, &type, sizep);
 	if (!used)
 		die("object offset outside of pack file");
+	*curpos += used;
 
-	return offset + used;
+	return type;
 }
 
-void packed_object_info_detail(struct packed_git *p,
-			       unsigned long offset,
-			       char *type,
-			       unsigned long *size,
-			       unsigned long *store_size,
-			       unsigned int *delta_chain_length,
-			       unsigned char *base_sha1)
+const char *packed_object_info_detail(struct packed_git *p,
+				      off_t obj_offset,
+				      unsigned long *size,
+				      unsigned long *store_size,
+				      unsigned int *delta_chain_length,
+				      unsigned char *base_sha1)
 {
 	struct pack_window *w_curs = NULL;
-	unsigned long obj_offset, val;
+	off_t curpos;
+	unsigned long dummy;
 	unsigned char *next_sha1;
-	enum object_type kind;
+	enum object_type type;
 
 	*delta_chain_length = 0;
-	obj_offset = offset;
-	offset = unpack_object_header(p, &w_curs, offset, &kind, size);
+	curpos = obj_offset;
+	type = unpack_object_header(p, &w_curs, &curpos, size);
 
 	for (;;) {
-		switch (kind) {
+		switch (type) {
 		default:
 			die("pack %s contains unknown object type %d",
-			    p->pack_name, kind);
+			    p->pack_name, type);
 		case OBJ_COMMIT:
 		case OBJ_TREE:
 		case OBJ_BLOB:
 		case OBJ_TAG:
-			strcpy(type, type_names[kind]);
 			*store_size = 0; /* notyet */
 			unuse_pack(&w_curs);
-			return;
+			return typename(type);
 		case OBJ_OFS_DELTA:
-			get_delta_base(p, &w_curs, offset, kind,
-				obj_offset, &offset);
+			obj_offset = get_delta_base(p, &w_curs, &curpos, type, obj_offset);
 			if (*delta_chain_length == 0) {
-				/* TODO: find base_sha1 as pointed by offset */
+				/* TODO: find base_sha1 as pointed by curpos */
+				hashclr(base_sha1);
 			}
 			break;
 		case OBJ_REF_DELTA:
-			next_sha1 = use_pack(p, &w_curs, offset, NULL);
+			next_sha1 = use_pack(p, &w_curs, curpos, NULL);
 			if (*delta_chain_length == 0)
 				hashcpy(base_sha1, next_sha1);
-			offset = find_pack_entry_one(next_sha1, p);
+			obj_offset = find_pack_entry_one(next_sha1, p);
 			break;
 		}
-		obj_offset = offset;
-		offset = unpack_object_header(p, &w_curs, offset, &kind, &val);
 		(*delta_chain_length)++;
+		curpos = obj_offset;
+		type = unpack_object_header(p, &w_curs, &curpos, &dummy);
 	}
 }
 
-static int packed_object_info(struct packed_git *p, unsigned long offset,
-			      char *type, unsigned long *sizep)
+static int packed_object_info(struct packed_git *p, off_t obj_offset,
+			      unsigned long *sizep)
 {
 	struct pack_window *w_curs = NULL;
-	unsigned long size, obj_offset = offset;
-	enum object_type kind;
-	int r;
+	unsigned long size;
+	off_t curpos = obj_offset;
+	enum object_type type;
 
-	offset = unpack_object_header(p, &w_curs, offset, &kind, &size);
+	type = unpack_object_header(p, &w_curs, &curpos, &size);
 
-	switch (kind) {
+	switch (type) {
 	case OBJ_OFS_DELTA:
 	case OBJ_REF_DELTA:
-		r = packed_delta_info(p, &w_curs, offset, kind,
-			obj_offset, type, sizep);
-		unuse_pack(&w_curs);
-		return r;
+		type = packed_delta_info(p, &w_curs, curpos,
+					 type, obj_offset, sizep);
+		break;
 	case OBJ_COMMIT:
 	case OBJ_TREE:
 	case OBJ_BLOB:
 	case OBJ_TAG:
-		strcpy(type, type_names[kind]);
-		unuse_pack(&w_curs);
+		if (sizep)
+			*sizep = size;
 		break;
 	default:
 		die("pack %s contains unknown object type %d",
-		    p->pack_name, kind);
+		    p->pack_name, type);
 	}
-	if (sizep)
-		*sizep = size;
-	return 0;
+	unuse_pack(&w_curs);
+	return type;
 }
 
 static void *unpack_compressed_entry(struct packed_git *p,
 				    struct pack_window **w_curs,
-				    unsigned long offset,
+				    off_t curpos,
 				    unsigned long size)
 {
 	int st;
@@ -1273,10 +1351,10 @@
 
 	inflateInit(&stream);
 	do {
-		in = use_pack(p, w_curs, offset, &stream.avail_in);
+		in = use_pack(p, w_curs, curpos, &stream.avail_in);
 		stream.next_in = in;
 		st = inflate(&stream, Z_FINISH);
-		offset += stream.next_in - in;
+		curpos += stream.next_in - in;
 	} while (st == Z_OK || st == Z_BUF_ERROR);
 	inflateEnd(&stream);
 	if ((st != Z_STREAM_END) || stream.total_out != size) {
@@ -1287,94 +1365,197 @@
 	return buffer;
 }
 
+#define MAX_DELTA_CACHE (256)
+
+static size_t delta_base_cached;
+
+static struct delta_base_cache_lru_list {
+	struct delta_base_cache_lru_list *prev;
+	struct delta_base_cache_lru_list *next;
+} delta_base_cache_lru = { &delta_base_cache_lru, &delta_base_cache_lru };
+
+static struct delta_base_cache_entry {
+	struct delta_base_cache_lru_list lru;
+	void *data;
+	struct packed_git *p;
+	off_t base_offset;
+	unsigned long size;
+	enum object_type type;
+} delta_base_cache[MAX_DELTA_CACHE];
+
+static unsigned long pack_entry_hash(struct packed_git *p, off_t base_offset)
+{
+	unsigned long hash;
+
+	hash = (unsigned long)p + (unsigned long)base_offset;
+	hash += (hash >> 8) + (hash >> 16);
+	return hash % MAX_DELTA_CACHE;
+}
+
+static void *cache_or_unpack_entry(struct packed_git *p, off_t base_offset,
+	unsigned long *base_size, enum object_type *type, int keep_cache)
+{
+	void *ret;
+	unsigned long hash = pack_entry_hash(p, base_offset);
+	struct delta_base_cache_entry *ent = delta_base_cache + hash;
+
+	ret = ent->data;
+	if (ret && ent->p == p && ent->base_offset == base_offset)
+		goto found_cache_entry;
+	return unpack_entry(p, base_offset, type, base_size);
+
+found_cache_entry:
+	if (!keep_cache) {
+		ent->data = NULL;
+		ent->lru.next->prev = ent->lru.prev;
+		ent->lru.prev->next = ent->lru.next;
+		delta_base_cached -= ent->size;
+	}
+	else {
+		ret = xmalloc(ent->size + 1);
+		memcpy(ret, ent->data, ent->size);
+		((char *)ret)[ent->size] = 0;
+	}
+	*type = ent->type;
+	*base_size = ent->size;
+	return ret;
+}
+
+static inline void release_delta_base_cache(struct delta_base_cache_entry *ent)
+{
+	if (ent->data) {
+		free(ent->data);
+		ent->data = NULL;
+		ent->lru.next->prev = ent->lru.prev;
+		ent->lru.prev->next = ent->lru.next;
+		delta_base_cached -= ent->size;
+	}
+}
+
+static void add_delta_base_cache(struct packed_git *p, off_t base_offset,
+	void *base, unsigned long base_size, enum object_type type)
+{
+	unsigned long hash = pack_entry_hash(p, base_offset);
+	struct delta_base_cache_entry *ent = delta_base_cache + hash;
+	struct delta_base_cache_lru_list *lru;
+
+	release_delta_base_cache(ent);
+	delta_base_cached += base_size;
+
+	for (lru = delta_base_cache_lru.next;
+	     delta_base_cached > delta_base_cache_limit
+	     && lru != &delta_base_cache_lru;
+	     lru = lru->next) {
+		struct delta_base_cache_entry *f = (void *)lru;
+		if (f->type == OBJ_BLOB)
+			release_delta_base_cache(f);
+	}
+	for (lru = delta_base_cache_lru.next;
+	     delta_base_cached > delta_base_cache_limit
+	     && lru != &delta_base_cache_lru;
+	     lru = lru->next) {
+		struct delta_base_cache_entry *f = (void *)lru;
+		release_delta_base_cache(f);
+	}
+
+	ent->p = p;
+	ent->base_offset = base_offset;
+	ent->type = type;
+	ent->data = base;
+	ent->size = base_size;
+	ent->lru.next = &delta_base_cache_lru;
+	ent->lru.prev = delta_base_cache_lru.prev;
+	delta_base_cache_lru.prev->next = &ent->lru;
+	delta_base_cache_lru.prev = &ent->lru;
+}
+
 static void *unpack_delta_entry(struct packed_git *p,
 				struct pack_window **w_curs,
-				unsigned long offset,
+				off_t curpos,
 				unsigned long delta_size,
-				enum object_type kind,
-				unsigned long obj_offset,
-				char *type,
+				off_t obj_offset,
+				enum object_type *type,
 				unsigned long *sizep)
 {
 	void *delta_data, *result, *base;
-	unsigned long result_size, base_size, base_offset;
+	unsigned long base_size;
+	off_t base_offset;
 
-	offset = get_delta_base(p, w_curs, offset, kind,
-		obj_offset, &base_offset);
-	base = unpack_entry(p, base_offset, type, &base_size);
+	base_offset = get_delta_base(p, w_curs, &curpos, *type, obj_offset);
+	base = cache_or_unpack_entry(p, base_offset, &base_size, type, 0);
 	if (!base)
-		die("failed to read delta base object at %lu from %s",
-		    base_offset, p->pack_name);
+		die("failed to read delta base object"
+		    " at %"PRIuMAX" from %s",
+		    (uintmax_t)base_offset, p->pack_name);
 
-	delta_data = unpack_compressed_entry(p, w_curs, offset, delta_size);
+	delta_data = unpack_compressed_entry(p, w_curs, curpos, delta_size);
 	result = patch_delta(base, base_size,
 			     delta_data, delta_size,
-			     &result_size);
+			     sizep);
 	if (!result)
 		die("failed to apply delta");
 	free(delta_data);
-	free(base);
-	*sizep = result_size;
+	add_delta_base_cache(p, base_offset, base, base_size, *type);
 	return result;
 }
 
-void *unpack_entry(struct packed_git *p, unsigned long offset,
-			  char *type, unsigned long *sizep)
+void *unpack_entry(struct packed_git *p, off_t obj_offset,
+		   enum object_type *type, unsigned long *sizep)
 {
 	struct pack_window *w_curs = NULL;
-	unsigned long size, obj_offset = offset;
-	enum object_type kind;
-	void *retval;
+	off_t curpos = obj_offset;
+	void *data;
 
-	offset = unpack_object_header(p, &w_curs, offset, &kind, &size);
-	switch (kind) {
+	*type = unpack_object_header(p, &w_curs, &curpos, sizep);
+	switch (*type) {
 	case OBJ_OFS_DELTA:
 	case OBJ_REF_DELTA:
-		retval = unpack_delta_entry(p, &w_curs, offset, size,
-			kind, obj_offset, type, sizep);
+		data = unpack_delta_entry(p, &w_curs, curpos, *sizep,
+					  obj_offset, type, sizep);
 		break;
 	case OBJ_COMMIT:
 	case OBJ_TREE:
 	case OBJ_BLOB:
 	case OBJ_TAG:
-		strcpy(type, type_names[kind]);
-		*sizep = size;
-		retval = unpack_compressed_entry(p, &w_curs, offset, size);
+		data = unpack_compressed_entry(p, &w_curs, curpos, *sizep);
 		break;
 	default:
-		die("unknown object type %i in %s", kind, p->pack_name);
+		die("unknown object type %i in %s", *type, p->pack_name);
 	}
 	unuse_pack(&w_curs);
-	return retval;
+	return data;
 }
 
-int num_packed_objects(const struct packed_git *p)
+uint32_t num_packed_objects(const struct packed_git *p)
 {
 	/* See check_packed_git_idx() */
-	return (p->index_size - 20 - 20 - 4*256) / 24;
+	return (uint32_t)((p->index_size - 20 - 20 - 4*256) / 24);
 }
 
-int nth_packed_object_sha1(const struct packed_git *p, int n,
+int nth_packed_object_sha1(const struct packed_git *p, uint32_t n,
 			   unsigned char* sha1)
 {
-	void *index = p->index_base + 256;
-	if (n < 0 || num_packed_objects(p) <= n)
+	const unsigned char *index = p->index_data;
+	index += 4 * 256;
+	if (num_packed_objects(p) <= n)
 		return -1;
-	hashcpy(sha1, (unsigned char *) index + (24 * n) + 4);
+	hashcpy(sha1, index + 24 * n + 4);
 	return 0;
 }
 
-unsigned long find_pack_entry_one(const unsigned char *sha1,
+off_t find_pack_entry_one(const unsigned char *sha1,
 				  struct packed_git *p)
 {
-	uint32_t *level1_ofs = p->index_base;
+	const uint32_t *level1_ofs = p->index_data;
 	int hi = ntohl(level1_ofs[*sha1]);
 	int lo = ((*sha1 == 0x0) ? 0 : ntohl(level1_ofs[*sha1 - 1]));
-	void *index = p->index_base + 256;
+	const unsigned char *index = p->index_data;
+
+	index += 4 * 256;
 
 	do {
 		int mi = (lo + hi) / 2;
-		int cmp = hashcmp((unsigned char *)index + (24 * mi) + 4, sha1);
+		int cmp = hashcmp(index + 24 * mi + 4, sha1);
 		if (!cmp)
 			return ntohl(*((uint32_t *)((char *)index + (24 * mi))));
 		if (cmp > 0)
@@ -1406,7 +1587,7 @@
 static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e, const char **ignore_packed)
 {
 	struct packed_git *p;
-	unsigned long offset;
+	off_t offset;
 
 	prepare_packed_git();
 
@@ -1452,16 +1633,16 @@
 			return p;
 	}
 	return NULL;
-	
+
 }
 
-static int sha1_loose_object_info(const unsigned char *sha1, char *type, unsigned long *sizep)
+static int sha1_loose_object_info(const unsigned char *sha1, unsigned long *sizep)
 {
 	int status;
 	unsigned long mapsize, size;
 	void *map;
 	z_stream stream;
-	char hdr[128];
+	char hdr[32];
 
 	map = map_sha1_file(sha1, &mapsize);
 	if (!map)
@@ -1469,38 +1650,36 @@
 	if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0)
 		status = error("unable to unpack %s header",
 			       sha1_to_hex(sha1));
-	if (parse_sha1_header(hdr, type, &size) < 0)
+	else if ((status = parse_sha1_header(hdr, &size)) < 0)
 		status = error("unable to parse %s header", sha1_to_hex(sha1));
-	else {
-		status = 0;
-		if (sizep)
-			*sizep = size;
-	}
+	else if (sizep)
+		*sizep = size;
 	inflateEnd(&stream);
 	munmap(map, mapsize);
 	return status;
 }
 
-int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep)
+int sha1_object_info(const unsigned char *sha1, unsigned long *sizep)
 {
 	struct pack_entry e;
 
 	if (!find_pack_entry(sha1, &e, NULL)) {
 		reprepare_packed_git();
 		if (!find_pack_entry(sha1, &e, NULL))
-			return sha1_loose_object_info(sha1, type, sizep);
+			return sha1_loose_object_info(sha1, sizep);
 	}
-	return packed_object_info(e.p, e.offset, type, sizep);
+	return packed_object_info(e.p, e.offset, sizep);
 }
 
-static void *read_packed_sha1(const unsigned char *sha1, char *type, unsigned long *size)
+static void *read_packed_sha1(const unsigned char *sha1,
+			      enum object_type *type, unsigned long *size)
 {
 	struct pack_entry e;
 
 	if (!find_pack_entry(sha1, &e, NULL))
 		return NULL;
 	else
-		return unpack_entry(e.p, e.offset, type, size);
+		return cache_or_unpack_entry(e.p, e.offset, size, type, 1);
 }
 
 /*
@@ -1511,7 +1690,7 @@
  */
 static struct cached_object {
 	unsigned char sha1[20];
-	const char *type;
+	enum object_type type;
 	void *buf;
 	unsigned long size;
 } *cached_objects;
@@ -1529,11 +1708,12 @@
 	return NULL;
 }
 
-int pretend_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *sha1)
+int pretend_sha1_file(void *buf, unsigned long len, enum object_type type,
+		      unsigned char *sha1)
 {
 	struct cached_object *co;
 
-	hash_sha1_file(buf, len, type, sha1);
+	hash_sha1_file(buf, len, typename(type), sha1);
 	if (has_sha1_file(sha1) || find_cached_object(sha1))
 		return 0;
 	if (cached_object_alloc <= cached_object_nr) {
@@ -1544,14 +1724,15 @@
 	}
 	co = &cached_objects[cached_object_nr++];
 	co->size = len;
-	co->type = strdup(type);
+	co->type = type;
 	co->buf = xmalloc(len);
 	memcpy(co->buf, buf, len);
 	hashcpy(co->sha1, sha1);
 	return 0;
 }
 
-void *read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size)
+void *read_sha1_file(const unsigned char *sha1, enum object_type *type,
+		     unsigned long *size)
 {
 	unsigned long mapsize;
 	void *map, *buf;
@@ -1562,7 +1743,7 @@
 		buf = xmalloc(co->size + 1);
 		memcpy(buf, co->buf, co->size);
 		((char*)buf)[co->size] = 0;
-		strcpy(type, co->type);
+		*type = co->type;
 		*size = co->size;
 		return buf;
 	}
@@ -1572,7 +1753,7 @@
 		return buf;
 	map = map_sha1_file(sha1, &mapsize);
 	if (map) {
-		buf = unpack_sha1_file(map, mapsize, type, size);
+		buf = unpack_sha1_file(map, mapsize, type, size, sha1);
 		munmap(map, mapsize);
 		return buf;
 	}
@@ -1581,33 +1762,34 @@
 }
 
 void *read_object_with_reference(const unsigned char *sha1,
-				 const char *required_type,
+				 const char *required_type_name,
 				 unsigned long *size,
 				 unsigned char *actual_sha1_return)
 {
-	char type[20];
+	enum object_type type, required_type;
 	void *buffer;
 	unsigned long isize;
 	unsigned char actual_sha1[20];
 
+	required_type = type_from_string(required_type_name);
 	hashcpy(actual_sha1, sha1);
 	while (1) {
 		int ref_length = -1;
 		const char *ref_type = NULL;
 
-		buffer = read_sha1_file(actual_sha1, type, &isize);
+		buffer = read_sha1_file(actual_sha1, &type, &isize);
 		if (!buffer)
 			return NULL;
-		if (!strcmp(type, required_type)) {
+		if (type == required_type) {
 			*size = isize;
 			if (actual_sha1_return)
 				hashcpy(actual_sha1_return, actual_sha1);
 			return buffer;
 		}
 		/* Handle references */
-		else if (!strcmp(type, commit_type))
+		else if (type == OBJ_COMMIT)
 			ref_type = "tree ";
-		else if (!strcmp(type, tag_type))
+		else if (type == OBJ_TAG)
 			ref_type = "object ";
 		else {
 			free(buffer);
@@ -1626,14 +1808,14 @@
 	}
 }
 
-static void write_sha1_file_prepare(void *buf, unsigned long len,
+static void write_sha1_file_prepare(const void *buf, unsigned long len,
                                     const char *type, unsigned char *sha1,
-                                    unsigned char *hdr, int *hdrlen)
+                                    char *hdr, int *hdrlen)
 {
 	SHA_CTX c;
 
 	/* Generate the header */
-	*hdrlen = sprintf((char *)hdr, "%s %lu", type, len)+1;
+	*hdrlen = sprintf(hdr, "%s %lu", type, len)+1;
 
 	/* Sha1.. */
 	SHA1_Init(&c);
@@ -1740,33 +1922,24 @@
 
 static void setup_object_header(z_stream *stream, const char *type, unsigned long len)
 {
-	int obj_type, hdr;
+	int obj_type, hdrlen;
 
 	if (use_legacy_headers) {
 		while (deflate(stream, 0) == Z_OK)
 			/* nothing */;
 		return;
 	}
-	if (!strcmp(type, blob_type))
-		obj_type = OBJ_BLOB;
-	else if (!strcmp(type, tree_type))
-		obj_type = OBJ_TREE;
-	else if (!strcmp(type, commit_type))
-		obj_type = OBJ_COMMIT;
-	else if (!strcmp(type, tag_type))
-		obj_type = OBJ_TAG;
-	else
-		die("trying to generate bogus object of type '%s'", type);
-	hdr = write_binary_header(stream->next_out, obj_type, len);
-	stream->total_out = hdr;
-	stream->next_out += hdr;
-	stream->avail_out -= hdr;
+	obj_type = type_from_string(type);
+	hdrlen = write_binary_header(stream->next_out, obj_type, len);
+	stream->total_out = hdrlen;
+	stream->next_out += hdrlen;
+	stream->avail_out -= hdrlen;
 }
 
-int hash_sha1_file(void *buf, unsigned long len, const char *type,
+int hash_sha1_file(const void *buf, unsigned long len, const char *type,
                    unsigned char *sha1)
 {
-	unsigned char hdr[50];
+	char hdr[32];
 	int hdrlen;
 	write_sha1_file_prepare(buf, len, type, sha1, hdr, &hdrlen);
 	return 0;
@@ -1774,13 +1947,13 @@
 
 int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *returnsha1)
 {
-	int size;
+	int size, ret;
 	unsigned char *compressed;
 	z_stream stream;
 	unsigned char sha1[20];
 	char *filename;
 	static char tmpfile[PATH_MAX];
-	unsigned char hdr[50];
+	char hdr[32];
 	int fd, hdrlen;
 
 	/* Normally if we have it in the pack then we do not bother writing
@@ -1806,7 +1979,7 @@
 		return error("sha1 file %s: %s\n", filename, strerror(errno));
 	}
 
-	snprintf(tmpfile, sizeof(tmpfile), "%s/obj_XXXXXX", get_object_directory());
+	snprintf(tmpfile, sizeof(tmpfile), "%s/tmp_obj_XXXXXX", get_object_directory());
 
 	fd = mkstemp(tmpfile);
 	if (fd < 0) {
@@ -1827,22 +2000,28 @@
 	stream.avail_out = size;
 
 	/* First header.. */
-	stream.next_in = hdr;
+	stream.next_in = (unsigned char *)hdr;
 	stream.avail_in = hdrlen;
 	setup_object_header(&stream, type, len);
 
 	/* Then the data itself.. */
 	stream.next_in = buf;
 	stream.avail_in = len;
-	while (deflate(&stream, Z_FINISH) == Z_OK)
-		/* nothing */;
-	deflateEnd(&stream);
+	ret = deflate(&stream, Z_FINISH);
+	if (ret != Z_STREAM_END)
+		die("unable to deflate new object %s (%d)", sha1_to_hex(sha1), ret);
+
+	ret = deflateEnd(&stream);
+	if (ret != Z_OK)
+		die("deflateEnd on object %s failed (%d)", sha1_to_hex(sha1), ret);
+
 	size = stream.total_out;
 
 	if (write_buffer(fd, compressed, size) < 0)
 		die("unable to write sha1 file");
 	fchmod(fd, 0444);
-	close(fd);
+	if (close(fd))
+		die("unable to write sha1 file");
 	free(compressed);
 
 	return move_temp_to_file(tmpfile, filename);
@@ -1858,17 +2037,17 @@
 	z_stream stream;
 	unsigned char *unpacked;
 	unsigned long len;
-	char type[20];
-	char hdr[50];
+	enum object_type type;
+	char hdr[32];
 	int hdrlen;
 	void *buf;
 
 	/* need to unpack and recompress it by itself */
-	unpacked = read_packed_sha1(sha1, type, &len);
+	unpacked = read_packed_sha1(sha1, &type, &len);
 	if (!unpacked)
 		error("cannot read sha1_file for %s", sha1_to_hex(sha1));
 
-	hdrlen = sprintf(hdr, "%s %lu", type, len) + 1;
+	hdrlen = sprintf(hdr, "%s %lu", typename(type), len) + 1;
 
 	/* Set it up */
 	memset(&stream, 0, sizeof(stream));
@@ -1927,7 +2106,7 @@
 	int ret;
 	SHA_CTX c;
 
-	snprintf(tmpfile, sizeof(tmpfile), "%s/obj_XXXXXX", get_object_directory());
+	snprintf(tmpfile, sizeof(tmpfile), "%s/tmp_obj_XXXXXX", get_object_directory());
 
 	local = mkstemp(tmpfile);
 	if (local < 0) {
@@ -1976,7 +2155,9 @@
 	} while (1);
 	inflateEnd(&stream);
 
-	close(local);
+	fchmod(local, 0444);
+	if (close(local) != 0)
+		die("unable to write sha1 file");
 	SHA1_Final(real_sha1, &c);
 	if (ret != Z_STREAM_END) {
 		unlink(tmpfile);
@@ -2078,24 +2259,43 @@
 	return ret;
 }
 
-int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, const char *type)
+int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object,
+	     enum object_type type, const char *path)
 {
-	unsigned long size = st->st_size;
-	void *buf;
-	int ret;
+	size_t size = xsize_t(st->st_size);
+	void *buf = NULL;
+	int ret, re_allocated = 0;
 
-	buf = "";
 	if (size)
 		buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
 	close(fd);
 
 	if (!type)
-		type = blob_type;
-	/* FIXME: CRLF -> LF conversion here for blobs! We'll need the path! */
+		type = OBJ_BLOB;
+
+	/*
+	 * Convert blobs to git internal format
+	 */
+	if ((type == OBJ_BLOB) && S_ISREG(st->st_mode)) {
+		unsigned long nsize = size;
+		char *nbuf = buf;
+		if (convert_to_git(path, &nbuf, &nsize)) {
+			if (size)
+				munmap(buf, size);
+			size = nsize;
+			buf = nbuf;
+			re_allocated = 1;
+		}
+	}
+
 	if (write_object)
-		ret = write_sha1_file(buf, size, type, sha1);
+		ret = write_sha1_file(buf, size, typename(type), sha1);
 	else
-		ret = hash_sha1_file(buf, size, type, sha1);
+		ret = hash_sha1_file(buf, size, typename(type), sha1);
+	if (re_allocated) {
+		free(buf);
+		return ret;
+	}
 	if (size)
 		munmap(buf, size);
 	return ret;
@@ -2105,6 +2305,7 @@
 {
 	int fd;
 	char *target;
+	size_t len;
 
 	switch (st->st_mode & S_IFMT) {
 	case S_IFREG:
@@ -2112,21 +2313,22 @@
 		if (fd < 0)
 			return error("open(\"%s\"): %s", path,
 				     strerror(errno));
-		if (index_fd(sha1, fd, st, write_object, NULL) < 0)
+		if (index_fd(sha1, fd, st, write_object, OBJ_BLOB, path) < 0)
 			return error("%s: failed to insert into database",
 				     path);
 		break;
 	case S_IFLNK:
-		target = xmalloc(st->st_size+1);
-		if (readlink(path, target, st->st_size+1) != st->st_size) {
+		len = xsize_t(st->st_size);
+		target = xmalloc(len + 1);
+		if (readlink(path, target, len + 1) != st->st_size) {
 			char *errstr = strerror(errno);
 			free(target);
 			return error("readlink(\"%s\"): %s", path,
 			             errstr);
 		}
 		if (!write_object)
-			hash_sha1_file(target, st->st_size, blob_type, sha1);
-		else if (write_sha1_file(target, st->st_size, blob_type, sha1))
+			hash_sha1_file(target, len, blob_type, sha1);
+		else if (write_sha1_file(target, len, blob_type, sha1))
 			return error("%s: failed to insert into database",
 				     path);
 		free(target);
diff --git a/sha1_name.c b/sha1_name.c
index a7efa96..bede0e5 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -76,10 +76,10 @@
 
 	prepare_packed_git();
 	for (p = packed_git; p && found < 2; p = p->next) {
-		unsigned num = num_packed_objects(p);
-		unsigned first = 0, last = num;
+		uint32_t num = num_packed_objects(p);
+		uint32_t first = 0, last = num;
 		while (first < last) {
-			unsigned mid = (first + last) / 2;
+			uint32_t mid = (first + last) / 2;
 			unsigned char now[20];
 			int cmp;
 
@@ -577,6 +577,66 @@
 	return get_short_sha1(name, len, sha1, 0);
 }
 
+static int handle_one_ref(const char *path,
+		const unsigned char *sha1, int flag, void *cb_data)
+{
+	struct commit_list **list = cb_data;
+	struct object *object = parse_object(sha1);
+	if (!object)
+		return 0;
+	if (object->type == OBJ_TAG)
+		object = deref_tag(object, path, strlen(path));
+	if (object->type != OBJ_COMMIT)
+		return 0;
+	insert_by_date((struct commit *)object, list);
+	return 0;
+}
+
+/*
+ * This interprets names like ':/Initial revision of "git"' by searching
+ * through history and returning the first commit whose message starts
+ * with the given string.
+ *
+ * For future extension, ':/!' is reserved. If you want to match a message
+ * beginning with a '!', you have to repeat the exclamation mark.
+ */
+
+#define ONELINE_SEEN (1u<<20)
+static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
+{
+	struct commit_list *list = NULL, *backup = NULL, *l;
+	int retval = -1;
+
+	if (prefix[0] == '!') {
+		if (prefix[1] != '!')
+			die ("Invalid search pattern: %s", prefix);
+		prefix++;
+	}
+	if (!save_commit_buffer)
+		return error("Could not expand oneline-name.");
+	for_each_ref(handle_one_ref, &list);
+	for (l = list; l; l = l->next)
+		commit_list_insert(l->item, &backup);
+	while (list) {
+		char *p;
+		struct commit *commit;
+
+		commit = pop_most_recent_commit(&list, ONELINE_SEEN);
+		parse_object(commit->object.sha1);
+		if (!commit->buffer || !(p = strstr(commit->buffer, "\n\n")))
+			continue;
+		if (!prefixcmp(p + 2, prefix)) {
+			hashcpy(sha1, commit->object.sha1);
+			retval = 0;
+			break;
+		}
+	}
+	free_commit_list(list);
+	for (l = backup; l; l = l->next)
+		clear_commit_marks(l->item, ONELINE_SEEN);
+	return retval;
+}
+
 /*
  * This is like "get_sha1_basic()", except it allows "sha1 expressions",
  * notably "xyz^" for "parent of xyz"
@@ -600,6 +660,8 @@
 		int stage = 0;
 		struct cache_entry *ce;
 		int pos;
+		if (namelen > 2 && name[1] == '/')
+			return get_sha1_oneline(name + 2, sha1);
 		if (namelen < 3 ||
 		    name[2] != ':' ||
 		    name[1] < '0' || '3' < name[1])
diff --git a/shell.c b/shell.c
index 8c08cf0..c983fc7 100644
--- a/shell.c
+++ b/shell.c
@@ -8,7 +8,7 @@
 
 	if (!arg || !(arg = sq_dequote(arg)))
 		die("bad argument");
-	if (strncmp(me, "git-", 4))
+	if (prefixcmp(me, "git-"))
 		die("bad command");
 
 	my_argv[0] = me + 4;
diff --git a/t/diff-lib.sh b/t/diff-lib.sh
index 745a1b0..4624fe6 100755
--- a/t/diff-lib.sh
+++ b/t/diff-lib.sh
@@ -11,7 +11,7 @@
 
     sed -e "$sanitize_diff_raw" <"$1" >.tmp-1
     sed -e "$sanitize_diff_raw" <"$2" >.tmp-2
-    diff -u .tmp-1 .tmp-2 && rm -f .tmp-1 .tmp-2
+    git diff .tmp-1 .tmp-2 && rm -f .tmp-1 .tmp-2
 }
 
 sanitize_diff_raw_z='/^:/s/ '"$_x40"' '"$_x40"' \([A-Z]\)[0-9]*$/ X X \1#/'
@@ -23,7 +23,7 @@
 
     tr '\0' '\012' <"$1" | sed -e "$sanitize_diff_raw_z" >.tmp-1
     tr '\0' '\012' <"$2" | sed -e "$sanitize_diff_raw_z" >.tmp-2
-    diff -u .tmp-1 .tmp-2 && rm -f .tmp-1 .tmp-2
+    git diff .tmp-1 .tmp-2 && rm -f .tmp-1 .tmp-2
 }
 
 compare_diff_patch () {
@@ -37,5 +37,5 @@
 	/^[dis]*imilarity index [0-9]*%$/d
 	/^index [0-9a-f]*\.\.[0-9a-f]/d
     ' <"$2" >.tmp-2
-    diff -u .tmp-1 .tmp-2 && rm -f .tmp-1 .tmp-2
+    git diff .tmp-1 .tmp-2 && rm -f .tmp-1 .tmp-2
 }
diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh
index 67d08cf..f6fe78c 100644
--- a/t/lib-git-svn.sh
+++ b/t/lib-git-svn.sh
@@ -42,9 +42,9 @@
 	exit
 fi
 
+rawsvnrepo="$svnrepo"
 svnrepo="file://$svnrepo"
 
-
 poke() {
-	perl -e '@x = stat($ARGV[0]); utime($x[8], $x[9] + 1, $ARGV[0])' "$1"
+	test-chmtime +1 "$1"
 }
diff --git a/t/t0020-crlf.sh b/t/t0020-crlf.sh
new file mode 100755
index 0000000..723b29a
--- /dev/null
+++ b/t/t0020-crlf.sh
@@ -0,0 +1,217 @@
+#!/bin/sh
+
+test_description='CRLF conversion'
+
+. ./test-lib.sh
+
+append_cr () {
+	sed -e 's/$/Q/' | tr Q '\015'
+}
+
+remove_cr () {
+	tr '\015' Q <"$1" | grep Q >/dev/null &&
+	tr '\015' Q <"$1" | sed -ne 's/Q$//p'
+}
+
+test_expect_success setup '
+
+	git repo-config core.autocrlf false &&
+
+	for w in Hello world how are you; do echo $w; done >one &&
+	mkdir dir &&
+	for w in I am very very fine thank you; do echo $w; done >dir/two &&
+	git add . &&
+
+	git commit -m initial &&
+
+	one=`git rev-parse HEAD:one` &&
+	dir=`git rev-parse HEAD:dir` &&
+	two=`git rev-parse HEAD:dir/two` &&
+
+	for w in Some extra lines here; do echo $w; done >>one &&
+	git diff >patch.file &&
+	patched=`git hash-object --stdin <one` &&
+	git read-tree --reset -u HEAD &&
+
+	echo happy.
+'
+
+test_expect_success 'update with autocrlf=input' '
+
+	rm -f tmp one dir/two &&
+	git read-tree --reset -u HEAD &&
+	git repo-config core.autocrlf input &&
+
+	for f in one dir/two
+	do
+		append_cr <$f >tmp && mv -f tmp $f &&
+		git update-index -- $f || {
+			echo Oops
+			false
+			break
+		}
+	done &&
+
+	differs=`git diff-index --cached HEAD` &&
+	test -z "$differs" || {
+		echo Oops "$differs"
+		false
+	}
+
+'
+
+test_expect_success 'update with autocrlf=true' '
+
+	rm -f tmp one dir/two &&
+	git read-tree --reset -u HEAD &&
+	git repo-config core.autocrlf true &&
+
+	for f in one dir/two
+	do
+		append_cr <$f >tmp && mv -f tmp $f &&
+		git update-index -- $f || {
+			echo "Oops $f"
+			false
+			break
+		}
+	done &&
+
+	differs=`git diff-index --cached HEAD` &&
+	test -z "$differs" || {
+		echo Oops "$differs"
+		false
+	}
+
+'
+
+test_expect_success 'checkout with autocrlf=true' '
+
+	rm -f tmp one dir/two &&
+	git repo-config core.autocrlf true &&
+	git read-tree --reset -u HEAD &&
+
+	for f in one dir/two
+	do
+		remove_cr "$f" >tmp && mv -f tmp $f &&
+		git update-index -- $f || {
+			echo "Eh? $f"
+			false
+			break
+		}
+	done &&
+	test "$one" = `git hash-object --stdin <one` &&
+	test "$two" = `git hash-object --stdin <dir/two` &&
+	differs=`git diff-index --cached HEAD` &&
+	test -z "$differs" || {
+		echo Oops "$differs"
+		false
+	}
+'
+
+test_expect_success 'checkout with autocrlf=input' '
+
+	rm -f tmp one dir/two &&
+	git repo-config core.autocrlf input &&
+	git read-tree --reset -u HEAD &&
+
+	for f in one dir/two
+	do
+		if remove_cr "$f" >/dev/null
+		then
+			echo "Eh? $f"
+			false
+			break
+		else
+			git update-index -- $f
+		fi
+	done &&
+	test "$one" = `git hash-object --stdin <one` &&
+	test "$two" = `git hash-object --stdin <dir/two` &&
+	differs=`git diff-index --cached HEAD` &&
+	test -z "$differs" || {
+		echo Oops "$differs"
+		false
+	}
+'
+
+test_expect_success 'apply patch (autocrlf=input)' '
+
+	rm -f tmp one dir/two &&
+	git repo-config core.autocrlf input &&
+	git read-tree --reset -u HEAD &&
+
+	git apply patch.file &&
+	test "$patched" = "`git hash-object --stdin <one`" || {
+		echo "Eh?  apply without index"
+		false
+	}
+'
+
+test_expect_success 'apply patch --cached (autocrlf=input)' '
+
+	rm -f tmp one dir/two &&
+	git repo-config core.autocrlf input &&
+	git read-tree --reset -u HEAD &&
+
+	git apply --cached patch.file &&
+	test "$patched" = `git rev-parse :one` || {
+		echo "Eh?  apply with --cached"
+		false
+	}
+'
+
+test_expect_success 'apply patch --index (autocrlf=input)' '
+
+	rm -f tmp one dir/two &&
+	git repo-config core.autocrlf input &&
+	git read-tree --reset -u HEAD &&
+
+	git apply --index patch.file &&
+	test "$patched" = `git rev-parse :one` &&
+	test "$patched" = `git hash-object --stdin <one` || {
+		echo "Eh?  apply with --index"
+		false
+	}
+'
+
+test_expect_success 'apply patch (autocrlf=true)' '
+
+	rm -f tmp one dir/two &&
+	git repo-config core.autocrlf true &&
+	git read-tree --reset -u HEAD &&
+
+	git apply patch.file &&
+	test "$patched" = "`remove_cr one | git hash-object --stdin`" || {
+		echo "Eh?  apply without index"
+		false
+	}
+'
+
+test_expect_success 'apply patch --cached (autocrlf=true)' '
+
+	rm -f tmp one dir/two &&
+	git repo-config core.autocrlf true &&
+	git read-tree --reset -u HEAD &&
+
+	git apply --cached patch.file &&
+	test "$patched" = `git rev-parse :one` || {
+		echo "Eh?  apply without index"
+		false
+	}
+'
+
+test_expect_success 'apply patch --index (autocrlf=true)' '
+
+	rm -f tmp one dir/two &&
+	git repo-config core.autocrlf true &&
+	git read-tree --reset -u HEAD &&
+
+	git apply --index patch.file &&
+	test "$patched" = `git rev-parse :one` &&
+	test "$patched" = "`remove_cr one | git hash-object --stdin`" || {
+		echo "Eh?  apply with --index"
+		false
+	}
+'
+
+test_done
diff --git a/t/t1000-read-tree-m-3way.sh b/t/t1000-read-tree-m-3way.sh
index d0af8c3..e26a36c 100755
--- a/t/t1000-read-tree-m-3way.sh
+++ b/t/t1000-read-tree-m-3way.sh
@@ -131,7 +131,7 @@
 
 check_result () {
     git-ls-files --stage | sed -e 's/ '"$_x40"' / X /' >current &&
-    diff -u expected current
+    git diff expected current
 }
 
 # This is done on an empty work directory, which is the normal
diff --git a/t/t1001-read-tree-m-2way.sh b/t/t1001-read-tree-m-2way.sh
index 75e4c9a..030226b 100755
--- a/t/t1001-read-tree-m-2way.sh
+++ b/t/t1001-read-tree-m-2way.sh
@@ -33,7 +33,7 @@
 	    -e '/^--- /d; /^+++ /d; /^@@ /d;' \
 	    -e 's/^\([-+][0-7][0-7][0-7][0-7][0-7][0-7]\) '"$_x40"' /\1 X /p' \
 	    "$1"
-	diff -u expected current
+	git diff expected current
 }
 
 check_cache_at () {
@@ -86,7 +86,7 @@
     'rm -f .git/index &&
      read_tree_twoway $treeH $treeM &&
      git-ls-files --stage >1-3.out &&
-     diff -u M.out 1-3.out &&
+     git diff M.out 1-3.out &&
      check_cache_at bozbar dirty &&
      check_cache_at frotz dirty &&
      check_cache_at nitfol dirty'
@@ -101,7 +101,7 @@
      git-update-index --add yomin &&
      read_tree_twoway $treeH $treeM &&
      git-ls-files --stage >4.out || return 1
-     diff -u M.out 4.out >4diff.out
+     git diff M.out 4.out >4diff.out
      compare_change 4diff.out expected &&
      check_cache_at yomin clean'
 
@@ -115,7 +115,7 @@
      echo yomin yomin >yomin &&
      read_tree_twoway $treeH $treeM &&
      git-ls-files --stage >5.out || return 1
-     diff -u M.out 5.out >5diff.out
+     git diff M.out 5.out >5diff.out
      compare_change 5diff.out expected &&
      check_cache_at yomin dirty'
 
@@ -127,7 +127,7 @@
      git-update-index --add frotz &&
      read_tree_twoway $treeH $treeM &&
      git-ls-files --stage >6.out &&
-     diff -u M.out 6.out &&
+     git diff M.out 6.out &&
      check_cache_at frotz clean'
 
 test_expect_success \
@@ -140,7 +140,7 @@
      echo frotz frotz >frotz &&
      read_tree_twoway $treeH $treeM &&
      git-ls-files --stage >7.out &&
-     diff -u M.out 7.out &&
+     git diff M.out 7.out &&
      check_cache_at frotz dirty'
 
 test_expect_success \
@@ -171,7 +171,7 @@
      git-update-index --add rezrov &&
      read_tree_twoway $treeH $treeM &&
      git-ls-files --stage >10.out &&
-     diff -u M.out 10.out'
+     git diff M.out 10.out'
 
 test_expect_success \
     '11 - dirty path removed.' \
@@ -216,7 +216,7 @@
      git-update-index --add nitfol &&
      read_tree_twoway $treeH $treeM &&
      git-ls-files --stage >14.out || return 1
-     diff -u M.out 14.out >14diff.out
+     git diff M.out 14.out >14diff.out
      compare_change 14diff.out expected &&
      check_cache_at nitfol clean'
 
@@ -230,7 +230,7 @@
      echo nitfol nitfol nitfol >nitfol &&
      read_tree_twoway $treeH $treeM &&
      git-ls-files --stage >15.out || return 1
-     diff -u M.out 15.out >15diff.out
+     git diff M.out 15.out >15diff.out
      compare_change 15diff.out expected &&
      check_cache_at nitfol dirty'
 
@@ -262,7 +262,7 @@
      git-update-index --add bozbar &&
      read_tree_twoway $treeH $treeM &&
      git-ls-files --stage >18.out &&
-     diff -u M.out 18.out &&
+     git diff M.out 18.out &&
      check_cache_at bozbar clean'
 
 test_expect_success \
@@ -275,7 +275,7 @@
      echo gnusto gnusto >bozbar &&
      read_tree_twoway $treeH $treeM &&
      git-ls-files --stage >19.out &&
-     diff -u M.out 19.out &&
+     git diff M.out 19.out &&
      check_cache_at bozbar dirty'
 
 test_expect_success \
@@ -287,7 +287,7 @@
      git-update-index --add bozbar &&
      read_tree_twoway $treeH $treeM &&
      git-ls-files --stage >20.out &&
-     diff -u M.out 20.out &&
+     git diff M.out 20.out &&
      check_cache_at bozbar dirty'
 
 test_expect_success \
@@ -337,7 +337,7 @@
      git-update-index --add DF &&
      read_tree_twoway $treeDF $treeDFDF &&
      git-ls-files --stage >DFDFcheck.out &&
-     diff -u DFDF.out DFDFcheck.out &&
+     git diff DFDF.out DFDFcheck.out &&
      check_cache_at DF/DF dirty &&
      :'
 
diff --git a/t/t1002-read-tree-m-u-2way.sh b/t/t1002-read-tree-m-u-2way.sh
index da3c813..87fe993 100755
--- a/t/t1002-read-tree-m-u-2way.sh
+++ b/t/t1002-read-tree-m-u-2way.sh
@@ -16,7 +16,7 @@
 	sed >current \
 	    -e '/^--- /d; /^+++ /d; /^@@ /d;' \
 	    -e 's/^\(.[0-7][0-7][0-7][0-7][0-7][0-7]\) '"$_x40"' /\1 X /' "$1"
-	diff -u expected current
+	git diff expected current
 }
 
 check_cache_at () {
diff --git a/t/t1200-tutorial.sh b/t/t1200-tutorial.sh
index eebe643..ca2c30f 100755
--- a/t/t1200-tutorial.sh
+++ b/t/t1200-tutorial.sh
@@ -101,7 +101,9 @@
 echo "Lots of fun" >>example
 git commit -m 'Some fun.' -i hello example
 
-test_expect_failure 'git resolve now fails' 'git resolve HEAD mybranch "Merge work in mybranch"'
+test_expect_failure 'git resolve now fails' '
+	git merge -m "Merge work in mybranch" mybranch
+'
 
 cat > hello << EOF
 Hello World
@@ -134,8 +136,8 @@
  2 files changed, 2 insertions(+), 0 deletions(-)
 EOF
 
-git resolve HEAD master "Merge upstream changes." | \
-	sed -e "1s/[0-9a-f]\{40\}/VARIABLE/g" > resolve.output
+git merge -s "Merge upstream changes." master | \
+	sed -e "1s/[0-9a-f]\{40\}/VARIABLE/g" >resolve.output
 test_expect_success 'git resolve' 'cmp resolve.expect resolve.output'
 
 cat > show-branch2.expect << EOF
diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh
index 49b5666..78c2e08 100755
--- a/t/t1300-repo-config.sh
+++ b/t/t1300-repo-config.sh
@@ -368,12 +368,12 @@
 weird
 EOF
 
-test_expect_success "rename succeeded" "diff -u expect .git/config"
+test_expect_success "rename succeeded" "git diff expect .git/config"
 
 test_expect_failure "rename non-existing section" \
 	'git-config --rename-section branch."world domination" branch.drei'
 
-test_expect_success "rename succeeded" "diff -u expect .git/config"
+test_expect_success "rename succeeded" "git diff expect .git/config"
 
 test_expect_success "rename another section" \
 	'git-config --rename-section branch."1 234 blabl/a" branch.drei'
@@ -389,7 +389,23 @@
 weird
 EOF
 
-test_expect_success "rename succeeded" "diff -u expect .git/config"
+test_expect_success "rename succeeded" "git diff expect .git/config"
+
+cat >> .git/config << EOF
+  [branch "zwei"] a = 1 [branch "vier"]
+EOF
+
+test_expect_success "remove section" "git config --remove-section branch.zwei"
+
+cat > expect << EOF
+# Hallo
+	#Bello
+[branch "drei"]
+weird
+EOF
+
+test_expect_success "section was removed properly" \
+	"git diff -u expect .git/config"
 
 test_expect_success numbers '
 
diff --git a/t/t2005-checkout-index-symlinks.sh b/t/t2005-checkout-index-symlinks.sh
new file mode 100755
index 0000000..e34a515
--- /dev/null
+++ b/t/t2005-checkout-index-symlinks.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes Sixt
+#
+
+test_description='git-checkout-index on filesystem w/o symlinks test.
+
+This tests that git-checkout-index creates a symbolic link as a plain
+file if core.symlinks is false.'
+
+. ./test-lib.sh
+
+test_expect_success \
+'preparation' '
+git-config core.symlinks false &&
+l=$(echo -n file | git-hash-object -t blob -w --stdin) &&
+echo "120000 $l	symlink" | git-update-index --index-info'
+
+test_expect_success \
+'the checked-out symlink must be a file' '
+git-checkout-index symlink &&
+test -f symlink'
+
+test_expect_success \
+'the file must be the blob we added during the setup' '
+test "$(git-hash-object -t blob symlink)" = $l'
+
+test_done
diff --git a/t/t2102-update-index-symlinks.sh b/t/t2102-update-index-symlinks.sh
new file mode 100755
index 0000000..969ef89
--- /dev/null
+++ b/t/t2102-update-index-symlinks.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes Sixt
+#
+
+test_description='git-update-index on filesystem w/o symlinks test.
+
+This tests that git-update-index keeps the symbolic link property
+even if a plain file is in the working tree if core.symlinks is false.'
+
+. ./test-lib.sh
+
+test_expect_success \
+'preparation' '
+git-config core.symlinks false &&
+l=$(echo -n file | git-hash-object -t blob -w --stdin) &&
+echo "120000 $l	symlink" | git-update-index --index-info'
+
+test_expect_success \
+'modify the symbolic link' '
+echo -n new-file > symlink &&
+git-update-index symlink'
+
+test_expect_success \
+'the index entry must still be a symbolic link' '
+case "`git-ls-files --stage --cached symlink`" in
+120000" "*symlink) echo ok;;
+*) echo fail; git-ls-files --stage --cached symlink; (exit 1);;
+esac'
+
+test_done
diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh
index 6979b7c..db7a847 100755
--- a/t/t3001-ls-files-others-exclude.sh
+++ b/t/t3001-ls-files-others-exclude.sh
@@ -65,7 +65,7 @@
        --exclude-per-directory=.gitignore \
        --exclude-from=.git/ignore \
        >output &&
-     diff -u expect output'
+     git diff expect output'
 
 # Test \r\n (MSDOS-like systems)
 printf '*.1\r\n/*.3\r\n!*.6\r\n' >.gitignore
@@ -77,6 +77,6 @@
        --exclude-per-directory=.gitignore \
        --exclude-from=.git/ignore \
        >output &&
-     diff -u expect output'
+     git diff expect output'
 
 test_done
diff --git a/t/t3002-ls-files-dashpath.sh b/t/t3002-ls-files-dashpath.sh
index b42f138..cc8967d 100755
--- a/t/t3002-ls-files-dashpath.sh
+++ b/t/t3002-ls-files-dashpath.sh
@@ -23,7 +23,7 @@
 test_expect_success \
     'git-ls-files without path restriction.' \
     'git-ls-files --others >output &&
-     diff -u output - <<EOF
+     git diff output - <<EOF
 --
 -foo
 output
@@ -34,7 +34,7 @@
 test_expect_success \
     'git-ls-files with path restriction.' \
     'git-ls-files --others path0 >output &&
-	diff -u output - <<EOF
+	git diff output - <<EOF
 path0
 EOF
 '
@@ -42,7 +42,7 @@
 test_expect_success \
     'git-ls-files with path restriction with --.' \
     'git-ls-files --others -- path0 >output &&
-	diff -u output - <<EOF
+	git diff output - <<EOF
 path0
 EOF
 '
@@ -50,7 +50,7 @@
 test_expect_success \
     'git-ls-files with path restriction with -- --.' \
     'git-ls-files --others -- -- >output &&
-	diff -u output - <<EOF
+	git diff output - <<EOF
 --
 EOF
 '
@@ -58,7 +58,7 @@
 test_expect_success \
     'git-ls-files with no path restriction.' \
     'git-ls-files --others -- >output &&
-	diff -u output - <<EOF
+	git diff output - <<EOF
 --
 -foo
 output
diff --git a/t/t3100-ls-tree-restrict.sh b/t/t3100-ls-tree-restrict.sh
index 2ec06d3..e107492 100755
--- a/t/t3100-ls-tree-restrict.sh
+++ b/t/t3100-ls-tree-restrict.sh
@@ -35,7 +35,7 @@
 _x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
 test_output () {
     sed -e "s/ $_x40	/ X	/" <current >check
-    diff -u expected check
+    git diff expected check
 }
 
 test_expect_success \
diff --git a/t/t3101-ls-tree-dirname.sh b/t/t3101-ls-tree-dirname.sh
index d78deb1..087929a 100755
--- a/t/t3101-ls-tree-dirname.sh
+++ b/t/t3101-ls-tree-dirname.sh
@@ -43,7 +43,7 @@
 _x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
 test_output () {
     sed -e "s/ $_x40	/ X	/" <current >check
-    diff -u expected check
+    git diff expected check
 }
 
 test_expect_success \
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index 5565c27..9558bdb 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -47,17 +47,6 @@
 	 test ! -f .git/refs/heads/d/e/f &&
 	 test ! -f .git/logs/refs/heads/d/e/f'
 
-cat >expect <<EOF
-0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000	checkout: Created from master
-EOF
-test_expect_success \
-    'git checkout -b g/h/i -l should create a branch and a log' \
-	'GIT_COMMITTER_DATE="2005-05-26 23:30" \
-     git-checkout -b g/h/i -l master &&
-	 test -f .git/refs/heads/g/h/i &&
-	 test -f .git/logs/refs/heads/g/h/i &&
-	 diff expect .git/logs/refs/heads/g/h/i'
-
 test_expect_success \
     'git branch j/k should work after branch j has been deleted' \
        'git-branch j &&
@@ -117,4 +106,64 @@
      ln -s real-u .git/logs/refs/heads/u &&
      git-branch -m u v'
 
+test_expect_success 'test tracking setup via --track' \
+    'git-config remote.local.url . &&
+     git-config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
+     (git-show-ref -q refs/remotes/local/master || git-fetch local) &&
+     git-branch --track my1 local/master &&
+     test $(git-config branch.my1.remote) = local &&
+     test $(git-config branch.my1.merge) = refs/heads/master'
+
+test_expect_success 'test tracking setup (non-wildcard, matching)' \
+    'git-config remote.local.url . &&
+     git-config remote.local.fetch refs/heads/master:refs/remotes/local/master &&
+     (git-show-ref -q refs/remotes/local/master || git-fetch local) &&
+     git-branch --track my4 local/master &&
+     test $(git-config branch.my4.remote) = local &&
+     test $(git-config branch.my4.merge) = refs/heads/master'
+
+test_expect_success 'test tracking setup (non-wildcard, not matching)' \
+    'git-config remote.local.url . &&
+     git-config remote.local.fetch refs/heads/s:refs/remotes/local/s &&
+     (git-show-ref -q refs/remotes/local/master || git-fetch local) &&
+     git-branch --track my5 local/master &&
+     ! test $(git-config branch.my5.remote) = local &&
+     ! test $(git-config branch.my5.merge) = refs/heads/master'
+
+test_expect_success 'test tracking setup via config' \
+    'git-config branch.autosetupmerge true &&
+     git-config remote.local.url . &&
+     git-config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
+     (git-show-ref -q refs/remotes/local/master || git-fetch local) &&
+     git-branch my3 local/master &&
+     test $(git-config branch.my3.remote) = local &&
+     test $(git-config branch.my3.merge) = refs/heads/master'
+
+test_expect_success 'test overriding tracking setup via --no-track' \
+    'git-config branch.autosetupmerge true &&
+     git-config remote.local.url . &&
+     git-config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
+     (git-show-ref -q refs/remotes/local/master || git-fetch local) &&
+     git-branch --no-track my2 local/master &&
+     git-config branch.autosetupmerge false &&
+     ! test $(git-config branch.my2.remote) = local &&
+     ! test $(git-config branch.my2.merge) = refs/heads/master'
+
+test_expect_success 'test local tracking setup' \
+    'git branch --track my6 s &&
+     test $(git-config branch.my6.remote) = . &&
+     test $(git-config branch.my6.merge) = refs/heads/s'
+
+# Keep this test last, as it changes the current branch
+cat >expect <<EOF
+0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000	branch: Created from master
+EOF
+test_expect_success \
+    'git checkout -b g/h/i -l should create a branch and a log' \
+	'GIT_COMMITTER_DATE="2005-05-26 23:30" \
+     git-checkout -b g/h/i -l master &&
+	 test -f .git/refs/heads/g/h/i &&
+	 test -f .git/logs/refs/heads/g/h/i &&
+	 diff expect .git/logs/refs/heads/g/h/i'
+
 test_done
diff --git a/t/t3300-funny-names.sh b/t/t3300-funny-names.sh
index c12270e..b5a1400 100755
--- a/t/t3300-funny-names.sh
+++ b/t/t3300-funny-names.sh
@@ -35,7 +35,7 @@
 test_expect_success 'git-ls-files no-funny' \
 	'git-update-index --add "$p0" "$p2" &&
 	git-ls-files >current &&
-	diff -u expected current'
+	git diff expected current'
 
 t0=`git-write-tree`
 echo "$t0" >t0
@@ -48,14 +48,14 @@
 test_expect_success 'git-ls-files with-funny' \
 	'git-update-index --add "$p1" &&
 	git-ls-files >current &&
-	diff -u expected current'
+	git diff expected current'
 
 echo 'just space
 no-funny
 tabs	," (dq) and spaces' >expected
 test_expect_success 'git-ls-files -z with-funny' \
 	'git-ls-files -z | tr \\0 \\012 >current &&
-	diff -u expected current'
+	git diff expected current'
 
 t1=`git-write-tree`
 echo "$t1" >t1
@@ -67,28 +67,28 @@
 EOF
 test_expect_success 'git-ls-tree with funny' \
 	'git-ls-tree -r $t1 | sed -e "s/^[^	]*	//" >current &&
-	 diff -u expected current'
+	 git diff expected current'
 
 cat > expected <<\EOF
 A	"tabs\t,\" (dq) and spaces"
 EOF
 test_expect_success 'git-diff-index with-funny' \
 	'git-diff-index --name-status $t0 >current &&
-	diff -u expected current'
+	git diff expected current'
 
 test_expect_success 'git-diff-tree with-funny' \
 	'git-diff-tree --name-status $t0 $t1 >current &&
-	diff -u expected current'
+	git diff expected current'
 
 echo 'A
 tabs	," (dq) and spaces' >expected
 test_expect_success 'git-diff-index -z with-funny' \
 	'git-diff-index -z --name-status $t0 | tr \\0 \\012 >current &&
-	diff -u expected current'
+	git diff expected current'
 
 test_expect_success 'git-diff-tree -z with-funny' \
 	'git-diff-tree -z --name-status $t0 $t1 | tr \\0 \\012 >current &&
-	diff -u expected current'
+	git diff expected current'
 
 cat > expected <<\EOF
 CNUM	no-funny	"tabs\t,\" (dq) and spaces"
@@ -96,7 +96,7 @@
 test_expect_success 'git-diff-tree -C with-funny' \
 	'git-diff-tree -C --find-copies-harder --name-status \
 		$t0 $t1 | sed -e 's/^C[0-9]*/CNUM/' >current &&
-	diff -u expected current'
+	git diff expected current'
 
 cat > expected <<\EOF
 RNUM	no-funny	"tabs\t,\" (dq) and spaces"
@@ -105,7 +105,7 @@
 	'git-update-index --force-remove "$p0" &&
 	git-diff-index -M --name-status \
 		$t0 | sed -e 's/^R[0-9]*/RNUM/' >current &&
-	diff -u expected current'
+	git diff expected current'
 
 cat > expected <<\EOF
 diff --git a/no-funny "b/tabs\t,\" (dq) and spaces"
@@ -116,7 +116,7 @@
 test_expect_success 'git-diff-tree delete with-funny' \
 	'git-diff-index -M -p $t0 |
 	 sed -e "s/index [0-9]*%/index NUM%/" >current &&
-	 diff -u expected current'
+	 git diff expected current'
 
 chmod +x "$p1"
 cat > expected <<\EOF
@@ -130,7 +130,7 @@
 test_expect_success 'git-diff-tree delete with-funny' \
 	'git-diff-index -M -p $t0 |
 	 sed -e "s/index [0-9]*%/index NUM%/" >current &&
-	 diff -u expected current'
+	 git diff expected current'
 
 cat >expected <<\EOF
  "tabs\t,\" (dq) and spaces"
@@ -139,7 +139,7 @@
 test_expect_success 'git-diff-tree rename with-funny applied' \
 	'git-diff-index -M -p $t0 |
 	 git-apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
-	 diff -u expected current'
+	 git diff expected current'
 
 cat > expected <<\EOF
  no-funny
@@ -149,12 +149,12 @@
 test_expect_success 'git-diff-tree delete with-funny applied' \
 	'git-diff-index -p $t0 |
 	 git-apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
-	 diff -u expected current'
+	 git diff expected current'
 
 test_expect_success 'git-apply non-git diff' \
 	'git-diff-index -p $t0 |
 	 sed -ne "/^[-+@]/p" |
 	 git-apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
-	 diff -u expected current'
+	 git diff expected current'
 
 test_done
diff --git a/t/t3900-i18n-commit.sh b/t/t3900-i18n-commit.sh
index e54fe0f..ffddb68 100755
--- a/t/t3900-i18n-commit.sh
+++ b/t/t3900-i18n-commit.sh
@@ -9,7 +9,7 @@
 
 compare_with () {
 	git-show -s $1 | sed -e '1,/^$/d' -e 's/^    //' -e '$d' >current &&
-	diff -u current "$2"
+	git diff current "$2"
 }
 
 test_expect_success setup '
diff --git a/t/t4006-diff-mode.sh b/t/t4006-diff-mode.sh
index ca342f4..e72c6fd 100755
--- a/t/t4006-diff-mode.sh
+++ b/t/t4006-diff-mode.sh
@@ -38,7 +38,7 @@
 
 test_expect_success \
     'verify' \
-    'diff -u expected check'
+    'git diff expected check'
 
 test_done
 
diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh
index 3d85cea..488e075 100755
--- a/t/t4013-diff-various.sh
+++ b/t/t4013-diff-various.sh
@@ -106,12 +106,12 @@
 			echo "\$ git $cmd"
 			git $cmd |
 			sed -e "s/^\\(-*\\)$V\\(-*\\)\$/\\1g-i-t--v-e-r-s-i-o-n\2/" \
-			    -e "s/^\\( *boundary=\"-*\\)$V\\(-*\\)\"\$/\\1g-i-t--v-e-r-s-i-o-n\2\"/"
+			    -e "s/^\\(.*mixed; boundary=\"-*\\)$V\\(-*\\)\"\$/\\1g-i-t--v-e-r-s-i-o-n\2\"/"
 			echo "\$"
 		} >"$actual" &&
 		if test -f "$expect"
 		then
-			diff -u "$expect" "$actual" &&
+			git diff "$expect" "$actual" &&
 			rm -f "$actual"
 		else
 			# this is to help developing new tests.
@@ -238,6 +238,9 @@
 format-patch --attach --stdout initial..side
 format-patch --attach --stdout initial..master^
 format-patch --attach --stdout initial..master
+format-patch --inline --stdout initial..side
+format-patch --inline --stdout initial..master^
+format-patch --inline --stdout initial..master
 
 diff --abbrev initial..side
 diff -r initial..side
diff --git a/t/t4013/diff.format-patch_--attach_--stdout_initial..master b/t/t4013/diff.format-patch_--attach_--stdout_initial..master
index e5ddd6f..cf6891f 100644
--- a/t/t4013/diff.format-patch_--attach_--stdout_initial..master
+++ b/t/t4013/diff.format-patch_--attach_--stdout_initial..master
@@ -4,8 +4,7 @@
 Date: Mon, 26 Jun 2006 00:01:00 +0000
 Subject: [PATCH] Second
 MIME-Version: 1.0
-Content-Type: multipart/mixed;
- boundary="------------g-i-t--v-e-r-s-i-o-n"
+Content-Type: multipart/mixed; boundary="------------g-i-t--v-e-r-s-i-o-n"
 
 This is a multi-part message in MIME format.
 --------------g-i-t--v-e-r-s-i-o-n
@@ -21,11 +20,9 @@
  3 files changed, 5 insertions(+), 3 deletions(-)
  delete mode 100644 file2
 --------------g-i-t--v-e-r-s-i-o-n
-Content-Type: text/x-patch;
- name="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+Content-Type: text/x-patch; name="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
 Content-Transfer-Encoding: 8bit
-Content-Disposition: inline;
- filename="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+Content-Disposition: attachment; filename="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..8422d40 100644
@@ -66,8 +63,7 @@
 Date: Mon, 26 Jun 2006 00:02:00 +0000
 Subject: [PATCH] Third
 MIME-Version: 1.0
-Content-Type: multipart/mixed;
- boundary="------------g-i-t--v-e-r-s-i-o-n"
+Content-Type: multipart/mixed; boundary="------------g-i-t--v-e-r-s-i-o-n"
 
 This is a multi-part message in MIME format.
 --------------g-i-t--v-e-r-s-i-o-n
@@ -80,11 +76,9 @@
  2 files changed, 5 insertions(+), 0 deletions(-)
  create mode 100644 file1
 --------------g-i-t--v-e-r-s-i-o-n
-Content-Type: text/x-patch;
- name="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
+Content-Type: text/x-patch; name="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
 Content-Transfer-Encoding: 8bit
-Content-Disposition: inline;
- filename="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
+Content-Disposition: attachment; filename="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
 
 diff --git a/dir/sub b/dir/sub
 index 8422d40..cead32e 100644
@@ -115,8 +109,7 @@
 Date: Mon, 26 Jun 2006 00:03:00 +0000
 Subject: [PATCH] Side
 MIME-Version: 1.0
-Content-Type: multipart/mixed;
- boundary="------------g-i-t--v-e-r-s-i-o-n"
+Content-Type: multipart/mixed; boundary="------------g-i-t--v-e-r-s-i-o-n"
 
 This is a multi-part message in MIME format.
 --------------g-i-t--v-e-r-s-i-o-n
@@ -130,11 +123,9 @@
  3 files changed, 9 insertions(+), 0 deletions(-)
  create mode 100644 file3
 --------------g-i-t--v-e-r-s-i-o-n
-Content-Type: text/x-patch;
- name="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
+Content-Type: text/x-patch; name="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
 Content-Transfer-Encoding: 8bit
-Content-Disposition: inline;
- filename="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
+Content-Disposition: attachment; filename="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
diff --git a/t/t4013/diff.format-patch_--attach_--stdout_initial..master^ b/t/t4013/diff.format-patch_--attach_--stdout_initial..master^
index d0dd19b..fe02587 100644
--- a/t/t4013/diff.format-patch_--attach_--stdout_initial..master^
+++ b/t/t4013/diff.format-patch_--attach_--stdout_initial..master^
@@ -4,8 +4,7 @@
 Date: Mon, 26 Jun 2006 00:01:00 +0000
 Subject: [PATCH] Second
 MIME-Version: 1.0
-Content-Type: multipart/mixed;
- boundary="------------g-i-t--v-e-r-s-i-o-n"
+Content-Type: multipart/mixed; boundary="------------g-i-t--v-e-r-s-i-o-n"
 
 This is a multi-part message in MIME format.
 --------------g-i-t--v-e-r-s-i-o-n
@@ -21,11 +20,9 @@
  3 files changed, 5 insertions(+), 3 deletions(-)
  delete mode 100644 file2
 --------------g-i-t--v-e-r-s-i-o-n
-Content-Type: text/x-patch;
- name="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+Content-Type: text/x-patch; name="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
 Content-Transfer-Encoding: 8bit
-Content-Disposition: inline;
- filename="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+Content-Disposition: attachment; filename="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..8422d40 100644
@@ -66,8 +63,7 @@
 Date: Mon, 26 Jun 2006 00:02:00 +0000
 Subject: [PATCH] Third
 MIME-Version: 1.0
-Content-Type: multipart/mixed;
- boundary="------------g-i-t--v-e-r-s-i-o-n"
+Content-Type: multipart/mixed; boundary="------------g-i-t--v-e-r-s-i-o-n"
 
 This is a multi-part message in MIME format.
 --------------g-i-t--v-e-r-s-i-o-n
@@ -80,11 +76,9 @@
  2 files changed, 5 insertions(+), 0 deletions(-)
  create mode 100644 file1
 --------------g-i-t--v-e-r-s-i-o-n
-Content-Type: text/x-patch;
- name="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
+Content-Type: text/x-patch; name="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
 Content-Transfer-Encoding: 8bit
-Content-Disposition: inline;
- filename="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
+Content-Disposition: attachment; filename="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
 
 diff --git a/dir/sub b/dir/sub
 index 8422d40..cead32e 100644
diff --git a/t/t4013/diff.format-patch_--attach_--stdout_initial..side b/t/t4013/diff.format-patch_--attach_--stdout_initial..side
index 67a95c5..9ff828e 100644
--- a/t/t4013/diff.format-patch_--attach_--stdout_initial..side
+++ b/t/t4013/diff.format-patch_--attach_--stdout_initial..side
@@ -4,8 +4,7 @@
 Date: Mon, 26 Jun 2006 00:03:00 +0000
 Subject: [PATCH] Side
 MIME-Version: 1.0
-Content-Type: multipart/mixed;
- boundary="------------g-i-t--v-e-r-s-i-o-n"
+Content-Type: multipart/mixed; boundary="------------g-i-t--v-e-r-s-i-o-n"
 
 This is a multi-part message in MIME format.
 --------------g-i-t--v-e-r-s-i-o-n
@@ -19,11 +18,9 @@
  3 files changed, 9 insertions(+), 0 deletions(-)
  create mode 100644 file3
 --------------g-i-t--v-e-r-s-i-o-n
-Content-Type: text/x-patch;
- name="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
+Content-Type: text/x-patch; name="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
 Content-Transfer-Encoding: 8bit
-Content-Disposition: inline;
- filename="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
+Content-Disposition: attachment; filename="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
diff --git a/t/t4013/diff.format-patch_--inline_--stdout_initial..master b/t/t4013/diff.format-patch_--inline_--stdout_initial..master
new file mode 100644
index 0000000..aa110c0
--- /dev/null
+++ b/t/t4013/diff.format-patch_--inline_--stdout_initial..master
@@ -0,0 +1,164 @@
+$ git format-patch --inline --stdout initial..master
+From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:01:00 +0000
+Subject: [PATCH] Second
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="------------g-i-t--v-e-r-s-i-o-n"
+
+This is a multi-part message in MIME format.
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/plain; charset=UTF-8; format=fixed
+Content-Transfer-Encoding: 8bit
+
+
+This is the second commit.
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+ delete mode 100644 file2
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/x-patch; name="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline; filename="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+--------------g-i-t--v-e-r-s-i-o-n--
+
+
+
+From 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:02:00 +0000
+Subject: [PATCH] Third
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="------------g-i-t--v-e-r-s-i-o-n"
+
+This is a multi-part message in MIME format.
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/plain; charset=UTF-8; format=fixed
+Content-Transfer-Encoding: 8bit
+
+---
+ dir/sub |    2 ++
+ file1   |    3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+ create mode 100644 file1
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/x-patch; name="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline; filename="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+--------------g-i-t--v-e-r-s-i-o-n--
+
+
+
+From c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:03:00 +0000
+Subject: [PATCH] Side
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="------------g-i-t--v-e-r-s-i-o-n"
+
+This is a multi-part message in MIME format.
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/plain; charset=UTF-8; format=fixed
+Content-Transfer-Encoding: 8bit
+
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file3   |    4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+ create mode 100644 file3
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/x-patch; name="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline; filename="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+--------------g-i-t--v-e-r-s-i-o-n--
+
+
+$
diff --git a/t/t4013/diff.format-patch_--inline_--stdout_initial..master^ b/t/t4013/diff.format-patch_--inline_--stdout_initial..master^
new file mode 100644
index 0000000..95e9ea4
--- /dev/null
+++ b/t/t4013/diff.format-patch_--inline_--stdout_initial..master^
@@ -0,0 +1,106 @@
+$ git format-patch --inline --stdout initial..master^
+From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:01:00 +0000
+Subject: [PATCH] Second
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="------------g-i-t--v-e-r-s-i-o-n"
+
+This is a multi-part message in MIME format.
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/plain; charset=UTF-8; format=fixed
+Content-Transfer-Encoding: 8bit
+
+
+This is the second commit.
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+ delete mode 100644 file2
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/x-patch; name="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline; filename="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+--------------g-i-t--v-e-r-s-i-o-n--
+
+
+
+From 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:02:00 +0000
+Subject: [PATCH] Third
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="------------g-i-t--v-e-r-s-i-o-n"
+
+This is a multi-part message in MIME format.
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/plain; charset=UTF-8; format=fixed
+Content-Transfer-Encoding: 8bit
+
+---
+ dir/sub |    2 ++
+ file1   |    3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+ create mode 100644 file1
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/x-patch; name="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline; filename="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+--------------g-i-t--v-e-r-s-i-o-n--
+
+
+$
diff --git a/t/t4013/diff.format-patch_--inline_--stdout_initial..side b/t/t4013/diff.format-patch_--inline_--stdout_initial..side
new file mode 100644
index 0000000..86ae923
--- /dev/null
+++ b/t/t4013/diff.format-patch_--inline_--stdout_initial..side
@@ -0,0 +1,59 @@
+$ git format-patch --inline --stdout initial..side
+From c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:03:00 +0000
+Subject: [PATCH] Side
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="------------g-i-t--v-e-r-s-i-o-n"
+
+This is a multi-part message in MIME format.
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/plain; charset=UTF-8; format=fixed
+Content-Transfer-Encoding: 8bit
+
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file3   |    4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+ create mode 100644 file3
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/x-patch; name="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline; filename="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+--------------g-i-t--v-e-r-s-i-o-n--
+
+
+$
diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh
index adf4993..930e209 100755
--- a/t/t4015-diff-whitespace.sh
+++ b/t/t4015-diff-whitespace.sh
@@ -43,13 +43,13 @@
 EOF
 
 git-diff > out
-test_expect_success "Ray's example without options" 'diff -u expect out'
+test_expect_success "Ray's example without options" 'git diff expect out'
 
 git-diff -w > out
-test_expect_success "Ray's example with -w" 'diff -u expect out'
+test_expect_success "Ray's example with -w" 'git diff expect out'
 
 git-diff -b > out
-test_expect_success "Ray's example with -b" 'diff -u expect out'
+test_expect_success "Ray's example with -b" 'git diff expect out'
 
 tr 'Q' '\015' << EOF > x
 whitespace at beginning
@@ -90,14 +90,14 @@
 +CR at end
 EOF
 git-diff > out
-test_expect_success 'another test, without options' 'diff -u expect out'
+test_expect_success 'another test, without options' 'git diff expect out'
 
 cat << EOF > expect
 diff --git a/x b/x
 index d99af23..8b32fb5 100644
 EOF
 git-diff -w > out
-test_expect_success 'another test, with -w' 'diff -u expect out'
+test_expect_success 'another test, with -w' 'git diff expect out'
 
 tr 'Q' '\015' << EOF > expect
 diff --git a/x b/x
@@ -115,6 +115,6 @@
  CR at endQ
 EOF
 git-diff -b > out
-test_expect_success 'another test, with -b' 'diff -u expect out'
+test_expect_success 'another test, with -b' 'git diff expect out'
 
 test_done
diff --git a/t/t4016-diff-quote.sh b/t/t4016-diff-quote.sh
index edde8f5..5dbdc0c 100755
--- a/t/t4016-diff-quote.sh
+++ b/t/t4016-diff-quote.sh
@@ -13,6 +13,10 @@
 P2='pathname with SP'
 P3='pathname
 with LF'
+: >"$P1" 2>&1 && test -f "$P1" && rm -f "$P1" || {
+	echo >&2 'Filesystem does not support tabs in names'
+	test_done
+}
 
 test_expect_success setup '
 	echo P0.0 >"$P0.0" &&
@@ -45,7 +49,7 @@
 EOF
 test_expect_success 'git diff --summary -M HEAD' '
 	git diff --summary -M HEAD >actual &&
-	diff -u expect actual
+	git diff expect actual
 '
 
 cat >expect <<\EOF
@@ -60,7 +64,7 @@
 EOF
 test_expect_success 'git diff --stat -M HEAD' '
 	git diff --stat -M HEAD >actual &&
-	diff -u expect actual
+	git diff expect actual
 '
 
 test_done
diff --git a/t/t4017-diff-retval.sh b/t/t4017-diff-retval.sh
new file mode 100755
index 0000000..6873190
--- /dev/null
+++ b/t/t4017-diff-retval.sh
@@ -0,0 +1,79 @@
+#!/bin/sh
+
+test_description='Return value of diffs'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	echo 1 >a &&
+	git add . &&
+	git commit -m first &&
+	echo 2 >b &&
+	git add . &&
+	git commit -a -m second
+'
+
+test_expect_success 'git diff-tree HEAD^ HEAD' '
+	git diff-tree --exit-code HEAD^ HEAD
+	test $? = 1
+'
+test_expect_success 'git diff-tree HEAD^ HEAD -- a' '
+	git diff-tree --exit-code HEAD^ HEAD -- a
+	test $? = 0
+'
+test_expect_success 'git diff-tree HEAD^ HEAD -- b' '
+	git diff-tree --exit-code HEAD^ HEAD -- b
+	test $? = 1
+'
+test_expect_success 'echo HEAD | git diff-tree --stdin' '
+	echo $(git rev-parse HEAD) | git diff-tree --exit-code --stdin
+	test $? = 1
+'
+test_expect_success 'git diff-tree HEAD HEAD' '
+	git diff-tree --exit-code HEAD HEAD
+	test $? = 0
+'
+test_expect_success 'git diff-files' '
+	git diff-files --exit-code
+	test $? = 0
+'
+test_expect_success 'git diff-index --cached HEAD' '
+	git diff-index --exit-code --cached HEAD
+	test $? = 0
+'
+test_expect_success 'git diff-index --cached HEAD^' '
+	git diff-index --exit-code --cached HEAD^
+	test $? = 1
+'
+test_expect_success 'git diff-index --cached HEAD^' '
+	echo text >>b &&
+	echo 3 >c &&
+	git add . && {
+		git diff-index --exit-code --cached HEAD^
+		test $? = 1
+	}
+'
+test_expect_success 'git diff-tree -Stext HEAD^ HEAD -- b' '
+	git commit -m "text in b" && {
+		git diff-tree -p --exit-code -Stext HEAD^ HEAD -- b
+		test $? = 1
+	}
+'
+test_expect_success 'git diff-tree -Snot-found HEAD^ HEAD -- b' '
+	git diff-tree -p --exit-code -Snot-found HEAD^ HEAD -- b
+	test $? = 0
+'
+test_expect_success 'git diff-files' '
+	echo 3 >>c && {
+		git diff-files --exit-code
+		test $? = 1
+	}
+'
+test_expect_success 'git diff-index --cached HEAD' '
+	git update-index c && {
+		git diff-index --exit-code --cached HEAD
+		test $? = 1
+	}
+'
+
+test_done
diff --git a/t/t4017-quiet.sh b/t/t4017-quiet.sh
new file mode 100755
index 0000000..e747e84
--- /dev/null
+++ b/t/t4017-quiet.sh
@@ -0,0 +1,80 @@
+#!/bin/sh
+
+test_description='Return value of diffs'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	echo 1 >a &&
+	git add . &&
+	git commit -m first &&
+	echo 2 >b &&
+	git add . &&
+	git commit -a -m second
+'
+
+test_expect_success 'git diff-tree HEAD^ HEAD' '
+	git diff-tree --quiet HEAD^ HEAD >cnt
+	test $? = 1 && test $(wc -l <cnt) = 0
+'
+test_expect_success 'git diff-tree HEAD^ HEAD -- a' '
+	git diff-tree --quiet HEAD^ HEAD -- a >cnt
+	test $? = 0 && test $(wc -l <cnt) = 0
+'
+test_expect_success 'git diff-tree HEAD^ HEAD -- b' '
+	git diff-tree --quiet HEAD^ HEAD -- b >cnt
+	test $? = 1 && test $(wc -l <cnt) = 0
+'
+# this diff outputs one line: sha1 of the given head
+test_expect_success 'echo HEAD | git diff-tree --stdin' '
+	echo $(git rev-parse HEAD) | git diff-tree --quiet --stdin >cnt
+	test $? = 1 && test $(wc -l <cnt) = 1
+'
+test_expect_success 'git diff-tree HEAD HEAD' '
+	git diff-tree --quiet HEAD HEAD >cnt
+	test $? = 0 && test $(wc -l <cnt) = 0
+'
+test_expect_success 'git diff-files' '
+	git diff-files --quiet >cnt
+	test $? = 0 && test $(wc -l <cnt) = 0
+'
+test_expect_success 'git diff-index --cached HEAD' '
+	git diff-index --quiet --cached HEAD >cnt
+	test $? = 0 && test $(wc -l <cnt) = 0
+'
+test_expect_success 'git diff-index --cached HEAD^' '
+	git diff-index --quiet --cached HEAD^ >cnt
+	test $? = 1 && test $(wc -l <cnt) = 0
+'
+test_expect_success 'git diff-index --cached HEAD^' '
+	echo text >>b &&
+	echo 3 >c &&
+	git add . && {
+		git diff-index --quiet --cached HEAD^ >cnt
+		test $? = 1 && test $(wc -l <cnt) = 0
+	}
+'
+test_expect_success 'git diff-tree -Stext HEAD^ HEAD -- b' '
+	git commit -m "text in b" && {
+		git diff-tree --quiet -Stext HEAD^ HEAD -- b >cnt
+		test $? = 1 && test $(wc -l <cnt) = 0
+	}
+'
+test_expect_success 'git diff-tree -Snot-found HEAD^ HEAD -- b' '
+	git diff-tree --quiet -Snot-found HEAD^ HEAD -- b >cnt
+	test $? = 0 && test $(wc -l <cnt) = 0
+'
+test_expect_success 'git diff-files' '
+	echo 3 >>c && {
+		git diff-files --quiet >cnt
+		test $? = 1 && test $(wc -l <cnt) = 0
+	}
+'
+test_expect_success 'git diff-index --cached HEAD' '
+	git update-index c && {
+		git diff-index --quiet --cached HEAD >cnt
+		test $? = 1 && test $(wc -l <cnt) = 0
+	}
+'
+
+test_done
diff --git a/t/t4100-apply-stat.sh b/t/t4100-apply-stat.sh
index 6579f06..7b81c32 100755
--- a/t/t4100-apply-stat.sh
+++ b/t/t4100-apply-stat.sh
@@ -11,37 +11,37 @@
 test_expect_success \
     'rename' \
     'git-apply --stat --summary <../t4100/t-apply-1.patch >current &&
-    diff -u ../t4100/t-apply-1.expect current'
+    git diff ../t4100/t-apply-1.expect current'
 
 test_expect_success \
     'copy' \
     'git-apply --stat --summary <../t4100/t-apply-2.patch >current &&
-    diff -u ../t4100/t-apply-2.expect current'
+    git diff ../t4100/t-apply-2.expect current'
 
 test_expect_success \
     'rewrite' \
     'git-apply --stat --summary <../t4100/t-apply-3.patch >current &&
-    diff -u ../t4100/t-apply-3.expect current'
+    git diff ../t4100/t-apply-3.expect current'
 
 test_expect_success \
     'mode' \
     'git-apply --stat --summary <../t4100/t-apply-4.patch >current &&
-    diff -u ../t4100/t-apply-4.expect current'
+    git diff ../t4100/t-apply-4.expect current'
 
 test_expect_success \
     'non git' \
     'git-apply --stat --summary <../t4100/t-apply-5.patch >current &&
-    diff -u ../t4100/t-apply-5.expect current'
+    git diff ../t4100/t-apply-5.expect current'
 
 test_expect_success \
     'non git' \
     'git-apply --stat --summary <../t4100/t-apply-6.patch >current &&
-    diff -u ../t4100/t-apply-6.expect current'
+    git diff ../t4100/t-apply-6.expect current'
 
 test_expect_success \
     'non git' \
     'git-apply --stat --summary <../t4100/t-apply-7.patch >current &&
-    diff -u ../t4100/t-apply-7.expect current'
+    git diff ../t4100/t-apply-7.expect current'
 
 test_done
 
diff --git a/t/t4104-apply-boundary.sh b/t/t4104-apply-boundary.sh
index 2ff800c..a5fb3ea 100755
--- a/t/t4104-apply-boundary.sh
+++ b/t/t4104-apply-boundary.sh
@@ -90,7 +90,7 @@
 				cat '"$kind-patch.$with"'
 				(exit 1)
 			} &&
-			diff -u '"$kind"'-expect victim
+			git diff '"$kind"'-expect victim
 		'
 	done
 done
@@ -108,7 +108,7 @@
 			cat '"$kind-ng.without"'
 			(exit 1)
 		} &&
-		diff -u '"$kind"'-expect victim
+		git diff '"$kind"'-expect victim
 	'
 done
 
diff --git a/t/t4115-apply-symlink.sh b/t/t4115-apply-symlink.sh
index d5f2cfb..b947ed8 100755
--- a/t/t4115-apply-symlink.sh
+++ b/t/t4115-apply-symlink.sh
@@ -33,7 +33,7 @@
 	git checkout side &&
 	git apply patch &&
 	git diff-files -p >patched &&
-	diff -u patch patched
+	git diff patch patched
 
 '
 
@@ -42,7 +42,7 @@
 	git checkout -f side &&
 	git apply --index patch &&
 	git diff-index --cached -p HEAD >patched &&
-	diff -u patch patched
+	git diff patch patched
 
 '
 
diff --git a/t/t4116-apply-reverse.sh b/t/t4116-apply-reverse.sh
index aa2c869..2685b22 100755
--- a/t/t4116-apply-reverse.sh
+++ b/t/t4116-apply-reverse.sh
@@ -42,7 +42,7 @@
 	git reset --hard second &&
 	git apply --reverse --binary --index patch &&
 	git diff >diff &&
-	diff -u /dev/null diff
+	git diff /dev/null diff
 
 '
 
diff --git a/t/t4117-apply-reject.sh b/t/t4117-apply-reject.sh
index b4de075..91931f0 100755
--- a/t/t4117-apply-reject.sh
+++ b/t/t4117-apply-reject.sh
@@ -54,7 +54,7 @@
 		exit 1
 	fi
 
-	diff -u file1 saved.file1
+	git diff file1 saved.file1
 '
 
 test_expect_success 'apply without --reject should fail' '
@@ -65,7 +65,7 @@
 		exit 1
 	fi
 
-	diff -u file1 saved.file1
+	git diff file1 saved.file1
 '
 
 test_expect_success 'apply with --reject should fail but update the file' '
@@ -79,7 +79,7 @@
 		exit 1
 	fi
 
-	diff -u file1 expected &&
+	git diff file1 expected &&
 
 	cat file1.rej &&
 
@@ -105,7 +105,7 @@
 		echo "file1 still exists?"
 		exit 1
 	}
-	diff -u file2 expected &&
+	git diff file2 expected &&
 
 	cat file2.rej &&
 
@@ -132,7 +132,7 @@
 		echo "file1 still exists?"
 		exit 1
 	}
-	diff -u file2 expected &&
+	git diff file2 expected &&
 
 	cat file2.rej &&
 
@@ -151,7 +151,7 @@
 
 	git apply --verbose patch.1 &&
 
-	diff -u file1 clean
+	git diff file1 clean
 '
 
 test_done
diff --git a/t/t4118-apply-empty-context.sh b/t/t4118-apply-empty-context.sh
index e78b1a1..27cc6f2 100755
--- a/t/t4118-apply-empty-context.sh
+++ b/t/t4118-apply-empty-context.sh
@@ -38,7 +38,7 @@
 		echo "0	1	file1" &&
 		echo "0	1	file2"
 	} >expect &&
-	diff -u expect actual
+	git diff expect actual
 
 '
 
@@ -48,8 +48,8 @@
 	cat file2.orig >file2 &&
 	git update-index file1 file2 &&
 	git apply --index diff.output &&
-	diff -u file1.mods file1 &&
-	diff -u file2.mods file2
+	git diff file1.mods file1 &&
+	git diff file2.mods file2
 '
 
 test_done
diff --git a/t/t4119-apply-config.sh b/t/t4119-apply-config.sh
new file mode 100755
index 0000000..620a920
--- /dev/null
+++ b/t/t4119-apply-config.sh
@@ -0,0 +1,162 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Junio C Hamano
+#
+
+test_description='git-apply --whitespace=strip and configuration file.
+
+'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+	mkdir sub &&
+	echo A >sub/file1 &&
+	cp sub/file1 saved &&
+	git add sub/file1 &&
+	echo "B " >sub/file1 &&
+	git diff >patch.file
+'
+
+# Also handcraft GNU diff output; note this has trailing whitespace.
+cat >gpatch.file <<\EOF &&
+--- file1	2007-02-21 01:04:24.000000000 -0800
++++ file1+	2007-02-21 01:07:44.000000000 -0800
+@@ -1 +1 @@
+-A
++B 
+EOF
+
+sed -e 's|file1|sub/&|' gpatch.file >gpatch-sub.file &&
+sed -e '
+	/^--- /s|file1|a/sub/&|
+	/^+++ /s|file1|b/sub/&|
+' gpatch.file >gpatch-ab-sub.file &&
+
+check_result () {
+	if grep " " "$1"
+	then
+		echo "Eh?"
+		false
+	elif grep B "$1"
+	then
+		echo Happy
+	else
+		echo "Huh?"
+		false
+	fi
+}
+
+test_expect_success 'apply --whitespace=strip' '
+
+	rm -f sub/file1 &&
+	cp saved sub/file1 &&
+	git update-index --refresh &&
+
+	git apply --whitespace=strip patch.file &&
+	check_result sub/file1
+'
+
+test_expect_success 'apply --whitespace=strip from config' '
+
+	rm -f sub/file1 &&
+	cp saved sub/file1 &&
+	git update-index --refresh &&
+
+	git config apply.whitespace strip &&
+	git apply patch.file &&
+	check_result sub/file1
+'
+
+D=`pwd`
+
+test_expect_success 'apply --whitespace=strip in subdir' '
+
+	cd "$D" &&
+	git config --unset-all apply.whitespace
+	rm -f sub/file1 &&
+	cp saved sub/file1 &&
+	git update-index --refresh &&
+
+	cd sub &&
+	git apply --whitespace=strip ../patch.file &&
+	check_result file1
+'
+
+test_expect_success 'apply --whitespace=strip from config in subdir' '
+
+	cd "$D" &&
+	git config apply.whitespace strip &&
+	rm -f sub/file1 &&
+	cp saved sub/file1 &&
+	git update-index --refresh &&
+
+	cd sub &&
+	git apply ../patch.file &&
+	check_result file1
+'
+
+test_expect_success 'same in subdir but with traditional patch input' '
+
+	cd "$D" &&
+	git config apply.whitespace strip &&
+	rm -f sub/file1 &&
+	cp saved sub/file1 &&
+	git update-index --refresh &&
+
+	cd sub &&
+	git apply ../gpatch.file &&
+	check_result file1
+'
+
+test_expect_success 'same but with traditional patch input of depth 1' '
+
+	cd "$D" &&
+	git config apply.whitespace strip &&
+	rm -f sub/file1 &&
+	cp saved sub/file1 &&
+	git update-index --refresh &&
+
+	cd sub &&
+	git apply ../gpatch-sub.file &&
+	check_result file1
+'
+
+test_expect_success 'same but with traditional patch input of depth 2' '
+
+	cd "$D" &&
+	git config apply.whitespace strip &&
+	rm -f sub/file1 &&
+	cp saved sub/file1 &&
+	git update-index --refresh &&
+
+	cd sub &&
+	git apply ../gpatch-ab-sub.file &&
+	check_result file1
+'
+
+test_expect_success 'same but with traditional patch input of depth 1' '
+
+	cd "$D" &&
+	git config apply.whitespace strip &&
+	rm -f sub/file1 &&
+	cp saved sub/file1 &&
+	git update-index --refresh &&
+
+	git apply -p0 gpatch-sub.file &&
+	check_result sub/file1
+'
+
+test_expect_success 'same but with traditional patch input of depth 2' '
+
+	cd "$D" &&
+	git config apply.whitespace strip &&
+	rm -f sub/file1 &&
+	cp saved sub/file1 &&
+	git update-index --refresh &&
+
+	git apply gpatch-ab-sub.file &&
+	check_result sub/file1
+'
+
+test_done
diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh
index 69b8d26..8b611bb 100755
--- a/t/t4200-rerere.sh
+++ b/t/t4200-rerere.sh
@@ -83,7 +83,7 @@
 EOF
 git rerere diff > out
 
-test_expect_success 'rerere diff' 'diff -u expect out'
+test_expect_success 'rerere diff' 'git diff expect out'
 
 cat > expect << EOF
 a1
@@ -91,7 +91,7 @@
 
 git rerere status > out
 
-test_expect_success 'rerere status' 'diff -u expect out'
+test_expect_success 'rerere status' 'git diff expect out'
 
 test_expect_success 'commit succeeds' \
 	"git commit -q -a -m 'prefer first over second'"
@@ -107,7 +107,7 @@
 git show first:a1 | sed 's/To die: t/To die! T/' > expect
 test_expect_success 'rerere kicked in' "! grep ======= a1"
 
-test_expect_success 'rerere prefers first change' 'diff -u a1 expect'
+test_expect_success 'rerere prefers first change' 'git diff a1 expect'
 
 rm $rr/postimage
 echo "$sha1	a1" | tr '\012' '\0' > .git/rr-cache/MERGE_RR
@@ -125,39 +125,26 @@
 mkdir $rr2
 echo Hello > $rr2/preimage
 
-case "$(date -d @11111111 +%s 2>/dev/null)" in
-11111111)
-	# 'date' must be able to take arbitrary input with @11111111 notation.
-	# for this test to succeed.  We should fix this part using more
-	# portable script someday.
+almost_15_days_ago=$((60-15*86400))
+just_over_15_days_ago=$((-1-15*86400))
+almost_60_days_ago=$((60-60*86400))
+just_over_60_days_ago=$((-1-60*86400))
 
-	now=$(date +%s)
-	almost_15_days_ago=$(($now+60-15*86400))
-	just_over_15_days_ago=$(($now-1-15*86400))
-	almost_60_days_ago=$(($now+60-60*86400))
-	just_over_60_days_ago=$(($now-1-60*86400))
-	predate1="$(date -d "@$almost_60_days_ago" +%Y%m%d%H%M.%S)"
-	predate2="$(date -d "@$almost_15_days_ago" +%Y%m%d%H%M.%S)"
-	postdate1="$(date -d "@$just_over_60_days_ago" +%Y%m%d%H%M.%S)"
-	postdate2="$(date -d "@$just_over_15_days_ago" +%Y%m%d%H%M.%S)"
+test-chmtime =$almost_60_days_ago $rr/preimage
+test-chmtime =$almost_15_days_ago $rr2/preimage
 
-	touch -m -t "$predate1" $rr/preimage
-	touch -m -t "$predate2" $rr2/preimage
+test_expect_success 'garbage collection (part1)' 'git rerere gc'
 
-	test_expect_success 'garbage collection (part1)' 'git rerere gc'
+test_expect_success 'young records still live' \
+	"test -f $rr/preimage && test -f $rr2/preimage"
 
-	test_expect_success 'young records still live' \
-		"test -f $rr/preimage -a -f $rr2/preimage"
+test-chmtime =$just_over_60_days_ago $rr/preimage
+test-chmtime =$just_over_15_days_ago $rr2/preimage
 
-	touch -m -t "$postdate1" $rr/preimage
-	touch -m -t "$postdate2" $rr2/preimage
+test_expect_success 'garbage collection (part2)' 'git rerere gc'
 
-	test_expect_success 'garbage collection (part2)' 'git rerere gc'
-
-	test_expect_success 'old records rest in peace' \
-		"test ! -f $rr/preimage -a ! -f $rr2/preimage"
-	;;
-esac
+test_expect_success 'old records rest in peace' \
+	"test ! -f $rr/preimage && test ! -f $rr2/preimage"
 
 test_done
 
diff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh
index 4d2b781..ca96918 100755
--- a/t/t5100-mailinfo.sh
+++ b/t/t5100-mailinfo.sh
@@ -11,7 +11,7 @@
 	'git-mailsplit -o. ../t5100/sample.mbox >last &&
 	last=`cat last` &&
 	echo total is $last &&
-	test `cat last` = 6'
+	test `cat last` = 8'
 
 for mail in `echo 00*`
 do
diff --git a/t/t5100/info0007 b/t/t5100/info0007
new file mode 100644
index 0000000..49bb0fe
--- /dev/null
+++ b/t/t5100/info0007
@@ -0,0 +1,5 @@
+Author: A U Thor
+Email: a.u.thor@example.com
+Subject: another patch
+Date: Fri, 9 Jun 2006 00:44:16 -0700
+
diff --git a/t/t5100/info0008 b/t/t5100/info0008
new file mode 100644
index 0000000..e8a2951
--- /dev/null
+++ b/t/t5100/info0008
@@ -0,0 +1,5 @@
+Author: Junio C Hamano
+Email: junio@kernel.org
+Subject: another patch
+Date: Fri, 9 Jun 2006 00:44:16 -0700
+
diff --git a/t/t5100/msg0007 b/t/t5100/msg0007
new file mode 100644
index 0000000..71b23c0
--- /dev/null
+++ b/t/t5100/msg0007
@@ -0,0 +1,2 @@
+Here is an empty patch from A U Thor.
+
diff --git a/t/t5100/msg0008 b/t/t5100/msg0008
new file mode 100644
index 0000000..a80ecb9
--- /dev/null
+++ b/t/t5100/msg0008
@@ -0,0 +1,4 @@
+>Here is an empty patch from A U Thor.
+
+Hey you forgot the patch!
+
diff --git a/t/t5100/patch0007 b/t/t5100/patch0007
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/t/t5100/patch0007
diff --git a/t/t5100/patch0008 b/t/t5100/patch0008
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/t/t5100/patch0008
diff --git a/t/t5100/sample.mbox b/t/t5100/sample.mbox
index 86bfc27..b80c981 100644
--- a/t/t5100/sample.mbox
+++ b/t/t5100/sample.mbox
@@ -386,3 +386,21 @@
 -- 
 1.4.0.g6f2b
 
+From nobody Mon Sep 17 00:00:00 2001
+From: A U Thor <a.u.thor@example.com>
+Date: Fri, 9 Jun 2006 00:44:16 -0700
+Subject: [PATCH] another patch
+
+Here is an empty patch from A U Thor.
+
+From nobody Mon Sep 17 00:00:00 2001
+From: Junio C Hamano <junio@kernel.org>
+Date: Fri, 9 Jun 2006 00:44:16 -0700
+Subject: re: [PATCH] another patch
+
+From: A U Thor <a.u.thor@example.com>
+Subject: [PATCH] another patch
+>Here is an empty patch from A U Thor.
+
+Hey you forgot the patch!
+
diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh
index f511547..35e036a 100755
--- a/t/t5300-pack-object.sh
+++ b/t/t5300-pack-object.sh
@@ -64,7 +64,7 @@
 cd "$TRASH"
 
 test_expect_success \
-    'pack with delta' \
+    'pack with REF_DELTA' \
     'pwd &&
      packname_2=$(git-pack-objects test-2 <obj-list)'
 
@@ -72,7 +72,7 @@
 mkdir .git2
 
 test_expect_success \
-    'unpack with delta' \
+    'unpack with REF_DELTA' \
     'GIT_OBJECT_DIRECTORY=.git2/objects &&
      export GIT_OBJECT_DIRECTORY &&
      git-init &&
@@ -82,7 +82,7 @@
 unset GIT_OBJECT_DIRECTORY
 cd "$TRASH/.git2"
 test_expect_success \
-    'check unpack with delta' \
+    'check unpack with REF_DELTA' \
     '(cd ../.git && find objects -type f -print) |
      while read path
      do
@@ -93,6 +93,42 @@
      done'
 cd "$TRASH"
 
+test_expect_success \
+    'pack with OFS_DELTA' \
+    'pwd &&
+     packname_3=$(git-pack-objects --delta-base-offset test-3 <obj-list)'
+
+rm -fr .git2
+mkdir .git2
+
+test_expect_success \
+    'unpack with OFS_DELTA' \
+    'GIT_OBJECT_DIRECTORY=.git2/objects &&
+     export GIT_OBJECT_DIRECTORY &&
+     git-init &&
+     git-unpack-objects -n <test-3-${packname_3}.pack &&
+     git-unpack-objects <test-3-${packname_3}.pack'
+
+unset GIT_OBJECT_DIRECTORY
+cd "$TRASH/.git2"
+test_expect_success \
+    'check unpack with OFS_DELTA' \
+    '(cd ../.git && find objects -type f -print) |
+     while read path
+     do
+         cmp $path ../.git/$path || {
+	     echo $path differs.
+	     return 1
+	 }
+     done'
+cd "$TRASH"
+
+test_expect_success \
+    'compare delta flavors' \
+    'size_2=`stat -c "%s" test-2-${packname_2}.pack` &&
+     size_3=`stat -c "%s" test-3-${packname_3}.pack` &&
+     test $size_2 -gt $size_3'
+
 rm -fr .git2
 mkdir .git2
 
@@ -111,12 +147,11 @@
     } >current &&
     diff expect current'
 
-
 test_expect_success \
-    'use packed deltified objects' \
+    'use packed deltified (REF_DELTA) objects' \
     'GIT_OBJECT_DIRECTORY=.git2/objects &&
      export GIT_OBJECT_DIRECTORY &&
-     rm -f .git2/objects/pack/test-?.idx &&
+     rm .git2/objects/pack/test-* &&
      cp test-2-${packname_2}.pack test-2-${packname_2}.idx .git2/objects/pack && {
 	 git-diff-tree --root -p $commit &&
 	 while read object
@@ -127,11 +162,28 @@
     } >current &&
     diff expect current'
 
+test_expect_success \
+    'use packed deltified (OFS_DELTA) objects' \
+    'GIT_OBJECT_DIRECTORY=.git2/objects &&
+     export GIT_OBJECT_DIRECTORY &&
+     rm .git2/objects/pack/test-* &&
+     cp test-3-${packname_3}.pack test-3-${packname_3}.idx .git2/objects/pack && {
+	 git-diff-tree --root -p $commit &&
+	 while read object
+	 do
+	    t=`git-cat-file -t $object` &&
+	    git-cat-file $t $object || return 1
+	 done <obj-list
+    } >current &&
+    diff expect current'
+
 unset GIT_OBJECT_DIRECTORY
 
 test_expect_success \
     'verify pack' \
-    'git-verify-pack test-1-${packname_1}.idx test-2-${packname_2}.idx'
+    'git-verify-pack	test-1-${packname_1}.idx \
+			test-2-${packname_2}.idx \
+			test-3-${packname_3}.idx'
 
 test_expect_success \
     'corrupt a pack and see if verify catches' \
@@ -194,6 +246,23 @@
      git-index-pack test-3.pack &&
      cmp test-3.idx test-2-${packname_2}.idx &&
 
+     cp test-3-${packname_3}.pack test-3.pack &&
+     git-index-pack -o tmp.idx test-3-${packname_3}.pack &&
+     cmp tmp.idx test-3-${packname_3}.idx &&
+
+     git-index-pack test-3.pack &&
+     cmp test-3.idx test-3-${packname_3}.idx &&
+
      :'
 
+test_expect_success \
+    'fake a SHA1 hash collision' \
+    'test -f	.git/objects/c8/2de19312b6c3695c0c18f70709a6c535682a67 &&
+     cp -f	.git/objects/9d/235ed07cd19811a6ceb342de82f190e49c9f68 \
+		.git/objects/c8/2de19312b6c3695c0c18f70709a6c535682a67'
+
+test_expect_failure \
+    'make sure index-pack detects the SHA1 collision' \
+    'git-index-pack -o bad.idx test-3.pack'
+
 test_done
diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh
index 7d93d0d..477b267 100755
--- a/t/t5400-send-pack.sh
+++ b/t/t5400-send-pack.sh
@@ -108,9 +108,9 @@
 	cd victim &&
 	git-config receive.denyNonFastforwards true &&
 	cd .. &&
-	git-update-ref refs/heads/master master^ &&
-	git-send-pack --force ./victim/.git/ master &&
-	! diff -u .git/refs/heads/master victim/.git/refs/heads/master
+	git-update-ref refs/heads/master master^ || return 1
+	git-send-pack --force ./victim/.git/ master && return 1
+	! git diff .git/refs/heads/master victim/.git/refs/heads/master
 '
 
 test_done
diff --git a/t/t5401-update-hooks.sh b/t/t5401-update-hooks.sh
index 0514056..f1c7ff0 100755
--- a/t/t5401-update-hooks.sh
+++ b/t/t5401-update-hooks.sh
@@ -11,71 +11,126 @@
 	git-update-index --add a &&
 	tree0=$(git-write-tree) &&
 	commit0=$(echo setup | git-commit-tree $tree0) &&
-	git-update-ref HEAD $commit0 &&
-	git-clone ./. victim &&
 	echo We hope it works. >a &&
 	git-update-index a &&
 	tree1=$(git-write-tree) &&
 	commit1=$(echo modify | git-commit-tree $tree1 -p $commit0) &&
-	git-update-ref HEAD $commit1
+	git-update-ref refs/heads/master $commit0 &&
+	git-update-ref refs/heads/tofail $commit1 &&
+	git-clone ./. victim &&
+	GIT_DIR=victim/.git git-update-ref refs/heads/tofail $commit1 &&
+	git-update-ref refs/heads/master $commit1 &&
+	git-update-ref refs/heads/tofail $commit0
 '
 
+cat >victim/.git/hooks/pre-receive <<'EOF'
+#!/bin/sh
+printf "$@" >>$GIT_DIR/pre-receive.args
+cat - >$GIT_DIR/pre-receive.stdin
+echo STDOUT pre-receive
+echo STDERR pre-receive >&2
+EOF
+chmod u+x victim/.git/hooks/pre-receive
+
 cat >victim/.git/hooks/update <<'EOF'
 #!/bin/sh
-echo "$@" >$GIT_DIR/update.args
+echo "$@" >>$GIT_DIR/update.args
 read x; printf "$x" >$GIT_DIR/update.stdin
-echo STDOUT update
-echo STDERR update >&2
+echo STDOUT update $1
+echo STDERR update $1 >&2
+test "$1" = refs/heads/master || exit
 EOF
 chmod u+x victim/.git/hooks/update
 
+cat >victim/.git/hooks/post-receive <<'EOF'
+#!/bin/sh
+printf "$@" >>$GIT_DIR/post-receive.args
+cat - >$GIT_DIR/post-receive.stdin
+echo STDOUT post-receive
+echo STDERR post-receive >&2
+EOF
+chmod u+x victim/.git/hooks/post-receive
+
 cat >victim/.git/hooks/post-update <<'EOF'
 #!/bin/sh
-echo "$@" >$GIT_DIR/post-update.args
+echo "$@" >>$GIT_DIR/post-update.args
 read x; printf "$x" >$GIT_DIR/post-update.stdin
 echo STDOUT post-update
 echo STDERR post-update >&2
 EOF
 chmod u+x victim/.git/hooks/post-update
 
-test_expect_success push '
-	git-send-pack ./victim/.git/ master >send.out 2>send.err
+test_expect_failure push '
+	git-send-pack --force ./victim/.git master tofail >send.out 2>send.err
+'
+
+test_expect_success 'updated as expected' '
+	test $(GIT_DIR=victim/.git git-rev-parse master) = $commit1 &&
+	test $(GIT_DIR=victim/.git git-rev-parse tofail) = $commit1
 '
 
 test_expect_success 'hooks ran' '
+	test -f victim/.git/pre-receive.args &&
+	test -f victim/.git/pre-receive.stdin &&
 	test -f victim/.git/update.args &&
 	test -f victim/.git/update.stdin &&
+	test -f victim/.git/post-receive.args &&
+	test -f victim/.git/post-receive.stdin &&
 	test -f victim/.git/post-update.args &&
 	test -f victim/.git/post-update.stdin
 '
 
+test_expect_success 'pre-receive hook input' '
+	(echo $commit0 $commit1 refs/heads/master;
+	 echo $commit1 $commit0 refs/heads/tofail
+	) | git diff - victim/.git/pre-receive.stdin
+'
+
 test_expect_success 'update hook arguments' '
-	echo refs/heads/master $commit0 $commit1 |
-	diff -u - victim/.git/update.args
+	(echo refs/heads/master $commit0 $commit1;
+	 echo refs/heads/tofail $commit1 $commit0
+	) | git diff - victim/.git/update.args
+'
+
+test_expect_success 'post-receive hook input' '
+	echo $commit0 $commit1 refs/heads/master |
+	git diff - victim/.git/post-receive.stdin
 '
 
 test_expect_success 'post-update hook arguments' '
 	echo refs/heads/master |
-	diff -u - victim/.git/post-update.args
+	git diff - victim/.git/post-update.args
 '
 
-test_expect_failure 'update hook stdin is /dev/null' '
-	test -s victim/.git/update.stdin
+test_expect_success 'all hook stdin is /dev/null' '
+	! test -s victim/.git/update.stdin &&
+	! test -s victim/.git/post-update.stdin
 '
 
-test_expect_failure 'post-update hook stdin is /dev/null' '
-	test -s victim/.git/post-update.stdin
+test_expect_success 'all *-receive hook args are empty' '
+	! test -s victim/.git/pre-receive.args &&
+	! test -s victim/.git/post-receive.args
 '
 
 test_expect_failure 'send-pack produced no output' '
 	test -s send.out
 '
 
+cat <<EOF >expect
+STDOUT pre-receive
+STDERR pre-receive
+STDOUT update refs/heads/master
+STDERR update refs/heads/master
+STDOUT update refs/heads/tofail
+STDERR update refs/heads/tofail
+STDOUT post-receive
+STDERR post-receive
+STDOUT post-update
+STDERR post-update
+EOF
 test_expect_success 'send-pack stderr contains hook messages' '
-	grep "STDOUT update" send.err &&
-	grep "STDERR update" send.err &&
-	grep "STDOUT post-update" send.err &&
-	grep "STDERR post-update" send.err
+	egrep ^STD send.err >actual &&
+	git diff - actual <expect
 '
 
 test_done
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 50c6485..426017e 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -35,7 +35,9 @@
 		echo "URL: ../two/.git/"
 		echo "Pull: refs/heads/master:refs/heads/two"
 		echo "Pull: refs/heads/one:refs/heads/one"
-	} >.git/remotes/two
+	} >.git/remotes/two &&
+	cd .. &&
+	git clone . bundle
 '
 
 test_expect_success "fetch test" '
@@ -81,4 +83,66 @@
 
 '
 
+test_expect_success 'create bundle 1' '
+	cd "$D" &&
+	echo >file updated again by origin &&
+	git commit -a -m "tip" &&
+	git bundle create bundle1 master^..master
+'
+
+test_expect_success 'header of bundle looks right' '
+	head -n 1 "$D"/bundle1 | grep "^#" &&
+	head -n 2 "$D"/bundle1 | grep "^-[0-9a-f]\{40\} " &&
+	head -n 3 "$D"/bundle1 | grep "^[0-9a-f]\{40\} " &&
+	head -n 4 "$D"/bundle1 | grep "^$"
+'
+
+test_expect_success 'create bundle 2' '
+	cd "$D" &&
+	git bundle create bundle2 master~2..master
+'
+
+test_expect_failure 'unbundle 1' '
+	cd "$D/bundle" &&
+	git checkout -b some-branch &&
+	git fetch "$D/bundle1" master:master
+'
+
+test_expect_success 'bundle 1 has only 3 files ' '
+	cd "$D" &&
+	(
+		while read x && test -n "$x"
+		do
+			:;
+		done
+		cat
+	) <bundle1 >bundle.pack &&
+	git index-pack bundle.pack &&
+	verify=$(git verify-pack -v bundle.pack) &&
+	test 4 = $(echo "$verify" | wc -l)
+'
+
+test_expect_success 'unbundle 2' '
+	cd "$D/bundle" &&
+	git fetch ../bundle2 master:master &&
+	test "tip" = "$(git log -1 --pretty=oneline master | cut -b42-)"
+'
+
+test_expect_success 'bundle does not prerequisite objects' '
+	cd "$D" &&
+	touch file2 &&
+	git add file2 &&
+	git commit -m add.file2 file2 &&
+	git bundle create bundle3 -1 HEAD &&
+	(
+		while read x && test -n "$x"
+		do
+			:;
+		done
+		cat
+	) <bundle3 >bundle.pack &&
+	git index-pack bundle.pack &&
+	test 4 = $(git verify-pack -v bundle.pack | wc -l)
+'
+
 test_done
diff --git a/t/t5515-fetch-merge-logic.sh b/t/t5515-fetch-merge-logic.sh
new file mode 100755
index 0000000..6c9cc67
--- /dev/null
+++ b/t/t5515-fetch-merge-logic.sh
@@ -0,0 +1,162 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Santi Béjar, based on t4013 by Junio C Hamano
+#
+#
+
+test_description='Merge logic in fetch'
+
+. ./test-lib.sh
+
+LF='
+'
+
+test_expect_success setup '
+	GIT_AUTHOR_DATE="2006-06-26 00:00:00 +0000" &&
+	GIT_COMMITTER_DATE="2006-06-26 00:00:00 +0000" &&
+	export GIT_AUTHOR_DATE GIT_COMMITTER_DATE &&
+
+	echo >file original &&
+	git add file &&
+	git commit -a -m One &&
+	git tag tag-one &&
+	git tag tag-one-tree HEAD^{tree} &&
+	git branch one &&
+
+	echo two >> file &&
+	git commit -a -m Two &&
+	git tag -a -m "Tag Two" tag-two &&
+	git branch two &&
+
+	echo three >> file &&
+	git commit -a -m Three &&
+	git tag -a -m "Tag Three" tag-three &&
+	git tag -a -m "Tag Three file" tag-three-file HEAD^{tree}:file &&
+	git branch three &&
+
+	echo master >> file &&
+	git commit -a -m Master &&
+	git tag -a -m "Tag Master" tag-master &&
+
+	git checkout three &&
+
+	git clone . cloned &&
+	cd cloned &&
+	git config remote.origin.url ../.git/ &&
+
+	git config remote.config-explicit.url ../.git/ &&
+	git config remote.config-explicit.fetch refs/heads/master:remotes/rem/master &&
+	git config --add remote.config-explicit.fetch refs/heads/one:remotes/rem/one &&
+	git config --add remote.config-explicit.fetch two:remotes/rem/two &&
+	git config --add remote.config-explicit.fetch refs/heads/three:remotes/rem/three &&
+	remotes="config-explicit" &&
+
+	git config remote.config-glob.url ../.git/ &&
+	git config remote.config-glob.fetch refs/heads/*:refs/remotes/rem/* &&
+	remotes="$remotes config-glob" &&
+
+	mkdir -p .git/remotes &&
+	{
+		echo "URL: ../.git/"
+		echo "Pull: refs/heads/master:remotes/rem/master"
+		echo "Pull: refs/heads/one:remotes/rem/one"
+		echo "Pull: two:remotes/rem/two"
+		echo "Pull: refs/heads/three:remotes/rem/three"
+	} >.git/remotes/remote-explicit &&
+	remotes="$remotes remote-explicit" &&
+
+	{
+		echo "URL: ../.git/"
+		echo "Pull: refs/heads/*:refs/remotes/rem/*"
+	} >.git/remotes/remote-glob &&
+	remotes="$remotes remote-glob" &&
+
+	mkdir -p .git/branches &&
+	echo "../.git" > .git/branches/branches-default &&
+	remotes="$remotes branches-default" &&
+
+	echo "../.git#one" > .git/branches/branches-one &&
+	remotes="$remotes branches-one" &&
+
+	for remote in $remotes ; do
+		git config branch.br-$remote.remote $remote &&
+		git config branch.br-$remote-merge.remote $remote &&
+		git config branch.br-$remote-merge.merge refs/heads/three &&
+		git config branch.br-$remote-octopus.remote $remote &&
+		git config branch.br-$remote-octopus.merge refs/heads/one &&
+		git config --add branch.br-$remote-octopus.merge two &&
+		git config --add branch.br-$remote-octopus.merge remotes/rem/three
+	done
+'
+
+# Merge logic depends on branch properties and Pull: or .fetch lines
+for remote in $remotes ; do
+    for branch in "" "-merge" "-octopus" ; do
+cat <<EOF
+br-$remote$branch
+br-$remote$branch $remote
+EOF
+    done
+done > tests
+
+# Merge logic does not depend on branch properties,
+# but does depend on Pull: or fetch lines.
+# Use two branches completely unrelated from the arguments,
+# the clone default and one without branch properties
+for branch in master br-unconfig ; do
+    echo $branch
+    for remote in $remotes ; do
+	echo $branch $remote
+    done
+done >> tests
+
+# Merge logic does not depend on branch properties
+# neither in the Pull: or .fetch config
+for branch in master br-unconfig ; do
+    cat <<EOF
+$branch ../.git
+$branch ../.git one
+$branch ../.git one two
+$branch --tags ../.git
+$branch ../.git tag tag-one tag tag-three
+$branch ../.git tag tag-one-tree tag tag-three-file
+$branch ../.git one tag tag-one tag tag-three-file
+EOF
+done >> tests
+
+while read cmd
+do
+	case "$cmd" in
+	'' | '#'*) continue ;;
+	esac
+	test=`echo "$cmd" | sed -e 's|[/ ][/ ]*|_|g'`
+	cnt=`expr $test_count + 1`
+	pfx=`printf "%04d" $cnt`
+	expect="../../t5515/fetch.$test"
+	actual="$pfx-fetch.$test"
+
+	test_expect_success "$cmd" '
+		{
+			echo "# $cmd"
+			set x $cmd; shift
+			git symbolic-ref HEAD refs/heads/$1 ; shift
+			rm -f .git/FETCH_HEAD
+			rm -f .git/refs/heads/*
+			rm -f .git/refs/remotes/rem/*
+			rm -f .git/refs/tags/*
+			git fetch "$@" >/dev/null
+			cat .git/FETCH_HEAD
+		} >"$actual" &&
+		if test -f "$expect"
+		then
+			git diff -u "$expect" "$actual" &&
+			rm -f "$actual"
+		else
+			# this is to help developing new tests.
+			cp "$actual" "$expect"
+			false
+		fi
+	'
+done < tests
+
+test_done
diff --git a/t/t5515/fetch.br-branches-default b/t/t5515/fetch.br-branches-default
new file mode 100644
index 0000000..2e0414f
--- /dev/null
+++ b/t/t5515/fetch.br-branches-default
@@ -0,0 +1,8 @@
+# br-branches-default
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f		branch 'master' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-branches-default-merge b/t/t5515/fetch.br-branches-default-merge
new file mode 100644
index 0000000..ea65f31
--- /dev/null
+++ b/t/t5515/fetch.br-branches-default-merge
@@ -0,0 +1,8 @@
+# br-branches-default-merge
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f		branch 'master' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-branches-default-merge_branches-default b/t/t5515/fetch.br-branches-default-merge_branches-default
new file mode 100644
index 0000000..7b5fa94
--- /dev/null
+++ b/t/t5515/fetch.br-branches-default-merge_branches-default
@@ -0,0 +1,8 @@
+# br-branches-default-merge branches-default
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f		branch 'master' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-branches-default-octopus b/t/t5515/fetch.br-branches-default-octopus
new file mode 100644
index 0000000..128397d
--- /dev/null
+++ b/t/t5515/fetch.br-branches-default-octopus
@@ -0,0 +1,8 @@
+# br-branches-default-octopus
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f		branch 'master' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-branches-default-octopus_branches-default b/t/t5515/fetch.br-branches-default-octopus_branches-default
new file mode 100644
index 0000000..4b37cd4
--- /dev/null
+++ b/t/t5515/fetch.br-branches-default-octopus_branches-default
@@ -0,0 +1,8 @@
+# br-branches-default-octopus branches-default
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f		branch 'master' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-branches-default_branches-default b/t/t5515/fetch.br-branches-default_branches-default
new file mode 100644
index 0000000..4a2bf3c
--- /dev/null
+++ b/t/t5515/fetch.br-branches-default_branches-default
@@ -0,0 +1,8 @@
+# br-branches-default branches-default
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f		branch 'master' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-branches-one b/t/t5515/fetch.br-branches-one
new file mode 100644
index 0000000..12ac8d2
--- /dev/null
+++ b/t/t5515/fetch.br-branches-one
@@ -0,0 +1,8 @@
+# br-branches-one
+8e32a6d901327a23ef831511badce7bf3bf46689		branch 'one' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-branches-one-merge b/t/t5515/fetch.br-branches-one-merge
new file mode 100644
index 0000000..3a4e77e
--- /dev/null
+++ b/t/t5515/fetch.br-branches-one-merge
@@ -0,0 +1,8 @@
+# br-branches-one-merge
+8e32a6d901327a23ef831511badce7bf3bf46689		branch 'one' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-branches-one-merge_branches-one b/t/t5515/fetch.br-branches-one-merge_branches-one
new file mode 100644
index 0000000..00e04b4
--- /dev/null
+++ b/t/t5515/fetch.br-branches-one-merge_branches-one
@@ -0,0 +1,8 @@
+# br-branches-one-merge branches-one
+8e32a6d901327a23ef831511badce7bf3bf46689		branch 'one' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-branches-one-octopus b/t/t5515/fetch.br-branches-one-octopus
new file mode 100644
index 0000000..53fe808
--- /dev/null
+++ b/t/t5515/fetch.br-branches-one-octopus
@@ -0,0 +1,8 @@
+# br-branches-one-octopus
+8e32a6d901327a23ef831511badce7bf3bf46689		branch 'one' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-branches-one-octopus_branches-one b/t/t5515/fetch.br-branches-one-octopus_branches-one
new file mode 100644
index 0000000..41b18ff
--- /dev/null
+++ b/t/t5515/fetch.br-branches-one-octopus_branches-one
@@ -0,0 +1,8 @@
+# br-branches-one-octopus branches-one
+8e32a6d901327a23ef831511badce7bf3bf46689		branch 'one' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-branches-one_branches-one b/t/t5515/fetch.br-branches-one_branches-one
new file mode 100644
index 0000000..281fa09
--- /dev/null
+++ b/t/t5515/fetch.br-branches-one_branches-one
@@ -0,0 +1,8 @@
+# br-branches-one branches-one
+8e32a6d901327a23ef831511badce7bf3bf46689		branch 'one' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-config-explicit b/t/t5515/fetch.br-config-explicit
new file mode 100644
index 0000000..e2fa9c8
--- /dev/null
+++ b/t/t5515/fetch.br-config-explicit
@@ -0,0 +1,11 @@
+# br-config-explicit
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f		branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	branch 'one' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	branch 'two' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	branch 'three' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-config-explicit-merge b/t/t5515/fetch.br-config-explicit-merge
new file mode 100644
index 0000000..ec1a723
--- /dev/null
+++ b/t/t5515/fetch.br-config-explicit-merge
@@ -0,0 +1,11 @@
+# br-config-explicit-merge
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	branch 'one' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	branch 'two' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b		branch 'three' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-config-explicit-merge_config-explicit b/t/t5515/fetch.br-config-explicit-merge_config-explicit
new file mode 100644
index 0000000..54f6891
--- /dev/null
+++ b/t/t5515/fetch.br-config-explicit-merge_config-explicit
@@ -0,0 +1,11 @@
+# br-config-explicit-merge config-explicit
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	branch 'one' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	branch 'two' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b		branch 'three' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-config-explicit-octopus b/t/t5515/fetch.br-config-explicit-octopus
new file mode 100644
index 0000000..7011dfc
--- /dev/null
+++ b/t/t5515/fetch.br-config-explicit-octopus
@@ -0,0 +1,11 @@
+# br-config-explicit-octopus
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689		branch 'one' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8		branch 'two' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	branch 'three' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-config-explicit-octopus_config-explicit b/t/t5515/fetch.br-config-explicit-octopus_config-explicit
new file mode 100644
index 0000000..bdad51f
--- /dev/null
+++ b/t/t5515/fetch.br-config-explicit-octopus_config-explicit
@@ -0,0 +1,11 @@
+# br-config-explicit-octopus config-explicit
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689		branch 'one' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8		branch 'two' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	branch 'three' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-config-explicit_config-explicit b/t/t5515/fetch.br-config-explicit_config-explicit
new file mode 100644
index 0000000..1b237dd
--- /dev/null
+++ b/t/t5515/fetch.br-config-explicit_config-explicit
@@ -0,0 +1,11 @@
+# br-config-explicit config-explicit
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f		branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	branch 'one' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	branch 'two' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	branch 'three' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-config-glob b/t/t5515/fetch.br-config-glob
new file mode 100644
index 0000000..e75ec2f
--- /dev/null
+++ b/t/t5515/fetch.br-config-glob
@@ -0,0 +1,11 @@
+# br-config-glob
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	branch 'one' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	branch 'three' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-config-glob-merge b/t/t5515/fetch.br-config-glob-merge
new file mode 100644
index 0000000..ce8f739
--- /dev/null
+++ b/t/t5515/fetch.br-config-glob-merge
@@ -0,0 +1,11 @@
+# br-config-glob-merge
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	branch 'one' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b		branch 'three' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-config-glob-merge_config-glob b/t/t5515/fetch.br-config-glob-merge_config-glob
new file mode 100644
index 0000000..5817bed
--- /dev/null
+++ b/t/t5515/fetch.br-config-glob-merge_config-glob
@@ -0,0 +1,11 @@
+# br-config-glob-merge config-glob
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	branch 'one' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b		branch 'three' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-config-glob-octopus b/t/t5515/fetch.br-config-glob-octopus
new file mode 100644
index 0000000..9ee213e
--- /dev/null
+++ b/t/t5515/fetch.br-config-glob-octopus
@@ -0,0 +1,11 @@
+# br-config-glob-octopus
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689		branch 'one' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	branch 'three' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-config-glob-octopus_config-glob b/t/t5515/fetch.br-config-glob-octopus_config-glob
new file mode 100644
index 0000000..44bd0ec
--- /dev/null
+++ b/t/t5515/fetch.br-config-glob-octopus_config-glob
@@ -0,0 +1,11 @@
+# br-config-glob-octopus config-glob
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689		branch 'one' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	branch 'three' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-config-glob_config-glob b/t/t5515/fetch.br-config-glob_config-glob
new file mode 100644
index 0000000..a6c20f9
--- /dev/null
+++ b/t/t5515/fetch.br-config-glob_config-glob
@@ -0,0 +1,11 @@
+# br-config-glob config-glob
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	branch 'one' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	branch 'three' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-remote-explicit b/t/t5515/fetch.br-remote-explicit
new file mode 100644
index 0000000..83534d2
--- /dev/null
+++ b/t/t5515/fetch.br-remote-explicit
@@ -0,0 +1,11 @@
+# br-remote-explicit
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f		branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	branch 'one' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	branch 'two' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	branch 'three' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-remote-explicit-merge b/t/t5515/fetch.br-remote-explicit-merge
new file mode 100644
index 0000000..a9064dd
--- /dev/null
+++ b/t/t5515/fetch.br-remote-explicit-merge
@@ -0,0 +1,11 @@
+# br-remote-explicit-merge
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	branch 'one' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	branch 'two' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b		branch 'three' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-remote-explicit-merge_remote-explicit b/t/t5515/fetch.br-remote-explicit-merge_remote-explicit
new file mode 100644
index 0000000..732a37e
--- /dev/null
+++ b/t/t5515/fetch.br-remote-explicit-merge_remote-explicit
@@ -0,0 +1,11 @@
+# br-remote-explicit-merge remote-explicit
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	branch 'one' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	branch 'two' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b		branch 'three' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-remote-explicit-octopus b/t/t5515/fetch.br-remote-explicit-octopus
new file mode 100644
index 0000000..ecf020d
--- /dev/null
+++ b/t/t5515/fetch.br-remote-explicit-octopus
@@ -0,0 +1,11 @@
+# br-remote-explicit-octopus
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689		branch 'one' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8		branch 'two' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	branch 'three' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-remote-explicit-octopus_remote-explicit b/t/t5515/fetch.br-remote-explicit-octopus_remote-explicit
new file mode 100644
index 0000000..af77531
--- /dev/null
+++ b/t/t5515/fetch.br-remote-explicit-octopus_remote-explicit
@@ -0,0 +1,11 @@
+# br-remote-explicit-octopus remote-explicit
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689		branch 'one' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8		branch 'two' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	branch 'three' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-remote-explicit_remote-explicit b/t/t5515/fetch.br-remote-explicit_remote-explicit
new file mode 100644
index 0000000..51fae56
--- /dev/null
+++ b/t/t5515/fetch.br-remote-explicit_remote-explicit
@@ -0,0 +1,11 @@
+# br-remote-explicit remote-explicit
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f		branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	branch 'one' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	branch 'two' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	branch 'three' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-remote-glob b/t/t5515/fetch.br-remote-glob
new file mode 100644
index 0000000..94e6ad3
--- /dev/null
+++ b/t/t5515/fetch.br-remote-glob
@@ -0,0 +1,11 @@
+# br-remote-glob
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	branch 'one' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	branch 'three' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-remote-glob-merge b/t/t5515/fetch.br-remote-glob-merge
new file mode 100644
index 0000000..09362e2
--- /dev/null
+++ b/t/t5515/fetch.br-remote-glob-merge
@@ -0,0 +1,11 @@
+# br-remote-glob-merge
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	branch 'one' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b		branch 'three' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-remote-glob-merge_remote-glob b/t/t5515/fetch.br-remote-glob-merge_remote-glob
new file mode 100644
index 0000000..e2eabec
--- /dev/null
+++ b/t/t5515/fetch.br-remote-glob-merge_remote-glob
@@ -0,0 +1,11 @@
+# br-remote-glob-merge remote-glob
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	branch 'one' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b		branch 'three' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-remote-glob-octopus b/t/t5515/fetch.br-remote-glob-octopus
new file mode 100644
index 0000000..c1554f8
--- /dev/null
+++ b/t/t5515/fetch.br-remote-glob-octopus
@@ -0,0 +1,11 @@
+# br-remote-glob-octopus
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689		branch 'one' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	branch 'three' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-remote-glob-octopus_remote-glob b/t/t5515/fetch.br-remote-glob-octopus_remote-glob
new file mode 100644
index 0000000..e613434
--- /dev/null
+++ b/t/t5515/fetch.br-remote-glob-octopus_remote-glob
@@ -0,0 +1,11 @@
+# br-remote-glob-octopus remote-glob
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689		branch 'one' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	branch 'three' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-remote-glob_remote-glob b/t/t5515/fetch.br-remote-glob_remote-glob
new file mode 100644
index 0000000..646dbc8
--- /dev/null
+++ b/t/t5515/fetch.br-remote-glob_remote-glob
@@ -0,0 +1,11 @@
+# br-remote-glob remote-glob
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	branch 'one' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	branch 'three' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-unconfig b/t/t5515/fetch.br-unconfig
new file mode 100644
index 0000000..65ce6d9
--- /dev/null
+++ b/t/t5515/fetch.br-unconfig
@@ -0,0 +1,11 @@
+# br-unconfig
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	branch 'one' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	branch 'three' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-unconfig_--tags_.._.git b/t/t5515/fetch.br-unconfig_--tags_.._.git
new file mode 100644
index 0000000..8258c80
--- /dev/null
+++ b/t/t5515/fetch.br-unconfig_--tags_.._.git
@@ -0,0 +1,7 @@
+# br-unconfig --tags ../.git
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-unconfig_.._.git b/t/t5515/fetch.br-unconfig_.._.git
new file mode 100644
index 0000000..284bb1f
--- /dev/null
+++ b/t/t5515/fetch.br-unconfig_.._.git
@@ -0,0 +1,2 @@
+# br-unconfig ../.git
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b		../
diff --git a/t/t5515/fetch.br-unconfig_.._.git_one b/t/t5515/fetch.br-unconfig_.._.git_one
new file mode 100644
index 0000000..11eb5a6
--- /dev/null
+++ b/t/t5515/fetch.br-unconfig_.._.git_one
@@ -0,0 +1,2 @@
+# br-unconfig ../.git one
+8e32a6d901327a23ef831511badce7bf3bf46689		branch 'one' of ../
diff --git a/t/t5515/fetch.br-unconfig_.._.git_one_tag_tag-one_tag_tag-three-file b/t/t5515/fetch.br-unconfig_.._.git_one_tag_tag-one_tag_tag-three-file
new file mode 100644
index 0000000..f02bab2
--- /dev/null
+++ b/t/t5515/fetch.br-unconfig_.._.git_one_tag_tag-one_tag_tag-three-file
@@ -0,0 +1,8 @@
+# br-unconfig ../.git one tag tag-one tag tag-three-file
+8e32a6d901327a23ef831511badce7bf3bf46689		branch 'one' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689		tag 'tag-one' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-unconfig_.._.git_one_two b/t/t5515/fetch.br-unconfig_.._.git_one_two
new file mode 100644
index 0000000..3f1be22
--- /dev/null
+++ b/t/t5515/fetch.br-unconfig_.._.git_one_two
@@ -0,0 +1,3 @@
+# br-unconfig ../.git one two
+8e32a6d901327a23ef831511badce7bf3bf46689		branch 'one' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8		branch 'two' of ../
diff --git a/t/t5515/fetch.br-unconfig_.._.git_tag_tag-one-tree_tag_tag-three-file b/t/t5515/fetch.br-unconfig_.._.git_tag_tag-one-tree_tag_tag-three-file
new file mode 100644
index 0000000..85de411
--- /dev/null
+++ b/t/t5515/fetch.br-unconfig_.._.git_tag_tag-one-tree_tag_tag-three-file
@@ -0,0 +1,7 @@
+# br-unconfig ../.git tag tag-one-tree tag tag-three-file
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-unconfig_.._.git_tag_tag-one_tag_tag-three b/t/t5515/fetch.br-unconfig_.._.git_tag_tag-one_tag_tag-three
new file mode 100644
index 0000000..0da2337
--- /dev/null
+++ b/t/t5515/fetch.br-unconfig_.._.git_tag_tag-one_tag_tag-three
@@ -0,0 +1,7 @@
+# br-unconfig ../.git tag tag-one tag tag-three
+8e32a6d901327a23ef831511badce7bf3bf46689		tag 'tag-one' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b		tag 'tag-three' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-unconfig_branches-default b/t/t5515/fetch.br-unconfig_branches-default
new file mode 100644
index 0000000..fc7041e
--- /dev/null
+++ b/t/t5515/fetch.br-unconfig_branches-default
@@ -0,0 +1,8 @@
+# br-unconfig branches-default
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f		branch 'master' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-unconfig_branches-one b/t/t5515/fetch.br-unconfig_branches-one
new file mode 100644
index 0000000..e94cde7
--- /dev/null
+++ b/t/t5515/fetch.br-unconfig_branches-one
@@ -0,0 +1,8 @@
+# br-unconfig branches-one
+8e32a6d901327a23ef831511badce7bf3bf46689		branch 'one' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-unconfig_config-explicit b/t/t5515/fetch.br-unconfig_config-explicit
new file mode 100644
index 0000000..01a283e
--- /dev/null
+++ b/t/t5515/fetch.br-unconfig_config-explicit
@@ -0,0 +1,11 @@
+# br-unconfig config-explicit
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f		branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	branch 'one' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	branch 'two' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	branch 'three' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-unconfig_config-glob b/t/t5515/fetch.br-unconfig_config-glob
new file mode 100644
index 0000000..3a556c5
--- /dev/null
+++ b/t/t5515/fetch.br-unconfig_config-glob
@@ -0,0 +1,11 @@
+# br-unconfig config-glob
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	branch 'one' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	branch 'three' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-unconfig_remote-explicit b/t/t5515/fetch.br-unconfig_remote-explicit
new file mode 100644
index 0000000..db216df
--- /dev/null
+++ b/t/t5515/fetch.br-unconfig_remote-explicit
@@ -0,0 +1,11 @@
+# br-unconfig remote-explicit
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f		branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	branch 'one' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	branch 'two' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	branch 'three' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.br-unconfig_remote-glob b/t/t5515/fetch.br-unconfig_remote-glob
new file mode 100644
index 0000000..aee65c2
--- /dev/null
+++ b/t/t5515/fetch.br-unconfig_remote-glob
@@ -0,0 +1,11 @@
+# br-unconfig remote-glob
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	branch 'one' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	branch 'three' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.master b/t/t5515/fetch.master
new file mode 100644
index 0000000..950fd07
--- /dev/null
+++ b/t/t5515/fetch.master
@@ -0,0 +1,11 @@
+# master
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	branch 'one' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	branch 'three' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.master_--tags_.._.git b/t/t5515/fetch.master_--tags_.._.git
new file mode 100644
index 0000000..0e59950
--- /dev/null
+++ b/t/t5515/fetch.master_--tags_.._.git
@@ -0,0 +1,7 @@
+# master --tags ../.git
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.master_.._.git b/t/t5515/fetch.master_.._.git
new file mode 100644
index 0000000..66d1aad
--- /dev/null
+++ b/t/t5515/fetch.master_.._.git
@@ -0,0 +1,2 @@
+# master ../.git
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b		../
diff --git a/t/t5515/fetch.master_.._.git_one b/t/t5515/fetch.master_.._.git_one
new file mode 100644
index 0000000..35deddb
--- /dev/null
+++ b/t/t5515/fetch.master_.._.git_one
@@ -0,0 +1,2 @@
+# master ../.git one
+8e32a6d901327a23ef831511badce7bf3bf46689		branch 'one' of ../
diff --git a/t/t5515/fetch.master_.._.git_one_tag_tag-one_tag_tag-three-file b/t/t5515/fetch.master_.._.git_one_tag_tag-one_tag_tag-three-file
new file mode 100644
index 0000000..8286852
--- /dev/null
+++ b/t/t5515/fetch.master_.._.git_one_tag_tag-one_tag_tag-three-file
@@ -0,0 +1,8 @@
+# master ../.git one tag tag-one tag tag-three-file
+8e32a6d901327a23ef831511badce7bf3bf46689		branch 'one' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689		tag 'tag-one' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.master_.._.git_one_two b/t/t5515/fetch.master_.._.git_one_two
new file mode 100644
index 0000000..35ec578
--- /dev/null
+++ b/t/t5515/fetch.master_.._.git_one_two
@@ -0,0 +1,3 @@
+# master ../.git one two
+8e32a6d901327a23ef831511badce7bf3bf46689		branch 'one' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8		branch 'two' of ../
diff --git a/t/t5515/fetch.master_.._.git_tag_tag-one-tree_tag_tag-three-file b/t/t5515/fetch.master_.._.git_tag_tag-one-tree_tag_tag-three-file
new file mode 100644
index 0000000..2e133ef
--- /dev/null
+++ b/t/t5515/fetch.master_.._.git_tag_tag-one-tree_tag_tag-three-file
@@ -0,0 +1,7 @@
+# master ../.git tag tag-one-tree tag tag-three-file
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.master_.._.git_tag_tag-one_tag_tag-three b/t/t5515/fetch.master_.._.git_tag_tag-one_tag_tag-three
new file mode 100644
index 0000000..92b18b4
--- /dev/null
+++ b/t/t5515/fetch.master_.._.git_tag_tag-one_tag_tag-three
@@ -0,0 +1,7 @@
+# master ../.git tag tag-one tag tag-three
+8e32a6d901327a23ef831511badce7bf3bf46689		tag 'tag-one' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b		tag 'tag-three' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.master_branches-default b/t/t5515/fetch.master_branches-default
new file mode 100644
index 0000000..603d6d2
--- /dev/null
+++ b/t/t5515/fetch.master_branches-default
@@ -0,0 +1,8 @@
+# master branches-default
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f		branch 'master' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.master_branches-one b/t/t5515/fetch.master_branches-one
new file mode 100644
index 0000000..fe9bb0b
--- /dev/null
+++ b/t/t5515/fetch.master_branches-one
@@ -0,0 +1,8 @@
+# master branches-one
+8e32a6d901327a23ef831511badce7bf3bf46689		branch 'one' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.master_config-explicit b/t/t5515/fetch.master_config-explicit
new file mode 100644
index 0000000..4be97c7
--- /dev/null
+++ b/t/t5515/fetch.master_config-explicit
@@ -0,0 +1,11 @@
+# master config-explicit
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f		branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	branch 'one' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	branch 'two' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	branch 'three' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.master_config-glob b/t/t5515/fetch.master_config-glob
new file mode 100644
index 0000000..cb0726f
--- /dev/null
+++ b/t/t5515/fetch.master_config-glob
@@ -0,0 +1,11 @@
+# master config-glob
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	branch 'one' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	branch 'three' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.master_remote-explicit b/t/t5515/fetch.master_remote-explicit
new file mode 100644
index 0000000..44a1ca8
--- /dev/null
+++ b/t/t5515/fetch.master_remote-explicit
@@ -0,0 +1,11 @@
+# master remote-explicit
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f		branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	branch 'one' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	branch 'two' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	branch 'three' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5515/fetch.master_remote-glob b/t/t5515/fetch.master_remote-glob
new file mode 100644
index 0000000..724e8db
--- /dev/null
+++ b/t/t5515/fetch.master_remote-glob
@@ -0,0 +1,11 @@
+# master remote-glob
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	branch 'master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	branch 'one' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	branch 'three' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	branch 'two' of ../
+754b754407bf032e9a2f9d5a9ad05ca79a6b228f	not-for-merge	tag 'tag-master' of ../
+8e32a6d901327a23ef831511badce7bf3bf46689	not-for-merge	tag 'tag-one' of ../
+22feea448b023a2d864ef94b013735af34d238ba	not-for-merge	tag 'tag-one-tree' of ../
+0567da4d5edd2ff4bb292a465ba9e64dcad9536b	not-for-merge	tag 'tag-three' of ../
+0e3b14047d3ee365f4f2a1b673db059c3972589c	not-for-merge	tag 'tag-three-file' of ../
+6134ee8f857693b96ff1cc98d3e2fd62b199e5a8	not-for-merge	tag 'tag-two' of ../
diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
index 7eb3783..243212d 100755
--- a/t/t5520-pull.sh
+++ b/t/t5520-pull.sh
@@ -29,5 +29,29 @@
 	diff file cloned/file
 '
 
+test_expect_success 'test . as a remote' '
+
+	git branch copy master &&
+	git config branch.copy.remote . &&
+	git config branch.copy.merge refs/heads/master &&
+	echo updated >file &&
+	git commit -a -m updated &&
+	git checkout copy &&
+	test `cat file` = file &&
+	git pull &&
+	test `cat file` = updated
+'
+
+test_expect_success 'the default remote . should not break explicit pull' '
+	git checkout -b second master^ &&
+	echo modified >file &&
+	git commit -a -m modified &&
+	git checkout copy &&
+	git reset --hard HEAD^ &&
+	test `cat file` = file &&
+	git pull . second &&
+	test `cat file` = modified
+'
+
 test_done
 
diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh
new file mode 100755
index 0000000..aab17fa
--- /dev/null
+++ b/t/t6006-rev-list-format.sh
@@ -0,0 +1,150 @@
+#!/bin/sh
+
+test_description='git-rev-list --pretty=format test'
+
+. ./test-lib.sh
+
+test_tick
+test_expect_success 'setup' '
+touch foo && git-add foo && git-commit -m "added foo" &&
+  echo changed >foo && git-commit -a -m "changed foo"
+'
+
+# usage: test_format name format_string <expected_output
+test_format() {
+	cat >expect.$1
+	test_expect_success "format $1" "
+git-rev-list --pretty=format:$2 master >output.$1 &&
+git-diff expect.$1 output.$1
+"
+}
+
+test_format hash %H%n%h <<'EOF'
+commit 131a310eb913d107dd3c09a65d1651175898735d
+131a310eb913d107dd3c09a65d1651175898735d
+131a310
+commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
+86c75cfd708a0e5868dc876ed5b8bb66c80b4873
+86c75cf
+EOF
+
+test_format tree %T%n%t <<'EOF'
+commit 131a310eb913d107dd3c09a65d1651175898735d
+fe722612f26da5064c32ca3843aa154bdb0b08a0
+fe72261
+commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
+4d5fcadc293a348e88f777dc0920f11e7d71441c
+4d5fcad
+EOF
+
+test_format parents %P%n%p <<'EOF'
+commit 131a310eb913d107dd3c09a65d1651175898735d
+86c75cfd708a0e5868dc876ed5b8bb66c80b4873
+86c75cf
+commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
+
+
+EOF
+
+# we don't test relative here
+test_format author %an%n%ae%n%ad%n%aD%n%at <<'EOF'
+commit 131a310eb913d107dd3c09a65d1651175898735d
+A U Thor
+author@example.com
+Thu Apr 7 15:13:13 2005 -0700
+Thu, 7 Apr 2005 15:13:13 -0700
+1112911993
+commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
+A U Thor
+author@example.com
+Thu Apr 7 15:13:13 2005 -0700
+Thu, 7 Apr 2005 15:13:13 -0700
+1112911993
+EOF
+
+test_format committer %cn%n%ce%n%cd%n%cD%n%ct <<'EOF'
+commit 131a310eb913d107dd3c09a65d1651175898735d
+C O Mitter
+committer@example.com
+Thu Apr 7 15:13:13 2005 -0700
+Thu, 7 Apr 2005 15:13:13 -0700
+1112911993
+commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
+C O Mitter
+committer@example.com
+Thu Apr 7 15:13:13 2005 -0700
+Thu, 7 Apr 2005 15:13:13 -0700
+1112911993
+EOF
+
+test_format encoding %e <<'EOF'
+commit 131a310eb913d107dd3c09a65d1651175898735d
+<unknown>
+commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
+<unknown>
+EOF
+
+test_format subject %s <<'EOF'
+commit 131a310eb913d107dd3c09a65d1651175898735d
+changed foo
+commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
+added foo
+EOF
+
+test_format body %b <<'EOF'
+commit 131a310eb913d107dd3c09a65d1651175898735d
+<unknown>
+commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
+<unknown>
+EOF
+
+test_format colors %Credfoo%Cgreenbar%Cbluebaz%Cresetxyzzy <<'EOF'
+commit 131a310eb913d107dd3c09a65d1651175898735d
+foobarbazxyzzy
+commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
+foobarbazxyzzy
+EOF
+
+cat >commit-msg <<'EOF'
+Test printing of complex bodies
+
+This commit message is much longer than the others,
+and it will be encoded in iso8859-1. We should therefore
+include an iso8859 character: ¡bueno!
+EOF
+test_expect_success 'setup complex body' '
+git-config i18n.commitencoding iso8859-1 &&
+  echo change2 >foo && git-commit -a -F commit-msg
+'
+
+test_format complex-encoding %e <<'EOF'
+commit f58db70b055c5718631e5c61528b28b12090cdea
+iso8859-1
+commit 131a310eb913d107dd3c09a65d1651175898735d
+<unknown>
+commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
+<unknown>
+EOF
+
+test_format complex-subject %s <<'EOF'
+commit f58db70b055c5718631e5c61528b28b12090cdea
+Test printing of complex bodies
+commit 131a310eb913d107dd3c09a65d1651175898735d
+changed foo
+commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
+added foo
+EOF
+
+test_format complex-body %b <<'EOF'
+commit f58db70b055c5718631e5c61528b28b12090cdea
+This commit message is much longer than the others,
+and it will be encoded in iso8859-1. We should therefore
+include an iso8859 character: ¡bueno!
+
+commit 131a310eb913d107dd3c09a65d1651175898735d
+<unknown>
+commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
+<unknown>
+EOF
+
+test_done
diff --git a/t/t6023-merge-file.sh b/t/t6023-merge-file.sh
index f3cd3db..c76fccf 100644
--- a/t/t6023-merge-file.sh
+++ b/t/t6023-merge-file.sh
@@ -63,7 +63,7 @@
 	"git-merge-file test2.txt orig.txt new2.txt"
 
 test_expect_success "merge result added missing LF" \
-	"diff -u test.txt test2.txt"
+	"git diff test.txt test2.txt"
 
 cp test.txt backup.txt
 test_expect_failure "merge with conflicts" \
@@ -86,7 +86,7 @@
 virga tua et baculus tuus ipsa me consolata sunt.
 EOF
 
-test_expect_success "expected conflict markers" "diff -u test.txt expect.txt"
+test_expect_success "expected conflict markers" "git diff test.txt expect.txt"
 
 cp backup.txt test.txt
 test_expect_failure "merge with conflicts, using -L" \
@@ -110,7 +110,7 @@
 EOF
 
 test_expect_success "expected conflict markers, with -L" \
-	"diff -u test.txt expect.txt"
+	"git diff test.txt expect.txt"
 
 sed "s/ tu / TU /" < new1.txt > new5.txt
 test_expect_failure "conflict in removed tail" \
@@ -132,7 +132,7 @@
 >>>>>>> new5.txt
 EOF
 
-test_expect_success "expected conflict markers" "diff -u expect out"
+test_expect_success "expected conflict markers" "git diff expect out"
 
 test_done
 
diff --git a/t/t6024-recursive-merge.sh b/t/t6024-recursive-merge.sh
index 31b9625..a398556 100644
--- a/t/t6024-recursive-merge.sh
+++ b/t/t6024-recursive-merge.sh
@@ -70,7 +70,7 @@
 >>>>>>> G:a1
 EOF
 
-test_expect_success "result contains a conflict" "diff -u expect a1"
+test_expect_success "result contains a conflict" "git diff expect a1"
 
 git ls-files --stage > out
 cat > expect << EOF
@@ -79,6 +79,6 @@
 100644 fd7923529855d0b274795ae3349c5e0438333979 3	a1
 EOF
 
-test_expect_success "virtual trees were processed" "diff -u expect out"
+test_expect_success "virtual trees were processed" "git diff expect out"
 
 test_done
diff --git a/t/t6025-merge-symlinks.sh b/t/t6025-merge-symlinks.sh
new file mode 100644
index 0000000..3c1a697
--- /dev/null
+++ b/t/t6025-merge-symlinks.sh
@@ -0,0 +1,62 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes Sixt
+#
+
+test_description='merging symlinks on filesystem w/o symlink support.
+
+This tests that git-merge-recursive writes merge results as plain files
+if core.symlinks is false.'
+
+. ./test-lib.sh
+
+test_expect_success \
+'setup' '
+git-config core.symlinks false &&
+> file &&
+git-add file &&
+git-commit -m initial &&
+git-branch b-symlink &&
+git-branch b-file &&
+l=$(echo -n file | git-hash-object -t blob -w --stdin) &&
+echo "120000 $l	symlink" | git-update-index --index-info &&
+git-commit -m master &&
+git-checkout b-symlink &&
+l=$(echo -n file-different | git-hash-object -t blob -w --stdin) &&
+echo "120000 $l	symlink" | git-update-index --index-info &&
+git-commit -m b-symlink &&
+git-checkout b-file &&
+echo plain-file > symlink &&
+git-add symlink &&
+git-commit -m b-file'
+
+test_expect_failure \
+'merge master into b-symlink, which has a different symbolic link' '
+! git-checkout b-symlink ||
+git-merge master'
+
+test_expect_success \
+'the merge result must be a file' '
+test -f symlink'
+
+test_expect_failure \
+'merge master into b-file, which has a file instead of a symbolic link' '
+! (git-reset --hard &&
+git-checkout b-file) ||
+git-merge master'
+
+test_expect_success \
+'the merge result must be a file' '
+test -f symlink'
+
+test_expect_failure \
+'merge b-file, which has a file instead of a symbolic link, into master' '
+! (git-reset --hard &&
+git-checkout master) ||
+git-merge b-file'
+
+test_expect_success \
+'the merge result must be a file' '
+test -f symlink'
+
+test_done
diff --git a/t/t6030-bisect-run.sh b/t/t6030-bisect-run.sh
new file mode 100755
index 0000000..39c7228
--- /dev/null
+++ b/t/t6030-bisect-run.sh
@@ -0,0 +1,57 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Christian Couder
+#
+test_description='Tests git-bisect run functionality'
+
+. ./test-lib.sh
+
+add_line_into_file()
+{
+    _line=$1
+    _file=$2
+
+    if [ -f "$_file" ]; then
+        echo "$_line" >> $_file || return $?
+        MSG="Add <$_line> into <$_file>."
+    else
+        echo "$_line" > $_file || return $?
+        git add $_file || return $?
+        MSG="Create file <$_file> with <$_line> inside."
+    fi
+
+    git-commit -m "$MSG" $_file
+}
+
+HASH1=
+HASH3=
+HASH4=
+
+test_expect_success \
+    'set up basic repo with 1 file (hello) and 4 commits' \
+    'add_line_into_file "1: Hello World" hello &&
+     add_line_into_file "2: A new day for git" hello &&
+     add_line_into_file "3: Another new day for git" hello &&
+     add_line_into_file "4: Ciao for now" hello &&
+     HASH1=$(git rev-list HEAD | tail -1) &&
+     HASH3=$(git rev-list HEAD | head -2 | tail -1) &&
+     HASH4=$(git rev-list HEAD | head -1)'
+
+# We want to automatically find the commit that
+# introduced "Another" into hello.
+test_expect_success \
+    'git bisect run simple case' \
+    'echo "#!/bin/sh" > test_script.sh &&
+     echo "grep Another hello > /dev/null" >> test_script.sh &&
+     echo "test \$? -ne 0" >> test_script.sh &&
+     chmod +x test_script.sh &&
+     git bisect start &&
+     git bisect good $HASH1 &&
+     git bisect bad $HASH4 &&
+     git bisect run ./test_script.sh > my_bisect_log.txt &&
+     grep "$HASH3 is first bad commit" my_bisect_log.txt'
+
+#
+#
+test_done
+
diff --git a/t/t6200-fmt-merge-msg.sh b/t/t6200-fmt-merge-msg.sh
index ea14023..526d7d1 100755
--- a/t/t6200-fmt-merge-msg.sh
+++ b/t/t6200-fmt-merge-msg.sh
@@ -79,7 +79,7 @@
 	git fetch . left &&
 
 	git fmt-merge-msg <.git/FETCH_HEAD >actual &&
-	diff -u actual expected
+	git diff actual expected
 '
 
 cat >expected <<\EOF
@@ -92,7 +92,7 @@
 	git fetch ../trash left &&
 
 	git fmt-merge-msg <.git/FETCH_HEAD >actual &&
-	diff -u actual expected
+	git diff actual expected
 '
 
 cat >expected <<\EOF
@@ -115,7 +115,7 @@
 	git fetch . left &&
 
 	git fmt-merge-msg <.git/FETCH_HEAD >actual &&
-	diff -u actual expected
+	git diff actual expected
 '
 
 cat >expected <<\EOF
@@ -145,7 +145,7 @@
 	git fetch . left right &&
 
 	git fmt-merge-msg <.git/FETCH_HEAD >actual &&
-	diff -u actual expected
+	git diff actual expected
 '
 
 test_expect_success 'merge-msg test #5' '
@@ -157,7 +157,7 @@
 	git fetch . left right &&
 
 	git fmt-merge-msg <.git/FETCH_HEAD >actual &&
-	diff -u actual expected
+	git diff actual expected
 '
 
 test_done
diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh
index 040da92..eb628fe 100755
--- a/t/t9100-git-svn-basic.sh
+++ b/t/t9100-git-svn-basic.sh
@@ -169,7 +169,7 @@
 	svn up '$SVN_TREE' &&
 	test -f '$SVN_TREE'/exec-2.sh &&
 	test ! -L '$SVN_TREE'/exec-2.sh &&
-	diff -u help $SVN_TREE/exec-2.sh"
+	git diff help $SVN_TREE/exec-2.sh"
 
 if test "$have_utf8" = t
 then
@@ -193,7 +193,7 @@
     "git-svn init $svnrepo && git-svn fetch &&
      git-rev-list --pretty=raw remotes/git-svn | grep ^tree | uniq > a &&
      git-rev-list --pretty=raw remotes/alt | grep ^tree | uniq > b &&
-     diff -u a b"
+     git diff a b"
 
 name='check imported tree checksums expected tree checksums'
 rm -f expected
@@ -211,8 +211,58 @@
 tree 8f51f74cf0163afc9ad68a4b1537288c4558b5a4
 EOF
 
-echo tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904 >> expected
+test_expect_success "$name" "git diff a expected"
 
-test_expect_success "$name" "diff -u a expected"
+test_expect_failure 'exit if remote refs are ambigious' "
+        git-config --add svn-remote.svn.fetch \
+                              bar:refs/remotes/git-svn &&
+        git-svn migrate
+        "
+
+test_expect_failure 'exit if init-ing a would clobber a URL' "
+        svnadmin create ${PWD}/svnrepo2 &&
+        svn mkdir -m 'mkdir bar' ${svnrepo}2/bar &&
+        git-config --unset svn-remote.svn.fetch \
+                                '^bar:refs/remotes/git-svn$' &&
+        git-svn init ${svnrepo}2/bar
+        "
+
+test_expect_success \
+  'init allows us to connect to another directory in the same repo' "
+        git-svn init -i bar $svnrepo/bar &&
+        git config --get svn-remote.svn.fetch \
+                              '^bar:refs/remotes/bar$' &&
+        git config --get svn-remote.svn.fetch \
+                              '^:refs/remotes/git-svn$'
+        "
+
+test_expect_success 'able to dcommit to a subdirectory' "
+	git-svn fetch -i bar &&
+	git checkout -b my-bar refs/remotes/bar &&
+	echo abc > d &&
+	git update-index --add d &&
+	git commit -m '/bar/d should be in the log' &&
+	git-svn dcommit -i bar &&
+	test -z \"\`git diff refs/heads/my-bar refs/remotes/bar\`\" &&
+	mkdir newdir &&
+	echo new > newdir/dir &&
+	git update-index --add newdir/dir &&
+	git commit -m 'add a new directory' &&
+	git-svn dcommit -i bar &&
+	test -z \"\`git diff refs/heads/my-bar refs/remotes/bar\`\" &&
+	echo foo >> newdir/dir &&
+	git update-index newdir/dir &&
+	git commit -m 'modify a file in new directory' &&
+	git-svn dcommit -i bar &&
+	test -z \"\`git diff refs/heads/my-bar refs/remotes/bar\`\"
+	"
+
+test_expect_success 'able to set-tree to a subdirectory' "
+	echo cba > d &&
+	git update-index d &&
+	git commit -m 'update /bar/d' &&
+	git-svn set-tree -i bar HEAD &&
+	test -z \"\`git diff refs/heads/my-bar refs/remotes/bar\`\"
+	"
 
 test_done
diff --git a/t/t9101-git-svn-props.sh b/t/t9101-git-svn-props.sh
index e8133d8..622ea1c 100755
--- a/t/t9101-git-svn-props.sh
+++ b/t/t9101-git-svn-props.sh
@@ -121,4 +121,30 @@
 test_expect_success 'CRLF + $Id$' "test '$a_cr' = '$b_cr'"
 test_expect_success 'CRLF + $Id$ (no newline)' "test '$a_ne_cr' = '$b_ne_cr'"
 
+cat > show-ignore.expect <<\EOF
+
+# /
+/no-such-file*
+
+# deeply
+/deeply/no-such-file*
+
+# deeply/nested
+/deeply/nested/no-such-file*
+
+# deeply/nested/directory
+/deeply/nested/directory/no-such-file*
+EOF
+
+test_expect_success 'test show-ignore' "
+	cd test_wc &&
+	mkdir -p deeply/nested/directory &&
+	svn add deeply &&
+	svn propset -R svn:ignore 'no-such-file*' .
+	svn commit -m 'propset svn:ignore'
+	cd .. &&
+	git-svn show-ignore > show-ignore.got &&
+	cmp show-ignore.expect show-ignore.got
+	"
+
 test_done
diff --git a/t/t9103-git-svn-graft-branches.sh b/t/t9103-git-svn-graft-branches.sh
deleted file mode 100755
index 183ae3b..0000000
--- a/t/t9103-git-svn-graft-branches.sh
+++ /dev/null
@@ -1,61 +0,0 @@
-#!/bin/sh
-test_description='git-svn graft-branches'
-. ./lib-git-svn.sh
-
-svnrepo="$svnrepo/test-git-svn"
-
-test_expect_success 'initialize repo' "
-	mkdir import &&
-	cd import &&
-	mkdir -p trunk branches tags &&
-	echo hello > trunk/readme &&
-	svn import -m 'import for git-svn' . $svnrepo &&
-	cd .. &&
-	svn cp -m 'tag a' $svnrepo/trunk $svnrepo/tags/a &&
-	svn cp -m 'branch a' $svnrepo/trunk $svnrepo/branches/a &&
-	svn co $svnrepo wc &&
-	cd wc &&
-	echo feedme >> branches/a/readme &&
-	poke branches/a/readme &&
-	svn commit -m hungry &&
-	cd trunk &&
-	svn merge -r3:4 $svnrepo/branches/a &&
-	svn commit -m 'merge with a' &&
-	cd ../.. &&
-	git-svn multi-init $svnrepo -T trunk -b branches -t tags &&
-	git-svn multi-fetch
-	"
-
-r1=`git-rev-list remotes/trunk | tail -n1`
-r2=`git-rev-list remotes/tags/a | tail -n1`
-r3=`git-rev-list remotes/a | tail -n1`
-r4=`git-rev-parse remotes/a`
-r5=`git-rev-parse remotes/trunk`
-
-test_expect_success 'test graft-branches regexes and copies' "
-	test -n "$r1" &&
-	test -n "$r2" &&
-	test -n "$r3" &&
-	test -n "$r4" &&
-	test -n "$r5" &&
-	git-svn graft-branches &&
-	grep '^$r2 $r1' $GIT_DIR/info/grafts &&
-	grep '^$r3 $r1' $GIT_DIR/info/grafts &&
-	grep '^$r5 ' $GIT_DIR/info/grafts | grep '$r4' | grep '$r1'
-	"
-
-test_debug 'gitk --all & sleep 1'
-
-test_expect_success 'test graft-branches with tree-joins' "
-	rm $GIT_DIR/info/grafts &&
-	git-svn graft-branches --no-default-regex --no-graft-copy -B &&
-	grep '^$r3 ' $GIT_DIR/info/grafts | grep '$r1' | grep '$r2' &&
-	grep '^$r2 $r1' $GIT_DIR/info/grafts &&
-	grep '^$r5 ' $GIT_DIR/info/grafts | grep '$r1' | grep '$r4'
-	"
-
-# the result of this is kinda funky, we have a strange history and
-# this is just a test :)
-test_debug 'gitk --all &'
-
-test_done
diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh
index 405b555..bd4f366 100755
--- a/t/t9104-git-svn-follow-parent.sh
+++ b/t/t9104-git-svn-follow-parent.sh
@@ -3,7 +3,7 @@
 # Copyright (c) 2006 Eric Wong
 #
 
-test_description='git-svn --follow-parent fetching'
+test_description='git-svn fetching'
 . ./lib-git-svn.sh
 
 test_expect_success 'initialize repo' "
@@ -27,11 +27,141 @@
 	cd ..
 	"
 
-test_expect_success 'init and fetch --follow-parent a moved directory' "
+test_expect_success 'init and fetch a moved directory' "
 	git-svn init -i thunk $svnrepo/thunk &&
-	git-svn fetch --follow-parent -i thunk &&
-	git-rev-parse --verify refs/remotes/trunk &&
-	test '$?' -eq '0'
+	git-svn fetch -i thunk &&
+	test \"\`git-rev-parse --verify refs/remotes/thunk@2\`\" \
+           = \"\`git-rev-parse --verify refs/remotes/thunk~1\`\" &&
+        test \"\`git-cat-file blob refs/remotes/thunk:readme |\
+                 sed -n -e '3p'\`\" = goodbye &&
+	test -z \"\`git-config --get svn-remote.svn.fetch \
+	         '^trunk:refs/remotes/thunk@2$'\`\"
+	"
+
+test_expect_success 'init and fetch from one svn-remote' "
+        git-config svn-remote.svn.url $svnrepo &&
+        git-config --add svn-remote.svn.fetch \
+          trunk:refs/remotes/svn/trunk &&
+        git-config --add svn-remote.svn.fetch \
+          thunk:refs/remotes/svn/thunk &&
+        git-svn fetch -i svn/thunk &&
+	test \"\`git-rev-parse --verify refs/remotes/svn/trunk\`\" \
+           = \"\`git-rev-parse --verify refs/remotes/svn/thunk~1\`\" &&
+        test \"\`git-cat-file blob refs/remotes/svn/thunk:readme |\
+                 sed -n -e '3p'\`\" = goodbye
+        "
+
+test_expect_success 'follow deleted parent' "
+        svn cp -m 'resurrecting trunk as junk' \
+               -r2 $svnrepo/trunk $svnrepo/junk &&
+        git-config --add svn-remote.svn.fetch \
+          junk:refs/remotes/svn/junk &&
+        git-svn fetch -i svn/thunk &&
+        git-svn fetch -i svn/junk &&
+        test -z \"\`git diff svn/junk svn/trunk\`\" &&
+        test \"\`git merge-base svn/junk svn/trunk\`\" \
+           = \"\`git rev-parse svn/trunk\`\"
+        "
+
+test_expect_success 'follow larger parent' "
+        mkdir -p import/trunk/thunk/bump/thud &&
+        echo hi > import/trunk/thunk/bump/thud/file &&
+        svn import -m 'import a larger parent' import $svnrepo/larger-parent &&
+        svn cp -m 'hi' $svnrepo/larger-parent $svnrepo/another-larger &&
+        git-svn init -i larger $svnrepo/another-larger/trunk/thunk/bump/thud &&
+        git-svn fetch -i larger &&
+        git-rev-parse --verify refs/remotes/larger &&
+        git-rev-parse --verify \
+           refs/remotes/larger-parent/trunk/thunk/bump/thud &&
+        test \"\`git-merge-base \
+                 refs/remotes/larger-parent/trunk/thunk/bump/thud \
+                 refs/remotes/larger\`\" = \
+             \"\`git-rev-parse refs/remotes/larger\`\"
+        true
+        "
+
+test_expect_success 'follow higher-level parent' "
+        svn mkdir -m 'follow higher-level parent' $svnrepo/blob &&
+        svn co $svnrepo/blob blob &&
+        cd blob &&
+                echo hi > hi &&
+                svn add hi &&
+                svn commit -m 'hihi' &&
+                cd ..
+        svn mkdir -m 'new glob at top level' $svnrepo/glob &&
+        svn mv -m 'move blob down a level' $svnrepo/blob $svnrepo/glob/blob &&
+        git-svn init -i blob $svnrepo/glob/blob &&
+        git-svn fetch -i blob
+        "
+
+test_expect_success 'follow deleted directory' "
+	svn mv -m 'bye!' $svnrepo/glob/blob/hi $svnrepo/glob/blob/bye &&
+	svn rm -m 'remove glob' $svnrepo/glob &&
+	git-svn init -i glob $svnrepo/glob &&
+	git-svn fetch -i glob &&
+	test \"\`git cat-file blob refs/remotes/glob:blob/bye\`\" = hi &&
+	test \"\`git ls-tree refs/remotes/glob | wc -l \`\" -eq 1
+	"
+
+# ref: r9270 of the Subversion repository: (http://svn.collab.net/repos/svn)
+# in trunk/subversion/bindings/swig/perl
+test_expect_success 'follow-parent avoids deleting relevant info' "
+	mkdir -p import/trunk/subversion/bindings/swig/perl/t &&
+	for i in a b c ; do \
+	  echo \$i > import/trunk/subversion/bindings/swig/perl/\$i.pm &&
+	  echo _\$i > import/trunk/subversion/bindings/swig/perl/t/\$i.t; \
+	done &&
+	  echo 'bad delete test' > \
+	   import/trunk/subversion/bindings/swig/perl/t/larger-parent &&
+	  echo 'bad delete test 2' > \
+	   import/trunk/subversion/bindings/swig/perl/another-larger &&
+	cd import &&
+	  svn import -m 'r9270 test' . $svnrepo/r9270 &&
+	cd .. &&
+	svn co $svnrepo/r9270/trunk/subversion/bindings/swig/perl r9270 &&
+	cd r9270 &&
+	  svn mkdir native &&
+	  svn mv t native/t &&
+	  for i in a b c; do svn mv \$i.pm native/\$i.pm; done &&
+	  echo z >> native/t/c.t &&
+	  poke native/t/c.t &&
+	  svn commit -m 'reorg test' &&
+	cd .. &&
+	git-svn init -i r9270-t \
+	  $svnrepo/r9270/trunk/subversion/bindings/swig/perl/native/t &&
+	git-svn fetch -i r9270-t &&
+	test \`git rev-list r9270-t | wc -l\` -eq 2 &&
+	test \"\`git ls-tree --name-only r9270-t~1\`\" = \
+	     \"\`git ls-tree --name-only r9270-t\`\"
+	"
+
+test_expect_success "track initial change if it was only made to parent" "
+	svn cp -m 'wheee!' $svnrepo/r9270/trunk $svnrepo/r9270/drunk &&
+	git-svn init -i r9270-d \
+	  $svnrepo/r9270/drunk/subversion/bindings/swig/perl/native/t &&
+	git-svn fetch -i r9270-d &&
+	test \`git rev-list r9270-d | wc -l\` -eq 3 &&
+	test \"\`git ls-tree --name-only r9270-t\`\" = \
+	     \"\`git ls-tree --name-only r9270-d\`\" &&
+	test \"\`git rev-parse r9270-t\`\" = \
+	     \"\`git rev-parse r9270-d~1\`\"
+	"
+
+test_expect_success "track multi-parent paths" "
+	svn cp -m 'resurrect /glob' $svnrepo/r9270 $svnrepo/glob &&
+	git-svn multi-fetch &&
+	test \`git cat-file commit refs/remotes/glob | \
+	       grep '^parent ' | wc -l\` -eq 2
+	"
+
+test_expect_success "multi-fetch continues to work" "
+	git-svn multi-fetch
+	"
+
+test_expect_success "multi-fetch works off a 'clean' repository" "
+	rm -r $GIT_DIR/svn $GIT_DIR/refs/remotes $GIT_DIR/logs &&
+	mkdir $GIT_DIR/svn &&
+	git-svn multi-fetch
 	"
 
 test_debug 'gitk --all &'
diff --git a/t/t9105-git-svn-commit-diff.sh b/t/t9105-git-svn-commit-diff.sh
index 6323c7e..c668dd1 100755
--- a/t/t9105-git-svn-commit-diff.sh
+++ b/t/t9105-git-svn-commit-diff.sh
@@ -31,4 +31,13 @@
 	cmp readme wc/readme
 	"
 
+test_expect_success 'commit-diff to a sub-directory (with git-svn config)' "
+	svn import -m 'sub-directory' import $svnrepo/subdir &&
+	git-svn init $svnrepo/subdir &&
+	git-svn fetch &&
+	git-svn commit-diff -r3 '$prev' '$head' &&
+	svn cat $svnrepo/subdir/readme > readme.2 &&
+	cmp readme readme.2
+	"
+
 test_done
diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh
new file mode 100755
index 0000000..dc2afda
--- /dev/null
+++ b/t/t9107-git-svn-migrate.sh
@@ -0,0 +1,112 @@
+#!/bin/sh
+# Copyright (c) 2006 Eric Wong
+test_description='git-svn metadata migrations from previous versions'
+. ./lib-git-svn.sh
+
+test_expect_success 'setup old-looking metadata' "
+	cp $GIT_DIR/config $GIT_DIR/config-old-git-svn &&
+	mkdir import &&
+	cd import &&
+		for i in trunk branches/a branches/b \
+		         tags/0.1 tags/0.2 tags/0.3; do
+			mkdir -p \$i && \
+			echo hello >> \$i/README || exit 1
+		done && \
+		svn import -m test . $svnrepo
+		cd .. &&
+	git-svn init $svnrepo &&
+	git-svn fetch &&
+	mv $GIT_DIR/svn/* $GIT_DIR/ &&
+	mv $GIT_DIR/svn/.metadata $GIT_DIR/ &&
+	rmdir $GIT_DIR/svn &&
+	git-update-ref refs/heads/git-svn-HEAD refs/remotes/git-svn &&
+	git-update-ref refs/heads/svn-HEAD refs/remotes/git-svn &&
+	git-update-ref -d refs/remotes/git-svn refs/remotes/git-svn
+	"
+
+head=`git rev-parse --verify refs/heads/git-svn-HEAD^0`
+test_expect_success 'git-svn-HEAD is a real HEAD' "test -n '$head'"
+
+test_expect_success 'initialize old-style (v0) git-svn layout' "
+	mkdir -p $GIT_DIR/git-svn/info $GIT_DIR/svn/info &&
+	echo $svnrepo > $GIT_DIR/git-svn/info/url &&
+	echo $svnrepo > $GIT_DIR/svn/info/url &&
+	git-svn migrate &&
+	! test -d $GIT_DIR/git-svn &&
+	git-rev-parse --verify refs/remotes/git-svn^0 &&
+	git-rev-parse --verify refs/remotes/svn^0 &&
+	test \`git config --get svn-remote.svn.url\` = '$svnrepo' &&
+	test \`git config --get svn-remote.svn.fetch\` = \
+             ':refs/remotes/git-svn'
+	"
+
+test_expect_success 'initialize a multi-repository repo' "
+	git-svn init $svnrepo -T trunk -t tags -b branches &&
+	git-config --get-all svn-remote.svn.fetch > fetch.out &&
+	grep '^trunk:refs/remotes/trunk$' fetch.out &&
+	test -n \"\`git-config --get svn-remote.svn.branches \
+	            '^branches/\*:refs/remotes/\*$'\`\" &&
+	test -n \"\`git-config --get svn-remote.svn.tags \
+	            '^tags/\*:refs/remotes/tags/\*$'\`\" &&
+	git config --unset svn-remote.svn.branches \
+	                        '^branches/\*:refs/remotes/\*$' &&
+	git config --unset svn-remote.svn.tags \
+	                        '^tags/\*:refs/remotes/tags/\*$' &&
+	git-config --add svn-remote.svn.fetch 'branches/a:refs/remotes/a' &&
+	git-config --add svn-remote.svn.fetch 'branches/b:refs/remotes/b' &&
+	for i in tags/0.1 tags/0.2 tags/0.3; do
+		git-config --add svn-remote.svn.fetch \
+		                 \$i:refs/remotes/\$i || exit 1; done
+	"
+
+# refs should all be different, but the trees should all be the same:
+test_expect_success 'multi-fetch works on partial urls + paths' "
+	git-svn multi-fetch &&
+	for i in trunk a b tags/0.1 tags/0.2 tags/0.3; do
+		git rev-parse --verify refs/remotes/\$i^0 >> refs.out || exit 1;
+	    done &&
+	test -z \"\`sort < refs.out | uniq -d\`\" &&
+	for i in trunk a b tags/0.1 tags/0.2 tags/0.3; do
+	  for j in trunk a b tags/0.1 tags/0.2 tags/0.3; do
+		if test \$j != \$i; then continue; fi
+	    test -z \"\`git diff refs/remotes/\$i \
+	                         refs/remotes/\$j\`\" ||exit 1; done; done
+	"
+
+test_expect_success 'migrate --minimize on old inited layout' "
+	git config --unset-all svn-remote.svn.fetch &&
+	git config --unset-all svn-remote.svn.url &&
+	rm -rf $GIT_DIR/svn &&
+	for i in \`cat fetch.out\`; do
+		path=\`expr \$i : '\\([^:]*\\):.*$'\`
+		ref=\`expr \$i : '[^:]*:refs/remotes/\\(.*\\)$'\`
+		if test -z \"\$ref\"; then continue; fi
+		if test -n \"\$path\"; then path=\"/\$path\"; fi
+		( mkdir -p $GIT_DIR/svn/\$ref/info/ &&
+		echo $svnrepo\$path > $GIT_DIR/svn/\$ref/info/url ) || exit 1;
+	done &&
+	git-svn migrate --minimize &&
+	test -z \"\`git-config -l |grep -v '^svn-remote\.git-svn\.'\`\" &&
+	git-config --get-all svn-remote.svn.fetch > fetch.out &&
+	grep '^trunk:refs/remotes/trunk$' fetch.out &&
+	grep '^branches/a:refs/remotes/a$' fetch.out &&
+	grep '^branches/b:refs/remotes/b$' fetch.out &&
+	grep '^tags/0\.1:refs/remotes/tags/0\.1$' fetch.out &&
+	grep '^tags/0\.2:refs/remotes/tags/0\.2$' fetch.out &&
+	grep '^tags/0\.3:refs/remotes/tags/0\.3$' fetch.out
+	grep '^:refs/remotes/git-svn' fetch.out
+	"
+
+test_expect_success  ".rev_db auto-converted to .rev_db.UUID" "
+	git-svn fetch -i trunk &&
+	expect=$GIT_DIR/svn/trunk/.rev_db.* &&
+	test -n \"\$expect\" &&
+	mv \$expect $GIT_DIR/svn/trunk/.rev_db &&
+	git-svn fetch -i trunk &&
+	test -L $GIT_DIR/svn/trunk/.rev_db &&
+	test -f \$expect &&
+	cmp \$expect $GIT_DIR/svn/trunk/.rev_db
+	"
+
+test_done
+
diff --git a/t/t9108-git-svn-glob.sh b/t/t9108-git-svn-glob.sh
new file mode 100755
index 0000000..db4344c
--- /dev/null
+++ b/t/t9108-git-svn-glob.sh
@@ -0,0 +1,86 @@
+#!/bin/sh
+# Copyright (c) 2007 Eric Wong
+test_description='git-svn globbing refspecs'
+. ./lib-git-svn.sh
+
+cat > expect.end <<EOF
+the end
+hi
+start a new branch
+initial
+EOF
+
+test_expect_success 'test refspec globbing' "
+	mkdir -p trunk/src/a trunk/src/b trunk/doc &&
+	echo 'hello world' > trunk/src/a/readme &&
+	echo 'goodbye world' > trunk/src/b/readme &&
+	svn import -m 'initial' trunk $svnrepo/trunk &&
+	svn co $svnrepo tmp &&
+	cd tmp &&
+		mkdir branches tags &&
+		svn add branches tags &&
+		svn cp trunk branches/start &&
+		svn commit -m 'start a new branch' &&
+		svn up &&
+		echo 'hi' >> branches/start/src/b/readme &&
+		poke branches/start/src/b/readme &&
+		echo 'hey' >> branches/start/src/a/readme &&
+		poke branches/start/src/a/readme &&
+		svn commit -m 'hi' &&
+		svn up &&
+		svn cp branches/start tags/end &&
+		echo 'bye' >> tags/end/src/b/readme &&
+		poke tags/end/src/b/readme &&
+		echo 'aye' >> tags/end/src/a/readme &&
+		poke tags/end/src/a/readme &&
+		svn commit -m 'the end' &&
+		echo 'byebye' >> tags/end/src/b/readme &&
+		poke tags/end/src/b/readme &&
+		svn commit -m 'nothing to see here'
+		cd .. &&
+	git config --add svn-remote.svn.url $svnrepo &&
+	git config --add svn-remote.svn.fetch \
+	                 'trunk/src/a:refs/remotes/trunk' &&
+	git config --add svn-remote.svn.branches \
+	                 'branches/*/src/a:refs/remotes/branches/*' &&
+	git config --add svn-remote.svn.tags\
+	                 'tags/*/src/a:refs/remotes/tags/*' &&
+	git-svn multi-fetch &&
+	git log --pretty=oneline refs/remotes/tags/end | \
+	    sed -e 's/^.\{41\}//' > output.end &&
+	cmp expect.end output.end &&
+	test \"\`git rev-parse refs/remotes/tags/end~1\`\" = \
+		\"\`git rev-parse refs/remotes/branches/start\`\" &&
+	test \"\`git rev-parse refs/remotes/branches/start~2\`\" = \
+		\"\`git rev-parse refs/remotes/trunk\`\"
+	"
+
+echo try to try > expect.two
+echo nothing to see here >> expect.two
+cat expect.end >> expect.two
+
+test_expect_success 'test left-hand-side only globbing' "
+	git config --add svn-remote.two.url $svnrepo &&
+	git config --add svn-remote.two.fetch trunk:refs/remotes/two/trunk &&
+	git config --add svn-remote.two.branches \
+	                 'branches/*:refs/remotes/two/branches/*' &&
+	git config --add svn-remote.two.tags \
+	                 'tags/*:refs/remotes/two/tags/*' &&
+	cd tmp &&
+		echo 'try try' >> tags/end/src/b/readme &&
+		poke tags/end/src/b/readme &&
+		svn commit -m 'try to try'
+		cd .. &&
+	git-svn fetch two &&
+	test \`git rev-list refs/remotes/two/tags/end | wc -l\` -eq 6 &&
+	test \`git rev-list refs/remotes/two/branches/start | wc -l\` -eq 3 &&
+	test \`git rev-parse refs/remotes/two/branches/start~2\` = \
+	     \`git rev-parse refs/remotes/two/trunk\` &&
+	test \`git rev-parse refs/remotes/two/tags/end~3\` = \
+	     \`git rev-parse refs/remotes/two/branches/start\` &&
+	git log --pretty=oneline refs/remotes/two/tags/end | \
+	    sed -e 's/^.\{41\}//' > output.two &&
+	cmp expect.two output.two
+	"
+
+test_done
diff --git a/t/t9110-git-svn-use-svm-props.sh b/t/t9110-git-svn-use-svm-props.sh
new file mode 100755
index 0000000..9db0d8f
--- /dev/null
+++ b/t/t9110-git-svn-use-svm-props.sh
@@ -0,0 +1,51 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Eric Wong
+#
+
+test_description='git-svn useSvmProps test'
+
+. ./lib-git-svn.sh
+
+test_expect_success 'load svm repo' "
+	svnadmin load -q $rawsvnrepo < ../t9110/svm.dump &&
+	git-svn init -R arr -i bar $svnrepo/mirror/arr &&
+	git-svn init -R argh -i dir $svnrepo/mirror/argh &&
+	git-svn init -R argh -i e $svnrepo/mirror/argh/a/b/c/d/e &&
+	git-config svn.useSvmProps true &&
+	git-svn fetch --all
+	"
+
+uuid=161ce429-a9dd-4828-af4a-52023f968c89
+
+bar_url=http://mayonaise/svnrepo/bar
+test_expect_success 'verify metadata for /bar' "
+	git-cat-file commit refs/remotes/bar | \
+	   grep '^git-svn-id: $bar_url@12 $uuid$' &&
+	git-cat-file commit refs/remotes/bar~1 | \
+	   grep '^git-svn-id: $bar_url@11 $uuid$' &&
+	git-cat-file commit refs/remotes/bar~2 | \
+	   grep '^git-svn-id: $bar_url@10 $uuid$' &&
+	git-cat-file commit refs/remotes/bar~3 | \
+	   grep '^git-svn-id: $bar_url@9 $uuid$' &&
+	git-cat-file commit refs/remotes/bar~4 | \
+	   grep '^git-svn-id: $bar_url@6 $uuid$' &&
+	git-cat-file commit refs/remotes/bar~5 | \
+	   grep '^git-svn-id: $bar_url@1 $uuid$'
+	"
+
+e_url=http://mayonaise/svnrepo/dir/a/b/c/d/e
+test_expect_success 'verify metadata for /dir/a/b/c/d/e' "
+	git-cat-file commit refs/remotes/e | \
+	   grep '^git-svn-id: $e_url@1 $uuid$'
+	"
+
+dir_url=http://mayonaise/svnrepo/dir
+test_expect_success 'verify metadata for /dir' "
+	git-cat-file commit refs/remotes/dir | \
+	   grep '^git-svn-id: $dir_url@2 $uuid$' &&
+	git-cat-file commit refs/remotes/dir~1 | \
+	   grep '^git-svn-id: $dir_url@1 $uuid$'
+	"
+
+test_done
diff --git a/t/t9110/svm.dump b/t/t9110/svm.dump
new file mode 100644
index 0000000..cc799c2
--- /dev/null
+++ b/t/t9110/svm.dump
@@ -0,0 +1,511 @@
+SVN-fs-dump-format-version: 2
+
+UUID: de5973c6-545d-41da-aded-c265f9039e74
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2007-02-17T06:54:59.793104Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 200
+Content-length: 200
+
+K 7
+svn:log
+V 40
+SVM: initializing mirror for /mirror/arr
+K 10
+svn:author
+V 3
+svm
+K 11
+svm:headrev
+V 39
+161ce429-a9dd-4828-af4a-52023f968c89:0
+
+K 8
+svn:date
+V 27
+2007-02-17T06:55:00.121647Z
+PROPS-END
+
+Node-path: 
+Node-kind: dir
+Node-action: change
+Prop-content-length: 44
+Content-length: 44
+
+K 10
+svm:mirror
+V 12
+/mirror/arr
+
+PROPS-END
+
+
+Node-path: mirror
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: mirror/arr
+Node-kind: dir
+Node-action: add
+Prop-content-length: 116
+Content-length: 116
+
+K 10
+svm:source
+V 29
+http://mayonaise/svnrepo!/bar
+K 8
+svm:uuid
+V 36
+161ce429-a9dd-4828-af4a-52023f968c89
+PROPS-END
+
+
+Revision-number: 2
+Prop-content-length: 182
+Content-length: 182
+
+K 7
+svn:log
+V 18
+import for git-svn
+K 10
+svn:author
+V 7
+svnsync
+K 11
+svm:headrev
+V 39
+161ce429-a9dd-4828-af4a-52023f968c89:1
+
+K 8
+svn:date
+V 27
+2007-02-17T05:10:52.108847Z
+PROPS-END
+
+Node-path: mirror/arr
+Node-kind: dir
+Node-action: change
+Prop-content-length: 116
+Content-length: 116
+
+K 10
+svm:source
+V 29
+http://mayonaise/svnrepo!/bar
+K 8
+svm:uuid
+V 36
+161ce429-a9dd-4828-af4a-52023f968c89
+PROPS-END
+
+
+Node-path: mirror/arr/zzz
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 4
+Text-content-md5: 33b02bc15ce9557d2dd8484d58f95ac4
+Content-length: 14
+
+PROPS-END
+zzz
+
+
+Revision-number: 3
+Prop-content-length: 230
+Content-length: 230
+
+K 7
+svn:log
+V 66
+new symlink is added to a file that was also just made executable
+
+K 10
+svn:author
+V 7
+svnsync
+K 11
+svm:headrev
+V 39
+161ce429-a9dd-4828-af4a-52023f968c89:6
+
+K 8
+svn:date
+V 27
+2007-02-17T05:11:01.686891Z
+PROPS-END
+
+Node-path: mirror/arr/zzz
+Node-kind: file
+Node-action: change
+Prop-content-length: 36
+Text-content-length: 4
+Text-content-md5: 33b02bc15ce9557d2dd8484d58f95ac4
+Content-length: 40
+
+K 14
+svn:executable
+V 1
+*
+PROPS-END
+zzz
+
+
+Revision-number: 4
+Prop-content-length: 192
+Content-length: 192
+
+K 7
+svn:log
+V 28
+/bar/d should be in the log
+
+K 10
+svn:author
+V 7
+svnsync
+K 11
+svm:headrev
+V 39
+161ce429-a9dd-4828-af4a-52023f968c89:9
+
+K 8
+svn:date
+V 27
+2007-02-17T05:11:07.686552Z
+PROPS-END
+
+Node-path: mirror/arr/d
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 4
+Text-content-md5: 0bee89b07a248e27c83fc3d5951213c1
+Content-length: 14
+
+PROPS-END
+abc
+
+
+Revision-number: 5
+Prop-content-length: 185
+Content-length: 185
+
+K 7
+svn:log
+V 20
+add a new directory
+
+K 10
+svn:author
+V 7
+svnsync
+K 11
+svm:headrev
+V 40
+161ce429-a9dd-4828-af4a-52023f968c89:10
+
+K 8
+svn:date
+V 27
+2007-02-17T05:11:08.405953Z
+PROPS-END
+
+Node-path: mirror/arr/newdir
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: mirror/arr/newdir/dir
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 4
+Text-content-md5: 9cd599a3523898e6a12e13ec787da50a
+Content-length: 14
+
+PROPS-END
+new
+
+
+Revision-number: 6
+Prop-content-length: 196
+Content-length: 196
+
+K 7
+svn:log
+V 31
+modify a file in new directory
+
+K 10
+svn:author
+V 7
+svnsync
+K 11
+svm:headrev
+V 40
+161ce429-a9dd-4828-af4a-52023f968c89:11
+
+K 8
+svn:date
+V 27
+2007-02-17T05:11:09.126645Z
+PROPS-END
+
+Node-path: mirror/arr/newdir/dir
+Node-kind: file
+Node-action: change
+Text-content-length: 8
+Text-content-md5: a950e20332358e523a5e9d571e47fa64
+Content-length: 8
+
+new
+foo
+
+
+Revision-number: 7
+Prop-content-length: 179
+Content-length: 179
+
+K 7
+svn:log
+V 14
+update /bar/d
+
+K 10
+svn:author
+V 7
+svnsync
+K 11
+svm:headrev
+V 40
+161ce429-a9dd-4828-af4a-52023f968c89:12
+
+K 8
+svn:date
+V 27
+2007-02-17T05:11:09.846221Z
+PROPS-END
+
+Node-path: mirror/arr/d
+Node-kind: file
+Node-action: change
+Text-content-length: 4
+Text-content-md5: 7abb78de7f2756ca8b511cbc879fd5e7
+Content-length: 4
+
+cba
+
+
+Revision-number: 8
+Prop-content-length: 201
+Content-length: 201
+
+K 7
+svn:log
+V 41
+SVM: initializing mirror for /mirror/argh
+K 10
+svn:author
+V 3
+svm
+K 11
+svm:headrev
+V 39
+161ce429-a9dd-4828-af4a-52023f968c89:0
+
+K 8
+svn:date
+V 27
+2007-02-17T06:56:03.703677Z
+PROPS-END
+
+Node-path: 
+Node-kind: dir
+Node-action: change
+Prop-content-length: 57
+Content-length: 57
+
+K 10
+svm:mirror
+V 25
+/mirror/argh
+/mirror/arr
+
+PROPS-END
+
+
+Node-path: mirror/argh
+Node-kind: dir
+Node-action: add
+Prop-content-length: 116
+Content-length: 116
+
+K 10
+svm:source
+V 29
+http://mayonaise/svnrepo!/dir
+K 8
+svm:uuid
+V 36
+161ce429-a9dd-4828-af4a-52023f968c89
+PROPS-END
+
+
+Revision-number: 9
+Prop-content-length: 182
+Content-length: 182
+
+K 7
+svn:log
+V 18
+import for git-svn
+K 10
+svn:author
+V 7
+svnsync
+K 11
+svm:headrev
+V 39
+161ce429-a9dd-4828-af4a-52023f968c89:1
+
+K 8
+svn:date
+V 27
+2007-02-17T05:10:52.108847Z
+PROPS-END
+
+Node-path: mirror/argh
+Node-kind: dir
+Node-action: change
+Prop-content-length: 116
+Content-length: 116
+
+K 10
+svm:source
+V 29
+http://mayonaise/svnrepo!/dir
+K 8
+svm:uuid
+V 36
+161ce429-a9dd-4828-af4a-52023f968c89
+PROPS-END
+
+
+Node-path: mirror/argh/a
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: mirror/argh/a/b
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: mirror/argh/a/b/c
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: mirror/argh/a/b/c/d
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: mirror/argh/a/b/c/d/e
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: mirror/argh/a/b/c/d/e/file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 9
+Text-content-md5: 3fd46fe46fcdcf062c802ca60dc826d5
+Content-length: 19
+
+PROPS-END
+deep dir
+
+
+Revision-number: 10
+Prop-content-length: 197
+Content-length: 197
+
+K 7
+svn:log
+V 33
+try a deep --rmdir with a commit
+
+K 10
+svn:author
+V 7
+svnsync
+K 11
+svm:headrev
+V 39
+161ce429-a9dd-4828-af4a-52023f968c89:2
+
+K 8
+svn:date
+V 27
+2007-02-17T05:10:54.847015Z
+PROPS-END
+
+Node-path: mirror/argh/file
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 9
+Node-copyfrom-path: mirror/argh/a/b/c/d/e/file
+Text-content-length: 9
+Text-content-md5: 3fd46fe46fcdcf062c802ca60dc826d5
+Content-length: 9
+
+deep dir
+
+
+Node-path: mirror/argh/a
+Node-action: delete
+
+
diff --git a/t/t9111-git-svn-use-svnsync-props.sh b/t/t9111-git-svn-use-svnsync-props.sh
new file mode 100755
index 0000000..483d7f8
--- /dev/null
+++ b/t/t9111-git-svn-use-svnsync-props.sh
@@ -0,0 +1,51 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Eric Wong
+#
+
+test_description='git-svn useSvnsyncProps test'
+
+. ./lib-git-svn.sh
+
+test_expect_success 'load svnsync repo' "
+	svnadmin load -q $rawsvnrepo < ../t9111/svnsync.dump &&
+	git-svn init -R arr -i bar $svnrepo/bar &&
+	git-svn init -R argh -i dir $svnrepo/dir &&
+	git-svn init -R argh -i e $svnrepo/dir/a/b/c/d/e &&
+	git-config svn.useSvnsyncProps true &&
+	git-svn fetch --all
+	"
+
+uuid=161ce429-a9dd-4828-af4a-52023f968c89
+
+bar_url=http://mayonaise/svnrepo/bar
+test_expect_success 'verify metadata for /bar' "
+	git-cat-file commit refs/remotes/bar | \
+	   grep '^git-svn-id: $bar_url@12 $uuid$' &&
+	git-cat-file commit refs/remotes/bar~1 | \
+	   grep '^git-svn-id: $bar_url@11 $uuid$' &&
+	git-cat-file commit refs/remotes/bar~2 | \
+	   grep '^git-svn-id: $bar_url@10 $uuid$' &&
+	git-cat-file commit refs/remotes/bar~3 | \
+	   grep '^git-svn-id: $bar_url@9 $uuid$' &&
+	git-cat-file commit refs/remotes/bar~4 | \
+	   grep '^git-svn-id: $bar_url@6 $uuid$' &&
+	git-cat-file commit refs/remotes/bar~5 | \
+	   grep '^git-svn-id: $bar_url@1 $uuid$'
+	"
+
+e_url=http://mayonaise/svnrepo/dir/a/b/c/d/e
+test_expect_success 'verify metadata for /dir/a/b/c/d/e' "
+	git-cat-file commit refs/remotes/e | \
+	   grep '^git-svn-id: $e_url@1 $uuid$'
+	"
+
+dir_url=http://mayonaise/svnrepo/dir
+test_expect_success 'verify metadata for /dir' "
+	git-cat-file commit refs/remotes/dir | \
+	   grep '^git-svn-id: $dir_url@2 $uuid$' &&
+	git-cat-file commit refs/remotes/dir~1 | \
+	   grep '^git-svn-id: $dir_url@1 $uuid$'
+	"
+
+test_done
diff --git a/t/t9111/svnsync.dump b/t/t9111/svnsync.dump
new file mode 100644
index 0000000..a9a46ee
--- /dev/null
+++ b/t/t9111/svnsync.dump
@@ -0,0 +1,562 @@
+SVN-fs-dump-format-version: 2
+
+UUID: b4bfe35e-f256-4096-874c-08c5639ecad7
+
+Revision-number: 0
+Prop-content-length: 240
+Content-length: 240
+
+K 18
+svn:sync-from-uuid
+V 36
+161ce429-a9dd-4828-af4a-52023f968c89
+K 10
+svn:author
+V 7
+svnsync
+K 24
+svn:sync-last-merged-rev
+V 2
+12
+K 8
+svn:date
+V 27
+2007-02-17T05:10:52.017552Z
+K 17
+svn:sync-from-url
+V 24
+http://mayonaise/svnrepo
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 120
+Content-length: 120
+
+K 7
+svn:log
+V 18
+import for git-svn
+K 10
+svn:author
+V 7
+svnsync
+K 8
+svn:date
+V 27
+2007-02-17T05:10:52.108847Z
+PROPS-END
+
+Node-path: bar
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: bar/zzz
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 4
+Text-content-md5: 33b02bc15ce9557d2dd8484d58f95ac4
+Content-length: 14
+
+PROPS-END
+zzz
+
+
+Node-path: dir
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: dir/a
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: dir/a/b
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: dir/a/b/c
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: dir/a/b/c/d
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: dir/a/b/c/d/e
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: dir/a/b/c/d/e/file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 9
+Text-content-md5: 3fd46fe46fcdcf062c802ca60dc826d5
+Content-length: 19
+
+PROPS-END
+deep dir
+
+
+Node-path: exec.sh
+Node-kind: file
+Node-action: add
+Prop-content-length: 35
+Text-content-length: 10
+Text-content-md5: 3e2b31c72181b87149ff995e7202c0e3
+Content-length: 45
+
+K 14
+svn:executable
+V 0
+
+PROPS-END
+#!/bin/sh
+
+
+Node-path: foo
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 4
+Text-content-md5: d3b07384d113edec49eaa6238ad5ff00
+Content-length: 14
+
+PROPS-END
+foo
+
+
+Node-path: foo.link
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 8
+Text-content-md5: 1043146e49ef02cab12eef865cb34ff3
+Content-length: 41
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+link foo
+
+Revision-number: 2
+Prop-content-length: 135
+Content-length: 135
+
+K 7
+svn:log
+V 33
+try a deep --rmdir with a commit
+
+K 10
+svn:author
+V 7
+svnsync
+K 8
+svn:date
+V 27
+2007-02-17T05:10:54.847015Z
+PROPS-END
+
+Node-path: dir/file
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: dir/a/b/c/d/e/file
+Text-content-length: 9
+Text-content-md5: 3fd46fe46fcdcf062c802ca60dc826d5
+Content-length: 9
+
+deep dir
+
+
+Node-path: dir/a
+Node-action: delete
+
+
+Node-path: file
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: dir/a/b/c/d/e/file
+Text-content-length: 9
+Text-content-md5: 3fd46fe46fcdcf062c802ca60dc826d5
+Content-length: 9
+
+deep dir
+
+
+Revision-number: 3
+Prop-content-length: 136
+Content-length: 136
+
+K 7
+svn:log
+V 34
+remove executable bit from a file
+
+K 10
+svn:author
+V 7
+svnsync
+K 8
+svn:date
+V 27
+2007-02-17T05:10:58.232691Z
+PROPS-END
+
+Node-path: exec.sh
+Node-kind: file
+Node-action: change
+Prop-content-length: 10
+Text-content-length: 10
+Text-content-md5: 3e2b31c72181b87149ff995e7202c0e3
+Content-length: 20
+
+PROPS-END
+#!/bin/sh
+
+
+Revision-number: 4
+Prop-content-length: 131
+Content-length: 131
+
+K 7
+svn:log
+V 29
+add executable bit back file
+
+K 10
+svn:author
+V 7
+svnsync
+K 8
+svn:date
+V 27
+2007-02-17T05:10:59.666560Z
+PROPS-END
+
+Node-path: exec.sh
+Node-kind: file
+Node-action: change
+Prop-content-length: 36
+Text-content-length: 10
+Text-content-md5: 3e2b31c72181b87149ff995e7202c0e3
+Content-length: 46
+
+K 14
+svn:executable
+V 1
+*
+PROPS-END
+#!/bin/sh
+
+
+Revision-number: 5
+Prop-content-length: 154
+Content-length: 154
+
+K 7
+svn:log
+V 52
+executable file becomes a symlink to bar/zzz (file)
+
+K 10
+svn:author
+V 7
+svnsync
+K 8
+svn:date
+V 27
+2007-02-17T05:11:00.676495Z
+PROPS-END
+
+Node-path: exec.sh
+Node-kind: file
+Node-action: change
+Prop-content-length: 33
+Text-content-length: 12
+Text-content-md5: f138693371665cc117742508761d684d
+Content-length: 45
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+link bar/zzz
+
+Revision-number: 6
+Prop-content-length: 168
+Content-length: 168
+
+K 7
+svn:log
+V 66
+new symlink is added to a file that was also just made executable
+
+K 10
+svn:author
+V 7
+svnsync
+K 8
+svn:date
+V 27
+2007-02-17T05:11:01.686891Z
+PROPS-END
+
+Node-path: bar/zzz
+Node-kind: file
+Node-action: change
+Prop-content-length: 36
+Text-content-length: 4
+Text-content-md5: 33b02bc15ce9557d2dd8484d58f95ac4
+Content-length: 40
+
+K 14
+svn:executable
+V 1
+*
+PROPS-END
+zzz
+
+
+Node-path: exec-2.sh
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: exec.sh
+Text-content-length: 12
+Text-content-md5: f138693371665cc117742508761d684d
+Content-length: 12
+
+link bar/zzz
+
+Revision-number: 7
+Prop-content-length: 136
+Content-length: 136
+
+K 7
+svn:log
+V 34
+modify a symlink to become a file
+
+K 10
+svn:author
+V 7
+svnsync
+K 8
+svn:date
+V 27
+2007-02-17T05:11:02.677035Z
+PROPS-END
+
+Node-path: exec-2.sh
+Node-kind: file
+Node-action: change
+Prop-content-length: 10
+Text-content-length: 9
+Text-content-md5: 8e92eff9e911886cede27d420f89c735
+Content-length: 19
+
+PROPS-END
+git help
+
+
+Revision-number: 8
+Prop-content-length: 109
+Content-length: 109
+
+K 7
+svn:log
+V 8
+éï∏
+
+K 10
+svn:author
+V 7
+svnsync
+K 8
+svn:date
+V 27
+2007-02-17T05:11:03.676862Z
+PROPS-END
+
+Node-path: exec-2.sh
+Node-kind: file
+Node-action: change
+Text-content-length: 17
+Text-content-md5: 49881954063cf26ca48c212396a957ca
+Content-length: 17
+
+git help
+# hello
+
+
+Revision-number: 9
+Prop-content-length: 130
+Content-length: 130
+
+K 7
+svn:log
+V 28
+/bar/d should be in the log
+
+K 10
+svn:author
+V 7
+svnsync
+K 8
+svn:date
+V 27
+2007-02-17T05:11:07.686552Z
+PROPS-END
+
+Node-path: bar/d
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 4
+Text-content-md5: 0bee89b07a248e27c83fc3d5951213c1
+Content-length: 14
+
+PROPS-END
+abc
+
+
+Revision-number: 10
+Prop-content-length: 122
+Content-length: 122
+
+K 7
+svn:log
+V 20
+add a new directory
+
+K 10
+svn:author
+V 7
+svnsync
+K 8
+svn:date
+V 27
+2007-02-17T05:11:08.405953Z
+PROPS-END
+
+Node-path: bar/newdir
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: bar/newdir/dir
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 4
+Text-content-md5: 9cd599a3523898e6a12e13ec787da50a
+Content-length: 14
+
+PROPS-END
+new
+
+
+Revision-number: 11
+Prop-content-length: 133
+Content-length: 133
+
+K 7
+svn:log
+V 31
+modify a file in new directory
+
+K 10
+svn:author
+V 7
+svnsync
+K 8
+svn:date
+V 27
+2007-02-17T05:11:09.126645Z
+PROPS-END
+
+Node-path: bar/newdir/dir
+Node-kind: file
+Node-action: change
+Text-content-length: 8
+Text-content-md5: a950e20332358e523a5e9d571e47fa64
+Content-length: 8
+
+new
+foo
+
+
+Revision-number: 12
+Prop-content-length: 116
+Content-length: 116
+
+K 7
+svn:log
+V 14
+update /bar/d
+
+K 10
+svn:author
+V 7
+svnsync
+K 8
+svn:date
+V 27
+2007-02-17T05:11:09.846221Z
+PROPS-END
+
+Node-path: bar/d
+Node-kind: file
+Node-action: change
+Text-content-length: 4
+Text-content-md5: 7abb78de7f2756ca8b511cbc879fd5e7
+Content-length: 4
+
+cba
+
+
diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh
index 970d683..8e958da 100755
--- a/t/t9300-fast-import.sh
+++ b/t/t9300-fast-import.sh
@@ -74,7 +74,7 @@
 test_expect_success \
 	'A: verify commit' \
 	'git-cat-file commit master | sed 1d >actual &&
-	diff -u expect actual'
+	git diff expect actual'
 
 cat >expect <<EOF
 100644 blob file2
@@ -84,22 +84,22 @@
 test_expect_success \
 	'A: verify tree' \
 	'git-cat-file -p master^{tree} | sed "s/ [0-9a-f]*	/ /" >actual &&
-	 diff -u expect actual'
+	 git diff expect actual'
 
 echo "$file2_data" >expect
 test_expect_success \
 	'A: verify file2' \
-	'git-cat-file blob master:file2 >actual && diff -u expect actual'
+	'git-cat-file blob master:file2 >actual && git diff expect actual'
 
 echo "$file3_data" >expect
 test_expect_success \
 	'A: verify file3' \
-	'git-cat-file blob master:file3 >actual && diff -u expect actual'
+	'git-cat-file blob master:file3 >actual && git diff expect actual'
 
 printf "$file4_data" >expect
 test_expect_success \
 	'A: verify file4' \
-	'git-cat-file blob master:file4 >actual && diff -u expect actual'
+	'git-cat-file blob master:file4 >actual && git diff expect actual'
 
 cat >expect <<EOF
 :2 `git-rev-parse --verify master:file2`
@@ -109,7 +109,15 @@
 EOF
 test_expect_success \
 	'A: verify marks output' \
-	'diff -u expect marks.out'
+	'git diff expect marks.out'
+
+test_expect_success \
+	'A: verify marks import' \
+	'git-fast-import \
+		--import-marks=marks.out \
+		--export-marks=marks.new \
+		</dev/null &&
+	git diff -u expect marks.new'
 
 ###
 ### series B
@@ -175,7 +183,7 @@
 test_expect_success \
 	'C: verify commit' \
 	'git-cat-file commit branch | sed 1d >actual &&
-	 diff -u expect actual'
+	 git diff expect actual'
 
 cat >expect <<EOF
 :000000 100755 0000000000000000000000000000000000000000 f1fb5da718392694d0076d677d6d0e364c79b0bc A	file2/newf
@@ -232,13 +240,13 @@
 test_expect_success \
 	'D: verify file5' \
 	'git-cat-file blob branch:newdir/interesting >actual &&
-	 diff -u expect actual'
+	 git diff expect actual'
 
 echo "$file6_data" >expect
 test_expect_success \
 	'D: verify file6' \
 	'git-cat-file blob branch:newdir/exec.sh >actual &&
-	 diff -u expect actual'
+	 git diff expect actual'
 
 ###
 ### series E
@@ -274,7 +282,7 @@
 test_expect_success \
 	'E: verify commit' \
 	'git-cat-file commit branch | sed 1,2d >actual &&
-	diff -u expect actual'
+	git diff expect actual'
 
 ###
 ### series F
@@ -327,7 +335,7 @@
 test_expect_success \
 	'F: verify other commit' \
 	'git-cat-file commit other >actual &&
-	diff -u expect actual'
+	git diff expect actual'
 
 ###
 ### series G
@@ -405,7 +413,7 @@
 test_expect_success \
 	'H: verify file' \
 	'git-cat-file blob H:h/e/l/lo >actual &&
-	 diff -u expect actual'
+	 git diff expect actual'
 
 ###
 ### series I
@@ -431,7 +439,7 @@
 test_expect_success \
 	'I: verify edge list' \
 	'sed -e s/pack-.*pack/pack-.pack/ edges.list >actual &&
-	 diff -u expect actual'
+	 git diff expect actual'
 
 ###
 ### series J
@@ -493,4 +501,54 @@
     'test `git-rev-parse --verify branch^1` \
 		= `git-rev-parse --verify K^1`'
 
+###
+### series L
+###
+
+cat >input <<INPUT_END
+blob
+mark :1
+data <<EOF
+some data
+EOF
+
+blob
+mark :2
+data <<EOF
+other data
+EOF
+
+commit refs/heads/L
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+create L
+COMMIT
+
+M 644 :1 b.
+M 644 :1 b/other
+M 644 :1 ba
+
+commit refs/heads/L
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+update L
+COMMIT
+
+M 644 :2 b.
+M 644 :2 b/other
+M 644 :2 ba
+INPUT_END
+
+cat >expect <<EXPECT_END
+:100644 100644 4268632... 55d3a52... M	b.
+:040000 040000 0ae5cac... 443c768... M	b
+:100644 100644 4268632... 55d3a52... M	ba
+EXPECT_END
+
+test_expect_success \
+    'L: verify internal tree sorting' \
+	'git-fast-import <input &&
+	 git-diff --raw L^ L >output &&
+	 git diff expect output'
+
 test_done
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 37822fc..c075474 100755
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -255,8 +255,8 @@
 PATH=$(pwd)/..:$PATH
 GIT_EXEC_PATH=$(pwd)/..
 GIT_TEMPLATE_DIR=$(pwd)/../templates/blt
-HOME=$(pwd)/trash
-export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR HOME
+GIT_CONFIG=.git/config
+export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG
 
 GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git
 export GITPERLLIB
@@ -264,6 +264,12 @@
 	error "You haven't built things yet, have you?"
 }
 
+if ! test -x ../test-chmtime; then
+	echo >&2 'You need to build test-chmtime:'
+	echo >&2 'Run "make test-chmtime" in the source (toplevel) directory'
+	exit 1
+fi
+
 # Test repository
 test=trash
 rm -fr "$test"
diff --git a/tag.c b/tag.c
index 864ac1b..56a49f4 100644
--- a/tag.c
+++ b/tag.c
@@ -1,5 +1,8 @@
 #include "cache.h"
 #include "tag.h"
+#include "commit.h"
+#include "tree.h"
+#include "blob.h"
 
 const char *tag_type = "tag";
 
@@ -37,7 +40,7 @@
 int parse_tag_buffer(struct tag *item, void *data, unsigned long size)
 {
 	int typelen, taglen;
-	unsigned char object[20];
+	unsigned char sha1[20];
 	const char *type_line, *tag_line, *sig_line;
 	char type[20];
 
@@ -47,7 +50,7 @@
 
 	if (size < 64)
 		return -1;
-	if (memcmp("object ", data, 7) || get_sha1_hex((char *) data + 7, object))
+	if (memcmp("object ", data, 7) || get_sha1_hex((char *) data + 7, sha1))
 		return -1;
 
 	type_line = (char *) data + 48;
@@ -73,7 +76,19 @@
 	memcpy(item->tag, tag_line + 4, taglen);
 	item->tag[taglen] = '\0';
 
-	item->tagged = lookup_object_type(object, type);
+	if (!strcmp(type, blob_type)) {
+		item->tagged = &lookup_blob(sha1)->object;
+	} else if (!strcmp(type, tree_type)) {
+		item->tagged = &lookup_tree(sha1)->object;
+	} else if (!strcmp(type, commit_type)) {
+		item->tagged = &lookup_commit(sha1)->object;
+	} else if (!strcmp(type, tag_type)) {
+		item->tagged = &lookup_tag(sha1)->object;
+	} else {
+		error("Unknown type %s", type);
+		item->tagged = NULL;
+	}
+
 	if (item->tagged && track_object_refs) {
 		struct object_refs *refs = alloc_object_refs(1);
 		refs->ref[0] = item->tagged;
@@ -85,18 +100,18 @@
 
 int parse_tag(struct tag *item)
 {
-	char type[20];
+	enum object_type type;
 	void *data;
 	unsigned long size;
 	int ret;
 
 	if (item->object.parsed)
 		return 0;
-	data = read_sha1_file(item->object.sha1, type, &size);
+	data = read_sha1_file(item->object.sha1, &type, &size);
 	if (!data)
 		return error("Could not read %s",
 			     sha1_to_hex(item->object.sha1));
-	if (strcmp(type, tag_type)) {
+	if (type != OBJ_TAG) {
 		free(data);
 		return error("Object %s not a tag",
 			     sha1_to_hex(item->object.sha1));
diff --git a/templates/Makefile b/templates/Makefile
index 0eeee43..b8352e7 100644
--- a/templates/Makefile
+++ b/templates/Makefile
@@ -1,5 +1,9 @@
 # make and install sample templates
 
+ifndef V
+	QUIET = @
+endif
+
 INSTALL ?= install
 TAR ?= tar
 prefix ?= $(HOME)
@@ -18,7 +22,7 @@
 
 bpsrc = $(filter-out %~,$(wildcard *--*))
 boilerplates.made : $(bpsrc)
-	ls *--* 2>/dev/null | \
+	$(QUIET)ls *--* 2>/dev/null | \
 	while read boilerplate; \
 	do \
 		case "$$boilerplate" in *~) continue ;; esac && \
@@ -29,13 +33,13 @@
 		*--) ;; \
 		*) cp $$boilerplate blt/$$dst ;; \
 		esac || exit; \
-	done || exit
+	done && \
 	date >$@
 
 # If you need build-tailored templates, build them into blt/
 # directory yourself here.
 custom:
-	: no custom templates yet
+	$(QUIET): no custom templates yet
 
 clean:
 	rm -rf blt boilerplates.made
diff --git a/templates/hooks--post-receive b/templates/hooks--post-receive
new file mode 100644
index 0000000..190de26
--- /dev/null
+++ b/templates/hooks--post-receive
@@ -0,0 +1,17 @@
+#!/bin/sh
+#
+# An example hook script for the post-receive event
+#
+# This script is run after receive-pack has accepted a pack and the
+# repository has been updated.  It is passed arguments in through stdin
+# in the form
+#  <oldrev> <newrev> <refname>
+# For example:
+#  aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master
+#
+# see contrib/hooks/ for an sample, or uncomment the next line (on debian)
+#
+
+
+#. /usr/share/doc/git-core/contrib/hooks/post-receive-email
+
diff --git a/templates/hooks--update b/templates/hooks--update
index 555bd5f..0ff0330 100644
--- a/templates/hooks--update
+++ b/templates/hooks--update
@@ -1,36 +1,16 @@
 #!/bin/sh
 #
-# An example hook script to mail out commit update information.
-# It can also blocks tags that aren't annotated.
+# An example hook script to blocks unannotated tags from entering.
 # Called by git-receive-pack with arguments: refname sha1-old sha1-new
 #
 # To enable this hook, make this file executable by "chmod +x update".
 #
 # Config
 # ------
-# hooks.mailinglist
-#   This is the list that all pushes will go to; leave it blank to not send
-#   emails frequently.  The log email will list every log entry in full between
-#   the old ref value and the new ref value.
-# hooks.announcelist
-#   This is the list that all pushes of annotated tags will go to.  Leave it
-#   blank to just use the mailinglist field.  The announce emails list the
-#   short log summary of the changes since the last annotated tag
 # hooks.allowunannotated
 #   This boolean sets whether unannotated tags will be allowed into the
 #   repository.  By default they won't be.
 #
-# Notes
-# -----
-# All emails have their subjects prefixed with "[SCM]" to aid filtering.
-# All emails include the headers "X-Git-Refname", "X-Git-Oldrev",
-# "X-Git-Newrev", and "X-Git-Reftype" to enable fine tuned filtering and info.
-
-# --- Constants
-EMAILPREFIX="[SCM] "
-LOGBEGIN="- Log -----------------------------------------------------------------"
-LOGEND="-----------------------------------------------------------------------"
-DATEFORMAT="%F %R %z"
 
 # --- Command line
 refname="$1"
@@ -51,235 +31,42 @@
 fi
 
 # --- Config
-projectdesc=$(cat $GIT_DIR/description)
-recipients=$(git-repo-config hooks.mailinglist)
-announcerecipients=$(git-repo-config hooks.announcelist)
 allowunannotated=$(git-repo-config --bool hooks.allowunannotated)
 
+# check for no description
+if [ -z "$projectdesc" -o "$projectdesc" = "Unnamed repository; edit this file to name it for gitweb" ]; then
+	echo "*** Project description file hasn't been set" >&2
+	exit 1
+fi
+
 # --- Check types
-newrev_type=$(git-cat-file -t "$newrev")
+newrev_type=$(git-cat-file -t $newrev)
 
 case "$refname","$newrev_type" in
 	refs/tags/*,commit)
 		# un-annotated tag
-		refname_type="tag"
 		short_refname=${refname##refs/tags/}
 		if [ "$allowunannotated" != "true" ]; then
-			echo "*** The un-annotated tag, $short_refname is not allowed in this repository" >&2
+			echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2
 			echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
 			exit 1
 		fi
 		;;
 	refs/tags/*,tag)
 		# annotated tag
-		refname_type="annotated tag"
-		short_refname=${refname##refs/tags/}
-		# change recipients
-		if [ -n "$announcerecipients" ]; then
-			recipients="$announcerecipients"
-		fi
 		;;
 	refs/heads/*,commit)
 		# branch
-		refname_type="branch"
-		short_refname=${refname##refs/heads/}
 		;;
 	refs/remotes/*,commit)
 		# tracking branch
-		refname_type="tracking branch"
-		short_refname=${refname##refs/remotes/}
-		# Should this even be allowed?
-		echo "*** Push-update of tracking branch, $refname.  No email generated." >&2
-		exit 0
 		;;
 	*)
 		# Anything else (is there anything else?)
-		echo "*** Update hook: unknown type of update, \"$newrev_type\", to ref $refname" >&2
+		echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
 		exit 1
 		;;
 esac
 
-# Check if we've got anyone to send to
-if [ -z "$recipients" ]; then
-	# If the email isn't sent, then at least give the user some idea of what command
-	# would generate the email at a later date
-	echo "*** No recipients found - no email will be sent, but the push will continue" >&2
-	echo "*** for $0 $1 $2 $3" >&2
-	exit 0
-fi
-
-# --- Email parameters
-committer=$(git show --pretty=full -s $newrev | grep "^Commit: " | sed -e "s/^Commit: //")
-describe=$(git describe $newrev 2>/dev/null)
-if [ -z "$describe" ]; then
-	describe=$newrev
-fi
-
-# --- Email (all stdout will be the email)
-(
-# Generate header
-cat <<-EOF
-From: $committer
-To: $recipients
-Subject: ${EMAILPREFIX}$projectdesc $refname_type, $short_refname now at $describe
-X-Git-Refname: $refname
-X-Git-Reftype: $refname_type
-X-Git-Oldrev: $oldrev
-X-Git-Newrev: $newrev
-
-Hello,
-
-This is an automated email from the git hooks/update script, it was
-generated because a ref change was pushed to the repository.
-
-Updating $refname_type, $short_refname,
-EOF
-
-case "$refname_type" in
-	"tracking branch"|branch)
-		if expr "$oldrev" : '0*$' >/dev/null
-		then
-			# If the old reference is "0000..0000" then this is a new branch
-			# and so oldrev is not valid
-			echo "  as a new  $refname_type"
-		    echo "        to  $newrev ($newrev_type)"
-			echo ""
-			echo $LOGBEGIN
-			# This shows all log entries that are not already covered by
-			# another ref - i.e. commits that are now accessible from this
-			# ref that were previously not accessible
-			git-rev-parse --not --all | git-rev-list --stdin --pretty $newref
-			echo $LOGEND
-		else
-			# oldrev is valid
-			oldrev_type=$(git-cat-file -t "$oldrev")
-
-			# Now the problem is for cases like this:
-			#   * --- * --- * --- * (oldrev)
-			#          \
-			#           * --- * --- * (newrev)
-			# i.e. there is no guarantee that newrev is a strict subset
-			# of oldrev - (would have required a force, but that's allowed).
-			# So, we can't simply say rev-list $oldrev..$newrev.  Instead
-			# we find the common base of the two revs and list from there
-			baserev=$(git-merge-base $oldrev $newrev)
-
-			# Commit with a parent
-			for rev in $(git-rev-list $newrev ^$baserev)
-			do
-				revtype=$(git-cat-file -t "$rev")
-				echo "       via  $rev ($revtype)"
-			done
-			if [ "$baserev" = "$oldrev" ]; then
-				echo "      from  $oldrev ($oldrev_type)"
-			else
-				echo "  based on  $baserev"
-				echo "      from  $oldrev ($oldrev_type)"
-				echo ""
-				echo "This ref update crossed a branch point; i.e. the old rev is not a strict subset"
-				echo "of the new rev.  This occurs, when you --force push a change in a situation"
-				echo "like this:"
-				echo ""
-				echo " * -- * -- B -- O -- O -- O ($oldrev)"
-				echo "            \\"
-				echo "             N -- N -- N ($newrev)"
-				echo ""
-				echo "Therefore, we assume that you've already had alert emails for all of the O"
-				echo "revisions, and now give you all the revisions in the N branch from the common"
-				echo "base, B ($baserev), up to the new revision."
-			fi
-			echo ""
-			echo $LOGBEGIN
-			git-rev-list --pretty $newrev ^$baserev
-			echo $LOGEND
-			echo ""
-			echo "Diffstat:"
-			git-diff-tree --no-color --stat -M -C --find-copies-harder $newrev ^$baserev
-		fi
-		;;
-	"annotated tag")
-		# Should we allow changes to annotated tags?
-		if expr "$oldrev" : '0*$' >/dev/null
-		then
-			# If the old reference is "0000..0000" then this is a new atag
-			# and so oldrev is not valid
-			echo "        to  $newrev ($newrev_type)"
-		else
-			echo "        to  $newrev ($newrev_type)"
-			echo "      from  $oldrev"
-		fi
-
-		# If this tag succeeds another, then show which tag it replaces
-		prevtag=$(git describe $newrev^ 2>/dev/null | sed 's/-g.*//')
-		if [ -n "$prevtag" ]; then
-			echo "  replaces  $prevtag"
-		fi
-
-		# Read the tag details
-		eval $(git cat-file tag $newrev | \
-			sed -n '4s/tagger \([^>]*>\)[^0-9]*\([0-9]*\).*/tagger="\1" ts="\2"/p')
-		tagged=$(date --date="1970-01-01 00:00:00 +0000 $ts seconds" +"$DATEFORMAT")
-
-		echo " tagged by  $tagger"
-		echo "        on  $tagged"
-
-		echo ""
-		echo $LOGBEGIN
-		echo ""
-
-		if [ -n "$prevtag" ]; then
-			git rev-list --pretty=short "$prevtag..$newrev" | git shortlog
-		else
-			git rev-list --pretty=short $newrev | git shortlog
-		fi
-
-		echo $LOGEND
-		echo ""
-		;;
-	*)
-		# By default, unannotated tags aren't allowed in; if
-		# they are though, it's debatable whether we would even want an
-		# email to be generated; however, I don't want to add another config
-		# option just for that.
-		#
-		# Unannotated tags are more about marking a point than releasing
-		# a version; therefore we don't do the shortlog summary that we
-		# do for annotated tags above - we simply show that the point has
-		# been marked, and print the log message for the marked point for
-		# reference purposes
-		#
-		# Note this section also catches any other reference type (although
-		# there aren't any) and deals with them in the same way.
-		if expr "$oldrev" : '0*$' >/dev/null
-		then
-			# If the old reference is "0000..0000" then this is a new tag
-			# and so oldrev is not valid
-			echo "  as a new  $refname_type"
-			echo "        to  $newrev ($newrev_type)"
-		else
-			echo "        to  $newrev ($newrev_type)"
-			echo "      from  $oldrev"
-		fi
-		echo ""
-		echo $LOGBEGIN
-		git-show --no-color --root -s $newrev
-		echo $LOGEND
-		echo ""
-		;;
-esac
-
-# Footer
-cat <<-EOF
-
-hooks/update
----
-Git Source Code Management System
-$0 $1 \\
-  $2 \\
-  $3
-EOF
-#) | cat >&2
-) | /usr/sbin/sendmail -t
-
 # --- Finished
 exit 0
diff --git a/test-chmtime.c b/test-chmtime.c
new file mode 100644
index 0000000..90da448
--- /dev/null
+++ b/test-chmtime.c
@@ -0,0 +1,61 @@
+#include "git-compat-util.h"
+#include <utime.h>
+
+static const char usage_str[] = "(+|=|=+|=-|-)<seconds> <file>...";
+
+int main(int argc, const char *argv[])
+{
+	int i;
+	int set_eq;
+	long int set_time;
+	char *test;
+	const char *timespec;
+
+	if (argc < 3)
+		goto usage;
+
+	timespec = argv[1];
+	set_eq = (*timespec == '=') ? 1 : 0;
+	if (set_eq) {
+		timespec++;
+		if (*timespec == '+') {
+			set_eq = 2; /* relative "in the future" */
+			timespec++;
+		}
+	}
+	set_time = strtol(timespec, &test, 10);
+	if (*test) {
+		fprintf(stderr, "Not a base-10 integer: %s\n", argv[1] + 1);
+		goto usage;
+	}
+	if ((set_eq && set_time < 0) || set_eq == 2) {
+		time_t now = time(NULL);
+		set_time += now;
+	}
+
+	for (i = 2; i < argc; i++) {
+		struct stat sb;
+		struct utimbuf utb;
+
+		if (stat(argv[i], &sb) < 0) {
+			fprintf(stderr, "Failed to stat %s: %s\n",
+			        argv[i], strerror(errno));
+			return -1;
+		}
+
+		utb.actime = sb.st_atime;
+		utb.modtime = set_eq ? set_time : sb.st_mtime + set_time;
+
+		if (utime(argv[i], &utb) < 0) {
+			fprintf(stderr, "Failed to modify time on %s: %s\n",
+			        argv[i], strerror(errno));
+			return -1;
+		}
+	}
+
+	return 0;
+
+usage:
+	fprintf(stderr, "Usage: %s %s\n", argv[0], usage_str);
+	return -1;
+}
diff --git a/trace.c b/trace.c
index 27fef86..7961a27 100644
--- a/trace.c
+++ b/trace.c
@@ -26,14 +26,14 @@
 #include "quote.h"
 
 /* Stolen from "imap-send.c". */
-static int git_vasprintf(char **strp, const char *fmt, va_list ap)
+int nfvasprintf(char **strp, const char *fmt, va_list ap)
 {
 	int len;
 	char tmp[1024];
 
 	if ((len = vsnprintf(tmp, sizeof(tmp), fmt, ap)) < 0 ||
 	    !(*strp = xmalloc(len + 1)))
-		return -1;
+		die("Fatal: Out of memory\n");
 	if (len >= (int)sizeof(tmp))
 		vsprintf(*strp, fmt, ap);
 	else
@@ -41,13 +41,15 @@
 	return len;
 }
 
-/* Stolen from "imap-send.c". */
-int nfvasprintf(char **str, const char *fmt, va_list va)
+int nfasprintf(char **str, const char *fmt, ...)
 {
-	int ret = git_vasprintf(str, fmt, va);
-	if (ret < 0)
-		die("Fatal: Out of memory\n");
-	return ret;
+	int rc;
+	va_list args;
+
+	va_start(args, fmt);
+	rc = nfvasprintf(str, fmt, args);
+	va_end(args);
+	return rc;
 }
 
 /* Get a trace file descriptor from GIT_TRACE env variable. */
diff --git a/tree-diff.c b/tree-diff.c
index 37d235e..852498e 100644
--- a/tree-diff.c
+++ b/tree-diff.c
@@ -5,9 +5,8 @@
 #include "diff.h"
 #include "tree.h"
 
-static char *malloc_base(const char *base, const char *path, int pathlen)
+static char *malloc_base(const char *base, int baselen, const char *path, int pathlen)
 {
-	int baselen = strlen(base);
 	char *newbase = xmalloc(baselen + pathlen + 2);
 	memcpy(newbase, base, baselen);
 	memcpy(newbase + baselen, path, pathlen);
@@ -16,9 +15,9 @@
 }
 
 static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc,
-		       const char *base);
+		       const char *base, int baselen);
 
-static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt)
+static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const char *base, int baselen, struct diff_options *opt)
 {
 	unsigned mode1, mode2;
 	const char *path1, *path2;
@@ -28,15 +27,15 @@
 	sha1 = tree_entry_extract(t1, &path1, &mode1);
 	sha2 = tree_entry_extract(t2, &path2, &mode2);
 
-	pathlen1 = strlen(path1);
-	pathlen2 = strlen(path2);
+	pathlen1 = tree_entry_len(path1, sha1);
+	pathlen2 = tree_entry_len(path2, sha2);
 	cmp = base_name_compare(path1, pathlen1, mode1, path2, pathlen2, mode2);
 	if (cmp < 0) {
-		show_entry(opt, "-", t1, base);
+		show_entry(opt, "-", t1, base, baselen);
 		return -1;
 	}
 	if (cmp > 0) {
-		show_entry(opt, "+", t2, base);
+		show_entry(opt, "+", t2, base, baselen);
 		return 1;
 	}
 	if (!opt->find_copies_harder && !hashcmp(sha1, sha2) && mode1 == mode2)
@@ -47,14 +46,14 @@
 	 * file, we need to consider it a remove and an add.
 	 */
 	if (S_ISDIR(mode1) != S_ISDIR(mode2)) {
-		show_entry(opt, "-", t1, base);
-		show_entry(opt, "+", t2, base);
+		show_entry(opt, "-", t1, base, baselen);
+		show_entry(opt, "+", t2, base, baselen);
 		return 0;
 	}
 
 	if (opt->recursive && S_ISDIR(mode1)) {
 		int retval;
-		char *newbase = malloc_base(base, path1, pathlen1);
+		char *newbase = malloc_base(base, baselen, path1, pathlen1);
 		if (opt->tree_in_recursive)
 			opt->change(opt, mode1, mode2,
 				    sha1, sha2, base, path1);
@@ -67,32 +66,46 @@
 	return 0;
 }
 
-static int interesting(struct tree_desc *desc, const char *base, struct diff_options *opt)
+/*
+ * Is a tree entry interesting given the pathspec we have?
+ *
+ * Return:
+ *  - 2 for "yes, and all subsequent entries will be"
+ *  - 1 for yes
+ *  - zero for no
+ *  - negative for "no, and no subsequent entries will be either"
+ */
+static int tree_entry_interesting(struct tree_desc *desc, const char *base, int baselen, struct diff_options *opt)
 {
 	const char *path;
+	const unsigned char *sha1;
 	unsigned mode;
 	int i;
-	int baselen, pathlen;
+	int pathlen;
+	int never_interesting = -1;
 
 	if (!opt->nr_paths)
 		return 1;
 
-	(void)tree_entry_extract(desc, &path, &mode);
+	sha1 = tree_entry_extract(desc, &path, &mode);
 
-	pathlen = strlen(path);
-	baselen = strlen(base);
+	pathlen = tree_entry_len(path, sha1);
 
-	for (i=0; i < opt->nr_paths; i++) {
+	for (i = 0; i < opt->nr_paths; i++) {
 		const char *match = opt->paths[i];
 		int matchlen = opt->pathlens[i];
+		int m = -1; /* signals that we haven't called strncmp() */
 
 		if (baselen >= matchlen) {
 			/* If it doesn't match, move along... */
 			if (strncmp(base, match, matchlen))
 				continue;
 
-			/* The base is a subdirectory of a path which was specified. */
-			return 1;
+			/*
+			 * The base is a subdirectory of a path which
+			 * was specified, so all of them are interesting.
+			 */
+			return 2;
 		}
 
 		/* Does the base match? */
@@ -102,6 +115,37 @@
 		match += baselen;
 		matchlen -= baselen;
 
+		if (never_interesting) {
+			/*
+			 * We have not seen any match that sorts later
+			 * than the current path.
+			 */
+
+			/*
+			 * Does match sort strictly earlier than path
+			 * with their common parts?
+			 */
+			m = strncmp(match, path,
+				    (matchlen < pathlen) ? matchlen : pathlen);
+			if (m < 0)
+				continue;
+
+			/*
+			 * If we come here even once, that means there is at
+			 * least one pathspec that would sort equal to or
+			 * later than the path we are currently looking at.
+			 * In other words, if we have never reached this point
+			 * after iterating all pathspecs, it means all
+			 * pathspecs are either outside of base, or inside the
+			 * base but sorts strictly earlier than the current
+			 * one.  In either case, they will never match the
+			 * subsequent entries.  In such a case, we initialized
+			 * the variable to -1 and that is what will be
+			 * returned, allowing the caller to terminate early.
+			 */
+			never_interesting = 0;
+		}
+
 		if (pathlen > matchlen)
 			continue;
 
@@ -112,44 +156,69 @@
 				continue;
 		}
 
-		if (strncmp(path, match, pathlen))
-			continue;
+		if (m == -1)
+			/*
+			 * we cheated and did not do strncmp(), so we do
+			 * that here.
+			 */
+			m = strncmp(match, path, pathlen);
 
-		return 1;
+		/*
+		 * If common part matched earlier then it is a hit,
+		 * because we rejected the case where path is not a
+		 * leading directory and is shorter than match.
+		 */
+		if (!m)
+			return 1;
 	}
-	return 0; /* No matches */
+	return never_interesting; /* No matches */
 }
 
 /* A whole sub-tree went away or appeared */
-static void show_tree(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base)
+static void show_tree(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base, int baselen)
 {
+	int all_interesting = 0;
 	while (desc->size) {
-		if (interesting(desc, base, opt))
-			show_entry(opt, prefix, desc, base);
+		int show;
+
+		if (all_interesting)
+			show = 1;
+		else {
+			show = tree_entry_interesting(desc, base, baselen,
+						      opt);
+			if (show == 2)
+				all_interesting = 1;
+		}
+		if (show < 0)
+			break;
+		if (show)
+			show_entry(opt, prefix, desc, base, baselen);
 		update_tree_entry(desc);
 	}
 }
 
 /* A file entry went away or appeared */
 static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc,
-		       const char *base)
+		       const char *base, int baselen)
 {
 	unsigned mode;
 	const char *path;
 	const unsigned char *sha1 = tree_entry_extract(desc, &path, &mode);
 
 	if (opt->recursive && S_ISDIR(mode)) {
-		char type[20];
-		char *newbase = malloc_base(base, path, strlen(path));
+		enum object_type type;
+		int pathlen = tree_entry_len(path, sha1);
+		char *newbase = malloc_base(base, baselen, path, pathlen);
 		struct tree_desc inner;
 		void *tree;
+		unsigned long size;
 
-		tree = read_sha1_file(sha1, type, &inner.size);
-		if (!tree || strcmp(type, tree_type))
+		tree = read_sha1_file(sha1, &type, &size);
+		if (!tree || type != OBJ_TREE)
 			die("corrupt tree sha %s", sha1_to_hex(sha1));
 
-		inner.buf = tree;
-		show_tree(opt, prefix, &inner, newbase);
+		init_tree_desc(&inner, tree, size);
+		show_tree(opt, prefix, &inner, newbase, baselen + 1 + pathlen);
 
 		free(tree);
 		free(newbase);
@@ -158,28 +227,54 @@
 	}
 }
 
+static void skip_uninteresting(struct tree_desc *t, const char *base, int baselen, struct diff_options *opt)
+{
+	int all_interesting = 0;
+	while (t->size) {
+		int show;
+
+		if (all_interesting)
+			show = 1;
+		else {
+			show = tree_entry_interesting(t, base, baselen, opt);
+			if (show == 2)
+				all_interesting = 1;
+		}
+		if (!show) {
+			update_tree_entry(t);
+			continue;
+		}
+		/* Skip it all? */
+		if (show < 0)
+			t->size = 0;
+		return;
+	}
+}
+
 int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt)
 {
-	while (t1->size | t2->size) {
-		if (opt->nr_paths && t1->size && !interesting(t1, base, opt)) {
-			update_tree_entry(t1);
-			continue;
-		}
-		if (opt->nr_paths && t2->size && !interesting(t2, base, opt)) {
-			update_tree_entry(t2);
-			continue;
+	int baselen = strlen(base);
+
+	for (;;) {
+		if (opt->quiet && opt->has_changes)
+			break;
+		if (opt->nr_paths) {
+			skip_uninteresting(t1, base, baselen, opt);
+			skip_uninteresting(t2, base, baselen, opt);
 		}
 		if (!t1->size) {
-			show_entry(opt, "+", t2, base);
+			if (!t2->size)
+				break;
+			show_entry(opt, "+", t2, base, baselen);
 			update_tree_entry(t2);
 			continue;
 		}
 		if (!t2->size) {
-			show_entry(opt, "-", t1, base);
+			show_entry(opt, "-", t1, base, baselen);
 			update_tree_entry(t1);
 			continue;
 		}
-		switch (compare_tree_entry(t1, t2, base, opt)) {
+		switch (compare_tree_entry(t1, t2, base, baselen, opt)) {
 		case -1:
 			update_tree_entry(t1);
 			continue;
@@ -199,16 +294,17 @@
 {
 	void *tree1, *tree2;
 	struct tree_desc t1, t2;
+	unsigned long size1, size2;
 	int retval;
 
-	tree1 = read_object_with_reference(old, tree_type, &t1.size, NULL);
+	tree1 = read_object_with_reference(old, tree_type, &size1, NULL);
 	if (!tree1)
 		die("unable to read source tree (%s)", sha1_to_hex(old));
-	tree2 = read_object_with_reference(new, tree_type, &t2.size, NULL);
+	tree2 = read_object_with_reference(new, tree_type, &size2, NULL);
 	if (!tree2)
 		die("unable to read destination tree (%s)", sha1_to_hex(new));
-	t1.buf = tree1;
-	t2.buf = tree2;
+	init_tree_desc(&t1, tree1, size1);
+	init_tree_desc(&t2, tree2, size2);
 	retval = diff_tree(&t1, &t2, base, opt);
 	free(tree1);
 	free(tree2);
@@ -219,15 +315,15 @@
 {
 	int retval;
 	void *tree;
+	unsigned long size;
 	struct tree_desc empty, real;
 
-	tree = read_object_with_reference(new, tree_type, &real.size, NULL);
+	tree = read_object_with_reference(new, tree_type, &size, NULL);
 	if (!tree)
 		die("unable to read root tree (%s)", sha1_to_hex(new));
-	real.buf = tree;
+	init_tree_desc(&real, tree, size);
 
-	empty.size = 0;
-	empty.buf = "";
+	init_tree_desc(&empty, "", 0);
 	retval = diff_tree(&empty, &real, base, opt);
 	free(tree);
 	return retval;
diff --git a/tree-walk.c b/tree-walk.c
index 70f8999..cbb24eb 100644
--- a/tree-walk.c
+++ b/tree-walk.c
@@ -2,51 +2,6 @@
 #include "tree-walk.h"
 #include "tree.h"
 
-void *fill_tree_descriptor(struct tree_desc *desc, const unsigned char *sha1)
-{
-	unsigned long size = 0;
-	void *buf = NULL;
-
-	if (sha1) {
-		buf = read_object_with_reference(sha1, tree_type, &size, NULL);
-		if (!buf)
-			die("unable to read tree %s", sha1_to_hex(sha1));
-	}
-	desc->size = size;
-	desc->buf = buf;
-	return buf;
-}
-
-static int entry_compare(struct name_entry *a, struct name_entry *b)
-{
-	return base_name_compare(
-			a->path, a->pathlen, a->mode,
-			b->path, b->pathlen, b->mode);
-}
-
-static void entry_clear(struct name_entry *a)
-{
-	memset(a, 0, sizeof(*a));
-}
-
-static void entry_extract(struct tree_desc *t, struct name_entry *a)
-{
-	a->sha1 = tree_entry_extract(t, &a->path, &a->mode);
-	a->pathlen = strlen(a->path);
-}
-
-void update_tree_entry(struct tree_desc *desc)
-{
-	const void *buf = desc->buf;
-	unsigned long size = desc->size;
-	int len = strlen(buf) + 1 + 20;
-
-	if (size < len)
-		die("corrupt tree file");
-	desc->buf = (char *) buf + len;
-	desc->size = size - len;
-}
-
 static const char *get_mode(const char *str, unsigned int *modep)
 {
 	unsigned char c;
@@ -61,50 +16,85 @@
 	return str;
 }
 
-const unsigned char *tree_entry_extract(struct tree_desc *desc, const char **pathp, unsigned int *modep)
+static void decode_tree_entry(struct tree_desc *desc, const void *buf, unsigned long size)
 {
-	const void *tree = desc->buf;
-	unsigned long size = desc->size;
-	int len = strlen(tree)+1;
-	const unsigned char *sha1 = (unsigned char *) tree + len;
 	const char *path;
-	unsigned int mode;
+	unsigned int mode, len;
 
-	path = get_mode(tree, &mode);
-	if (!path || size < len + 20)
+	path = get_mode(buf, &mode);
+	if (!path)
 		die("corrupt tree file");
-	*pathp = path;
-	*modep = canon_mode(mode);
-	return sha1;
+	len = strlen(path) + 1;
+
+	/* Initialize the descriptor entry */
+	desc->entry.path = path;
+	desc->entry.mode = mode;
+	desc->entry.sha1 = (const unsigned char *)(path + len);
+}
+
+void init_tree_desc(struct tree_desc *desc, const void *buffer, unsigned long size)
+{
+	desc->buffer = buffer;
+	desc->size = size;
+	if (size)
+		decode_tree_entry(desc, buffer, size);
+}
+
+void *fill_tree_descriptor(struct tree_desc *desc, const unsigned char *sha1)
+{
+	unsigned long size = 0;
+	void *buf = NULL;
+
+	if (sha1) {
+		buf = read_object_with_reference(sha1, tree_type, &size, NULL);
+		if (!buf)
+			die("unable to read tree %s", sha1_to_hex(sha1));
+	}
+	init_tree_desc(desc, buf, size);
+	return buf;
+}
+
+static int entry_compare(struct name_entry *a, struct name_entry *b)
+{
+	return base_name_compare(
+			a->path, tree_entry_len(a->path, a->sha1), a->mode,
+			b->path, tree_entry_len(b->path, b->sha1), b->mode);
+}
+
+static void entry_clear(struct name_entry *a)
+{
+	memset(a, 0, sizeof(*a));
+}
+
+static void entry_extract(struct tree_desc *t, struct name_entry *a)
+{
+	*a = t->entry;
+}
+
+void update_tree_entry(struct tree_desc *desc)
+{
+	const void *buf = desc->buffer;
+	const unsigned char *end = desc->entry.sha1 + 20;
+	unsigned long size = desc->size;
+	unsigned long len = end - (const unsigned char *)buf;
+
+	if (size < len)
+		die("corrupt tree file");
+	buf = end;
+	size -= len;
+	desc->buffer = buf;
+	desc->size = size;
+	if (size)
+		decode_tree_entry(desc, buf, size);
 }
 
 int tree_entry(struct tree_desc *desc, struct name_entry *entry)
 {
-	const void *tree = desc->buf;
-	const char *path;
-	unsigned long len, size = desc->size;
-
-	if (!size)
+	if (!desc->size)
 		return 0;
 
-	path = get_mode(tree, &entry->mode);
-	if (!path)
-		die("corrupt tree file");
-
-	entry->path = path;
-	len = strlen(path);
-	entry->pathlen = len;
-
-	path += len + 1;
-	entry->sha1 = (const unsigned char *) path;
-
-	path += 20;
-	len = path - (char *) tree;
-	if (len > size)
-		die("corrupt tree file");
-
-	desc->buf = path;
-	desc->size = size - len;
+	*entry = desc->entry;
+	update_tree_entry(desc);
 	return 1;
 }
 
@@ -169,7 +159,7 @@
 
 		sha1 = tree_entry_extract(t, &entry, mode);
 		update_tree_entry(t);
-		entrylen = strlen(entry);
+		entrylen = tree_entry_len(entry, sha1);
 		if (entrylen > namelen)
 			continue;
 		cmp = memcmp(name, entry, entrylen);
@@ -198,10 +188,11 @@
 {
 	int retval;
 	void *tree;
+	unsigned long size;
 	struct tree_desc t;
 	unsigned char root[20];
 
-	tree = read_object_with_reference(tree_sha1, tree_type, &t.size, root);
+	tree = read_object_with_reference(tree_sha1, tree_type, &size, root);
 	if (!tree)
 		return -1;
 
@@ -210,7 +201,7 @@
 		return 0;
 	}
 
-	t.buf = tree;
+	init_tree_desc(&t, tree, size);
 	retval = find_tree_entry(&t, name, sha1, mode);
 	free(tree);
 	return retval;
diff --git a/tree-walk.h b/tree-walk.h
index e57befa..43458cf 100644
--- a/tree-walk.h
+++ b/tree-walk.h
@@ -1,19 +1,32 @@
 #ifndef TREE_WALK_H
 #define TREE_WALK_H
 
-struct tree_desc {
-	const void *buf;
-	unsigned long size;
-};
-
 struct name_entry {
 	const unsigned char *sha1;
 	const char *path;
 	unsigned int mode;
-	int pathlen;
 };
 
+struct tree_desc {
+	const void *buffer;
+	struct name_entry entry;
+	unsigned int size;
+};
+
+static inline const unsigned char *tree_entry_extract(struct tree_desc *desc, const char **pathp, unsigned int *modep)
+{
+	*pathp = desc->entry.path;
+	*modep = canon_mode(desc->entry.mode);
+	return desc->entry.sha1;
+}
+
+static inline int tree_entry_len(const char *name, const unsigned char *sha1)
+{
+	return (char *)sha1 - (char *)name - 1;
+}
+
 void update_tree_entry(struct tree_desc *);
+void init_tree_desc(struct tree_desc *desc, const void *buf, unsigned long size);
 const unsigned char *tree_entry_extract(struct tree_desc *, const char **, unsigned int *);
 
 /* Helper function that does both of the above and returns true for success */
diff --git a/tree.c b/tree.c
index b6f02fe..d188c0f 100644
--- a/tree.c
+++ b/tree.c
@@ -83,8 +83,7 @@
 	if (parse_tree(tree))
 		return -1;
 
-	desc.buf = tree->buffer;
-	desc.size = tree->size;
+	init_tree_desc(&desc, tree->buffer, tree->size);
 
 	while (tree_entry(&desc, &entry)) {
 		if (!match_tree_entry(base, baselen, entry.path, entry.mode, match))
@@ -101,14 +100,15 @@
 		if (S_ISDIR(entry.mode)) {
 			int retval;
 			char *newbase;
+			unsigned int pathlen = tree_entry_len(entry.path, entry.sha1);
 
-			newbase = xmalloc(baselen + 1 + entry.pathlen);
+			newbase = xmalloc(baselen + 1 + pathlen);
 			memcpy(newbase, base, baselen);
-			memcpy(newbase + baselen, entry.path, entry.pathlen);
-			newbase[baselen + entry.pathlen] = '/';
+			memcpy(newbase + baselen, entry.path, pathlen);
+			newbase[baselen + pathlen] = '/';
 			retval = read_tree_recursive(lookup_tree(entry.sha1),
 						     newbase,
-						     baselen + entry.pathlen + 1,
+						     baselen + pathlen + 1,
 						     stage, match, fn);
 			free(newbase);
 			if (retval)
@@ -151,18 +151,14 @@
 	struct name_entry entry;
 
 	/* Count how many entries there are.. */
-	desc.buf = item->buffer;
-	desc.size = item->size;
-	while (desc.size) {
+	init_tree_desc(&desc, item->buffer, item->size);
+	while (tree_entry(&desc, &entry))
 		n_refs++;
-		update_tree_entry(&desc);
-	}
 
 	/* Allocate object refs and walk it again.. */
 	i = 0;
 	refs = alloc_object_refs(n_refs);
-	desc.buf = item->buffer;
-	desc.size = item->size;
+	init_tree_desc(&desc, item->buffer, item->size);
 	while (tree_entry(&desc, &entry)) {
 		struct object *obj;
 
@@ -190,17 +186,17 @@
 
 int parse_tree(struct tree *item)
 {
-	 char type[20];
+	 enum object_type type;
 	 void *buffer;
 	 unsigned long size;
 
 	if (item->object.parsed)
 		return 0;
-	buffer = read_sha1_file(item->object.sha1, type, &size);
+	buffer = read_sha1_file(item->object.sha1, &type, &size);
 	if (!buffer)
 		return error("Could not read %s",
 			     sha1_to_hex(item->object.sha1));
-	if (strcmp(type, tree_type)) {
+	if (type != OBJ_TREE) {
 		free(buffer);
 		return error("Object %s not a tree",
 			     sha1_to_hex(item->object.sha1));
diff --git a/unpack-file.c b/unpack-file.c
index d24acc2..25c56b3 100644
--- a/unpack-file.c
+++ b/unpack-file.c
@@ -5,12 +5,12 @@
 {
 	static char path[50];
 	void *buf;
-	char type[100];
+	enum object_type type;
 	unsigned long size;
 	int fd;
 
-	buf = read_sha1_file(sha1, type, &size);
-	if (!buf || strcmp(type, blob_type))
+	buf = read_sha1_file(sha1, &type, &size);
+	if (!buf || type != OBJ_BLOB)
 		die("unable to read blob object %s", sha1_to_hex(sha1));
 
 	strcpy(path, ".merge_file_XXXXXX");
diff --git a/unpack-trees.c b/unpack-trees.c
index 2e2232c..ee10eea 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -27,8 +27,7 @@
 	if (!tree->object.parsed)
 		parse_tree(tree);
 
-	desc.buf = tree->buffer;
-	desc.size = tree->size;
+	init_tree_desc(&desc, tree->buffer, tree->size);
 
 	while (tree_entry(&desc, &one)) {
 		struct tree_entry_list *entry;
diff --git a/upload-pack.c b/upload-pack.c
index 044c33b..d3a09e7 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -26,7 +26,7 @@
 static unsigned long oldest_have;
 
 static int multi_ack, nr_our_refs;
-static int use_thin_pack, use_ofs_delta;
+static int use_thin_pack, use_ofs_delta, no_progress;
 static struct object_array have_obj;
 static struct object_array want_obj;
 static unsigned int timeout;
@@ -165,6 +165,9 @@
 		die("git-upload-pack: unable to fork git-pack-objects");
 	}
 	if (!pid_pack_objects) {
+		const char *argv[10];
+		int i = 0;
+
 		dup2(lp_pipe[0], 0);
 		dup2(pu_pipe[1], 1);
 		dup2(pe_pipe[1], 2);
@@ -175,9 +178,16 @@
 		close(pu_pipe[1]);
 		close(pe_pipe[0]);
 		close(pe_pipe[1]);
-		execl_git_cmd("pack-objects", "--stdout", "--progress",
-			      use_ofs_delta ? "--delta-base-offset" : NULL,
-			      NULL);
+
+		argv[i++] = "pack-objects";
+		argv[i++] = "--stdout";
+		if (!no_progress)
+			argv[i++] = "--progress";
+		if (use_ofs_delta)
+			argv[i++] = "--delta-base-offset";
+		argv[i++] = NULL;
+
+		execv_git_cmd(argv);
 		kill(pid_rev_list, SIGKILL);
 		die("git-upload-pack: unable to exec git-pack-objects");
 	}
@@ -456,7 +466,7 @@
 			continue;
 		}
 		len = strip(line, len);
-		if (!strncmp(line, "have ", 5)) {
+		if (!prefixcmp(line, "have ")) {
 			switch (got_sha1(line+5, sha1)) {
 			case -1: /* they have what we do not */
 				if (multi_ack && ok_to_give_up())
@@ -503,7 +513,7 @@
 		if (!len)
 			break;
 
-		if (!strncmp("shallow ", line, 8)) {
+		if (!prefixcmp(line, "shallow ")) {
 			unsigned char sha1[20];
 			struct object *object;
 			use_thin_pack = 0;
@@ -516,7 +526,7 @@
 			add_object_array(object, NULL, &shallows);
 			continue;
 		}
-		if (!strncmp("deepen ", line, 7)) {
+		if (!prefixcmp(line, "deepen ")) {
 			char *end;
 			use_thin_pack = 0;
 			depth = strtol(line + 7, &end, 0);
@@ -524,7 +534,7 @@
 				die("Invalid deepen: %s", line);
 			continue;
 		}
-		if (strncmp("want ", line, 5) ||
+		if (prefixcmp(line, "want ") ||
 		    get_sha1_hex(line+5, sha1_buf))
 			die("git-upload-pack: protocol error, "
 			    "expected to get sha, not '%s'", line);
@@ -538,6 +548,8 @@
 			use_sideband = LARGE_PACKET_MAX;
 		else if (strstr(line+45, "side-band"))
 			use_sideband = DEFAULT_PACKET_MAX;
+		if (strstr(line+45, "no-progress"))
+			no_progress = 1;
 
 		/* We have sent all our refs already, and the other end
 		 * should have chosen out of them; otherwise they are
@@ -606,7 +618,7 @@
 static int send_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
 {
 	static const char *capabilities = "multi_ack thin-pack side-band"
-		" side-band-64k ofs-delta shallow";
+		" side-band-64k ofs-delta shallow no-progress";
 	struct object *o = parse_object(sha1);
 
 	if (!o)
@@ -657,7 +669,7 @@
 			strict = 1;
 			continue;
 		}
-		if (!strncmp(arg, "--timeout=", 10)) {
+		if (!prefixcmp(arg, "--timeout=")) {
 			timeout = atoi(arg+10);
 			continue;
 		}
diff --git a/usage.c b/usage.c
index 4dc5c77..f5e652c 100644
--- a/usage.c
+++ b/usage.c
@@ -86,7 +86,7 @@
 	return -1;
 }
 
-void warn(const char *warn, ...)
+void warning(const char *warn, ...)
 {
 	va_list params;
 
diff --git a/utf8.c b/utf8.c
index f381a7f..a2965c9 100644
--- a/utf8.c
+++ b/utf8.c
@@ -237,12 +237,19 @@
 /*
  * Wrap the text, if necessary. The variable indent is the indent for the
  * first line, indent2 is the indent for all other lines.
+ * If indent is negative, assume that already -indent columns have been
+ * consumed (and no extra indent is necessary for the first line).
  */
-void print_wrapped_text(const char *text, int indent, int indent2, int width)
+int print_wrapped_text(const char *text, int indent, int indent2, int width)
 {
 	int w = indent, assume_utf8 = is_utf8(text);
 	const char *bol = text, *space = NULL;
 
+	if (indent < 0) {
+		w = -indent;
+		space = text;
+	}
+
 	for (;;) {
 		char c = *text;
 		if (!c || isspace(c)) {
@@ -253,10 +260,9 @@
 				else
 					print_spaces(indent);
 				fwrite(start, text - start, 1, stdout);
-				if (!c) {
-					putchar('\n');
-					return;
-				} else if (c == '\t')
+				if (!c)
+					return w;
+				else if (c == '\t')
 					w |= 0x07;
 				space = text;
 				w++;
@@ -264,7 +270,7 @@
 			}
 			else {
 				putchar('\n');
-				text = bol = space + 1;
+				text = bol = space + isspace(*space);
 				space = NULL;
 				w = indent = indent2;
 			}
@@ -277,6 +283,7 @@
 			text++;
 		}
 	}
+	return w;
 }
 
 int is_encoding_utf8(const char *name)
diff --git a/utf8.h b/utf8.h
index a07c5a8..15db6f1 100644
--- a/utf8.h
+++ b/utf8.h
@@ -5,7 +5,7 @@
 int is_utf8(const char *text);
 int is_encoding_utf8(const char *name);
 
-void print_wrapped_text(const char *text, int indent, int indent2, int len);
+int print_wrapped_text(const char *text, int indent, int indent2, int len);
 
 #ifndef NO_ICONV
 char *reencode_string(const char *in, const char *out_encoding, const char *in_encoding);
diff --git a/wt-status.c b/wt-status.c
index 2879c3d..a25632b 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -191,12 +191,18 @@
 		wt_status_print_trailer();
 }
 
+static void wt_read_cache(struct wt_status *s)
+{
+	discard_cache();
+	read_cache();
+}
+
 void wt_status_print_initial(struct wt_status *s)
 {
 	int i;
 	char buf[PATH_MAX];
 
-	read_cache();
+	wt_read_cache(s);
 	if (active_nr) {
 		s->commitable = 1;
 		wt_status_print_cached_header(NULL);
@@ -220,6 +226,7 @@
 	rev.diffopt.format_callback = wt_status_print_updated_cb;
 	rev.diffopt.format_callback_data = s;
 	rev.diffopt.detect_rename = 1;
+	wt_read_cache(s);
 	run_diff_index(&rev, 1);
 }
 
@@ -231,6 +238,7 @@
 	rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
 	rev.diffopt.format_callback = wt_status_print_changed_cb;
 	rev.diffopt.format_callback_data = s;
+	wt_read_cache(s);
 	run_diff_files(&rev, 0);
 }
 
@@ -287,6 +295,7 @@
 	setup_revisions(0, NULL, &rev, s->reference);
 	rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
 	rev.diffopt.detect_rename = 1;
+	wt_read_cache(s);
 	run_diff_index(&rev, 1);
 }
 
@@ -298,7 +307,7 @@
 	if (s->branch) {
 		const char *on_what = "On branch ";
 		const char *branch_name = s->branch;
-		if (!strncmp(branch_name, "refs/heads/", 11))
+		if (!prefixcmp(branch_name, "refs/heads/"))
 			branch_name += 11;
 		else if (!strcmp(branch_name, "HEAD")) {
 			branch_name = "";
@@ -316,7 +325,6 @@
 	}
 	else {
 		wt_status_print_updated(s);
-		discard_cache();
 	}
 
 	wt_status_print_changed(s);
@@ -344,7 +352,7 @@
 		wt_status_use_color = git_config_colorbool(k, v);
 		return 0;
 	}
-	if (!strncmp(k, "status.color.", 13) || !strncmp(k, "color.status.", 13)) {
+	if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) {
 		int slot = parse_status_slot(k, 13);
 		color_parse(v, k, wt_status_colors[slot]);
 	}
diff --git a/xdiff-interface.c b/xdiff-interface.c
index 6c1f99b..10816e9 100644
--- a/xdiff-interface.c
+++ b/xdiff-interface.c
@@ -107,16 +107,18 @@
 {
 	struct stat st;
 	FILE *f;
+	size_t sz;
 
 	if (stat(filename, &st))
 		return error("Could not stat %s", filename);
 	if ((f = fopen(filename, "rb")) == NULL)
 		return error("Could not open %s", filename);
-	ptr->ptr = xmalloc(st.st_size);
-	if (fread(ptr->ptr, st.st_size, 1, f) != 1)
+	sz = xsize_t(st.st_size);
+	ptr->ptr = xmalloc(sz);
+	if (fread(ptr->ptr, sz, 1, f) != 1)
 		return error("Could not read %s", filename);
 	fclose(f);
-	ptr->size = st.st_size;
+	ptr->size = sz;
 	return 0;
 }
 
diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h
index fa409d5..e874a7c 100644
--- a/xdiff/xdiff.h
+++ b/xdiff/xdiff.h
@@ -31,7 +31,8 @@
 #define XDF_NEED_MINIMAL (1 << 1)
 #define XDF_IGNORE_WHITESPACE (1 << 2)
 #define XDF_IGNORE_WHITESPACE_CHANGE (1 << 3)
-#define XDF_WHITESPACE_FLAGS (XDF_IGNORE_WHITESPACE | XDF_IGNORE_WHITESPACE_CHANGE)
+#define XDF_IGNORE_WHITESPACE_AT_EOL (1 << 4)
+#define XDF_WHITESPACE_FLAGS (XDF_IGNORE_WHITESPACE | XDF_IGNORE_WHITESPACE_CHANGE | XDF_IGNORE_WHITESPACE_AT_EOL)
 
 #define XDL_PATCH_NORMAL '-'
 #define XDL_PATCH_REVERSE '+'
diff --git a/xdiff/xutils.c b/xdiff/xutils.c
index 1b899f3..bf91c0f 100644
--- a/xdiff/xutils.c
+++ b/xdiff/xutils.c
@@ -215,18 +215,35 @@
 				return 0;
 		}
 		return (i1 >= s1 && i2 >= s2);
+	} else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL) {
+		for (i1 = i2 = 0; i1 < s1 && i2 < s2; ) {
+			if (l1[i1] != l2[i2]) {
+				while (i1 < s1 && isspace(l1[i1]))
+					i1++;
+				while (i2 < s2 && isspace(l2[i2]))
+					i2++;
+				if (i1 < s1 || i2 < s2)
+					return 0;
+				return 1;
+			}
+			i1++;
+			i2++;
+		}
+		return i1 >= s1 && i2 >= s2;
 	} else
 		return s1 == s2 && !memcmp(l1, l2, s1);
 
 	return 0;
 }
 
-unsigned long xdl_hash_record(char const **data, char const *top, long flags) {
+static unsigned long xdl_hash_record_with_whitespace(char const **data,
+		char const *top, long flags) {
 	unsigned long ha = 5381;
 	char const *ptr = *data;
 
 	for (; ptr < top && *ptr != '\n'; ptr++) {
-		if (isspace(*ptr) && (flags & XDF_WHITESPACE_FLAGS)) {
+		if (isspace(*ptr)) {
+			const char *ptr2 = ptr;
 			while (ptr + 1 < top && isspace(ptr[1])
 					&& ptr[1] != '\n')
 				ptr++;
@@ -235,6 +252,14 @@
 				ha += (ha << 5);
 				ha ^= (unsigned long) ' ';
 			}
+			if (flags & XDF_IGNORE_WHITESPACE_AT_EOL
+					&& ptr[1] != '\n') {
+				while (ptr2 != ptr + 1) {
+					ha += (ha << 5);
+					ha ^= (unsigned long) *ptr2;
+					ptr2++;
+				}
+			}
 			continue;
 		}
 		ha += (ha << 5);
@@ -246,6 +271,23 @@
 }
 
 
+unsigned long xdl_hash_record(char const **data, char const *top, long flags) {
+	unsigned long ha = 5381;
+	char const *ptr = *data;
+
+	if (flags & XDF_WHITESPACE_FLAGS)
+		return xdl_hash_record_with_whitespace(data, top, flags);
+
+	for (; ptr < top && *ptr != '\n'; ptr++) {
+		ha += (ha << 5);
+		ha ^= (unsigned long) *ptr;
+	}
+	*data = ptr < top ? ptr + 1: ptr;
+
+	return ha;
+}
+
+
 unsigned int xdl_hashbits(unsigned int size) {
 	unsigned int val = 1, bits = 0;