Merge part of 'js/fmt-patch' for RFC2822 dates into 'sp/reflog'
authorJunio C Hamano <junkio@cox.net>
Fri, 19 May 2006 22:25:57 +0000 (15:25 -0700)
committerJunio C Hamano <junkio@cox.net>
Fri, 19 May 2006 22:25:57 +0000 (15:25 -0700)
An earlier patch from Shawn Pearce dependes on a change that is
only in "next".  I do not want to make this series hostage to
the yet-to-graduate js/fmt-patch branch, but let's try fixing it
by merging the early parts of the branch to see what happens.

Right now, 'sp/reflog' will not be in "next" for now, so I won't
have to regret this -- if this merge causes problem down the road
merging I can always rebuild the topic branch ;-).

142 files changed:
Documentation/Makefile
Documentation/callouts.xsl [new file with mode: 0644]
Documentation/config.txt [new file with mode: 0644]
Documentation/core-tutorial.txt
Documentation/diff-options.txt
Documentation/everyday.txt
Documentation/git-add.txt
Documentation/git-branch.txt
Documentation/git-checkout-index.txt
Documentation/git-checkout.txt
Documentation/git-cherry.txt
Documentation/git-clean.txt
Documentation/git-clone.txt
Documentation/git-commit.txt
Documentation/git-count-objects.txt
Documentation/git-cvsexportcommit.txt
Documentation/git-diff-tree.txt
Documentation/git-diff.txt
Documentation/git-grep.txt
Documentation/git-imap-send.txt
Documentation/git-init-db.txt
Documentation/git-log.txt
Documentation/git-ls-files.txt
Documentation/git-merge-base.txt
Documentation/git-merge-index.txt
Documentation/git-name-rev.txt
Documentation/git-prune.txt
Documentation/git-rebase.txt
Documentation/git-repack.txt
Documentation/git-repo-config.txt
Documentation/git-reset.txt
Documentation/git-rev-list.txt
Documentation/git-rev-parse.txt
Documentation/git-rm.txt
Documentation/git-unpack-objects.txt
Documentation/git-update-index.txt
Documentation/git-update-ref.txt
Documentation/git-var.txt
Documentation/git-verify-pack.txt
Documentation/git-whatchanged.txt
Documentation/gitk.txt
Documentation/glossary.txt
Documentation/repository-layout.txt
Documentation/sort_glossary.pl
Makefile
apply.c
base85.c [new file with mode: 0644]
blame.c
builtin-count.c [new file with mode: 0644]
builtin-diff.c [new file with mode: 0644]
builtin-grep.c [new file with mode: 0644]
builtin-help.c
builtin-push.c [new file with mode: 0644]
builtin.h
cache.h
cat-file.c
checkout-index.c
combine-diff.c
commit-tree.c
commit.c
config.c
contrib/colordiff/README [new file with mode: 0644]
contrib/colordiff/colordiff.perl [new file with mode: 0755]
contrib/git-svn/git-svn.perl
contrib/git-svn/git-svn.txt
convert-objects.c
daemon.c
delta.h
describe.c
diff-delta.c
diff-files.c
diff-index.c
diff-lib.c
diff.c [new file with mode: 0644]
diff.h
environment.c
fetch.c
fetch.h
git-am.sh
git-annotate.perl
git-applypatch.sh
git-branch.sh
git-checkout.sh
git-clean.sh
git-clone.sh
git-commit.sh
git-count-objects.sh [deleted file]
git-cvsexportcommit.perl
git-cvsserver.perl
git-diff.sh [deleted file]
git-fetch.sh
git-format-patch.sh
git-grep.sh [deleted file]
git-merge.sh
git-rebase.sh
git-repack.sh
git-request-pull.sh
git-reset.sh
git-revert.sh
git-send-email.perl
git-tag.sh
git-whatchanged.sh [deleted file]
git.c
git.spec.in
gitk
http-fetch.c
local-fetch.c
log-tree.c
ls-tree.c
merge-base.c
merge-tree.c
pack-check.c
pack-objects.c
patch-delta.c
read-cache.c
read-tree.c
refs.c
refs.h
repo-config.c
rev-parse.c
revision.c
setup.c
sha1_file.c
sha1_name.c
show-branch.c
ssh-fetch.c
ssh-upload.c
t/t0000-basic.sh
t/t1001-read-tree-m-2way.sh
t/t1002-read-tree-m-u-2way.sh
t/t1300-repo-config.sh
t/t1400-update-ref.sh [new file with mode: 0755]
t/t2101-update-index-reupdate.sh [new file with mode: 0755]
t/t4010-diff-pathspec.sh
t/t4012-diff-binary.sh [new file with mode: 0755]
t/t5700-clone-reference.sh [new file with mode: 0755]
t/t5710-info-alternate.sh [new file with mode: 0755]
t/test4012.png [new file with mode: 0644]
tar-tree.c
unpack-file.c
update-index.c
update-ref.c

index f4cbf7e..c1af22c 100644 (file)
@@ -79,7 +79,7 @@ clean:
        asciidoc -b xhtml11 -d manpage -f asciidoc.conf $<
 
 %.1 %.7 : %.xml
-       xmlto man $<
+       xmlto -m callouts.xsl man $<
 
 %.xml : %.txt
        asciidoc -b docbook -d manpage -f asciidoc.conf $<
diff --git a/Documentation/callouts.xsl b/Documentation/callouts.xsl
new file mode 100644 (file)
index 0000000..ad03755
--- /dev/null
@@ -0,0 +1,16 @@
+<!-- callout.xsl: converts asciidoc callouts to man page format -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+<xsl:template match="co">
+       <xsl:value-of select="concat('\fB(',substring-after(@id,'-'),')\fR')"/>
+</xsl:template>
+<xsl:template match="calloutlist">
+       <xsl:text>.sp&#10;</xsl:text>
+       <xsl:apply-templates/>
+       <xsl:text>&#10;</xsl:text>
+</xsl:template>
+<xsl:template match="callout">
+       <xsl:value-of select="concat('\fB',substring-after(@arearefs,'-'),'. \fR')"/>
+       <xsl:apply-templates/>
+       <xsl:text>.br&#10;</xsl:text>
+</xsl:template>
+</xsl:stylesheet>
diff --git a/Documentation/config.txt b/Documentation/config.txt
new file mode 100644 (file)
index 0000000..e178ee2
--- /dev/null
@@ -0,0 +1,191 @@
+CONFIGURATION FILE
+------------------
+
+The git configuration file contains a number of variables that affect
+the git commands behaviour. They can be used by both the git plumbing
+and the porcelains. The variables are divided to sections, where
+in the fully qualified variable name the variable itself is the last
+dot-separated segment and the section name is everything before the last
+dot. The variable names are case-insensitive and only alphanumeric
+characters are allowed. Some variables may appear multiple times.
+
+The syntax is fairly flexible and permissive; whitespaces are mostly
+ignored. The '#' and ';' characters begin commends to the end of line,
+blank lines are ignored, lines containing strings enclosed in square
+brackets start sections and all the other lines are recognized
+as setting variables, in the form 'name = value'. If there is no equal
+sign on the line, the entire line is taken as 'name' and the variable
+is recognized as boolean "true". String values may be entirely or partially
+enclosed in double quotes; some variables may require special value format.
+
+Example
+~~~~~~~
+
+       # Core variables
+       [core]
+               ; Don't trust file modes
+               filemode = false
+
+       # Our diff algorithm
+       [diff]
+               external = "/usr/local/bin/gnu-diff -u"
+               renames = true
+
+Variables
+~~~~~~~~~
+
+Note that this list is non-comprehensive and not necessarily complete.
+For command-specific variables, you will find more detailed description
+in the appropriate manual page. You will find description of non-core
+porcelain configuration variables in the respective porcelain documentation.
+
+core.fileMode::
+       If false, the executable bit differences between the index and
+       the working copy are ignored; useful on broken filesystems like FAT.
+       See gitlink:git-update-index[1]. True by default.
+
+core.gitProxy::
+       A "proxy command" to execute (as 'command host port') instead
+       of establishing direct connection to the remote server when
+       using the git protocol for fetching. If the variable value is
+       in the "COMMAND for DOMAIN" format, the command is applied only
+       on hostnames ending with the specified domain string. This variable
+       may be set multiple times and is matched in the given order;
+       the first match wins.
+
+       Can be overriden by the 'GIT_PROXY_COMMAND' environment variable
+       (which always applies universally, without the special "for"
+       handling).
+
+core.ignoreStat::
+       The working copy files are assumed to stay unchanged until you
+       mark them otherwise manually - Git will not detect the file changes
+       by lstat() calls. This is useful on systems where those are very
+       slow, such as Microsoft Windows.  See gitlink:git-update-index[1].
+       False by default.
+
+core.preferSymlinkRefs::
+       Instead of the default "symref" format for HEAD
+       and other symbolic reference files, use symbolic links.
+       This is sometimes needed to work with old scripts that
+       expect HEAD to be a symbolic link.
+
+core.logAllRefUpdates::
+       If true, `git-update-ref` will append a line to
+       "$GIT_DIR/logs/<ref>" listing the new SHA1 and the date/time
+       of the update.  If the file does not exist it will be
+       created automatically.  This information can be used to
+       determine what commit was the tip of a branch "2 days ago".
+       This value is false by default (no logging).
+
+core.repositoryFormatVersion::
+       Internal variable identifying the repository format and layout
+       version.
+
+core.sharedRepository::
+       If true, the repository is made shareable between several users
+       in a group (making sure all the files and objects are group-writable).
+       See gitlink:git-init-db[1]. False by default.
+
+core.warnAmbiguousRefs::
+       If true, git will warn you if the ref name you passed it is ambiguous
+       and might match multiple refs in the .git/refs/ tree. True by default.
+
+apply.whitespace::
+       Tells `git-apply` how to handle whitespaces, in the same way
+       as the '--whitespace' option. See gitlink:git-apply[1].
+
+diff.renameLimit::
+       The number of files to consider when performing the copy/rename
+       detection; equivalent to the git diff option '-l'.
+
+format.headers::
+       Additional email headers to include in a patch to be submitted
+       by mail.  See gitlink:git-format-patch[1].
+
+gitcvs.enabled::
+       Whether the cvs pserver interface is enabled for this repository.
+       See gitlink:git-cvsserver[1].
+
+gitcvs.logfile::
+       Path to a log file where the cvs pserver interface well... logs
+       various stuff. See gitlink:git-cvsserver[1].
+
+http.sslVerify::
+       Whether to verify the SSL certificate when fetching or pushing
+       over HTTPS. Can be overriden by the 'GIT_SSL_NO_VERIFY' environment
+       variable.
+
+http.sslCert::
+       File containing the SSL certificate when fetching or pushing
+       over HTTPS. Can be overriden by the 'GIT_SSL_CERT' environment
+       variable.
+
+http.sslKey::
+       File containing the SSL private key when fetching or pushing
+       over HTTPS. Can be overriden by the 'GIT_SSL_KEY' environment
+       variable.
+
+http.sslCAInfo::
+       File containing the certificates to verify the peer with when
+       fetching or pushing over HTTPS. Can be overriden by the
+       'GIT_SSL_CAINFO' environment variable.
+
+http.sslCAPath::
+       Path containing files with the CA certificates to verify the peer
+       with when fetching or pushing over HTTPS. Can be overriden
+       by the 'GIT_SSL_CAPATH' environment variable.
+
+http.maxRequests::
+       How many HTTP requests to launch in parallel. Can be overriden
+       by the 'GIT_HTTP_MAX_REQUESTS' environment variable. Default is 5.
+
+http.lowSpeedLimit, http.lowSpeedTime::
+       If the HTTP transfer speed is less than 'http.lowSpeedLimit'
+       for longer than 'http.lowSpeedTime' seconds, the transfer is aborted.
+       Can be overriden by the 'GIT_HTTP_LOW_SPEED_LIMIT' and
+       'GIT_HTTP_LOW_SPEED_TIME' environment variables.
+
+i18n.commitEncoding::
+       Character encoding the commit messages are stored in; git itself
+       does not care per se, but this information is necessary e.g. when
+       importing commits from emails or in the gitk graphical history
+       browser (and possibly at other places in the future or in other
+       porcelains). See e.g. gitlink:git-mailinfo[1]. Defaults to 'utf-8'.
+
+merge.summary::
+       Whether to include summaries of merged commits in newly created
+       merge commit messages. False by default.
+
+pull.octopus::
+       The default merge strategy to use when pulling multiple branches
+       at once.
+
+pull.twohead::
+       The default merge strategy to use when pulling a single branch.
+
+show.difftree::
+       The default gitlink:git-diff-tree[1] arguments to be used
+       for gitlink:git-show[1].
+
+showbranch.default::
+       The default set of branches for gitlink:git-show-branch[1].
+       See gitlink:git-show-branch[1].
+
+user.email::
+       Your email address to be recorded in any newly created commits.
+       Can be overriden by the 'GIT_AUTHOR_EMAIL' and 'GIT_COMMITTER_EMAIL'
+       environment variables.  See gitlink:git-commit-tree[1].
+
+user.name::
+       Your full name to be recorded in any newly created commits.
+       Can be overriden by the 'GIT_AUTHOR_NAME' and 'GIT_COMMITTER_NAME'
+       environment variables.  See gitlink:git-commit-tree[1].
+
+whatchanged.difftree::
+       The default gitlink:git-diff-tree[1] arguments to be used
+       for gitlink:git-whatchanged[1].
+
+imap::
+       The configuration variables in the 'imap' section are described
+       in gitlink:git-imap-send[1].
index 4211c81..d1360ec 100644 (file)
@@ -971,7 +971,7 @@ $ git show-branch --topo-order master mybranch
 The first two lines indicate that it is showing the two branches
 and the first line of the commit log message from their
 top-of-the-tree commits, you are currently on `master` branch
-(notice the asterisk `*` character), and the first column for
+(notice the asterisk `\*` character), and the first column for
 the later output lines is used to show commits contained in the
 `master` branch, and the second column for the `mybranch`
 branch. Three commits are shown along with their log messages.
index c183dc9..f523ec2 100644 (file)
 --stat::
        Generate a diffstat instead of a patch.
 
+--summary::
+       Output a condensed summary of extended header information
+       such as creations, renames and mode changes.
+
 --patch-with-stat::
        Generate patch and prepend its diffstat.
 
index 3ab9b91..4b56370 100644 (file)
@@ -61,7 +61,8 @@ $ git prune
 $ git count-objects <2>
 $ git repack <3>
 $ git prune <4>
-
+------------
++
 <1> running without "--full" is usually cheap and assures the
 repository health reasonably well.
 <2> check how many loose objects there are and how much
@@ -69,17 +70,16 @@ diskspace is wasted by not repacking.
 <3> without "-a" repacks incrementally.  repacking every 4-5MB
 of loose objects accumulation may be a good rule of thumb.
 <4> after repack, prune removes the duplicate loose objects.
-------------
 
 Repack a small project into single pack.::
 +
 ------------
 $ git repack -a -d <1>
 $ git prune
-
+------------
++
 <1> pack all the objects reachable from the refs into one pack
 and remove unneeded other packs
-------------
 
 
 Individual Developer (Standalone)[[Individual Developer (Standalone)]]
@@ -129,10 +129,10 @@ $ git-init-db
 $ git add . <1>
 $ git commit -m 'import of frotz source tree.'
 $ git tag v2.43 <2>
-
+------------
++
 <1> add everything under the current directory.
 <2> make a lightweight, unannotated tag.
-------------
 
 Create a topic branch and develop.::
 +
@@ -153,7 +153,8 @@ $ git checkout master <9>
 $ git pull . alsa-audio <10>
 $ git log --since='3 days ago' <11>
 $ git log v2.43.. curses/ <12>
-
+------------
++
 <1> create a new topic branch.
 <2> revert your botched changes in "curses/ux_audio_oss.c".
 <3> you need to tell git if you added a new file; removal and
@@ -170,7 +171,6 @@ you originally wrote.
 combined and include --max-count=10 (show 10 commits), --until='2005-12-10'.
 <12> view only the changes that touch what's in curses/
 directory, since v2.43 tag.
-------------
 
 
 Individual Developer (Participant)[[Individual Developer (Participant)]]
@@ -208,7 +208,8 @@ $ git pull git://git.kernel.org/pub/.../jgarzik/libata-dev.git ALL <5>
 $ git reset --hard ORIG_HEAD <6>
 $ git prune <7>
 $ git fetch --tags <8>
-
+------------
++
 <1> repeat as needed.
 <2> extract patches from your branch for e-mail submission.
 <3> "pull" fetches from "origin" by default and merges into the
@@ -221,7 +222,6 @@ area we are interested in.
 <7> garbage collect leftover objects from reverted pull.
 <8> from time to time, obtain official tags from the "origin"
 and store them under .git/refs/tags/.
-------------
 
 
 Push into another repository.::
@@ -239,7 +239,8 @@ satellite$ git push origin <4>
 mothership$ cd frotz
 mothership$ git checkout master
 mothership$ git pull . satellite <5>
-
+------------
++
 <1> mothership machine has a frotz repository under your home
 directory; clone from it to start a repository on the satellite
 machine.
@@ -252,7 +253,6 @@ to local "origin" branch.
 mothership machine.  You could use this as a back-up method.
 <5> on mothership machine, merge the work done on the satellite
 machine into the master branch.
-------------
 
 Branch off of a specific tag.::
 +
@@ -262,12 +262,12 @@ $ edit/compile/test; git commit -a
 $ git checkout master
 $ git format-patch -k -m --stdout v2.6.14..private2.6.14 |
   git am -3 -k <2>
-
+------------
++
 <1> create a private branch based on a well known (but somewhat behind)
 tag.
 <2> forward port all changes in private2.6.14 branch to master branch
 without a formal "merging".
-------------
 
 
 Integrator[[Integrator]]
@@ -317,7 +317,8 @@ $ git tag -s -m 'GIT 0.99.9x' v0.99.9x <10>
 $ git fetch ko && git show-branch master maint 'tags/ko-*' <11>
 $ git push ko <12>
 $ git push ko v0.99.9x <13>
-
+------------
++
 <1> see what I was in the middle of doing, if any.
 <2> see what topic branches I have and think about how ready
 they are.
@@ -346,7 +347,6 @@ In the output from "git show-branch", "master" should have
 everything "ko-master" has.
 <12> push out the bleeding edge.
 <13> push the tag out, too.
-------------
 
 
 Repository Administration[[Repository Administration]]
@@ -367,7 +367,6 @@ example of managing a shared central repository.
 
 Examples
 ~~~~~~~~
-
 Run git-daemon to serve /pub/scm from inetd.::
 +
 ------------
@@ -388,13 +387,13 @@ cindy:x:1002:1002::/home/cindy:/usr/bin/git-shell
 david:x:1003:1003::/home/david:/usr/bin/git-shell
 $ grep git /etc/shells <2>
 /usr/bin/git-shell
-
+------------
++
 <1> log-in shell is set to /usr/bin/git-shell, which does not
 allow anything but "git push" and "git pull".  The users should
 get an ssh access to the machine.
 <2> in many distributions /etc/shells needs to list what is used
 as the login shell.
-------------
 
 CVS-style shared repository.::
 +
@@ -419,7 +418,8 @@ $ cat info/allowed-users <4>
 refs/heads/master      alice\|cindy
 refs/heads/doc-update  bob
 refs/tags/v[0-9]*      david
-
+------------
++
 <1> place the developers into the same git group.
 <2> and make the shared repository writable by the group.
 <3> use update-hook example by Carl from Documentation/howto/
@@ -427,7 +427,6 @@ for branch policy control.
 <4> alice and cindy can push into master, only bob can push into doc-update.
 david is the release manager and is the only person who can
 create and push version tags.
-------------
 
 HTTP server to support dumb protocol transfer.::
 +
@@ -435,7 +434,7 @@ HTTP server to support dumb protocol transfer.::
 dev$ git update-server-info <1>
 dev$ ftp user@isp.example.com <2>
 ftp> cp -r .git /home/user/myproject.git
-
+------------
++
 <1> make sure your info/refs and objects/info/packs are up-to-date
 <2> upload to public HTTP server hosted by your ISP.
-------------
index ae24547..5e31129 100644 (file)
@@ -26,7 +26,7 @@ OPTIONS
 -v::
         Be verbose.
 
---::
+\--::
        This option can be used to separate command-line options from
        the list of files, (useful when filenames might be mistaken
        for command-line options).
index 71ecd85..a7bec3c 100644 (file)
@@ -3,22 +3,28 @@ git-branch(1)
 
 NAME
 ----
-git-branch - Create a new branch, or remove an old one
+git-branch - List, create, or delete branches.
 
 SYNOPSIS
 --------
 [verse]
-'git-branch' [[-f] <branchname> [<start-point>]]
-'git-branch' (-d | -D) <branchname>
+'git-branch' [-r]
+'git-branch' [-l] [-f] <branchname> [<start-point>]
+'git-branch' (-d | -D) <branchname>...
 
 DESCRIPTION
 -----------
-If no argument is provided, show available branches and mark current
-branch with star. Otherwise, create a new branch of name <branchname>.
-If a starting point is also specified, that will be where the branch is
-created, otherwise it will be created at the current HEAD.
+With no arguments given (or just `-r`) a list of available branches
+will be shown, the current branch will be highlighted with an asterisk.
 
-With a `-d` or `-D` option, `<branchname>` will be deleted.
+In its second form, a new branch named <branchname> will be created.
+It will start out with a head equal to the one given as <start-point>.
+If no <start-point> is given, the branch will be created with a head
+equal to that of the currently checked out branch.
+
+With a `-d` or `-D` option, `<branchname>` will be deleted.  You may
+specify more than one branch for deletion.  If the branch currently
+has a ref log then the ref log will also be deleted.
 
 
 OPTIONS
@@ -29,41 +35,62 @@ OPTIONS
 -D::
        Delete a branch irrespective of its index status.
 
+-l::
+       Create the branch's ref log.  This activates recording of
+       all changes to made the branch ref, enabling use of date
+       based sha1 expressions such as "<branchname>@{yesterday}".
+
 -f::
-       Force a reset of <branchname> to <start-point> (or current head).
+       Force the creation of a new branch even if it means deleting
+       a branch that already exists with the same name.
+
+-r::
+       List only the "remote" branches.
 
 <branchname>::
        The name of the branch to create or delete.
 
 <start-point>::
-       Where to create the branch; defaults to HEAD. This
-       option has no meaning with -d and -D.
+       The new branch will be created with a HEAD equal to this.  It may
+       be given as a branch name, a commit-id, or a tag.  If this option
+       is omitted, the current branch is assumed.
+
 
 
 Examples
-~~~~~~~~
+--------
 
 Start development off of a known tag::
 +
 ------------
 $ git clone git://git.kernel.org/pub/scm/.../linux-2.6 my2.6
 $ cd my2.6
-$ git branch my2.6.14 v2.6.14 <1>
+$ git branch my2.6.14 v2.6.14   <1>
 $ git checkout my2.6.14
-
-<1> These two steps are the same as "checkout -b my2.6.14 v2.6.14".
 ------------
++
+<1> This step and the next one could be combined into a single step with
+"checkout -b my2.6.14 v2.6.14".
 
 Delete unneeded branch::
 +
 ------------
 $ git clone git://git.kernel.org/.../git.git my.git
 $ cd my.git
-$ git branch -D todo <1>
-
+$ git branch -D todo    <1>
+------------
++
 <1> delete todo branch even if the "master" branch does not have all
 commits from todo branch.
-------------
+
+
+Notes
+-----
+
+If you are creating a branch that you want to immediately checkout, it's
+easier to use the git checkout command with its `-b` option to create
+a branch and check it out with a single command.
+
 
 Author
 ------
index 09bd6a5..765c173 100644 (file)
@@ -63,7 +63,7 @@ OPTIONS
        Only meaningful with `--stdin`; paths are separated with
        NUL character instead of LF.
 
---::
+\--::
        Do not interpret any more arguments as options.
 
 The order of the flags used to matter, but not anymore.
index 985bb2f..0643943 100644 (file)
@@ -8,7 +8,7 @@ git-checkout - Checkout and switch to a branch
 SYNOPSIS
 --------
 [verse]
-'git-checkout' [-f] [-b <new_branch>] [-m] [<branch>]
+'git-checkout' [-f] [-b <new_branch> [-l]] [-m] [<branch>]
 'git-checkout' [-m] [<branch>] <paths>...
 
 DESCRIPTION
@@ -37,6 +37,11 @@ OPTIONS
 -b::
        Create a new branch and start it at <branch>.
 
+-l::
+       Create the new branch's ref log.  This activates recording of
+       all changes to made the branch ref, enabling use of date
+       based sha1 expressions such as "<branchname>@{yesterday}".
+
 -m::
        If you have local modifications to one or more files that
        are different between the current branch and the branch to
@@ -66,19 +71,19 @@ the `Makefile` to two revisions back, deletes hello.c by
 mistake, and gets it back from the index.
 +
 ------------
-$ git checkout master <1>
-$ git checkout master~2 Makefile <2>
+$ git checkout master             <1>
+$ git checkout master~2 Makefile  <2>
 $ rm -f hello.c
-$ git checkout hello.c <3>
-
+$ git checkout hello.c            <3>
+------------
++
 <1> switch branch
 <2> take out a file out of other commit
-<3> or "git checkout -- hello.c", as in the next example.
-------------
+<3> restore hello.c from HEAD of current branch
 +
-If you have an unfortunate branch that is named `hello.c`, the
-last step above would be confused as an instruction to switch to
-that branch.  You should instead write:
+If you have an unfortunate branch that is named `hello.c`, this
+step would be confused as an instruction to switch to that branch.
+You should instead write:
 +
 ------------
 $ git checkout -- hello.c
index 9a5e371..893baaa 100644 (file)
@@ -11,11 +11,20 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-Each commit between the fork-point and <head> is examined, and compared against
-the change each commit between the fork-point and <upstream> introduces.
-Commits already included in upstream are prefixed with '-' (meaning "drop from
-my local pull"), while commits missing from upstream are prefixed with '+'
-(meaning "add to the updated upstream").
+The changeset (or "diff") of each commit between the fork-point and <head>
+is compared against each commit between the fork-point and <upstream>.
+
+Every commit with a changeset that doesn't exist in the other branch
+has its id (sha1) reported, prefixed by a symbol.  Those existing only
+in the <upstream> branch are prefixed with a minus (-) sign, and those
+that only exist in the <head> branch are prefixed with a plus (+) symbol.
+
+Because git-cherry compares the changeset rather than the commit id
+(sha1), you can use git-cherry to find out if a commit you made locally
+has been applied <upstream> under a different commit id.  For example,
+this will happen if you're feeding patches <upstream> via email rather
+than pushing or pulling commits directly.
+
 
 OPTIONS
 -------
index 36890c5..c61afbc 100644 (file)
@@ -8,7 +8,7 @@ git-clean - Remove untracked files from the working tree
 SYNOPSIS
 --------
 [verse]
-'git-clean' [-d] [-n] [-q] [-x | -X]
+'git-clean' [-d] [-n] [-q] [-x | -X] [--] <paths>...
 
 DESCRIPTION
 -----------
@@ -16,6 +16,9 @@ Removes files unknown to git.  This allows to clean the working tree
 from files that are not under version control.  If the '-x' option is
 specified, ignored files are also removed, allowing to remove all
 build products.
+When optional `<paths>...` arguments are given, the paths
+affected are further limited to those that match them.
+
 
 OPTIONS
 -------
index 131e445..b333f51 100644 (file)
@@ -101,7 +101,7 @@ OPTIONS
        is not allowed.
 
 Examples
-~~~~~~~~
+--------
 
 Clone from upstream::
 +
index 0a7365b..38df59c 100644 (file)
@@ -106,7 +106,7 @@ but can be used to amend a merge commit.
        index and the latest commit does not match on the
        specified paths to avoid confusion.
 
---::
+\--::
        Do not interpret any more arguments as options.
 
 <file>...::
index 47216f4..198ce77 100644 (file)
@@ -7,13 +7,23 @@ git-count-objects - Reports on unpacked objects
 
 SYNOPSIS
 --------
-'git-count-objects'
+'git-count-objects' [-v]
 
 DESCRIPTION
 -----------
 This counts the number of unpacked object files and disk space consumed by
 them, to help you decide when it is a good time to repack.
 
+
+OPTIONS
+-------
+-v::
+       In addition to the number of loose objects and disk
+       space consumed, it reports the number of in-pack
+       objects, and number of objects that can be removed by
+       running `git-prune-packed`.
+
+
 Author
 ------
 Written by Junio C Hamano <junkio@cox.net>
index d30435a..56bd3e5 100644 (file)
@@ -8,7 +8,7 @@ git-cvsexportcommit - Export a commit to a CVS checkout
 
 SYNOPSIS
 --------
-'git-cvsexportcommmit' [-h] [-v] [-c] [-p] [PARENTCOMMIT] COMMITID
+'git-cvsexportcommmit' [-h] [-v] [-c] [-p] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID
 
 
 DESCRIPTION
@@ -39,6 +39,13 @@ OPTIONS
        Be pedantic (paranoid) when applying patches. Invokes patch with 
        --fuzz=0
 
+-f::
+       Force the merge even if the files are not up to date.
+
+-m::
+       Prepend the commit message with the provided prefix. 
+       Useful for patch series and the like.
+
 -v::
        Verbose.
 
index 2169169..906830d 100644 (file)
@@ -92,7 +92,7 @@ separated with a single space are given.
        Furthermore, it lists only files which were modified
        from all parents.
 
--cc::
+--cc::
        This flag changes the way a merge commit patch is displayed,
        in a similar way to the '-c' option. It implies the '-c'
        and '-p' options and further compresses the patch output
index 890931c..7267bcd 100644 (file)
@@ -46,40 +46,41 @@ EXAMPLES
 Various ways to check your working tree::
 +
 ------------
-$ git diff <1>
-$ git diff --cached <2>
-$ git diff HEAD <3>
-
+$ git diff            <1>
+$ git diff --cached   <2>
+$ git diff HEAD       <3>
+------------
++
 <1> changes in the working tree since your last git-update-index.
 <2> changes between the index and your last commit; what you
 would be committing if you run "git commit" without "-a" option.
 <3> changes in the working tree since your last commit; what you
 would be committing if you run "git commit -a"
-------------
 
 Comparing with arbitrary commits::
 +
 ------------
-$ git diff test <1>
-$ git diff HEAD -- ./test <2>
-$ git diff HEAD^ HEAD <3>
-
+$ git diff test            <1>
+$ git diff HEAD -- ./test  <2>
+$ git diff HEAD^ HEAD      <3>
+------------
++
 <1> instead of using the tip of the current branch, compare with the
 tip of "test" branch.
 <2> instead of comparing with the tip of "test" branch, compare with
 the tip of the current branch, but limit the comparison to the
 file "test".
 <3> compare the version before the last commit and the last commit.
-------------
 
 
 Limiting the diff output::
 +
 ------------
-$ git diff --diff-filter=MRC <1>
-$ git diff --name-status -r <2>
-$ git diff arch/i386 include/asm-i386 <3>
-
+$ git diff --diff-filter=MRC            <1>
+$ git diff --name-status -r             <2>
+$ git diff arch/i386 include/asm-i386   <3>
+------------
++
 <1> show only modification, rename and copy, but not addition
 nor deletion.
 <2> show only names and the nature of change, but not actual
@@ -88,18 +89,17 @@ which in turn also disables recursive behaviour, so without -r
 you would only see the directory name if there is a change in a
 file in a subdirectory.
 <3> limit diff output to named subtrees.
-------------
 
 Munging the diff output::
 +
 ------------
-$ git diff --find-copies-harder -B -C <1>
-$ git diff -R <2>
-
+$ git diff --find-copies-harder -B -C  <1>
+$ git diff -R                          <2>
+------------
++
 <1> spend extra cycles to find renames, copies and complete
 rewrites (very expensive).
 <2> output diff in reverse.
-------------
 
 
 Author
index d55456a..74102b7 100644 (file)
@@ -8,43 +8,82 @@ git-grep - Print lines matching a pattern
 
 SYNOPSIS
 --------
-'git-grep' [<option>...] [-e] <pattern> [--] [<path>...]
+[verse]
+'git-grep' [--cached]
+          [-a | --text] [-I] [-i | --ignore-case] [-w | --word-regexp]
+          [-v | --invert-match]
+          [-E | --extended-regexp] [-G | --basic-regexp] [-F | --fixed-strings]
+          [-n] [-l | --files-with-matches] [-L | --files-without-match]
+          [-c | --count]
+          [-A <post-context>] [-B <pre-context>] [-C <context>]
+          [-f <file>] [-e <pattern>]
+          [<tree>...]
+          [--] [<path>...]
 
 DESCRIPTION
 -----------
-Searches list of files `git-ls-files` produces for lines
-containing a match to the given pattern.
+Look for specified patterns in the working tree files, blobs
+registered in the index file, or given tree objects.
 
 
 OPTIONS
 -------
-`--`::
-       Signals the end of options; the rest of the parameters
-       are <path> limiters.
+--cached::
+       Instead of searching in the working tree files, check
+       the blobs registerd in the index file.
+
+-a | --text::
+       Process binary files as if they were text.
+
+-i | --ignore-case::
+       Ignore case differences between the patterns and the
+       files.
+
+-w | --word-regexp::
+       Match the pattern only at word boundary (either begin at the
+       beginning of a line, or preceded by a non-word character; end at
+       the end of a line or followed by a non-word character).
+
+-v | --invert-match::
+       Select non-matching lines.
+
+-E | --extended-regexp | -G | --basic-regexp::
+       Use POSIX extended/basic regexp for patterns.  Default
+       is to use basic regexp.
 
-<option>...::
-       Either an option to pass to `grep` or `git-ls-files`.
-+
-The following are the specific `git-ls-files` options
-that may be given: `-o`, `--cached`, `--deleted`, `--others`,
-`--killed`, `--ignored`, `--modified`, `--exclude=\*`,
-`--exclude-from=\*`, and `--exclude-per-directory=\*`.
-+
-All other options will be passed to `grep`.
+-n::
+       Prefix the line number to matching lines.
 
-<pattern>::
-       The pattern to look for.  The first non option is taken
-       as the pattern; if your pattern begins with a dash, use
-       `-e <pattern>`.
+-l | --files-with-matches | -L | --files-without-match::
+       Instead of showing every matched line, show only the
+       names of files that contain (or do not contain) matches.
 
-<path>...::
-       Optional paths to limit the set of files to be searched;
-       passed to `git-ls-files`.
+-c | --count::
+       Instead of showing every matched line, show the number of
+       lines that match.
+
+-[ABC] <context>::
+       Show `context` trailing (`A` -- after), or leading (`B`
+       -- before), or both (`C` -- context) lines, and place a
+       line containing `--` between continguous groups of
+       matches.
+
+-f <file>::
+       Read patterns from <file>, one per line.
+
+`<tree>...`::
+       Search blobs in the trees for specified patterns.
+
+`--`::
+       Signals the end of options; the rest of the parameters
+       are <path> limiters.
 
 
 Author
 ------
-Written by Linus Torvalds <torvalds@osdl.org>
+Originally written by Linus Torvalds <torvalds@osdl.org>, later
+revamped by Junio C Hamano.
+
 
 Documentation
 --------------
index cfc0d88..eca9e9c 100644 (file)
@@ -29,6 +29,7 @@ CONFIGURATION
 git-imap-send requires the following values in the repository
 configuration file (shown with examples):
 
+..........................
 [imap]
     Folder = "INBOX.Drafts"
 
@@ -38,8 +39,9 @@ configuration file (shown with examples):
 [imap]
     Host = imap.server.com
     User = bob
-    Password = pwd
+    Pass = pwd
     Port = 143
+..........................
 
 
 BUGS
index aeb1115..8a150d8 100644 (file)
@@ -60,12 +60,12 @@ Start a new git repository for an existing code base::
 +
 ----------------
 $ cd /path/to/my/codebase
-$ git-init-db <1>
-$ git-add . <2>
-
+$ git-init-db   <1>
+$ git-add .     <2>
+----------------
++
 <1> prepare /path/to/my/codebase/.git directory
 <2> add all existing file to the index
-----------------
 
 
 Author
index 76cb894..c9ffff7 100644 (file)
@@ -14,13 +14,12 @@ DESCRIPTION
 -----------
 Shows the commit logs.
 
-The command takes options applicable to the gitlink::git-rev-list[1]
+The command takes options applicable to the gitlink:git-rev-list[1]
 command to control what is shown and how, and options applicable to
-the gitlink::git-diff-tree[1] commands to control how the change
+the gitlink:git-diff-tree[1] commands to control how the change
 each commit introduces are shown.
 
-This manual page describes only the most frequently used
-options.
+This manual page describes only the most frequently used options.
 
 
 OPTIONS
@@ -52,7 +51,7 @@ git log v2.6.12.. include/scsi drivers/scsi::
        Show all commits since version 'v2.6.12' that changed any file
        in the include/scsi or drivers/scsi subdirectories
 
-git log --since="2 weeks ago" -- gitk::
+git log --since="2 weeks ago" \-- gitk::
 
        Show the changes during the last two weeks to the file 'gitk'.
        The "--" is necessary to avoid confusion with the *branch* named
index 796d049..a29c633 100644 (file)
@@ -106,7 +106,7 @@ OPTIONS
        lines, show only handful hexdigits prefix.
        Non default number of digits can be specified with --abbrev=<n>.
 
---::
+\--::
        Do not interpret any more arguments as options.
 
 <file>::
index d1d56f1..6099be2 100644 (file)
@@ -8,16 +8,26 @@ git-merge-base - Finds as good a common ancestor as possible for a merge
 
 SYNOPSIS
 --------
-'git-merge-base' <commit> <commit>
+'git-merge-base' [--all] <commit> <commit>
 
 DESCRIPTION
 -----------
-"git-merge-base" finds as good a common ancestor as possible. Given a
-selection of equally good common ancestors it should not be relied on
-to decide in any particular way.
+
+"git-merge-base" finds as good a common ancestor as possible between
+the two commits. That is, given two commits A and B 'git-merge-base A
+B' will output a commit which is reachable from both A and B through
+the parent relationship.
+
+Given a selection of equally good common ancestors it should not be
+relied on to decide in any particular way.
 
 The "git-merge-base" algorithm is still in flux - use the source...
 
+OPTIONS
+-------
+--all::
+       Output all common ancestors for the two commits instead of
+       just one.
 
 Author
 ------
index fbc986a..332e023 100644 (file)
@@ -8,7 +8,7 @@ git-merge-index - Runs a merge for files needing merging
 
 SYNOPSIS
 --------
-'git-merge-index' [-o] [-q] <merge-program> (-a | -- | <file>\*)
+'git-merge-index' [-o] [-q] <merge-program> (-a | \-- | <file>\*)
 
 DESCRIPTION
 -----------
@@ -19,7 +19,7 @@ files are passed as arguments 5, 6 and 7.
 
 OPTIONS
 -------
---::
+\--::
        Do not interpret any more arguments as options.
 
 -a::
index 6870708..ffaa004 100644 (file)
@@ -41,6 +41,7 @@ Enter git-name-rev:
 
 ------------
 % git name-rev 33db5f4d9027a10e477ccf054b2c1ab94f74c85a
+33db5f4d9027a10e477ccf054b2c1ab94f74c85a tags/v0.99^0~940
 ------------
 
 Now you are wiser, because you know that it happened 940 revisions before v0.99.
index f694fcb..a11e303 100644 (file)
@@ -28,7 +28,7 @@ OPTIONS
        Do not remove anything; just report what it would
        remove.
 
---::
+\--::
        Do not interpret any more arguments as options.
 
 <head>...::
index 4a7e67a..08ee4aa 100644 (file)
@@ -3,38 +3,53 @@ git-rebase(1)
 
 NAME
 ----
-git-rebase - Rebase local commits to new upstream head
+git-rebase - Rebase local commits to a new head
 
 SYNOPSIS
 --------
 'git-rebase' [--onto <newbase>] <upstream> [<branch>]
 
+'git-rebase' --continue | --skip | --abort
+
 DESCRIPTION
 -----------
-git-rebase applies to <upstream> (or optionally to <newbase>) commits
-from <branch> that do not appear in <upstream>. When <branch> is not
-specified it defaults to the current branch (HEAD).
-
-When git-rebase is complete, <branch> will be updated to point to the
-newly created line of commit objects, so the previous line will not be
-accessible unless there are other references to it already.
+git-rebase replaces <branch> with a new branch of the same name.  When
+the --onto option is provided the new branch starts out with a HEAD equal
+to <newbase>, otherwise it is equal to <upstream>.  It then attempts to
+create a new commit for each commit from the original <branch> that does
+not exist in the <upstream> branch.
+
+It is possible that a merge failure will prevent this process from being
+completely automatic.  You will have to resolve any such merge failure
+and run `git rebase --continue`.  Another option is to bypass the commit
+that caused the merge failure with `git rebase --skip`.  To restore the
+original <branch> and remove the .dotest working files, use the command
+`git rebase --abort` instead.
+
+Note that if <branch> is not specified on the command line, the currently
+checked out branch is used.
 
 Assume the following history exists and the current branch is "topic":
 
+------------
           A---B---C topic
          /
     D---E---F---G master
+------------
 
 From this point, the result of either of the following commands:
 
+
     git-rebase master
     git-rebase master topic
 
 would be:
 
+------------
                   A'--B'--C' topic
                  /
     D---E---F---G master
+------------
 
 While, starting from the same point, the result of either of the following
 commands:
@@ -44,21 +59,33 @@ commands:
 
 would be:
 
+------------
               A'--B'--C' topic
              /
     D---E---F---G master
+------------
 
 In case of conflict, git-rebase will stop at the first problematic commit
-and leave conflict markers in the tree.  After resolving the conflict manually
-and updating the index with the desired resolution, you can continue the
-rebasing process with
+and leave conflict markers in the tree.  You can use git diff to locate
+the markers (<<<<<<) and make edits to resolve the conflict.  For each
+file you edit, you need to tell git that the conflict has been resolved,
+typically this would be done with
+
+
+    git update-index <filename>
+
+
+After resolving the conflict manually and updating the index with the
+desired resolution, you can continue the rebasing process with
+
+
+    git rebase --continue
 
-    git am --resolved --3way
 
 Alternatively, you can undo the git-rebase with
 
-    git reset --hard ORIG_HEAD
-    rm -r .dotest
+
+    git rebase --abort
 
 OPTIONS
 -------
@@ -73,6 +100,28 @@ OPTIONS
 <branch>::
        Working branch; defaults to HEAD.
 
+--continue::
+       Restart the rebasing process after having resolved a merge conflict.
+
+--abort::
+       Restore the original branch and abort the rebase operation.
+
+NOTES
+-----
+When you rebase a branch, you are changing its history in a way that
+will cause problems for anyone who already has a copy of the branch
+in their repository and tries to pull updates from you.  You should
+understand the implications of using 'git rebase' on a repository that
+you share.
+
+When the git rebase command is run, it will first execute a "pre-rebase"
+hook if one exists.  You can use this hook to do sanity checks and
+reject the rebase if it isn't appropriate.  Please see the template
+pre-rebase hook script for an example.
+
+You must be in the top directory of your project to start (or continue)
+a rebase.  Upon completion, <branch> will be the current branch.
+
 Author
 ------
 Written by Junio C Hamano <junkio@cox.net>
index d2f9a44..9516227 100644 (file)
@@ -38,6 +38,7 @@ OPTIONS
 -d::
        After packing, if the newly created packs make some
        existing packs redundant, remove the redundant packs.
+       Also runs gitlink:git-prune-packed[1].
 
 -l::
         Pass the `--local` option to `git pack-objects`, see
index 26759a8..660c18f 100644 (file)
@@ -15,6 +15,7 @@ SYNOPSIS
 'git-repo-config' [type] --get-all name [value_regex]
 'git-repo-config' [type] --unset name [value_regex]
 'git-repo-config' [type] --unset-all name [value_regex]
+'git-repo-config' -l | --list
 
 DESCRIPTION
 -----------
@@ -22,10 +23,11 @@ You can query/set/replace/unset options with this command. The name is
 actually the section and the key separated by a dot, and the value will be
 escaped.
 
-If you want to set/unset an option which can occur on multiple lines, you
-should provide a POSIX regex for the value. If you want to handle the lines
-*not* matching the regex, just prepend a single exclamation mark in front
-(see EXAMPLES).
+If you want to set/unset an option which can occur on multiple
+lines, a POSIX regexp `value_regex` needs to be given.  Only the
+existing values that match the regexp are updated or unset.  If
+you want to handle the lines that do *not* match the regex, just
+prepend a single exclamation mark in front (see EXAMPLES).
 
 The type specifier can be either '--int' or '--bool', which will make
 'git-repo-config' ensure that the variable(s) are of the given type and
@@ -33,10 +35,10 @@ convert the value to the canonical form (simple decimal number for int,
 a "true" or "false" string for bool). If no type specifier is passed,
 no checks or transformations are performed on the value.
 
-This command will fail if
+This command will fail if:
 
-. .git/config is invalid,
-. .git/config can not be written to,
+. The .git/config file is invalid,
+. Can not write to .git/config,
 . no section was provided,
 . the section or key is invalid,
 . you try to unset an option which does not exist, or
@@ -48,7 +50,7 @@ OPTIONS
 
 --replace-all::
        Default behaviour is to replace at most one line. This replaces
-       all lines matching the key (and optionally the value_regex)
+       all lines matching the key (and optionally the value_regex).
 
 --get::
        Get the value for a given key (optionally filtered by a regex
@@ -58,12 +60,18 @@ OPTIONS
        Like get, but does not fail if the number of values for the key
        is not exactly one.
 
+--get-regexp::
+       Like --get-all, but interprets the name as a regular expression.
+
 --unset::
        Remove the line matching the key from .git/config.
 
 --unset-all::
        Remove all matching lines from .git/config.
 
+-l, --list::
+       List all variables set in .git/config.
+
 
 EXAMPLE
 -------
@@ -87,11 +95,11 @@ Given a .git/config like this:
                renames = true
 
        ; Proxy settings
-       [proxy]
-               command="ssh" for "ssh://kernel.org/"
-               command="proxy-command" for kernel.org
-               command="myprotocol-command" for "my://"
-               command=default-proxy ; for all the rest
+       [core]
+               gitproxy="ssh" for "ssh://kernel.org/"
+               gitproxy="proxy-command" for kernel.org
+               gitproxy="myprotocol-command" for "my://"
+               gitproxy=default-proxy ; for all the rest
 
 you can set the filemode to true with
 
@@ -104,7 +112,7 @@ to what URL they apply. Here is how to change the entry for kernel.org
 to "ssh".
 
 ------------
-% git repo-config proxy.command '"ssh" for kernel.org' 'for kernel.org$'
+% git repo-config core.gitproxy '"ssh" for kernel.org' 'for kernel.org$'
 ------------
 
 This makes sure that only the key/value pair for kernel.org is replaced.
@@ -115,7 +123,7 @@ To delete the entry for renames, do
 % git repo-config --unset diff.renames
 ------------
 
-If you want to delete an entry for a multivar (like proxy.command above),
+If you want to delete an entry for a multivar (like core.gitproxy above),
 you have to provide a regex matching the value of exactly one line.
 
 To query the value for a given key, do
@@ -133,27 +141,27 @@ or
 or, to query a multivar:
 
 ------------
-% git repo-config --get proxy.command "for kernel.org$"
+% git repo-config --get core.gitproxy "for kernel.org$"
 ------------
 
 If you want to know all the values for a multivar, do:
 
 ------------
-% git repo-config --get-all proxy.command
+% git repo-config --get-all core.gitproxy
 ------------
 
-If you like to live dangerous, you can replace *all* proxy.commands by a
+If you like to live dangerous, you can replace *all* core.gitproxy by a
 new one with
 
 ------------
-% git repo-config --replace-all proxy.command ssh
+% git repo-config --replace-all core.gitproxy ssh
 ------------
 
 However, if you really only want to replace the line for the default proxy,
 i.e. the one without a "for ..." postfix, do something like this:
 
 ------------
-% git repo-config proxy.command ssh '! for '
+% git repo-config core.gitproxy ssh '! for '
 ------------
 
 To actually match only values with an exclamation mark, you have to
@@ -163,13 +171,16 @@ To actually match only values with an exclamation mark, you have to
 ------------
 
 
+include::config.txt[]
+
+
 Author
 ------
 Written by Johannes Schindelin <Johannes.Schindelin@gmx.de>
 
 Documentation
 --------------
-Documentation by Johannes Schindelin.
+Documentation by Johannes Schindelin, Petr Baudis and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
index b7b9798..b27399d 100644 (file)
@@ -43,16 +43,17 @@ OPTIONS
        Commit to make the current HEAD.
 
 Examples
-~~~~~~~~
+--------
 
 Undo a commit and redo::
 +
 ------------
 $ git commit ...
-$ git reset --soft HEAD^ <1>
-$ edit <2>
-$ git commit -a -c ORIG_HEAD <3>
-
+$ git reset --soft HEAD^      <1>
+$ edit                        <2>
+$ git commit -a -c ORIG_HEAD  <3>
+------------
++
 <1> This is most often done when you remembered what you
 just committed is incomplete, or you misspelled your commit
 message, or both.  Leaves working tree as it was before "reset".
@@ -60,43 +61,43 @@ message, or both.  Leaves working tree as it was before "reset".
 <3> "reset" copies the old head to .git/ORIG_HEAD; redo the
 commit by starting with its log message.  If you do not need to
 edit the message further, you can give -C option instead.
-------------
 
 Undo commits permanently::
 +
 ------------
 $ git commit ...
-$ git reset --hard HEAD~3 <1>
-
+$ git reset --hard HEAD~3   <1>
+------------
++
 <1> The last three commits (HEAD, HEAD^, and HEAD~2) were bad
 and you do not want to ever see them again.  Do *not* do this if
 you have already given these commits to somebody else.
-------------
 
 Undo a commit, making it a topic branch::
 +
 ------------
-$ git branch topic/wip <1>
-$ git reset --hard HEAD~3 <2>
-$ git checkout topic/wip <3>
-
+$ git branch topic/wip     <1>
+$ git reset --hard HEAD~3  <2>
+$ git checkout topic/wip   <3>
+------------
++
 <1> You have made some commits, but realize they were premature
 to be in the "master" branch.  You want to continue polishing
 them in a topic branch, so create "topic/wip" branch off of the
 current HEAD.
 <2> Rewind the master branch to get rid of those three commits.
 <3> Switch to "topic/wip" branch and keep working.
-------------
 
 Undo update-index::
 +
 ------------
-$ edit <1>
+$ edit                                     <1>
 $ git-update-index frotz.c filfre.c
-$ mailx <2>
-$ git reset <3>
-$ git pull git://info.example.com/ nitfol <4>
-
+$ mailx                                    <2>
+$ git reset                                <3>
+$ git pull git://info.example.com/ nitfol  <4>
+------------
++
 <1> you are happily working on something, and find the changes
 in these files are in good order.  You do not want to see them
 when you run "git diff", because you plan to work on other files
@@ -109,12 +110,11 @@ index changes for these two files.  Your changes in working tree
 remain there.
 <4> then you can pull and merge, leaving frotz.c and filfre.c
 changes still in the working tree.
-------------
 
 Undo a merge or pull::
 +
 ------------
-$ git pull <1>
+$ git pull                         <1>
 Trying really trivial in-index merge...
 fatal: Merge requires file-level merging
 Nope.
@@ -122,20 +122,19 @@ Nope.
 Auto-merging nitfol
 CONFLICT (content): Merge conflict in nitfol
 Automatic merge failed/prevented; fix up by hand
-$ git reset --hard <2>
-
+$ git reset --hard                 <2>
+$ git pull . topic/branch          <3>
+Updating from 41223... to 13134...
+Fast forward
+$ git reset --hard ORIG_HEAD       <4>
+------------
++
 <1> try to update from the upstream resulted in a lot of
 conflicts; you were not ready to spend a lot of time merging
 right now, so you decide to do that later.
 <2> "pull" has not made merge commit, so "git reset --hard"
 which is a synonym for "git reset --hard HEAD" clears the mess
 from the index file and the working tree.
-
-$ git pull . topic/branch <3>
-Updating from 41223... to 13134...
-Fast forward
-$ git reset --hard ORIG_HEAD <4>
-
 <3> merge a topic branch into the current branch, which resulted
 in a fast forward.
 <4> but you decided that the topic branch is not ready for public
@@ -143,7 +142,6 @@ consumption yet.  "pull" or "merge" always leaves the original
 tip of the current branch in ORIG_HEAD, so resetting hard to it
 brings your index file and the working tree back to that state,
 and resets the tip of the branch to that commit.
-------------
 
 Interrupted workflow::
 +
@@ -155,21 +153,21 @@ need to get to the other branch for a quick bugfix.
 ------------
 $ git checkout feature ;# you were working in "feature" branch and
 $ work work work       ;# got interrupted
-$ git commit -a -m 'snapshot WIP' <1>
+$ git commit -a -m 'snapshot WIP'                 <1>
 $ git checkout master
 $ fix fix fix
 $ git commit ;# commit with real log
 $ git checkout feature
-$ git reset --soft HEAD^ ;# go back to WIP state <2>
-$ git reset <3>
-
+$ git reset --soft HEAD^ ;# go back to WIP state  <2>
+$ git reset                                       <3>
+------------
++
 <1> This commit will get blown away so a throw-away log message is OK.
 <2> This removes the 'WIP' commit from the commit history, and sets
     your working tree to the state just before you made that snapshot.
-<3> After <2>, the index file still has all the WIP changes you
-    committed in <1>.  This sets it to the last commit you were
-    basing the WIP changes on.
-------------
+<3> At this point the index file still has all the WIP changes you
+    committed as 'snapshot WIP'.  This updates the index to show your
+    WIP files as uncommitted.
 
 Author
 ------
index 8255ae1..ad6d14c 100644 (file)
@@ -68,9 +68,10 @@ OPTIONS
 --bisect::
        Limit output to the one commit object which is roughly halfway
        between the included and excluded commits. Thus, if 'git-rev-list
-       --bisect foo ^bar ^baz' outputs 'midpoint', the output
-       of 'git-rev-list foo ^midpoint' and 'git-rev-list midpoint
-       ^bar ^baz' would be of roughly the same length. Finding the change
+       --bisect foo {caret}bar {caret}baz' outputs 'midpoint', the output
+       of 'git-rev-list foo {caret}midpoint' and 'git-rev-list midpoint
+       {caret}bar {caret}baz' would be of roughly the same length.
+       Finding the change
        which introduces a regression is thus reduced to a binary search:
        repeatedly generate and test new 'midpoint's until the commit chain
        is of length one.
index 8b95df0..b894694 100644 (file)
@@ -67,6 +67,15 @@ OPTIONS
 --all::
        Show all refs found in `$GIT_DIR/refs`.
 
+--branches::
+       Show branch refs found in `$GIT_DIR/refs/heads`.
+
+--tags::
+       Show tag refs found in `$GIT_DIR/refs/tags`.
+
+--remotes::
+       Show tag refs found in `$GIT_DIR/refs/remotes`.
+
 --show-prefix::
        When the command is invoked from a subdirectory, show the
        path of the current directory relative to the top-level
@@ -115,6 +124,13 @@ syntax.
   happen to have both heads/master and tags/master, you can
   explicitly say 'heads/master' to tell git which one you mean.
 
+* A suffix '@' followed by a date specification enclosed in a brace
+  pair (e.g. '\{yesterday\}', '\{1 month 2 weeks 3 days 1 hour 1
+  second ago\}' or '\{1979-02-26 18:30:00\}') to specify the value
+  of the ref at a prior point in time.  This suffix may only be
+  used immediately following a ref name and the ref must have an
+  existing log ($GIT_DIR/logs/<ref>).
+
 * A suffix '{caret}' to a revision parameter means the first parent of
   that commit object.  '{caret}<n>' means the <n>th parent (i.e.
   'rev{caret}'
index c9c3088..66fc478 100644 (file)
@@ -32,7 +32,7 @@ OPTIONS
 -v::
         Be verbose.
 
---::
+\--::
        This option can be used to separate command-line options from
        the list of files, (useful when filenames might be mistaken
        for command-line options).
index 1828062..c20b38b 100644 (file)
@@ -13,9 +13,16 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-Reads a packed archive (.pack) from the standard input, and
-expands the objects contained in the pack into "one-file
-one-object" format in $GIT_OBJECT_DIRECTORY.
+Read a packed archive (.pack) from the standard input, expanding
+the objects contained within and writing them into the repository in
+"loose" (one object per file) format.
+
+Objects that already exist in the repository will *not* be unpacked
+from the pack-file.  Therefore, nothing will be unpacked if you use
+this command on a pack-file that exists within the target repository.
+
+Please see the `git-repack` documentation for options to generate
+new packs and replace existing ones.
 
 OPTIONS
 -------
index 0a1b0ad..d043e86 100644 (file)
@@ -10,12 +10,12 @@ SYNOPSIS
 --------
 [verse]
 'git-update-index'
-            [--add] [--remove | --force-remove] [--replace] 
-            [--refresh [-q] [--unmerged] [--ignore-missing]]
+            [--add] [--remove | --force-remove] [--replace]
+            [--refresh] [-q] [--unmerged] [--ignore-missing]
             [--cacheinfo <mode> <object> <file>]\*
             [--chmod=(+|-)x]
             [--assume-unchanged | --no-assume-unchanged]
-            [--really-refresh]
+            [--really-refresh] [--unresolve] [--again]
             [--info-only] [--index-info]
             [-z] [--stdin]
             [--verbose]
@@ -80,6 +80,14 @@ OPTIONS
        filesystem that has very slow lstat(2) system call
        (e.g. cifs).
 
+--again::
+       Runs `git-update-index` itself on the paths whose index
+       entries are different from those from the `HEAD` commit.
+
+--unresolve::
+       Restores the 'unmerged' or 'needs updating' state of a
+       file during a merge if it was cleared by accident.
+
 --info-only::
        Do not create objects in the object database for all
        <file> arguments that follow this flag; just insert
@@ -109,7 +117,7 @@ OPTIONS
        Only meaningful with `--stdin`; paths are separated with
        NUL character instead of LF.
 
---::
+\--::
        Do not interpret any more arguments as options.
 
 <file>::
@@ -247,34 +255,33 @@ To update and refresh only the files already checked out:
 $ git-checkout-index -n -f -a && git-update-index --ignore-missing --refresh
 ----------------
 
-On an inefficient filesystem with `core.ignorestat` set:
-
+On an inefficient filesystem with `core.ignorestat` set::
++
 ------------
-$ git update-index --really-refresh <1>
-$ git update-index --no-assume-unchanged foo.c <2>
-$ git diff --name-only <3>
+$ git update-index --really-refresh              <1>
+$ git update-index --no-assume-unchanged foo.c   <2>
+$ git diff --name-only                           <3>
 $ edit foo.c
-$ git diff --name-only <4>
+$ git diff --name-only                           <4>
 M foo.c
-$ git update-index foo.c <5>
-$ git diff --name-only <6>
+$ git update-index foo.c                         <5>
+$ git diff --name-only                           <6>
 $ edit foo.c
-$ git diff --name-only <7>
-$ git update-index --no-assume-unchanged foo.c <8>
-$ git diff --name-only <9>
+$ git diff --name-only                           <7>
+$ git update-index --no-assume-unchanged foo.c   <8>
+$ git diff --name-only                           <9>
 M foo.c
-
-<1> forces lstat(2) to set "assume unchanged" bits for paths
-    that match index.
+------------
++
+<1> forces lstat(2) to set "assume unchanged" bits for paths that match index.
 <2> mark the path to be edited.
 <3> this does lstat(2) and finds index matches the path.
-<4> this does lstat(2) and finds index does not match the path.
+<4> this does lstat(2) and finds index does *not* match the path.
 <5> registering the new version to index sets "assume unchanged" bit.
 <6> and it is assumed unchanged.
 <7> even after you edit it.
 <8> you can tell about the change after the fact.
 <9> now it checks with lstat(2) and finds it has been changed.
-------------
 
 
 Configuration
index 475237f..e062030 100644 (file)
@@ -7,7 +7,7 @@ git-update-ref - update the object name stored in a ref safely
 
 SYNOPSIS
 --------
-'git-update-ref' <ref> <newvalue> [<oldvalue>]
+'git-update-ref' [-m <reason>] <ref> <newvalue> [<oldvalue>]
 
 DESCRIPTION
 -----------
@@ -49,6 +49,32 @@ for reading but not for writing (so we'll never write through a
 ref symlink to some other tree, if you have copied a whole
 archive by creating a symlink tree).
 
+Logging Updates
+---------------
+If config parameter "core.logAllRefUpdates" is true or the file
+"$GIT_DIR/logs/<ref>" exists then `git-update-ref` will append
+a line to the log file "$GIT_DIR/logs/<ref>" (dereferencing all
+symbolic refs before creating the log name) describing the change
+in ref value.  Log lines are formatted as:
+
+    . oldsha1 SP newsha1 SP committer LF
++
+Where "oldsha1" is the 40 character hexadecimal value previously
+stored in <ref>, "newsha1" is the 40 character hexadecimal value of
+<newvalue> and "committer" is the committer's name, email address
+and date in the standard GIT committer ident format.
+
+Optionally with -m:
+
+    . oldsha1 SP newsha1 SP committer TAB message LF
++
+Where all fields are as described above and "message" is the
+value supplied to the -m option.
+
+An update will fail (without changing <ref>) if the current user is
+unable to create a new log file, append to the existing log file
+or does not have committer information available.
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>.
index 90cb157..a5b1a0d 100644 (file)
@@ -17,7 +17,10 @@ Prints a git logical variable.
 OPTIONS
 -------
 -l::
-       Cause the logical variables to be listed.
+       Cause the logical variables to be listed. In addition, all the
+       variables of the git configuration file .git/config are listed
+       as well. (However, the configuration variables listing functionality
+       is deprecated in favor of `git-repo-config -l`.)
 
 EXAMPLE
 --------
@@ -46,6 +49,7 @@ See Also
 --------
 gitlink:git-commit-tree[1]
 gitlink:git-tag[1]
+gitlink:git-repo-config[1]
 
 Author
 ------
index 4962d69..7a6132b 100644 (file)
@@ -25,7 +25,7 @@ OPTIONS
 -v::
        After verifying the pack, show list of objects contained
        in the pack.
---::
+\--::
        Do not interpret any more arguments as options.
 
 OUTPUT FORMAT
index 641cb7e..e8f21d0 100644 (file)
@@ -58,7 +58,7 @@ git-whatchanged -p v2.6.12.. include/scsi drivers/scsi::
        Show as patches the commits since version 'v2.6.12' that changed
        any file in the include/scsi or drivers/scsi subdirectories
 
-git-whatchanged --since="2 weeks ago" -- gitk::
+git-whatchanged --since="2 weeks ago" \-- gitk::
 
        Show the changes during the last two weeks to the file 'gitk'.
        The "--" is necessary to avoid confusion with the *branch* named
index eb126d7..cb482bf 100644 (file)
@@ -31,7 +31,7 @@ gitk v2.6.12.. include/scsi drivers/scsi::
        Show as the changes since version 'v2.6.12' that changed any
        file in the include/scsi or drivers/scsi subdirectories
 
-gitk --since="2 weeks ago" -- gitk::
+gitk --since="2 weeks ago" \-- gitk::
 
        Show the changes during the last two weeks to the file 'gitk'.
        The "--" is necessary to avoid confusion with the *branch* named
index 02a9d9c..39c90ad 100644 (file)
@@ -1,79 +1,57 @@
-object::
-       The unit of storage in git. It is uniquely identified by
-       the SHA1 of its contents. Consequently, an object can not
-       be changed.
-
-object name::
-       The unique identifier of an object. The hash of the object's contents
-       using the Secure Hash Algorithm 1 and usually represented by the 40
-       character hexadecimal encoding of the hash of the object (possibly
-       followed by a white space).
-
-SHA1::
-       Synonym for object name.
-
-object identifier::
-       Synonym for object name.
-
-hash::
-       In git's context, synonym to object name.
+alternate object database::
+       Via the alternates mechanism, a repository can inherit part of its
+       object database from another object database, which is called
+       "alternate".
 
-object database::
-       Stores a set of "objects", and an individual object is identified
-       by its object name. The objects usually live in `$GIT_DIR/objects/`.
+bare repository::
+       A bare repository is normally an appropriately named
+       directory with a `.git` suffix that does not have a
+       locally checked-out copy of any of the files under revision
+       control.  That is, all of the `git` administrative and
+       control files that would normally be present in the
+       hidden `.git` sub-directory are directly present in
+       the `repository.git` directory instead, and no other files
+       are present and checked out.  Usually publishers of public
+       repositories make bare repositories available.
 
 blob object::
        Untyped object, e.g. the contents of a file.
 
-tree object::
-       An object containing a list of file names and modes along with refs
-       to the associated blob and/or tree objects. A tree is equivalent
-       to a directory.
-
-tree::
-       Either a working tree, or a tree object together with the
-       dependent blob and tree objects (i.e. a stored representation
-       of a working tree).
-
-DAG::
-       Directed acyclic graph. The commit objects form a directed acyclic
-       graph, because they have parents (directed), and the graph of commit
-       objects is acyclic (there is no chain which begins and ends with the
-       same object).
-
-index::
-       A collection of files with stat information, whose contents are
-       stored as objects. The index is a stored version of your working
-       tree. Truth be told, it can also contain a second, and even a third
-       version of a working tree, which are used when merging.
-
-index entry::
-       The information regarding a particular file, stored in the index.
-       An index entry can be unmerged, if a merge was started, but not
-       yet finished (i.e. if the index contains multiple versions of
-       that file).
-
-unmerged index:
-       An index which contains unmerged index entries.
+branch::
+       A non-cyclical graph of revisions, i.e. the complete history of
+       a particular revision, which is called the branch head. The
+       branch heads are stored in `$GIT_DIR/refs/heads/`.
 
 cache::
        Obsolete for: index.
 
-working tree::
-       The set of files and directories currently being worked on,
-       i.e. you can work in your working tree without using git at all.
-
-directory::
-       The list you get with "ls" :-)
+chain::
+       A list of objects, where each object in the list contains a
+       reference to its successor (for example, the successor of a commit
+       could be one of its parents).
 
-revision::
-       A particular state of files and directories which was stored in
-       the object database. It is referenced by a commit object.
+changeset::
+       BitKeeper/cvsps speak for "commit". Since git does not store
+       changes, but states, it really does not make sense to use
+       the term "changesets" with git.
 
 checkout::
        The action of updating the working tree to a revision which was
        stored in the object database.
 
+cherry-picking::
+       In SCM jargon, "cherry pick" means to choose a subset of
+       changes out of a series of changes (typically commits)
+       and record them as a new series of changes on top of
+       different codebase.  In GIT, this is performed by
+       "git cherry-pick" command to extract the change
+       introduced by an existing commit and to record it based
+       on the tip of the current branch as a new commit.
+
+clean::
+       A working tree is clean, if it corresponds to the revision
+       referenced by the current head.  Also see "dirty".
+
 commit::
        As a verb: The action of storing the current state of the index in the
        object database. The result is a revision.
@@ -85,73 +63,90 @@ commit object::
        tree object which corresponds to the top directory of the
        stored revision.
 
-parent::
-       A commit object contains a (possibly empty) list of the logical
-       predecessor(s) in the line of development, i.e. its parents.
+core git::
+       Fundamental data structures and utilities of git. Exposes only
+       limited source code management tools.
 
-changeset::
-       BitKeeper/cvsps speak for "commit". Since git does not store
-       changes, but states, it really does not make sense to use
-       the term "changesets" with git.
+DAG::
+       Directed acyclic graph. The commit objects form a directed acyclic
+       graph, because they have parents (directed), and the graph of commit
+       objects is acyclic (there is no chain which begins and ends with the
+       same object).
 
-clean::
-       A working tree is clean, if it corresponds to the revision
-       referenced by the current head.
+dircache::
+       You are *waaaaay* behind.
 
 dirty::
        A working tree is said to be dirty if it contains modifications
        which have not been committed to the current branch.
 
-head::
-       The top of a branch. It contains a ref to the corresponding
-       commit object.
+directory::
+       The list you get with "ls" :-)
 
-branch::
-       A non-cyclical graph of revisions, i.e. the complete history of
-       a particular revision, which is called the branch head. The
-       branch heads are stored in `$GIT_DIR/refs/heads/`.
+ent::
+       Favorite synonym to "tree-ish" by some total geeks. See
+       `http://en.wikipedia.org/wiki/Ent_(Middle-earth)` for an in-depth
+       explanation.
 
-master::
-       The default branch. Whenever you create a git repository, a branch
-       named "master" is created, and becomes the active branch. In most
-       cases, this contains the local development.
+fast forward::
+       A fast-forward is a special type of merge where you have
+       a revision and you are "merging" another branch's changes
+       that happen to be a descendant of what you have.
+       In such these cases, you do not make a new merge commit but
+       instead just update to his revision. This will happen
+       frequently on a tracking branch of a remote repository.
 
-origin::
-       The default upstream branch. Most projects have one upstream
-       project which they track, and by default 'origin' is used for
-       that purpose.  New updates from upstream will be fetched into
-       this branch; you should never commit to it yourself.
+fetch::
+       Fetching a branch means to get the branch's head ref from a
+       remote repository, to find out which objects are missing from
+       the local object database, and to get them, too.
 
-ref::
-       A 40-byte hex representation of a SHA1 pointing to a particular
-       object. These may be stored in `$GIT_DIR/refs/`.
+file system::
+       Linus Torvalds originally designed git to be a user space file
+       system, i.e. the infrastructure to hold files and directories.
+       That ensured the efficiency and speed of git.
+
+git archive::
+       Synonym for repository (for arch people).
+
+hash::
+       In git's context, synonym to object name.
+
+head::
+       The top of a branch. It contains a ref to the corresponding
+       commit object.
 
 head ref::
        A ref pointing to a head. Often, this is abbreviated to "head".
        Head refs are stored in `$GIT_DIR/refs/heads/`.
 
-tree-ish::
-       A ref pointing to either a commit object, a tree object, or a
-       tag object pointing to a tag or commit or tree object.
+hook::
+       During the normal execution of several git commands,
+       call-outs are made to optional scripts that allow
+       a developer to add functionality or checking.
+       Typically, the hooks allow for a command to be pre-verified
+       and potentially aborted, and allow for a post-notification
+       after the operation is done.
+       The hook scripts are found in the `$GIT_DIR/hooks/` directory,
+       and are enabled by simply making them executable.
 
-ent::
-       Favorite synonym to "tree-ish" by some total geeks. See
-       `http://en.wikipedia.org/wiki/Ent_(Middle-earth)` for an in-depth
-       explanation.
+index::
+       A collection of files with stat information, whose contents are
+       stored as objects. The index is a stored version of your working
+       tree. Truth be told, it can also contain a second, and even a third
+       version of a working tree, which are used when merging.
 
-tag object::
-       An object containing a ref pointing to another object, which can
-       contain a message just like a commit object. It can also
-       contain a (PGP) signature, in which case it is called a "signed
-       tag object".
+index entry::
+       The information regarding a particular file, stored in the index.
+       An index entry can be unmerged, if a merge was started, but not
+       yet finished (i.e. if the index contains multiple versions of
+       that file).
 
-tag::
-       A ref pointing to a tag or commit object. In contrast to a head,
-       a tag is not changed by a commit. Tags (not tag objects) are
-       stored in `$GIT_DIR/refs/tags/`. A git tag has nothing to do with
-       a Lisp tag (which is called object type in git's context).
-       A tag is most typically used to mark a particular point in the
-       commit ancestry chain.
+master::
+       The default development branch. Whenever you create a git
+       repository, a branch named "master" is created, and becomes
+       the active branch. In most cases, this contains the local
+       development, though that is purely conventional and not required.
 
 merge::
        To merge branches means to try to accumulate the changes since a
@@ -159,55 +154,65 @@ merge::
        merge uses heuristics to accomplish that. Evidently, an automatic
        merge can fail.
 
-octopus::
-       To merge more than two branches. Also denotes an intelligent
-       predator.
+object::
+       The unit of storage in git. It is uniquely identified by
+       the SHA1 of its contents. Consequently, an object can not
+       be changed.
 
-resolve::
-       The action of fixing up manually what a failed automatic merge
-       left behind.
+object database::
+       Stores a set of "objects", and an individual object is identified
+       by its object name. The objects usually live in `$GIT_DIR/objects/`.
 
-rewind::
-       To throw away part of the development, i.e. to assign the head to
-       an earlier revision.
+object identifier::
+       Synonym for object name.
 
-rebase::
-       To clean a branch by starting from the head of the main line of
-       development ("master"), and reapply the (possibly cherry-picked)
-       changes from that branch.
+object name::
+       The unique identifier of an object. The hash of the object's contents
+       using the Secure Hash Algorithm 1 and usually represented by the 40
+       character hexadecimal encoding of the hash of the object (possibly
+       followed by a white space).
 
-repository::
-       A collection of refs together with an object database containing
-       all objects, which are reachable from the refs, possibly accompanied
-       by meta data from one or more porcelains. A repository can
-       share an object database with other repositories.
+object type:
+       One of the identifiers "commit","tree","tag" and "blob" describing
+       the type of an object.
 
-git archive::
-       Synonym for repository (for arch people).
+octopus::
+       To merge more than two branches. Also denotes an intelligent
+       predator.
 
-file system::
-       Linus Torvalds originally designed git to be a user space file
-       system, i.e. the infrastructure to hold files and directories.
-       That ensured the efficiency and speed of git.
+origin::
+       The default upstream tracking branch. Most projects have at
+       least one upstream project which they track. By default
+       'origin' is used for that purpose.  New upstream updates
+       will be fetched into this branch; you should never commit
+       to it yourself.
 
-alternate object database::
-       Via the alternates mechanism, a repository can inherit part of its
-       object database from another object database, which is called
-       "alternate".
+pack::
+       A set of objects which have been compressed into one file (to save
+       space or to transmit them efficiently).
 
-reachable::
-       An object is reachable from a ref/commit/tree/tag, if there is a
-       chain leading from the latter to the former.
+pack index::
+       The list of identifiers, and other information, of the objects in a
+       pack, to assist in efficiently accessing the contents of a pack.
 
-chain::
-       A list of objects, where each object in the list contains a
-       reference to its successor (for example, the successor of a commit
-       could be one of its parents).
+parent::
+       A commit object contains a (possibly empty) list of the logical
+       predecessor(s) in the line of development, i.e. its parents.
 
-fetch::
-       Fetching a branch means to get the branch's head ref from a
-       remote repository, to find out which objects are missing from
-       the local object database, and to get them, too.
+pickaxe::
+       The term pickaxe refers to an option to the diffcore routines
+       that help select changes that add or delete a given text string.
+       With the --pickaxe-all option, it can be used to view the
+       full changeset that introduced or removed, say, a particular
+       line of text.  See gitlink:git-diff[1].
+
+plumbing::
+       Cute name for core git.
+
+porcelain::
+       Cute name for programs and program suites depending on core git,
+       presenting a high level access to core git. Porcelains expose
+       more of a SCM interface than the plumbing.
 
 pull::
        Pulling a branch means to fetch it and merge it.
@@ -221,33 +226,101 @@ push::
        the remote head ref. If the remote head is not an ancestor to the
        local head, the push fails.
 
-pack::
-       A set of objects which have been compressed into one file (to save
-       space or to transmit them efficiently).
+reachable::
+       An object is reachable from a ref/commit/tree/tag, if there is a
+       chain leading from the latter to the former.
 
-pack index::
-       The list of identifiers, and other information, of the objects in a
-       pack, to assist in efficiently accessing the contents of a pack. 
+rebase::
+       To clean a branch by starting from the head of the main line of
+       development ("master"), and reapply the (possibly cherry-picked)
+       changes from that branch.
 
-core git::
-       Fundamental data structures and utilities of git. Exposes only
-       limited source code management tools.
+ref::
+       A 40-byte hex representation of a SHA1 or a name that denotes
+       a particular object. These may be stored in `$GIT_DIR/refs/`.
+
+refspec::
+       A refspec is used by fetch and push to describe the mapping
+       between remote ref and local ref.  They are combined with
+       a colon in the format <src>:<dst>, preceded by an optional
+       plus sign, +.  For example:
+       `git fetch $URL refs/heads/master:refs/heads/origin`
+       means "grab the master branch head from the $URL and store
+       it as my origin branch head".
+       And `git push $URL refs/heads/master:refs/heads/to-upstream`
+       means "publish my master branch head as to-upstream master head
+       at $URL".   See also gitlink:git-push[1]
 
-plumbing::
-       Cute name for core git.
+repository::
+       A collection of refs together with an object database containing
+       all objects, which are reachable from the refs, possibly accompanied
+       by meta data from one or more porcelains. A repository can
+       share an object database with other repositories.
 
-porcelain::
-       Cute name for programs and program suites depending on core git,
-       presenting a high level access to core git. Porcelains expose
-       more of a SCM interface than the plumbing.
+resolve::
+       The action of fixing up manually what a failed automatic merge
+       left behind.
 
-object type:
-       One of the identifiers "commit","tree","tag" and "blob" describing
-       the type of an object.
+revision::
+       A particular state of files and directories which was stored in
+       the object database. It is referenced by a commit object.
+
+rewind::
+       To throw away part of the development, i.e. to assign the head to
+       an earlier revision.
 
 SCM::
        Source code management (tool).
 
-dircache::
-       You are *waaaaay* behind.
+SHA1::
+       Synonym for object name.
+
+topic branch::
+       A regular git branch that is used by a developer to
+       identify a conceptual line of development.  Since branches
+       are very easy and inexpensive, it is often desirable to
+       have several small branches that each contain very well
+       defined concepts or small incremental yet related changes.
+
+tracking branch::
+       A regular git branch that is used to follow changes from
+       another repository.  A tracking branch should not contain
+       direct modifications or have local commits made to it.
+       A tracking branch can usually be identified as the
+       right-hand-side ref in a Pull: refspec.
+
+tree object::
+       An object containing a list of file names and modes along with refs
+       to the associated blob and/or tree objects. A tree is equivalent
+       to a directory.
+
+tree::
+       Either a working tree, or a tree object together with the
+       dependent blob and tree objects (i.e. a stored representation
+       of a working tree).
+
+tree-ish::
+       A ref pointing to either a commit object, a tree object, or a
+       tag object pointing to a tag or commit or tree object.
+
+tag object::
+       An object containing a ref pointing to another object, which can
+       contain a message just like a commit object. It can also
+       contain a (PGP) signature, in which case it is called a "signed
+       tag object".
+
+tag::
+       A ref pointing to a tag or commit object. In contrast to a head,
+       a tag is not changed by a commit. Tags (not tag objects) are
+       stored in `$GIT_DIR/refs/tags/`. A git tag has nothing to do with
+       a Lisp tag (which is called object type in git's context).
+       A tag is most typically used to mark a particular point in the
+       commit ancestry chain.
+
+unmerged index:
+       An index which contains unmerged index entries.
+
+working tree::
+       The set of files and directories currently being worked on,
+       i.e. you can work in your working tree without using git at all.
 
index 98fbe7d..b52dfdc 100644 (file)
@@ -128,3 +128,14 @@ remotes::
        Stores shorthands to be used to give URL and default
        refnames to interact with remote repository to `git
        fetch`, `git pull` and `git push` commands.
+
+logs::
+       Records of changes made to refs are stored in this
+       directory.  See the documentation on git-update-ref
+       for more information.
+
+logs/refs/heads/`name`::
+       Records all changes made to the branch tip named `name`.
+
+logs/refs/tags/`name`::
+       Records all changes made to the tag named `name`.
index e57dc78..e0bc552 100644 (file)
@@ -48,7 +48,7 @@ This list is sorted alphabetically:
 ';
 
 @keys=sort {uc($a) cmp uc($b)} keys %terms;
-$pattern='(\b'.join('\b|\b',reverse @keys).'\b)';
+$pattern='(\b(?<!link:git-)'.join('\b|\b(?<!link:git-)',reverse @keys).'\b)';
 foreach $key (@keys) {
        $terms{$key}=~s/$pattern/sprintf "<<ref_".no_spaces($1).",$1>>";/eg;
        print '[[ref_'.no_spaces($key).']]'.$key."::\n"
index a83c502..9ba608c 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -28,8 +28,8 @@ all:
 #
 # Define NO_SETENV if you don't have setenv in the C library.
 #
-# Define USE_SYMLINK_HEAD if you want .git/HEAD to be a symbolic link.
-# Don't enable it on Windows.
+# Define NO_SYMLINK_HEAD if you never want .git/HEAD to be a symbolic link.
+# Enable it on Windows.  By default, symrefs are still used.
 #
 # Define PPC_SHA1 environment variable when running make to make use of
 # a bundled SHA1 routine optimized for PowerPC.
@@ -115,23 +115,24 @@ SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powerpc__
 SCRIPT_SH = \
        git-add.sh git-bisect.sh git-branch.sh git-checkout.sh \
        git-cherry.sh git-clean.sh git-clone.sh git-commit.sh \
-       git-count-objects.sh git-diff.sh git-fetch.sh \
+       git-fetch.sh \
        git-format-patch.sh git-ls-remote.sh \
        git-merge-one-file.sh git-parse-remote.sh \
-       git-prune.sh git-pull.sh git-push.sh git-rebase.sh \
+       git-prune.sh git-pull.sh git-rebase.sh \
        git-repack.sh git-request-pull.sh git-reset.sh \
        git-resolve.sh git-revert.sh git-rm.sh git-sh-setup.sh \
-       git-tag.sh git-verify-tag.sh git-whatchanged.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 \
-       git-merge-resolve.sh git-merge-ours.sh git-grep.sh \
+       git-merge-resolve.sh git-merge-ours.sh \
        git-lost-found.sh
 
 SCRIPT_PERL = \
        git-archimport.perl git-cvsimport.perl git-relink.perl \
        git-shortlog.perl git-fmt-merge-msg.perl git-rerere.perl \
        git-annotate.perl git-cvsserver.perl \
-       git-svnimport.perl git-mv.perl git-cvsexportcommit.perl
+       git-svnimport.perl git-mv.perl git-cvsexportcommit.perl \
+       git-send-email.perl
 
 SCRIPT_PYTHON = \
        git-merge-recursive.py
@@ -139,7 +140,7 @@ SCRIPT_PYTHON = \
 SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
          $(patsubst %.perl,%,$(SCRIPT_PERL)) \
          $(patsubst %.py,%,$(SCRIPT_PYTHON)) \
-         git-cherry-pick git-show git-status
+         git-cherry-pick git-status
 
 # The ones that do not have to link with lcrypto, lz nor xdiff.
 SIMPLE_PROGRAMS = \
@@ -167,7 +168,9 @@ PROGRAMS = \
        git-name-rev$X git-pack-redundant$X git-repo-config$X git-var$X \
        git-describe$X git-merge-tree$X git-blame$X git-imap-send$X
 
-BUILT_INS = git-log$X
+BUILT_INS = git-log$X git-whatchanged$X git-show$X \
+       git-count-objects$X git-diff$X git-push$X \
+       git-grep$X
 
 # what 'all' will build and 'install' will install, in gitexecdir
 ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
@@ -199,12 +202,12 @@ LIB_H = \
        tree-walk.h log-tree.h
 
 DIFF_OBJS = \
-       diff-lib.o diffcore-break.o diffcore-order.o \
+       diff.o diff-lib.o diffcore-break.o diffcore-order.o \
        diffcore-pickaxe.o diffcore-rename.o tree-diff.o combine-diff.o \
        diffcore-delta.o log-tree.o
 
 LIB_OBJS = \
-       blob.o commit.o connect.o csum-file.o \
+       blob.o commit.o connect.o csum-file.o base85.o \
        date.o diff-delta.o entry.o exec_cmd.o ident.o index.o \
        object.o pack-check.o patch-delta.o path.o pkt-line.o \
        quote.o read-cache.o refs.o run-command.o \
@@ -214,7 +217,8 @@ LIB_OBJS = \
        $(DIFF_OBJS)
 
 BUILTIN_OBJS = \
-       builtin-log.o builtin-help.o
+       builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \
+       builtin-grep.o
 
 GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
 LIBS = $(GITLIBS) -lz
@@ -263,6 +267,7 @@ ifeq ($(uname_O),Cygwin)
        NO_D_TYPE_IN_DIRENT = YesPlease
        NO_D_INO_IN_DIRENT = YesPlease
        NO_STRCASESTR = YesPlease
+       NO_SYMLINK_HEAD = YesPlease
        NEEDS_LIBICONV = YesPlease
        # There are conflicting reports about this.
        # On some boxes NO_MMAP is needed, and not so elsewhere.
@@ -283,7 +288,9 @@ ifeq ($(uname_S),OpenBSD)
        ALL_LDFLAGS += -L/usr/local/lib
 endif
 ifeq ($(uname_S),NetBSD)
-       NEEDS_LIBICONV = YesPlease
+       ifeq ($(shell expr "$(uname_R)" : '[01]\.'),2)
+               NEEDS_LIBICONV = YesPlease
+       endif
        ALL_CFLAGS += -I/usr/pkg/include
        ALL_LDFLAGS += -L/usr/pkg/lib -Wl,-rpath,/usr/pkg/lib
 endif
@@ -317,10 +324,6 @@ else
        endif
 endif
 
-ifdef WITH_SEND_EMAIL
-       SCRIPT_PERL += git-send-email.perl
-endif
-
 ifndef NO_CURL
        ifdef CURLDIR
                # This is still problematic -- gcc does not always want -R.
@@ -386,6 +389,9 @@ endif
 ifdef NO_D_INO_IN_DIRENT
        ALL_CFLAGS += -DNO_D_INO_IN_DIRENT
 endif
+ifdef NO_SYMLINK_HEAD
+       ALL_CFLAGS += -DNO_SYMLINK_HEAD
+endif
 ifdef NO_STRCASESTR
        COMPAT_CFLAGS += -DNO_STRCASESTR
        COMPAT_OBJS += compat/strcasestr.o
@@ -470,6 +476,8 @@ git$X: git.c common-cmds.h $(BUILTIN_OBJS) $(GITLIBS)
                $(ALL_CFLAGS) -o $@ $(filter %.c,$^) \
                $(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS)
 
+builtin-help.o: common-cmds.h
+
 $(BUILT_INS): git$X
        rm -f $@ && ln git$X $@
 
@@ -503,9 +511,6 @@ $(patsubst %.py,%,$(SCRIPT_PYTHON)) : % : %.py
 git-cherry-pick: git-revert
        cp $< $@
 
-git-show: git-whatchanged
-       cp $< $@
-
 git-status: git-commit
        cp $< $@
 
@@ -560,10 +565,6 @@ git-http-push$X: revision.o http.o http-push.o $(LIB_FILE)
        $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
                $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
 
-git-rev-list$X: rev-list.o $(LIB_FILE)
-       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
-               $(LIBS) $(OPENSSL_LIBSSL)
-
 init-db.o: init-db.c
        $(CC) -c $(ALL_CFLAGS) \
                -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' $*.c
@@ -573,12 +574,12 @@ $(patsubst git-%$X,%.o,$(PROGRAMS)): $(GITLIBS)
 $(DIFF_OBJS): diffcore.h
 
 $(LIB_FILE): $(LIB_OBJS)
-       $(AR) rcs $@ $(LIB_OBJS)
+       rm -f $@ && $(AR) rcs $@ $(LIB_OBJS)
 
 XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o
 
 $(XDIFF_LIB): $(XDIFF_OBJS)
-       $(AR) rcs $@ $(XDIFF_OBJS)
+       rm -f $@ && $(AR) rcs $@ $(XDIFF_OBJS)
 
 
 doc:
@@ -607,7 +608,7 @@ test-date$X: test-date.c date.o ctype.o
        $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) test-date.c date.o ctype.o
 
 test-delta$X: test-delta.c diff-delta.o patch-delta.o
-       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $^ -lz
+       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $^
 
 check:
        for i in *.c; do sparse $(ALL_CFLAGS) $(SPARSE_FLAGS) $$i || exit; done
diff --git a/apply.c b/apply.c
index 269210a..7c8146a 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -10,6 +10,7 @@
 #include "cache.h"
 #include "quote.h"
 #include "blob.h"
+#include "delta.h"
 
 //  --check turns on checking that the working tree matches the
 //    files that are being modified, but doesn't apply the patch
@@ -19,6 +20,7 @@
 //
 static const char *prefix;
 static int prefix_length = -1;
+static int newfd = -1;
 
 static int p_value = 1;
 static int allow_binary_replacement = 0;
@@ -113,6 +115,9 @@ struct patch {
        char *new_name, *old_name, *def_name;
        unsigned int old_mode, new_mode;
        int is_rename, is_copy, is_new, is_delete, is_binary;
+#define BINARY_DELTA_DEFLATED 1
+#define BINARY_LITERAL_DEFLATED 2
+       unsigned long deflate_origlen;
        int lines_added, lines_deleted;
        int score;
        struct fragment *fragments;
@@ -966,6 +971,88 @@ static inline int metadata_changes(struct patch *patch)
                 patch->old_mode != patch->new_mode);
 }
 
+static int parse_binary(char *buffer, unsigned long size, struct patch *patch)
+{
+       /* We have read "GIT binary patch\n"; what follows is a line
+        * that says the patch method (currently, either "deflated
+        * literal" or "deflated delta") and the length of data before
+        * deflating; a sequence of 'length-byte' followed by base-85
+        * encoded data follows.
+        *
+        * Each 5-byte sequence of base-85 encodes up to 4 bytes,
+        * and we would limit the patch line to 66 characters,
+        * so one line can fit up to 13 groups that would decode
+        * to 52 bytes max.  The length byte 'A'-'Z' corresponds
+        * to 1-26 bytes, and 'a'-'z' corresponds to 27-52 bytes.
+        * The end of binary is signalled with an empty line.
+        */
+       int llen, used;
+       struct fragment *fragment;
+       char *data = NULL;
+
+       patch->fragments = fragment = xcalloc(1, sizeof(*fragment));
+
+       /* Grab the type of patch */
+       llen = linelen(buffer, size);
+       used = llen;
+       linenr++;
+
+       if (!strncmp(buffer, "delta ", 6)) {
+               patch->is_binary = BINARY_DELTA_DEFLATED;
+               patch->deflate_origlen = strtoul(buffer + 6, NULL, 10);
+       }
+       else if (!strncmp(buffer, "literal ", 8)) {
+               patch->is_binary = BINARY_LITERAL_DEFLATED;
+               patch->deflate_origlen = strtoul(buffer + 8, NULL, 10);
+       }
+       else
+               return error("unrecognized binary patch at line %d: %.*s",
+                            linenr-1, llen-1, buffer);
+       buffer += llen;
+       while (1) {
+               int byte_length, max_byte_length, newsize;
+               llen = linelen(buffer, size);
+               used += llen;
+               linenr++;
+               if (llen == 1)
+                       break;
+               /* Minimum line is "A00000\n" which is 7-byte long,
+                * and the line length must be multiple of 5 plus 2.
+                */
+               if ((llen < 7) || (llen-2) % 5)
+                       goto corrupt;
+               max_byte_length = (llen - 2) / 5 * 4;
+               byte_length = *buffer;
+               if ('A' <= byte_length && byte_length <= 'Z')
+                       byte_length = byte_length - 'A' + 1;
+               else if ('a' <= byte_length && byte_length <= 'z')
+                       byte_length = byte_length - 'a' + 27;
+               else
+                       goto corrupt;
+               /* if the input length was not multiple of 4, we would
+                * have filler at the end but the filler should never
+                * exceed 3 bytes
+                */
+               if (max_byte_length < byte_length ||
+                   byte_length <= max_byte_length - 4)
+                       goto corrupt;
+               newsize = fragment->size + byte_length;
+               data = xrealloc(data, newsize);
+               if (decode_85(data + fragment->size,
+                             buffer + 1,
+                             byte_length))
+                       goto corrupt;
+               fragment->size = newsize;
+               buffer += llen;
+               size -= llen;
+       }
+       fragment->patch = data;
+       return used;
+ corrupt:
+       return error("corrupt binary patch at line %d: %.*s",
+                    linenr-1, llen-1, buffer);
+}
+
 static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
 {
        int hdrsize, patchsize;
@@ -982,19 +1069,34 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
                        "Files ",
                        NULL,
                };
+               static const char git_binary[] = "GIT binary patch\n";
                int i;
                int hd = hdrsize + offset;
                unsigned long llen = linelen(buffer + hd, size - hd);
 
-               if (!memcmp(" differ\n", buffer + hd + llen - 8, 8))
+               if (llen == sizeof(git_binary) - 1 &&
+                   !memcmp(git_binary, buffer + hd, llen)) {
+                       int used;
+                       linenr++;
+                       used = parse_binary(buffer + hd + llen,
+                                           size - hd - llen, patch);
+                       if (used)
+                               patchsize = used + llen;
+                       else
+                               patchsize = 0;
+               }
+               else if (!memcmp(" differ\n", buffer + hd + llen - 8, 8)) {
                        for (i = 0; binhdr[i]; i++) {
                                int len = strlen(binhdr[i]);
                                if (len < size - hd &&
                                    !memcmp(binhdr[i], buffer + hd, len)) {
+                                       linenr++;
                                        patch->is_binary = 1;
+                                       patchsize = llen;
                                        break;
                                }
                        }
+               }
 
                /* Empty patch cannot be applied if:
                 * - it is a binary patch and we do not do binary_replace, or
@@ -1345,76 +1447,150 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag)
        return offset;
 }
 
-static int apply_fragments(struct buffer_desc *desc, struct patch *patch)
+static char *inflate_it(const void *data, unsigned long size,
+                       unsigned long inflated_size)
+{
+       z_stream stream;
+       void *out;
+       int st;
+
+       memset(&stream, 0, sizeof(stream));
+
+       stream.next_in = (unsigned char *)data;
+       stream.avail_in = size;
+       stream.next_out = out = xmalloc(inflated_size);
+       stream.avail_out = inflated_size;
+       inflateInit(&stream);
+       st = inflate(&stream, Z_FINISH);
+       if ((st != Z_STREAM_END) || stream.total_out != inflated_size) {
+               free(out);
+               return NULL;
+       }
+       return out;
+}
+
+static int apply_binary_fragment(struct buffer_desc *desc, struct patch *patch)
+{
+       unsigned long dst_size;
+       struct fragment *fragment = patch->fragments;
+       void *data;
+       void *result;
+
+       data = inflate_it(fragment->patch, fragment->size,
+                         patch->deflate_origlen);
+       if (!data)
+               return error("corrupt patch data");
+       switch (patch->is_binary) {
+       case BINARY_DELTA_DEFLATED:
+               result = patch_delta(desc->buffer, desc->size,
+                                    data,
+                                    patch->deflate_origlen,
+                                    &dst_size);
+               free(desc->buffer);
+               desc->buffer = result;
+               free(data);
+               break;
+       case BINARY_LITERAL_DEFLATED:
+               free(desc->buffer);
+               desc->buffer = data;
+               dst_size = patch->deflate_origlen;
+               break;
+       }
+       if (!desc->buffer)
+               return -1;
+       desc->size = desc->alloc = dst_size;
+       return 0;
+}
+
+static int apply_binary(struct buffer_desc *desc, struct patch *patch)
 {
-       struct fragment *frag = patch->fragments;
        const char *name = patch->old_name ? patch->old_name : patch->new_name;
+       unsigned char sha1[20];
+       unsigned char hdr[50];
+       int hdrlen;
 
-       if (patch->is_binary) {
-               unsigned char sha1[20];
+       if (!allow_binary_replacement)
+               return error("cannot apply binary patch to '%s' "
+                            "without --allow-binary-replacement",
+                            name);
 
-               if (!allow_binary_replacement)
-                       return error("cannot apply binary patch to '%s' "
-                                    "without --allow-binary-replacement",
-                                    name);
+       /* For safety, we require patch index line to contain
+        * full 40-byte textual SHA1 for old and new, at least for now.
+        */
+       if (strlen(patch->old_sha1_prefix) != 40 ||
+           strlen(patch->new_sha1_prefix) != 40 ||
+           get_sha1_hex(patch->old_sha1_prefix, sha1) ||
+           get_sha1_hex(patch->new_sha1_prefix, sha1))
+               return error("cannot apply binary patch to '%s' "
+                            "without full index line", name);
 
-               /* For safety, we require patch index line to contain
-                * full 40-byte textual SHA1 for old and new, at least for now.
+       if (patch->old_name) {
+               /* See if the old one matches what the patch
+                * applies to.
                 */
-               if (strlen(patch->old_sha1_prefix) != 40 ||
-                   strlen(patch->new_sha1_prefix) != 40 ||
-                   get_sha1_hex(patch->old_sha1_prefix, sha1) ||
-                   get_sha1_hex(patch->new_sha1_prefix, sha1))
-                       return error("cannot apply binary patch to '%s' "
-                                    "without full index line", name);
-
-               if (patch->old_name) {
-                       unsigned char hdr[50];
-                       int hdrlen;
-
-                       /* See if the old one matches what the patch
-                        * applies to.
-                        */
-                       write_sha1_file_prepare(desc->buffer, desc->size,
-                                               blob_type, sha1, hdr, &hdrlen);
-                       if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix))
-                               return error("the patch applies to '%s' (%s), "
-                                            "which does not match the "
-                                            "current contents.",
-                                            name, sha1_to_hex(sha1));
-               }
-               else {
-                       /* Otherwise, the old one must be empty. */
-                       if (desc->size)
-                               return error("the patch applies to an empty "
-                                            "'%s' but it is not empty", name);
-               }
+               write_sha1_file_prepare(desc->buffer, desc->size,
+                                       blob_type, sha1, hdr, &hdrlen);
+               if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix))
+                       return error("the patch applies to '%s' (%s), "
+                                    "which does not match the "
+                                    "current contents.",
+                                    name, sha1_to_hex(sha1));
+       }
+       else {
+               /* Otherwise, the old one must be empty. */
+               if (desc->size)
+                       return error("the patch applies to an empty "
+                                    "'%s' but it is not empty", name);
+       }
 
-               /* For now, we do not record post-image data in the patch,
-                * and require the object already present in the recipient's
-                * object database.
+       get_sha1_hex(patch->new_sha1_prefix, sha1);
+       if (!memcmp(sha1, null_sha1, 20)) {
+               free(desc->buffer);
+               desc->alloc = desc->size = 0;
+               desc->buffer = NULL;
+               return 0; /* deletion patch */
+       }
+
+       if (has_sha1_file(sha1)) {
+               /* We already have the postimage */
+               char type[10];
+               unsigned long size;
+
+               free(desc->buffer);
+               desc->buffer = read_sha1_file(sha1, type, &size);
+               if (!desc->buffer)
+                       return error("the necessary postimage %s for "
+                                    "'%s' cannot be read",
+                                    patch->new_sha1_prefix, name);
+               desc->alloc = desc->size = size;
+       }
+       else {
+               /* We have verified desc matches the preimage;
+                * apply the patch data to it, which is stored
+                * in the patch->fragments->{patch,size}.
                 */
-               if (desc->buffer) {
-                       free(desc->buffer);
-                       desc->alloc = desc->size = 0;
-               }
-               get_sha1_hex(patch->new_sha1_prefix, sha1);
-
-               if (memcmp(sha1, null_sha1, 20)) {
-                       char type[10];
-                       unsigned long size;
-
-                       desc->buffer = read_sha1_file(sha1, type, &size);
-                       if (!desc->buffer)
-                               return error("the necessary postimage %s for "
-                                            "'%s' does not exist",
-                                            patch->new_sha1_prefix, name);
-                       desc->alloc = desc->size = size;
-               }
+               if (apply_binary_fragment(desc, patch))
+                       return error("binary patch does not apply to '%s'",
+                                    name);
 
-               return 0;
+               /* verify that the result matches */
+               write_sha1_file_prepare(desc->buffer, desc->size, blob_type,
+                                       sha1, hdr, &hdrlen);
+               if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix))
+                       return error("binary patch to '%s' creates incorrect result", name);
        }
 
+       return 0;
+}
+
+static int apply_fragments(struct buffer_desc *desc, struct patch *patch)
+{
+       struct fragment *frag = patch->fragments;
+       const char *name = patch->old_name ? patch->old_name : patch->new_name;
+
+       if (patch->is_binary)
+               return apply_binary(desc, patch);
+
        while (frag) {
                if (apply_one_fragment(desc, frag) < 0)
                        return error("patch failed: %s:%ld",
@@ -1873,7 +2049,6 @@ static int use_patch(struct patch *p)
 
 static int apply_patch(int fd, const char *filename)
 {
-       int newfd;
        unsigned long offset, size;
        char *buffer = read_patch_file(fd, &size);
        struct patch *list = NULL, **listp = &list;
@@ -1904,12 +2079,11 @@ static int apply_patch(int fd, const char *filename)
                size -= nr;
        }
 
-       newfd = -1;
        if (whitespace_error && (new_whitespace == error_on_whitespace))
                apply = 0;
 
        write_index = check_index && apply;
-       if (write_index)
+       if (write_index && newfd < 0)
                newfd = hold_index_file_for_update(&cache_file, get_index_file());
        if (check_index) {
                if (read_cache() < 0)
@@ -1922,12 +2096,6 @@ static int apply_patch(int fd, const char *filename)
        if (apply)
                write_out_results(list, skipped_patch);
 
-       if (write_index) {
-               if (write_cache(newfd, active_cache, active_nr) ||
-                   commit_index_file(&cache_file))
-                       die("Unable to write new cachefile");
-       }
-
        if (show_index_info)
                show_index_list(list);
 
@@ -1990,7 +2158,8 @@ int main(int argc, char **argv)
                        diffstat = 1;
                        continue;
                }
-               if (!strcmp(arg, "--allow-binary-replacement")) {
+               if (!strcmp(arg, "--allow-binary-replacement") ||
+                   !strcmp(arg, "--binary")) {
                        allow_binary_replacement = 1;
                        continue;
                }
@@ -2085,5 +2254,12 @@ int main(int argc, char **argv)
                                whitespace_error == 1 ? "" : "s",
                                whitespace_error == 1 ? "s" : "");
        }
+
+       if (write_index) {
+               if (write_cache(newfd, active_cache, active_nr) ||
+                   commit_index_file(&cache_file))
+                       die("Unable to write new cachefile");
+       }
+
        return 0;
 }
diff --git a/base85.c b/base85.c
new file mode 100644 (file)
index 0000000..a9e97f8
--- /dev/null
+++ b/base85.c
@@ -0,0 +1,140 @@
+#include "cache.h"
+
+#undef DEBUG_85
+
+#ifdef DEBUG_85
+#define say(a) fprintf(stderr, a)
+#define say1(a,b) fprintf(stderr, a, b)
+#define say2(a,b,c) fprintf(stderr, a, b, c)
+#else
+#define say(a) do {} while(0)
+#define say1(a,b) do {} while(0)
+#define say2(a,b,c) do {} while(0)
+#endif
+
+static const char en85[] = {
+       '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+       'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
+       'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
+       'U', 'V', 'W', 'X', 'Y', 'Z',
+       'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
+       'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
+       'u', 'v', 'w', 'x', 'y', 'z',
+       '!', '#', '$', '%', '&', '(', ')', '*', '+', '-',
+       ';', '<', '=', '>', '?', '@', '^', '_', '`', '{',
+       '|', '}', '~'
+};
+
+static char de85[256];
+static void prep_base85(void)
+{
+       int i;
+       if (de85['Z'])
+               return;
+       for (i = 0; i < ARRAY_SIZE(en85); i++) {
+               int ch = en85[i];
+               de85[ch] = i + 1;
+       }
+}
+
+int decode_85(char *dst, char *buffer, int len)
+{
+       prep_base85();
+
+       say2("decode 85 <%.*s>", len/4*5, buffer);
+       while (len) {
+               unsigned acc = 0;
+               int de, cnt = 4;
+               unsigned char ch;
+               do {
+                       ch = *buffer++;
+                       de = de85[ch];
+                       if (--de < 0)
+                               return error("invalid base85 alphabet %c", ch);
+                       acc = acc * 85 + de;
+               } while (--cnt);
+               ch = *buffer++;
+               de = de85[ch];
+               if (--de < 0)
+                       return error("invalid base85 alphabet %c", ch);
+               /*
+                * Detect overflow.  The largest
+                * 5-letter possible is "|NsC0" to
+                * encode 0xffffffff, and "|NsC" gives
+                * 0x03030303 at this point (i.e.
+                * 0xffffffff = 0x03030303 * 85).
+                */
+               if (0x03030303 < acc ||
+                   0xffffffff - de < (acc *= 85))
+                       error("invalid base85 sequence %.5s", buffer-5);
+               acc += de;
+               say1(" %08x", acc);
+
+               cnt = (len < 4) ? len : 4;
+               len -= cnt;
+               do {
+                       acc = (acc << 8) | (acc >> 24);
+                       *dst++ = acc;
+               } while (--cnt);
+       }
+       say("\n");
+
+       return 0;
+}
+
+void encode_85(char *buf, unsigned char *data, int bytes)
+{
+       prep_base85();
+
+       say("encode 85");
+       while (bytes) {
+               unsigned acc = 0;
+               int cnt;
+               for (cnt = 24; cnt >= 0; cnt -= 8) {
+                       int ch = *data++;
+                       acc |= ch << cnt;
+                       if (--bytes == 0)
+                               break;
+               }
+               say1(" %08x", acc);
+               for (cnt = 4; cnt >= 0; cnt--) {
+                       int val = acc % 85;
+                       acc /= 85;
+                       buf[cnt] = en85[val];
+               }
+               buf += 5;
+       }
+       say("\n");
+
+       *buf = 0;
+}
+
+#ifdef DEBUG_85
+int main(int ac, char **av)
+{
+       char buf[1024];
+
+       if (!strcmp(av[1], "-e")) {
+               int len = strlen(av[2]);
+               encode_85(buf, av[2], len);
+               if (len <= 26) len = len + 'A' - 1;
+               else len = len + 'a' - 26 + 1;
+               printf("encoded: %c%s\n", len, buf);
+               return 0;
+       }
+       if (!strcmp(av[1], "-d")) {
+               int len = *av[2];
+               if ('A' <= len && len <= 'Z') len = len - 'A' + 1;
+               else len = len - 'a' + 26 + 1;
+               decode_85(buf, av[2]+1, len);
+               printf("decoded: %.*s\n", len, buf);
+               return 0;
+       }
+       if (!strcmp(av[1], "-t")) {
+               char t[4] = { -1,-1,-1,-1 };
+               encode_85(buf, t, 4);
+               printf("encoded: D%s\n", buf);
+               return 0;
+       }
+}
+#endif
diff --git a/blame.c b/blame.c
index 07d2d27..99ceea8 100644 (file)
--- a/blame.c
+++ b/blame.c
@@ -515,9 +515,9 @@ static int compare_tree_path(struct rev_info* revs,
        paths[1] = NULL;
 
        diff_tree_setup_paths(get_pathspec(revs->prefix, paths),
-                             &revs->diffopt);
+                             &revs->pruning);
        ret = rev_compare_tree(revs, c1->tree, c2->tree);
-       diff_tree_release_paths(&revs->diffopt);
+       diff_tree_release_paths(&revs->pruning);
        return ret;
 }
 
@@ -531,9 +531,9 @@ static int same_tree_as_empty_path(struct rev_info *revs, struct tree* t1,
        paths[1] = NULL;
 
        diff_tree_setup_paths(get_pathspec(revs->prefix, paths),
-                             &revs->diffopt);
+                             &revs->pruning);
        ret = rev_same_tree_as_empty(revs, t1);
-       diff_tree_release_paths(&revs->diffopt);
+       diff_tree_release_paths(&revs->pruning);
        return ret;
 }
 
@@ -834,7 +834,7 @@ int main(int argc, const char **argv)
 
        args[0] = filename;
        args[1] = NULL;
-       diff_tree_setup_paths(args, &rev.diffopt);
+       diff_tree_setup_paths(args, &rev.pruning);
        prepare_revision_walk(&rev);
        process_commits(&rev, filename, &initial);
 
diff --git a/builtin-count.c b/builtin-count.c
new file mode 100644 (file)
index 0000000..5ee72df
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * Builtin "git count-objects".
+ *
+ * Copyright (c) 2006 Junio C Hamano
+ */
+
+#include "cache.h"
+#include "builtin.h"
+
+static const char count_objects_usage[] = "git-count-objects [-v]";
+
+static void count_objects(DIR *d, char *path, int len, int verbose,
+                         unsigned long *loose,
+                         unsigned long *loose_size,
+                         unsigned long *packed_loose,
+                         unsigned long *garbage)
+{
+       struct dirent *ent;
+       while ((ent = readdir(d)) != NULL) {
+               char hex[41];
+               unsigned char sha1[20];
+               const char *cp;
+               int bad = 0;
+
+               if ((ent->d_name[0] == '.') &&
+                   (ent->d_name[1] == 0 ||
+                    ((ent->d_name[1] == '.') && (ent->d_name[2] == 0))))
+                       continue;
+               for (cp = ent->d_name; *cp; cp++) {
+                       int ch = *cp;
+                       if (('0' <= ch && ch <= '9') ||
+                           ('a' <= ch && ch <= 'f'))
+                               continue;
+                       bad = 1;
+                       break;
+               }
+               if (cp - ent->d_name != 38)
+                       bad = 1;
+               else {
+                       struct stat st;
+                       memcpy(path + len + 3, ent->d_name, 38);
+                       path[len + 2] = '/';
+                       path[len + 41] = 0;
+                       if (lstat(path, &st) || !S_ISREG(st.st_mode))
+                               bad = 1;
+                       else
+                               (*loose_size) += st.st_blocks;
+               }
+               if (bad) {
+                       if (verbose) {
+                               error("garbage found: %.*s/%s",
+                                     len + 2, path, ent->d_name);
+                               (*garbage)++;
+                       }
+                       continue;
+               }
+               (*loose)++;
+               if (!verbose)
+                       continue;
+               memcpy(hex, path+len, 2);
+               memcpy(hex+2, ent->d_name, 38);
+               hex[40] = 0;
+               if (get_sha1_hex(hex, sha1))
+                       die("internal error");
+               if (has_sha1_pack(sha1))
+                       (*packed_loose)++;
+       }
+}
+
+int cmd_count_objects(int ac, const char **av, char **ep)
+{
+       int i;
+       int verbose = 0;
+       const char *objdir = get_object_directory();
+       int len = strlen(objdir);
+       char *path = xmalloc(len + 50);
+       unsigned long loose = 0, packed = 0, packed_loose = 0, garbage = 0;
+       unsigned long loose_size = 0;
+
+       for (i = 1; i < ac; i++) {
+               const char *arg = av[i];
+               if (*arg != '-')
+                       break;
+               else if (!strcmp(arg, "-v"))
+                       verbose = 1;
+               else
+                       usage(count_objects_usage);
+       }
+
+       /* we do not take arguments other than flags for now */
+       if (i < ac)
+               usage(count_objects_usage);
+       memcpy(path, objdir, len);
+       if (len && objdir[len-1] != '/')
+               path[len++] = '/';
+       for (i = 0; i < 256; i++) {
+               DIR *d;
+               sprintf(path + len, "%02x", i);
+               d = opendir(path);
+               if (!d)
+                       continue;
+               count_objects(d, path, len, verbose,
+                             &loose, &loose_size, &packed_loose, &garbage);
+               closedir(d);
+       }
+       if (verbose) {
+               struct packed_git *p;
+               if (!packed_git)
+                       prepare_packed_git();
+               for (p = packed_git; p; p = p->next) {
+                       if (!p->pack_local)
+                               continue;
+                       packed += num_packed_objects(p);
+               }
+               printf("count: %lu\n", loose);
+               printf("size: %lu\n", loose_size / 2);
+               printf("in-pack: %lu\n", packed);
+               printf("prune-packable: %lu\n", packed_loose);
+               printf("garbage: %lu\n", garbage);
+       }
+       else
+               printf("%lu objects, %lu kilobytes\n",
+                      loose, loose_size / 2);
+       return 0;
+}
diff --git a/builtin-diff.c b/builtin-diff.c
new file mode 100644 (file)
index 0000000..b0c9cad
--- /dev/null
@@ -0,0 +1,370 @@
+/*
+ * Builtin "git diff"
+ *
+ * Copyright (c) 2006 Junio C Hamano
+ */
+#include "cache.h"
+#include "commit.h"
+#include "blob.h"
+#include "tag.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "log-tree.h"
+#include "builtin.h"
+
+/* NEEDSWORK: struct object has place for name but we _do_
+ * know mode when we extracted the blob out of a tree, which
+ * we currently lose.
+ */
+struct blobinfo {
+       unsigned char sha1[20];
+       const char *name;
+};
+
+static const char builtin_diff_usage[] =
+"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 if (!strcmp(arg, "--raw"))
+                       revs->diffopt.output_format = DIFF_FORMAT_RAW;
+               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_objects ||
+           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;
+       /*
+        * Backward compatibility wart - "diff-files -s" used to
+        * defeat the common diff option "-s" which asked for
+        * DIFF_FORMAT_NO_OUTPUT.
+        */
+       if (revs->diffopt.output_format == DIFF_FORMAT_NO_OUTPUT)
+               revs->diffopt.output_format = DIFF_FORMAT_RAW;
+       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,
+                        const unsigned char *new_sha1,
+                        const char *old_name,
+                        const char *new_name)
+{
+       struct diff_filespec *one, *two;
+
+       if (memcmp(null_sha1, old_sha1, 20) &&
+           memcmp(null_sha1, new_sha1, 20) &&
+           !memcmp(old_sha1, new_sha1, 20))
+               return;
+
+       if (opt->reverse_diff) {
+               unsigned tmp;
+               const unsigned char *tmp_u;
+               const char *tmp_c;
+               tmp = old_mode; old_mode = new_mode; new_mode = tmp;
+               tmp_u = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_u;
+               tmp_c = old_name; old_name = new_name; new_name = tmp_c;
+       }
+       one = alloc_filespec(old_name);
+       two = alloc_filespec(new_name);
+       fill_filespec(one, old_sha1, old_mode);
+       fill_filespec(two, new_sha1, new_mode);
+
+       /* NEEDSWORK: shouldn't this part of diffopt??? */
+       diff_queue(&diff_queued_diff, one, two);
+}
+
+static int builtin_diff_b_f(struct rev_info *revs,
+                           int argc, const char **argv,
+                           struct blobinfo *blob,
+                           const char *path)
+{
+       /* Blob vs file in the working tree*/
+       struct stat st;
+
+       while (1 < argc) {
+               const char *arg = argv[1];
+               if (!strcmp(arg, "--raw"))
+                       revs->diffopt.output_format = DIFF_FORMAT_RAW;
+               else
+                       usage(builtin_diff_usage);
+               argv++; argc--;
+       }
+       if (lstat(path, &st))
+               die("'%s': %s", path, strerror(errno));
+       if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)))
+               die("'%s': not a regular file or symlink", path);
+       stuff_change(&revs->diffopt,
+                    canon_mode(st.st_mode), canon_mode(st.st_mode),
+                    blob[0].sha1, null_sha1,
+                    blob[0].name, path);
+       diffcore_std(&revs->diffopt);
+       diff_flush(&revs->diffopt);
+       return 0;
+}
+
+static int builtin_diff_blobs(struct rev_info *revs,
+                             int argc, const char **argv,
+                             struct blobinfo *blob)
+{
+       /* Blobs: the arguments are reversed when setup_revisions()
+        * picked them up.
+        */
+       unsigned mode = canon_mode(S_IFREG | 0644);
+
+       while (1 < argc) {
+               const char *arg = argv[1];
+               if (!strcmp(arg, "--raw"))
+                       revs->diffopt.output_format = DIFF_FORMAT_RAW;
+               else
+                       usage(builtin_diff_usage);
+               argv++; argc--;
+       }
+       stuff_change(&revs->diffopt,
+                    mode, mode,
+                    blob[1].sha1, blob[0].sha1,
+                    blob[0].name, blob[0].name);
+       diffcore_std(&revs->diffopt);
+       diff_flush(&revs->diffopt);
+       return 0;
+}
+
+static int builtin_diff_index(struct rev_info *revs,
+                             int argc, const char **argv)
+{
+       int cached = 0;
+       while (1 < argc) {
+               const char *arg = argv[1];
+               if (!strcmp(arg, "--cached"))
+                       cached = 1;
+               else if (!strcmp(arg, "--raw"))
+                       revs->diffopt.output_format = DIFF_FORMAT_RAW;
+               else
+                       usage(builtin_diff_usage);
+               argv++; argc--;
+       }
+       /*
+        * Make sure there is one revision (i.e. pending object),
+        * and there is no revision filtering parameters.
+        */
+       if (!revs->pending_objects || revs->pending_objects->next ||
+           revs->max_count != -1 || revs->min_age != -1 ||
+           revs->max_age != -1)
+               usage(builtin_diff_usage);
+       return run_diff_index(revs, cached);
+}
+
+static int builtin_diff_tree(struct rev_info *revs,
+                            int argc, const char **argv,
+                            struct object_list *ent)
+{
+       const unsigned char *(sha1[2]);
+       int swap = 1;
+       while (1 < argc) {
+               const char *arg = argv[1];
+               if (!strcmp(arg, "--raw"))
+                       revs->diffopt.output_format = DIFF_FORMAT_RAW;
+               else
+                       usage(builtin_diff_usage);
+               argv++; argc--;
+       }
+
+       /* We saw two trees, ent[0] and ent[1].
+        * unless ent[0] is unintesting, they are swapped
+        */
+       if (ent[0].item->flags & UNINTERESTING)
+               swap = 0;
+       sha1[swap] = ent[0].item->sha1;
+       sha1[1-swap] = ent[1].item->sha1;
+       diff_tree_sha1(sha1[0], sha1[1], "", &revs->diffopt);
+       log_tree_diff_flush(revs);
+       return 0;
+}
+
+static int builtin_diff_combined(struct rev_info *revs,
+                                int argc, const char **argv,
+                                struct object_list *ent,
+                                int ents)
+{
+       const unsigned char (*parent)[20];
+       int i;
+
+       while (1 < argc) {
+               const char *arg = argv[1];
+               if (!strcmp(arg, "--raw"))
+                       revs->diffopt.output_format = DIFF_FORMAT_RAW;
+               else
+                       usage(builtin_diff_usage);
+               argv++; argc--;
+       }
+       if (!revs->dense_combined_merges && !revs->combine_merges)
+               revs->dense_combined_merges = revs->combine_merges = 1;
+       parent = xmalloc(ents * sizeof(*parent));
+       /* Again, the revs are all reverse */
+       for (i = 0; i < ents; i++)
+               memcpy(parent + i, ent[ents - 1 - i].item->sha1, 20);
+       diff_tree_combined(parent[0], parent + 1, ents - 1,
+                          revs->dense_combined_merges, revs);
+       return 0;
+}
+
+static void add_head(struct rev_info *revs)
+{
+       unsigned char sha1[20];
+       struct object *obj;
+       if (get_sha1("HEAD", sha1))
+               return;
+       obj = parse_object(sha1);
+       if (!obj)
+               return;
+       add_object(obj, &revs->pending_objects, NULL, "HEAD");
+}
+
+int cmd_diff(int argc, const char **argv, char **envp)
+{
+       struct rev_info rev;
+       struct object_list *list, ent[100];
+       int ents = 0, blobs = 0, paths = 0;
+       const char *path = NULL;
+       struct blobinfo blob[2];
+
+       /*
+        * We could get N tree-ish in the rev.pending_objects list.
+        * Also there could be M blobs there, and P pathspecs.
+        *
+        * N=0, M=0:
+        *      cache vs files (diff-files)
+        * N=0, M=2:
+        *      compare two random blobs.  P must be zero.
+        * N=0, M=1, P=1:
+        *      compare a blob with a working tree file.
+        *
+        * N=1, M=0:
+        *      tree vs cache (diff-index --cached)
+        *
+        * N=2, M=0:
+        *      tree vs tree (diff-tree)
+        *
+        * Other cases are errors.
+        */
+
+       git_config(git_diff_config);
+       init_revisions(&rev);
+       rev.diffopt.output_format = DIFF_FORMAT_PATCH;
+
+       argc = setup_revisions(argc, argv, &rev, NULL);
+       /* Do we have --cached and not have a pending object, then
+        * default to HEAD by hand.  Eek.
+        */
+       if (!rev.pending_objects) {
+               int i;
+               for (i = 1; i < argc; i++) {
+                       const char *arg = argv[i];
+                       if (!strcmp(arg, "--"))
+                               break;
+                       else if (!strcmp(arg, "--cached")) {
+                               add_head(&rev);
+                               break;
+                       }
+               }
+       }
+
+       for (list = rev.pending_objects; list; list = list->next) {
+               struct object *obj = list->item;
+               const char *name = list->name;
+               int flags = (obj->flags & UNINTERESTING);
+               if (!obj->parsed)
+                       obj = parse_object(obj->sha1);
+               obj = deref_tag(obj, NULL, 0);
+               if (!obj)
+                       die("invalid object '%s' given.", name);
+               if (!strcmp(obj->type, commit_type))
+                       obj = &((struct commit *)obj)->tree->object;
+               if (!strcmp(obj->type, tree_type)) {
+                       if (ARRAY_SIZE(ent) <= ents)
+                               die("more than %d trees given: '%s'",
+                                   (int) ARRAY_SIZE(ent), name);
+                       obj->flags |= flags;
+                       ent[ents].item = obj;
+                       ent[ents].name = name;
+                       ents++;
+                       continue;
+               }
+               if (!strcmp(obj->type, blob_type)) {
+                       if (2 <= blobs)
+                               die("more than two blobs given: '%s'", name);
+                       memcpy(blob[blobs].sha1, obj->sha1, 20);
+                       blob[blobs].name = name;
+                       blobs++;
+                       continue;
+
+               }
+               die("unhandled object '%s' given.", name);
+       }
+       if (rev.prune_data) {
+               const char **pathspec = rev.prune_data;
+               while (*pathspec) {
+                       if (!path)
+                               path = *pathspec;
+                       paths++;
+                       pathspec++;
+               }
+       }
+
+       /*
+        * Now, do the arguments look reasonable?
+        */
+       if (!ents) {
+               switch (blobs) {
+               case 0:
+                       return builtin_diff_files(&rev, argc, argv);
+                       break;
+               case 1:
+                       if (paths != 1)
+                               usage(builtin_diff_usage);
+                       return 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);
+                       break;
+               default:
+                       usage(builtin_diff_usage);
+               }
+       }
+       else if (blobs)
+               usage(builtin_diff_usage);
+       else if (ents == 1)
+               return builtin_diff_index(&rev, argc, argv);
+       else if (ents == 2)
+               return builtin_diff_tree(&rev, argc, argv, ent);
+       else
+               return builtin_diff_combined(&rev, argc, argv, ent, ents);
+       usage(builtin_diff_usage);
+}
diff --git a/builtin-grep.c b/builtin-grep.c
new file mode 100644 (file)
index 0000000..66111de
--- /dev/null
@@ -0,0 +1,885 @@
+/*
+ * Builtin "git grep"
+ *
+ * Copyright (c) 2006 Junio C Hamano
+ */
+#include "cache.h"
+#include "blob.h"
+#include "tree.h"
+#include "commit.h"
+#include "tag.h"
+#include "tree-walk.h"
+#include "builtin.h"
+#include <regex.h>
+#include <fnmatch.h>
+#include <sys/wait.h>
+
+/*
+ * git grep pathspecs are somewhat different from diff-tree pathspecs;
+ * pathname wildcards are allowed.
+ */
+static int pathspec_matches(const char **paths, const char *name)
+{
+       int namelen, i;
+       if (!paths || !*paths)
+               return 1;
+       namelen = strlen(name);
+       for (i = 0; paths[i]; i++) {
+               const char *match = paths[i];
+               int matchlen = strlen(match);
+               const char *cp, *meta;
+
+               if ((matchlen <= namelen) &&
+                   !strncmp(name, match, matchlen) &&
+                   (match[matchlen-1] == '/' ||
+                    name[matchlen] == '\0' || name[matchlen] == '/'))
+                       return 1;
+               if (!fnmatch(match, name, 0))
+                       return 1;
+               if (name[namelen-1] != '/')
+                       continue;
+
+               /* We are being asked if the directory ("name") is worth
+                * descending into.
+                *
+                * Find the longest leading directory name that does
+                * not have metacharacter in the pathspec; the name
+                * we are looking at must overlap with that directory.
+                */
+               for (cp = match, meta = NULL; cp - match < matchlen; cp++) {
+                       char ch = *cp;
+                       if (ch == '*' || ch == '[' || ch == '?') {
+                               meta = cp;
+                               break;
+                       }
+               }
+               if (!meta)
+                       meta = cp; /* fully literal */
+
+               if (namelen <= meta - match) {
+                       /* Looking at "Documentation/" and
+                        * the pattern says "Documentation/howto/", or
+                        * "Documentation/diff*.txt".  The name we
+                        * have should match prefix.
+                        */
+                       if (!memcmp(match, name, namelen))
+                               return 1;
+                       continue;
+               }
+
+               if (meta - match < namelen) {
+                       /* Looking at "Documentation/howto/" and
+                        * the pattern says "Documentation/h*";
+                        * match up to "Do.../h"; this avoids descending
+                        * into "Documentation/technical/".
+                        */
+                       if (!memcmp(match, name, meta - match))
+                               return 1;
+                       continue;
+               }
+       }
+       return 0;
+}
+
+struct grep_pat {
+       struct grep_pat *next;
+       const char *origin;
+       int no;
+       const char *pattern;
+       regex_t regexp;
+};
+
+struct grep_opt {
+       struct grep_pat *pattern_list;
+       struct grep_pat **pattern_tail;
+       regex_t regexp;
+       unsigned linenum:1;
+       unsigned invert:1;
+       unsigned name_only:1;
+       unsigned unmatch_name_only:1;
+       unsigned count:1;
+       unsigned word_regexp:1;
+       unsigned fixed:1;
+#define GREP_BINARY_DEFAULT    0
+#define GREP_BINARY_NOMATCH    1
+#define GREP_BINARY_TEXT       2
+       unsigned binary:2;
+       int regflags;
+       unsigned pre_context;
+       unsigned post_context;
+};
+
+static void add_pattern(struct grep_opt *opt, const char *pat,
+                       const char *origin, int no)
+{
+       struct grep_pat *p = xcalloc(1, sizeof(*p));
+       p->pattern = pat;
+       p->origin = origin;
+       p->no = no;
+       *opt->pattern_tail = p;
+       opt->pattern_tail = &p->next;
+       p->next = NULL;
+}
+
+static void compile_patterns(struct grep_opt *opt)
+{
+       struct grep_pat *p;
+       for (p = opt->pattern_list; p; p = p->next) {
+               int err = regcomp(&p->regexp, p->pattern, opt->regflags);
+               if (err) {
+                       char errbuf[1024];
+                       char where[1024];
+                       if (p->no)
+                               sprintf(where, "In '%s' at %d, ",
+                                       p->origin, p->no);
+                       else if (p->origin)
+                               sprintf(where, "%s, ", p->origin);
+                       else
+                               where[0] = 0;
+                       regerror(err, &p->regexp, errbuf, 1024);
+                       regfree(&p->regexp);
+                       die("%s'%s': %s", where, p->pattern, errbuf);
+               }
+       }
+}
+
+static char *end_of_line(char *cp, unsigned long *left)
+{
+       unsigned long l = *left;
+       while (l && *cp != '\n') {
+               l--;
+               cp++;
+       }
+       *left = l;
+       return cp;
+}
+
+static int word_char(char ch)
+{
+       return isalnum(ch) || ch == '_';
+}
+
+static void show_line(struct grep_opt *opt, const char *bol, const char *eol,
+                     const char *name, unsigned lno, char sign)
+{
+       printf("%s%c", name, sign);
+       if (opt->linenum)
+               printf("%d%c", lno, sign);
+       printf("%.*s\n", (int)(eol-bol), bol);
+}
+
+/*
+ * NEEDSWORK: share code with diff.c
+ */
+#define FIRST_FEW_BYTES 8000
+static int buffer_is_binary(const char *ptr, unsigned long size)
+{
+       if (FIRST_FEW_BYTES < size)
+               size = FIRST_FEW_BYTES;
+       if (memchr(ptr, 0, size))
+               return 1;
+       return 0;
+}
+
+static int fixmatch(const char *pattern, char *line, regmatch_t *match)
+{
+       char *hit = strstr(line, pattern);
+       if (!hit) {
+               match->rm_so = match->rm_eo = -1;
+               return REG_NOMATCH;
+       }
+       else {
+               match->rm_so = hit - line;
+               match->rm_eo = match->rm_so + strlen(pattern);
+               return 0;
+       }
+}
+
+static int grep_buffer(struct grep_opt *opt, const char *name,
+                      char *buf, unsigned long size)
+{
+       char *bol = buf;
+       unsigned long left = size;
+       unsigned lno = 1;
+       struct pre_context_line {
+               char *bol;
+               char *eol;
+       } *prev = NULL, *pcl;
+       unsigned last_hit = 0;
+       unsigned last_shown = 0;
+       int binary_match_only = 0;
+       const char *hunk_mark = "";
+       unsigned count = 0;
+
+       if (buffer_is_binary(buf, size)) {
+               switch (opt->binary) {
+               case GREP_BINARY_DEFAULT:
+                       binary_match_only = 1;
+                       break;
+               case GREP_BINARY_NOMATCH:
+                       return 0; /* Assume unmatch */
+                       break;
+               default:
+                       break;
+               }
+       }
+
+       if (opt->pre_context)
+               prev = xcalloc(opt->pre_context, sizeof(*prev));
+       if (opt->pre_context || opt->post_context)
+               hunk_mark = "--\n";
+
+       while (left) {
+               regmatch_t pmatch[10];
+               char *eol, ch;
+               int hit = 0;
+               struct grep_pat *p;
+
+               eol = end_of_line(bol, &left);
+               ch = *eol;
+               *eol = 0;
+
+               for (p = opt->pattern_list; p; p = p->next) {
+                       if (!opt->fixed) {
+                               regex_t *exp = &p->regexp;
+                               hit = !regexec(exp, bol, ARRAY_SIZE(pmatch),
+                                              pmatch, 0);
+                       }
+                       else {
+                               hit = !fixmatch(p->pattern, bol, pmatch);
+                       }
+
+                       if (hit && opt->word_regexp) {
+                               /* Match beginning must be either
+                                * beginning of the line, or at word
+                                * boundary (i.e. the last char must
+                                * not be alnum or underscore).
+                                */
+                               if ((pmatch[0].rm_so < 0) ||
+                                   (eol - bol) <= pmatch[0].rm_so ||
+                                   (pmatch[0].rm_eo < 0) ||
+                                   (eol - bol) < pmatch[0].rm_eo)
+                                       die("regexp returned nonsense");
+                               if (pmatch[0].rm_so != 0 &&
+                                   word_char(bol[pmatch[0].rm_so-1]))
+                                       hit = 0;
+                               if (pmatch[0].rm_eo != (eol-bol) &&
+                                   word_char(bol[pmatch[0].rm_eo]))
+                                       hit = 0;
+                       }
+                       if (hit)
+                               break;
+               }
+               /* "grep -v -e foo -e bla" should list lines
+                * that do not have either, so inversion should
+                * be done outside.
+                */
+               if (opt->invert)
+                       hit = !hit;
+               if (opt->unmatch_name_only) {
+                       if (hit)
+                               return 0;
+                       goto next_line;
+               }
+               if (hit) {
+                       count++;
+                       if (binary_match_only) {
+                               printf("Binary file %s matches\n", name);
+                               return 1;
+                       }
+                       if (opt->name_only) {
+                               printf("%s\n", name);
+                               return 1;
+                       }
+                       /* Hit at this line.  If we haven't shown the
+                        * pre-context lines, we would need to show them.
+                        * When asked to do "count", this still show
+                        * the context which is nonsense, but the user
+                        * deserves to get that ;-).
+                        */
+                       if (opt->pre_context) {
+                               unsigned from;
+                               if (opt->pre_context < lno)
+                                       from = lno - opt->pre_context;
+                               else
+                                       from = 1;
+                               if (from <= last_shown)
+                                       from = last_shown + 1;
+                               if (last_shown && from != last_shown + 1)
+                                       printf(hunk_mark);
+                               while (from < lno) {
+                                       pcl = &prev[lno-from-1];
+                                       show_line(opt, pcl->bol, pcl->eol,
+                                                 name, from, '-');
+                                       from++;
+                               }
+                               last_shown = lno-1;
+                       }
+                       if (last_shown && lno != last_shown + 1)
+                               printf(hunk_mark);
+                       if (!opt->count)
+                               show_line(opt, bol, eol, name, lno, ':');
+                       last_shown = last_hit = lno;
+               }
+               else if (last_hit &&
+                        lno <= last_hit + opt->post_context) {
+                       /* If the last hit is within the post context,
+                        * we need to show this line.
+                        */
+                       if (last_shown && lno != last_shown + 1)
+                               printf(hunk_mark);
+                       show_line(opt, bol, eol, name, lno, '-');
+                       last_shown = lno;
+               }
+               if (opt->pre_context) {
+                       memmove(prev+1, prev,
+                               (opt->pre_context-1) * sizeof(*prev));
+                       prev->bol = bol;
+                       prev->eol = eol;
+               }
+
+       next_line:
+               *eol = ch;
+               bol = eol + 1;
+               if (!left)
+                       break;
+               left--;
+               lno++;
+       }
+
+       if (opt->unmatch_name_only) {
+               /* We did not see any hit, so we want to show this */
+               printf("%s\n", name);
+               return 1;
+       }
+
+       /* NEEDSWORK:
+        * The real "grep -c foo *.c" gives many "bar.c:0" lines,
+        * which feels mostly useless but sometimes useful.  Maybe
+        * make it another option?  For now suppress them.
+        */
+       if (opt->count && count)
+               printf("%s:%u\n", name, count);
+       return !!last_hit;
+}
+
+static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, const char *name)
+{
+       unsigned long size;
+       char *data;
+       char type[20];
+       int hit;
+       data = read_sha1_file(sha1, type, &size);
+       if (!data) {
+               error("'%s': unable to read %s", name, sha1_to_hex(sha1));
+               return 0;
+       }
+       hit = grep_buffer(opt, name, data, size);
+       free(data);
+       return hit;
+}
+
+static int grep_file(struct grep_opt *opt, const char *filename)
+{
+       struct stat st;
+       int i;
+       char *data;
+       if (lstat(filename, &st) < 0) {
+       err_ret:
+               if (errno != ENOENT)
+                       error("'%s': %s", filename, strerror(errno));
+               return 0;
+       }
+       if (!st.st_size)
+               return 0; /* empty file -- no grep hit */
+       if (!S_ISREG(st.st_mode))
+               return 0;
+       i = open(filename, O_RDONLY);
+       if (i < 0)
+               goto err_ret;
+       data = xmalloc(st.st_size + 1);
+       if (st.st_size != xread(i, data, st.st_size)) {
+               error("'%s': short read %s", filename, strerror(errno));
+               close(i);
+               free(data);
+               return 0;
+       }
+       close(i);
+       i = grep_buffer(opt, filename, data, st.st_size);
+       free(data);
+       return i;
+}
+
+static int exec_grep(int argc, const char **argv)
+{
+       pid_t pid;
+       int status;
+
+       argv[argc] = NULL;
+       pid = fork();
+       if (pid < 0)
+               return pid;
+       if (!pid) {
+               execvp("grep", (char **) argv);
+               exit(255);
+       }
+       while (waitpid(pid, &status, 0) < 0) {
+               if (errno == EINTR)
+                       continue;
+               return -1;
+       }
+       if (WIFEXITED(status)) {
+               if (!WEXITSTATUS(status))
+                       return 1;
+               return 0;
+       }
+       return -1;
+}
+
+#define MAXARGS 1000
+#define ARGBUF 4096
+#define push_arg(a) do { \
+       if (nr < MAXARGS) argv[nr++] = (a); \
+       else die("maximum number of args exceeded"); \
+       } while (0)
+
+static int external_grep(struct grep_opt *opt, const char **paths, int cached)
+{
+       int i, nr, argc, hit, len;
+       const char *argv[MAXARGS+1];
+       char randarg[ARGBUF];
+       char *argptr = randarg;
+       struct grep_pat *p;
+
+       len = nr = 0;
+       push_arg("grep");
+       push_arg("-H");
+       if (opt->fixed)
+               push_arg("-F");
+       if (opt->linenum)
+               push_arg("-n");
+       if (opt->regflags & REG_EXTENDED)
+               push_arg("-E");
+       if (opt->word_regexp)
+               push_arg("-w");
+       if (opt->name_only)
+               push_arg("-l");
+       if (opt->unmatch_name_only)
+               push_arg("-L");
+       if (opt->count)
+               push_arg("-c");
+       if (opt->post_context || opt->pre_context) {
+               if (opt->post_context != opt->pre_context) {
+                       if (opt->pre_context) {
+                               push_arg("-B");
+                               len += snprintf(argptr, sizeof(randarg)-len,
+                                               "%u", opt->pre_context);
+                               if (sizeof(randarg) <= len)
+                                       die("maximum length of args exceeded");
+                               push_arg(argptr);
+                               argptr += len;
+                       }
+                       if (opt->post_context) {
+                               push_arg("-A");
+                               len += snprintf(argptr, sizeof(randarg)-len,
+                                               "%u", opt->post_context);
+                               if (sizeof(randarg) <= len)
+                                       die("maximum length of args exceeded");
+                               push_arg(argptr);
+                               argptr += len;
+                       }
+               }
+               else {
+                       push_arg("-C");
+                       len += snprintf(argptr, sizeof(randarg)-len,
+                                       "%u", opt->post_context);
+                       if (sizeof(randarg) <= len)
+                               die("maximum length of args exceeded");
+                       push_arg(argptr);
+                       argptr += len;
+               }
+       }
+       for (p = opt->pattern_list; p; p = p->next) {
+               push_arg("-e");
+               push_arg(p->pattern);
+       }
+       push_arg("--");
+
+       hit = 0;
+       argc = nr;
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               if (ce_stage(ce) || !S_ISREG(ntohl(ce->ce_mode)))
+                       continue;
+               if (!pathspec_matches(paths, ce->name))
+                       continue;
+               argv[argc++] = ce->name;
+               if (argc < MAXARGS)
+                       continue;
+               hit += exec_grep(argc, argv);
+               argc = nr;
+       }
+       if (argc > nr)
+               hit += exec_grep(argc, argv);
+       return 0;
+}
+
+static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
+{
+       int hit = 0;
+       int nr;
+       read_cache();
+
+#ifdef __unix__
+       /*
+        * Use the external "grep" command for the case where
+        * we grep through the checked-out files. It tends to
+        * be a lot more optimized
+        */
+       if (!cached) {
+               hit = external_grep(opt, paths, cached);
+               if (hit >= 0)
+                       return hit;
+       }
+#endif
+
+       for (nr = 0; nr < active_nr; nr++) {
+               struct cache_entry *ce = active_cache[nr];
+               if (ce_stage(ce) || !S_ISREG(ntohl(ce->ce_mode)))
+                       continue;
+               if (!pathspec_matches(paths, ce->name))
+                       continue;
+               if (cached)
+                       hit |= grep_sha1(opt, ce->sha1, ce->name);
+               else
+                       hit |= grep_file(opt, ce->name);
+       }
+       return hit;
+}
+
+static int grep_tree(struct grep_opt *opt, const char **paths,
+                    struct tree_desc *tree,
+                    const char *tree_name, const char *base)
+{
+       unsigned mode;
+       int len;
+       int hit = 0;
+       const char *path;
+       const unsigned char *sha1;
+       char *down;
+       char *path_buf = xmalloc(PATH_MAX + strlen(tree_name) + 100);
+
+       if (tree_name[0]) {
+               int offset = sprintf(path_buf, "%s:", tree_name);
+               down = path_buf + offset;
+               strcat(down, base);
+       }
+       else {
+               down = path_buf;
+               strcpy(down, base);
+       }
+       len = strlen(path_buf);
+
+       while (tree->size) {
+               int pathlen;
+               sha1 = tree_entry_extract(tree, &path, &mode);
+               pathlen = strlen(path);
+               strcpy(path_buf + len, path);
+
+               if (S_ISDIR(mode))
+                       /* Match "abc/" against pathspec to
+                        * decide if we want to descend into "abc"
+                        * directory.
+                        */
+                       strcpy(path_buf + len + pathlen, "/");
+
+               if (!pathspec_matches(paths, down))
+                       ;
+               else if (S_ISREG(mode))
+                       hit |= grep_sha1(opt, sha1, path_buf);
+               else if (S_ISDIR(mode)) {
+                       char type[20];
+                       struct tree_desc sub;
+                       void *data;
+                       data = read_sha1_file(sha1, type, &sub.size);
+                       if (!data)
+                               die("unable to read tree (%s)",
+                                   sha1_to_hex(sha1));
+                       sub.buf = data;
+                       hit |= grep_tree(opt, paths, &sub, tree_name, down);
+                       free(data);
+               }
+               update_tree_entry(tree);
+       }
+       return hit;
+}
+
+static int grep_object(struct grep_opt *opt, const char **paths,
+                      struct object *obj, const char *name)
+{
+       if (!strcmp(obj->type, blob_type))
+               return grep_sha1(opt, obj->sha1, name);
+       if (!strcmp(obj->type, commit_type) ||
+           !strcmp(obj->type, tree_type)) {
+               struct tree_desc tree;
+               void *data;
+               int hit;
+               data = read_object_with_reference(obj->sha1, tree_type,
+                                                 &tree.size, NULL);
+               if (!data)
+                       die("unable to read tree (%s)", sha1_to_hex(obj->sha1));
+               tree.buf = data;
+               hit = grep_tree(opt, paths, &tree, name, "");
+               free(data);
+               return hit;
+       }
+       die("unable to grep from object of type %s", obj->type);
+}
+
+static const char builtin_grep_usage[] =
+"git-grep <option>* <rev>* [-e] <pattern> [<path>...]";
+
+int cmd_grep(int argc, const char **argv, char **envp)
+{
+       int hit = 0;
+       int cached = 0;
+       int seen_dashdash = 0;
+       struct grep_opt opt;
+       struct object_list *list, **tail, *object_list = NULL;
+       const char *prefix = setup_git_directory();
+       const char **paths = NULL;
+       int i;
+
+       memset(&opt, 0, sizeof(opt));
+       opt.pattern_tail = &opt.pattern_list;
+       opt.regflags = REG_NEWLINE;
+
+       /*
+        * If there is no -- then the paths must exist in the working
+        * tree.  If there is no explicit pattern specified with -e or
+        * -f, we take the first unrecognized non option to be the
+        * pattern, but then what follows it must be zero or more
+        * valid refs up to the -- (if exists), and then existing
+        * paths.  If there is an explicit pattern, then the first
+        * unrecocnized non option is the beginning of the refs list
+        * that continues up to the -- (if exists), and then paths.
+        */
+
+       tail = &object_list;
+       while (1 < argc) {
+               const char *arg = argv[1];
+               argc--; argv++;
+               if (!strcmp("--cached", arg)) {
+                       cached = 1;
+                       continue;
+               }
+               if (!strcmp("-a", arg) ||
+                   !strcmp("--text", arg)) {
+                       opt.binary = GREP_BINARY_TEXT;
+                       continue;
+               }
+               if (!strcmp("-i", arg) ||
+                   !strcmp("--ignore-case", arg)) {
+                       opt.regflags |= REG_ICASE;
+                       continue;
+               }
+               if (!strcmp("-I", arg)) {
+                       opt.binary = GREP_BINARY_NOMATCH;
+                       continue;
+               }
+               if (!strcmp("-v", arg) ||
+                   !strcmp("--invert-match", arg)) {
+                       opt.invert = 1;
+                       continue;
+               }
+               if (!strcmp("-E", arg) ||
+                   !strcmp("--extended-regexp", arg)) {
+                       opt.regflags |= REG_EXTENDED;
+                       continue;
+               }
+               if (!strcmp("-F", arg) ||
+                   !strcmp("--fixed-strings", arg)) {
+                       opt.fixed = 1;
+                       continue;
+               }
+               if (!strcmp("-G", arg) ||
+                   !strcmp("--basic-regexp", arg)) {
+                       opt.regflags &= ~REG_EXTENDED;
+                       continue;
+               }
+               if (!strcmp("-n", arg)) {
+                       opt.linenum = 1;
+                       continue;
+               }
+               if (!strcmp("-H", arg)) {
+                       /* We always show the pathname, so this
+                        * is a noop.
+                        */
+                       continue;
+               }
+               if (!strcmp("-l", arg) ||
+                   !strcmp("--files-with-matches", arg)) {
+                       opt.name_only = 1;
+                       continue;
+               }
+               if (!strcmp("-L", arg) ||
+                   !strcmp("--files-without-match", arg)) {
+                       opt.unmatch_name_only = 1;
+                       continue;
+               }
+               if (!strcmp("-c", arg) ||
+                   !strcmp("--count", arg)) {
+                       opt.count = 1;
+                       continue;
+               }
+               if (!strcmp("-w", arg) ||
+                   !strcmp("--word-regexp", arg)) {
+                       opt.word_regexp = 1;
+                       continue;
+               }
+               if (!strncmp("-A", arg, 2) ||
+                   !strncmp("-B", arg, 2) ||
+                   !strncmp("-C", arg, 2) ||
+                   (arg[0] == '-' && '1' <= arg[1] && arg[1] <= '9')) {
+                       unsigned num;
+                       const char *scan;
+                       switch (arg[1]) {
+                       case 'A': case 'B': case 'C':
+                               if (!arg[2]) {
+                                       if (argc <= 1)
+                                               usage(builtin_grep_usage);
+                                       scan = *++argv;
+                                       argc--;
+                               }
+                               else
+                                       scan = arg + 2;
+                               break;
+                       default:
+                               scan = arg + 1;
+                               break;
+                       }
+                       if (sscanf(scan, "%u", &num) != 1)
+                               usage(builtin_grep_usage);
+                       switch (arg[1]) {
+                       case 'A':
+                               opt.post_context = num;
+                               break;
+                       default:
+                       case 'C':
+                               opt.post_context = num;
+                       case 'B':
+                               opt.pre_context = num;
+                               break;
+                       }
+                       continue;
+               }
+               if (!strcmp("-f", arg)) {
+                       FILE *patterns;
+                       int lno = 0;
+                       char buf[1024];
+                       if (argc <= 1)
+                               usage(builtin_grep_usage);
+                       patterns = fopen(argv[1], "r");
+                       if (!patterns)
+                               die("'%s': %s", argv[1], strerror(errno));
+                       while (fgets(buf, sizeof(buf), patterns)) {
+                               int len = strlen(buf);
+                               if (buf[len-1] == '\n')
+                                       buf[len-1] = 0;
+                               /* ignore empty line like grep does */
+                               if (!buf[0])
+                                       continue;
+                               add_pattern(&opt, strdup(buf), argv[1], ++lno);
+                       }
+                       fclose(patterns);
+                       argv++;
+                       argc--;
+                       continue;
+               }
+               if (!strcmp("-e", arg)) {
+                       if (1 < argc) {
+                               add_pattern(&opt, argv[1], "-e option", 0);
+                               argv++;
+                               argc--;
+                               continue;
+                       }
+                       usage(builtin_grep_usage);
+               }
+               if (!strcmp("--", arg))
+                       break;
+               if (*arg == '-')
+                       usage(builtin_grep_usage);
+
+               /* First unrecognized non-option token */
+               if (!opt.pattern_list) {
+                       add_pattern(&opt, arg, "command line", 0);
+                       break;
+               }
+               else {
+                       /* We are looking at the first path or rev;
+                        * it is found at argv[1] after leaving the
+                        * loop.
+                        */
+                       argc++; argv--;
+                       break;
+               }
+       }
+
+       if (!opt.pattern_list)
+               die("no pattern given.");
+       if ((opt.regflags != REG_NEWLINE) && opt.fixed)
+               die("cannot mix --fixed-strings and regexp");
+       if (!opt.fixed)
+               compile_patterns(&opt);
+
+       /* Check revs and then paths */
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               unsigned char sha1[20];
+               /* Is it a rev? */
+               if (!get_sha1(arg, sha1)) {
+                       struct object *object = parse_object(sha1);
+                       struct object_list *elem;
+                       if (!object)
+                               die("bad object %s", arg);
+                       elem = object_list_insert(object, tail);
+                       elem->name = arg;
+                       tail = &elem->next;
+                       continue;
+               }
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       seen_dashdash = 1;
+               }
+               break;
+       }
+
+       /* The rest are paths */
+       if (!seen_dashdash) {
+               int j;
+               for (j = i; j < argc; j++)
+                       verify_filename(prefix, argv[j]);
+       }
+
+       if (i < argc)
+               paths = get_pathspec(prefix, argv + i);
+       else if (prefix) {
+               paths = xcalloc(2, sizeof(const char *));
+               paths[0] = prefix;
+               paths[1] = NULL;
+       }
+
+       if (!object_list)
+               return !grep_cache(&opt, paths, cached);
+
+       if (cached)
+               die("both --cached and trees are given.");
+
+       for (list = object_list; list; list = list->next) {
+               struct object *real_obj;
+               real_obj = deref_tag(list->item, NULL, 0);
+               if (grep_object(&opt, paths, real_obj, list->name))
+                       hit = 1;
+       }
+       return !hit;
+}
index 10a59cc..7470faa 100644 (file)
@@ -3,6 +3,7 @@
  *
  * Builtin help-related commands (help, usage, version)
  */
+#include <sys/ioctl.h>
 #include "cache.h"
 #include "builtin.h"
 #include "exec_cmd.h"
diff --git a/builtin-push.c b/builtin-push.c
new file mode 100644 (file)
index 0000000..e530022
--- /dev/null
@@ -0,0 +1,312 @@
+/*
+ * "git push"
+ */
+#include "cache.h"
+#include "refs.h"
+#include "run-command.h"
+#include "builtin.h"
+
+#define MAX_URI (16)
+
+static const char push_usage[] = "git push [--all] [--tags] [--force] <repository> [<refspec>...]";
+
+static int all = 0, tags = 0, force = 0, thin = 1;
+static const char *execute = NULL;
+
+#define BUF_SIZE (2084)
+static char buffer[BUF_SIZE];
+
+static const char **refspec = NULL;
+static int refspec_nr = 0;
+
+static void add_refspec(const char *ref)
+{
+       int nr = refspec_nr + 1;
+       refspec = xrealloc(refspec, nr * sizeof(char *));
+       refspec[nr-1] = ref;
+       refspec_nr = nr;
+}
+
+static int expand_one_ref(const char *ref, const unsigned char *sha1)
+{
+       /* Ignore the "refs/" at the beginning of the refname */
+       ref += 5;
+
+       if (strncmp(ref, "tags/", 5))
+               return 0;
+
+       add_refspec(strdup(ref));
+       return 0;
+}
+
+static void expand_refspecs(void)
+{
+       if (all) {
+               if (refspec_nr)
+                       die("cannot mix '--all' and a refspec");
+
+               /*
+                * No need to expand "--all" - we'll just use
+                * the "--all" flag to send-pack
+                */
+               return;
+       }
+       if (!tags)
+               return;
+       for_each_ref(expand_one_ref);
+}
+
+static void set_refspecs(const char **refs, int nr)
+{
+       if (nr) {
+               size_t bytes = nr * sizeof(char *);
+
+               refspec = xrealloc(refspec, bytes);
+               memcpy(refspec, refs, bytes);
+               refspec_nr = nr;
+       }
+       expand_refspecs();
+}
+
+static int get_remotes_uri(const char *repo, const char *uri[MAX_URI])
+{
+       int n = 0;
+       FILE *f = fopen(git_path("remotes/%s", repo), "r");
+       int has_explicit_refspec = refspec_nr || all || tags;
+
+       if (!f)
+               return -1;
+       while (fgets(buffer, BUF_SIZE, f)) {
+               int is_refspec;
+               char *s, *p;
+
+               if (!strncmp("URL: ", buffer, 5)) {
+                       is_refspec = 0;
+                       s = buffer + 5;
+               } else if (!strncmp("Push: ", buffer, 6)) {
+                       is_refspec = 1;
+                       s = buffer + 6;
+               } else
+                       continue;
+
+               /* Remove whitespace at the head.. */
+               while (isspace(*s))
+                       s++;
+               if (!*s)
+                       continue;
+
+               /* ..and at the end */
+               p = s + strlen(s);
+               while (isspace(p[-1]))
+                       *--p = 0;
+
+               if (!is_refspec) {
+                       if (n < MAX_URI)
+                               uri[n++] = strdup(s);
+                       else
+                               error("more than %d URL's specified, ignoreing the rest", MAX_URI);
+               }
+               else if (is_refspec && !has_explicit_refspec)
+                       add_refspec(strdup(s));
+       }
+       fclose(f);
+       if (!n)
+               die("remote '%s' has no URL", repo);
+       return n;
+}
+
+static const char **config_uri;
+static const char *config_repo;
+static int config_repo_len;
+static int config_current_uri;
+static int config_get_refspecs;
+
+static int get_remote_config(const char* key, const char* value)
+{
+       if (!strncmp(key, "remote.", 7) &&
+           !strncmp(key + 7, config_repo, config_repo_len)) {
+               if (!strcmp(key + 7 + config_repo_len, ".url")) {
+                       if (config_current_uri < MAX_URI)
+                               config_uri[config_current_uri++] = strdup(value);
+                       else
+                               error("more than %d URL's specified, ignoring the rest", MAX_URI);
+               }
+               else if (config_get_refspecs &&
+                        !strcmp(key + 7 + config_repo_len, ".push"))
+                       add_refspec(strdup(value));
+       }
+       return 0;
+}
+
+static int get_config_remotes_uri(const char *repo, const char *uri[MAX_URI])
+{
+       config_repo_len = strlen(repo);
+       config_repo = repo;
+       config_current_uri = 0;
+       config_uri = uri;
+       config_get_refspecs = !(refspec_nr || all || tags);
+
+       git_config(get_remote_config);
+       return config_current_uri;
+}
+
+static int get_branches_uri(const char *repo, const char *uri[MAX_URI])
+{
+       const char *slash = strchr(repo, '/');
+       int n = slash ? slash - repo : 1000;
+       FILE *f = fopen(git_path("branches/%.*s", n, repo), "r");
+       char *s, *p;
+       int len;
+
+       if (!f)
+               return 0;
+       s = fgets(buffer, BUF_SIZE, f);
+       fclose(f);
+       if (!s)
+               return 0;
+       while (isspace(*s))
+               s++;
+       if (!*s)
+               return 0;
+       p = s + strlen(s);
+       while (isspace(p[-1]))
+               *--p = 0;
+       len = p - s;
+       if (slash)
+               len += strlen(slash);
+       p = xmalloc(len + 1);
+       strcpy(p, s);
+       if (slash)
+               strcat(p, slash);
+       uri[0] = p;
+       return 1;
+}
+
+/*
+ * Read remotes and branches file, fill the push target URI
+ * list.  If there is no command line refspecs, read Push: lines
+ * to set up the *refspec list as well.
+ * return the number of push target URIs
+ */
+static int read_config(const char *repo, const char *uri[MAX_URI])
+{
+       int n;
+
+       if (*repo != '/') {
+               n = get_remotes_uri(repo, uri);
+               if (n > 0)
+                       return n;
+
+               n = get_config_remotes_uri(repo, uri);
+               if (n > 0)
+                       return n;
+
+               n = get_branches_uri(repo, uri);
+               if (n > 0)
+                       return n;
+       }
+
+       uri[0] = repo;
+       return 1;
+}
+
+static int do_push(const char *repo)
+{
+       const char *uri[MAX_URI];
+       int i, n;
+       int remote;
+       const char **argv;
+       int argc;
+
+       n = read_config(repo, uri);
+       if (n <= 0)
+               die("bad repository '%s'", repo);
+
+       argv = xmalloc((refspec_nr + 10) * sizeof(char *));
+       argv[0] = "dummy-send-pack";
+       argc = 1;
+       if (all)
+               argv[argc++] = "--all";
+       if (force)
+               argv[argc++] = "--force";
+       if (execute)
+               argv[argc++] = execute;
+       if (thin)
+               argv[argc++] = "--thin";
+       remote = argc;
+       argv[argc++] = "dummy-remote";
+       while (refspec_nr--)
+               argv[argc++] = *refspec++;
+       argv[argc] = NULL;
+
+       for (i = 0; i < n; i++) {
+               int error;
+               const char *dest = uri[i];
+               const char *sender = "git-send-pack";
+               if (!strncmp(dest, "http://", 7) ||
+                   !strncmp(dest, "https://", 8))
+                       sender = "git-http-push";
+               argv[0] = sender;
+               argv[remote] = dest;
+               error = run_command_v(argc, argv);
+               if (!error)
+                       continue;
+               switch (error) {
+               case -ERR_RUN_COMMAND_FORK:
+                       die("unable to fork for %s", sender);
+               case -ERR_RUN_COMMAND_EXEC:
+                       die("unable to exec %s", sender);
+               case -ERR_RUN_COMMAND_WAITPID:
+               case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
+               case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
+               case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
+                       die("%s died with strange error", sender);
+               default:
+                       return -error;
+               }
+       }
+       return 0;
+}
+
+int cmd_push(int argc, const char **argv, char **envp)
+{
+       int i;
+       const char *repo = "origin";    // default repository
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (arg[0] != '-') {
+                       repo = arg;
+                       i++;
+                       break;
+               }
+               if (!strcmp(arg, "--all")) {
+                       all = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--tags")) {
+                       tags = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--force")) {
+                       force = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--thin")) {
+                       thin = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--no-thin")) {
+                       thin = 0;
+                       continue;
+               }
+               if (!strncmp(arg, "--exec=", 7)) {
+                       execute = arg;
+                       continue;
+               }
+               usage(push_usage);
+       }
+       set_refspecs(argv + i, argc - i);
+       return do_push(repo);
+}
index 94dc073..97e2464 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -19,6 +19,11 @@ extern int cmd_version(int argc, const char **argv, char **envp);
 extern int cmd_whatchanged(int argc, const char **argv, char **envp);
 extern int cmd_show(int argc, const char **argv, char **envp);
 extern int cmd_log(int argc, const char **argv, char **envp);
+extern int cmd_diff(int argc, const char **argv, char **envp);
 extern int cmd_format_patch(int argc, const char **argv, char **envp);
+extern int cmd_count_objects(int argc, const char **argv, char **envp);
+
+extern int cmd_push(int argc, const char **argv, char **envp);
+extern int cmd_grep(int argc, const char **argv, char **envp);
 
 #endif
diff --git a/cache.h b/cache.h
index f6c5909..f01ca43 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -134,6 +134,8 @@ extern const char *setup_git_directory_gently(int *);
 extern const char *setup_git_directory(void);
 extern const char *prefix_path(const char *prefix, int len, const char *path);
 extern const char *prefix_filename(const char *prefix, int len, const char *path);
+extern void verify_filename(const char *prefix, const char *name);
+extern void verify_non_filename(const char *prefix, const char *name);
 
 #define alloc_nr(x) (((x)+16)*3/2)
 
@@ -167,7 +169,8 @@ extern void rollback_index_file(struct cache_file *);
 /* Environment bits from configuration mechanism */
 extern int trust_executable_bit;
 extern int assume_unchanged;
-extern int only_use_symrefs;
+extern int prefer_symlink_refs;
+extern int log_all_ref_updates;
 extern int warn_ambiguous_refs;
 extern int diff_rename_limit_default;
 extern int shared_repository;
@@ -362,4 +365,8 @@ extern int receive_keep_pack(int fd[2], const char *me, int quiet);
 /* pager.c */
 extern void setup_pager(void);
 
+/* base85 */
+int decode_85(char *dst, char *line, int linelen);
+void encode_85(char *buf, unsigned char *data, int bytes);
+
 #endif /* CACHE_H */
index 628f6ca..7413fee 100644 (file)
@@ -103,8 +103,10 @@ int main(int argc, char **argv)
 
        setup_git_directory();
        git_config(git_default_config);
-       if (argc != 3 || get_sha1(argv[2], sha1))
+       if (argc != 3)
                usage("git-cat-file [-t|-s|-e|-p|<type>] <sha1>");
+       if (get_sha1(argv[2], sha1))
+               die("Not a valid object name %s", argv[2]);
 
        opt = 0;
        if ( argv[1][0] == '-' ) {
@@ -133,8 +135,7 @@ int main(int argc, char **argv)
                return !has_sha1_file(sha1);
 
        case 'p':
-               if (get_sha1(argv[2], sha1) ||
-                   sha1_object_info(sha1, type, NULL))
+               if (sha1_object_info(sha1, type, NULL))
                        die("Not a valid object name %s", argv[2]);
 
                /* custom pretty-print here */
index dd6a2d8..cc3a745 100644 (file)
@@ -269,12 +269,16 @@ int main(int argc, char **argv)
        /* Check out named files first */
        for ( ; i < argc; i++) {
                const char *arg = argv[i];
+               const char *p;
 
                if (all)
                        die("git-checkout-index: don't mix '--all' and explicit filenames");
                if (read_from_stdin)
                        die("git-checkout-index: don't mix '--stdin' and explicit filenames");
-               checkout_file(prefix_path(prefix, prefix_length, arg));
+               p = prefix_path(prefix, prefix_length, arg);
+               checkout_file(p);
+               if (p < arg || p > arg + strlen(arg))
+                       free((char*)p);
        }
 
        if (read_from_stdin) {
@@ -284,6 +288,8 @@ int main(int argc, char **argv)
                strbuf_init(&buf);
                while (1) {
                        char *path_name;
+                       const char *p;
+
                        read_line(&buf, stdin, line_termination);
                        if (buf.eof)
                                break;
@@ -291,7 +297,10 @@ int main(int argc, char **argv)
                                path_name = unquote_c_style(buf.buf, NULL);
                        else
                                path_name = buf.buf;
-                       checkout_file(prefix_path(prefix, prefix_length, path_name));
+                       p = prefix_path(prefix, prefix_length, path_name);
+                       checkout_file(p);
+                       if (p < path_name || p > path_name + strlen(path_name))
+                               free((char *)p);
                        if (path_name != buf.buf)
                                free(path_name);
                }
index ca36f5d..64b20cc 100644 (file)
@@ -608,6 +608,7 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
        int abbrev = opt->full_index ? 40 : DEFAULT_ABBREV;
        mmfile_t result_file;
 
+       context = opt->context;
        /* Read the result of merge first */
        if (!working_tree_file)
                result = grab_blob(elem->sha1, &result_size);
@@ -831,15 +832,16 @@ void show_combined_diff(struct combine_diff_path *p,
        }
 }
 
-void diff_tree_combined_merge(const unsigned char *sha1,
-                            int dense, struct rev_info *rev)
+void diff_tree_combined(const unsigned char *sha1,
+                       const unsigned char parent[][20],
+                       int num_parent,
+                       int dense,
+                       struct rev_info *rev)
 {
        struct diff_options *opt = &rev->diffopt;
-       struct commit *commit = lookup_commit(sha1);
        struct diff_options diffopts;
-       struct commit_list *parents;
        struct combine_diff_path *p, *paths = NULL;
-       int num_parent, i, num_paths;
+       int i, num_paths;
        int do_diffstat;
 
        do_diffstat = (opt->output_format == DIFF_FORMAT_DIFFSTAT ||
@@ -849,17 +851,8 @@ void diff_tree_combined_merge(const unsigned char *sha1,
        diffopts.with_stat = 0;
        diffopts.recursive = 1;
 
-       /* count parents */
-       for (parents = commit->parents, num_parent = 0;
-            parents;
-            parents = parents->next, num_parent++)
-               ; /* nothing */
-
        /* find set of paths that everybody touches */
-       for (parents = commit->parents, i = 0;
-            parents;
-            parents = parents->next, i++) {
-               struct commit *parent = parents->item;
+       for (i = 0; i < num_parent; i++) {
                /* show stat against the first parent even
                 * when doing combined diff.
                 */
@@ -867,8 +860,7 @@ void diff_tree_combined_merge(const unsigned char *sha1,
                        diffopts.output_format = DIFF_FORMAT_DIFFSTAT;
                else
                        diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
-               diff_tree_sha1(parent->object.sha1, commit->object.sha1, "",
-                              &diffopts);
+               diff_tree_sha1(parent[i], sha1, "", &diffopts);
                diffcore_std(&diffopts);
                paths = intersect_paths(paths, i, num_parent);
 
@@ -907,3 +899,25 @@ void diff_tree_combined_merge(const unsigned char *sha1,
                free(tmp);
        }
 }
+
+void diff_tree_combined_merge(const unsigned char *sha1,
+                            int dense, struct rev_info *rev)
+{
+       int num_parent;
+       const unsigned char (*parent)[20];
+       struct commit *commit = lookup_commit(sha1);
+       struct commit_list *parents;
+
+       /* count parents */
+       for (parents = commit->parents, num_parent = 0;
+            parents;
+            parents = parents->next, num_parent++)
+               ; /* nothing */
+
+       parent = xmalloc(num_parent * sizeof(*parent));
+       for (parents = commit->parents, num_parent = 0;
+            parents;
+            parents = parents->next, num_parent++)
+               memcpy(parent + num_parent, parents->item->object.sha1, 20);
+       diff_tree_combined(sha1, parent, num_parent, dense, rev);
+}
index 2d86518..0320036 100644 (file)
@@ -45,14 +45,13 @@ static void add_buffer(char **bufp, unsigned int *sizep, const char *fmt, ...)
 
 static void check_valid(unsigned char *sha1, const char *expect)
 {
-       void *buf;
        char type[20];
-       unsigned long size;
 
-       buf = read_sha1_file(sha1, type, &size);
-       if (!buf || strcmp(type, expect))
-               die("%s is not a valid '%s' object", sha1_to_hex(sha1), expect);
-       free(buf);
+       if (sha1_object_info(sha1, type, NULL))
+               die("%s is not a valid object", sha1_to_hex(sha1));
+       if (expect && strcmp(type, expect))
+               die("%s is not a valid '%s' object", sha1_to_hex(sha1),
+                   expect);
 }
 
 /*
@@ -92,15 +91,19 @@ int main(int argc, char **argv)
 
        git_config(git_default_config);
 
-       if (argc < 2 || get_sha1_hex(argv[1], tree_sha1) < 0)
+       if (argc < 2)
                usage(commit_tree_usage);
+       if (get_sha1(argv[1], tree_sha1))
+               die("Not a valid object name %s", argv[1]);
 
        check_valid(tree_sha1, tree_type);
        for (i = 2; i < argc; i += 2) {
                char *a, *b;
                a = argv[i]; b = argv[i+1];
-               if (!b || strcmp(a, "-p") || get_sha1(b, parent_sha1[parents]))
+               if (!b || strcmp(a, "-p"))
                        usage(commit_tree_usage);
+               if (get_sha1(b, parent_sha1[parents]))
+                       die("Not a valid object name %s", b);
                check_valid(parent_sha1[parents], commit_type);
                if (new_parent(parents))
                        parents++;
index 42b44bb..8317f6d 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -22,25 +22,34 @@ struct sort_node
 
 const char *commit_type = "commit";
 
+struct cmt_fmt_map {
+       const char *n;
+       size_t cmp_len;
+       enum cmit_fmt v;
+} cmt_fmts[] = {
+       { "raw",        1,      CMIT_FMT_RAW },
+       { "medium",     1,      CMIT_FMT_MEDIUM },
+       { "short",      1,      CMIT_FMT_SHORT },
+       { "email",      1,      CMIT_FMT_EMAIL },
+       { "full",       5,      CMIT_FMT_FULL },
+       { "fuller",     5,      CMIT_FMT_FULLER },
+       { "oneline",    1,      CMIT_FMT_ONELINE },
+};
+
 enum cmit_fmt get_commit_format(const char *arg)
 {
-       if (!*arg)
+       int i;
+
+       if (!arg || !*arg)
                return CMIT_FMT_DEFAULT;
-       if (!strcmp(arg, "=raw"))
-               return CMIT_FMT_RAW;
-       if (!strcmp(arg, "=medium"))
-               return CMIT_FMT_MEDIUM;
-       if (!strcmp(arg, "=short"))
-               return CMIT_FMT_SHORT;
-       if (!strcmp(arg, "=full"))
-               return CMIT_FMT_FULL;
-       if (!strcmp(arg, "=fuller"))
-               return CMIT_FMT_FULLER;
-       if (!strcmp(arg, "=email"))
-               return CMIT_FMT_EMAIL;
-       if (!strcmp(arg, "=oneline"))
-               return CMIT_FMT_ONELINE;
-       die("invalid --pretty format");
+       if (*arg == '=')
+               arg++;
+       for (i = 0; i < ARRAY_SIZE(cmt_fmts); i++) {
+               if (!strncmp(arg, cmt_fmts[i].n, cmt_fmts[i].cmp_len))
+                       return cmt_fmts[i].v;
+       }
+
+       die("invalid --pretty format: %s", arg);
 }
 
 static struct commit *check_commit(struct object *obj,
index 7ea8a73..2ae6153 100644 (file)
--- a/config.c
+++ b/config.c
@@ -60,6 +60,12 @@ static char *parse_value(void)
                        space = 1;
                        continue;
                }
+               if (!quote) {
+                       if (c == ';' || c == '#') {
+                               comment = 1;
+                               continue;
+                       }
+               }
                if (space) {
                        if (len)
                                value[len++] = ' ';
@@ -93,12 +99,6 @@ static char *parse_value(void)
                        quote = 1-quote;
                        continue;
                }
-               if (!quote) {
-                       if (c == ';' || c == '#') {
-                               comment = 1;
-                               continue;
-                       }
-               }
                value[len++] = c;
        }
 }
@@ -134,6 +134,41 @@ static int get_value(config_fn_t fn, char *name, unsigned int len)
        return fn(name, value);
 }
 
+static int get_extended_base_var(char *name, int baselen, int c)
+{
+       do {
+               if (c == '\n')
+                       return -1;
+               c = get_next_char();
+       } while (isspace(c));
+
+       /* We require the format to be '[base "extension"]' */
+       if (c != '"')
+               return -1;
+       name[baselen++] = '.';
+
+       for (;;) {
+               int c = get_next_char();
+               if (c == '\n')
+                       return -1;
+               if (c == '"')
+                       break;
+               if (c == '\\') {
+                       c = get_next_char();
+                       if (c == '\n')
+                               return -1;
+               }
+               name[baselen++] = c;
+               if (baselen > MAXNAME / 2)
+                       return -1;
+       }
+
+       /* Final ']' */
+       if (get_next_char() != ']')
+               return -1;
+       return baselen;
+}
+
 static int get_base_var(char *name)
 {
        int baselen = 0;
@@ -144,6 +179,8 @@ static int get_base_var(char *name)
                        return -1;
                if (c == ']')
                        return baselen;
+               if (isspace(c))
+                       return get_extended_base_var(name, baselen, c);
                if (!isalnum(c) && c != '.')
                        return -1;
                if (baselen > MAXNAME / 2)
@@ -227,8 +264,13 @@ int git_default_config(const char *var, const char *value)
                return 0;
        }
 
-       if (!strcmp(var, "core.symrefsonly")) {
-               only_use_symrefs = git_config_bool(var, value);
+       if (!strcmp(var, "core.prefersymlinkrefs")) {
+               prefer_symlink_refs = git_config_bool(var, value);
+               return 0;
+       }
+
+       if (!strcmp(var, "core.logallrefupdates")) {
+               log_all_ref_updates = git_config_bool(var, value);
                return 0;
        }
 
@@ -252,7 +294,7 @@ int git_default_config(const char *var, const char *value)
                return 0;
        }
 
-       /* Add other config variables here.. */
+       /* Add other config variables here and to Documentation/config.txt. */
        return 0;
 }
 
@@ -335,16 +377,43 @@ static int store_aux(const char* key, const char* value)
                        store.offset[store.seen] = ftell(config_file);
                        store.state = KEY_SEEN;
                        store.seen++;
-               } else if(!strncmp(key, store.key, store.baselen))
-                       store.state = SECTION_SEEN;
+               } else {
+                       if (strrchr(key, '.') - key == store.baselen &&
+                             !strncmp(key, store.key, store.baselen)) {
+                                       store.state = SECTION_SEEN;
+                                       store.offset[store.seen] = ftell(config_file);
+                       }
+               }
        }
        return 0;
 }
 
 static void store_write_section(int fd, const char* key)
 {
+       const char *dot = strchr(key, '.');
+       int len1 = store.baselen, len2 = -1;
+
+       dot = strchr(key, '.');
+       if (dot) {
+               int dotlen = dot - key;
+               if (dotlen < len1) {
+                       len2 = len1 - dotlen - 1;
+                       len1 = dotlen;
+               }
+       }
+
        write(fd, "[", 1);
-       write(fd, key, store.baselen);
+       write(fd, key, len1);
+       if (len2 >= 0) {
+               write(fd, " \"", 2);
+               while (--len2 >= 0) {
+                       unsigned char c = *++dot;
+                       if (c == '"')
+                               write(fd, "\\", 1);
+                       write(fd, &c, 1);
+               }
+               write(fd, "\"", 1);
+       }
        write(fd, "]\n", 2);
 }
 
@@ -418,8 +487,8 @@ int git_config_set(const char* key, const char* value)
 int git_config_set_multivar(const char* key, const char* value,
        const char* value_regex, int multi_replace)
 {
-       int i;
-       int fd, in_fd;
+       int i, dot;
+       int fd = -1, in_fd;
        int ret;
        char* config_filename = strdup(git_path("config"));
        char* lock_file = strdup(git_path("config.lock"));
@@ -443,16 +512,23 @@ int git_config_set_multivar(const char* key, const char* value,
         * Validate the key and while at it, lower case it for matching.
         */
        store.key = (char*)malloc(strlen(key)+1);
-       for (i = 0; key[i]; i++)
-               if (i != store.baselen &&
-                               ((!isalnum(key[i]) && key[i] != '.') ||
-                                (i == store.baselen+1 && !isalpha(key[i])))) {
-                       fprintf(stderr, "invalid key: %s\n", key);
-                       free(store.key);
-                       ret = 1;
-                       goto out_free;
-               } else
-                       store.key[i] = tolower(key[i]);
+       dot = 0;
+       for (i = 0; key[i]; i++) {
+               unsigned char c = key[i];
+               if (c == '.')
+                       dot = 1;
+               /* Leave the extended basename untouched.. */
+               if (!dot || i > store.baselen) {
+                       if (!isalnum(c) || (i == store.baselen+1 && !isalpha(c))) {
+                               fprintf(stderr, "invalid key: %s\n", key);
+                               free(store.key);
+                               ret = 1;
+                               goto out_free;
+                       }
+                       c = tolower(c);
+               }
+               store.key[i] = c;
+       }
        store.key[i] = 0;
 
        /*
@@ -477,15 +553,11 @@ int git_config_set_multivar(const char* key, const char* value,
                if ( ENOENT != errno ) {
                        error("opening %s: %s", config_filename,
                              strerror(errno));
-                       close(fd);
-                       unlink(lock_file);
                        ret = 3; /* same as "invalid config file" */
                        goto out_free;
                }
                /* if nothing to unset, error out */
                if (value == NULL) {
-                       close(fd);
-                       unlink(lock_file);
                        ret = 5;
                        goto out_free;
                }
@@ -548,8 +620,6 @@ int git_config_set_multivar(const char* key, const char* value,
                /* if nothing to unset, or too many matches, error out */
                if ((store.seen == 0 && value == NULL) ||
                                (store.seen > 1 && multi_replace == 0)) {
-                       close(fd);
-                       unlink(lock_file);
                        ret = 5;
                        goto out_free;
                }
@@ -598,8 +668,6 @@ int git_config_set_multivar(const char* key, const char* value,
                unlink(config_filename);
        }
 
-       close(fd);
-
        if (rename(lock_file, config_filename) < 0) {
                fprintf(stderr, "Could not rename the lock file?\n");
                ret = 4;
@@ -609,10 +677,14 @@ int git_config_set_multivar(const char* key, const char* value,
        ret = 0;
 
 out_free:
+       if (0 <= fd)
+               close(fd);
        if (config_filename)
                free(config_filename);
-       if (lock_file)
+       if (lock_file) {
+               unlink(lock_file);
                free(lock_file);
+       }
        return ret;
 }
 
diff --git a/contrib/colordiff/README b/contrib/colordiff/README
new file mode 100644 (file)
index 0000000..2678fdf
--- /dev/null
@@ -0,0 +1,2 @@
+This is "colordiff" (http://colordiff.sourceforge.net/) by Dave
+Ewart <davee@sungate.co.uk>, modified specifically for git.
diff --git a/contrib/colordiff/colordiff.perl b/contrib/colordiff/colordiff.perl
new file mode 100755 (executable)
index 0000000..5789cfb
--- /dev/null
@@ -0,0 +1,196 @@
+#!/usr/bin/perl -w
+#
+# $Id: colordiff.pl,v 1.4.2.10 2004/01/04 15:02:59 daveewart Exp $
+
+########################################################################
+#                                                                      #
+# ColorDiff - a wrapper/replacment for 'diff' producing                #
+#             colourful output                                         #
+#                                                                      #
+# Copyright (C)2002-2004 Dave Ewart (davee@sungate.co.uk)              #
+#                                                                      #
+########################################################################
+#                                                                      #
+# This program is free software; you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation; either version 2 of the License, or    #
+# (at your option) any later version.                                  #
+#                                                                      #
+# This program is distributed in the hope that it will be useful,      #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of       #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        #
+# GNU General Public License for more details.                         #
+#                                                                      #
+# You should have received a copy of the GNU General Public License    #
+# along with this program; if not, write to the Free Software          #
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.            #
+#                                                                      #
+########################################################################
+
+use strict;
+use Getopt::Long qw(:config pass_through);
+use IPC::Open2;
+
+my $app_name     = 'colordiff';
+my $version      = '1.0.4';
+my $author       = 'Dave Ewart';
+my $author_email = 'davee@sungate.co.uk';
+my $app_www      = 'http://colordiff.sourceforge.net/';
+my $copyright    = '(C)2002-2004';
+my $show_banner  = 1;
+
+# ANSI sequences for colours
+my %colour;
+$colour{white}       = "\033[1;37m";
+$colour{yellow}      = "\033[1;33m";
+$colour{green}       = "\033[1;32m";
+$colour{blue}        = "\033[1;34m";
+$colour{cyan}        = "\033[1;36m";
+$colour{red}         = "\033[1;31m";
+$colour{magenta}     = "\033[1;35m";
+$colour{black}       = "\033[1;30m";
+$colour{darkwhite}   = "\033[0;37m";
+$colour{darkyellow}  = "\033[0;33m";
+$colour{darkgreen}   = "\033[0;32m";
+$colour{darkblue}    = "\033[0;34m";
+$colour{darkcyan}    = "\033[0;36m";
+$colour{darkred}     = "\033[0;31m";
+$colour{darkmagenta} = "\033[0;35m";
+$colour{darkblack}   = "\033[0;30m";
+$colour{OFF}         = "\033[0;0m";
+
+# Default colours if /etc/colordiffrc or ~/.colordiffrc do not exist
+my $plain_text = $colour{OFF};
+my $file_old   = $colour{red};
+my $file_new   = $colour{blue};
+my $diff_stuff = $colour{magenta};
+
+# Locations for personal and system-wide colour configurations
+my $HOME   = $ENV{HOME};
+my $etcdir = '/etc';
+
+my ($setting, $value);
+my @config_files = ("$etcdir/colordiffrc", "$HOME/.colordiffrc");
+my $config_file;
+
+foreach $config_file (@config_files) {
+    if (open(COLORDIFFRC, "<$config_file")) {
+        while (<COLORDIFFRC>) {
+            chop;
+            next if (/^#/ || /^$/);
+            s/\s+//g;
+            ($setting, $value) = split ('=');
+            if ($setting eq 'banner') {
+                if ($value eq 'no') {
+                    $show_banner = 0;
+                }
+                next;
+            }
+            if (!defined $colour{$value}) {
+                print "Invalid colour specification ($value) in $config_file\n";
+                next;
+            }
+            if ($setting eq 'plain') {
+                $plain_text = $colour{$value};
+            }
+            elsif ($setting eq 'oldtext') {
+                $file_old = $colour{$value};
+            }
+            elsif ($setting eq 'newtext') {
+                $file_new = $colour{$value};
+            }
+            elsif ($setting eq 'diffstuff') {
+                $diff_stuff = $colour{$value};
+            }
+            else {
+                print "Unknown option in $etcdir/colordiffrc: $setting\n";
+            }
+        }
+        close COLORDIFFRC;
+    }
+}
+
+# colordiff specfic options here.  Need to pre-declare if using variables
+GetOptions(
+    "no-banner" => sub { $show_banner = 0 },
+    "plain-text=s" => \&set_color,
+    "file-old=s"   => \&set_color,
+    "file-new=s"   => \&set_color,
+    "diff-stuff=s" => \&set_color
+);
+
+if ($show_banner == 1) {
+    print STDERR "$app_name $version ($app_www)\n";
+    print STDERR "$copyright $author, $author_email\n\n";
+}
+
+if (defined $ARGV[0]) {
+    # More reliable way of pulling in arguments
+    open2(\*INPUTSTREAM, undef, "git", "diff", @ARGV);
+}
+else {
+    *INPUTSTREAM = \*STDIN;
+}
+
+my $record;
+my $nrecs           = 0;
+my $inside_file_old = 1;
+my $nparents        = undef;
+
+while (<INPUTSTREAM>) {
+    $nrecs++;
+    if (/^(\@\@+) -[-+0-9, ]+ \1/) {
+           print "$diff_stuff";
+           $nparents = length($1) - 1;
+    }
+    elsif (/^diff -/ || /^index / ||
+          /^old mode / || /^new mode / ||
+          /^deleted file mode / || /^new file mode / ||
+          /^similarity index / || /^dissimilarity index / ||
+          /^copy from / || /^copy to / ||
+          /^rename from / || /^rename to /) {
+           $nparents = undef;
+           print "$diff_stuff";
+    }
+    elsif (defined $nparents) {
+           if ($nparents == 1) {
+                   if (/^\+/) {
+                           print $file_new;
+                   }
+                   elsif (/^-/) {
+                           print $file_old;
+                   }
+                   else {
+                           print $plain_text;
+                   }
+           }
+           elsif (/^ {$nparents}/) {
+                   print "$plain_text";
+           }
+           elsif (/^[+ ]{$nparents}/) {
+                   print "$file_new";
+           }
+           elsif (/^[- ]{$nparents}/) {
+                   print "$file_old";
+           }
+           else {
+                   print $plain_text;
+           }
+    }
+    elsif (/^--- / || /^\+\+\+ /) {
+           print $diff_stuff;
+    }
+    else {
+           print "$plain_text";
+    }
+    s/$/$colour{OFF}/;
+    print "$_";
+}
+close INPUTSTREAM;
+
+sub set_color {
+    my ($type, $color) = @_;
+
+    $type =~ s/-/_/;
+    eval "\$$type = \$colour{$color}";
+}
index 7c44450..de13a96 100755 (executable)
@@ -8,7 +8,7 @@ use vars qw/    $AUTHOR $VERSION
                $GIT_SVN_INDEX $GIT_SVN
                $GIT_DIR $REV_DIR/;
 $AUTHOR = 'Eric Wong <normalperson@yhbt.net>';
-$VERSION = '0.11.0';
+$VERSION = '1.0.0';
 
 use Cwd qw/abs_path/;
 $GIT_DIR = abs_path($ENV{GIT_DIR} || '.git');
@@ -42,7 +42,8 @@ my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext,
 my %cmd = (
        fetch => [ \&fetch, "Download new revisions from SVN",
                        { 'revision|r=s' => \$_revision, %fc_opts } ],
-       init => [ \&init, "Initialize and fetch (import)", { } ],
+       init => [ \&init, "Initialize a repo for tracking" .
+                         " (requires URL argument)", { } ],
        commit => [ \&commit, "Commit git revisions to SVN",
                        {       'stdin|' => \$_stdin,
                                'edit|e' => \$_edit,
@@ -220,7 +221,8 @@ when you have upgraded your tools and habits to use refs/remotes/$GIT_SVN
 }
 
 sub init {
-       $SVN_URL = shift or croak "SVN repository location required\n";
+       $SVN_URL = shift or die "SVN repository location required " .
+                               "as a command-line argument\n";
        unless (-d $GIT_DIR) {
                sys('git-init-db');
        }
index e18fcaf..f7d3de4 100644 (file)
@@ -36,17 +36,22 @@ COMMANDS
 --------
 init::
        Creates an empty git repository with additional metadata
-       directories for git-svn.  The SVN_URL must be specified
-       at this point.
+       directories for git-svn.  The Subversion URL must be specified
+       as a command-line argument.
 
 fetch::
-       Fetch unfetched revisions from the SVN_URL we are tracking.
-       refs/heads/remotes/git-svn will be updated to the latest revision.
+       Fetch unfetched revisions from the Subversion URL we are
+       tracking.  refs/remotes/git-svn will be updated to the
+       latest revision.
 
-       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 'commit' command (see below)
-       to write git commits back to remotes/git-svn.
+       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 'commit'
+       command (see below) to write git commits back to
+       remotes/git-svn.
+
+       See 'Additional Fetch Arguments' if you are interested in
+       manually joining branches on commit.
 
 commit::
        Commit specified commit or tree objects to SVN.  This relies on
@@ -62,9 +67,9 @@ rebuild::
        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.  SVN_URL may be optionally specified if
-       the directory/repository you're tracking has moved or changed
-       protocols.
+       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
@@ -123,6 +128,24 @@ OPTIONS
        repo-config key: svn.l
        repo-config key: svn.findcopiesharder
 
+-A<filename>::
+--authors-file=<filename>::
+
+       Syntax is compatible with the files used by git-svnimport and
+       git-cvsimport:
+
+------------------------------------------------------------------------
+loginname = Joe User <user@example.com>
+------------------------------------------------------------------------
+
+       If this option is specified and git-svn encounters an SVN
+       committer name that does not exist in the authors-file, git-svn
+       will abort operation. The user will then have to add the
+       appropriate entry.  Re-running the previous git-svn command
+       after the authors-file is modified should continue operation.
+
+       repo-config key: svn.authors-file
+
 ADVANCED OPTIONS
 ----------------
 -b<refname>::
index 12aacef..a67d6b4 100644 (file)
@@ -321,8 +321,10 @@ int main(int argc, char **argv)
 
        setup_git_directory();
 
-       if (argc != 2 || get_sha1(argv[1], sha1))
+       if (argc != 2)
                usage("git-convert-objects <sha1>");
+       if (get_sha1(argv[1], sha1))
+               die("Not a valid object name %s", argv[1]);
 
        entry = convert_entry(sha1);
        printf("new sha1: %s\n", sha1_to_hex(entry->new_sha1));
index a1ccda3..776749e 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -535,7 +535,7 @@ static int socksetup(int port, int **socklist_p)
 
                if (set_reuse_addr(sockfd)) {
                        close(sockfd);
-                       return 0;       /* not fatal */
+                       continue;
                }
 
                if (bind(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) {
diff --git a/delta.h b/delta.h
index 9464f3e..7b3f86d 100644 (file)
--- a/delta.h
+++ b/delta.h
@@ -1,12 +1,75 @@
 #ifndef DELTA_H
 #define DELTA_H
 
-/* handling of delta buffers */
-extern void *diff_delta(void *from_buf, unsigned long from_size,
-                       void *to_buf, unsigned long to_size,
-                       unsigned long *delta_size, unsigned long max_size);
-extern void *patch_delta(void *src_buf, unsigned long src_size,
-                        void *delta_buf, unsigned long delta_size,
+/* opaque object for delta index */
+struct delta_index;
+
+/*
+ * create_delta_index: compute index data from given buffer
+ *
+ * This returns a pointer to a struct delta_index that should be passed to
+ * subsequent create_delta() calls, or to free_delta_index().  A NULL pointer
+ * is returned on failure.  The given buffer must not be freed nor altered
+ * before free_delta_index() is called.  The returned pointer must be freed
+ * using free_delta_index().
+ */
+extern struct delta_index *
+create_delta_index(const void *buf, unsigned long bufsize);
+
+/*
+ * free_delta_index: free the index created by create_delta_index()
+ *
+ * Given pointer must be what create_delta_index() returned, or NULL.
+ */
+extern void free_delta_index(struct delta_index *index);
+
+/*
+ * create_delta: create a delta from given index for the given buffer
+ *
+ * This function may be called multiple times with different buffers using
+ * the same delta_index pointer.  If max_delta_size is non-zero and the
+ * resulting delta is to be larger than max_delta_size then NULL is returned.
+ * On success, a non-NULL pointer to the buffer with the delta data is
+ * returned and *delta_size is updated with its size.  The returned buffer
+ * must be freed by the caller.
+ */
+extern void *
+create_delta(const struct delta_index *index,
+            const void *buf, unsigned long bufsize,
+            unsigned long *delta_size, unsigned long max_delta_size);
+
+/*
+ * diff_delta: create a delta from source buffer to target buffer
+ *
+ * If max_delta_size is non-zero and the resulting delta is to be larger
+ * than max_delta_size then NULL is returned.  On success, a non-NULL
+ * pointer to the buffer with the delta data is returned and *delta_size is
+ * updated with its size.  The returned buffer must be freed by the caller.
+ */
+static inline void *
+diff_delta(const void *src_buf, unsigned long src_bufsize,
+          const void *trg_buf, unsigned long trg_bufsize,
+          unsigned long *delta_size, unsigned long max_delta_size)
+{
+       struct delta_index *index = create_delta_index(src_buf, src_bufsize);
+       if (index) {
+               void *delta = create_delta(index, trg_buf, trg_bufsize,
+                                          delta_size, max_delta_size);
+               free_delta_index(index);
+               return delta;
+       }
+       return NULL;
+}
+
+/*
+ * patch_delta: recreate target buffer given source buffer and delta data
+ *
+ * On success, a non-NULL pointer to the target buffer is returned and
+ * *trg_bufsize is updated with its size.  On failure a NULL pointer is
+ * returned.  The returned buffer must be freed by the caller.
+ */
+extern void *patch_delta(const void *src_buf, unsigned long src_size,
+                        const void *delta_buf, unsigned long delta_size,
                         unsigned long *dst_size);
 
 /* the smallest possible delta size is 4 bytes */
@@ -14,7 +77,7 @@ extern void *patch_delta(void *src_buf, unsigned long src_size,
 
 /*
  * This must be called twice on the delta data buffer, first to get the
- * expected reference buffer size, and again to get the result buffer size.
+ * expected source buffer size, and again to get the target buffer size.
  */
 static inline unsigned long get_delta_hdr_size(const unsigned char **datap,
                                               const unsigned char *top)
index ff65742..8a9cd5d 100644 (file)
@@ -105,11 +105,11 @@ static void describe(char *arg, int last_one)
        static int initialized = 0;
        struct commit_name *n;
 
-       if (get_sha1(arg, sha1) < 0)
-               usage(describe_usage);
+       if (get_sha1(arg, sha1))
+               die("Not a valid object name %s", arg);
        cmit = lookup_commit_reference(sha1);
        if (!cmit)
-               usage(describe_usage);
+               die("%s is not a valid '%s' object", arg, commit_type);
 
        if (!initialized) {
                initialized = 1;
index 1188b31..25a798d 100644 (file)
 
 #include <stdlib.h>
 #include <string.h>
-#include <zlib.h>
 #include "delta.h"
 
 
-/* block size: min = 16, max = 64k, power of 2 */
-#define BLK_SIZE 16
-
-#define MIN(a, b) ((a) < (b) ? (a) : (b))
+/* maximum hash entry list for the same hash bucket */
+#define HASH_LIMIT 64
+
+#define RABIN_SHIFT 23
+#define RABIN_WINDOW 16
+
+static const unsigned int T[256] = {
+       0x00000000, 0xab59b4d1, 0x56b369a2, 0xfdeadd73, 0x063f6795, 0xad66d344,
+       0x508c0e37, 0xfbd5bae6, 0x0c7ecf2a, 0xa7277bfb, 0x5acda688, 0xf1941259,
+       0x0a41a8bf, 0xa1181c6e, 0x5cf2c11d, 0xf7ab75cc, 0x18fd9e54, 0xb3a42a85,
+       0x4e4ef7f6, 0xe5174327, 0x1ec2f9c1, 0xb59b4d10, 0x48719063, 0xe32824b2,
+       0x1483517e, 0xbfdae5af, 0x423038dc, 0xe9698c0d, 0x12bc36eb, 0xb9e5823a,
+       0x440f5f49, 0xef56eb98, 0x31fb3ca8, 0x9aa28879, 0x6748550a, 0xcc11e1db,
+       0x37c45b3d, 0x9c9defec, 0x6177329f, 0xca2e864e, 0x3d85f382, 0x96dc4753,
+       0x6b369a20, 0xc06f2ef1, 0x3bba9417, 0x90e320c6, 0x6d09fdb5, 0xc6504964,
+       0x2906a2fc, 0x825f162d, 0x7fb5cb5e, 0xd4ec7f8f, 0x2f39c569, 0x846071b8,
+       0x798aaccb, 0xd2d3181a, 0x25786dd6, 0x8e21d907, 0x73cb0474, 0xd892b0a5,
+       0x23470a43, 0x881ebe92, 0x75f463e1, 0xdeadd730, 0x63f67950, 0xc8afcd81,
+       0x354510f2, 0x9e1ca423, 0x65c91ec5, 0xce90aa14, 0x337a7767, 0x9823c3b6,
+       0x6f88b67a, 0xc4d102ab, 0x393bdfd8, 0x92626b09, 0x69b7d1ef, 0xc2ee653e,
+       0x3f04b84d, 0x945d0c9c, 0x7b0be704, 0xd05253d5, 0x2db88ea6, 0x86e13a77,
+       0x7d348091, 0xd66d3440, 0x2b87e933, 0x80de5de2, 0x7775282e, 0xdc2c9cff,
+       0x21c6418c, 0x8a9ff55d, 0x714a4fbb, 0xda13fb6a, 0x27f92619, 0x8ca092c8,
+       0x520d45f8, 0xf954f129, 0x04be2c5a, 0xafe7988b, 0x5432226d, 0xff6b96bc,
+       0x02814bcf, 0xa9d8ff1e, 0x5e738ad2, 0xf52a3e03, 0x08c0e370, 0xa39957a1,
+       0x584ced47, 0xf3155996, 0x0eff84e5, 0xa5a63034, 0x4af0dbac, 0xe1a96f7d,
+       0x1c43b20e, 0xb71a06df, 0x4ccfbc39, 0xe79608e8, 0x1a7cd59b, 0xb125614a,
+       0x468e1486, 0xedd7a057, 0x103d7d24, 0xbb64c9f5, 0x40b17313, 0xebe8c7c2,
+       0x16021ab1, 0xbd5bae60, 0x6cb54671, 0xc7ecf2a0, 0x3a062fd3, 0x915f9b02,
+       0x6a8a21e4, 0xc1d39535, 0x3c394846, 0x9760fc97, 0x60cb895b, 0xcb923d8a,
+       0x3678e0f9, 0x9d215428, 0x66f4eece, 0xcdad5a1f, 0x3047876c, 0x9b1e33bd,
+       0x7448d825, 0xdf116cf4, 0x22fbb187, 0x89a20556, 0x7277bfb0, 0xd92e0b61,
+       0x24c4d612, 0x8f9d62c3, 0x7836170f, 0xd36fa3de, 0x2e857ead, 0x85dcca7c,
+       0x7e09709a, 0xd550c44b, 0x28ba1938, 0x83e3ade9, 0x5d4e7ad9, 0xf617ce08,
+       0x0bfd137b, 0xa0a4a7aa, 0x5b711d4c, 0xf028a99d, 0x0dc274ee, 0xa69bc03f,
+       0x5130b5f3, 0xfa690122, 0x0783dc51, 0xacda6880, 0x570fd266, 0xfc5666b7,
+       0x01bcbbc4, 0xaae50f15, 0x45b3e48d, 0xeeea505c, 0x13008d2f, 0xb85939fe,
+       0x438c8318, 0xe8d537c9, 0x153feaba, 0xbe665e6b, 0x49cd2ba7, 0xe2949f76,
+       0x1f7e4205, 0xb427f6d4, 0x4ff24c32, 0xe4abf8e3, 0x19412590, 0xb2189141,
+       0x0f433f21, 0xa41a8bf0, 0x59f05683, 0xf2a9e252, 0x097c58b4, 0xa225ec65,
+       0x5fcf3116, 0xf49685c7, 0x033df00b, 0xa86444da, 0x558e99a9, 0xfed72d78,
+       0x0502979e, 0xae5b234f, 0x53b1fe3c, 0xf8e84aed, 0x17bea175, 0xbce715a4,
+       0x410dc8d7, 0xea547c06, 0x1181c6e0, 0xbad87231, 0x4732af42, 0xec6b1b93,
+       0x1bc06e5f, 0xb099da8e, 0x4d7307fd, 0xe62ab32c, 0x1dff09ca, 0xb6a6bd1b,
+       0x4b4c6068, 0xe015d4b9, 0x3eb80389, 0x95e1b758, 0x680b6a2b, 0xc352defa,
+       0x3887641c, 0x93ded0cd, 0x6e340dbe, 0xc56db96f, 0x32c6cca3, 0x999f7872,
+       0x6475a501, 0xcf2c11d0, 0x34f9ab36, 0x9fa01fe7, 0x624ac294, 0xc9137645,
+       0x26459ddd, 0x8d1c290c, 0x70f6f47f, 0xdbaf40ae, 0x207afa48, 0x8b234e99,
+       0x76c993ea, 0xdd90273b, 0x2a3b52f7, 0x8162e626, 0x7c883b55, 0xd7d18f84,
+       0x2c043562, 0x875d81b3, 0x7ab75cc0, 0xd1eee811
+};
 
-#define GR_PRIME 0x9e370001
-#define HASH(v, shift) (((unsigned int)(v) * GR_PRIME) >> (shift))
+static const unsigned int U[256] = {
+       0x00000000, 0x7eb5200d, 0x5633f4cb, 0x2886d4c6, 0x073e5d47, 0x798b7d4a,
+       0x510da98c, 0x2fb88981, 0x0e7cba8e, 0x70c99a83, 0x584f4e45, 0x26fa6e48,
+       0x0942e7c9, 0x77f7c7c4, 0x5f711302, 0x21c4330f, 0x1cf9751c, 0x624c5511,
+       0x4aca81d7, 0x347fa1da, 0x1bc7285b, 0x65720856, 0x4df4dc90, 0x3341fc9d,
+       0x1285cf92, 0x6c30ef9f, 0x44b63b59, 0x3a031b54, 0x15bb92d5, 0x6b0eb2d8,
+       0x4388661e, 0x3d3d4613, 0x39f2ea38, 0x4747ca35, 0x6fc11ef3, 0x11743efe,
+       0x3eccb77f, 0x40799772, 0x68ff43b4, 0x164a63b9, 0x378e50b6, 0x493b70bb,
+       0x61bda47d, 0x1f088470, 0x30b00df1, 0x4e052dfc, 0x6683f93a, 0x1836d937,
+       0x250b9f24, 0x5bbebf29, 0x73386bef, 0x0d8d4be2, 0x2235c263, 0x5c80e26e,
+       0x740636a8, 0x0ab316a5, 0x2b7725aa, 0x55c205a7, 0x7d44d161, 0x03f1f16c,
+       0x2c4978ed, 0x52fc58e0, 0x7a7a8c26, 0x04cfac2b, 0x73e5d470, 0x0d50f47d,
+       0x25d620bb, 0x5b6300b6, 0x74db8937, 0x0a6ea93a, 0x22e87dfc, 0x5c5d5df1,
+       0x7d996efe, 0x032c4ef3, 0x2baa9a35, 0x551fba38, 0x7aa733b9, 0x041213b4,
+       0x2c94c772, 0x5221e77f, 0x6f1ca16c, 0x11a98161, 0x392f55a7, 0x479a75aa,
+       0x6822fc2b, 0x1697dc26, 0x3e1108e0, 0x40a428ed, 0x61601be2, 0x1fd53bef,
+       0x3753ef29, 0x49e6cf24, 0x665e46a5, 0x18eb66a8, 0x306db26e, 0x4ed89263,
+       0x4a173e48, 0x34a21e45, 0x1c24ca83, 0x6291ea8e, 0x4d29630f, 0x339c4302,
+       0x1b1a97c4, 0x65afb7c9, 0x446b84c6, 0x3adea4cb, 0x1258700d, 0x6ced5000,
+       0x4355d981, 0x3de0f98c, 0x15662d4a, 0x6bd30d47, 0x56ee4b54, 0x285b6b59,
+       0x00ddbf9f, 0x7e689f92, 0x51d01613, 0x2f65361e, 0x07e3e2d8, 0x7956c2d5,
+       0x5892f1da, 0x2627d1d7, 0x0ea10511, 0x7014251c, 0x5facac9d, 0x21198c90,
+       0x099f5856, 0x772a785b, 0x4c921c31, 0x32273c3c, 0x1aa1e8fa, 0x6414c8f7,
+       0x4bac4176, 0x3519617b, 0x1d9fb5bd, 0x632a95b0, 0x42eea6bf, 0x3c5b86b2,
+       0x14dd5274, 0x6a687279, 0x45d0fbf8, 0x3b65dbf5, 0x13e30f33, 0x6d562f3e,
+       0x506b692d, 0x2ede4920, 0x06589de6, 0x78edbdeb, 0x5755346a, 0x29e01467,
+       0x0166c0a1, 0x7fd3e0ac, 0x5e17d3a3, 0x20a2f3ae, 0x08242768, 0x76910765,
+       0x59298ee4, 0x279caee9, 0x0f1a7a2f, 0x71af5a22, 0x7560f609, 0x0bd5d604,
+       0x235302c2, 0x5de622cf, 0x725eab4e, 0x0ceb8b43, 0x246d5f85, 0x5ad87f88,
+       0x7b1c4c87, 0x05a96c8a, 0x2d2fb84c, 0x539a9841, 0x7c2211c0, 0x029731cd,
+       0x2a11e50b, 0x54a4c506, 0x69998315, 0x172ca318, 0x3faa77de, 0x411f57d3,
+       0x6ea7de52, 0x1012fe5f, 0x38942a99, 0x46210a94, 0x67e5399b, 0x19501996,
+       0x31d6cd50, 0x4f63ed5d, 0x60db64dc, 0x1e6e44d1, 0x36e89017, 0x485db01a,
+       0x3f77c841, 0x41c2e84c, 0x69443c8a, 0x17f11c87, 0x38499506, 0x46fcb50b,
+       0x6e7a61cd, 0x10cf41c0, 0x310b72cf, 0x4fbe52c2, 0x67388604, 0x198da609,
+       0x36352f88, 0x48800f85, 0x6006db43, 0x1eb3fb4e, 0x238ebd5d, 0x5d3b9d50,
+       0x75bd4996, 0x0b08699b, 0x24b0e01a, 0x5a05c017, 0x728314d1, 0x0c3634dc,
+       0x2df207d3, 0x534727de, 0x7bc1f318, 0x0574d315, 0x2acc5a94, 0x54797a99,
+       0x7cffae5f, 0x024a8e52, 0x06852279, 0x78300274, 0x50b6d6b2, 0x2e03f6bf,
+       0x01bb7f3e, 0x7f0e5f33, 0x57888bf5, 0x293dabf8, 0x08f998f7, 0x764cb8fa,
+       0x5eca6c3c, 0x207f4c31, 0x0fc7c5b0, 0x7172e5bd, 0x59f4317b, 0x27411176,
+       0x1a7c5765, 0x64c97768, 0x4c4fa3ae, 0x32fa83a3, 0x1d420a22, 0x63f72a2f,
+       0x4b71fee9, 0x35c4dee4, 0x1400edeb, 0x6ab5cde6, 0x42331920, 0x3c86392d,
+       0x133eb0ac, 0x6d8b90a1, 0x450d4467, 0x3bb8646a
+};
 
-struct index {
+struct index_entry {
        const unsigned char *ptr;
        unsigned int val;
-       struct index *next;
+       struct index_entry *next;
+};
+
+struct delta_index {
+       const void *src_buf;
+       unsigned long src_size;
+       unsigned int hash_mask;
+       struct index_entry *hash[0];
 };
 
-static struct index ** delta_index(const unsigned char *buf,
-                                  unsigned long bufsize,
-                                  unsigned long trg_bufsize,
-                                  unsigned int *hash_shift)
+struct delta_index * create_delta_index(const void *buf, unsigned long bufsize)
 {
-       unsigned int i, hsize, hshift, hlimit, entries, *hash_count;
-       const unsigned char *data;
-       struct index *entry, **hash;
+       unsigned int i, hsize, hmask, entries, prev_val, *hash_count;
+       const unsigned char *data, *buffer = buf;
+       struct delta_index *index;
+       struct index_entry *entry, **hash;
        void *mem;
+       unsigned long memsize;
+
+       if (!buf || !bufsize)
+               return NULL;
 
-       /* determine index hash size */
-       entries = bufsize  / BLK_SIZE;
+       /* Determine index hash size.  Note that indexing skips the
+          first byte to allow for optimizing the rabin polynomial
+          initialization in create_delta(). */
+       entries = (bufsize - 1)  / RABIN_WINDOW;
        hsize = entries / 4;
        for (i = 4; (1 << i) < hsize && i < 31; i++);
        hsize = 1 << i;
-       hshift = 32 - i;
-       *hash_shift = hshift;
+       hmask = hsize - 1;
 
        /* allocate lookup index */
-       mem = malloc(hsize * sizeof(*hash) + entries * sizeof(*entry));
+       memsize = sizeof(*index) +
+                 sizeof(*hash) * hsize +
+                 sizeof(*entry) * entries;
+       mem = malloc(memsize);
        if (!mem)
                return NULL;
+       index = mem;
+       mem = index + 1;
        hash = mem;
-       entry = mem + hsize * sizeof(*hash);
+       mem = hash + hsize;
+       entry = mem;
+
+       index->src_buf = buf;
+       index->src_size = bufsize;
+       index->hash_mask = hmask;
        memset(hash, 0, hsize * sizeof(*hash));
 
        /* allocate an array to count hash entries */
        hash_count = calloc(hsize, sizeof(*hash_count));
        if (!hash_count) {
-               free(hash);
+               free(index);
                return NULL;
        }
 
        /* then populate the index */
-       data = buf + entries * BLK_SIZE - BLK_SIZE;
-       while (data >= buf) {
-               unsigned int val = adler32(0, data, BLK_SIZE);
-               i = HASH(val, hshift);
-               entry->ptr = data;
-               entry->val = val;
-               entry->next = hash[i];
-               hash[i] = entry++;
-               hash_count[i]++;
-               data -= BLK_SIZE;
-       }
+       prev_val = ~0;
+       for (data = buffer + entries * RABIN_WINDOW - RABIN_WINDOW;
+            data >= buffer;
+            data -= RABIN_WINDOW) {
+               unsigned int val = 0;
+               for (i = 1; i <= RABIN_WINDOW; i++)
+                       val = ((val << 8) | data[i]) ^ T[val >> RABIN_SHIFT];
+               if (val == prev_val) {
+                       /* keep the lowest of consecutive identical blocks */
+                       entry[-1].ptr = data + RABIN_WINDOW;
+               } else {
+                       prev_val = val;
+                       i = val & hmask;
+                       entry->ptr = data + RABIN_WINDOW;
+                       entry->val = val;
+                       entry->next = hash[i];
+                       hash[i] = entry++;
+                       hash_count[i]++;
+               }
+       }
 
        /*
         * Determine a limit on the number of entries in the same hash
@@ -91,27 +209,18 @@ static struct index ** delta_index(const unsigned char *buf,
         * bucket that would bring us to O(m*n) computing costs (m and n
         * corresponding to reference and target buffer sizes).
         *
-        * The more the target buffer is large, the more it is important to
-        * have small entry lists for each hash buckets.  With such a limit
-        * the cost is bounded to something more like O(m+n).
-        */
-       hlimit = (1 << 26) / trg_bufsize;
-       if (hlimit < 4*BLK_SIZE)
-               hlimit = 4*BLK_SIZE;
-
-       /*
-        * Now make sure none of the hash buckets has more entries than
+        * Make sure none of the hash buckets has more entries than
         * we're willing to test.  Otherwise we cull the entry list
         * uniformly to still preserve a good repartition across
         * the reference buffer.
         */
        for (i = 0; i < hsize; i++) {
-               if (hash_count[i] < hlimit)
+               if (hash_count[i] < HASH_LIMIT)
                        continue;
                entry = hash[i];
                do {
-                       struct index *keep = entry;
-                       int skip = hash_count[i] / hlimit / 2;
+                       struct index_entry *keep = entry;
+                       int skip = hash_count[i] / HASH_LIMIT / 2;
                        do {
                                entry = entry->next;
                        } while(--skip && entry);
@@ -120,32 +229,31 @@ static struct index ** delta_index(const unsigned char *buf,
        }
        free(hash_count);
 
-       return hash;
+       return index;
 }
 
-/* provide the size of the copy opcode given the block offset and size */
-#define COPYOP_SIZE(o, s) \
-    (!!(o & 0xff) + !!(o & 0xff00) + !!(o & 0xff0000) + !!(o & 0xff000000) + \
-     !!(s & 0xff) + !!(s & 0xff00) + 1)
+void free_delta_index(struct delta_index *index)
+{
+       free(index);
+}
 
-/* the maximum size for any opcode */
-#define MAX_OP_SIZE COPYOP_SIZE(0xffffffff, 0xffffffff)
+/*
+ * The maximum size for any opcode sequence, including the initial header
+ * plus rabin window plus biggest copy.
+ */
+#define MAX_OP_SIZE    (5 + 5 + 1 + RABIN_WINDOW + 7)
 
-void *diff_delta(void *from_buf, unsigned long from_size,
-                void *to_buf, unsigned long to_size,
-                unsigned long *delta_size,
-                unsigned long max_size)
+void *
+create_delta(const struct delta_index *index,
+            const void *trg_buf, unsigned long trg_size,
+            unsigned long *delta_size, unsigned long max_size)
 {
-       unsigned int i, outpos, outsize, hash_shift;
+       unsigned int i, outpos, outsize, val;
        int inscnt;
        const unsigned char *ref_data, *ref_top, *data, *top;
        unsigned char *out;
-       struct index *entry, **hash;
 
-       if (!from_size || !to_size)
-               return NULL;
-       hash = delta_index(from_buf, from_size, to_size, &hash_shift);
-       if (!hash)
+       if (!trg_buf || !trg_size)
                return NULL;
 
        outpos = 0;
@@ -153,64 +261,66 @@ void *diff_delta(void *from_buf, unsigned long from_size,
        if (max_size && outsize >= max_size)
                outsize = max_size + MAX_OP_SIZE + 1;
        out = malloc(outsize);
-       if (!out) {
-               free(hash);
+       if (!out)
                return NULL;
-       }
-
-       ref_data = from_buf;
-       ref_top = from_buf + from_size;
-       data = to_buf;
-       top = to_buf + to_size;
 
        /* store reference buffer size */
-       out[outpos++] = from_size;
-       from_size >>= 7;
-       while (from_size) {
-               out[outpos - 1] |= 0x80;
-               out[outpos++] = from_size;
-               from_size >>= 7;
+       i = index->src_size;
+       while (i >= 0x80) {
+               out[outpos++] = i | 0x80;
+               i >>= 7;
        }
+       out[outpos++] = i;
 
        /* store target buffer size */
-       out[outpos++] = to_size;
-       to_size >>= 7;
-       while (to_size) {
-               out[outpos - 1] |= 0x80;
-               out[outpos++] = to_size;
-               to_size >>= 7;
+       i = trg_size;
+       while (i >= 0x80) {
+               out[outpos++] = i | 0x80;
+               i >>= 7;
        }
-
-       inscnt = 0;
+       out[outpos++] = i;
+
+       ref_data = index->src_buf;
+       ref_top = ref_data + index->src_size;
+       data = trg_buf;
+       top = trg_buf + trg_size;
+
+       outpos++;
+       val = 0;
+       for (i = 0; i < RABIN_WINDOW && data < top; i++, data++) {
+               out[outpos++] = *data;
+               val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT];
+       }
+       inscnt = i;
 
        while (data < top) {
                unsigned int moff = 0, msize = 0;
-               if (data + BLK_SIZE <= top) {
-                       unsigned int val = adler32(0, data, BLK_SIZE);
-                       i = HASH(val, hash_shift);
-                       for (entry = hash[i]; entry; entry = entry->next) {
-                               const unsigned char *ref = entry->ptr;
-                               const unsigned char *src = data;
-                               unsigned int ref_size = ref_top - ref;
-                               if (entry->val != val)
-                                       continue;
-                               if (ref_size > top - src)
-                                       ref_size = top - src;
-                               if (ref_size > 0x10000)
-                                       ref_size = 0x10000;
-                               if (ref_size <= msize)
-                                       break;
-                               while (ref_size-- && *src++ == *ref)
-                                       ref++;
-                               if (msize < ref - entry->ptr) {
-                                       /* this is our best match so far */
-                                       msize = ref - entry->ptr;
-                                       moff = entry->ptr - ref_data;
-                               }
+               struct index_entry *entry;
+               val ^= U[data[-RABIN_WINDOW]];
+               val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT];
+               i = val & index->hash_mask;
+               for (entry = index->hash[i]; entry; entry = entry->next) {
+                       const unsigned char *ref = entry->ptr;
+                       const unsigned char *src = data;
+                       unsigned int ref_size = ref_top - ref;
+                       if (entry->val != val)
+                               continue;
+                       if (ref_size > top - src)
+                               ref_size = top - src;
+                       if (ref_size > 0x10000)
+                               ref_size = 0x10000;
+                       if (ref_size <= msize)
+                               break;
+                       while (ref_size-- && *src++ == *ref)
+                               ref++;
+                       if (msize < ref - entry->ptr) {
+                               /* this is our best match so far */
+                               msize = ref - entry->ptr;
+                               moff = entry->ptr - ref_data;
                        }
                }
 
-               if (!msize || msize < COPYOP_SIZE(moff, msize)) {
+               if (msize < 4) {
                        if (!inscnt)
                                outpos++;
                        out[outpos++] = *data++;
@@ -222,6 +332,20 @@ void *diff_delta(void *from_buf, unsigned long from_size,
                } else {
                        unsigned char *op;
 
+                       if (msize >= RABIN_WINDOW) {
+                               const unsigned char *sk;
+                               sk = data + msize - RABIN_WINDOW;
+                               val = 0;
+                               for (i = 0; i < RABIN_WINDOW; i++)
+                                       val = ((val << 8) | *sk++) ^ T[val >> RABIN_SHIFT];
+                       } else {
+                               const unsigned char *sk = data + 1;
+                               for (i = 1; i < msize; i++) {
+                                       val ^= U[sk[-RABIN_WINDOW]];
+                                       val = ((val << 8) | *sk++) ^ T[val >> RABIN_SHIFT];
+                               }
+                       }
+
                        if (inscnt) {
                                while (moff && ref_data[moff-1] == data[-1]) {
                                        if (msize == 0x10000)
@@ -266,12 +390,10 @@ void *diff_delta(void *from_buf, unsigned long from_size,
                        if (max_size && outsize >= max_size)
                                outsize = max_size + MAX_OP_SIZE + 1;
                        if (max_size && outpos > max_size)
-                               out = NULL;
-                       else
-                               out = realloc(out, outsize);
+                               break;
+                       out = realloc(out, outsize);
                        if (!out) {
                                free(tmp);
-                               free(hash);
                                return NULL;
                        }
                }
@@ -280,7 +402,11 @@ void *diff_delta(void *from_buf, unsigned long from_size,
        if (inscnt)
                out[outpos - inscnt - 1] = inscnt;
 
-       free(hash);
+       if (max_size && outpos > max_size) {
+               free(out);
+               return NULL;
+       }
+
        *delta_size = outpos;
        return out;
 }
index ffbef48..b9d193d 100644 (file)
@@ -12,203 +12,43 @@ static const char diff_files_usage[] =
 "git-diff-files [-q] [-0/-1/2/3 |-c|--cc] [<common diff options>] [<path>...]"
 COMMON_DIFF_OPTIONS_HELP;
 
-static struct rev_info rev;
-static int silent = 0;
-static int diff_unmerged_stage = 2;
-static int combine_merges = 0;
-static int dense_combined_merges = 0;
-
-static void show_unmerge(const char *path)
-{
-       diff_unmerge(&rev.diffopt, path);
-}
-
-static void show_file(int pfx, struct cache_entry *ce)
-{
-       diff_addremove(&rev.diffopt, pfx, ntohl(ce->ce_mode),
-                      ce->sha1, ce->name, NULL);
-}
-
-static void show_modified(int oldmode, int mode,
-                         const unsigned char *old_sha1, const unsigned char *sha1,
-                         char *path)
-{
-       diff_change(&rev.diffopt, oldmode, mode, old_sha1, sha1, path, NULL);
-}
-
 int main(int argc, const char **argv)
 {
-       const char **pathspec;
-       const char *prefix = setup_git_directory();
-       int entries, i;
+       struct rev_info rev;
+       int silent = 0;
 
        git_config(git_diff_config);
-       diff_setup(&rev.diffopt);
+       init_revisions(&rev);
+       rev.abbrev = 0;
+
+       argc = setup_revisions(argc, argv, &rev, NULL);
        while (1 < argc && argv[1][0] == '-') {
-               if (!strcmp(argv[1], "--")) {
-                       argv++;
-                       argc--;
-                       break;
-               }
-               if (!strcmp(argv[1], "-0"))
-                       diff_unmerged_stage = 0;
-               else if (!strcmp(argv[1], "-1"))
-                       diff_unmerged_stage = 1;
-               else if (!strcmp(argv[1], "-2"))
-                       diff_unmerged_stage = 2;
-               else if (!strcmp(argv[1], "-3"))
-                       diff_unmerged_stage = 3;
-               else if (!strcmp(argv[1], "--base"))
-                       diff_unmerged_stage = 1;
+               if (!strcmp(argv[1], "--base"))
+                       rev.max_count = 1;
                else if (!strcmp(argv[1], "--ours"))
-                       diff_unmerged_stage = 2;
+                       rev.max_count = 2;
                else if (!strcmp(argv[1], "--theirs"))
-                       diff_unmerged_stage = 3;
+                       rev.max_count = 3;
                else if (!strcmp(argv[1], "-q"))
                        silent = 1;
-               else if (!strcmp(argv[1], "-r"))
-                       ; /* no-op */
-               else if (!strcmp(argv[1], "-s"))
-                       ; /* no-op */
-               else if (!strcmp(argv[1], "-c"))
-                       combine_merges = 1;
-               else if (!strcmp(argv[1], "--cc"))
-                       dense_combined_merges = combine_merges = 1;
-               else {
-                       int diff_opt_cnt;
-                       diff_opt_cnt = diff_opt_parse(&rev.diffopt,
-                                                     argv+1, argc-1);
-                       if (diff_opt_cnt < 0)
-                               usage(diff_files_usage);
-                       else if (diff_opt_cnt) {
-                               argv += diff_opt_cnt;
-                               argc -= diff_opt_cnt;
-                               continue;
-                       }
-                       else
-                               usage(diff_files_usage);
-               }
+               else
+                       usage(diff_files_usage);
                argv++; argc--;
        }
-       if (dense_combined_merges)
-               rev.diffopt.output_format = DIFF_FORMAT_PATCH;
-
-       /* Find the directory, and set up the pathspec */
-       pathspec = get_pathspec(prefix, argv + 1);
-       entries = read_cache();
-
-       if (diff_setup_done(&rev.diffopt) < 0)
+       /*
+        * 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_objects ||
+           rev.min_age != -1 || rev.max_age != -1)
                usage(diff_files_usage);
-
-       /* At this point, if argc == 1, then we are doing everything.
-        * Otherwise argv[1] .. argv[argc-1] have the explicit paths.
+       /*
+        * Backward compatibility wart - "diff-files -s" used to
+        * defeat the common diff option "-s" which asked for
+        * DIFF_FORMAT_NO_OUTPUT.
         */
-       if (entries < 0) {
-               perror("read_cache");
-               exit(1);
-       }
-
-       for (i = 0; i < entries; i++) {
-               struct stat st;
-               unsigned int oldmode, newmode;
-               struct cache_entry *ce = active_cache[i];
-               int changed;
-
-               if (!ce_path_match(ce, pathspec))
-                       continue;
-
-               if (ce_stage(ce)) {
-                       struct {
-                               struct combine_diff_path p;
-                               struct combine_diff_parent filler[5];
-                       } combine;
-                       int num_compare_stages = 0;
-
-                       combine.p.next = NULL;
-                       combine.p.len = ce_namelen(ce);
-                       combine.p.path = xmalloc(combine.p.len + 1);
-                       memcpy(combine.p.path, ce->name, combine.p.len);
-                       combine.p.path[combine.p.len] = 0;
-                       combine.p.mode = 0;
-                       memset(combine.p.sha1, 0, 20);
-                       memset(&combine.p.parent[0], 0,
-                              sizeof(combine.filler));
-
-                       while (i < entries) {
-                               struct cache_entry *nce = active_cache[i];
-                               int stage;
-
-                               if (strcmp(ce->name, nce->name))
-                                       break;
-
-                               /* Stage #2 (ours) is the first parent,
-                                * stage #3 (theirs) is the second.
-                                */
-                               stage = ce_stage(nce);
-                               if (2 <= stage) {
-                                       int mode = ntohl(nce->ce_mode);
-                                       num_compare_stages++;
-                                       memcpy(combine.p.parent[stage-2].sha1,
-                                              nce->sha1, 20);
-                                       combine.p.parent[stage-2].mode =
-                                               canon_mode(mode);
-                                       combine.p.parent[stage-2].status =
-                                               DIFF_STATUS_MODIFIED;
-                               }
-
-                               /* diff against the proper unmerged stage */
-                               if (stage == diff_unmerged_stage)
-                                       ce = nce;
-                               i++;
-                       }
-                       /*
-                        * Compensate for loop update
-                        */
-                       i--;
-
-                       if (combine_merges && num_compare_stages == 2) {
-                               show_combined_diff(&combine.p, 2,
-                                                  dense_combined_merges,
-                                                  &rev);
-                               free(combine.p.path);
-                               continue;
-                       }
-                       free(combine.p.path);
-
-                       /*
-                        * Show the diff for the 'ce' if we found the one
-                        * from the desired stage.
-                        */
-                       show_unmerge(ce->name);
-                       if (ce_stage(ce) != diff_unmerged_stage)
-                               continue;
-               }
-
-               if (lstat(ce->name, &st) < 0) {
-                       if (errno != ENOENT && errno != ENOTDIR) {
-                               perror(ce->name);
-                               continue;
-                       }
-                       if (silent)
-                               continue;
-                       show_file('-', ce);
-                       continue;
-               }
-               changed = ce_match_stat(ce, &st, 0);
-               if (!changed && !rev.diffopt.find_copies_harder)
-                       continue;
-               oldmode = ntohl(ce->ce_mode);
-
-               newmode = canon_mode(st.st_mode);
-               if (!trust_executable_bit &&
-                   S_ISREG(newmode) && S_ISREG(oldmode) &&
-                   ((newmode ^ oldmode) == 0111))
-                       newmode = oldmode;
-               show_modified(oldmode, newmode,
-                             ce->sha1, (changed ? null_sha1 : ce->sha1),
-                             ce->name);
-       }
-       diffcore_std(&rev.diffopt);
-       diff_flush(&rev.diffopt);
-       return 0;
+       if (rev.diffopt.output_format == DIFF_FORMAT_NO_OUTPUT)
+               rev.diffopt.output_format = DIFF_FORMAT_RAW;
+       return run_diff_files(&rev, silent);
 }
index e376d65..8c9f601 100644 (file)
@@ -1,166 +1,7 @@
 #include "cache.h"
-#include "tree.h"
 #include "diff.h"
-
-static int cached_only = 0;
-static int match_nonexisting = 0;
-static struct diff_options diff_options;
-
-/* A file entry went away or appeared */
-static void show_file(const char *prefix,
-                     struct cache_entry *ce,
-                     unsigned char *sha1, unsigned int mode)
-{
-       diff_addremove(&diff_options, prefix[0], ntohl(mode),
-                      sha1, ce->name, NULL);
-}
-
-static int get_stat_data(struct cache_entry *ce,
-                        unsigned char ** sha1p, unsigned int *modep)
-{
-       unsigned char *sha1 = ce->sha1;
-       unsigned int mode = ce->ce_mode;
-
-       if (!cached_only) {
-               static unsigned char no_sha1[20];
-               int changed;
-               struct stat st;
-               if (lstat(ce->name, &st) < 0) {
-                       if (errno == ENOENT && match_nonexisting) {
-                               *sha1p = sha1;
-                               *modep = mode;
-                               return 0;
-                       }
-                       return -1;
-               }
-               changed = ce_match_stat(ce, &st, 0);
-               if (changed) {
-                       mode = create_ce_mode(st.st_mode);
-                       if (!trust_executable_bit && S_ISREG(st.st_mode))
-                               mode = ce->ce_mode;
-                       sha1 = no_sha1;
-               }
-       }
-
-       *sha1p = sha1;
-       *modep = mode;
-       return 0;
-}
-
-static void show_new_file(struct cache_entry *new)
-{
-       unsigned char *sha1;
-       unsigned int mode;
-
-       /* New file in the index: it might actually be different in
-        * the working copy.
-        */
-       if (get_stat_data(new, &sha1, &mode) < 0)
-               return;
-
-       show_file("+", new, sha1, mode);
-}
-
-static int show_modified(struct cache_entry *old,
-                        struct cache_entry *new,
-                        int report_missing)
-{
-       unsigned int mode, oldmode;
-       unsigned char *sha1;
-
-       if (get_stat_data(new, &sha1, &mode) < 0) {
-               if (report_missing)
-                       show_file("-", old, old->sha1, old->ce_mode);
-               return -1;
-       }
-
-       oldmode = old->ce_mode;
-       if (mode == oldmode && !memcmp(sha1, old->sha1, 20) &&
-           !diff_options.find_copies_harder)
-               return 0;
-
-       mode = ntohl(mode);
-       oldmode = ntohl(oldmode);
-
-       diff_change(&diff_options, oldmode, mode,
-                   old->sha1, sha1, old->name, NULL);
-       return 0;
-}
-
-static int diff_cache(struct cache_entry **ac, int entries, const char **pathspec)
-{
-       while (entries) {
-               struct cache_entry *ce = *ac;
-               int same = (entries > 1) && ce_same_name(ce, ac[1]);
-
-               if (!ce_path_match(ce, pathspec))
-                       goto skip_entry;
-
-               switch (ce_stage(ce)) {
-               case 0:
-                       /* No stage 1 entry? That means it's a new file */
-                       if (!same) {
-                               show_new_file(ce);
-                               break;
-                       }
-                       /* Show difference between old and new */
-                       show_modified(ac[1], ce, 1);
-                       break;
-               case 1:
-                       /* No stage 3 (merge) entry? That means it's been deleted */
-                       if (!same) {
-                               show_file("-", ce, ce->sha1, ce->ce_mode);
-                               break;
-                       }
-                       /* We come here with ce pointing at stage 1
-                        * (original tree) and ac[1] pointing at stage
-                        * 3 (unmerged).  show-modified with
-                        * report-missing set to false does not say the
-                        * file is deleted but reports true if work
-                        * tree does not have it, in which case we
-                        * fall through to report the unmerged state.
-                        * Otherwise, we show the differences between
-                        * the original tree and the work tree.
-                        */
-                       if (!cached_only && !show_modified(ce, ac[1], 0))
-                               break;
-                       /* fallthru */
-               case 3:
-                       diff_unmerge(&diff_options, ce->name);
-                       break;
-
-               default:
-                       die("impossible cache entry stage");
-               }
-
-skip_entry:
-               /*
-                * Ignore all the different stages for this file,
-                * we've handled the relevant cases now.
-                */
-               do {
-                       ac++;
-                       entries--;
-               } while (entries && ce_same_name(ce, ac[0]));
-       }
-       return 0;
-}
-
-/*
- * This turns all merge entries into "stage 3". That guarantees that
- * when we read in the new tree (into "stage 1"), we won't lose sight
- * of the fact that we had unmerged entries.
- */
-static void mark_merge_entries(void)
-{
-       int i;
-       for (i = 0; i < active_nr; i++) {
-               struct cache_entry *ce = active_cache[i];
-               if (!ce_stage(ce))
-                       continue;
-               ce->ce_flags |= htons(CE_STAGEMASK);
-       }
-}
+#include "commit.h"
+#include "revision.h"
 
 static const char diff_cache_usage[] =
 "git-diff-index [-m] [--cached] "
@@ -169,85 +10,29 @@ COMMON_DIFF_OPTIONS_HELP;
 
 int main(int argc, const char **argv)
 {
-       const char *tree_name = NULL;
-       unsigned char sha1[20];
-       const char *prefix = setup_git_directory();
-       const char **pathspec = NULL;
-       struct tree *tree;
-       int ret;
-       int allow_options = 1;
+       struct rev_info rev;
+       int cached = 0;
        int i;
 
        git_config(git_diff_config);
-       diff_setup(&diff_options);
+       init_revisions(&rev);
+       rev.abbrev = 0;
+
+       argc = setup_revisions(argc, argv, &rev, NULL);
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
-               int diff_opt_cnt;
-
-               if (!allow_options || *arg != '-') {
-                       if (tree_name)
-                               break;
-                       tree_name = arg;
-                       continue;
-               }
                        
-               if (!strcmp(arg, "--")) {
-                       allow_options = 0;
-                       continue;
-               }
-               if (!strcmp(arg, "-r")) {
-                       /* We accept the -r flag just to look like git-diff-tree */
-                       continue;
-               }
-               if (!strcmp(arg, "--cc"))
-                       /*
-                        * I _think_ "diff-index --cached HEAD" with an
-                        * unmerged index could show something else
-                        * later, but pretend --cc is the same as -p for
-                        * now.  "git diff" uses --cc by default.
-                        */
-                       argv[i] = arg = "-p";
-               diff_opt_cnt = diff_opt_parse(&diff_options, argv + i,
-                                             argc - i);
-               if (diff_opt_cnt < 0)
+               if (!strcmp(arg, "--cached"))
+                       cached = 1;
+               else
                        usage(diff_cache_usage);
-               else if (diff_opt_cnt) {
-                       i += diff_opt_cnt - 1;
-                       continue;
-               }
-
-               if (!strcmp(arg, "-m")) {
-                       match_nonexisting = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--cached")) {
-                       cached_only = 1;
-                       continue;
-               }
-               usage(diff_cache_usage);
        }
-
-       pathspec = get_pathspec(prefix, argv + i);
-
-       if (diff_setup_done(&diff_options) < 0)
-               usage(diff_cache_usage);
-
-       if (!tree_name || get_sha1(tree_name, sha1))
+       /*
+        * Make sure there is one revision (i.e. pending object),
+        * and there is no revision filtering parameters.
+        */
+       if (!rev.pending_objects || rev.pending_objects->next ||
+           rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1)
                usage(diff_cache_usage);
-
-       read_cache();
-
-       mark_merge_entries();
-
-       tree = parse_tree_indirect(sha1);
-       if (!tree)
-               die("bad tree object %s", tree_name);
-       if (read_tree(tree, 1, pathspec))
-               die("unable to read tree object %s", tree_name);
-
-       ret = diff_cache(active_cache, active_nr, pathspec);
-
-       diffcore_std(&diff_options);
-       diff_flush(&diff_options);
-       return ret;
+       return run_diff_index(&rev, cached);
 }
index 0a832c3..2183b41 100644 (file)
 /*
  * Copyright (C) 2005 Junio C Hamano
  */
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <signal.h>
 #include "cache.h"
 #include "quote.h"
+#include "commit.h"
 #include "diff.h"
 #include "diffcore.h"
-#include "xdiff-interface.h"
+#include "revision.h"
 
-static int use_size_cache;
-
-int diff_rename_limit_default = -1;
-
-int git_diff_config(const char *var, const char *value)
-{
-       if (!strcmp(var, "diff.renamelimit")) {
-               diff_rename_limit_default = git_config_int(var, value);
-               return 0;
-       }
-
-       return git_default_config(var, value);
-}
-
-static char *quote_one(const char *str)
-{
-       int needlen;
-       char *xp;
-
-       if (!str)
-               return NULL;
-       needlen = quote_c_style(str, NULL, NULL, 0);
-       if (!needlen)
-               return strdup(str);
-       xp = xmalloc(needlen + 1);
-       quote_c_style(str, xp, NULL, 0);
-       return xp;
-}
-
-static char *quote_two(const char *one, const char *two)
-{
-       int need_one = quote_c_style(one, NULL, NULL, 1);
-       int need_two = quote_c_style(two, NULL, NULL, 1);
-       char *xp;
-
-       if (need_one + need_two) {
-               if (!need_one) need_one = strlen(one);
-               if (!need_two) need_one = strlen(two);
-
-               xp = xmalloc(need_one + need_two + 3);
-               xp[0] = '"';
-               quote_c_style(one, xp + 1, NULL, 1);
-               quote_c_style(two, xp + need_one + 1, NULL, 1);
-               strcpy(xp + need_one + need_two + 1, "\"");
-               return xp;
-       }
-       need_one = strlen(one);
-       need_two = strlen(two);
-       xp = xmalloc(need_one + need_two + 1);
-       strcpy(xp, one);
-       strcpy(xp + need_one, two);
-       return xp;
-}
-
-static const char *external_diff(void)
-{
-       static const char *external_diff_cmd = NULL;
-       static int done_preparing = 0;
-
-       if (done_preparing)
-               return external_diff_cmd;
-       external_diff_cmd = getenv("GIT_EXTERNAL_DIFF");
-       done_preparing = 1;
-       return external_diff_cmd;
-}
-
-#define TEMPFILE_PATH_LEN              50
-
-static struct diff_tempfile {
-       const char *name; /* filename external diff should read from */
-       char hex[41];
-       char mode[10];
-       char tmp_path[TEMPFILE_PATH_LEN];
-} diff_temp[2];
-
-static int count_lines(const char *data, int size)
-{
-       int count, ch, completely_empty = 1, nl_just_seen = 0;
-       count = 0;
-       while (0 < size--) {
-               ch = *data++;
-               if (ch == '\n') {
-                       count++;
-                       nl_just_seen = 1;
-                       completely_empty = 0;
-               }
-               else {
-                       nl_just_seen = 0;
-                       completely_empty = 0;
-               }
-       }
-       if (completely_empty)
-               return 0;
-       if (!nl_just_seen)
-               count++; /* no trailing newline */
-       return count;
-}
-
-static void print_line_count(int count)
-{
-       switch (count) {
-       case 0:
-               printf("0,0");
-               break;
-       case 1:
-               printf("1");
-               break;
-       default:
-               printf("1,%d", count);
-               break;
-       }
-}
-
-static void copy_file(int prefix, const char *data, int size)
-{
-       int ch, nl_just_seen = 1;
-       while (0 < size--) {
-               ch = *data++;
-               if (nl_just_seen)
-                       putchar(prefix);
-               putchar(ch);
-               if (ch == '\n')
-                       nl_just_seen = 1;
-               else
-                       nl_just_seen = 0;
-       }
-       if (!nl_just_seen)
-               printf("\n\\ No newline at end of file\n");
-}
+/*
+ * diff-files
+ */
 
-static void emit_rewrite_diff(const char *name_a,
-                             const char *name_b,
-                             struct diff_filespec *one,
-                             struct diff_filespec *two)
+int run_diff_files(struct rev_info *revs, int silent_on_removed)
 {
-       int lc_a, lc_b;
-       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);
-       printf("--- %s\n+++ %s\n@@ -", name_a, name_b);
-       print_line_count(lc_a);
-       printf(" +");
-       print_line_count(lc_b);
-       printf(" @@\n");
-       if (lc_a)
-               copy_file('-', one->data, one->size);
-       if (lc_b)
-               copy_file('+', two->data, two->size);
-}
+       int entries, i;
+       int diff_unmerged_stage = revs->max_count;
 
-static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
-{
-       if (!DIFF_FILE_VALID(one)) {
-               mf->ptr = ""; /* does not matter */
-               mf->size = 0;
-               return 0;
-       }
-       else if (diff_populate_filespec(one, 0))
+       if (diff_unmerged_stage < 0)
+               diff_unmerged_stage = 2;
+       entries = read_cache();
+       if (entries < 0) {
+               perror("read_cache");
                return -1;
-       mf->ptr = one->data;
-       mf->size = one->size;
-       return 0;
-}
-
-struct emit_callback {
-       const char **label_path;
-};
-
-static int fn_out(void *priv, mmbuffer_t *mb, int nbuf)
-{
-       int i;
-       struct emit_callback *ecbdata = priv;
-
-       if (ecbdata->label_path[0]) {
-               printf("--- %s\n", ecbdata->label_path[0]);
-               printf("+++ %s\n", ecbdata->label_path[1]);
-               ecbdata->label_path[0] = ecbdata->label_path[1] = NULL;
-       }
-       for (i = 0; i < nbuf; i++)
-               if (!fwrite(mb[i].ptr, mb[i].size, 1, stdout))
-                       return -1;
-       return 0;
-}
-
-struct diffstat_t {
-       struct xdiff_emit_state xm;
-
-       int nr;
-       int alloc;
-       struct diffstat_file {
-               char *name;
-               unsigned is_unmerged:1;
-               unsigned is_binary:1;
-               unsigned int added, deleted;
-       } **files;
-};
-
-static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,
-               const char *name)
-{
-       struct diffstat_file *x;
-       x = xcalloc(sizeof (*x), 1);
-       if (diffstat->nr == diffstat->alloc) {
-               diffstat->alloc = alloc_nr(diffstat->alloc);
-               diffstat->files = xrealloc(diffstat->files,
-                               diffstat->alloc * sizeof(x));
        }
-       diffstat->files[diffstat->nr++] = x;
-       x->name = strdup(name);
-       return x;
-}
-
-static void diffstat_consume(void *priv, char *line, unsigned long len)
-{
-       struct diffstat_t *diffstat = priv;
-       struct diffstat_file *x = diffstat->files[diffstat->nr - 1];
-
-       if (line[0] == '+')
-               x->added++;
-       else if (line[0] == '-')
-               x->deleted++;
-}
-
-static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
-static const char minuses[]= "----------------------------------------------------------------------";
-
-static void show_stats(struct diffstat_t* data)
-{
-       char *prefix = "";
-       int i, len, add, del, total, adds = 0, dels = 0;
-       int max, max_change = 0, max_len = 0;
-       int total_files = data->nr;
-
-       if (data->nr == 0)
-               return;
-
-       for (i = 0; i < data->nr; i++) {
-               struct diffstat_file *file = data->files[i];
-
-               len = strlen(file->name);
-               if (max_len < len)
-                       max_len = len;
+       for (i = 0; i < entries; i++) {
+               struct stat st;
+               unsigned int oldmode, newmode;
+               struct cache_entry *ce = active_cache[i];
+               int changed;
 
-               if (file->is_binary || file->is_unmerged)
+               if (!ce_path_match(ce, revs->prune_data))
                        continue;
-               if (max_change < file->added + file->deleted)
-                       max_change = file->added + file->deleted;
-       }
 
-       for (i = 0; i < data->nr; i++) {
-               char *name = data->files[i]->name;
-               int added = data->files[i]->added;
-               int deleted = data->files[i]->deleted;
+               if (ce_stage(ce)) {
+                       struct {
+                               struct combine_diff_path p;
+                               struct combine_diff_parent filler[5];
+                       } combine;
+                       int num_compare_stages = 0;
+
+                       combine.p.next = NULL;
+                       combine.p.len = ce_namelen(ce);
+                       combine.p.path = xmalloc(combine.p.len + 1);
+                       memcpy(combine.p.path, ce->name, combine.p.len);
+                       combine.p.path[combine.p.len] = 0;
+                       combine.p.mode = 0;
+                       memset(combine.p.sha1, 0, 20);
+                       memset(&combine.p.parent[0], 0,
+                              sizeof(combine.filler));
+
+                       while (i < entries) {
+                               struct cache_entry *nce = active_cache[i];
+                               int stage;
+
+                               if (strcmp(ce->name, nce->name))
+                                       break;
+
+                               /* Stage #2 (ours) is the first parent,
+                                * stage #3 (theirs) is the second.
+                                */
+                               stage = ce_stage(nce);
+                               if (2 <= stage) {
+                                       int mode = ntohl(nce->ce_mode);
+                                       num_compare_stages++;
+                                       memcpy(combine.p.parent[stage-2].sha1,
+                                              nce->sha1, 20);
+                                       combine.p.parent[stage-2].mode =
+                                               canon_mode(mode);
+                                       combine.p.parent[stage-2].status =
+                                               DIFF_STATUS_MODIFIED;
+                               }
+
+                               /* diff against the proper unmerged stage */
+                               if (stage == diff_unmerged_stage)
+                                       ce = nce;
+                               i++;
+                       }
+                       /*
+                        * Compensate for loop update
+                        */
+                       i--;
 
-               if (0 < (len = quote_c_style(name, NULL, NULL, 0))) {
-                       char *qname = xmalloc(len + 1);
-                       quote_c_style(name, qname, NULL, 0);
-                       free(name);
-                       data->files[i]->name = name = qname;
-               }
+                       if (revs->combine_merges && num_compare_stages == 2) {
+                               show_combined_diff(&combine.p, 2,
+                                                  revs->dense_combined_merges,
+                                                  revs);
+                               free(combine.p.path);
+                               continue;
+                       }
+                       free(combine.p.path);
 
-               /*
-                * "scale" the filename
-                */
-               len = strlen(name);
-               max = max_len;
-               if (max > 50)
-                       max = 50;
-               if (len > max) {
-                       char *slash;
-                       prefix = "...";
-                       max -= 3;
-                       name += len - max;
-                       slash = strchr(name, '/');
-                       if (slash)
-                               name = slash;
+                       /*
+                        * Show the diff for the 'ce' if we found the one
+                        * from the desired stage.
+                        */
+                       diff_unmerge(&revs->diffopt, ce->name);
+                       if (ce_stage(ce) != diff_unmerged_stage)
+                               continue;
                }
-               len = max;
-
-               /*
-                * scale the add/delete
-                */
-               max = max_change;
-               if (max + len > 70)
-                       max = 70 - len;
 
-               if (data->files[i]->is_binary) {
-                       printf(" %s%-*s |  Bin\n", prefix, len, name);
-                       goto free_diffstat_file;
-               }
-               else if (data->files[i]->is_unmerged) {
-                       printf(" %s%-*s |  Unmerged\n", prefix, len, name);
-                       goto free_diffstat_file;
-               }
-               else if (added + deleted == 0) {
-                       total_files--;
-                       goto free_diffstat_file;
+               if (lstat(ce->name, &st) < 0) {
+                       if (errno != ENOENT && errno != ENOTDIR) {
+                               perror(ce->name);
+                               continue;
+                       }
+                       if (silent_on_removed)
+                               continue;
+                       diff_addremove(&revs->diffopt, '-', ntohl(ce->ce_mode),
+                                      ce->sha1, ce->name, NULL);
+                       continue;
                }
+               changed = ce_match_stat(ce, &st, 0);
+               if (!changed && !revs->diffopt.find_copies_harder)
+                       continue;
+               oldmode = ntohl(ce->ce_mode);
 
-               add = added;
-               del = deleted;
-               total = add + del;
-               adds += add;
-               dels += del;
+               newmode = canon_mode(st.st_mode);
+               if (!trust_executable_bit &&
+                   S_ISREG(newmode) && S_ISREG(oldmode) &&
+                   ((newmode ^ oldmode) == 0111))
+                       newmode = oldmode;
+               diff_change(&revs->diffopt, oldmode, newmode,
+                           ce->sha1, (changed ? null_sha1 : ce->sha1),
+                           ce->name, NULL);
 
-               if (max_change > 0) {
-                       total = (total * max + max_change / 2) / max_change;
-                       add = (add * max + max_change / 2) / max_change;
-                       del = total - add;
-               }
-               printf(" %s%-*s |%5d %.*s%.*s\n", prefix,
-                               len, name, added + deleted,
-                               add, pluses, del, minuses);
-       free_diffstat_file:
-               free(data->files[i]->name);
-               free(data->files[i]);
        }
-       free(data->files);
-       printf(" %d files changed, %d insertions(+), %d deletions(-)\n",
-                       total_files, adds, dels);
-}
-
-#define FIRST_FEW_BYTES 8000
-static int mmfile_is_binary(mmfile_t *mf)
-{
-       long sz = mf->size;
-       if (FIRST_FEW_BYTES < sz)
-               sz = FIRST_FEW_BYTES;
-       if (memchr(mf->ptr, 0, sz))
-               return 1;
+       diffcore_std(&revs->diffopt);
+       diff_flush(&revs->diffopt);
        return 0;
 }
 
-static void builtin_diff(const char *name_a,
-                        const char *name_b,
-                        struct diff_filespec *one,
-                        struct diff_filespec *two,
-                        const char *xfrm_msg,
-                        int complete_rewrite)
-{
-       mmfile_t mf1, mf2;
-       const char *lbl[2];
-       char *a_one, *b_two;
-
-       a_one = quote_two("a/", name_a);
-       b_two = quote_two("b/", name_b);
-       lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
-       lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
-       printf("diff --git %s %s\n", a_one, b_two);
-       if (lbl[0][0] == '/') {
-               /* /dev/null */
-               printf("new file mode %06o\n", two->mode);
-               if (xfrm_msg && xfrm_msg[0])
-                       puts(xfrm_msg);
-       }
-       else if (lbl[1][0] == '/') {
-               printf("deleted file mode %06o\n", one->mode);
-               if (xfrm_msg && xfrm_msg[0])
-                       puts(xfrm_msg);
-       }
-       else {
-               if (one->mode != two->mode) {
-                       printf("old mode %06o\n", one->mode);
-                       printf("new mode %06o\n", two->mode);
-               }
-               if (xfrm_msg && xfrm_msg[0])
-                       puts(xfrm_msg);
-               /*
-                * we do not run diff between different kind
-                * of objects.
-                */
-               if ((one->mode ^ two->mode) & S_IFMT)
-                       goto free_ab_and_return;
-               if (complete_rewrite) {
-                       emit_rewrite_diff(name_a, name_b, one, two);
-                       goto free_ab_and_return;
-               }
-       }
-
-       if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
-               die("unable to read files to diff");
-
-       if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2))
-               printf("Binary files %s and %s differ\n", lbl[0], lbl[1]);
-       else {
-               /* Crazy xdl interfaces.. */
-               const char *diffopts = getenv("GIT_DIFF_OPTS");
-               xpparam_t xpp;
-               xdemitconf_t xecfg;
-               xdemitcb_t ecb;
-               struct emit_callback ecbdata;
-
-               ecbdata.label_path = lbl;
-               xpp.flags = XDF_NEED_MINIMAL;
-               xecfg.ctxlen = 3;
-               xecfg.flags = XDL_EMIT_FUNCNAMES;
-               if (!diffopts)
-                       ;
-               else if (!strncmp(diffopts, "--unified=", 10))
-                       xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10);
-               else if (!strncmp(diffopts, "-u", 2))
-                       xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10);
-               ecb.outf = fn_out;
-               ecb.priv = &ecbdata;
-               xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
-       }
-
- free_ab_and_return:
-       free(a_one);
-       free(b_two);
-       return;
-}
-
-static void builtin_diffstat(const char *name_a, const char *name_b,
-               struct diff_filespec *one, struct diff_filespec *two,
-               struct diffstat_t *diffstat)
-{
-       mmfile_t mf1, mf2;
-       struct diffstat_file *data;
-
-       data = diffstat_add(diffstat, name_a ? name_a : name_b);
-
-       if (!one || !two) {
-               data->is_unmerged = 1;
-               return;
-       }
-
-       if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
-               die("unable to read files to diff");
-
-       if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2))
-               data->is_binary = 1;
-       else {
-               /* Crazy xdl interfaces.. */
-               xpparam_t xpp;
-               xdemitconf_t xecfg;
-               xdemitcb_t ecb;
-
-               xpp.flags = XDF_NEED_MINIMAL;
-               xecfg.ctxlen = 0;
-               xecfg.flags = 0;
-               ecb.outf = xdiff_outf;
-               ecb.priv = diffstat;
-               xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
-       }
-}
-
-struct diff_filespec *alloc_filespec(const char *path)
-{
-       int namelen = strlen(path);
-       struct diff_filespec *spec = xmalloc(sizeof(*spec) + namelen + 1);
-
-       memset(spec, 0, sizeof(*spec));
-       spec->path = (char *)(spec + 1);
-       memcpy(spec->path, path, namelen+1);
-       return spec;
-}
-
-void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1,
-                  unsigned short mode)
-{
-       if (mode) {
-               spec->mode = canon_mode(mode);
-               memcpy(spec->sha1, sha1, 20);
-               spec->sha1_valid = !!memcmp(sha1, null_sha1, 20);
-       }
-}
-
 /*
- * Given a name and sha1 pair, if the dircache tells us the file in
- * the work tree has that object contents, return true, so that
- * prepare_temp_file() does not have to inflate and extract.
+ * diff-index
  */
-static int work_tree_matches(const char *name, const unsigned char *sha1)
-{
-       struct cache_entry *ce;
-       struct stat st;
-       int pos, len;
 
-       /* We do not read the cache ourselves here, because the
-        * benchmark with my previous version that always reads cache
-        * shows that it makes things worse for diff-tree comparing
-        * two linux-2.6 kernel trees in an already checked out work
-        * tree.  This is because most diff-tree comparisons deal with
-        * only a small number of files, while reading the cache is
-        * expensive for a large project, and its cost outweighs the
-        * savings we get by not inflating the object to a temporary
-        * file.  Practically, this code only helps when we are used
-        * by diff-cache --cached, which does read the cache before
-        * calling us.
-        */
-       if (!active_cache)
-               return 0;
-
-       len = strlen(name);
-       pos = cache_name_pos(name, len);
-       if (pos < 0)
-               return 0;
-       ce = active_cache[pos];
-       if ((lstat(name, &st) < 0) ||
-           !S_ISREG(st.st_mode) || /* careful! */
-           ce_match_stat(ce, &st, 0) ||
-           memcmp(sha1, ce->sha1, 20))
-               return 0;
-       /* we return 1 only when we can stat, it is a regular file,
-        * stat information matches, and sha1 recorded in the cache
-        * matches.  I.e. we know the file in the work tree really is
-        * the same as the <name, sha1> pair.
-        */
-       return 1;
-}
-
-static struct sha1_size_cache {
-       unsigned char sha1[20];
-       unsigned long size;
-} **sha1_size_cache;
-static int sha1_size_cache_nr, sha1_size_cache_alloc;
-
-static struct sha1_size_cache *locate_size_cache(unsigned char *sha1,
-                                                int find_only,
-                                                unsigned long size)
+/* A file entry went away or appeared */
+static void diff_index_show_file(struct rev_info *revs,
+                                const char *prefix,
+                                struct cache_entry *ce,
+                                unsigned char *sha1, unsigned int mode)
 {
-       int first, last;
-       struct sha1_size_cache *e;
-
-       first = 0;
-       last = sha1_size_cache_nr;
-       while (last > first) {
-               int cmp, next = (last + first) >> 1;
-               e = sha1_size_cache[next];
-               cmp = memcmp(e->sha1, sha1, 20);
-               if (!cmp)
-                       return e;
-               if (cmp < 0) {
-                       last = next;
-                       continue;
-               }
-               first = next+1;
-       }
-       /* not found */
-       if (find_only)
-               return NULL;
-       /* insert to make it at "first" */
-       if (sha1_size_cache_alloc <= sha1_size_cache_nr) {
-               sha1_size_cache_alloc = alloc_nr(sha1_size_cache_alloc);
-               sha1_size_cache = xrealloc(sha1_size_cache,
-                                          sha1_size_cache_alloc *
-                                          sizeof(*sha1_size_cache));
-       }
-       sha1_size_cache_nr++;
-       if (first < sha1_size_cache_nr)
-               memmove(sha1_size_cache + first + 1, sha1_size_cache + first,
-                       (sha1_size_cache_nr - first - 1) *
-                       sizeof(*sha1_size_cache));
-       e = xmalloc(sizeof(struct sha1_size_cache));
-       sha1_size_cache[first] = e;
-       memcpy(e->sha1, sha1, 20);
-       e->size = size;
-       return e;
+       diff_addremove(&revs->diffopt, prefix[0], ntohl(mode),
+                      sha1, ce->name, NULL);
 }
 
-/*
- * 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.
- * diff_filespec has data and size fields for this purpose.
- */
-int diff_populate_filespec(struct diff_filespec *s, int size_only)
+static int get_stat_data(struct cache_entry *ce,
+                        unsigned char **sha1p,
+                        unsigned int *modep,
+                        int cached, int match_missing)
 {
-       int err = 0;
-       if (!DIFF_FILE_VALID(s))
-               die("internal error: asking to populate invalid file.");
-       if (S_ISDIR(s->mode))
-               return -1;
-
-       if (!use_size_cache)
-               size_only = 0;
+       unsigned char *sha1 = ce->sha1;
+       unsigned int mode = ce->ce_mode;
 
-       if (s->data)
-               return err;
-       if (!s->sha1_valid ||
-           work_tree_matches(s->path, s->sha1)) {
+       if (!cached) {
+               static unsigned char no_sha1[20];
+               int changed;
                struct stat st;
-               int fd;
-               if (lstat(s->path, &st) < 0) {
-                       if (errno == ENOENT) {
-                       err_empty:
-                               err = -1;
-                       empty:
-                               s->data = "";
-                               s->size = 0;
-                               return err;
-                       }
-               }
-               s->size = st.st_size;
-               if (!s->size)
-                       goto empty;
-               if (size_only)
-                       return 0;
-               if (S_ISLNK(st.st_mode)) {
-                       int ret;
-                       s->data = xmalloc(s->size);
-                       s->should_free = 1;
-                       ret = readlink(s->path, s->data, s->size);
-                       if (ret < 0) {
-                               free(s->data);
-                               goto err_empty;
-                       }
-                       return 0;
-               }
-               fd = open(s->path, O_RDONLY);
-               if (fd < 0)
-                       goto err_empty;
-               s->data = mmap(NULL, s->size, PROT_READ, MAP_PRIVATE, fd, 0);
-               close(fd);
-               if (s->data == MAP_FAILED)
-                       goto err_empty;
-               s->should_munmap = 1;
-       }
-       else {
-               char type[20];
-               struct sha1_size_cache *e;
-
-               if (size_only) {
-                       e = locate_size_cache(s->sha1, 1, 0);
-                       if (e) {
-                               s->size = e->size;
+               if (lstat(ce->name, &st) < 0) {
+                       if (errno == ENOENT && match_missing) {
+                               *sha1p = sha1;
+                               *modep = mode;
                                return 0;
                        }
-                       if (!sha1_object_info(s->sha1, type, &s->size))
-                               locate_size_cache(s->sha1, 0, s->size);
-               }
-               else {
-                       s->data = read_sha1_file(s->sha1, type, &s->size);
-                       s->should_free = 1;
-               }
-       }
-       return 0;
-}
-
-void diff_free_filespec_data(struct diff_filespec *s)
-{
-       if (s->should_free)
-               free(s->data);
-       else if (s->should_munmap)
-               munmap(s->data, s->size);
-       s->should_free = s->should_munmap = 0;
-       s->data = NULL;
-       free(s->cnt_data);
-       s->cnt_data = NULL;
-}
-
-static void prep_temp_blob(struct diff_tempfile *temp,
-                          void *blob,
-                          unsigned long size,
-                          const unsigned char *sha1,
-                          int mode)
-{
-       int fd;
-
-       fd = git_mkstemp(temp->tmp_path, TEMPFILE_PATH_LEN, ".diff_XXXXXX");
-       if (fd < 0)
-               die("unable to create temp-file");
-       if (write(fd, blob, size) != size)
-               die("unable to write temp-file");
-       close(fd);
-       temp->name = temp->tmp_path;
-       strcpy(temp->hex, sha1_to_hex(sha1));
-       temp->hex[40] = 0;
-       sprintf(temp->mode, "%06o", mode);
-}
-
-static void prepare_temp_file(const char *name,
-                             struct diff_tempfile *temp,
-                             struct diff_filespec *one)
-{
-       if (!DIFF_FILE_VALID(one)) {
-       not_a_valid_file:
-               /* A '-' entry produces this for file-2, and
-                * a '+' entry produces this for file-1.
-                */
-               temp->name = "/dev/null";
-               strcpy(temp->hex, ".");
-               strcpy(temp->mode, ".");
-               return;
-       }
-
-       if (!one->sha1_valid ||
-           work_tree_matches(name, one->sha1)) {
-               struct stat st;
-               if (lstat(name, &st) < 0) {
-                       if (errno == ENOENT)
-                               goto not_a_valid_file;
-                       die("stat(%s): %s", name, strerror(errno));
-               }
-               if (S_ISLNK(st.st_mode)) {
-                       int ret;
-                       char buf[PATH_MAX + 1]; /* ought to be SYMLINK_MAX */
-                       if (sizeof(buf) <= st.st_size)
-                               die("symlink too long: %s", name);
-                       ret = readlink(name, buf, st.st_size);
-                       if (ret < 0)
-                               die("readlink(%s)", name);
-                       prep_temp_blob(temp, buf, st.st_size,
-                                      (one->sha1_valid ?
-                                       one->sha1 : null_sha1),
-                                      (one->sha1_valid ?
-                                       one->mode : S_IFLNK));
+                       return -1;
                }
-               else {
-                       /* we can borrow from the file in the work tree */
-                       temp->name = name;
-                       if (!one->sha1_valid)
-                               strcpy(temp->hex, sha1_to_hex(null_sha1));
-                       else
-                               strcpy(temp->hex, sha1_to_hex(one->sha1));
-                       /* Even though we may sometimes borrow the
-                        * contents from the work tree, we always want
-                        * one->mode.  mode is trustworthy even when
-                        * !(one->sha1_valid), as long as
-                        * DIFF_FILE_VALID(one).
-                        */
-                       sprintf(temp->mode, "%06o", one->mode);
+               changed = ce_match_stat(ce, &st, 0);
+               if (changed) {
+                       mode = create_ce_mode(st.st_mode);
+                       if (!trust_executable_bit && S_ISREG(st.st_mode))
+                               mode = ce->ce_mode;
+                       sha1 = no_sha1;
                }
-               return;
        }
-       else {
-               if (diff_populate_filespec(one, 0))
-                       die("cannot read data blob for %s", one->path);
-               prep_temp_blob(temp, one->data, one->size,
-                              one->sha1, one->mode);
-       }
-}
 
-static void remove_tempfile(void)
-{
-       int i;
-
-       for (i = 0; i < 2; i++)
-               if (diff_temp[i].name == diff_temp[i].tmp_path) {
-                       unlink(diff_temp[i].name);
-                       diff_temp[i].name = NULL;
-               }
-}
-
-static void remove_tempfile_on_signal(int signo)
-{
-       remove_tempfile();
-       signal(SIGINT, SIG_DFL);
-       raise(signo);
+       *sha1p = sha1;
+       *modep = mode;
+       return 0;
 }
 
-static int spawn_prog(const char *pgm, const char **arg)
+static void show_new_file(struct rev_info *revs,
+                         struct cache_entry *new,
+                         int cached, int match_missing)
 {
-       pid_t pid;
-       int status;
-
-       fflush(NULL);
-       pid = fork();
-       if (pid < 0)
-               die("unable to fork");
-       if (!pid) {
-               execvp(pgm, (char *const*) arg);
-               exit(255);
-       }
-
-       while (waitpid(pid, &status, 0) < 0) {
-               if (errno == EINTR)
-                       continue;
-               return -1;
-       }
+       unsigned char *sha1;
+       unsigned int mode;
 
-       /* Earlier we did not check the exit status because
-        * diff exits non-zero if files are different, and
-        * we are not interested in knowing that.  It was a
-        * mistake which made it harder to quit a diff-*
-        * session that uses the git-apply-patch-script as
-        * the GIT_EXTERNAL_DIFF.  A custom GIT_EXTERNAL_DIFF
-        * should also exit non-zero only when it wants to
-        * abort the entire diff-* session.
+       /* New file in the index: it might actually be different in
+        * the working copy.
         */
-       if (WIFEXITED(status) && !WEXITSTATUS(status))
-               return 0;
-       return -1;
-}
-
-/* An external diff command takes:
- *
- * diff-cmd name infile1 infile1-sha1 infile1-mode \
- *               infile2 infile2-sha1 infile2-mode [ rename-to ]
- *
- */
-static void run_external_diff(const char *pgm,
-                             const char *name,
-                             const char *other,
-                             struct diff_filespec *one,
-                             struct diff_filespec *two,
-                             const char *xfrm_msg,
-                             int complete_rewrite)
-{
-       const char *spawn_arg[10];
-       struct diff_tempfile *temp = diff_temp;
-       int retval;
-       static int atexit_asked = 0;
-       const char *othername;
-       const char **arg = &spawn_arg[0];
-
-       othername = (other? other : name);
-       if (one && two) {
-               prepare_temp_file(name, &temp[0], one);
-               prepare_temp_file(othername, &temp[1], two);
-               if (! atexit_asked &&
-                   (temp[0].name == temp[0].tmp_path ||
-                    temp[1].name == temp[1].tmp_path)) {
-                       atexit_asked = 1;
-                       atexit(remove_tempfile);
-               }
-               signal(SIGINT, remove_tempfile_on_signal);
-       }
-
-       if (one && two) {
-               *arg++ = pgm;
-               *arg++ = name;
-               *arg++ = temp[0].name;
-               *arg++ = temp[0].hex;
-               *arg++ = temp[0].mode;
-               *arg++ = temp[1].name;
-               *arg++ = temp[1].hex;
-               *arg++ = temp[1].mode;
-               if (other) {
-                       *arg++ = other;
-                       *arg++ = xfrm_msg;
-               }
-       } else {
-               *arg++ = pgm;
-               *arg++ = name;
-       }
-       *arg = NULL;
-       retval = spawn_prog(pgm, spawn_arg);
-       remove_tempfile();
-       if (retval) {
-               fprintf(stderr, "external diff died, stopping at %s.\n", name);
-               exit(1);
-       }
-}
-
-static void run_diff_cmd(const char *pgm,
-                        const char *name,
-                        const char *other,
-                        struct diff_filespec *one,
-                        struct diff_filespec *two,
-                        const char *xfrm_msg,
-                        int complete_rewrite)
-{
-       if (pgm) {
-               run_external_diff(pgm, name, other, one, two, xfrm_msg,
-                                 complete_rewrite);
-               return;
-       }
-       if (one && two)
-               builtin_diff(name, other ? other : name,
-                            one, two, xfrm_msg, complete_rewrite);
-       else
-               printf("* Unmerged path %s\n", name);
-}
-
-static void diff_fill_sha1_info(struct diff_filespec *one)
-{
-       if (DIFF_FILE_VALID(one)) {
-               if (!one->sha1_valid) {
-                       struct stat st;
-                       if (lstat(one->path, &st) < 0)
-                               die("stat %s", one->path);
-                       if (index_path(one->sha1, one->path, &st, 0))
-                               die("cannot hash %s\n", one->path);
-               }
-       }
-       else
-               memset(one->sha1, 0, 20);
-}
-
-static void run_diff(struct diff_filepair *p, struct diff_options *o)
-{
-       const char *pgm = external_diff();
-       char msg[PATH_MAX*2+300], *xfrm_msg;
-       struct diff_filespec *one;
-       struct diff_filespec *two;
-       const char *name;
-       const char *other;
-       char *name_munged, *other_munged;
-       int complete_rewrite = 0;
-       int len;
-
-       if (DIFF_PAIR_UNMERGED(p)) {
-               /* unmerged */
-               run_diff_cmd(pgm, p->one->path, NULL, NULL, NULL, NULL, 0);
-               return;
-       }
-
-       name = p->one->path;
-       other = (strcmp(name, p->two->path) ? p->two->path : NULL);
-       name_munged = quote_one(name);
-       other_munged = quote_one(other);
-       one = p->one; two = p->two;
-
-       diff_fill_sha1_info(one);
-       diff_fill_sha1_info(two);
-
-       len = 0;
-       switch (p->status) {
-       case DIFF_STATUS_COPIED:
-               len += snprintf(msg + len, sizeof(msg) - len,
-                               "similarity index %d%%\n"
-                               "copy from %s\n"
-                               "copy to %s\n",
-                               (int)(0.5 + p->score * 100.0/MAX_SCORE),
-                               name_munged, other_munged);
-               break;
-       case DIFF_STATUS_RENAMED:
-               len += snprintf(msg + len, sizeof(msg) - len,
-                               "similarity index %d%%\n"
-                               "rename from %s\n"
-                               "rename to %s\n",
-                               (int)(0.5 + p->score * 100.0/MAX_SCORE),
-                               name_munged, other_munged);
-               break;
-       case DIFF_STATUS_MODIFIED:
-               if (p->score) {
-                       len += snprintf(msg + len, sizeof(msg) - len,
-                                       "dissimilarity index %d%%\n",
-                                       (int)(0.5 + p->score *
-                                             100.0/MAX_SCORE));
-                       complete_rewrite = 1;
-                       break;
-               }
-               /* fallthru */
-       default:
-               /* nothing */
-               ;
-       }
-
-       if (memcmp(one->sha1, two->sha1, 20)) {
-               char one_sha1[41];
-               int abbrev = o->full_index ? 40 : DEFAULT_ABBREV;
-               memcpy(one_sha1, sha1_to_hex(one->sha1), 41);
-
-               len += snprintf(msg + len, sizeof(msg) - len,
-                               "index %.*s..%.*s",
-                               abbrev, one_sha1, abbrev,
-                               sha1_to_hex(two->sha1));
-               if (one->mode == two->mode)
-                       len += snprintf(msg + len, sizeof(msg) - len,
-                                       " %06o", one->mode);
-               len += snprintf(msg + len, sizeof(msg) - len, "\n");
-       }
-
-       if (len)
-               msg[--len] = 0;
-       xfrm_msg = len ? msg : NULL;
-
-       if (!pgm &&
-           DIFF_FILE_VALID(one) && DIFF_FILE_VALID(two) &&
-           (S_IFMT & one->mode) != (S_IFMT & two->mode)) {
-               /* a filepair that changes between file and symlink
-                * needs to be split into deletion and creation.
-                */
-               struct diff_filespec *null = alloc_filespec(two->path);
-               run_diff_cmd(NULL, name, other, one, null, xfrm_msg, 0);
-               free(null);
-               null = alloc_filespec(one->path);
-               run_diff_cmd(NULL, name, other, null, two, xfrm_msg, 0);
-               free(null);
-       }
-       else
-               run_diff_cmd(pgm, name, other, one, two, xfrm_msg,
-                            complete_rewrite);
-
-       free(name_munged);
-       free(other_munged);
-}
-
-static void run_diffstat(struct diff_filepair *p, struct diff_options *o,
-               struct diffstat_t *diffstat)
-{
-       const char *name;
-       const char *other;
-
-       if (DIFF_PAIR_UNMERGED(p)) {
-               /* unmerged */
-               builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat);
+       if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0)
                return;
-       }
-
-       name = p->one->path;
-       other = (strcmp(name, p->two->path) ? p->two->path : NULL);
 
-       diff_fill_sha1_info(p->one);
-       diff_fill_sha1_info(p->two);
-
-       builtin_diffstat(name, other, p->one, p->two, diffstat);
+       diff_index_show_file(revs, "+", new, sha1, mode);
 }
 
-void diff_setup(struct diff_options *options)
+static int show_modified(struct rev_info *revs,
+                        struct cache_entry *old,
+                        struct cache_entry *new,
+                        int report_missing,
+                        int cached, int match_missing)
 {
-       memset(options, 0, sizeof(*options));
-       options->output_format = DIFF_FORMAT_RAW;
-       options->line_termination = '\n';
-       options->break_opt = -1;
-       options->rename_limit = -1;
-
-       options->change = diff_change;
-       options->add_remove = diff_addremove;
-}
+       unsigned int mode, oldmode;
+       unsigned char *sha1;
 
-int diff_setup_done(struct diff_options *options)
-{
-       if ((options->find_copies_harder &&
-            options->detect_rename != DIFF_DETECT_COPY) ||
-           (0 <= options->rename_limit && !options->detect_rename))
+       if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0) {
+               if (report_missing)
+                       diff_index_show_file(revs, "-", old,
+                                            old->sha1, old->ce_mode);
                return -1;
-
-       /*
-        * These cases always need recursive; we do not drop caller-supplied
-        * recursive bits for other formats here.
-        */
-       if ((options->output_format == DIFF_FORMAT_PATCH) ||
-           (options->output_format == DIFF_FORMAT_DIFFSTAT))
-               options->recursive = 1;
-
-       if (options->detect_rename && options->rename_limit < 0)
-               options->rename_limit = diff_rename_limit_default;
-       if (options->setup & DIFF_SETUP_USE_CACHE) {
-               if (!active_cache)
-                       /* read-cache does not die even when it fails
-                        * so it is safe for us to do this here.  Also
-                        * it does not smudge active_cache or active_nr
-                        * when it fails, so we do not have to worry about
-                        * cleaning it up ourselves either.
-                        */
-                       read_cache();
        }
-       if (options->setup & DIFF_SETUP_USE_SIZE_CACHE)
-               use_size_cache = 1;
-       if (options->abbrev <= 0 || 40 < options->abbrev)
-               options->abbrev = 40; /* full */
 
-       return 0;
-}
-
-int diff_opt_parse(struct diff_options *options, const char **av, int ac)
-{
-       const char *arg = av[0];
-       if (!strcmp(arg, "-p") || !strcmp(arg, "-u"))
-               options->output_format = DIFF_FORMAT_PATCH;
-       else if (!strcmp(arg, "--patch-with-raw")) {
-               options->output_format = DIFF_FORMAT_PATCH;
-               options->with_raw = 1;
-       }
-       else if (!strcmp(arg, "--stat"))
-               options->output_format = DIFF_FORMAT_DIFFSTAT;
-       else if (!strcmp(arg, "--patch-with-stat")) {
-               options->output_format = DIFF_FORMAT_PATCH;
-               options->with_stat = 1;
-       }
-       else if (!strcmp(arg, "-z"))
-               options->line_termination = 0;
-       else if (!strncmp(arg, "-l", 2))
-               options->rename_limit = strtoul(arg+2, NULL, 10);
-       else if (!strcmp(arg, "--full-index"))
-               options->full_index = 1;
-       else if (!strcmp(arg, "--name-only"))
-               options->output_format = DIFF_FORMAT_NAME;
-       else if (!strcmp(arg, "--name-status"))
-               options->output_format = DIFF_FORMAT_NAME_STATUS;
-       else if (!strcmp(arg, "-R"))
-               options->reverse_diff = 1;
-       else if (!strncmp(arg, "-S", 2))
-               options->pickaxe = arg + 2;
-       else if (!strcmp(arg, "-s"))
-               options->output_format = DIFF_FORMAT_NO_OUTPUT;
-       else if (!strncmp(arg, "-O", 2))
-               options->orderfile = arg + 2;
-       else if (!strncmp(arg, "--diff-filter=", 14))
-               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)) {
-               if ((options->break_opt =
-                    diff_scoreopt_parse(arg)) == -1)
-                       return -1;
-       }
-       else if (!strncmp(arg, "-M", 2)) {
-               if ((options->rename_score =
-                    diff_scoreopt_parse(arg)) == -1)
-                       return -1;
-               options->detect_rename = DIFF_DETECT_RENAME;
-       }
-       else if (!strncmp(arg, "-C", 2)) {
-               if ((options->rename_score =
-                    diff_scoreopt_parse(arg)) == -1)
-                       return -1;
-               options->detect_rename = DIFF_DETECT_COPY;
-       }
-       else if (!strcmp(arg, "--find-copies-harder"))
-               options->find_copies_harder = 1;
-       else if (!strcmp(arg, "--abbrev"))
-               options->abbrev = DEFAULT_ABBREV;
-       else if (!strncmp(arg, "--abbrev=", 9)) {
-               options->abbrev = strtoul(arg + 9, NULL, 10);
-               if (options->abbrev < MINIMUM_ABBREV)
-                       options->abbrev = MINIMUM_ABBREV;
-               else if (40 < options->abbrev)
-                       options->abbrev = 40;
-       }
-       else
+       oldmode = old->ce_mode;
+       if (mode == oldmode && !memcmp(sha1, old->sha1, 20) &&
+           !revs->diffopt.find_copies_harder)
                return 0;
-       return 1;
-}
-
-static int parse_num(const char **cp_p)
-{
-       unsigned long num, scale;
-       int ch, dot;
-       const char *cp = *cp_p;
-
-       num = 0;
-       scale = 1;
-       dot = 0;
-       for(;;) {
-               ch = *cp;
-               if ( !dot && ch == '.' ) {
-                       scale = 1;
-                       dot = 1;
-               } else if ( ch == '%' ) {
-                       scale = dot ? scale*100 : 100;
-                       cp++;   /* % is always at the end */
-                       break;
-               } else if ( ch >= '0' && ch <= '9' ) {
-                       if ( scale < 100000 ) {
-                               scale *= 10;
-                               num = (num*10) + (ch-'0');
-                       }
-               } else {
-                       break;
-               }
-               cp++;
-       }
-       *cp_p = cp;
-
-       /* 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);
-}
-
-int diff_scoreopt_parse(const char *opt)
-{
-       int opt1, opt2, cmd;
-
-       if (*opt++ != '-')
-               return -1;
-       cmd = *opt++;
-       if (cmd != 'M' && cmd != 'C' && cmd != 'B')
-               return -1; /* that is not a -M, -C nor -B option */
-
-       opt1 = parse_num(&opt);
-       if (cmd != 'B')
-               opt2 = 0;
-       else {
-               if (*opt == 0)
-                       opt2 = 0;
-               else if (*opt != '/')
-                       return -1; /* we expect -B80/99 or -B80 */
-               else {
-                       opt++;
-                       opt2 = parse_num(&opt);
-               }
-       }
-       if (*opt != 0)
-               return -1;
-       return opt1 | (opt2 << 16);
-}
-
-struct diff_queue_struct diff_queued_diff;
-
-void diff_q(struct diff_queue_struct *queue, struct diff_filepair *dp)
-{
-       if (queue->alloc <= queue->nr) {
-               queue->alloc = alloc_nr(queue->alloc);
-               queue->queue = xrealloc(queue->queue,
-                                       sizeof(dp) * queue->alloc);
-       }
-       queue->queue[queue->nr++] = dp;
-}
-
-struct diff_filepair *diff_queue(struct diff_queue_struct *queue,
-                                struct diff_filespec *one,
-                                struct diff_filespec *two)
-{
-       struct diff_filepair *dp = xmalloc(sizeof(*dp));
-       dp->one = one;
-       dp->two = two;
-       dp->score = 0;
-       dp->status = 0;
-       dp->source_stays = 0;
-       dp->broken_pair = 0;
-       if (queue)
-               diff_q(queue, dp);
-       return dp;
-}
-
-void diff_free_filepair(struct diff_filepair *p)
-{
-       diff_free_filespec_data(p->one);
-       diff_free_filespec_data(p->two);
-       free(p->one);
-       free(p->two);
-       free(p);
-}
-
-/* This is different from find_unique_abbrev() in that
- * it stuffs the result with dots for alignment.
- */
-const char *diff_unique_abbrev(const unsigned char *sha1, int len)
-{
-       int abblen;
-       const char *abbrev;
-       if (len == 40)
-               return sha1_to_hex(sha1);
-
-       abbrev = find_unique_abbrev(sha1, len);
-       if (!abbrev)
-               return sha1_to_hex(sha1);
-       abblen = strlen(abbrev);
-       if (abblen < 37) {
-               static char hex[41];
-               if (len < abblen && abblen <= len + 2)
-                       sprintf(hex, "%s%.*s", abbrev, len+3-abblen, "..");
-               else
-                       sprintf(hex, "%s...", abbrev);
-               return hex;
-       }
-       return sha1_to_hex(sha1);
-}
-
-static void diff_flush_raw(struct diff_filepair *p,
-                          int line_termination,
-                          int inter_name_termination,
-                          struct diff_options *options,
-                          int output_format)
-{
-       int two_paths;
-       char status[10];
-       int abbrev = options->abbrev;
-       const char *path_one, *path_two;
-
-       path_one = p->one->path;
-       path_two = p->two->path;
-       if (line_termination) {
-               path_one = quote_one(path_one);
-               path_two = quote_one(path_two);
-       }
-
-       if (p->score)
-               sprintf(status, "%c%03d", p->status,
-                       (int)(0.5 + p->score * 100.0/MAX_SCORE));
-       else {
-               status[0] = p->status;
-               status[1] = 0;
-       }
-       switch (p->status) {
-       case DIFF_STATUS_COPIED:
-       case DIFF_STATUS_RENAMED:
-               two_paths = 1;
-               break;
-       case DIFF_STATUS_ADDED:
-       case DIFF_STATUS_DELETED:
-               two_paths = 0;
-               break;
-       default:
-               two_paths = 0;
-               break;
-       }
-       if (output_format != DIFF_FORMAT_NAME_STATUS) {
-               printf(":%06o %06o %s ",
-                      p->one->mode, p->two->mode,
-                      diff_unique_abbrev(p->one->sha1, abbrev));
-               printf("%s ",
-                      diff_unique_abbrev(p->two->sha1, abbrev));
-       }
-       printf("%s%c%s", status, inter_name_termination, path_one);
-       if (two_paths)
-               printf("%c%s", inter_name_termination, path_two);
-       putchar(line_termination);
-       if (path_one != p->one->path)
-               free((void*)path_one);
-       if (path_two != p->two->path)
-               free((void*)path_two);
-}
-
-static void diff_flush_name(struct diff_filepair *p,
-                           int inter_name_termination,
-                           int line_termination)
-{
-       char *path = p->two->path;
 
-       if (line_termination)
-               path = quote_one(p->two->path);
-       else
-               path = p->two->path;
-       printf("%s%c", path, line_termination);
-       if (p->two->path != path)
-               free(path);
-}
-
-int diff_unmodified_pair(struct diff_filepair *p)
-{
-       /* This function is written stricter than necessary to support
-        * the currently implemented transformers, but the idea is to
-        * let transformers to produce diff_filepairs any way they want,
-        * and filter and clean them up here before producing the output.
-        */
-       struct diff_filespec *one, *two;
+       mode = ntohl(mode);
+       oldmode = ntohl(oldmode);
 
-       if (DIFF_PAIR_UNMERGED(p))
-               return 0; /* unmerged is interesting */
-
-       one = p->one;
-       two = p->two;
-
-       /* deletion, addition, mode or type change
-        * and rename are all interesting.
-        */
-       if (DIFF_FILE_VALID(one) != DIFF_FILE_VALID(two) ||
-           DIFF_PAIR_MODE_CHANGED(p) ||
-           strcmp(one->path, two->path))
-               return 0;
-
-       /* both are valid and point at the same path.  that is, we are
-        * dealing with a change.
-        */
-       if (one->sha1_valid && two->sha1_valid &&
-           !memcmp(one->sha1, two->sha1, sizeof(one->sha1)))
-               return 1; /* no change */
-       if (!one->sha1_valid && !two->sha1_valid)
-               return 1; /* both look at the same file on the filesystem. */
+       diff_change(&revs->diffopt, oldmode, mode,
+                   old->sha1, sha1, old->name, NULL);
        return 0;
 }
 
-static void diff_flush_patch(struct diff_filepair *p, struct diff_options *o)
-{
-       if (diff_unmodified_pair(p))
-               return;
-
-       if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
-           (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
-               return; /* no tree diffs in patch format */
-
-       run_diff(p, o);
-}
-
-static void diff_flush_stat(struct diff_filepair *p, struct diff_options *o,
-               struct diffstat_t *diffstat)
-{
-       if (diff_unmodified_pair(p))
-               return;
-
-       if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
-           (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
-               return; /* no tree diffs in patch format */
-
-       run_diffstat(p, o, diffstat);
-}
-
-int diff_queue_is_empty(void)
-{
-       struct diff_queue_struct *q = &diff_queued_diff;
-       int i;
-       for (i = 0; i < q->nr; i++)
-               if (!diff_unmodified_pair(q->queue[i]))
-                       return 0;
-       return 1;
-}
-
-#if DIFF_DEBUG
-void diff_debug_filespec(struct diff_filespec *s, int x, const char *one)
-{
-       fprintf(stderr, "queue[%d] %s (%s) %s %06o %s\n",
-               x, one ? one : "",
-               s->path,
-               DIFF_FILE_VALID(s) ? "valid" : "invalid",
-               s->mode,
-               s->sha1_valid ? sha1_to_hex(s->sha1) : "");
-       fprintf(stderr, "queue[%d] %s size %lu flags %d\n",
-               x, one ? one : "",
-               s->size, s->xfrm_flags);
-}
-
-void diff_debug_filepair(const struct diff_filepair *p, int i)
-{
-       diff_debug_filespec(p->one, i, "one");
-       diff_debug_filespec(p->two, i, "two");
-       fprintf(stderr, "score %d, status %c stays %d broken %d\n",
-               p->score, p->status ? p->status : '?',
-               p->source_stays, p->broken_pair);
-}
-
-void diff_debug_queue(const char *msg, struct diff_queue_struct *q)
-{
-       int i;
-       if (msg)
-               fprintf(stderr, "%s\n", msg);
-       fprintf(stderr, "q->nr = %d\n", q->nr);
-       for (i = 0; i < q->nr; i++) {
-               struct diff_filepair *p = q->queue[i];
-               diff_debug_filepair(p, i);
-       }
-}
-#endif
-
-static void diff_resolve_rename_copy(void)
+static int diff_cache(struct rev_info *revs,
+                     struct cache_entry **ac, int entries,
+                     const char **pathspec,
+                     int cached, int match_missing)
 {
-       int i, j;
-       struct diff_filepair *p, *pp;
-       struct diff_queue_struct *q = &diff_queued_diff;
-
-       diff_debug_queue("resolve-rename-copy", q);
+       while (entries) {
+               struct cache_entry *ce = *ac;
+               int same = (entries > 1) && ce_same_name(ce, ac[1]);
 
-       for (i = 0; i < q->nr; i++) {
-               p = q->queue[i];
-               p->status = 0; /* undecided */
-               if (DIFF_PAIR_UNMERGED(p))
-                       p->status = DIFF_STATUS_UNMERGED;
-               else if (!DIFF_FILE_VALID(p->one))
-                       p->status = DIFF_STATUS_ADDED;
-               else if (!DIFF_FILE_VALID(p->two))
-                       p->status = DIFF_STATUS_DELETED;
-               else if (DIFF_PAIR_TYPE_CHANGED(p))
-                       p->status = DIFF_STATUS_TYPE_CHANGED;
+               if (!ce_path_match(ce, pathspec))
+                       goto skip_entry;
 
-               /* from this point on, we are dealing with a pair
-                * whose both sides are valid and of the same type, i.e.
-                * either in-place edit or rename/copy edit.
-                */
-               else if (DIFF_PAIR_RENAME(p)) {
-                       if (p->source_stays) {
-                               p->status = DIFF_STATUS_COPIED;
-                               continue;
+               switch (ce_stage(ce)) {
+               case 0:
+                       /* No stage 1 entry? That means it's a new file */
+                       if (!same) {
+                               show_new_file(revs, ce, cached, match_missing);
+                               break;
                        }
-                       /* See if there is some other filepair that
-                        * copies from the same source as us.  If so
-                        * we are a copy.  Otherwise we are either a
-                        * copy if the path stays, or a rename if it
-                        * does not, but we already handled "stays" case.
+                       /* Show difference between old and new */
+                       show_modified(revs,ac[1], ce, 1,
+                                     cached, match_missing);
+                       break;
+               case 1:
+                       /* No stage 3 (merge) entry?
+                        * That means it's been deleted.
                         */
-                       for (j = i + 1; j < q->nr; j++) {
-                               pp = q->queue[j];
-                               if (strcmp(pp->one->path, p->one->path))
-                                       continue; /* not us */
-                               if (!DIFF_PAIR_RENAME(pp))
-                                       continue; /* not a rename/copy */
-                               /* pp is a rename/copy from the same source */
-                               p->status = DIFF_STATUS_COPIED;
+                       if (!same) {
+                               diff_index_show_file(revs, "-", ce,
+                                                    ce->sha1, ce->ce_mode);
                                break;
                        }
-                       if (!p->status)
-                               p->status = DIFF_STATUS_RENAMED;
-               }
-               else if (memcmp(p->one->sha1, p->two->sha1, 20) ||
-                        p->one->mode != p->two->mode)
-                       p->status = DIFF_STATUS_MODIFIED;
-               else {
-                       /* This is a "no-change" entry and should not
-                        * happen anymore, but prepare for broken callers.
+                       /* We come here with ce pointing at stage 1
+                        * (original tree) and ac[1] pointing at stage
+                        * 3 (unmerged).  show-modified with
+                        * report-missing set to false does not say the
+                        * file is deleted but reports true if work
+                        * tree does not have it, in which case we
+                        * fall through to report the unmerged state.
+                        * Otherwise, we show the differences between
+                        * the original tree and the work tree.
                         */
-                       error("feeding unmodified %s to diffcore",
-                             p->one->path);
-                       p->status = DIFF_STATUS_UNKNOWN;
-               }
-       }
-       diff_debug_queue("resolve-rename-copy done", q);
-}
-
-static void flush_one_pair(struct diff_filepair *p,
-                          int diff_output_format,
-                          struct diff_options *options,
-                          struct diffstat_t *diffstat)
-{
-       int inter_name_termination = '\t';
-       int line_termination = options->line_termination;
-       if (!line_termination)
-               inter_name_termination = 0;
-
-       switch (p->status) {
-       case DIFF_STATUS_UNKNOWN:
-               break;
-       case 0:
-               die("internal error in diff-resolve-rename-copy");
-               break;
-       default:
-               switch (diff_output_format) {
-               case DIFF_FORMAT_DIFFSTAT:
-                       diff_flush_stat(p, options, diffstat);
-                       break;
-               case DIFF_FORMAT_PATCH:
-                       diff_flush_patch(p, options);
-                       break;
-               case DIFF_FORMAT_RAW:
-               case DIFF_FORMAT_NAME_STATUS:
-                       diff_flush_raw(p, line_termination,
-                                      inter_name_termination,
-                                      options, diff_output_format);
-                       break;
-               case DIFF_FORMAT_NAME:
-                       diff_flush_name(p,
-                                       inter_name_termination,
-                                       line_termination);
-                       break;
-               case DIFF_FORMAT_NO_OUTPUT:
+                       if (!cached &&
+                           !show_modified(revs, ce, ac[1], 0,
+                                          cached, match_missing))
+                               break;
+                       /* fallthru */
+               case 3:
+                       diff_unmerge(&revs->diffopt, ce->name);
                        break;
-               }
-       }
-}
-
-void diff_flush(struct diff_options *options)
-{
-       struct diff_queue_struct *q = &diff_queued_diff;
-       int i;
-       int diff_output_format = options->output_format;
-       struct diffstat_t *diffstat = NULL;
-
-       if (diff_output_format == DIFF_FORMAT_DIFFSTAT || options->with_stat) {
-               diffstat = xcalloc(sizeof (struct diffstat_t), 1);
-               diffstat->xm.consume = diffstat_consume;
-       }
 
-       if (options->with_raw) {
-               for (i = 0; i < q->nr; i++) {
-                       struct diff_filepair *p = q->queue[i];
-                       flush_one_pair(p, DIFF_FORMAT_RAW, options, NULL);
+               default:
+                       die("impossible cache entry stage");
                }
-               putchar(options->line_termination);
-       }
-       if (options->with_stat) {
-               for (i = 0; i < q->nr; i++) {
-                       struct diff_filepair *p = q->queue[i];
-                       flush_one_pair(p, DIFF_FORMAT_DIFFSTAT, options,
-                                       diffstat);
-               }
-               show_stats(diffstat);
-               free(diffstat);
-               diffstat = NULL;
-               putchar(options->line_termination);
-       }
-       for (i = 0; i < q->nr; i++) {
-               struct diff_filepair *p = q->queue[i];
-               flush_one_pair(p, diff_output_format, options, diffstat);
-               diff_free_filepair(p);
-       }
 
-       if (diffstat) {
-               show_stats(diffstat);
-               free(diffstat);
+skip_entry:
+               /*
+                * Ignore all the different stages for this file,
+                * we've handled the relevant cases now.
+                */
+               do {
+                       ac++;
+                       entries--;
+               } while (entries && ce_same_name(ce, ac[0]));
        }
-
-       free(q->queue);
-       q->queue = NULL;
-       q->nr = q->alloc = 0;
+       return 0;
 }
 
-static void diffcore_apply_filter(const char *filter)
+/*
+ * This turns all merge entries into "stage 3". That guarantees that
+ * when we read in the new tree (into "stage 1"), we won't lose sight
+ * of the fact that we had unmerged entries.
+ */
+static void mark_merge_entries(void)
 {
        int i;
-       struct diff_queue_struct *q = &diff_queued_diff;
-       struct diff_queue_struct outq;
-       outq.queue = NULL;
-       outq.nr = outq.alloc = 0;
-
-       if (!filter)
-               return;
-
-       if (strchr(filter, DIFF_STATUS_FILTER_AON)) {
-               int found;
-               for (i = found = 0; !found && i < q->nr; i++) {
-                       struct diff_filepair *p = q->queue[i];
-                       if (((p->status == DIFF_STATUS_MODIFIED) &&
-                            ((p->score &&
-                              strchr(filter, DIFF_STATUS_FILTER_BROKEN)) ||
-                             (!p->score &&
-                              strchr(filter, DIFF_STATUS_MODIFIED)))) ||
-                           ((p->status != DIFF_STATUS_MODIFIED) &&
-                            strchr(filter, p->status)))
-                               found++;
-               }
-               if (found)
-                       return;
-
-               /* otherwise we will clear the whole queue
-                * by copying the empty outq at the end of this
-                * function, but first clear the current entries
-                * in the queue.
-                */
-               for (i = 0; i < q->nr; i++)
-                       diff_free_filepair(q->queue[i]);
-       }
-       else {
-               /* Only the matching ones */
-               for (i = 0; i < q->nr; i++) {
-                       struct diff_filepair *p = q->queue[i];
-
-                       if (((p->status == DIFF_STATUS_MODIFIED) &&
-                            ((p->score &&
-                              strchr(filter, DIFF_STATUS_FILTER_BROKEN)) ||
-                             (!p->score &&
-                              strchr(filter, DIFF_STATUS_MODIFIED)))) ||
-                           ((p->status != DIFF_STATUS_MODIFIED) &&
-                            strchr(filter, p->status)))
-                               diff_q(&outq, p);
-                       else
-                               diff_free_filepair(p);
-               }
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               if (!ce_stage(ce))
+                       continue;
+               ce->ce_flags |= htons(CE_STAGEMASK);
        }
-       free(q->queue);
-       *q = outq;
 }
 
-void diffcore_std(struct diff_options *options)
+int run_diff_index(struct rev_info *revs, int cached)
 {
-       if (options->break_opt != -1)
-               diffcore_break(options->break_opt);
-       if (options->detect_rename)
-               diffcore_rename(options);
-       if (options->break_opt != -1)
-               diffcore_merge_broken();
-       if (options->pickaxe)
-               diffcore_pickaxe(options->pickaxe, options->pickaxe_opts);
-       if (options->orderfile)
-               diffcore_order(options->orderfile);
-       diff_resolve_rename_copy();
-       diffcore_apply_filter(options->filter);
-}
-
-
-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);
-}
+       int ret;
+       struct object *ent;
+       struct tree *tree;
+       const char *tree_name;
+       int match_missing = 0;
 
-void diff_addremove(struct diff_options *options,
-                   int addremove, unsigned mode,
-                   const unsigned char *sha1,
-                   const char *base, const char *path)
-{
-       char concatpath[PATH_MAX];
-       struct diff_filespec *one, *two;
-
-       /* This may look odd, but it is a preparation for
-        * feeding "there are unchanged files which should
-        * not produce diffs, but when you are doing copy
-        * detection you would need them, so here they are"
-        * entries to the diff-core.  They will be prefixed
-        * with something like '=' or '*' (I haven't decided
-        * which but should not make any difference).
-        * Feeding the same new and old to diff_change() 
-        * also has the same effect.
-        * Before the final output happens, they are pruned after
-        * merged into rename/copy pairs as appropriate.
+       /* 
+        * Backward compatibility wart - "diff-index -m" does
+        * not mean "do not ignore merges", but totally different.
         */
-       if (options->reverse_diff)
-               addremove = (addremove == '+' ? '-' :
-                            addremove == '-' ? '+' : addremove);
-
-       if (!path) path = "";
-       sprintf(concatpath, "%s%s", base, path);
-       one = alloc_filespec(concatpath);
-       two = alloc_filespec(concatpath);
-
-       if (addremove != '+')
-               fill_filespec(one, sha1, mode);
-       if (addremove != '-')
-               fill_filespec(two, sha1, mode);
-
-       diff_queue(&diff_queued_diff, one, two);
-}
-
-void diff_change(struct diff_options *options,
-                unsigned old_mode, unsigned new_mode,
-                const unsigned char *old_sha1,
-                const unsigned char *new_sha1,
-                const char *base, const char *path) 
-{
-       char concatpath[PATH_MAX];
-       struct diff_filespec *one, *two;
+       if (!revs->ignore_merges)
+               match_missing = 1;
 
-       if (options->reverse_diff) {
-               unsigned tmp;
-               const unsigned char *tmp_c;
-               tmp = old_mode; old_mode = new_mode; new_mode = tmp;
-               tmp_c = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_c;
+       if (read_cache() < 0) {
+               perror("read_cache");
+               return -1;
        }
-       if (!path) path = "";
-       sprintf(concatpath, "%s%s", base, path);
-       one = alloc_filespec(concatpath);
-       two = alloc_filespec(concatpath);
-       fill_filespec(one, old_sha1, old_mode);
-       fill_filespec(two, new_sha1, new_mode);
-
-       diff_queue(&diff_queued_diff, one, two);
-}
-
-void diff_unmerge(struct diff_options *options,
-                 const char *path)
-{
-       struct diff_filespec *one, *two;
-       one = alloc_filespec(path);
-       two = alloc_filespec(path);
-       diff_queue(&diff_queued_diff, one, two);
+       mark_merge_entries();
+
+       ent = revs->pending_objects->item;
+       tree_name = revs->pending_objects->name;
+       tree = parse_tree_indirect(ent->sha1);
+       if (!tree)
+               return error("bad tree object %s", tree_name);
+       if (read_tree(tree, 1, revs->prune_data))
+               return error("unable to read tree object %s", tree_name);
+       ret = diff_cache(revs, active_cache, active_nr, revs->prune_data,
+                        cached, match_missing);
+       diffcore_std(&revs->diffopt);
+       diff_flush(&revs->diffopt);
+       return ret;
 }
diff --git a/diff.c b/diff.c
new file mode 100644 (file)
index 0000000..e16e0bf
--- /dev/null
+++ b/diff.c
@@ -0,0 +1,2044 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include "cache.h"
+#include "quote.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "delta.h"
+#include "xdiff-interface.h"
+
+static int use_size_cache;
+
+int diff_rename_limit_default = -1;
+
+int git_diff_config(const char *var, const char *value)
+{
+       if (!strcmp(var, "diff.renamelimit")) {
+               diff_rename_limit_default = git_config_int(var, value);
+               return 0;
+       }
+
+       return git_default_config(var, value);
+}
+
+static char *quote_one(const char *str)
+{
+       int needlen;
+       char *xp;
+
+       if (!str)
+               return NULL;
+       needlen = quote_c_style(str, NULL, NULL, 0);
+       if (!needlen)
+               return strdup(str);
+       xp = xmalloc(needlen + 1);
+       quote_c_style(str, xp, NULL, 0);
+       return xp;
+}
+
+static char *quote_two(const char *one, const char *two)
+{
+       int need_one = quote_c_style(one, NULL, NULL, 1);
+       int need_two = quote_c_style(two, NULL, NULL, 1);
+       char *xp;
+
+       if (need_one + need_two) {
+               if (!need_one) need_one = strlen(one);
+               if (!need_two) need_one = strlen(two);
+
+               xp = xmalloc(need_one + need_two + 3);
+               xp[0] = '"';
+               quote_c_style(one, xp + 1, NULL, 1);
+               quote_c_style(two, xp + need_one + 1, NULL, 1);
+               strcpy(xp + need_one + need_two + 1, "\"");
+               return xp;
+       }
+       need_one = strlen(one);
+       need_two = strlen(two);
+       xp = xmalloc(need_one + need_two + 1);
+       strcpy(xp, one);
+       strcpy(xp + need_one, two);
+       return xp;
+}
+
+static const char *external_diff(void)
+{
+       static const char *external_diff_cmd = NULL;
+       static int done_preparing = 0;
+
+       if (done_preparing)
+               return external_diff_cmd;
+       external_diff_cmd = getenv("GIT_EXTERNAL_DIFF");
+       done_preparing = 1;
+       return external_diff_cmd;
+}
+
+#define TEMPFILE_PATH_LEN              50
+
+static struct diff_tempfile {
+       const char *name; /* filename external diff should read from */
+       char hex[41];
+       char mode[10];
+       char tmp_path[TEMPFILE_PATH_LEN];
+} diff_temp[2];
+
+static int count_lines(const char *data, int size)
+{
+       int count, ch, completely_empty = 1, nl_just_seen = 0;
+       count = 0;
+       while (0 < size--) {
+               ch = *data++;
+               if (ch == '\n') {
+                       count++;
+                       nl_just_seen = 1;
+                       completely_empty = 0;
+               }
+               else {
+                       nl_just_seen = 0;
+                       completely_empty = 0;
+               }
+       }
+       if (completely_empty)
+               return 0;
+       if (!nl_just_seen)
+               count++; /* no trailing newline */
+       return count;
+}
+
+static void print_line_count(int count)
+{
+       switch (count) {
+       case 0:
+               printf("0,0");
+               break;
+       case 1:
+               printf("1");
+               break;
+       default:
+               printf("1,%d", count);
+               break;
+       }
+}
+
+static void copy_file(int prefix, const char *data, int size)
+{
+       int ch, nl_just_seen = 1;
+       while (0 < size--) {
+               ch = *data++;
+               if (nl_just_seen)
+                       putchar(prefix);
+               putchar(ch);
+               if (ch == '\n')
+                       nl_just_seen = 1;
+               else
+                       nl_just_seen = 0;
+       }
+       if (!nl_just_seen)
+               printf("\n\\ No newline at end of file\n");
+}
+
+static void emit_rewrite_diff(const char *name_a,
+                             const char *name_b,
+                             struct diff_filespec *one,
+                             struct diff_filespec *two)
+{
+       int lc_a, lc_b;
+       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);
+       printf("--- %s\n+++ %s\n@@ -", name_a, name_b);
+       print_line_count(lc_a);
+       printf(" +");
+       print_line_count(lc_b);
+       printf(" @@\n");
+       if (lc_a)
+               copy_file('-', one->data, one->size);
+       if (lc_b)
+               copy_file('+', two->data, two->size);
+}
+
+static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
+{
+       if (!DIFF_FILE_VALID(one)) {
+               mf->ptr = ""; /* does not matter */
+               mf->size = 0;
+               return 0;
+       }
+       else if (diff_populate_filespec(one, 0))
+               return -1;
+       mf->ptr = one->data;
+       mf->size = one->size;
+       return 0;
+}
+
+struct emit_callback {
+       const char **label_path;
+};
+
+static int fn_out(void *priv, mmbuffer_t *mb, int nbuf)
+{
+       int i;
+       struct emit_callback *ecbdata = priv;
+
+       if (ecbdata->label_path[0]) {
+               printf("--- %s\n", ecbdata->label_path[0]);
+               printf("+++ %s\n", ecbdata->label_path[1]);
+               ecbdata->label_path[0] = ecbdata->label_path[1] = NULL;
+       }
+       for (i = 0; i < nbuf; i++)
+               if (!fwrite(mb[i].ptr, mb[i].size, 1, stdout))
+                       return -1;
+       return 0;
+}
+
+static char *pprint_rename(const char *a, const char *b)
+{
+       const char *old = a;
+       const char *new = b;
+       char *name = NULL;
+       int pfx_length, sfx_length;
+       int len_a = strlen(a);
+       int len_b = strlen(b);
+
+       /* Find common prefix */
+       pfx_length = 0;
+       while (*old && *new && *old == *new) {
+               if (*old == '/')
+                       pfx_length = old - a + 1;
+               old++;
+               new++;
+       }
+
+       /* Find common suffix */
+       old = a + len_a;
+       new = b + len_b;
+       sfx_length = 0;
+       while (a <= old && b <= new && *old == *new) {
+               if (*old == '/')
+                       sfx_length = len_a - (old - a);
+               old--;
+               new--;
+       }
+
+       /*
+        * pfx{mid-a => mid-b}sfx
+        * {pfx-a => pfx-b}sfx
+        * pfx{sfx-a => sfx-b}
+        * name-a => name-b
+        */
+       if (pfx_length + sfx_length) {
+               int a_midlen = len_a - pfx_length - sfx_length;
+               int b_midlen = len_b - pfx_length - sfx_length;
+               if (a_midlen < 0) a_midlen = 0;
+               if (b_midlen < 0) b_midlen = 0;
+
+               name = xmalloc(len_a + len_b - pfx_length - sfx_length + 7);
+               sprintf(name, "%.*s{%.*s => %.*s}%s",
+                       pfx_length, a,
+                       a_midlen, a + pfx_length,
+                       b_midlen, b + pfx_length,
+                       a + len_a - sfx_length);
+       }
+       else {
+               name = xmalloc(len_a + len_b + 5);
+               sprintf(name, "%s => %s", a, b);
+       }
+       return name;
+}
+
+struct diffstat_t {
+       struct xdiff_emit_state xm;
+
+       int nr;
+       int alloc;
+       struct diffstat_file {
+               char *name;
+               unsigned is_unmerged:1;
+               unsigned is_binary:1;
+               unsigned is_renamed:1;
+               unsigned int added, deleted;
+       } **files;
+};
+
+static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,
+                                         const char *name_a,
+                                         const char *name_b)
+{
+       struct diffstat_file *x;
+       x = xcalloc(sizeof (*x), 1);
+       if (diffstat->nr == diffstat->alloc) {
+               diffstat->alloc = alloc_nr(diffstat->alloc);
+               diffstat->files = xrealloc(diffstat->files,
+                               diffstat->alloc * sizeof(x));
+       }
+       diffstat->files[diffstat->nr++] = x;
+       if (name_b) {
+               x->name = pprint_rename(name_a, name_b);
+               x->is_renamed = 1;
+       }
+       else
+               x->name = strdup(name_a);
+       return x;
+}
+
+static void diffstat_consume(void *priv, char *line, unsigned long len)
+{
+       struct diffstat_t *diffstat = priv;
+       struct diffstat_file *x = diffstat->files[diffstat->nr - 1];
+
+       if (line[0] == '+')
+               x->added++;
+       else if (line[0] == '-')
+               x->deleted++;
+}
+
+static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
+static const char minuses[]= "----------------------------------------------------------------------";
+
+static void show_stats(struct diffstat_t* data)
+{
+       int i, len, add, del, total, adds = 0, dels = 0;
+       int max, max_change = 0, max_len = 0;
+       int total_files = data->nr;
+
+       if (data->nr == 0)
+               return;
+
+       for (i = 0; i < data->nr; i++) {
+               struct diffstat_file *file = data->files[i];
+
+               len = strlen(file->name);
+               if (max_len < len)
+                       max_len = len;
+
+               if (file->is_binary || file->is_unmerged)
+                       continue;
+               if (max_change < file->added + file->deleted)
+                       max_change = file->added + file->deleted;
+       }
+
+       for (i = 0; i < data->nr; i++) {
+               char *prefix = "";
+               char *name = data->files[i]->name;
+               int added = data->files[i]->added;
+               int deleted = data->files[i]->deleted;
+
+               if (0 < (len = quote_c_style(name, NULL, NULL, 0))) {
+                       char *qname = xmalloc(len + 1);
+                       quote_c_style(name, qname, NULL, 0);
+                       free(name);
+                       data->files[i]->name = name = qname;
+               }
+
+               /*
+                * "scale" the filename
+                */
+               len = strlen(name);
+               max = max_len;
+               if (max > 50)
+                       max = 50;
+               if (len > max) {
+                       char *slash;
+                       prefix = "...";
+                       max -= 3;
+                       name += len - max;
+                       slash = strchr(name, '/');
+                       if (slash)
+                               name = slash;
+               }
+               len = max;
+
+               /*
+                * scale the add/delete
+                */
+               max = max_change;
+               if (max + len > 70)
+                       max = 70 - len;
+
+               if (data->files[i]->is_binary) {
+                       printf(" %s%-*s |  Bin\n", prefix, len, name);
+                       goto free_diffstat_file;
+               }
+               else if (data->files[i]->is_unmerged) {
+                       printf(" %s%-*s |  Unmerged\n", prefix, len, name);
+                       goto free_diffstat_file;
+               }
+               else if (!data->files[i]->is_renamed &&
+                        (added + deleted == 0)) {
+                       total_files--;
+                       goto free_diffstat_file;
+               }
+
+               add = added;
+               del = deleted;
+               total = add + del;
+               adds += add;
+               dels += del;
+
+               if (max_change > 0) {
+                       total = (total * max + max_change / 2) / max_change;
+                       add = (add * max + max_change / 2) / max_change;
+                       del = total - add;
+               }
+               printf(" %s%-*s |%5d %.*s%.*s\n", prefix,
+                               len, name, added + deleted,
+                               add, pluses, del, minuses);
+       free_diffstat_file:
+               free(data->files[i]->name);
+               free(data->files[i]);
+       }
+       free(data->files);
+       printf(" %d files changed, %d insertions(+), %d deletions(-)\n",
+                       total_files, adds, dels);
+}
+
+static unsigned char *deflate_it(char *data,
+                                unsigned long size,
+                                unsigned long *result_size)
+{
+       int bound;
+       unsigned char *deflated;
+       z_stream stream;
+
+       memset(&stream, 0, sizeof(stream));
+       deflateInit(&stream, Z_BEST_COMPRESSION);
+       bound = deflateBound(&stream, size);
+       deflated = xmalloc(bound);
+       stream.next_out = deflated;
+       stream.avail_out = bound;
+
+       stream.next_in = (unsigned char *)data;
+       stream.avail_in = size;
+       while (deflate(&stream, Z_FINISH) == Z_OK)
+               ; /* nothing */
+       deflateEnd(&stream);
+       *result_size = stream.total_out;
+       return deflated;
+}
+
+static void emit_binary_diff(mmfile_t *one, mmfile_t *two)
+{
+       void *cp;
+       void *delta;
+       void *deflated;
+       void *data;
+       unsigned long orig_size;
+       unsigned long delta_size;
+       unsigned long deflate_size;
+       unsigned long data_size;
+
+       printf("GIT binary patch\n");
+       /* We could do deflated delta, or we could do just deflated two,
+        * whichever is smaller.
+        */
+       delta = NULL;
+       deflated = deflate_it(two->ptr, two->size, &deflate_size);
+       if (one->size && two->size) {
+               delta = diff_delta(one->ptr, one->size,
+                                  two->ptr, two->size,
+                                  &delta_size, deflate_size);
+               if (delta) {
+                       void *to_free = delta;
+                       orig_size = delta_size;
+                       delta = deflate_it(delta, delta_size, &delta_size);
+                       free(to_free);
+               }
+       }
+
+       if (delta && delta_size < deflate_size) {
+               printf("delta %lu\n", orig_size);
+               free(deflated);
+               data = delta;
+               data_size = delta_size;
+       }
+       else {
+               printf("literal %lu\n", two->size);
+               free(delta);
+               data = deflated;
+               data_size = deflate_size;
+       }
+
+       /* emit data encoded in base85 */
+       cp = data;
+       while (data_size) {
+               int bytes = (52 < data_size) ? 52 : data_size;
+               char line[70];
+               data_size -= bytes;
+               if (bytes <= 26)
+                       line[0] = bytes + 'A' - 1;
+               else
+                       line[0] = bytes - 26 + 'a' - 1;
+               encode_85(line + 1, cp, bytes);
+               cp += bytes;
+               puts(line);
+       }
+       printf("\n");
+       free(data);
+}
+
+#define FIRST_FEW_BYTES 8000
+static int mmfile_is_binary(mmfile_t *mf)
+{
+       long sz = mf->size;
+       if (FIRST_FEW_BYTES < sz)
+               sz = FIRST_FEW_BYTES;
+       if (memchr(mf->ptr, 0, sz))
+               return 1;
+       return 0;
+}
+
+static void builtin_diff(const char *name_a,
+                        const char *name_b,
+                        struct diff_filespec *one,
+                        struct diff_filespec *two,
+                        const char *xfrm_msg,
+                        struct diff_options *o,
+                        int complete_rewrite)
+{
+       mmfile_t mf1, mf2;
+       const char *lbl[2];
+       char *a_one, *b_two;
+
+       a_one = quote_two("a/", name_a);
+       b_two = quote_two("b/", name_b);
+       lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
+       lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
+       printf("diff --git %s %s\n", a_one, b_two);
+       if (lbl[0][0] == '/') {
+               /* /dev/null */
+               printf("new file mode %06o\n", two->mode);
+               if (xfrm_msg && xfrm_msg[0])
+                       puts(xfrm_msg);
+       }
+       else if (lbl[1][0] == '/') {
+               printf("deleted file mode %06o\n", one->mode);
+               if (xfrm_msg && xfrm_msg[0])
+                       puts(xfrm_msg);
+       }
+       else {
+               if (one->mode != two->mode) {
+                       printf("old mode %06o\n", one->mode);
+                       printf("new mode %06o\n", two->mode);
+               }
+               if (xfrm_msg && xfrm_msg[0])
+                       puts(xfrm_msg);
+               /*
+                * we do not run diff between different kind
+                * of objects.
+                */
+               if ((one->mode ^ two->mode) & S_IFMT)
+                       goto free_ab_and_return;
+               if (complete_rewrite) {
+                       emit_rewrite_diff(name_a, name_b, one, two);
+                       goto free_ab_and_return;
+               }
+       }
+
+       if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
+               die("unable to read files to diff");
+
+       if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2)) {
+               /* Quite common confusing case */
+               if (mf1.size == mf2.size &&
+                   !memcmp(mf1.ptr, mf2.ptr, mf1.size))
+                       goto free_ab_and_return;
+               if (o->binary)
+                       emit_binary_diff(&mf1, &mf2);
+               else
+                       printf("Binary files %s and %s differ\n",
+                              lbl[0], lbl[1]);
+       }
+       else {
+               /* Crazy xdl interfaces.. */
+               const char *diffopts = getenv("GIT_DIFF_OPTS");
+               xpparam_t xpp;
+               xdemitconf_t xecfg;
+               xdemitcb_t ecb;
+               struct emit_callback ecbdata;
+
+               ecbdata.label_path = lbl;
+               xpp.flags = XDF_NEED_MINIMAL;
+               xecfg.ctxlen = o->context;
+               xecfg.flags = XDL_EMIT_FUNCNAMES;
+               if (!diffopts)
+                       ;
+               else if (!strncmp(diffopts, "--unified=", 10))
+                       xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10);
+               else if (!strncmp(diffopts, "-u", 2))
+                       xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10);
+               ecb.outf = fn_out;
+               ecb.priv = &ecbdata;
+               xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
+       }
+
+ free_ab_and_return:
+       free(a_one);
+       free(b_two);
+       return;
+}
+
+static void builtin_diffstat(const char *name_a, const char *name_b,
+                            struct diff_filespec *one,
+                            struct diff_filespec *two,
+                            struct diffstat_t *diffstat,
+                            int complete_rewrite)
+{
+       mmfile_t mf1, mf2;
+       struct diffstat_file *data;
+
+       data = diffstat_add(diffstat, name_a, name_b);
+
+       if (!one || !two) {
+               data->is_unmerged = 1;
+               return;
+       }
+       if (complete_rewrite) {
+               diff_populate_filespec(one, 0);
+               diff_populate_filespec(two, 0);
+               data->deleted = count_lines(one->data, one->size);
+               data->added = count_lines(two->data, two->size);
+               return;
+       }
+       if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
+               die("unable to read files to diff");
+
+       if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2))
+               data->is_binary = 1;
+       else {
+               /* Crazy xdl interfaces.. */
+               xpparam_t xpp;
+               xdemitconf_t xecfg;
+               xdemitcb_t ecb;
+
+               xpp.flags = XDF_NEED_MINIMAL;
+               xecfg.ctxlen = 0;
+               xecfg.flags = 0;
+               ecb.outf = xdiff_outf;
+               ecb.priv = diffstat;
+               xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
+       }
+}
+
+struct diff_filespec *alloc_filespec(const char *path)
+{
+       int namelen = strlen(path);
+       struct diff_filespec *spec = xmalloc(sizeof(*spec) + namelen + 1);
+
+       memset(spec, 0, sizeof(*spec));
+       spec->path = (char *)(spec + 1);
+       memcpy(spec->path, path, namelen+1);
+       return spec;
+}
+
+void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1,
+                  unsigned short mode)
+{
+       if (mode) {
+               spec->mode = canon_mode(mode);
+               memcpy(spec->sha1, sha1, 20);
+               spec->sha1_valid = !!memcmp(sha1, null_sha1, 20);
+       }
+}
+
+/*
+ * Given a name and sha1 pair, if the dircache tells us the file in
+ * the work tree has that object contents, return true, so that
+ * prepare_temp_file() does not have to inflate and extract.
+ */
+static int work_tree_matches(const char *name, const unsigned char *sha1)
+{
+       struct cache_entry *ce;
+       struct stat st;
+       int pos, len;
+
+       /* We do not read the cache ourselves here, because the
+        * benchmark with my previous version that always reads cache
+        * shows that it makes things worse for diff-tree comparing
+        * two linux-2.6 kernel trees in an already checked out work
+        * tree.  This is because most diff-tree comparisons deal with
+        * only a small number of files, while reading the cache is
+        * expensive for a large project, and its cost outweighs the
+        * savings we get by not inflating the object to a temporary
+        * file.  Practically, this code only helps when we are used
+        * by diff-cache --cached, which does read the cache before
+        * calling us.
+        */
+       if (!active_cache)
+               return 0;
+
+       len = strlen(name);
+       pos = cache_name_pos(name, len);
+       if (pos < 0)
+               return 0;
+       ce = active_cache[pos];
+       if ((lstat(name, &st) < 0) ||
+           !S_ISREG(st.st_mode) || /* careful! */
+           ce_match_stat(ce, &st, 0) ||
+           memcmp(sha1, ce->sha1, 20))
+               return 0;
+       /* we return 1 only when we can stat, it is a regular file,
+        * stat information matches, and sha1 recorded in the cache
+        * matches.  I.e. we know the file in the work tree really is
+        * the same as the <name, sha1> pair.
+        */
+       return 1;
+}
+
+static struct sha1_size_cache {
+       unsigned char sha1[20];
+       unsigned long size;
+} **sha1_size_cache;
+static int sha1_size_cache_nr, sha1_size_cache_alloc;
+
+static struct sha1_size_cache *locate_size_cache(unsigned char *sha1,
+                                                int find_only,
+                                                unsigned long size)
+{
+       int first, last;
+       struct sha1_size_cache *e;
+
+       first = 0;
+       last = sha1_size_cache_nr;
+       while (last > first) {
+               int cmp, next = (last + first) >> 1;
+               e = sha1_size_cache[next];
+               cmp = memcmp(e->sha1, sha1, 20);
+               if (!cmp)
+                       return e;
+               if (cmp < 0) {
+                       last = next;
+                       continue;
+               }
+               first = next+1;
+       }
+       /* not found */
+       if (find_only)
+               return NULL;
+       /* insert to make it at "first" */
+       if (sha1_size_cache_alloc <= sha1_size_cache_nr) {
+               sha1_size_cache_alloc = alloc_nr(sha1_size_cache_alloc);
+               sha1_size_cache = xrealloc(sha1_size_cache,
+                                          sha1_size_cache_alloc *
+                                          sizeof(*sha1_size_cache));
+       }
+       sha1_size_cache_nr++;
+       if (first < sha1_size_cache_nr)
+               memmove(sha1_size_cache + first + 1, sha1_size_cache + first,
+                       (sha1_size_cache_nr - first - 1) *
+                       sizeof(*sha1_size_cache));
+       e = xmalloc(sizeof(struct sha1_size_cache));
+       sha1_size_cache[first] = e;
+       memcpy(e->sha1, sha1, 20);
+       e->size = size;
+       return e;
+}
+
+/*
+ * 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.
+ * diff_filespec has data and size fields for this purpose.
+ */
+int diff_populate_filespec(struct diff_filespec *s, int size_only)
+{
+       int err = 0;
+       if (!DIFF_FILE_VALID(s))
+               die("internal error: asking to populate invalid file.");
+       if (S_ISDIR(s->mode))
+               return -1;
+
+       if (!use_size_cache)
+               size_only = 0;
+
+       if (s->data)
+               return err;
+       if (!s->sha1_valid ||
+           work_tree_matches(s->path, s->sha1)) {
+               struct stat st;
+               int fd;
+               if (lstat(s->path, &st) < 0) {
+                       if (errno == ENOENT) {
+                       err_empty:
+                               err = -1;
+                       empty:
+                               s->data = "";
+                               s->size = 0;
+                               return err;
+                       }
+               }
+               s->size = st.st_size;
+               if (!s->size)
+                       goto empty;
+               if (size_only)
+                       return 0;
+               if (S_ISLNK(st.st_mode)) {
+                       int ret;
+                       s->data = xmalloc(s->size);
+                       s->should_free = 1;
+                       ret = readlink(s->path, s->data, s->size);
+                       if (ret < 0) {
+                               free(s->data);
+                               goto err_empty;
+                       }
+                       return 0;
+               }
+               fd = open(s->path, O_RDONLY);
+               if (fd < 0)
+                       goto err_empty;
+               s->data = mmap(NULL, s->size, PROT_READ, MAP_PRIVATE, fd, 0);
+               close(fd);
+               if (s->data == MAP_FAILED)
+                       goto err_empty;
+               s->should_munmap = 1;
+       }
+       else {
+               char type[20];
+               struct sha1_size_cache *e;
+
+               if (size_only) {
+                       e = locate_size_cache(s->sha1, 1, 0);
+                       if (e) {
+                               s->size = e->size;
+                               return 0;
+                       }
+                       if (!sha1_object_info(s->sha1, type, &s->size))
+                               locate_size_cache(s->sha1, 0, s->size);
+               }
+               else {
+                       s->data = read_sha1_file(s->sha1, type, &s->size);
+                       s->should_free = 1;
+               }
+       }
+       return 0;
+}
+
+void diff_free_filespec_data(struct diff_filespec *s)
+{
+       if (s->should_free)
+               free(s->data);
+       else if (s->should_munmap)
+               munmap(s->data, s->size);
+       s->should_free = s->should_munmap = 0;
+       s->data = NULL;
+       free(s->cnt_data);
+       s->cnt_data = NULL;
+}
+
+static void prep_temp_blob(struct diff_tempfile *temp,
+                          void *blob,
+                          unsigned long size,
+                          const unsigned char *sha1,
+                          int mode)
+{
+       int fd;
+
+       fd = git_mkstemp(temp->tmp_path, TEMPFILE_PATH_LEN, ".diff_XXXXXX");
+       if (fd < 0)
+               die("unable to create temp-file");
+       if (write(fd, blob, size) != size)
+               die("unable to write temp-file");
+       close(fd);
+       temp->name = temp->tmp_path;
+       strcpy(temp->hex, sha1_to_hex(sha1));
+       temp->hex[40] = 0;
+       sprintf(temp->mode, "%06o", mode);
+}
+
+static void prepare_temp_file(const char *name,
+                             struct diff_tempfile *temp,
+                             struct diff_filespec *one)
+{
+       if (!DIFF_FILE_VALID(one)) {
+       not_a_valid_file:
+               /* A '-' entry produces this for file-2, and
+                * a '+' entry produces this for file-1.
+                */
+               temp->name = "/dev/null";
+               strcpy(temp->hex, ".");
+               strcpy(temp->mode, ".");
+               return;
+       }
+
+       if (!one->sha1_valid ||
+           work_tree_matches(name, one->sha1)) {
+               struct stat st;
+               if (lstat(name, &st) < 0) {
+                       if (errno == ENOENT)
+                               goto not_a_valid_file;
+                       die("stat(%s): %s", name, strerror(errno));
+               }
+               if (S_ISLNK(st.st_mode)) {
+                       int ret;
+                       char buf[PATH_MAX + 1]; /* ought to be SYMLINK_MAX */
+                       if (sizeof(buf) <= st.st_size)
+                               die("symlink too long: %s", name);
+                       ret = readlink(name, buf, st.st_size);
+                       if (ret < 0)
+                               die("readlink(%s)", name);
+                       prep_temp_blob(temp, buf, st.st_size,
+                                      (one->sha1_valid ?
+                                       one->sha1 : null_sha1),
+                                      (one->sha1_valid ?
+                                       one->mode : S_IFLNK));
+               }
+               else {
+                       /* we can borrow from the file in the work tree */
+                       temp->name = name;
+                       if (!one->sha1_valid)
+                               strcpy(temp->hex, sha1_to_hex(null_sha1));
+                       else
+                               strcpy(temp->hex, sha1_to_hex(one->sha1));
+                       /* Even though we may sometimes borrow the
+                        * contents from the work tree, we always want
+                        * one->mode.  mode is trustworthy even when
+                        * !(one->sha1_valid), as long as
+                        * DIFF_FILE_VALID(one).
+                        */
+                       sprintf(temp->mode, "%06o", one->mode);
+               }
+               return;
+       }
+       else {
+               if (diff_populate_filespec(one, 0))
+                       die("cannot read data blob for %s", one->path);
+               prep_temp_blob(temp, one->data, one->size,
+                              one->sha1, one->mode);
+       }
+}
+
+static void remove_tempfile(void)
+{
+       int i;
+
+       for (i = 0; i < 2; i++)
+               if (diff_temp[i].name == diff_temp[i].tmp_path) {
+                       unlink(diff_temp[i].name);
+                       diff_temp[i].name = NULL;
+               }
+}
+
+static void remove_tempfile_on_signal(int signo)
+{
+       remove_tempfile();
+       signal(SIGINT, SIG_DFL);
+       raise(signo);
+}
+
+static int spawn_prog(const char *pgm, const char **arg)
+{
+       pid_t pid;
+       int status;
+
+       fflush(NULL);
+       pid = fork();
+       if (pid < 0)
+               die("unable to fork");
+       if (!pid) {
+               execvp(pgm, (char *const*) arg);
+               exit(255);
+       }
+
+       while (waitpid(pid, &status, 0) < 0) {
+               if (errno == EINTR)
+                       continue;
+               return -1;
+       }
+
+       /* Earlier we did not check the exit status because
+        * diff exits non-zero if files are different, and
+        * we are not interested in knowing that.  It was a
+        * mistake which made it harder to quit a diff-*
+        * session that uses the git-apply-patch-script as
+        * the GIT_EXTERNAL_DIFF.  A custom GIT_EXTERNAL_DIFF
+        * should also exit non-zero only when it wants to
+        * abort the entire diff-* session.
+        */
+       if (WIFEXITED(status) && !WEXITSTATUS(status))
+               return 0;
+       return -1;
+}
+
+/* An external diff command takes:
+ *
+ * diff-cmd name infile1 infile1-sha1 infile1-mode \
+ *               infile2 infile2-sha1 infile2-mode [ rename-to ]
+ *
+ */
+static void run_external_diff(const char *pgm,
+                             const char *name,
+                             const char *other,
+                             struct diff_filespec *one,
+                             struct diff_filespec *two,
+                             const char *xfrm_msg,
+                             int complete_rewrite)
+{
+       const char *spawn_arg[10];
+       struct diff_tempfile *temp = diff_temp;
+       int retval;
+       static int atexit_asked = 0;
+       const char *othername;
+       const char **arg = &spawn_arg[0];
+
+       othername = (other? other : name);
+       if (one && two) {
+               prepare_temp_file(name, &temp[0], one);
+               prepare_temp_file(othername, &temp[1], two);
+               if (! atexit_asked &&
+                   (temp[0].name == temp[0].tmp_path ||
+                    temp[1].name == temp[1].tmp_path)) {
+                       atexit_asked = 1;
+                       atexit(remove_tempfile);
+               }
+               signal(SIGINT, remove_tempfile_on_signal);
+       }
+
+       if (one && two) {
+               *arg++ = pgm;
+               *arg++ = name;
+               *arg++ = temp[0].name;
+               *arg++ = temp[0].hex;
+               *arg++ = temp[0].mode;
+               *arg++ = temp[1].name;
+               *arg++ = temp[1].hex;
+               *arg++ = temp[1].mode;
+               if (other) {
+                       *arg++ = other;
+                       *arg++ = xfrm_msg;
+               }
+       } else {
+               *arg++ = pgm;
+               *arg++ = name;
+       }
+       *arg = NULL;
+       retval = spawn_prog(pgm, spawn_arg);
+       remove_tempfile();
+       if (retval) {
+               fprintf(stderr, "external diff died, stopping at %s.\n", name);
+               exit(1);
+       }
+}
+
+static void run_diff_cmd(const char *pgm,
+                        const char *name,
+                        const char *other,
+                        struct diff_filespec *one,
+                        struct diff_filespec *two,
+                        const char *xfrm_msg,
+                        struct diff_options *o,
+                        int complete_rewrite)
+{
+       if (pgm) {
+               run_external_diff(pgm, name, other, one, two, xfrm_msg,
+                                 complete_rewrite);
+               return;
+       }
+       if (one && two)
+               builtin_diff(name, other ? other : name,
+                            one, two, xfrm_msg, o, complete_rewrite);
+       else
+               printf("* Unmerged path %s\n", name);
+}
+
+static void diff_fill_sha1_info(struct diff_filespec *one)
+{
+       if (DIFF_FILE_VALID(one)) {
+               if (!one->sha1_valid) {
+                       struct stat st;
+                       if (lstat(one->path, &st) < 0)
+                               die("stat %s", one->path);
+                       if (index_path(one->sha1, one->path, &st, 0))
+                               die("cannot hash %s\n", one->path);
+               }
+       }
+       else
+               memset(one->sha1, 0, 20);
+}
+
+static void run_diff(struct diff_filepair *p, struct diff_options *o)
+{
+       const char *pgm = external_diff();
+       char msg[PATH_MAX*2+300], *xfrm_msg;
+       struct diff_filespec *one;
+       struct diff_filespec *two;
+       const char *name;
+       const char *other;
+       char *name_munged, *other_munged;
+       int complete_rewrite = 0;
+       int len;
+
+       if (DIFF_PAIR_UNMERGED(p)) {
+               /* unmerged */
+               run_diff_cmd(pgm, p->one->path, NULL, NULL, NULL, NULL, o, 0);
+               return;
+       }
+
+       name = p->one->path;
+       other = (strcmp(name, p->two->path) ? p->two->path : NULL);
+       name_munged = quote_one(name);
+       other_munged = quote_one(other);
+       one = p->one; two = p->two;
+
+       diff_fill_sha1_info(one);
+       diff_fill_sha1_info(two);
+
+       len = 0;
+       switch (p->status) {
+       case DIFF_STATUS_COPIED:
+               len += snprintf(msg + len, sizeof(msg) - len,
+                               "similarity index %d%%\n"
+                               "copy from %s\n"
+                               "copy to %s\n",
+                               (int)(0.5 + p->score * 100.0/MAX_SCORE),
+                               name_munged, other_munged);
+               break;
+       case DIFF_STATUS_RENAMED:
+               len += snprintf(msg + len, sizeof(msg) - len,
+                               "similarity index %d%%\n"
+                               "rename from %s\n"
+                               "rename to %s\n",
+                               (int)(0.5 + p->score * 100.0/MAX_SCORE),
+                               name_munged, other_munged);
+               break;
+       case DIFF_STATUS_MODIFIED:
+               if (p->score) {
+                       len += snprintf(msg + len, sizeof(msg) - len,
+                                       "dissimilarity index %d%%\n",
+                                       (int)(0.5 + p->score *
+                                             100.0/MAX_SCORE));
+                       complete_rewrite = 1;
+                       break;
+               }
+               /* fallthru */
+       default:
+               /* nothing */
+               ;
+       }
+
+       if (memcmp(one->sha1, two->sha1, 20)) {
+               int abbrev = o->full_index ? 40 : DEFAULT_ABBREV;
+
+               len += snprintf(msg + len, sizeof(msg) - len,
+                               "index %.*s..%.*s",
+                               abbrev, sha1_to_hex(one->sha1),
+                               abbrev, sha1_to_hex(two->sha1));
+               if (one->mode == two->mode)
+                       len += snprintf(msg + len, sizeof(msg) - len,
+                                       " %06o", one->mode);
+               len += snprintf(msg + len, sizeof(msg) - len, "\n");
+       }
+
+       if (len)
+               msg[--len] = 0;
+       xfrm_msg = len ? msg : NULL;
+
+       if (!pgm &&
+           DIFF_FILE_VALID(one) && DIFF_FILE_VALID(two) &&
+           (S_IFMT & one->mode) != (S_IFMT & two->mode)) {
+               /* a filepair that changes between file and symlink
+                * needs to be split into deletion and creation.
+                */
+               struct diff_filespec *null = alloc_filespec(two->path);
+               run_diff_cmd(NULL, name, other, one, null, xfrm_msg, o, 0);
+               free(null);
+               null = alloc_filespec(one->path);
+               run_diff_cmd(NULL, name, other, null, two, xfrm_msg, o, 0);
+               free(null);
+       }
+       else
+               run_diff_cmd(pgm, name, other, one, two, xfrm_msg, o,
+                            complete_rewrite);
+
+       free(name_munged);
+       free(other_munged);
+}
+
+static void run_diffstat(struct diff_filepair *p, struct diff_options *o,
+                        struct diffstat_t *diffstat)
+{
+       const char *name;
+       const char *other;
+       int complete_rewrite = 0;
+
+       if (DIFF_PAIR_UNMERGED(p)) {
+               /* unmerged */
+               builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat, 0);
+               return;
+       }
+
+       name = p->one->path;
+       other = (strcmp(name, p->two->path) ? p->two->path : NULL);
+
+       diff_fill_sha1_info(p->one);
+       diff_fill_sha1_info(p->two);
+
+       if (p->status == DIFF_STATUS_MODIFIED && p->score)
+               complete_rewrite = 1;
+       builtin_diffstat(name, other, p->one, p->two, diffstat, complete_rewrite);
+}
+
+void diff_setup(struct diff_options *options)
+{
+       memset(options, 0, sizeof(*options));
+       options->output_format = DIFF_FORMAT_RAW;
+       options->line_termination = '\n';
+       options->break_opt = -1;
+       options->rename_limit = -1;
+       options->context = 3;
+
+       options->change = diff_change;
+       options->add_remove = diff_addremove;
+}
+
+int diff_setup_done(struct diff_options *options)
+{
+       if ((options->find_copies_harder &&
+            options->detect_rename != DIFF_DETECT_COPY) ||
+           (0 <= options->rename_limit && !options->detect_rename))
+               return -1;
+
+       /*
+        * These cases always need recursive; we do not drop caller-supplied
+        * recursive bits for other formats here.
+        */
+       if ((options->output_format == DIFF_FORMAT_PATCH) ||
+           (options->output_format == DIFF_FORMAT_DIFFSTAT))
+               options->recursive = 1;
+
+       if (options->detect_rename && options->rename_limit < 0)
+               options->rename_limit = diff_rename_limit_default;
+       if (options->setup & DIFF_SETUP_USE_CACHE) {
+               if (!active_cache)
+                       /* read-cache does not die even when it fails
+                        * so it is safe for us to do this here.  Also
+                        * it does not smudge active_cache or active_nr
+                        * when it fails, so we do not have to worry about
+                        * cleaning it up ourselves either.
+                        */
+                       read_cache();
+       }
+       if (options->setup & DIFF_SETUP_USE_SIZE_CACHE)
+               use_size_cache = 1;
+       if (options->abbrev <= 0 || 40 < options->abbrev)
+               options->abbrev = 40; /* full */
+
+       return 0;
+}
+
+int opt_arg(const char *arg, int arg_short, const char *arg_long, int *val)
+{
+       char c, *eq;
+       int len;
+
+       if (*arg != '-')
+               return 0;
+       c = *++arg;
+       if (!c)
+               return 0;
+       if (c == arg_short) {
+               c = *++arg;
+               if (!c)
+                       return 1;
+               if (val && isdigit(c)) {
+                       char *end;
+                       int n = strtoul(arg, &end, 10);
+                       if (*end)
+                               return 0;
+                       *val = n;
+                       return 1;
+               }
+               return 0;
+       }
+       if (c != '-')
+               return 0;
+       arg++;
+       eq = strchr(arg, '=');
+       if (eq)
+               len = eq - arg;
+       else
+               len = strlen(arg);
+       if (!len || strncmp(arg, arg_long, len))
+               return 0;
+       if (eq) {
+               int n;
+               char *end;
+               if (!isdigit(*++eq))
+                       return 0;
+               n = strtoul(eq, &end, 10);
+               if (*end)
+                       return 0;
+               *val = n;
+       }
+       return 1;
+}
+
+int diff_opt_parse(struct diff_options *options, const char **av, int ac)
+{
+       const char *arg = av[0];
+       if (!strcmp(arg, "-p") || !strcmp(arg, "-u"))
+               options->output_format = DIFF_FORMAT_PATCH;
+       else if (opt_arg(arg, 'U', "unified", &options->context))
+               options->output_format = DIFF_FORMAT_PATCH;
+       else if (!strcmp(arg, "--patch-with-raw")) {
+               options->output_format = DIFF_FORMAT_PATCH;
+               options->with_raw = 1;
+       }
+       else if (!strcmp(arg, "--stat"))
+               options->output_format = DIFF_FORMAT_DIFFSTAT;
+       else if (!strcmp(arg, "--summary"))
+               options->summary = 1;
+       else if (!strcmp(arg, "--patch-with-stat")) {
+               options->output_format = DIFF_FORMAT_PATCH;
+               options->with_stat = 1;
+       }
+       else if (!strcmp(arg, "-z"))
+               options->line_termination = 0;
+       else if (!strncmp(arg, "-l", 2))
+               options->rename_limit = strtoul(arg+2, NULL, 10);
+       else if (!strcmp(arg, "--full-index"))
+               options->full_index = 1;
+       else if (!strcmp(arg, "--binary")) {
+               options->output_format = DIFF_FORMAT_PATCH;
+               options->full_index = options->binary = 1;
+       }
+       else if (!strcmp(arg, "--name-only"))
+               options->output_format = DIFF_FORMAT_NAME;
+       else if (!strcmp(arg, "--name-status"))
+               options->output_format = DIFF_FORMAT_NAME_STATUS;
+       else if (!strcmp(arg, "-R"))
+               options->reverse_diff = 1;
+       else if (!strncmp(arg, "-S", 2))
+               options->pickaxe = arg + 2;
+       else if (!strcmp(arg, "-s"))
+               options->output_format = DIFF_FORMAT_NO_OUTPUT;
+       else if (!strncmp(arg, "-O", 2))
+               options->orderfile = arg + 2;
+       else if (!strncmp(arg, "--diff-filter=", 14))
+               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)) {
+               if ((options->break_opt =
+                    diff_scoreopt_parse(arg)) == -1)
+                       return -1;
+       }
+       else if (!strncmp(arg, "-M", 2)) {
+               if ((options->rename_score =
+                    diff_scoreopt_parse(arg)) == -1)
+                       return -1;
+               options->detect_rename = DIFF_DETECT_RENAME;
+       }
+       else if (!strncmp(arg, "-C", 2)) {
+               if ((options->rename_score =
+                    diff_scoreopt_parse(arg)) == -1)
+                       return -1;
+               options->detect_rename = DIFF_DETECT_COPY;
+       }
+       else if (!strcmp(arg, "--find-copies-harder"))
+               options->find_copies_harder = 1;
+       else if (!strcmp(arg, "--abbrev"))
+               options->abbrev = DEFAULT_ABBREV;
+       else if (!strncmp(arg, "--abbrev=", 9)) {
+               options->abbrev = strtoul(arg + 9, NULL, 10);
+               if (options->abbrev < MINIMUM_ABBREV)
+                       options->abbrev = MINIMUM_ABBREV;
+               else if (40 < options->abbrev)
+                       options->abbrev = 40;
+       }
+       else
+               return 0;
+       return 1;
+}
+
+static int parse_num(const char **cp_p)
+{
+       unsigned long num, scale;
+       int ch, dot;
+       const char *cp = *cp_p;
+
+       num = 0;
+       scale = 1;
+       dot = 0;
+       for(;;) {
+               ch = *cp;
+               if ( !dot && ch == '.' ) {
+                       scale = 1;
+                       dot = 1;
+               } else if ( ch == '%' ) {
+                       scale = dot ? scale*100 : 100;
+                       cp++;   /* % is always at the end */
+                       break;
+               } else if ( ch >= '0' && ch <= '9' ) {
+                       if ( scale < 100000 ) {
+                               scale *= 10;
+                               num = (num*10) + (ch-'0');
+                       }
+               } else {
+                       break;
+               }
+               cp++;
+       }
+       *cp_p = cp;
+
+       /* 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);
+}
+
+int diff_scoreopt_parse(const char *opt)
+{
+       int opt1, opt2, cmd;
+
+       if (*opt++ != '-')
+               return -1;
+       cmd = *opt++;
+       if (cmd != 'M' && cmd != 'C' && cmd != 'B')
+               return -1; /* that is not a -M, -C nor -B option */
+
+       opt1 = parse_num(&opt);
+       if (cmd != 'B')
+               opt2 = 0;
+       else {
+               if (*opt == 0)
+                       opt2 = 0;
+               else if (*opt != '/')
+                       return -1; /* we expect -B80/99 or -B80 */
+               else {
+                       opt++;
+                       opt2 = parse_num(&opt);
+               }
+       }
+       if (*opt != 0)
+               return -1;
+       return opt1 | (opt2 << 16);
+}
+
+struct diff_queue_struct diff_queued_diff;
+
+void diff_q(struct diff_queue_struct *queue, struct diff_filepair *dp)
+{
+       if (queue->alloc <= queue->nr) {
+               queue->alloc = alloc_nr(queue->alloc);
+               queue->queue = xrealloc(queue->queue,
+                                       sizeof(dp) * queue->alloc);
+       }
+       queue->queue[queue->nr++] = dp;
+}
+
+struct diff_filepair *diff_queue(struct diff_queue_struct *queue,
+                                struct diff_filespec *one,
+                                struct diff_filespec *two)
+{
+       struct diff_filepair *dp = xmalloc(sizeof(*dp));
+       dp->one = one;
+       dp->two = two;
+       dp->score = 0;
+       dp->status = 0;
+       dp->source_stays = 0;
+       dp->broken_pair = 0;
+       if (queue)
+               diff_q(queue, dp);
+       return dp;
+}
+
+void diff_free_filepair(struct diff_filepair *p)
+{
+       diff_free_filespec_data(p->one);
+       diff_free_filespec_data(p->two);
+       free(p->one);
+       free(p->two);
+       free(p);
+}
+
+/* This is different from find_unique_abbrev() in that
+ * it stuffs the result with dots for alignment.
+ */
+const char *diff_unique_abbrev(const unsigned char *sha1, int len)
+{
+       int abblen;
+       const char *abbrev;
+       if (len == 40)
+               return sha1_to_hex(sha1);
+
+       abbrev = find_unique_abbrev(sha1, len);
+       if (!abbrev)
+               return sha1_to_hex(sha1);
+       abblen = strlen(abbrev);
+       if (abblen < 37) {
+               static char hex[41];
+               if (len < abblen && abblen <= len + 2)
+                       sprintf(hex, "%s%.*s", abbrev, len+3-abblen, "..");
+               else
+                       sprintf(hex, "%s...", abbrev);
+               return hex;
+       }
+       return sha1_to_hex(sha1);
+}
+
+static void diff_flush_raw(struct diff_filepair *p,
+                          int line_termination,
+                          int inter_name_termination,
+                          struct diff_options *options,
+                          int output_format)
+{
+       int two_paths;
+       char status[10];
+       int abbrev = options->abbrev;
+       const char *path_one, *path_two;
+
+       path_one = p->one->path;
+       path_two = p->two->path;
+       if (line_termination) {
+               path_one = quote_one(path_one);
+               path_two = quote_one(path_two);
+       }
+
+       if (p->score)
+               sprintf(status, "%c%03d", p->status,
+                       (int)(0.5 + p->score * 100.0/MAX_SCORE));
+       else {
+               status[0] = p->status;
+               status[1] = 0;
+       }
+       switch (p->status) {
+       case DIFF_STATUS_COPIED:
+       case DIFF_STATUS_RENAMED:
+               two_paths = 1;
+               break;
+       case DIFF_STATUS_ADDED:
+       case DIFF_STATUS_DELETED:
+               two_paths = 0;
+               break;
+       default:
+               two_paths = 0;
+               break;
+       }
+       if (output_format != DIFF_FORMAT_NAME_STATUS) {
+               printf(":%06o %06o %s ",
+                      p->one->mode, p->two->mode,
+                      diff_unique_abbrev(p->one->sha1, abbrev));
+               printf("%s ",
+                      diff_unique_abbrev(p->two->sha1, abbrev));
+       }
+       printf("%s%c%s", status, inter_name_termination, path_one);
+       if (two_paths)
+               printf("%c%s", inter_name_termination, path_two);
+       putchar(line_termination);
+       if (path_one != p->one->path)
+               free((void*)path_one);
+       if (path_two != p->two->path)
+               free((void*)path_two);
+}
+
+static void diff_flush_name(struct diff_filepair *p,
+                           int inter_name_termination,
+                           int line_termination)
+{
+       char *path = p->two->path;
+
+       if (line_termination)
+               path = quote_one(p->two->path);
+       else
+               path = p->two->path;
+       printf("%s%c", path, line_termination);
+       if (p->two->path != path)
+               free(path);
+}
+
+int diff_unmodified_pair(struct diff_filepair *p)
+{
+       /* This function is written stricter than necessary to support
+        * the currently implemented transformers, but the idea is to
+        * let transformers to produce diff_filepairs any way they want,
+        * and filter and clean them up here before producing the output.
+        */
+       struct diff_filespec *one, *two;
+
+       if (DIFF_PAIR_UNMERGED(p))
+               return 0; /* unmerged is interesting */
+
+       one = p->one;
+       two = p->two;
+
+       /* deletion, addition, mode or type change
+        * and rename are all interesting.
+        */
+       if (DIFF_FILE_VALID(one) != DIFF_FILE_VALID(two) ||
+           DIFF_PAIR_MODE_CHANGED(p) ||
+           strcmp(one->path, two->path))
+               return 0;
+
+       /* both are valid and point at the same path.  that is, we are
+        * dealing with a change.
+        */
+       if (one->sha1_valid && two->sha1_valid &&
+           !memcmp(one->sha1, two->sha1, sizeof(one->sha1)))
+               return 1; /* no change */
+       if (!one->sha1_valid && !two->sha1_valid)
+               return 1; /* both look at the same file on the filesystem. */
+       return 0;
+}
+
+static void diff_flush_patch(struct diff_filepair *p, struct diff_options *o)
+{
+       if (diff_unmodified_pair(p))
+               return;
+
+       if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
+           (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
+               return; /* no tree diffs in patch format */
+
+       run_diff(p, o);
+}
+
+static void diff_flush_stat(struct diff_filepair *p, struct diff_options *o,
+                           struct diffstat_t *diffstat)
+{
+       if (diff_unmodified_pair(p))
+               return;
+
+       if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
+           (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
+               return; /* no tree diffs in patch format */
+
+       run_diffstat(p, o, diffstat);
+}
+
+int diff_queue_is_empty(void)
+{
+       struct diff_queue_struct *q = &diff_queued_diff;
+       int i;
+       for (i = 0; i < q->nr; i++)
+               if (!diff_unmodified_pair(q->queue[i]))
+                       return 0;
+       return 1;
+}
+
+#if DIFF_DEBUG
+void diff_debug_filespec(struct diff_filespec *s, int x, const char *one)
+{
+       fprintf(stderr, "queue[%d] %s (%s) %s %06o %s\n",
+               x, one ? one : "",
+               s->path,
+               DIFF_FILE_VALID(s) ? "valid" : "invalid",
+               s->mode,
+               s->sha1_valid ? sha1_to_hex(s->sha1) : "");
+       fprintf(stderr, "queue[%d] %s size %lu flags %d\n",
+               x, one ? one : "",
+               s->size, s->xfrm_flags);
+}
+
+void diff_debug_filepair(const struct diff_filepair *p, int i)
+{
+       diff_debug_filespec(p->one, i, "one");
+       diff_debug_filespec(p->two, i, "two");
+       fprintf(stderr, "score %d, status %c stays %d broken %d\n",
+               p->score, p->status ? p->status : '?',
+               p->source_stays, p->broken_pair);
+}
+
+void diff_debug_queue(const char *msg, struct diff_queue_struct *q)
+{
+       int i;
+       if (msg)
+               fprintf(stderr, "%s\n", msg);
+       fprintf(stderr, "q->nr = %d\n", q->nr);
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               diff_debug_filepair(p, i);
+       }
+}
+#endif
+
+static void diff_resolve_rename_copy(void)
+{
+       int i, j;
+       struct diff_filepair *p, *pp;
+       struct diff_queue_struct *q = &diff_queued_diff;
+
+       diff_debug_queue("resolve-rename-copy", q);
+
+       for (i = 0; i < q->nr; i++) {
+               p = q->queue[i];
+               p->status = 0; /* undecided */
+               if (DIFF_PAIR_UNMERGED(p))
+                       p->status = DIFF_STATUS_UNMERGED;
+               else if (!DIFF_FILE_VALID(p->one))
+                       p->status = DIFF_STATUS_ADDED;
+               else if (!DIFF_FILE_VALID(p->two))
+                       p->status = DIFF_STATUS_DELETED;
+               else if (DIFF_PAIR_TYPE_CHANGED(p))
+                       p->status = DIFF_STATUS_TYPE_CHANGED;
+
+               /* from this point on, we are dealing with a pair
+                * whose both sides are valid and of the same type, i.e.
+                * either in-place edit or rename/copy edit.
+                */
+               else if (DIFF_PAIR_RENAME(p)) {
+                       if (p->source_stays) {
+                               p->status = DIFF_STATUS_COPIED;
+                               continue;
+                       }
+                       /* See if there is some other filepair that
+                        * copies from the same source as us.  If so
+                        * we are a copy.  Otherwise we are either a
+                        * copy if the path stays, or a rename if it
+                        * does not, but we already handled "stays" case.
+                        */
+                       for (j = i + 1; j < q->nr; j++) {
+                               pp = q->queue[j];
+                               if (strcmp(pp->one->path, p->one->path))
+                                       continue; /* not us */
+                               if (!DIFF_PAIR_RENAME(pp))
+                                       continue; /* not a rename/copy */
+                               /* pp is a rename/copy from the same source */
+                               p->status = DIFF_STATUS_COPIED;
+                               break;
+                       }
+                       if (!p->status)
+                               p->status = DIFF_STATUS_RENAMED;
+               }
+               else if (memcmp(p->one->sha1, p->two->sha1, 20) ||
+                        p->one->mode != p->two->mode)
+                       p->status = DIFF_STATUS_MODIFIED;
+               else {
+                       /* This is a "no-change" entry and should not
+                        * happen anymore, but prepare for broken callers.
+                        */
+                       error("feeding unmodified %s to diffcore",
+                             p->one->path);
+                       p->status = DIFF_STATUS_UNKNOWN;
+               }
+       }
+       diff_debug_queue("resolve-rename-copy done", q);
+}
+
+static void flush_one_pair(struct diff_filepair *p,
+                          int diff_output_format,
+                          struct diff_options *options,
+                          struct diffstat_t *diffstat)
+{
+       int inter_name_termination = '\t';
+       int line_termination = options->line_termination;
+       if (!line_termination)
+               inter_name_termination = 0;
+
+       switch (p->status) {
+       case DIFF_STATUS_UNKNOWN:
+               break;
+       case 0:
+               die("internal error in diff-resolve-rename-copy");
+               break;
+       default:
+               switch (diff_output_format) {
+               case DIFF_FORMAT_DIFFSTAT:
+                       diff_flush_stat(p, options, diffstat);
+                       break;
+               case DIFF_FORMAT_PATCH:
+                       diff_flush_patch(p, options);
+                       break;
+               case DIFF_FORMAT_RAW:
+               case DIFF_FORMAT_NAME_STATUS:
+                       diff_flush_raw(p, line_termination,
+                                      inter_name_termination,
+                                      options, diff_output_format);
+                       break;
+               case DIFF_FORMAT_NAME:
+                       diff_flush_name(p,
+                                       inter_name_termination,
+                                       line_termination);
+                       break;
+               case DIFF_FORMAT_NO_OUTPUT:
+                       break;
+               }
+       }
+}
+
+static void show_file_mode_name(const char *newdelete, struct diff_filespec *fs)
+{
+       if (fs->mode)
+               printf(" %s mode %06o %s\n", newdelete, fs->mode, fs->path);
+       else
+               printf(" %s %s\n", newdelete, fs->path);
+}
+
+
+static void show_mode_change(struct diff_filepair *p, int show_name)
+{
+       if (p->one->mode && p->two->mode && p->one->mode != p->two->mode) {
+               if (show_name)
+                       printf(" mode change %06o => %06o %s\n",
+                              p->one->mode, p->two->mode, p->two->path);
+               else
+                       printf(" mode change %06o => %06o\n",
+                              p->one->mode, p->two->mode);
+       }
+}
+
+static void show_rename_copy(const char *renamecopy, struct diff_filepair *p)
+{
+       const char *old, *new;
+
+       /* Find common prefix */
+       old = p->one->path;
+       new = p->two->path;
+       while (1) {
+               const char *slash_old, *slash_new;
+               slash_old = strchr(old, '/');
+               slash_new = strchr(new, '/');
+               if (!slash_old ||
+                   !slash_new ||
+                   slash_old - old != slash_new - new ||
+                   memcmp(old, new, slash_new - new))
+                       break;
+               old = slash_old + 1;
+               new = slash_new + 1;
+       }
+       /* p->one->path thru old is the common prefix, and old and new
+        * through the end of names are renames
+        */
+       if (old != p->one->path)
+               printf(" %s %.*s{%s => %s} (%d%%)\n", renamecopy,
+                      (int)(old - p->one->path), p->one->path,
+                      old, new, (int)(0.5 + p->score * 100.0/MAX_SCORE));
+       else
+               printf(" %s %s => %s (%d%%)\n", renamecopy,
+                      p->one->path, p->two->path,
+                      (int)(0.5 + p->score * 100.0/MAX_SCORE));
+       show_mode_change(p, 0);
+}
+
+static void diff_summary(struct diff_filepair *p)
+{
+       switch(p->status) {
+       case DIFF_STATUS_DELETED:
+               show_file_mode_name("delete", p->one);
+               break;
+       case DIFF_STATUS_ADDED:
+               show_file_mode_name("create", p->two);
+               break;
+       case DIFF_STATUS_COPIED:
+               show_rename_copy("copy", p);
+               break;
+       case DIFF_STATUS_RENAMED:
+               show_rename_copy("rename", p);
+               break;
+       default:
+               if (p->score) {
+                       printf(" rewrite %s (%d%%)\n", p->two->path,
+                               (int)(0.5 + p->score * 100.0/MAX_SCORE));
+                       show_mode_change(p, 0);
+               } else  show_mode_change(p, 1);
+               break;
+       }
+}
+
+void diff_flush(struct diff_options *options)
+{
+       struct diff_queue_struct *q = &diff_queued_diff;
+       int i;
+       int diff_output_format = options->output_format;
+       struct diffstat_t *diffstat = NULL;
+
+       if (diff_output_format == DIFF_FORMAT_DIFFSTAT || options->with_stat) {
+               diffstat = xcalloc(sizeof (struct diffstat_t), 1);
+               diffstat->xm.consume = diffstat_consume;
+       }
+
+       if (options->with_raw) {
+               for (i = 0; i < q->nr; i++) {
+                       struct diff_filepair *p = q->queue[i];
+                       flush_one_pair(p, DIFF_FORMAT_RAW, options, NULL);
+               }
+               putchar(options->line_termination);
+       }
+       if (options->with_stat) {
+               for (i = 0; i < q->nr; i++) {
+                       struct diff_filepair *p = q->queue[i];
+                       flush_one_pair(p, DIFF_FORMAT_DIFFSTAT, options,
+                                      diffstat);
+               }
+               show_stats(diffstat);
+               free(diffstat);
+               diffstat = NULL;
+               putchar(options->line_termination);
+       }
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               flush_one_pair(p, diff_output_format, options, diffstat);
+       }
+
+       if (diffstat) {
+               show_stats(diffstat);
+               free(diffstat);
+       }
+
+       for (i = 0; i < q->nr; i++) {
+               if (options->summary)
+                       diff_summary(q->queue[i]);
+               diff_free_filepair(q->queue[i]);
+       }
+
+       free(q->queue);
+       q->queue = NULL;
+       q->nr = q->alloc = 0;
+}
+
+static void diffcore_apply_filter(const char *filter)
+{
+       int i;
+       struct diff_queue_struct *q = &diff_queued_diff;
+       struct diff_queue_struct outq;
+       outq.queue = NULL;
+       outq.nr = outq.alloc = 0;
+
+       if (!filter)
+               return;
+
+       if (strchr(filter, DIFF_STATUS_FILTER_AON)) {
+               int found;
+               for (i = found = 0; !found && i < q->nr; i++) {
+                       struct diff_filepair *p = q->queue[i];
+                       if (((p->status == DIFF_STATUS_MODIFIED) &&
+                            ((p->score &&
+                              strchr(filter, DIFF_STATUS_FILTER_BROKEN)) ||
+                             (!p->score &&
+                              strchr(filter, DIFF_STATUS_MODIFIED)))) ||
+                           ((p->status != DIFF_STATUS_MODIFIED) &&
+                            strchr(filter, p->status)))
+                               found++;
+               }
+               if (found)
+                       return;
+
+               /* otherwise we will clear the whole queue
+                * by copying the empty outq at the end of this
+                * function, but first clear the current entries
+                * in the queue.
+                */
+               for (i = 0; i < q->nr; i++)
+                       diff_free_filepair(q->queue[i]);
+       }
+       else {
+               /* Only the matching ones */
+               for (i = 0; i < q->nr; i++) {
+                       struct diff_filepair *p = q->queue[i];
+
+                       if (((p->status == DIFF_STATUS_MODIFIED) &&
+                            ((p->score &&
+                              strchr(filter, DIFF_STATUS_FILTER_BROKEN)) ||
+                             (!p->score &&
+                              strchr(filter, DIFF_STATUS_MODIFIED)))) ||
+                           ((p->status != DIFF_STATUS_MODIFIED) &&
+                            strchr(filter, p->status)))
+                               diff_q(&outq, p);
+                       else
+                               diff_free_filepair(p);
+               }
+       }
+       free(q->queue);
+       *q = outq;
+}
+
+void diffcore_std(struct diff_options *options)
+{
+       if (options->break_opt != -1)
+               diffcore_break(options->break_opt);
+       if (options->detect_rename)
+               diffcore_rename(options);
+       if (options->break_opt != -1)
+               diffcore_merge_broken();
+       if (options->pickaxe)
+               diffcore_pickaxe(options->pickaxe, options->pickaxe_opts);
+       if (options->orderfile)
+               diffcore_order(options->orderfile);
+       diff_resolve_rename_copy();
+       diffcore_apply_filter(options->filter);
+}
+
+
+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,
+                   const char *base, const char *path)
+{
+       char concatpath[PATH_MAX];
+       struct diff_filespec *one, *two;
+
+       /* This may look odd, but it is a preparation for
+        * feeding "there are unchanged files which should
+        * not produce diffs, but when you are doing copy
+        * detection you would need them, so here they are"
+        * entries to the diff-core.  They will be prefixed
+        * with something like '=' or '*' (I haven't decided
+        * which but should not make any difference).
+        * Feeding the same new and old to diff_change() 
+        * also has the same effect.
+        * Before the final output happens, they are pruned after
+        * merged into rename/copy pairs as appropriate.
+        */
+       if (options->reverse_diff)
+               addremove = (addremove == '+' ? '-' :
+                            addremove == '-' ? '+' : addremove);
+
+       if (!path) path = "";
+       sprintf(concatpath, "%s%s", base, path);
+       one = alloc_filespec(concatpath);
+       two = alloc_filespec(concatpath);
+
+       if (addremove != '+')
+               fill_filespec(one, sha1, mode);
+       if (addremove != '-')
+               fill_filespec(two, sha1, mode);
+
+       diff_queue(&diff_queued_diff, one, two);
+}
+
+void diff_change(struct diff_options *options,
+                unsigned old_mode, unsigned new_mode,
+                const unsigned char *old_sha1,
+                const unsigned char *new_sha1,
+                const char *base, const char *path) 
+{
+       char concatpath[PATH_MAX];
+       struct diff_filespec *one, *two;
+
+       if (options->reverse_diff) {
+               unsigned tmp;
+               const unsigned char *tmp_c;
+               tmp = old_mode; old_mode = new_mode; new_mode = tmp;
+               tmp_c = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_c;
+       }
+       if (!path) path = "";
+       sprintf(concatpath, "%s%s", base, path);
+       one = alloc_filespec(concatpath);
+       two = alloc_filespec(concatpath);
+       fill_filespec(one, old_sha1, old_mode);
+       fill_filespec(two, new_sha1, new_mode);
+
+       diff_queue(&diff_queued_diff, one, two);
+}
+
+void diff_unmerge(struct diff_options *options,
+                 const char *path)
+{
+       struct diff_filespec *one, *two;
+       one = alloc_filespec(path);
+       two = alloc_filespec(path);
+       diff_queue(&diff_queued_diff, one, two);
+}
diff --git a/diff.h b/diff.h
index 52fff66..3027974 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -28,10 +28,14 @@ struct diff_options {
                 with_raw:1,
                 with_stat:1,
                 tree_in_recursive:1,
-                full_index:1;
+                binary:1,
+                full_index:1,
+                silent_on_remove:1,
+                find_copies_harder:1,
+                summary:1;
+       int context;
        int break_opt;
        int detect_rename;
-       int find_copies_harder;
        int line_termination;
        int output_format;
        int pickaxe_opts;
@@ -74,6 +78,8 @@ struct combine_diff_path {
 extern void show_combined_diff(struct combine_diff_path *elem, int num_parent,
                              int dense, struct rev_info *);
 
+extern void diff_tree_combined(const unsigned char *sha1, const unsigned char parent[][20], int num_parent, int dense, struct rev_info *rev);
+
 extern void diff_tree_combined_merge(const unsigned char *sha1, int, struct rev_info *);
 
 extern void diff_addremove(struct diff_options *,
@@ -168,4 +174,8 @@ extern void diff_flush(struct diff_options*);
 
 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 run_diff_index(struct rev_info *revs, int cached);
+
 #endif /* DIFF_H */
index 6df6478..2e79eab 100644 (file)
@@ -13,7 +13,8 @@ char git_default_email[MAX_GITNAME];
 char git_default_name[MAX_GITNAME];
 int trust_executable_bit = 1;
 int assume_unchanged = 0;
-int only_use_symrefs = 0;
+int prefer_symlink_refs = 0;
+int log_all_ref_updates = 0;
 int warn_ambiguous_refs = 1;
 int repository_format_version = 0;
 char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8";
diff --git a/fetch.c b/fetch.c
index 73bde07..fd57684 100644 (file)
--- a/fetch.c
+++ b/fetch.c
@@ -8,6 +8,7 @@
 #include "refs.h"
 
 const char *write_ref = NULL;
+const char *write_ref_log_details = NULL;
 
 const unsigned char *current_ref = NULL;
 
@@ -204,35 +205,49 @@ static int mark_complete(const char *path, const unsigned char *sha1)
 
 int pull(char *target)
 {
+       struct ref_lock *lock;
        unsigned char sha1[20];
-       int fd = -1;
+       char *msg;
+       int ret;
 
        save_commit_buffer = 0;
        track_object_refs = 0;
-       if (write_ref && current_ref) {
-               fd = lock_ref_sha1(write_ref, current_ref);
-               if (fd < 0)
+       if (write_ref) {
+               lock = lock_ref_sha1(write_ref, current_ref, 1);
+               if (!lock) {
+                       error("Can't lock ref %s", write_ref);
                        return -1;
+               }
        }
 
        if (!get_recover) {
                for_each_ref(mark_complete);
        }
 
-       if (interpret_target(target, sha1))
-               return error("Could not interpret %s as something to pull",
-                            target);
-       if (process(lookup_unknown_object(sha1)))
+       if (interpret_target(target, sha1)) {
+               error("Could not interpret %s as something to pull", target);
+               unlock_ref(lock);
+               return -1;
+       }
+       if (process(lookup_unknown_object(sha1))) {
+               unlock_ref(lock);
                return -1;
-       if (loop())
+       }
+       if (loop()) {
+               unlock_ref(lock);
                return -1;
-       
+       }
+
        if (write_ref) {
-               if (current_ref) {
-                       write_ref_sha1(write_ref, fd, sha1);
-               } else {
-                       write_ref_sha1_unlocked(write_ref, sha1);
-               }
+               if (write_ref_log_details) {
+                       msg = xmalloc(strlen(write_ref_log_details) + 12);
+                       sprintf(msg, "fetch from %s", write_ref_log_details);
+               } else
+                       msg = NULL;
+               ret = write_ref_sha1(lock, sha1, msg ? msg : "fetch (unknown)");
+               if (msg)
+                       free(msg);
+               return ret;
        }
        return 0;
 }
diff --git a/fetch.h b/fetch.h
index 9837a3d..0011548 100644 (file)
--- a/fetch.h
+++ b/fetch.h
@@ -25,6 +25,9 @@ extern int fetch_ref(char *ref, unsigned char *sha1);
 /* If set, the ref filename to write the target value to. */
 extern const char *write_ref;
 
+/* If set additional text will appear in the ref log. */
+extern const char *write_ref_log_details;
+
 /* If set, the hash that the current value of write_ref must be. */
 extern const unsigned char *current_ref;
 
index eab4aa8..25c95c2 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -14,6 +14,30 @@ stop_here () {
     exit 1
 }
 
+stop_here_user_resolve () {
+    if [ -n "$resolvemsg" ]; then
+           echo "$resolvemsg"
+           stop_here $1
+    fi
+    cmdline=$(basename $0)
+    if test '' != "$interactive"
+    then
+        cmdline="$cmdline -i"
+    fi
+    if test '' != "$threeway"
+    then
+        cmdline="$cmdline -3"
+    fi
+    if test '.dotest' != "$dotest"
+    then
+        cmdline="$cmdline -d=$dotest"
+    fi
+    echo "When you have resolved this problem run \"$cmdline --resolved\"."
+    echo "If you would prefer to skip this patch, instead run \"$cmdline --skip\"."
+
+    stop_here $1
+}
+
 go_next () {
        rm -f "$dotest/$msgnum" "$dotest/msg" "$dotest/msg-clean" \
                "$dotest/patch" "$dotest/info"
@@ -101,7 +125,7 @@ fall_back_3way () {
 }
 
 prec=4
-dotest=.dotest sign= utf8= keep= skip= interactive= resolved= binary= ws=
+dotest=.dotest sign= utf8= keep= skip= interactive= resolved= binary= ws= resolvemsg=
 
 while case "$#" in 0) break;; esac
 do
@@ -137,6 +161,9 @@ do
        --whitespace=*)
        ws=$1; shift ;;
 
+       --resolvemsg=*)
+       resolvemsg=$(echo "$1" | sed -e "s/^--resolvemsg=//"); shift ;;
+
        --)
        shift; break ;;
        -*)
@@ -165,7 +192,7 @@ then
 else
        # Make sure we are not given --skip nor --resolved
        test ",$skip,$resolved," = ,,, ||
-               die "we are not resuming."
+               die "Resolve operation not in progress, we are not resuming."
 
        # Start afresh.
        mkdir -p "$dotest" || exit
@@ -374,7 +401,14 @@ do
                if test '' = "$changed"
                then
                        echo "No changes - did you forget update-index?"
-                       stop_here $this
+                       stop_here_user_resolve $this
+               fi
+               unmerged=$(git-ls-files -u)
+               if test -n "$unmerged"
+               then
+                       echo "You still have unmerged paths in your index"
+                       echo "did you forget update-index?"
+                       stop_here_user_resolve $this
                fi
                apply_status=0
                ;;
@@ -400,7 +434,7 @@ do
        if test $apply_status != 0
        then
                echo Patch failed at $msgnum.
-               stop_here $this
+               stop_here_user_resolve $this
        fi
 
        if test -x "$GIT_DIR"/hooks/pre-applypatch
@@ -413,7 +447,7 @@ do
        parent=$(git-rev-parse --verify HEAD) &&
        commit=$(git-commit-tree $tree -p $parent <"$dotest/final-commit") &&
        echo Committed: $commit &&
-       git-update-ref HEAD $commit $parent ||
+       git-update-ref -m "am: $SUBJECT" HEAD $commit $parent ||
        stop_here $this
 
        if test -x "$GIT_DIR"/hooks/post-applypatch
index 9df72a1..a6a7a48 100755 (executable)
@@ -10,9 +10,10 @@ use warnings;
 use strict;
 use Getopt::Long;
 use POSIX qw(strftime gmtime);
+use File::Basename qw(basename dirname);
 
 sub usage() {
-       print STDERR 'Usage: ${\basename $0} [-s] [-S revs-file] file [ revision ]
+       print STDERR "Usage: ${\basename $0} [-s] [-S revs-file] file [ revision ]
        -l, --long
                        Show long rev (Defaults off)
        -t, --time
@@ -23,7 +24,7 @@ sub usage() {
                        Use revs from revs-file instead of calling git-rev-list
        -h, --help
                        This message.
-';
+";
 
        exit(1);
 }
@@ -35,7 +36,7 @@ my $rc = GetOptions(  "long|l" => \$longrev,
                        "help|h" => \$help,
                        "rename|r" => \$rename,
                        "rev-file|S=s" => \$rev_file);
-if (!$rc or $help) {
+if (!$rc or $help or !@ARGV) {
        usage();
 }
 
@@ -208,6 +209,9 @@ sub find_parent_renames {
        while (my $change = <$patch>) {
                chomp $change;
                my $filename = <$patch>;
+               if (!defined $filename) {
+                       next;
+               }
                chomp $filename;
 
                if ($change =~ m/^[AMD]$/ ) {
index 12cab1e..e4b0947 100755 (executable)
@@ -204,7 +204,7 @@ echo Wrote tree $tree
 parent=$(git-rev-parse --verify HEAD) &&
 commit=$(git-commit-tree $tree -p $parent <"$final") || exit 1
 echo Committed: $commit
-git-update-ref HEAD $commit $parent || exit
+git-update-ref -m "applypatch: $SUBJECT" HEAD $commit $parent || exit
 
 if test -x "$GIT_DIR"/hooks/post-applypatch
 then
index 663a3a3..e0501ec 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-USAGE='[(-d | -D) <branchname>] | [[-f] <branchname> [<start-point>]]'
+USAGE='[-l] [(-d | -D) <branchname>] | [[-f] <branchname> [<start-point>]] | -r'
 LONG_USAGE='If no arguments, show available branches and mark current branch with a star.
 If one argument, create a new branch <branchname> based off of current HEAD.
 If two arguments, create a new branch <branchname> based off of <start-point>.'
@@ -42,6 +42,7 @@ If you are sure you want to delete it, run 'git branch -D $branch_name'."
            esac
            ;;
        esac
+       rm -f "$GIT_DIR/logs/refs/heads/$branch_name"
        rm -f "$GIT_DIR/refs/heads/$branch_name"
        echo "Deleted branch $branch_name."
     done
@@ -55,6 +56,7 @@ ls_remote_branches () {
 }
 
 force=
+create_log=
 while case "$#,$1" in 0,*) break ;; *,-*) ;; *) break ;; esac
 do
        case "$1" in
@@ -69,6 +71,9 @@ do
        -f)
                force="$1"
                ;;
+       -l)
+               create_log="yes"
+               ;;
        --)
                shift
                break
@@ -82,8 +87,7 @@ done
 
 case "$#" in
 0)
-       git-rev-parse --symbolic --all |
-       sed -ne 's|^refs/heads/||p' |
+       git-rev-parse --symbolic --branches |
        sort |
        while read ref
        do
@@ -118,4 +122,9 @@ then
                die "cannot force-update the current branch."
        fi
 fi
-git update-ref "refs/heads/$branchname" $rev
+if test "$create_log" = 'yes'
+then
+       mkdir -p $(dirname "$GIT_DIR/logs/refs/heads/$branchname")
+       touch "$GIT_DIR/logs/refs/heads/$branchname"
+fi
+git update-ref -m "branch: Created from $head" "refs/heads/$branchname" $rev
index 463ed2e..360aabf 100755 (executable)
@@ -5,10 +5,13 @@ SUBDIRECTORY_OK=Sometimes
 . git-sh-setup
 
 old=$(git-rev-parse HEAD)
+old_name=HEAD
 new=
+new_name=
 force=
 branch=
 newbranch=
+newbranch_log=
 merge=
 while [ "$#" != "0" ]; do
     arg="$1"
@@ -24,6 +27,9 @@ while [ "$#" != "0" ]; do
                git-check-ref-format "heads/$newbranch" ||
                        die "git checkout: we do not like '$newbranch' as a branch name."
                ;;
+       "-l")
+               newbranch_log=1
+               ;;
        "-f")
                force=1
                ;;
@@ -44,6 +50,7 @@ while [ "$#" != "0" ]; do
                                exit 1
                        fi
                        new="$rev"
+                       new_name="$arg^0"
                        if [ -f "$GIT_DIR/refs/heads/$arg" ]; then
                                branch="$arg"
                        fi
@@ -51,9 +58,11 @@ while [ "$#" != "0" ]; do
                then
                        # checking out selected paths from a tree-ish.
                        new="$rev"
+                       new_name="$arg^{tree}"
                        branch=
                else
                        new=
+                       new_name=
                        branch=
                        set x "$arg" "$@"
                        shift
@@ -114,7 +123,7 @@ then
        cd "$cdup"
 fi
 
-[ -z "$new" ] && new=$old
+[ -z "$new" ] && new=$old && new_name="$old_name"
 
 # If we don't have an old branch that we're switching to,
 # and we don't have a new branch name for the target we
@@ -144,7 +153,7 @@ else
        work=`git write-tree` &&
        git read-tree --reset $new &&
        git checkout-index -f -u -q -a &&
-       git read-tree -m -u $old $new $work || exit
+       git read-tree -m -u --aggressive $old $new $work || exit
 
        if result=`git write-tree 2>/dev/null`
        then
@@ -187,9 +196,11 @@ fi
 #
 if [ "$?" -eq 0 ]; then
        if [ "$newbranch" ]; then
-               leading=`expr "refs/heads/$newbranch" : '\(.*\)/'` &&
-               mkdir -p "$GIT_DIR/$leading" &&
-               echo $new >"$GIT_DIR/refs/heads/$newbranch" || exit
+               if [ "$newbranch_log" ]; then
+                       mkdir -p $(dirname "$GIT_DIR/logs/refs/heads/$branchname")
+                       touch "$GIT_DIR/logs/refs/heads/$branchname"
+               fi
+               git-update-ref -m "checkout: Created from $new_name" "refs/heads/$newbranch" $new || exit
                branch="$newbranch"
        fi
        [ "$branch" ] &&
index b200868..bb56264 100755 (executable)
@@ -3,13 +3,15 @@
 # Copyright (c) 2005-2006 Pavel Roskin
 #
 
-USAGE="[-d] [-n] [-q] [-x | -X]"
+USAGE="[-d] [-n] [-q] [-x | -X] [--] <paths>..."
 LONG_USAGE='Clean untracked files from the working directory
        -d      remove directories as well
        -n      don'\''t remove anything, just show what would be done
        -q      be quiet, only report errors
        -x      remove ignored files as well
-       -X      remove only ignored files as well'
+       -X      remove only ignored files
+When optional <paths>... arguments are given, the paths
+affected are further limited to those that match them.'
 SUBDIRECTORY_OK=Yes
 . git-sh-setup
 
@@ -44,8 +46,15 @@ do
        -X)
                ignoredonly=1
                ;;
-       *)
+       --)
+               shift
+               break
+               ;;
+       -*)
                usage
+               ;;
+       *)
+               break
        esac
        shift
 done
@@ -64,7 +73,7 @@ if [ -z "$ignored" ]; then
        fi
 fi
 
-git-ls-files --others --directory $excl ${excl_info:+"$excl_info"} |
+git-ls-files --others --directory $excl ${excl_info:+"$excl_info"} -- "$@" |
 while read -r file; do
        if [ -d "$file" -a ! -L "$file" ]; then
                if [ -z "$cleandir" ]; then
index 0805168..227245c 100755 (executable)
@@ -261,11 +261,7 @@ yes,yes)
            ;;
        yes)
            mkdir -p "$GIT_DIR/objects/info"
-           {
-               test -f "$repo/objects/info/alternates" &&
-               cat "$repo/objects/info/alternates";
-               echo "$repo/objects"
-           } >"$GIT_DIR/objects/info/alternates"
+           echo "$repo/objects" >> "$GIT_DIR/objects/info/alternates"
            ;;
        esac
        git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD"
index 26cd7ca..a4b0a90 100755 (executable)
@@ -640,6 +640,8 @@ case "$no_edit" in
                exit 1
                ;;
        esac
+       git-var GIT_AUTHOR_IDENT > /dev/null  || die
+       git-var GIT_COMMITTER_IDENT > /dev/null  || die
        ${VISUAL:-${EDITOR:-vi}} "$GIT_DIR/COMMIT_EDITMSG"
        ;;
 esac
@@ -674,7 +676,8 @@ then
                rm -f "$TMP_INDEX"
        fi &&
        commit=$(cat "$GIT_DIR"/COMMIT_MSG | git-commit-tree $tree $PARENTS) &&
-       git-update-ref HEAD $commit $current &&
+       rlogm=$(sed -e 1q "$GIT_DIR"/COMMIT_MSG) &&
+       git-update-ref -m "commit: $rlogm" HEAD $commit $current &&
        rm -f -- "$GIT_DIR/MERGE_HEAD" &&
        if test -f "$NEXT_INDEX"
        then
diff --git a/git-count-objects.sh b/git-count-objects.sh
deleted file mode 100755 (executable)
index 40c58ef..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005 Junio C Hamano
-#
-
-GIT_DIR=`git-rev-parse --git-dir` || exit $?
-
-dc </dev/null 2>/dev/null || {
-       # This is not a real DC at all -- it just knows how
-       # this script feeds DC and does the computation itself.
-       dc () {
-               while read a b
-               do
-                       case $a,$b in
-                       0,)     acc=0 ;;
-                       *,+)    acc=$(($acc + $a)) ;;
-                       p,)     echo "$acc" ;;
-                       esac
-               done
-       }
-}
-
-echo $(find "$GIT_DIR/objects"/?? -type f -print 2>/dev/null | wc -l) objects, \
-$({
-    echo 0
-    # "no-such" is to help Darwin folks by not using xargs -r.
-    find "$GIT_DIR/objects"/?? -type f -print 2>/dev/null |
-    xargs du -k "$GIT_DIR/objects/no-such" 2>/dev/null |
-    sed -e 's/[        ].*/ +/'
-    echo p
-} | dc) kilobytes
index 7b3a3d3..f994443 100755 (executable)
@@ -10,9 +10,9 @@ unless ($ENV{GIT_DIR} && -r $ENV{GIT_DIR}){
     die "GIT_DIR is not defined or is unreadable";
 }
 
-our ($opt_h, $opt_p, $opt_v, $opt_c );
+our ($opt_h, $opt_p, $opt_v, $opt_c, $opt_f, $opt_m );
 
-getopts('hpvc');
+getopts('hpvcfm:');
 
 $opt_h && usage();
 
@@ -77,12 +77,16 @@ if ($parent) {
 $opt_v && print "Applying to CVS commit $commit from parent $parent\n";
 
 # grab the commit message
-`git-cat-file commit $commit | sed -e '1,/^\$/d' > .msg`;
+open(MSG, ">.msg") or die "Cannot open .msg for writing";
+print MSG $opt_m;
+close MSG;
+
+`git-cat-file commit $commit | sed -e '1,/^\$/d' >> .msg`;
 $? && die "Error extracting the commit message";
 
 my (@afiles, @dfiles, @mfiles);
 my @files = safe_pipe_capture('git-diff-tree', '-r', $parent, $commit);
-print @files;
+#print @files;
 $? && die "Error in git-diff-tree";
 foreach my $f (@files) {
     chomp $f;
@@ -109,7 +113,7 @@ foreach my $f (@afiles) {
     if (@status > 1) { warn 'Strange! cvs status returned more than one line?'};
     unless ($status[0] =~ m/Status: Unknown$/) {
        $dirty = 1;
-       warn "File $f is already known in your CVS checkout!\n";
+       warn "File $f is already known in your CVS checkout -- perhaps it has been added by another user. Or this may indicate that it exists on a different branch. If this is the case, use -f to force the merge.\n";
     }
 }
 foreach my $f (@mfiles, @dfiles) {
@@ -122,7 +126,11 @@ foreach my $f (@mfiles, @dfiles) {
     }
 }
 if ($dirty) {
-    die "Exiting: your CVS tree is not clean for this merge.";
+    if ($opt_f) {      warn "The tree is not clean -- forced merge\n";
+       $dirty = 0;
+    } else {
+       die "Exiting: your CVS tree is not clean for this merge.";
+    }
 }
 
 ###
@@ -215,7 +223,7 @@ if ($opt_c) {
 }
 sub usage {
        print STDERR <<END;
-Usage: GIT_DIR=/path/to/.git ${\basename $0} [-h] [-p] [-v] [-c] [ parent ] commit
+Usage: GIT_DIR=/path/to/.git ${\basename $0} [-h] [-p] [-v] [-c] [-f] [-m msgprefix] [ parent ] commit
 END
        exit(1);
 }
index 7d3f78e..5ccca4f 100755 (executable)
@@ -88,7 +88,7 @@ my $TEMP_DIR = tempdir( CLEANUP => 1 );
 $log->debug("Temporary directory is '$TEMP_DIR'");
 
 # if we are called with a pserver argument,
-# deal with the authentication cat before entereing the
+# deal with the authentication cat before entering the
 # main loop
 if (@ARGV && $ARGV[0] eq 'pserver') {
     my $line = <STDIN>; chomp $line;
@@ -117,7 +117,7 @@ while (<STDIN>)
 {
     chomp;
 
-    # Check to see if we've seen this method, and call appropiate function.
+    # Check to see if we've seen this method, and call appropriate function.
     if ( /^([\w-]+)(?:\s+(.*))?$/ and defined($methods->{$1}) )
     {
         # use the $methods hash to call the appropriate sub for this command
@@ -171,11 +171,11 @@ sub req_Root
        return 0;
     }
 
-    my @gitvars = `git-var -l`;
+    my @gitvars = `git-repo-config -l`;
     if ($?) {
-       print "E problems executing git-var on the server -- this is not a git repository or the PATH is not set correcly.\n";
+       print "E problems executing git-repo-config on the server -- this is not a git repository or the PATH is not set correctly.\n";
         print "E \n";
-        print "error 1 - problem executing git-var\n";
+        print "error 1 - problem executing git-repo-config\n";
        return 0;
     }
     foreach my $line ( @gitvars )
@@ -214,8 +214,7 @@ sub req_Globaloption
 {
     my ( $cmd, $data ) = @_;
     $log->debug("req_Globaloption : $data");
-
-    # TODO : is this data useful ???
+    $state->{globaloptions}{$data} = 1;
 }
 
 # Valid-responses request-list \n
@@ -224,7 +223,7 @@ sub req_Globaloption
 sub req_Validresponses
 {
     my ( $cmd, $data ) = @_;
-    $log->debug("req_Validrepsonses : $data");
+    $log->debug("req_Validresponses : $data");
 
     # TODO : re-enable this, currently it's not particularly useful
     #$state->{validresponses} = [ split /\s+/, $data ];
@@ -267,12 +266,32 @@ sub req_Directory
 
     $state->{localdir} = $data;
     $state->{repository} = $repository;
-    $state->{directory} = $repository;
-    $state->{directory} =~ s/^$state->{CVSROOT}\///;
-    $state->{module} = $1 if ($state->{directory} =~ s/^(.*?)(\/|$)//);
+    $state->{path} = $repository;
+    $state->{path} =~ s/^$state->{CVSROOT}\///;
+    $state->{module} = $1 if ($state->{path} =~ s/^(.*?)(\/|$)//);
+    $state->{path} .= "/" if ( $state->{path} =~ /\S/ );
+
+    $state->{directory} = $state->{localdir};
+    $state->{directory} = "" if ( $state->{directory} eq "." );
     $state->{directory} .= "/" if ( $state->{directory} =~ /\S/ );
 
-    $log->debug("req_Directory : localdir=$data repository=$repository directory=$state->{directory} module=$state->{module}");
+    if ( not defined($state->{prependdir}) and $state->{localdir} eq "." and $state->{path} =~ /\S/ )
+    {
+        $log->info("Setting prepend to '$state->{path}'");
+        $state->{prependdir} = $state->{path};
+        foreach my $entry ( keys %{$state->{entries}} )
+        {
+            $state->{entries}{$state->{prependdir} . $entry} = $state->{entries}{$entry};
+            delete $state->{entries}{$entry};
+        }
+    }
+
+    if ( defined ( $state->{prependdir} ) )
+    {
+        $log->debug("Prepending '$state->{prependdir}' to state|directory");
+        $state->{directory} = $state->{prependdir} . $state->{directory}
+    }
+    $log->debug("req_Directory : localdir=$data repository=$repository path=$state->{path} directory=$state->{directory} module=$state->{module}");
 }
 
 # Entry entry-line \n
@@ -290,7 +309,7 @@ sub req_Entry
 {
     my ( $cmd, $data ) = @_;
 
-    $log->debug("req_Entry : $data");
+    #$log->debug("req_Entry : $data");
 
     my @data = split(/\//, $data);
 
@@ -300,6 +319,22 @@ sub req_Entry
         options     => $data[4],
         tag_or_date => $data[5],
     };
+
+    $log->info("Received entry line '$data' => '" . $state->{directory} . $data[1] . "'");
+}
+
+# Questionable filename \n
+#     Response expected: no. Additional data: no. Tell the server to check
+#     whether filename should be ignored, and if not, next time the server
+#     sends responses, send (in a M response) `?' followed by the directory and
+#     filename. filename must not contain `/'; it needs to be a file in the
+#     directory named by the most recent Directory request.
+sub req_Questionable
+{
+    my ( $cmd, $data ) = @_;
+
+    $log->debug("req_Questionable : $data");
+    $state->{entries}{$state->{directory}.$data}{questionable} = 1;
 }
 
 # add \n
@@ -332,8 +367,7 @@ sub req_add
             next;
         }
 
-
-        my ( $filepart, $dirpart ) = filenamesplit($filename);
+        my ( $filepart, $dirpart ) = filenamesplit($filename, 1);
 
         print "E cvs add: scheduling file `$filename' for addition\n";
 
@@ -414,7 +448,7 @@ sub req_remove
         }
 
 
-        my ( $filepart, $dirpart ) = filenamesplit($filename);
+        my ( $filepart, $dirpart ) = filenamesplit($filename, 1);
 
         print "E cvs remove: scheduling `$filename' for removal\n";
 
@@ -502,22 +536,6 @@ sub req_Unchanged
     #$log->debug("req_Unchanged : $data");
 }
 
-# Questionable filename \n
-#     Response expected: no. Additional data: no.
-#     Tell the server to check whether filename should be ignored,
-#     and if not, next time the server sends responses, send (in
-#     a M response) `?' followed by the directory and filename.
-#     filename must not contain `/'; it needs to be a file in the
-#     directory named by the most recent Directory request.
-sub req_Questionable
-{
-    my ( $cmd, $data ) = @_;
-
-    $state->{entries}{$state->{directory}.$data}{questionable} = 1;
-
-    #$log->debug("req_Questionable : $data");
-}
-
 # Argument text \n
 #     Response expected: no. Save argument for use in a subsequent command.
 #     Arguments accumulate until an argument-using command is given, at which
@@ -733,7 +751,7 @@ sub req_update
     argsplit("update");
 
     #
-    # It may just be a client exploring the available heads/modukles
+    # It may just be a client exploring the available heads/modules
     # in that case, list them as top level directories and leave it
     # at that. Eclipse uses this technique to offer you a list of
     # projects (heads in this case) to checkout.
@@ -757,8 +775,7 @@ sub req_update
 
     $updater->update();
 
-    # if no files were specified, we need to work out what files we should be providing status on ...
-    argsfromdir($updater) if ( scalar ( @{$state->{args}} ) == 0 );
+    argsfromdir($updater);
 
     #$log->debug("update state : " . Dumper($state));
 
@@ -767,6 +784,8 @@ sub req_update
     {
         $filename = filecleanup($filename);
 
+        $log->debug("Processing file $filename");
+
         # if we have a -C we should pretend we never saw modified stuff
         if ( exists ( $state->{opt}{C} ) )
         {
@@ -821,13 +840,16 @@ sub req_update
 
         if ( $meta->{filehash} eq "deleted" )
         {
-            my ( $filepart, $dirpart ) = filenamesplit($filename);
+            my ( $filepart, $dirpart ) = filenamesplit($filename,1);
 
             $log->info("Removing '$filename' from working copy (no longer in the repo)");
 
             print "E cvs update: `$filename' is no longer in the repository\n";
-            print "Removed $dirpart\n";
-            print "$filepart\n";
+            # Don't want to actually _DO_ the update if -n specified
+            unless ( $state->{globaloptions}{-n} ) {
+               print "Removed $dirpart\n";
+               print "$filepart\n";
+           }
         }
         elsif ( not defined ( $state->{entries}{$filename}{modified_hash} )
                or $state->{entries}{$filename}{modified_hash} eq $oldmeta->{filehash} )
@@ -840,34 +862,42 @@ sub req_update
             print "MT newline\n";
             print "MT -updated\n";
 
-            my ( $filepart, $dirpart ) = filenamesplit($filename);
-            $dirpart =~ s/^$state->{directory}//;
-
-            if ( defined ( $wrev ) )
-            {
-                # instruct client we're sending a file to put in this path as a replacement
-                print "Update-existing $dirpart\n";
-                $log->debug("Updating existing file 'Update-existing $dirpart'");
-            } else {
-                # instruct client we're sending a file to put in this path as a new file
-                print "Created $dirpart\n";
-                $log->debug("Creating new file 'Created $dirpart'");
-            }
-            print $state->{CVSROOT} . "/$state->{module}/$filename\n";
-
-            # this is an "entries" line
-            $log->debug("/$filepart/1.$meta->{revision}///");
-            print "/$filepart/1.$meta->{revision}///\n";
-
-            # permissions
-            $log->debug("SEND : u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}");
-            print "u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}\n";
-
-            # transmit file
-            transmitfile($meta->{filehash});
+            my ( $filepart, $dirpart ) = filenamesplit($filename,1);
+
+           # Don't want to actually _DO_ the update if -n specified
+           unless ( $state->{globaloptions}{-n} )
+           {
+               if ( defined ( $wrev ) )
+               {
+                   # instruct client we're sending a file to put in this path as a replacement
+                   print "Update-existing $dirpart\n";
+                   $log->debug("Updating existing file 'Update-existing $dirpart'");
+               } else {
+                   # instruct client we're sending a file to put in this path as a new file
+                   print "Clear-static-directory $dirpart\n";
+                   print $state->{CVSROOT} . "/$state->{module}/$dirpart\n";
+                   print "Clear-sticky $dirpart\n";
+                   print $state->{CVSROOT} . "/$state->{module}/$dirpart\n";
+
+                   $log->debug("Creating new file 'Created $dirpart'");
+                   print "Created $dirpart\n";
+               }
+               print $state->{CVSROOT} . "/$state->{module}/$filename\n";
+
+               # this is an "entries" line
+               $log->debug("/$filepart/1.$meta->{revision}///");
+               print "/$filepart/1.$meta->{revision}///\n";
+
+               # permissions
+               $log->debug("SEND : u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}");
+               print "u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}\n";
+
+               # transmit file
+               transmitfile($meta->{filehash});
+           }
         } else {
             $log->info("Updating '$filename'");
-            my ( $filepart, $dirpart ) = filenamesplit($meta->{name});
+            my ( $filepart, $dirpart ) = filenamesplit($meta->{name},1);
 
             my $dir = tempdir( DIR => $TEMP_DIR, CLEANUP => 1 ) . "/";
 
@@ -892,19 +922,29 @@ sub req_update
                 $log->info("Merged successfully");
                 print "M M $filename\n";
                 $log->debug("Update-existing $dirpart");
-                print "Update-existing $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";
+
+                # Don't want to actually _DO_ the update if -n specified
+                unless ( $state->{globaloptions}{-n} )
+                {
+                    print "Update-existing $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";
+                }
             }
             elsif ( $return == 1 )
             {
                 $log->info("Merged with conflicts");
                 print "M C $filename\n";
-                print "Update-existing $dirpart\n";
-                print $state->{CVSROOT} . "/$state->{module}/$filename\n";
-                print "/$filepart/1.$meta->{revision}/+//\n";
+
+                # Don't want to actually _DO_ the update if -n specified
+                unless ( $state->{globaloptions}{-n} )
+                {
+                    print "Update-existing $dirpart\n";
+                    print $state->{CVSROOT} . "/$state->{module}/$filename\n";
+                    print "/$filepart/1.$meta->{revision}/+//\n";
+                }
             }
             else
             {
@@ -912,17 +952,21 @@ sub req_update
                 next;
             }
 
-            # permissions
-            $log->debug("SEND : u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}");
-            print "u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}\n";
-
-            # transmit file, format is single integer on a line by itself (file
-            # size) followed by the file contents
-            # TODO : we should copy files in blocks
-            my $data = `cat $file_local`;
-            $log->debug("File size : " . length($data));
-            print length($data) . "\n";
-            print $data;
+            # Don't want to actually _DO_ the update if -n specified
+            unless ( $state->{globaloptions}{-n} )
+            {
+                # permissions
+                $log->debug("SEND : u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}");
+                print "u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}\n";
+
+                # transmit file, format is single integer on a line by itself (file
+                # size) followed by the file contents
+                # TODO : we should copy files in blocks
+                my $data = `cat $file_local`;
+                $log->debug("File size : " . length($data));
+                print length($data) . "\n";
+                print $data;
+            }
 
             chdir "/";
         }
@@ -950,6 +994,7 @@ sub req_ci
 
     if ( -e $state->{CVSROOT} . "/index" )
     {
+        $log->warn("file 'index' already exists in the git repository");
         print "error 1 Index already exists in git repo\n";
         exit;
     }
@@ -957,6 +1002,7 @@ sub req_ci
     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;
     }
@@ -988,6 +1034,7 @@ sub req_ci
     # foreach file specified on the commandline ...
     foreach my $filename ( @{$state->{args}} )
     {
+        my $committedfile = $filename;
         $filename = filecleanup($filename);
 
         next unless ( exists $state->{entries}{$filename}{modified_filename} or not $state->{entries}{$filename}{unchanged} );
@@ -1022,7 +1069,7 @@ sub req_ci
             exit;
         }
 
-        push @committedfiles, $filename;
+        push @committedfiles, $committedfile;
         $log->info("Committing $filename");
 
         system("mkdir","-p",$dirpart) unless ( -d $dirpart );
@@ -1105,7 +1152,7 @@ sub req_ci
 
         my $meta = $updater->getmeta($filename);
 
-        my ( $filepart, $dirpart ) = filenamesplit($filename);
+        my ( $filepart, $dirpart ) = filenamesplit($filename, 1);
 
         $log->debug("Checked-in $dirpart : $filename");
 
@@ -1141,7 +1188,7 @@ sub req_status
     $updater->update();
 
     # if no files were specified, we need to work out what files we should be providing status on ...
-    argsfromdir($updater) if ( scalar ( @{$state->{args}} ) == 0 );
+    argsfromdir($updater);
 
     # foreach file specified on the commandline ...
     foreach my $filename ( @{$state->{args}} )
@@ -1242,7 +1289,7 @@ sub req_diff
     $updater->update();
 
     # if no files were specified, we need to work out what files we should be providing status on ...
-    argsfromdir($updater) if ( scalar ( @{$state->{args}} ) == 0 );
+    argsfromdir($updater);
 
     # foreach file specified on the commandline ...
     foreach my $filename ( @{$state->{args}} )
@@ -1384,7 +1431,7 @@ sub req_log
     $updater->update();
 
     # if no files were specified, we need to work out what files we should be providing status on ...
-    argsfromdir($updater) if ( scalar ( @{$state->{args}} ) == 0 );
+    argsfromdir($updater);
 
     # foreach file specified on the commandline ...
     foreach my $filename ( @{$state->{args}} )
@@ -1460,7 +1507,7 @@ sub req_annotate
     $updater->update();
 
     # if no files were specified, we need to work out what files we should be providing annotate on ...
-    argsfromdir($updater) if ( scalar ( @{$state->{args}} ) == 0 );
+    argsfromdir($updater);
 
     # we'll need a temporary checkout dir
     my $tmpdir = tempdir ( DIR => $TEMP_DIR );
@@ -1655,13 +1702,36 @@ sub argsfromdir
 {
     my $updater = shift;
 
-    $state->{args} = [];
+    $state->{args} = [] if ( scalar(@{$state->{args}}) == 1 and $state->{args}[0] eq "." );
+
+    return if ( scalar ( @{$state->{args}} ) > 1 );
 
-    foreach my $file ( @{$updater->gethead} )
+    if ( scalar(@{$state->{args}}) == 1 )
     {
-        next if ( $file->{filehash} eq "deleted" and not defined ( $state->{entries}{$file->{name}} ) );
-        next unless ( $file->{name} =~ s/^$state->{directory}// );
-        push @{$state->{args}}, $file->{name};
+        my $arg = $state->{args}[0];
+        $arg .= $state->{prependdir} if ( defined ( $state->{prependdir} ) );
+
+        $log->info("Only one arg specified, checking for directory expansion on '$arg'");
+
+        foreach my $file ( @{$updater->gethead} )
+        {
+            next if ( $file->{filehash} eq "deleted" and not defined ( $state->{entries}{$file->{name}} ) );
+            next unless ( $file->{name} =~ /^$arg\// or $file->{name} eq $arg  );
+            push @{$state->{args}}, $file->{name};
+        }
+
+        shift @{$state->{args}} if ( scalar(@{$state->{args}}) > 1 );
+    } else {
+        $log->info("Only one arg specified, populating file list automatically");
+
+        $state->{args} = [];
+
+        foreach my $file ( @{$updater->gethead} )
+        {
+            next if ( $file->{filehash} eq "deleted" and not defined ( $state->{entries}{$file->{name}} ) );
+            next unless ( $file->{name} =~ s/^$state->{prependdir}// );
+            push @{$state->{args}}, $file->{name};
+        }
     }
 }
 
@@ -1731,16 +1801,22 @@ sub transmitfile
 }
 
 # This method takes a file name, and returns ( $dirpart, $filepart ) which
-# refers to the directory porition and the file portion of the filename
+# refers to the directory portion and the file portion of the filename
 # respectively
 sub filenamesplit
 {
     my $filename = shift;
+    my $fixforlocaldir = shift;
 
     my ( $filepart, $dirpart ) = ( $filename, "." );
     ( $filepart, $dirpart ) = ( $2, $1 ) if ( $filename =~ /(.*)\/(.*)/ );
     $dirpart .= "/";
 
+    if ( $fixforlocaldir )
+    {
+        $dirpart =~ s/^$state->{prependdir}//;
+    }
+
     return ( $filepart, $dirpart );
 }
 
@@ -1756,8 +1832,7 @@ sub filecleanup
     }
 
     $filename =~ s/^\.\///g;
-    $filename = $state->{directory} . $filename;
-
+    $filename = $state->{prependdir} . $filename;
     return $filename;
 }
 
@@ -1790,7 +1865,7 @@ Log::Log4perl
 =head2 new
 
 Creates a new log object, optionally you can specify a filename here to
-indicate the file to log to. If no log file is specified, you can specifiy one
+indicate the file to log to. If no log file is specified, you can specify one
 later with method setfile, or indicate you no longer want logging with method
 nofile.
 
@@ -2076,14 +2151,15 @@ sub update
     # TODO: log processing is memory bound
     # if we can parse into a 2nd file that is in reverse order
     # we can probably do something really efficient
-    my @git_log_params = ('--parents', '--topo-order');
+    my @git_log_params = ('--pretty', '--parents', '--topo-order');
 
     if (defined $lastcommit) {
         push @git_log_params, "$lastcommit..$self->{module}";
     } else {
         push @git_log_params, $self->{module};
     }
-    open(GITLOG, '-|', 'git-log', @git_log_params) or die "Cannot call git-log: $!";
+    # git-rev-list is the backend / plumbing version of git-log
+    open(GITLOG, '-|', 'git-rev-list', @git_log_params) or die "Cannot call git-rev-list: $!";
 
     my @commits;
 
@@ -2595,7 +2671,7 @@ sub in_array
 
 =head2 safe_pipe_capture
 
-an alterative to `command` that allows input to be passed as an array
+an alternative to `command` that allows input to be passed as an array
 to work around shell problems with weird characters in arguments
 
 =cut
diff --git a/git-diff.sh b/git-diff.sh
deleted file mode 100755 (executable)
index 0fe6770..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005 Linus Torvalds
-# Copyright (c) 2005 Junio C Hamano
-
-USAGE='[ --diff-options ] <ent>{0,2} [<path>...]'
-SUBDIRECTORY_OK='Yes'
-. git-sh-setup
-
-rev=$(git-rev-parse --revs-only --no-flags --sq "$@") || exit
-flags=$(git-rev-parse --no-revs --flags --sq "$@")
-files=$(git-rev-parse --no-revs --no-flags --sq "$@")
-
-# I often say 'git diff --cached -p' and get scolded by git-diff-files, but
-# obviously I mean 'git diff --cached -p HEAD' in that case.
-case "$rev" in
-'')
-       case " $flags " in
-       *" '--cached' "*)
-               rev='HEAD '
-               ;;
-       esac
-esac
-
-# If we have -[123] --ours --theirs --base, don't do --cc by default.
-case " $flags " in
-*" '-"[123]"' "* | *" '--ours' "* | *" '--base' "* | *" '--theirs' "*)
-       cc_or_p=-p ;;
-*)
-       cc_or_p=--cc ;;
-esac
-
-# If we do not have --name-status, --name-only, -r, -c or --stat,
-# default to --cc.
-case " $flags " in
-*" '--name-status' "* | *" '--name-only' "* | *" '-r' "* | *" '-c' "* | \
-*" '--stat' "*)
-       ;;
-*)
-       flags="$flags'$cc_or_p' " ;;
-esac
-
-# If we do not have -B, -C, -r, nor -p, default to -M.
-case " $flags " in
-*" '-"[BCMrp]* | *" '--find-copies-harder' "*)
-       ;; # something like -M50.
-*)
-       flags="$flags'-M' " ;;
-esac
-
-case "$rev" in
-?*' '?*' '?*)
-       usage
-       ;;
-?*' '^?*)
-       begin=$(expr "$rev" : '.*^.\([0-9a-f]*\).*') &&
-       end=$(expr "$rev" : '.\([0-9a-f]*\). .*') || exit
-       cmd="git-diff-tree $flags $begin $end -- $files"
-       ;;
-?*' '?*)
-       cmd="git-diff-tree $flags $rev -- $files"
-       ;;
-?*' ')
-       cmd="git-diff-index $flags $rev -- $files"
-       ;;
-'')
-       cmd="git-diff-files $flags -- $files"
-       ;;
-*)
-       usage
-       ;;
-esac
-
-eval "$cmd"
index 83143f8..280f62e 100755 (executable)
@@ -270,14 +270,22 @@ fetch_main () {
          if [ -n "$GIT_SSL_NO_VERIFY" ]; then
              curl_extra_args="-k"
          fi
-         remote_name_quoted=$(perl -e '
+         max_depth=5
+         depth=0
+         head="ref: $remote_name"
+         while (expr "z$head" : "zref:" && expr $depth \< $max_depth) >/dev/null
+         do
+           remote_name_quoted=$(perl -e '
              my $u = $ARGV[0];
+              $u =~ s/^ref:\s*//;
              $u =~ s{([^-a-zA-Z0-9/.])}{sprintf"%%%02x",ord($1)}eg;
              print "$u";
-         ' "$remote_name")
-         head=$(curl -nsfL $curl_extra_args "$remote/$remote_name_quoted") &&
+         ' "$head")
+           head=$(curl -nsfL $curl_extra_args "$remote/$remote_name_quoted")
+           depth=$( expr \( $depth + 1 \) )
+         done
          expr "z$head" : "z$_x40\$" >/dev/null ||
-                 die "Failed to fetch $remote_name from $remote"
+             die "Failed to fetch $remote_name from $remote"
          echo >&2 Fetching "$remote_name from $remote" using http
          git-http-fetch -v -a "$head" "$remote/" || exit
          ;;
index c7133bc..8a16ead 100755 (executable)
@@ -205,11 +205,10 @@ sub show_date {
     }
     my $t = $time + $minutes * 60;
     my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = gmtime($t);
-    return sprintf("%s %s %d %02d:%02d:%02d %d %+05d",
-                  $weekday_names[$wday],
-                  $month_names[$mon],
-                  $mday, $hour, $min, $sec,
-                  $year+1900, $tz);
+    return sprintf("%s, %d %s %d %02d:%02d:%02d %+05d",
+                  $weekday_names[$wday], $mday,
+                  $month_names[$mon], $year+1900,
+                  $hour, $min, $sec, $tz);
 }
 
 print "From nobody Mon Sep 17 00:00:00 2001\n";
@@ -275,7 +274,7 @@ print "\n---\n\n";
 close FH or die "close $commsg pipe";
 ' "$keep_subject" "$num" "$signoff" "$headers" "$mimemagic" $commsg
 
-       git-diff-tree -p $diff_opts "$commit" | git-apply --stat --summary
+       git-diff-tree -p --stat --summary $diff_opts "$commit"
        echo
        case "$mimemagic" in
        '');;
diff --git a/git-grep.sh b/git-grep.sh
deleted file mode 100755 (executable)
index ad4f2fe..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) Linus Torvalds, 2005
-#
-
-USAGE='[<option>...] [-e] <pattern> [<path>...]'
-SUBDIRECTORY_OK='Yes'
-. git-sh-setup
-
-got_pattern () {
-       if [ -z "$no_more_patterns" ]
-       then
-               pattern="$1" no_more_patterns=yes
-       else
-               die "git-grep: do not specify more than one pattern"
-       fi
-}
-
-no_more_patterns=
-pattern=
-flags=()
-git_flags=()
-while : ; do
-       case "$1" in
-       -o|--cached|--deleted|--others|--killed|\
-       --ignored|--modified|--exclude=*|\
-       --exclude-from=*|\--exclude-per-directory=*)
-               git_flags=("${git_flags[@]}" "$1")
-               ;;
-       -e)
-               got_pattern "$2"
-               shift
-               ;;
-       -A|-B|-C|-D|-d|-f|-m)
-               flags=("${flags[@]}" "$1" "$2")
-               shift
-               ;;
-       --)
-               # The rest are git-ls-files paths
-               shift
-               break
-               ;;
-       -*)
-               flags=("${flags[@]}" "$1")
-               ;;
-       *)
-               if [ -z "$no_more_patterns" ]
-               then
-                       got_pattern "$1"
-                       shift
-               fi
-               [ "$1" = -- ] && shift
-               break
-               ;;
-       esac
-       shift
-done
-[ "$pattern" ] || {
-       usage
-}
-git-ls-files -z "${git_flags[@]}" -- "$@" |
-       xargs -0 grep "${flags[@]}" -e "$pattern" --
index b834e79..af1f25b 100755 (executable)
@@ -55,8 +55,7 @@ finish () {
 
        case "$no_summary" in
        '')
-               git-diff-tree -p -M "$head" "$1" |
-               git-apply --stat --summary
+               git-diff-tree -p --stat --summary -M "$head" "$1"
                ;;
        esac
 }
index 86dfe9c..6ff6088 100755 (executable)
@@ -4,37 +4,61 @@
 #
 
 USAGE='[--onto <newbase>] <upstream> [<branch>]'
-LONG_USAGE='git-rebase applies to <upstream> (or optionally to <newbase>) commits
-from <branch> that do not appear in <upstream>. When <branch> is not
-specified it defaults to the current branch (HEAD).
-
-When git-rebase is complete, <branch> will be updated to point to the
-newly created line of commit objects, so the previous line will not be
-accessible unless there are other references to it already.
-
-Assuming the following history:
-
-          A---B---C topic
-         /
-    D---E---F---G master
-
-The result of the following command:
-
-    git-rebase --onto master~1 master topic
-
-  would be:
-
-              A'\''--B'\''--C'\'' topic
-             /
-    D---E---F---G master
+LONG_USAGE='git-rebase replaces <branch> with a new branch of the
+same name.  When the --onto option is provided the new branch starts
+out with a HEAD equal to <newbase>, otherwise it is equal to <upstream>
+It then attempts to create a new commit for each commit from the original
+<branch> that does not exist in the <upstream> branch.
+
+It is possible that a merge failure will prevent this process from being
+completely automatic.  You will have to resolve any such merge failure
+and run git rebase --continue.  Another option is to bypass the commit
+that caused the merge failure with git rebase --skip.  To restore the
+original <branch> and remove the .dotest working files, use the command
+git rebase --abort instead.
+
+Note that if <branch> is not specified on the command line, the
+currently checked out branch is used.  You must be in the top
+directory of your project to start (or continue) a rebase.
+
+Example:       git-rebase master~1 topic
+
+        A---B---C topic                   A'\''--B'\''--C'\'' topic
+       /                   -->           /
+  D---E---F---G master          D---E---F---G master
 '
-
 . git-sh-setup
 
+RESOLVEMSG="
+When you have resolved this problem run \"git rebase --continue\".
+If you would prefer to skip this patch, instead run \"git rebase --skip\".
+To restore the original branch and stop rebasing run \"git rebase --abort\".
+"
 unset newbase
 while case "$#" in 0) break ;; esac
 do
        case "$1" in
+       --continue)
+               diff=$(git-diff-files)
+               case "$diff" in
+               ?*)     echo "You must edit all merge conflicts and then"
+                       echo "mark them as resolved using git update-index"
+                       exit 1
+                       ;;
+               esac
+               git am --resolved --3way --resolvemsg="$RESOLVEMSG"
+               exit
+               ;;
+       --skip)
+               git am -3 --skip --resolvemsg="$RESOLVEMSG"
+               exit
+               ;;
+       --abort)
+               [ -d .dotest ] || die "No rebase in progress?"
+               git reset --hard ORIG_HEAD
+               rm -r .dotest
+               exit
+               ;;
        --onto)
                test 2 -le "$#" || usage
                newbase="$2"
@@ -107,7 +131,7 @@ onto=$(git-rev-parse --verify "${onto_name}^0") || exit
 
 # Check if we are already based on $onto, but this should be
 # done only when upstream and onto are the same.
-if test "$upstream" = "onto"
+if test "$upstream" = "$onto"
 then
        mb=$(git-merge-base "$onto" "$branch")
        if test "$mb" = "$onto"
@@ -129,4 +153,5 @@ then
 fi
 
 git-format-patch -k --stdout --full-index "$upstream" ORIG_HEAD |
-git am --binary -3 -k
+git am --binary -3 -k --resolvemsg="$RESOLVEMSG"
+
index e0c9f32..4fb3f26 100755 (executable)
@@ -48,15 +48,15 @@ name=$(git-rev-list --objects --all $rev_list 2>&1 |
        exit 1
 if [ -z "$name" ]; then
        echo Nothing new to pack.
-       exit 0
-fi
-echo "Pack pack-$name created."
+else
+       echo "Pack pack-$name created."
 
-mkdir -p "$PACKDIR" || exit
+       mkdir -p "$PACKDIR" || exit
 
-mv .tmp-pack-$name.pack "$PACKDIR/pack-$name.pack" &&
-mv .tmp-pack-$name.idx  "$PACKDIR/pack-$name.idx" ||
-exit
+       mv .tmp-pack-$name.pack "$PACKDIR/pack-$name.pack" &&
+       mv .tmp-pack-$name.idx  "$PACKDIR/pack-$name.idx" ||
+       exit
+fi
 
 if test "$remove_redundant" = t
 then
index 2c48bfb..4319e35 100755 (executable)
@@ -30,4 +30,4 @@ echo "  $url"
 echo
 
 git log  $baserev..$headrev | git-shortlog ;
-git diff $baserev..$headrev | git-apply --stat --summary
+git diff --stat --summary $baserev..$headrev
index 6cb073c..296f3b7 100755 (executable)
@@ -6,6 +6,7 @@ USAGE='[--mixed | --soft | --hard]  [<commit-ish>]'
 tmp=${GIT_DIR}/reset.$$
 trap 'rm -f $tmp-*' 0 1 2 3 15
 
+update=
 reset_type=--mixed
 case "$1" in
 --mixed | --soft | --hard)
@@ -23,24 +24,7 @@ rev=$(git-rev-parse --verify $rev^0) || exit
 # behind before a hard reset, so that we can remove them.
 if test "$reset_type" = "--hard"
 then
-       {
-               git-ls-files --stage -z
-               git-rev-parse --verify HEAD 2>/dev/null &&
-               git-ls-tree -r -z HEAD
-       } | perl -e '
-           use strict;
-           my %seen;
-           $/ = "\0";
-           while (<>) {
-               chomp;
-               my ($info, $path) = split(/\t/, $_);
-               next if ($info =~ / tree /);
-               if (!$seen{$path}) {
-                       $seen{$path} = 1;
-                       print "$path\0";
-               }
-           }
-       ' >$tmp-exists
+       update=-u
 fi
 
 # Soft reset does not touch the index file nor the working tree
@@ -54,7 +38,7 @@ then
                die "Cannot do a soft reset in the middle of a merge."
        fi
 else
-       git-read-tree --reset "$rev" || exit
+       git-read-tree --reset $update "$rev" || exit
 fi
 
 # Any resets update HEAD to the head being switched to.
@@ -64,37 +48,11 @@ then
 else
        rm -f "$GIT_DIR/ORIG_HEAD"
 fi
-git-update-ref HEAD "$rev"
+git-update-ref -m "reset $reset_type $@" HEAD "$rev"
 
 case "$reset_type" in
 --hard )
-       # Hard reset matches the working tree to that of the tree
-       # being switched to.
-       git-checkout-index -f -u -q -a
-       git-ls-files --cached -z |
-       perl -e '
-               use strict;
-               my (%keep, $fh);
-               $/ = "\0";
-               while (<STDIN>) {
-                       chomp;
-                       $keep{$_} = 1;
-               }
-               open $fh, "<", $ARGV[0]
-                       or die "cannot open $ARGV[0]";
-               while (<$fh>) {
-                       chomp;
-                       if (! exists $keep{$_}) {
-                               # it is ok if this fails -- it may already
-                               # have been culled by checkout-index.
-                               unlink $_;
-                               while (s|/[^/]*$||) {
-                                       rmdir($_) or last;
-                               }
-                       }
-               }
-       ' $tmp-exists
-       ;;
+       ;; # Nothing else to do
 --soft )
        ;; # Nothing else to do
 --mixed )
index c19d3a6..de8b5f0 100755 (executable)
@@ -137,7 +137,7 @@ esac >.msg
 # $prev and $commit on top of us (when cherry-picking or replaying).
 
 echo >&2 "First trying simple merge strategy to $me."
-git-read-tree -m -u $base $head $next &&
+git-read-tree -m -u --aggressive $base $head $next &&
 result=$(git-write-tree 2>/dev/null) || {
     echo >&2 "Simple $me fails; trying Automatic $me."
     git-merge-index -o git-merge-one-file -a || {
index ecfa347..312a4ea 100755 (executable)
@@ -40,7 +40,8 @@ my $compose_filename = ".msg.$$";
 my (@to,@cc,@initial_cc,$initial_reply_to,$initial_subject,@files,$from,$compose,$time);
 
 # Behavior modification variables
-my ($chain_reply_to, $smtp_server, $quiet, $suppress_from, $no_signed_off_cc) = (1, "localhost", 0, 0, 0);
+my ($chain_reply_to, $quiet, $suppress_from, $no_signed_off_cc) = (1, 0, 0, 0);
+my $smtp_server;
 
 # Example reply to:
 #$initial_reply_to = ''; #<20050203173208.GA23964@foobar.com>';
@@ -89,6 +90,41 @@ sub gitvar_ident {
 my ($author) = gitvar_ident('GIT_AUTHOR_IDENT');
 my ($committer) = gitvar_ident('GIT_COMMITTER_IDENT');
 
+my %aliases;
+chomp(my @alias_files = `git-repo-config --get-all sendemail.aliasesfile`);
+chomp(my $aliasfiletype = `git-repo-config sendemail.aliasfiletype`);
+my %parse_alias = (
+       # multiline formats can be supported in the future
+       mutt => sub { my $fh = shift; while (<$fh>) {
+               if (/^alias\s+(\S+)\s+(.*)$/) {
+                       my ($alias, $addr) = ($1, $2);
+                       $addr =~ s/#.*$//; # mutt allows # comments
+                        # commas delimit multiple addresses
+                       $aliases{$alias} = [ split(/\s*,\s*/, $addr) ];
+               }}},
+       mailrc => sub { my $fh = shift; while (<$fh>) {
+               if (/^alias\s+(\S+)\s+(.*)$/) {
+                       # spaces delimit multiple addresses
+                       $aliases{$1} = [ split(/\s+/, $2) ];
+               }}},
+       pine => sub { my $fh = shift; while (<$fh>) {
+               if (/^(\S+)\s+(.*)$/) {
+                       $aliases{$1} = [ split(/\s*,\s*/, $2) ];
+               }}},
+       gnus => sub { my $fh = shift; while (<$fh>) {
+               if (/\(define-mail-alias\s+"(\S+?)"\s+"(\S+?)"\)/) {
+                       $aliases{$1} = [ $2 ];
+               }}}
+);
+
+if (@alias_files && defined $parse_alias{$aliasfiletype}) {
+       foreach my $file (@alias_files) {
+               open my $fh, '<', $file or die "opening $file: $!\n";
+               $parse_alias{$aliasfiletype}->($fh);
+               close $fh;
+       }
+}
+
 my $prompting = 0;
 if (!defined $from) {
        $from = $author || $committer;
@@ -112,6 +148,19 @@ if (!@to) {
        $prompting++;
 }
 
+sub expand_aliases {
+       my @cur = @_;
+       my @last;
+       do {
+               @last = @cur;
+               @cur = map { $aliases{$_} ? @{$aliases{$_}} : $_ } @last;
+       } while (join(',',@cur) ne join(',',@last));
+       return @cur;
+}
+
+@to = expand_aliases(@to);
+@initial_cc = expand_aliases(@initial_cc);
+
 if (!defined $initial_subject && $compose) {
        do {
                $_ = $term->readline("What subject should the emails start with? ",
@@ -131,8 +180,14 @@ if (!defined $initial_reply_to && $prompting) {
        $initial_reply_to =~ s/(^\s+|\s+$)//g;
 }
 
-if (!defined $smtp_server) {
-       $smtp_server = "localhost";
+if (!$smtp_server) {
+       foreach (qw( /usr/sbin/sendmail /usr/lib/sendmail )) {
+               if (-x $_) {
+                       $smtp_server = $_;
+                       last;
+               }
+       }
+       $smtp_server ||= 'localhost'; # could be 127.0.0.1, too... *shrug*
 }
 
 if ($compose) {
@@ -252,6 +307,10 @@ our ($message_id, $cc, %mail, $subject, $reply_to, $message);
 
 sub extract_valid_address {
        my $address = shift;
+
+       # check for a local address:
+       return $address if ($address =~ /^([\w\-]+)$/);
+
        if ($have_email_valid) {
                return Email::Valid->address($address);
        } else {
@@ -291,6 +350,13 @@ sub send_message
        my $to = join (",\n\t", @recipients);
        @recipients = unique_email_list(@recipients,@cc);
        my $date = strftime('%a, %d %b %Y %H:%M:%S %z', localtime($time++));
+       my $gitversion = '@@GIT_VERSION@@';
+       if ($gitversion =~ m/..GIT_VERSION../) {
+           $gitversion = `git --version`;
+           chomp $gitversion;
+           # keep only what's after the last space
+           $gitversion =~ s/^.* //;
+       }
 
        my $header = "From: $from
 To: $to
@@ -299,30 +365,43 @@ Subject: $subject
 Reply-To: $from
 Date: $date
 Message-Id: $message_id
-X-Mailer: git-send-email @@GIT_VERSION@@
+X-Mailer: git-send-email $gitversion
 ";
        $header .= "In-Reply-To: $reply_to\n" if $reply_to;
 
-       $smtp ||= Net::SMTP->new( $smtp_server );
-       $smtp->mail( $from ) or die $smtp->message;
-       $smtp->to( @recipients ) or die $smtp->message;
-       $smtp->data or die $smtp->message;
-       $smtp->datasend("$header\n$message") or die $smtp->message;
-       $smtp->dataend() or die $smtp->message;
-       $smtp->ok or die "Failed to send $subject\n".$smtp->message;
-
+       if ($smtp_server =~ m#^/#) {
+               my $pid = open my $sm, '|-';
+               defined $pid or die $!;
+               if (!$pid) {
+                       exec($smtp_server,'-i',@recipients) or die $!;
+               }
+               print $sm "$header\n$message";
+               close $sm or die $?;
+       } else {
+               $smtp ||= Net::SMTP->new( $smtp_server );
+               $smtp->mail( $from ) or die $smtp->message;
+               $smtp->to( @recipients ) or die $smtp->message;
+               $smtp->data or die $smtp->message;
+               $smtp->datasend("$header\n$message") or die $smtp->message;
+               $smtp->dataend() or die $smtp->message;
+               $smtp->ok or die "Failed to send $subject\n".$smtp->message;
+       }
        if ($quiet) {
                printf "Sent %s\n", $subject;
        } else {
-               print "OK. Log says:
-Date: $date
-Server: $smtp_server Port: 25
-From: $from
-Subject: $subject
-Cc: $cc
-To: $to
-
-Result: ", $smtp->code, ' ', ($smtp->message =~ /\n([^\n]+\n)$/s), "\n";
+               print "OK. Log says:\nDate: $date\n";
+               if ($smtp) {
+                       print "Server: $smtp_server\n";
+               } else {
+                       print "Sendmail: $smtp_server\n";
+               }
+               print "From: $from\nSubject: $subject\nCc: $cc\nTo: $to\n\n";
+               if ($smtp) {
+                       print "Result: ", $smtp->code, ' ',
+                               ($smtp->message =~ /\n([^\n]+\n)$/s), "\n";
+               } else {
+                       print "Result: OK\n";
+               }
        }
 }
 
@@ -423,9 +502,14 @@ sub unique_email_list(@) {
        my @emails;
 
        foreach my $entry (@_) {
-               my $clean = extract_valid_address($entry);
-               next if $seen{$clean}++;
-               push @emails, $entry;
+               if (my $clean = extract_valid_address($entry)) {
+                       $seen{$clean} ||= 0;
+                       next if $seen{$clean}++;
+                       push @emails, $entry;
+               } else {
+                       print STDERR "W: unable to extract a valid address",
+                                       " from: $entry\n";
+               }
        }
        return @emails;
 }
index dc6aa95..a0afa25 100755 (executable)
@@ -25,14 +25,12 @@ do
        force=1
        ;;
     -l)
-        cd "$GIT_DIR/refs" &&
        case "$#" in
        1)
-               find tags -type f -print ;;
-       *)
-               shift
-               find tags -type f -print | grep "$@" ;;
+               set x . ;;
        esac
+       shift
+       git rev-parse --symbolic --tags | sort | grep "$@"
        exit $?
        ;;
     -m)
diff --git a/git-whatchanged.sh b/git-whatchanged.sh
deleted file mode 100755 (executable)
index 1fb9feb..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/sh
-
-USAGE='[-p] [--max-count=<n>] [<since>..<limit>] [--pretty=<format>] [-m] [git-diff-tree options] [git-rev-list options]'
-SUBDIRECTORY_OK='Yes'
-. git-sh-setup
-
-diff_tree_flags=$(git-rev-parse --sq --no-revs --flags "$@") || exit
-case "$0" in
-*whatchanged)
-       count=
-       test -z "$diff_tree_flags" &&
-               diff_tree_flags=$(git-repo-config --get whatchanged.difftree)
-       diff_tree_default_flags='-c -M --abbrev' ;;
-*show)
-       count=-n1
-       test -z "$diff_tree_flags" &&
-               diff_tree_flags=$(git-repo-config --get show.difftree)
-       diff_tree_default_flags='--cc --always' ;;
-esac
-test -z "$diff_tree_flags" &&
-       diff_tree_flags="$diff_tree_default_flags"
-
-rev_list_args=$(git-rev-parse --sq --default HEAD --revs-only "$@") &&
-diff_tree_args=$(git-rev-parse --sq --no-revs --no-flags "$@") &&
-
-eval "git-rev-list $count $rev_list_args" |
-eval "git-diff-tree --stdin --pretty -r $diff_tree_flags $diff_tree_args" |
-LESS="$LESS -S" ${PAGER:-less}
diff --git a/git.c b/git.c
index 619c665..84803a6 100644 (file)
--- a/git.c
+++ b/git.c
@@ -8,7 +8,6 @@
 #include <errno.h>
 #include <limits.h>
 #include <stdarg.h>
-#include <sys/ioctl.h>
 #include "git-compat-util.h"
 #include "exec_cmd.h"
 
@@ -47,7 +46,11 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
                { "log", cmd_log },
                { "whatchanged", cmd_whatchanged },
                { "show", cmd_show },
+               { "push", cmd_push },
                { "fmt-patch", cmd_format_patch },
+               { "count-objects", cmd_count_objects },
+               { "diff", cmd_diff },
+               { "grep", cmd_grep },
        };
        int i;
 
index 96dfc1d..8ccd256 100644 (file)
@@ -74,12 +74,12 @@ Git revision tree visualiser ('gitk')
 %setup -q
 
 %build
-make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" WITH_OWN_SUBPROCESS_PY=YesPlease WITH_SEND_EMAIL=1 \
+make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" WITH_OWN_SUBPROCESS_PY=YesPlease \
      prefix=%{_prefix} all %{!?_without_docs: doc}
 
 %install
 rm -rf $RPM_BUILD_ROOT
-make %{_smp_mflags} DESTDIR=$RPM_BUILD_ROOT WITH_OWN_SUBPROCESS_PY=YesPlease WITH_SEND_EMAIL=1 \
+make %{_smp_mflags} DESTDIR=$RPM_BUILD_ROOT WITH_OWN_SUBPROCESS_PY=YesPlease \
      prefix=%{_prefix} mandir=%{_mandir} \
      install %{!?_without_docs: install-doc}
 
diff --git a/gitk b/gitk
index 87e7162..4aa57c0 100755 (executable)
--- a/gitk
+++ b/gitk
@@ -16,99 +16,112 @@ proc gitdir {} {
     }
 }
 
-proc parse_args {rargs} {
-    global parsed_args
-
-    if {[catch {
-       set parse_args [concat --default HEAD $rargs]
-       set parsed_args [split [eval exec git-rev-parse $parse_args] "\n"]
-    }]} {
-       # if git-rev-parse failed for some reason...
-       if {$rargs == {}} {
-           set rargs HEAD
-       }
-       set parsed_args $rargs
-    }
-    return $parsed_args
-}
-
-proc start_rev_list {rlargs} {
+proc start_rev_list {view} {
     global startmsecs nextupdate ncmupdate
     global commfd leftover tclencoding datemode
+    global viewargs viewfiles commitidx
 
     set startmsecs [clock clicks -milliseconds]
     set nextupdate [expr {$startmsecs + 100}]
     set ncmupdate 1
-    initlayout
+    set commitidx($view) 0
+    set args $viewargs($view)
+    if {$viewfiles($view) ne {}} {
+       set args [concat $args "--" $viewfiles($view)]
+    }
     set order "--topo-order"
     if {$datemode} {
        set order "--date-order"
     }
     if {[catch {
-       set commfd [open [concat | git-rev-list --header $order \
-                             --parents --boundary $rlargs] r]
+       set fd [open [concat | git-rev-list --header $order \
+                         --parents --boundary --default HEAD $args] r]
     } err]} {
        puts stderr "Error executing git-rev-list: $err"
        exit 1
     }
-    set leftover {}
-    fconfigure $commfd -blocking 0 -translation lf
+    set commfd($view) $fd
+    set leftover($view) {}
+    fconfigure $fd -blocking 0 -translation lf
     if {$tclencoding != {}} {
-       fconfigure $commfd -encoding $tclencoding
+       fconfigure $fd -encoding $tclencoding
+    }
+    fileevent $fd readable [list getcommitlines $fd $view]
+    nowbusy $view
+}
+
+proc stop_rev_list {} {
+    global commfd curview
+
+    if {![info exists commfd($curview)]} return
+    set fd $commfd($curview)
+    catch {
+       set pid [pid $fd]
+       exec kill $pid
     }
-    fileevent $commfd readable [list getcommitlines $commfd]
-    . config -cursor watch
-    settextcursor watch
+    catch {close $fd}
+    unset commfd($curview)
 }
 
-proc getcommits {rargs} {
-    global phase canv mainfont
+proc getcommits {} {
+    global phase canv mainfont curview
 
     set phase getcommits
-    start_rev_list [parse_args $rargs]
-    $canv delete all
-    $canv create text 3 3 -anchor nw -text "Reading commits..." \
-       -font $mainfont -tags textitems
+    initlayout
+    start_rev_list $curview
+    show_status "Reading commits..."
 }
 
-proc getcommitlines {commfd}  {
+proc getcommitlines {fd view}  {
     global commitlisted nextupdate
-    global leftover
+    global leftover commfd
     global displayorder commitidx commitrow commitdata
-    global parentlist childlist children
+    global parentlist childlist children curview hlview
+    global vparentlist vchildlist vdisporder vcmitlisted
 
-    set stuff [read $commfd]
+    set stuff [read $fd]
     if {$stuff == {}} {
-       if {![eof $commfd]} return
+       if {![eof $fd]} return
+       global viewname
+       unset commfd($view)
+       notbusy $view
        # set it blocking so we wait for the process to terminate
-       fconfigure $commfd -blocking 1
-       if {![catch {close $commfd} err]} {
-           after idle finishcommits
-           return
+       fconfigure $fd -blocking 1
+       if {[catch {close $fd} err]} {
+           set fv {}
+           if {$view != $curview} {
+               set fv " for the \"$viewname($view)\" view"
+           }
+           if {[string range $err 0 4] == "usage"} {
+               set err "Gitk: error reading commits$fv:\
+                       bad arguments to git-rev-list."
+               if {$viewname($view) eq "Command line"} {
+                   append err \
+                       "  (Note: arguments to gitk are passed to git-rev-list\
+                        to allow selection of commits to be displayed.)"
+               }
+           } else {
+               set err "Error reading commits$fv: $err"
+           }
+           error_popup $err
        }
-       if {[string range $err 0 4] == "usage"} {
-           set err \
-               "Gitk: error reading commits: bad arguments to git-rev-list.\
-               (Note: arguments to gitk are passed to git-rev-list\
-               to allow selection of commits to be displayed.)"
-       } else {
-           set err "Error reading commits: $err"
+       if {$view == $curview} {
+           after idle finishcommits
        }
-       error_popup $err
-       exit 1
+       return
     }
     set start 0
     set gotsome 0
     while 1 {
        set i [string first "\0" $stuff $start]
        if {$i < 0} {
-           append leftover [string range $stuff $start end]
+           append leftover($view) [string range $stuff $start end]
            break
        }
        if {$start == 0} {
-           set cmit $leftover
+           set cmit $leftover($view)
            append cmit [string range $stuff 0 [expr {$i - 1}]]
-           set leftover {}
+           set leftover($view) {}
        } else {
            set cmit [string range $stuff $start [expr {$i - 1}]]
        }
@@ -141,41 +154,52 @@ proc getcommitlines {commfd}  {
        set id [lindex $ids 0]
        if {$listed} {
            set olds [lrange $ids 1 end]
-           if {[llength $olds] > 1} {
-               set olds [lsort -unique $olds]
-           }
+           set i 0
            foreach p $olds {
-               lappend children($p) $id
+               if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
+                   lappend children($view,$p) $id
+               }
+               incr i
            }
        } else {
            set olds {}
        }
-       lappend parentlist $olds
-       if {[info exists children($id)]} {
-           lappend childlist $children($id)
-       } else {
-           lappend childlist {}
+       if {![info exists children($view,$id)]} {
+           set children($view,$id) {}
        }
        set commitdata($id) [string range $cmit [expr {$j + 1}] end]
-       set commitrow($id) $commitidx
-       incr commitidx
-       lappend displayorder $id
-       lappend commitlisted $listed
+       set commitrow($view,$id) $commitidx($view)
+       incr commitidx($view)
+       if {$view == $curview} {
+           lappend parentlist $olds
+           lappend childlist $children($view,$id)
+           lappend displayorder $id
+           lappend commitlisted $listed
+       } else {
+           lappend vparentlist($view) $olds
+           lappend vchildlist($view) $children($view,$id)
+           lappend vdisporder($view) $id
+           lappend vcmitlisted($view) $listed
+       }
        set gotsome 1
     }
     if {$gotsome} {
-       layoutmore
+       if {$view == $curview} {
+           layoutmore
+       } elseif {[info exists hlview] && $view == $hlview} {
+           highlightmore
+       }
     }
     if {[clock clicks -milliseconds] >= $nextupdate} {
-       doupdate 1
+       doupdate
     }
 }
 
-proc doupdate {reading} {
+proc doupdate {} {
     global commfd nextupdate numcommits ncmupdate
 
-    if {$reading} {
-       fileevent $commfd readable {}
+    foreach v [array names commfd] {
+       fileevent $commfd($v) readable {}
     }
     update
     set nextupdate [expr {[clock clicks -milliseconds] + 100}]
@@ -186,8 +210,9 @@ proc doupdate {reading} {
     } else {
        set ncmupdate [expr {$numcommits + 100}]
     }
-    if {$reading} {
-       fileevent $commfd readable [list getcommitlines $commfd]
+    foreach v [array names commfd] {
+       set fd $commfd($v)
+       fileevent $fd readable [list getcommitlines $fd $v]
     }
 }
 
@@ -196,18 +221,23 @@ proc readcommit {id} {
     parsecommit $id $contents 0
 }
 
-proc updatecommits {rargs} {
-    stopfindproc
-    foreach v {colormap selectedline matchinglines treediffs
-       mergefilelist currentid rowtextx commitrow
-       rowidlist rowoffsets idrowranges idrangedrawn iddrawn
-       linesegends crossings cornercrossings} {
-       global $v
-       catch {unset $v}
+proc updatecommits {} {
+    global viewdata curview phase displayorder
+    global children commitrow
+
+    if {$phase ne {}} {
+       stop_rev_list
+       set phase {}
     }
-    allcanvs delete all
+    set n $curview
+    foreach id $displayorder {
+       catch {unset children($n,$id)}
+       catch {unset commitrow($n,$id)}
+    }
+    set curview -1
+    catch {unset viewdata($n)}
     readrefs
-    getcommits $rargs
+    showview $n
 }
 
 proc parsecommit {id contents listed} {
@@ -290,10 +320,16 @@ proc readrefs {} {
            match id path]} {
            continue
        }
+       if {[regexp {^remotes/.*/HEAD$} $path match]} {
+           continue
+       }
        if {![regexp {^(tags|heads)/(.*)$} $path match type name]} {
            set type others
            set name $path
        }
+       if {[regexp {^remotes/} $path match]} {
+           set type heads
+       }
        if {$type == "tags"} {
            set tagids($name) $id
            lappend idtags($id) $name
@@ -321,10 +357,7 @@ proc readrefs {} {
     close $refd
 }
 
-proc error_popup msg {
-    set w .error
-    toplevel $w
-    wm transient $w .
+proc show_error {w msg} {
     message $w.m -text $msg -justify center -aspect 400
     pack $w.m -side top -fill x -padx 20 -pady 20
     button $w.ok -text OK -command "destroy $w"
@@ -334,8 +367,16 @@ proc error_popup msg {
     tkwait window $w
 }
 
-proc makewindow {rargs} {
-    global canv canv2 canv3 linespc charspc ctext cflist textfont mainfont uifont
+proc error_popup msg {
+    set w .error
+    toplevel $w
+    wm transient $w .
+    show_error $w $msg
+}
+
+proc makewindow {} {
+    global canv canv2 canv3 linespc charspc ctext cflist
+    global textfont mainfont uifont
     global findtype findtypemenu findloc findstring fstring geometry
     global entries sha1entry sha1string sha1but
     global maincursor textcursor curtextcursor
@@ -345,7 +386,7 @@ proc makewindow {rargs} {
     .bar add cascade -label "File" -menu .bar.file
     .bar configure -font $uifont
     menu .bar.file
-    .bar.file add command -label "Update" -command [list updatecommits $rargs]
+    .bar.file add command -label "Update" -command updatecommits
     .bar.file add command -label "Reread references" -command rereadrefs
     .bar.file add command -label "Quit" -command doquit
     .bar.file configure -font $uifont
@@ -353,6 +394,23 @@ proc makewindow {rargs} {
     .bar add cascade -label "Edit" -menu .bar.edit
     .bar.edit add command -label "Preferences" -command doprefs
     .bar.edit configure -font $uifont
+
+    menu .bar.view -font $uifont
+    menu .bar.view.hl -font $uifont -tearoff 0
+    .bar add cascade -label "View" -menu .bar.view
+    .bar.view add command -label "New view..." -command {newview 0}
+    .bar.view add command -label "Edit view..." -command editview \
+       -state disabled
+    .bar.view add command -label "Delete view" -command delview -state disabled
+    .bar.view add cascade -label "Highlight" -menu .bar.view.hl
+    .bar.view add separator
+    .bar.view add radiobutton -label "All files" -command {showview 0} \
+       -variable selectedview -value 0
+    .bar.view.hl add command -label "New view..." -command {newview 1}
+    .bar.view.hl add command -label "Remove" -command delhighlight \
+       -state disabled
+    .bar.view.hl add separator
+    
     menu .bar.help
     .bar add cascade -label "Help" -menu .bar.help
     .bar.help add command -label "About gitk" -command about
@@ -463,7 +521,7 @@ proc makewindow {rargs} {
     set ctext .ctop.cdet.left.ctext
     text $ctext -bg white -state disabled -font $textfont \
        -width $geometry(ctextw) -height $geometry(ctexth) \
-       -yscrollcommand ".ctop.cdet.left.sb set" -wrap none
+       -yscrollcommand {.ctop.cdet.left.sb set} -wrap none
     scrollbar .ctop.cdet.left.sb -command "$ctext yview"
     pack .ctop.cdet.left.sb -side right -fill y
     pack $ctext -side left -fill both -expand 1
@@ -496,12 +554,25 @@ proc makewindow {rargs} {
     $ctext tag conf found -back yellow
 
     frame .ctop.cdet.right
+    frame .ctop.cdet.right.mode
+    radiobutton .ctop.cdet.right.mode.patch -text "Patch" \
+       -command reselectline -variable cmitmode -value "patch"
+    radiobutton .ctop.cdet.right.mode.tree -text "Tree" \
+       -command reselectline -variable cmitmode -value "tree"
+    grid .ctop.cdet.right.mode.patch .ctop.cdet.right.mode.tree -sticky ew
+    pack .ctop.cdet.right.mode -side top -fill x
     set cflist .ctop.cdet.right.cfiles
-    listbox $cflist -bg white -selectmode extended -width $geometry(cflistw) \
-       -yscrollcommand ".ctop.cdet.right.sb set" -font $mainfont
+    set indent [font measure $mainfont "nn"]
+    text $cflist -width $geometry(cflistw) -background white -font $mainfont \
+       -tabs [list $indent [expr {2 * $indent}]] \
+       -yscrollcommand ".ctop.cdet.right.sb set" \
+       -cursor [. cget -cursor] \
+       -spacing1 1 -spacing3 1
     scrollbar .ctop.cdet.right.sb -command "$cflist yview"
     pack .ctop.cdet.right.sb -side right -fill y
     pack $cflist -side left -fill both -expand 1
+    $cflist tag configure highlight \
+       -background [$cflist cget -selectbackground]
     .ctop.cdet add .ctop.cdet.right
     bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
 
@@ -553,12 +624,14 @@ proc makewindow {rargs} {
     bind . <Control-KP_Add> {incrfont 1}
     bind . <Control-minus> {incrfont -1}
     bind . <Control-KP_Subtract> {incrfont -1}
-    bind $cflist <<ListboxSelect>> listboxsel
     bind . <Destroy> {savestuff %W}
     bind . <Button-1> "click %W"
     bind $fstring <Key-Return> dofind
     bind $sha1entry <Key-Return> gotocommit
     bind $sha1entry <<PasteSelection>> clearsha1
+    bind $cflist <1> {sel_flist %W %x %y; break}
+    bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
+    bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
 
     set maincursor [. cget -cursor]
     set textcursor [$ctext cget -cursor]
@@ -622,6 +695,8 @@ proc savestuff {w} {
     global canv canv2 canv3 ctext cflist mainfont textfont uifont
     global stuffsaved findmergefiles maxgraphpct
     global maxwidth
+    global viewname viewfiles viewargs viewperm nextviewnum
+    global cmitmode
 
     if {$stuffsaved} return
     if {![winfo viewable .]} return
@@ -633,6 +708,7 @@ proc savestuff {w} {
        puts $f [list set findmergefiles $findmergefiles]
        puts $f [list set maxgraphpct $maxgraphpct]
        puts $f [list set maxwidth $maxwidth]
+       puts $f [list set cmitmode $cmitmode]
        puts $f "set geometry(width) [winfo width .ctop]"
        puts $f "set geometry(height) [winfo height .ctop]"
        puts $f "set geometry(canv1) [expr {[winfo width $canv]-2}]"
@@ -645,6 +721,13 @@ proc savestuff {w} {
        set wid [expr {([winfo width $cflist] - 11) \
                           / [font measure [$cflist cget -font] "0"]}]
        puts $f "set geometry(cflistw) $wid"
+       puts -nonewline $f "set permviews {"
+       for {set v 0} {$v < $nextviewnum} {incr v} {
+           if {$viewperm($v)} {
+               puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v)]}"
+           }
+       }
+       puts $f "}"
        close $f
        file rename -force "~/.gitk-new" "~/.gitk"
     }
@@ -786,6 +869,754 @@ f         Scroll diff view to next file
     pack $w.ok -side bottom
 }
 
+# Procedures for manipulating the file list window at the
+# bottom right of the overall window.
+
+proc treeview {w l openlevs} {
+    global treecontents treediropen treeheight treeparent treeindex
+
+    set ix 0
+    set treeindex() 0
+    set lev 0
+    set prefix {}
+    set prefixend -1
+    set prefendstack {}
+    set htstack {}
+    set ht 0
+    set treecontents() {}
+    $w conf -state normal
+    foreach f $l {
+       while {[string range $f 0 $prefixend] ne $prefix} {
+           if {$lev <= $openlevs} {
+               $w mark set e:$treeindex($prefix) "end -1c"
+               $w mark gravity e:$treeindex($prefix) left
+           }
+           set treeheight($prefix) $ht
+           incr ht [lindex $htstack end]
+           set htstack [lreplace $htstack end end]
+           set prefixend [lindex $prefendstack end]
+           set prefendstack [lreplace $prefendstack end end]
+           set prefix [string range $prefix 0 $prefixend]
+           incr lev -1
+       }
+       set tail [string range $f [expr {$prefixend+1}] end]
+       while {[set slash [string first "/" $tail]] >= 0} {
+           lappend htstack $ht
+           set ht 0
+           lappend prefendstack $prefixend
+           incr prefixend [expr {$slash + 1}]
+           set d [string range $tail 0 $slash]
+           lappend treecontents($prefix) $d
+           set oldprefix $prefix
+           append prefix $d
+           set treecontents($prefix) {}
+           set treeindex($prefix) [incr ix]
+           set treeparent($prefix) $oldprefix
+           set tail [string range $tail [expr {$slash+1}] end]
+           if {$lev <= $openlevs} {
+               set ht 1
+               set treediropen($prefix) [expr {$lev < $openlevs}]
+               set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
+               $w mark set d:$ix "end -1c"
+               $w mark gravity d:$ix left
+               set str "\n"
+               for {set i 0} {$i < $lev} {incr i} {append str "\t"}
+               $w insert end $str
+               $w image create end -align center -image $bm -padx 1 \
+                   -name a:$ix
+               $w insert end $d
+               $w mark set s:$ix "end -1c"
+               $w mark gravity s:$ix left
+           }
+           incr lev
+       }
+       if {$tail ne {}} {
+           if {$lev <= $openlevs} {
+               incr ht
+               set str "\n"
+               for {set i 0} {$i < $lev} {incr i} {append str "\t"}
+               $w insert end $str
+               $w insert end $tail
+           }
+           lappend treecontents($prefix) $tail
+       }
+    }
+    while {$htstack ne {}} {
+       set treeheight($prefix) $ht
+       incr ht [lindex $htstack end]
+       set htstack [lreplace $htstack end end]
+    }
+    $w conf -state disabled
+}
+
+proc linetoelt {l} {
+    global treeheight treecontents
+
+    set y 2
+    set prefix {}
+    while {1} {
+       foreach e $treecontents($prefix) {
+           if {$y == $l} {
+               return "$prefix$e"
+           }
+           set n 1
+           if {[string index $e end] eq "/"} {
+               set n $treeheight($prefix$e)
+               if {$y + $n > $l} {
+                   append prefix $e
+                   incr y
+                   break
+               }
+           }
+           incr y $n
+       }
+    }
+}
+
+proc treeclosedir {w dir} {
+    global treediropen treeheight treeparent treeindex
+
+    set ix $treeindex($dir)
+    $w conf -state normal
+    $w delete s:$ix e:$ix
+    set treediropen($dir) 0
+    $w image configure a:$ix -image tri-rt
+    $w conf -state disabled
+    set n [expr {1 - $treeheight($dir)}]
+    while {$dir ne {}} {
+       incr treeheight($dir) $n
+       set dir $treeparent($dir)
+    }
+}
+
+proc treeopendir {w dir} {
+    global treediropen treeheight treeparent treecontents treeindex
+
+    set ix $treeindex($dir)
+    $w conf -state normal
+    $w image configure a:$ix -image tri-dn
+    $w mark set e:$ix s:$ix
+    $w mark gravity e:$ix right
+    set lev 0
+    set str "\n"
+    set n [llength $treecontents($dir)]
+    for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
+       incr lev
+       append str "\t"
+       incr treeheight($x) $n
+    }
+    foreach e $treecontents($dir) {
+       if {[string index $e end] eq "/"} {
+           set de $dir$e
+           set iy $treeindex($de)
+           $w mark set d:$iy e:$ix
+           $w mark gravity d:$iy left
+           $w insert e:$ix $str
+           set treediropen($de) 0
+           $w image create e:$ix -align center -image tri-rt -padx 1 \
+               -name a:$iy
+           $w insert e:$ix $e
+           $w mark set s:$iy e:$ix
+           $w mark gravity s:$iy left
+           set treeheight($de) 1
+       } else {
+           $w insert e:$ix $str
+           $w insert e:$ix $e
+       }
+    }
+    $w mark gravity e:$ix left
+    $w conf -state disabled
+    set treediropen($dir) 1
+    set top [lindex [split [$w index @0,0] .] 0]
+    set ht [$w cget -height]
+    set l [lindex [split [$w index s:$ix] .] 0]
+    if {$l < $top} {
+       $w yview $l.0
+    } elseif {$l + $n + 1 > $top + $ht} {
+       set top [expr {$l + $n + 2 - $ht}]
+       if {$l < $top} {
+           set top $l
+       }
+       $w yview $top.0
+    }
+}
+
+proc treeclick {w x y} {
+    global treediropen cmitmode ctext cflist cflist_top
+
+    if {$cmitmode ne "tree"} return
+    if {![info exists cflist_top]} return
+    set l [lindex [split [$w index "@$x,$y"] "."] 0]
+    $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
+    $cflist tag add highlight $l.0 "$l.0 lineend"
+    set cflist_top $l
+    if {$l == 1} {
+       $ctext yview 1.0
+       return
+    }
+    set e [linetoelt $l]
+    if {[string index $e end] ne "/"} {
+       showfile $e
+    } elseif {$treediropen($e)} {
+       treeclosedir $w $e
+    } else {
+       treeopendir $w $e
+    }
+}
+
+proc setfilelist {id} {
+    global treefilelist cflist
+
+    treeview $cflist $treefilelist($id) 0
+}
+
+image create bitmap tri-rt -background black -foreground blue -data {
+    #define tri-rt_width 13
+    #define tri-rt_height 13
+    static unsigned char tri-rt_bits[] = {
+       0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
+       0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
+       0x00, 0x00};
+} -maskdata {
+    #define tri-rt-mask_width 13
+    #define tri-rt-mask_height 13
+    static unsigned char tri-rt-mask_bits[] = {
+       0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
+       0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
+       0x08, 0x00};
+}
+image create bitmap tri-dn -background black -foreground blue -data {
+    #define tri-dn_width 13
+    #define tri-dn_height 13
+    static unsigned char tri-dn_bits[] = {
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
+       0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00};
+} -maskdata {
+    #define tri-dn-mask_width 13
+    #define tri-dn-mask_height 13
+    static unsigned char tri-dn-mask_bits[] = {
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
+       0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00};
+}
+
+proc init_flist {first} {
+    global cflist cflist_top selectedline difffilestart
+
+    $cflist conf -state normal
+    $cflist delete 0.0 end
+    if {$first ne {}} {
+       $cflist insert end $first
+       set cflist_top 1
+       $cflist tag add highlight 1.0 "1.0 lineend"
+    } else {
+       catch {unset cflist_top}
+    }
+    $cflist conf -state disabled
+    set difffilestart {}
+}
+
+proc add_flist {fl} {
+    global flistmode cflist
+
+    $cflist conf -state normal
+    if {$flistmode eq "flat"} {
+       foreach f $fl {
+           $cflist insert end "\n$f"
+       }
+    }
+    $cflist conf -state disabled
+}
+
+proc sel_flist {w x y} {
+    global flistmode ctext difffilestart cflist cflist_top cmitmode
+
+    if {$cmitmode eq "tree"} return
+    if {![info exists cflist_top]} return
+    set l [lindex [split [$w index "@$x,$y"] "."] 0]
+    $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
+    $cflist tag add highlight $l.0 "$l.0 lineend"
+    set cflist_top $l
+    if {$l == 1} {
+       $ctext yview 1.0
+    } else {
+       catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
+    }
+}
+
+# Functions for adding and removing shell-type quoting
+
+proc shellquote {str} {
+    if {![string match "*\['\"\\ \t]*" $str]} {
+       return $str
+    }
+    if {![string match "*\['\"\\]*" $str]} {
+       return "\"$str\""
+    }
+    if {![string match "*'*" $str]} {
+       return "'$str'"
+    }
+    return "\"[string map {\" \\\" \\ \\\\} $str]\""
+}
+
+proc shellarglist {l} {
+    set str {}
+    foreach a $l {
+       if {$str ne {}} {
+           append str " "
+       }
+       append str [shellquote $a]
+    }
+    return $str
+}
+
+proc shelldequote {str} {
+    set ret {}
+    set used -1
+    while {1} {
+       incr used
+       if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
+           append ret [string range $str $used end]
+           set used [string length $str]
+           break
+       }
+       set first [lindex $first 0]
+       set ch [string index $str $first]
+       if {$first > $used} {
+           append ret [string range $str $used [expr {$first - 1}]]
+           set used $first
+       }
+       if {$ch eq " " || $ch eq "\t"} break
+       incr used
+       if {$ch eq "'"} {
+           set first [string first "'" $str $used]
+           if {$first < 0} {
+               error "unmatched single-quote"
+           }
+           append ret [string range $str $used [expr {$first - 1}]]
+           set used $first
+           continue
+       }
+       if {$ch eq "\\"} {
+           if {$used >= [string length $str]} {
+               error "trailing backslash"
+           }
+           append ret [string index $str $used]
+           continue
+       }
+       # here ch == "\""
+       while {1} {
+           if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
+               error "unmatched double-quote"
+           }
+           set first [lindex $first 0]
+           set ch [string index $str $first]
+           if {$first > $used} {
+               append ret [string range $str $used [expr {$first - 1}]]
+               set used $first
+           }
+           if {$ch eq "\""} break
+           incr used
+           append ret [string index $str $used]
+           incr used
+       }
+    }
+    return [list $used $ret]
+}
+
+proc shellsplit {str} {
+    set l {}
+    while {1} {
+       set str [string trimleft $str]
+       if {$str eq {}} break
+       set dq [shelldequote $str]
+       set n [lindex $dq 0]
+       set word [lindex $dq 1]
+       set str [string range $str $n end]
+       lappend l $word
+    }
+    return $l
+}
+
+# Code to implement multiple views
+
+proc newview {ishighlight} {
+    global nextviewnum newviewname newviewperm uifont newishighlight
+    global newviewargs revtreeargs
+
+    set newishighlight $ishighlight
+    set top .gitkview
+    if {[winfo exists $top]} {
+       raise $top
+       return
+    }
+    set newviewname($nextviewnum) "View $nextviewnum"
+    set newviewperm($nextviewnum) 0
+    set newviewargs($nextviewnum) [shellarglist $revtreeargs]
+    vieweditor $top $nextviewnum "Gitk view definition" 
+}
+
+proc editview {} {
+    global curview
+    global viewname viewperm newviewname newviewperm
+    global viewargs newviewargs
+
+    set top .gitkvedit-$curview
+    if {[winfo exists $top]} {
+       raise $top
+       return
+    }
+    set newviewname($curview) $viewname($curview)
+    set newviewperm($curview) $viewperm($curview)
+    set newviewargs($curview) [shellarglist $viewargs($curview)]
+    vieweditor $top $curview "Gitk: edit view $viewname($curview)"
+}
+
+proc vieweditor {top n title} {
+    global newviewname newviewperm viewfiles
+    global uifont
+
+    toplevel $top
+    wm title $top $title
+    label $top.nl -text "Name" -font $uifont
+    entry $top.name -width 20 -textvariable newviewname($n)
+    grid $top.nl $top.name -sticky w -pady 5
+    checkbutton $top.perm -text "Remember this view" -variable newviewperm($n)
+    grid $top.perm - -pady 5 -sticky w
+    message $top.al -aspect 1000 -font $uifont \
+       -text "Commits to include (arguments to git-rev-list):"
+    grid $top.al - -sticky w -pady 5
+    entry $top.args -width 50 -textvariable newviewargs($n) \
+       -background white
+    grid $top.args - -sticky ew -padx 5
+    message $top.l -aspect 1000 -font $uifont \
+       -text "Enter files and directories to include, one per line:"
+    grid $top.l - -sticky w
+    text $top.t -width 40 -height 10 -background white
+    if {[info exists viewfiles($n)]} {
+       foreach f $viewfiles($n) {
+           $top.t insert end $f
+           $top.t insert end "\n"
+       }
+       $top.t delete {end - 1c} end
+       $top.t mark set insert 0.0
+    }
+    grid $top.t - -sticky ew -padx 5
+    frame $top.buts
+    button $top.buts.ok -text "OK" -command [list newviewok $top $n]
+    button $top.buts.can -text "Cancel" -command [list destroy $top]
+    grid $top.buts.ok $top.buts.can
+    grid columnconfigure $top.buts 0 -weight 1 -uniform a
+    grid columnconfigure $top.buts 1 -weight 1 -uniform a
+    grid $top.buts - -pady 10 -sticky ew
+    focus $top.t
+}
+
+proc doviewmenu {m first cmd op args} {
+    set nmenu [$m index end]
+    for {set i $first} {$i <= $nmenu} {incr i} {
+       if {[$m entrycget $i -command] eq $cmd} {
+           eval $m $op $i $args
+           break
+       }
+    }
+}
+
+proc allviewmenus {n op args} {
+    doviewmenu .bar.view 7 [list showview $n] $op $args
+    doviewmenu .bar.view.hl 3 [list addhighlight $n] $op $args
+}
+
+proc newviewok {top n} {
+    global nextviewnum newviewperm newviewname newishighlight
+    global viewname viewfiles viewperm selectedview curview
+    global viewargs newviewargs
+
+    if {[catch {
+       set newargs [shellsplit $newviewargs($n)]
+    } err]} {
+       error_popup "Error in commit selection arguments: $err"
+       wm raise $top
+       focus $top
+       return
+    }
+    set files {}
+    foreach f [split [$top.t get 0.0 end] "\n"] {
+       set ft [string trim $f]
+       if {$ft ne {}} {
+           lappend files $ft
+       }
+    }
+    if {![info exists viewfiles($n)]} {
+       # creating a new view
+       incr nextviewnum
+       set viewname($n) $newviewname($n)
+       set viewperm($n) $newviewperm($n)
+       set viewfiles($n) $files
+       set viewargs($n) $newargs
+       addviewmenu $n
+       if {!$newishighlight} {
+           after idle showview $n
+       } else {
+           after idle addhighlight $n
+       }
+    } else {
+       # editing an existing view
+       set viewperm($n) $newviewperm($n)
+       if {$newviewname($n) ne $viewname($n)} {
+           set viewname($n) $newviewname($n)
+           allviewmenus $n entryconf -label $viewname($n)
+       }
+       if {$files ne $viewfiles($n) || $newargs ne $viewargs($n)} {
+           set viewfiles($n) $files
+           set viewargs($n) $newargs
+           if {$curview == $n} {
+               after idle updatecommits
+           }
+       }
+    }
+    catch {destroy $top}
+}
+
+proc delview {} {
+    global curview viewdata viewperm
+
+    if {$curview == 0} return
+    allviewmenus $curview delete
+    set viewdata($curview) {}
+    set viewperm($curview) 0
+    showview 0
+}
+
+proc addviewmenu {n} {
+    global viewname
+
+    .bar.view add radiobutton -label $viewname($n) \
+       -command [list showview $n] -variable selectedview -value $n
+    .bar.view.hl add radiobutton -label $viewname($n) \
+       -command [list addhighlight $n] -variable selectedhlview -value $n
+}
+
+proc flatten {var} {
+    global $var
+
+    set ret {}
+    foreach i [array names $var] {
+       lappend ret $i [set $var\($i\)]
+    }
+    return $ret
+}
+
+proc unflatten {var l} {
+    global $var
+
+    catch {unset $var}
+    foreach {i v} $l {
+       set $var\($i\) $v
+    }
+}
+
+proc showview {n} {
+    global curview viewdata viewfiles
+    global displayorder parentlist childlist rowidlist rowoffsets
+    global colormap rowtextx commitrow nextcolor canvxmax
+    global numcommits rowrangelist commitlisted idrowranges
+    global selectedline currentid canv canvy0
+    global matchinglines treediffs
+    global pending_select phase
+    global commitidx rowlaidout rowoptim linesegends
+    global commfd nextupdate
+    global selectedview hlview selectedhlview
+    global vparentlist vchildlist vdisporder vcmitlisted
+
+    if {$n == $curview} return
+    set selid {}
+    if {[info exists selectedline]} {
+       set selid $currentid
+       set y [yc $selectedline]
+       set ymax [lindex [$canv cget -scrollregion] 3]
+       set span [$canv yview]
+       set ytop [expr {[lindex $span 0] * $ymax}]
+       set ybot [expr {[lindex $span 1] * $ymax}]
+       if {$ytop < $y && $y < $ybot} {
+           set yscreen [expr {$y - $ytop}]
+       } else {
+           set yscreen [expr {($ybot - $ytop) / 2}]
+       }
+    }
+    unselectline
+    normalline
+    stopfindproc
+    if {$curview >= 0} {
+       set vparentlist($curview) $parentlist
+       set vchildlist($curview) $childlist
+       set vdisporder($curview) $displayorder
+       set vcmitlisted($curview) $commitlisted
+       if {$phase ne {}} {
+           set viewdata($curview) \
+               [list $phase $rowidlist $rowoffsets $rowrangelist \
+                    [flatten idrowranges] [flatten idinlist] \
+                    $rowlaidout $rowoptim $numcommits $linesegends]
+       } elseif {![info exists viewdata($curview)]
+                 || [lindex $viewdata($curview) 0] ne {}} {
+           set viewdata($curview) \
+               [list {} $rowidlist $rowoffsets $rowrangelist]
+       }
+    }
+    catch {unset matchinglines}
+    catch {unset treediffs}
+    clear_display
+
+    set curview $n
+    set selectedview $n
+    set selectedhlview -1
+    .bar.view entryconf 2 -state [expr {$n == 0? "disabled": "normal"}]
+    .bar.view entryconf 3 -state [expr {$n == 0? "disabled": "normal"}]
+    catch {unset hlview}
+    .bar.view.hl entryconf 1 -state disabled
+
+    if {![info exists viewdata($n)]} {
+       set pending_select $selid
+       getcommits
+       return
+    }
+
+    set v $viewdata($n)
+    set phase [lindex $v 0]
+    set displayorder $vdisporder($n)
+    set parentlist $vparentlist($n)
+    set childlist $vchildlist($n)
+    set commitlisted $vcmitlisted($n)
+    set rowidlist [lindex $v 1]
+    set rowoffsets [lindex $v 2]
+    set rowrangelist [lindex $v 3]
+    if {$phase eq {}} {
+       set numcommits [llength $displayorder]
+       catch {unset idrowranges}
+    } else {
+       unflatten idrowranges [lindex $v 4]
+       unflatten idinlist [lindex $v 5]
+       set rowlaidout [lindex $v 6]
+       set rowoptim [lindex $v 7]
+       set numcommits [lindex $v 8]
+       set linesegends [lindex $v 9]
+    }
+
+    catch {unset colormap}
+    catch {unset rowtextx}
+    set nextcolor 0
+    set canvxmax [$canv cget -width]
+    set curview $n
+    set row 0
+    setcanvscroll
+    set yf 0
+    set row 0
+    if {$selid ne {} && [info exists commitrow($n,$selid)]} {
+       set row $commitrow($n,$selid)
+       # try to get the selected row in the same position on the screen
+       set ymax [lindex [$canv cget -scrollregion] 3]
+       set ytop [expr {[yc $row] - $yscreen}]
+       if {$ytop < 0} {
+           set ytop 0
+       }
+       set yf [expr {$ytop * 1.0 / $ymax}]
+    }
+    allcanvs yview moveto $yf
+    drawvisible
+    selectline $row 0
+    if {$phase ne {}} {
+       if {$phase eq "getcommits"} {
+           show_status "Reading commits..."
+       }
+       if {[info exists commfd($n)]} {
+           layoutmore
+       } else {
+           finishcommits
+       }
+    } elseif {$numcommits == 0} {
+       show_status "No commits selected"
+    }
+}
+
+proc addhighlight {n} {
+    global hlview curview viewdata highlighted highlightedrows
+    global selectedhlview
+
+    if {[info exists hlview]} {
+       delhighlight
+    }
+    set hlview $n
+    set selectedhlview $n
+    .bar.view.hl entryconf 1 -state normal
+    set highlighted($n) 0
+    set highlightedrows {}
+    if {$n != $curview && ![info exists viewdata($n)]} {
+       set viewdata($n) [list getcommits {{}} {{}} {} {} {} 0 0 0 {}]
+       set vparentlist($n) {}
+       set vchildlist($n) {}
+       set vdisporder($n) {}
+       set vcmitlisted($n) {}
+       start_rev_list $n
+    } else {
+       highlightmore
+    }
+}
+
+proc delhighlight {} {
+    global hlview highlightedrows canv linehtag mainfont
+    global selectedhlview selectedline
+
+    if {![info exists hlview]} return
+    unset hlview
+    set selectedhlview {}
+    .bar.view.hl entryconf 1 -state disabled
+    foreach l $highlightedrows {
+       $canv itemconf $linehtag($l) -font $mainfont
+       if {$l == $selectedline} {
+           $canv delete secsel
+           set t [eval $canv create rect [$canv bbox $linehtag($l)] \
+                      -outline {{}} -tags secsel \
+                      -fill [$canv cget -selectbackground]]
+           $canv lower $t
+       }
+    }
+}
+
+proc highlightmore {} {
+    global hlview highlighted commitidx highlightedrows linehtag mainfont
+    global displayorder vdisporder curview canv commitrow selectedline
+
+    set font [concat $mainfont bold]
+    set max $commitidx($hlview)
+    if {$hlview == $curview} {
+       set disp $displayorder
+    } else {
+       set disp $vdisporder($hlview)
+    }
+    for {set i $highlighted($hlview)} {$i < $max} {incr i} {
+       set id [lindex $disp $i]
+       if {[info exists commitrow($curview,$id)]} {
+           set row $commitrow($curview,$id)
+           if {[info exists linehtag($row)]} {
+               $canv itemconf $linehtag($row) -font $font
+               lappend highlightedrows $row
+               if {$row == $selectedline} {
+                   $canv delete secsel
+                   set t [eval $canv create rect \
+                              [$canv bbox $linehtag($row)] \
+                              -outline {{}} -tags secsel \
+                              -fill [$canv cget -selectbackground]]
+                   $canv lower $t
+               }
+           }
+       }
+    }
+    set highlighted($hlview) $max
+}
+
+# Graph layout functions
+
 proc shortids {ids} {
     set res {}
     foreach id $ids {
@@ -821,20 +1652,21 @@ proc ntimes {n o} {
 }
 
 proc usedinrange {id l1 l2} {
-    global children commitrow
+    global children commitrow childlist curview
 
-    if {[info exists commitrow($id)]} {
-       set r $commitrow($id)
+    if {[info exists commitrow($curview,$id)]} {
+       set r $commitrow($curview,$id)
        if {$l1 <= $r && $r <= $l2} {
            return [expr {$r - $l1 + 1}]
        }
+       set kids [lindex $childlist $r]
+    } else {
+       set kids $children($curview,$id)
     }
-    foreach c $children($id) {
-       if {[info exists commitrow($c)]} {
-           set r $commitrow($c)
-           if {$l1 <= $r && $r <= $l2} {
-               return [expr {$r - $l1 + 1}]
-           }
+    foreach c $kids {
+       set r $commitrow($curview,$c)
+       if {$l1 <= $r && $r <= $l2} {
+           return [expr {$r - $l1 + 1}]
        }
     }
     return 0
@@ -902,18 +1734,19 @@ proc makeuparrow {oid x y z} {
 proc initlayout {} {
     global rowidlist rowoffsets displayorder commitlisted
     global rowlaidout rowoptim
-    global idinlist rowchk
-    global commitidx numcommits canvxmax canv
+    global idinlist rowchk rowrangelist idrowranges
+    global numcommits canvxmax canv
     global nextcolor
     global parentlist childlist children
+    global colormap rowtextx
+    global linesegends
 
-    set commitidx 0
     set numcommits 0
     set displayorder {}
     set commitlisted {}
     set parentlist {}
     set childlist {}
-    catch {unset children}
+    set rowrangelist {}
     set nextcolor 0
     set rowidlist {{}}
     set rowoffsets {{}}
@@ -922,6 +1755,10 @@ proc initlayout {} {
     set rowlaidout 0
     set rowoptim 0
     set canvxmax [$canv cget -width]
+    catch {unset colormap}
+    catch {unset rowtextx}
+    catch {unset idrowranges}
+    set linesegends {}
 }
 
 proc setcanvscroll {} {
@@ -954,13 +1791,12 @@ proc visiblerows {} {
 
 proc layoutmore {} {
     global rowlaidout rowoptim commitidx numcommits optim_delay
-    global uparrowlen
+    global uparrowlen curview
 
     set row $rowlaidout
-    set rowlaidout [layoutrows $row $commitidx 0]
+    set rowlaidout [layoutrows $row $commitidx($curview) 0]
     set orow [expr {$rowlaidout - $uparrowlen - 1}]
     if {$orow > $rowoptim} {
-       checkcrossings $rowoptim $orow
        optimize_rows $rowoptim 0 $orow
        set rowoptim $orow
     }
@@ -971,8 +1807,8 @@ proc layoutmore {} {
 }
 
 proc showstuff {canshow} {
-    global numcommits
-    global linesegends idrowranges idrangedrawn
+    global numcommits commitrow pending_select selectedline
+    global linesegends idrowranges idrangedrawn curview
 
     if {$numcommits == 0} {
        global phase
@@ -985,17 +1821,16 @@ proc showstuff {canshow} {
     set rows [visiblerows]
     set r0 [lindex $rows 0]
     set r1 [lindex $rows 1]
+    set selrow -1
     for {set r $row} {$r < $canshow} {incr r} {
-       if {[info exists linesegends($r)]} {
-           foreach id $linesegends($r) {
-               set i -1
-               foreach {s e} $idrowranges($id) {
-                   incr i
-                   if {$e ne {} && $e < $numcommits && $s <= $r1 && $e >= $r0
-                       && ![info exists idrangedrawn($id,$i)]} {
-                       drawlineseg $id $i
-                       set idrangedrawn($id,$i) 1
-                   }
+       foreach id [lindex $linesegends [expr {$r+1}]] {
+           set i -1
+           foreach {s e} [rowranges $id] {
+               incr i
+               if {$e ne {} && $e < $numcommits && $s <= $r1 && $e >= $r0
+                   && ![info exists idrangedrawn($id,$i)]} {
+                   drawlineseg $id $i
+                   set idrangedrawn($id,$i) 1
                }
            }
        }
@@ -1007,6 +1842,14 @@ proc showstuff {canshow} {
        drawcmitrow $row
        incr row
     }
+    if {[info exists pending_select] &&
+       [info exists commitrow($curview,$pending_select)] &&
+       $commitrow($curview,$pending_select) < $numcommits} {
+       selectline $commitrow($curview,$pending_select) 1
+    }
+    if {![info exists selectedline] && ![info exists pending_select]} {
+       selectline 0 1
+    }
 }
 
 proc layoutrows {row endrow last} {
@@ -1014,8 +1857,8 @@ proc layoutrows {row endrow last} {
     global uparrowlen downarrowlen maxwidth mingaplen
     global childlist parentlist
     global idrowranges linesegends
-    global commitidx
-    global idinlist rowchk
+    global commitidx curview
+    global idinlist rowchk rowrangelist
 
     set idlist [lindex $rowidlist $row]
     set offs [lindex $rowoffsets $row]
@@ -1030,10 +1873,12 @@ proc layoutrows {row endrow last} {
                lappend oldolds $p
            }
        }
+       set lse {}
        set nev [expr {[llength $idlist] + [llength $newolds]
                       + [llength $oldolds] - $maxwidth + 1}]
        if {$nev > 0} {
-           if {!$last && $row + $uparrowlen + $mingaplen >= $commitidx} break
+           if {!$last &&
+               $row + $uparrowlen + $mingaplen >= $commitidx($curview)} break
            for {set x [llength $idlist]} {[incr x -1] >= 0} {} {
                set i [lindex $idlist $x]
                if {![info exists rowchk($i)] || $row >= $rowchk($i)} {
@@ -1045,7 +1890,7 @@ proc layoutrows {row endrow last} {
                        set offs [incrange $offs $x 1]
                        set idinlist($i) 0
                        set rm1 [expr {$row - 1}]
-                       lappend linesegends($rm1) $i
+                       lappend lse $i
                        lappend idrowranges($i) $rm1
                        if {[incr nev -1] <= 0} break
                        continue
@@ -1056,6 +1901,7 @@ proc layoutrows {row endrow last} {
            lset rowidlist $row $idlist
            lset rowoffsets $row $offs
        }
+       lappend linesegends $lse
        set col [lsearch -exact $idlist $id]
        if {$col < 0} {
            set col [llength $idlist]
@@ -1074,9 +1920,13 @@ proc layoutrows {row endrow last} {
        } else {
            unset idinlist($id)
        }
+       set ranges {}
        if {[info exists idrowranges($id)]} {
-           lappend idrowranges($id) $row
+           set ranges $idrowranges($id)
+           lappend ranges $row
+           unset idrowranges($id)
        }
+       lappend rowrangelist $ranges
        incr row
        set offs [ntimes [llength $idlist] 0]
        set l [llength $newolds]
@@ -1117,29 +1967,28 @@ proc layoutrows {row endrow last} {
 proc addextraid {id row} {
     global displayorder commitrow commitinfo
     global commitidx commitlisted
-    global parentlist childlist children
+    global parentlist childlist children curview
 
-    incr commitidx
+    incr commitidx($curview)
     lappend displayorder $id
     lappend commitlisted 0
     lappend parentlist {}
-    set commitrow($id) $row
+    set commitrow($curview,$id) $row
     readcommit $id
     if {![info exists commitinfo($id)]} {
        set commitinfo($id) {"No commit information available"}
     }
-    if {[info exists children($id)]} {
-       lappend childlist $children($id)
-    } else {
-       lappend childlist {}
+    if {![info exists children($curview,$id)]} {
+       set children($curview,$id) {}
     }
+    lappend childlist $children($curview,$id)
 }
 
 proc layouttail {} {
-    global rowidlist rowoffsets idinlist commitidx
-    global idrowranges
+    global rowidlist rowoffsets idinlist commitidx curview
+    global idrowranges rowrangelist
 
-    set row $commitidx
+    set row $commitidx($curview)
     set idlist [lindex $rowidlist $row]
     while {$idlist ne {}} {
        set col [expr {[llength $idlist] - 1}]
@@ -1147,6 +1996,8 @@ proc layouttail {} {
        addextraid $id $row
        unset idinlist($id)
        lappend idrowranges($id) $row
+       lappend rowrangelist $idrowranges($id)
+       unset idrowranges($id)
        incr row
        set offs [ntimes $col 0]
        set idlist [lreplace $idlist $col $col]
@@ -1160,6 +2011,8 @@ proc layouttail {} {
        lset rowoffsets $row 0
        makeuparrow $id 0 $row 0
        lappend idrowranges($id) $row
+       lappend rowrangelist $idrowranges($id)
+       unset idrowranges($id)
        incr row
        lappend rowidlist {}
        lappend rowoffsets {}
@@ -1176,7 +2029,7 @@ proc insert_pad {row col npad} {
 }
 
 proc optimize_rows {row col endrow} {
-    global rowidlist rowoffsets idrowranges linesegends displayorder
+    global rowidlist rowoffsets idrowranges displayorder
 
     for {} {$row < $endrow} {incr row} {
        set idlist [lindex $rowidlist $row]
@@ -1195,8 +2048,8 @@ proc optimize_rows {row col endrow} {
            set z0 [lindex $rowoffsets $y0 $x0]
            if {$z0 eq {}} {
                set id [lindex $idlist $col]
-               if {[info exists idrowranges($id)] &&
-                   $y0 > [lindex $idrowranges($id) 0]} {
+               set ranges [rowranges $id]
+               if {$ranges ne {} && $y0 > [lindex $ranges 0]} {
                    set isarrow 1
                }
            }
@@ -1254,8 +2107,8 @@ proc optimize_rows {row col endrow} {
                if {$o eq {}} {
                    # check if this is the link to the first child
                    set id [lindex $idlist $col]
-                   if {[info exists idrowranges($id)] &&
-                       $row == [lindex $idrowranges($id) 0]} {
+                   set ranges [rowranges $id]
+                   if {$ranges ne {} && $row == [lindex $ranges 0]} {
                        # it is, work out offset to child
                        set y0 [expr {$row - 1}]
                        set id [lindex $displayorder $y0]
@@ -1309,13 +2162,36 @@ proc linewidth {id} {
     return $wid
 }
 
+proc rowranges {id} {
+    global phase idrowranges commitrow rowlaidout rowrangelist curview
+
+    set ranges {}
+    if {$phase eq {} ||
+       ([info exists commitrow($curview,$id)]
+        && $commitrow($curview,$id) < $rowlaidout)} {
+       set ranges [lindex $rowrangelist $commitrow($curview,$id)]
+    } elseif {[info exists idrowranges($id)]} {
+       set ranges $idrowranges($id)
+    }
+    return $ranges
+}
+
 proc drawlineseg {id i} {
-    global rowoffsets rowidlist idrowranges
+    global rowoffsets rowidlist
     global displayorder
     global canv colormap linespc
+    global numcommits commitrow curview
 
-    set startrow [lindex $idrowranges($id) [expr {2 * $i}]]
-    set row [lindex $idrowranges($id) [expr {2 * $i + 1}]]
+    set ranges [rowranges $id]
+    set downarrow 1
+    if {[info exists commitrow($curview,$id)]
+       && $commitrow($curview,$id) < $numcommits} {
+       set downarrow [expr {$i < [llength $ranges] / 2 - 1}]
+    } else {
+       set downarrow 1
+    }
+    set startrow [lindex $ranges [expr {2 * $i}]]
+    set row [lindex $ranges [expr {2 * $i + 1}]]
     if {$startrow == $row} return
     assigncolor $id
     set coords {}
@@ -1359,8 +2235,7 @@ proc drawlineseg {id i} {
        }
     }
     if {[llength $coords] < 4} return
-    set last [expr {[llength $idrowranges($id)] / 2 - 1}]
-    if {$i < $last} {
+    if {$downarrow} {
        # This line has an arrow at the lower end: check if the arrow is
        # on a diagonal segment, and if so, work around the Tk 8.4
        # refusal to draw arrows on diagonal lines.
@@ -1380,7 +2255,7 @@ proc drawlineseg {id i} {
            }
        }
     }
-    set arrow [expr {2 * ($i > 0) + ($i < $last)}]
+    set arrow [expr {2 * ($i > 0) + $downarrow}]
     set arrow [lindex {none first last both} $arrow]
     set t [$canv create line $coords -width [linewidth $id] \
               -fill $colormap($id) -tags lines.$id -arrow $arrow]
@@ -1389,7 +2264,7 @@ proc drawlineseg {id i} {
 }
 
 proc drawparentlinks {id row col olds} {
-    global rowidlist canv colormap idrowranges
+    global rowidlist canv colormap
 
     set row2 [expr {$row + 1}]
     set x [xc $row $col]
@@ -1408,9 +2283,9 @@ proc drawparentlinks {id row col olds} {
        if {$x2 > $rmx} {
            set rmx $x2
        }
-       if {[info exists idrowranges($p)] &&
-           $row2 == [lindex $idrowranges($p) 0] &&
-           $row2 < [lindex $idrowranges($p) 1]} {
+       set ranges [rowranges $p]
+       if {$ranges ne {} && $row2 == [lindex $ranges 0]
+           && $row2 < [lindex $ranges 1]} {
            # drawlineseg will do this one for us
            continue
        }
@@ -1433,19 +2308,19 @@ proc drawparentlinks {id row col olds} {
 
 proc drawlines {id} {
     global colormap canv
-    global idrowranges idrangedrawn
-    global childlist iddrawn commitrow rowidlist
+    global idrangedrawn
+    global children iddrawn commitrow rowidlist curview
 
     $canv delete lines.$id
-    set nr [expr {[llength $idrowranges($id)] / 2}]
+    set nr [expr {[llength [rowranges $id]] / 2}]
     for {set i 0} {$i < $nr} {incr i} {
        if {[info exists idrangedrawn($id,$i)]} {
            drawlineseg $id $i
        }
     }
-    foreach child [lindex $childlist $commitrow($id)] {
+    foreach child $children($curview,$id) {
        if {[info exists iddrawn($child)]} {
-           set row $commitrow($child)
+           set row $commitrow($curview,$child)
            set col [lsearch -exact [lindex $rowidlist $row] $child]
            if {$col >= 0} {
                drawparentlinks $child $row $col [list $id]
@@ -1459,7 +2334,8 @@ proc drawcmittext {id row col rmx} {
     global commitlisted commitinfo rowidlist
     global rowtextx idpos idtags idheads idotherrefs
     global linehtag linentag linedtag
-    global mainfont namefont canvxmax
+    global mainfont canvxmax
+    global hlview commitrow highlightedrows
 
     set ofill [expr {[lindex $commitlisted $row]? "blue": "white"}]
     set x [xc $row $col]
@@ -1484,11 +2360,16 @@ proc drawcmittext {id row col rmx} {
     set name [lindex $commitinfo($id) 1]
     set date [lindex $commitinfo($id) 2]
     set date [formatdate $date]
+    set font $mainfont
+    if {[info exists hlview] && [info exists commitrow($hlview,$id)]} {
+       lappend font bold
+       lappend highlightedrows $row
+    }
     set linehtag($row) [$canv create text $xt $y -anchor w \
-                           -text $headline -font $mainfont ]
+                           -text $headline -font $font]
     $canv bind $linehtag($row) <Button-3> "rowmenu %X %Y $id"
     set linentag($row) [$canv2 create text 3 $y -anchor w \
-                           -text $name -font $namefont]
+                           -text $name -font $mainfont]
     set linedtag($row) [$canv3 create text 3 $y -anchor w \
                            -text $date -font $mainfont]
     set xr [expr {$xt + [font measure $mainfont $headline]}]
@@ -1500,14 +2381,14 @@ proc drawcmittext {id row col rmx} {
 
 proc drawcmitrow {row} {
     global displayorder rowidlist
-    global idrowranges idrangedrawn iddrawn
+    global idrangedrawn iddrawn
     global commitinfo parentlist numcommits
 
     if {$row >= $numcommits} return
     foreach id [lindex $rowidlist $row] {
-       if {![info exists idrowranges($id)]} continue
+       if {$id eq {}} continue
        set i -1
-       foreach {s e} $idrowranges($id) {
+       foreach {s e} [rowranges $id] {
            incr i
            if {$row < $s} continue
            if {$e eq {}} break
@@ -1576,62 +2457,90 @@ proc clear_display {} {
     catch {unset idrangedrawn}
 }
 
+proc findcrossings {id} {
+    global rowidlist parentlist numcommits rowoffsets displayorder
+
+    set cross {}
+    set ccross {}
+    foreach {s e} [rowranges $id] {
+       if {$e >= $numcommits} {
+           set e [expr {$numcommits - 1}]
+       }
+       if {$e <= $s} continue
+       set x [lsearch -exact [lindex $rowidlist $e] $id]
+       if {$x < 0} {
+           puts "findcrossings: oops, no [shortids $id] in row $e"
+           continue
+       }
+       for {set row $e} {[incr row -1] >= $s} {} {
+           set olds [lindex $parentlist $row]
+           set kid [lindex $displayorder $row]
+           set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
+           if {$kidx < 0} continue
+           set nextrow [lindex $rowidlist [expr {$row + 1}]]
+           foreach p $olds {
+               set px [lsearch -exact $nextrow $p]
+               if {$px < 0} continue
+               if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
+                   if {[lsearch -exact $ccross $p] >= 0} continue
+                   if {$x == $px + ($kidx < $px? -1: 1)} {
+                       lappend ccross $p
+                   } elseif {[lsearch -exact $cross $p] < 0} {
+                       lappend cross $p
+                   }
+               }
+           }
+           set inc [lindex $rowoffsets $row $x]
+           if {$inc eq {}} break
+           incr x $inc
+       }
+    }
+    return [concat $ccross {{}} $cross]
+}
+
 proc assigncolor {id} {
     global colormap colors nextcolor
-    global commitrow parentlist children childlist
-    global cornercrossings crossings
+    global commitrow parentlist children children curview
 
     if {[info exists colormap($id)]} return
     set ncolors [llength $colors]
-    if {[info exists commitrow($id)]} {
-       set kids [lindex $childlist $commitrow($id)]
-    } elseif {[info exists children($id)]} {
-       set kids $children($id)
+    if {[info exists children($curview,$id)]} {
+       set kids $children($curview,$id)
     } else {
        set kids {}
     }
     if {[llength $kids] == 1} {
        set child [lindex $kids 0]
        if {[info exists colormap($child)]
-           && [llength [lindex $parentlist $commitrow($child)]] == 1} {
+           && [llength [lindex $parentlist $commitrow($curview,$child)]] == 1} {
            set colormap($id) $colormap($child)
            return
        }
     }
     set badcolors {}
-    if {[info exists cornercrossings($id)]} {
-       foreach x $cornercrossings($id) {
-           if {[info exists colormap($x)]
-               && [lsearch -exact $badcolors $colormap($x)] < 0} {
-               lappend badcolors $colormap($x)
-           }
+    set origbad {}
+    foreach x [findcrossings $id] {
+       if {$x eq {}} {
+           # delimiter between corner crossings and other crossings
+           if {[llength $badcolors] >= $ncolors - 1} break
+           set origbad $badcolors
        }
-       if {[llength $badcolors] >= $ncolors} {
-           set badcolors {}
+       if {[info exists colormap($x)]
+           && [lsearch -exact $badcolors $colormap($x)] < 0} {
+           lappend badcolors $colormap($x)
        }
     }
-    set origbad $badcolors
-    if {[llength $badcolors] < $ncolors - 1} {
-       if {[info exists crossings($id)]} {
-           foreach x $crossings($id) {
-               if {[info exists colormap($x)]
-                   && [lsearch -exact $badcolors $colormap($x)] < 0} {
-                   lappend badcolors $colormap($x)
-               }
-           }
-           if {[llength $badcolors] >= $ncolors} {
-               set badcolors $origbad
-           }
-       }
-       set origbad $badcolors
+    if {[llength $badcolors] >= $ncolors} {
+       set badcolors $origbad
     }
+    set origbad $badcolors
     if {[llength $badcolors] < $ncolors - 1} {
        foreach child $kids {
            if {[info exists colormap($child)]
                && [lsearch -exact $badcolors $colormap($child)] < 0} {
                lappend badcolors $colormap($child)
            }
-           foreach p [lindex $parentlist $commitrow($child)] {
+           foreach p [lindex $parentlist $commitrow($curview,$child)] {
                if {[info exists colormap($p)]
                    && [lsearch -exact $badcolors $colormap($p)] < 0} {
                    lappend badcolors $colormap($p)
@@ -1664,7 +2573,7 @@ proc bindline {t id} {
 proc drawtags {id x xt y1} {
     global idtags idheads idotherrefs
     global linespc lthickness
-    global canv mainfont commitrow rowtextx
+    global canv mainfont commitrow rowtextx curview
 
     set marks {}
     set ntags 0
@@ -1707,7 +2616,7 @@ proc drawtags {id x xt y1} {
                       $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
                       -width 1 -outline black -fill yellow -tags tag.$id]
            $canv bind $t <1> [list showtag $tag 1]
-           set rowtextx($commitrow($id)) [expr {$xr + $linespc}]
+           set rowtextx($commitrow($curview,$id)) [expr {$xr + $linespc}]
        } else {
            # draw a head or other ref
            if {[incr nheads -1] >= 0} {
@@ -1718,6 +2627,14 @@ proc drawtags {id x xt y1} {
            set xl [expr {$xl - $delta/2}]
            $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
                -width 1 -outline black -fill $col -tags tag.$id
+           if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
+               set rwid [font measure $mainfont $remoteprefix]
+               set xi [expr {$x + 1}]
+               set yti [expr {$yt + 1}]
+               set xri [expr {$x + $rwid}]
+               $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
+                       -width 0 -fill "#ffddaa" -tags tag.$id
+           }
        }
        set t [$canv create text $xl $y1 -anchor w -text $tag \
                   -font $mainfont -tags tag.$id]
@@ -1728,55 +2645,6 @@ proc drawtags {id x xt y1} {
     return $xt
 }
 
-proc checkcrossings {row endrow} {
-    global displayorder parentlist rowidlist
-
-    for {} {$row < $endrow} {incr row} {
-       set id [lindex $displayorder $row]
-       set i [lsearch -exact [lindex $rowidlist $row] $id]
-       if {$i < 0} continue
-       set idlist [lindex $rowidlist [expr {$row+1}]]
-       foreach p [lindex $parentlist $row] {
-           set j [lsearch -exact $idlist $p]
-           if {$j > 0} {
-               if {$j < $i - 1} {
-                   notecrossings $row $p $j $i [expr {$j+1}]
-               } elseif {$j > $i + 1} {
-                   notecrossings $row $p $i $j [expr {$j-1}]
-               }
-           }
-       }
-    }
-}
-
-proc notecrossings {row id lo hi corner} {
-    global rowidlist crossings cornercrossings
-
-    for {set i $lo} {[incr i] < $hi} {} {
-       set p [lindex [lindex $rowidlist $row] $i]
-       if {$p == {}} continue
-       if {$i == $corner} {
-           if {![info exists cornercrossings($id)]
-               || [lsearch -exact $cornercrossings($id) $p] < 0} {
-               lappend cornercrossings($id) $p
-           }
-           if {![info exists cornercrossings($p)]
-               || [lsearch -exact $cornercrossings($p) $id] < 0} {
-               lappend cornercrossings($p) $id
-           }
-       } else {
-           if {![info exists crossings($id)]
-               || [lsearch -exact $crossings($id) $p] < 0} {
-               lappend crossings($id) $p
-           }
-           if {![info exists crossings($p)]
-               || [lsearch -exact $crossings($p) $id] < 0} {
-               lappend crossings($p) $id
-           }
-       }
-    }
-}
-
 proc xcoord {i level ln} {
     global canvx0 xspc1 xspc2
 
@@ -1789,23 +2657,25 @@ proc xcoord {i level ln} {
     return $x
 }
 
+proc show_status {msg} {
+    global canv mainfont
+
+    clear_display
+    $canv create text 3 3 -anchor nw -text $msg -font $mainfont -tags textitems
+}
+
 proc finishcommits {} {
-    global commitidx phase
+    global commitidx phase curview
     global canv mainfont ctext maincursor textcursor
-    global findinprogress
+    global findinprogress pending_select
 
-    if {$commitidx > 0} {
+    if {$commitidx($curview) > 0} {
        drawrest
     } else {
-       $canv delete all
-       $canv create text 3 3 -anchor nw -text "No commits selected" \
-           -font $mainfont -tags textitems
-    }
-    if {![info exists findinprogress]} {
-       . config -cursor $maincursor
-       settextcursor $textcursor
+       show_status "No commits selected"
     }
     set phase {}
+    catch {unset pending_select}
 }
 
 # Don't change the text pane cursor if it is currently the hand cursor,
@@ -1819,17 +2689,41 @@ proc settextcursor {c} {
     set curtextcursor $c
 }
 
+proc nowbusy {what} {
+    global isbusy
+
+    if {[array names isbusy] eq {}} {
+       . config -cursor watch
+       settextcursor watch
+    }
+    set isbusy($what) 1
+}
+
+proc notbusy {what} {
+    global isbusy maincursor textcursor
+
+    catch {unset isbusy($what)}
+    if {[array names isbusy] eq {}} {
+       . config -cursor $maincursor
+       settextcursor $textcursor
+    }
+}
+
 proc drawrest {} {
     global numcommits
     global startmsecs
     global canvy0 numcommits linespc
-    global rowlaidout commitidx
+    global rowlaidout commitidx curview
+    global pending_select
 
     set row $rowlaidout
-    layoutrows $rowlaidout $commitidx 1
+    layoutrows $rowlaidout $commitidx($curview) 1
     layouttail
-    optimize_rows $row 0 $commitidx
-    showstuff $commitidx
+    optimize_rows $row 0 $commitidx($curview)
+    showstuff $commitidx($curview)
+    if {[info exists pending_select]} {
+       selectline 0 1
+    }
 
     set drawmsecs [expr {[clock clicks -milliseconds] - $startmsecs}]
     #puts "overall $drawmsecs ms for $numcommits commits"
@@ -1858,7 +2752,7 @@ proc findmatches {f} {
 proc dofind {} {
     global findtype findloc findstring markedmatches commitinfo
     global numcommits displayorder linehtag linentag linedtag
-    global mainfont namefont canv canv2 canv3 selectedline
+    global mainfont canv canv2 canv3 selectedline
     global matchinglines foundstring foundstrlen matchstring
     global commitdata
 
@@ -1919,7 +2813,7 @@ proc dofind {} {
                markmatches $canv $l $f $linehtag($l) $matches $mainfont
            } elseif {$ty == "Author"} {
                drawcmitrow $l
-               markmatches $canv2 $l $f $linentag($l) $matches $namefont
+               markmatches $canv2 $l $f $linentag($l) $matches $mainfont
            } elseif {$ty == "Date"} {
                drawcmitrow $l
                markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
@@ -2017,13 +2911,8 @@ proc stopfindproc {{done 0}} {
        catch {close $findprocfile}
        unset findprocpid
     }
-    if {[info exists findinprogress]} {
-       unset findinprogress
-       if {$phase != "incrdraw"} {
-           . config -cursor $maincursor
-           settextcursor $textcursor
-       }
-    }
+    catch {unset findinprogress}
+    notbusy find
 }
 
 proc findpatches {} {
@@ -2063,14 +2952,13 @@ proc findpatches {} {
     fconfigure $f -blocking 0
     fileevent $f readable readfindproc
     set finddidsel 0
-    . config -cursor watch
-    settextcursor watch
+    nowbusy find
     set findinprogress 1
 }
 
 proc readfindproc {} {
     global findprocfile finddidsel
-    global commitrow matchinglines findinsertpos
+    global commitrow matchinglines findinsertpos curview
 
     set n [gets $findprocfile line]
     if {$n < 0} {
@@ -2087,11 +2975,11 @@ proc readfindproc {} {
        stopfindproc
        return
     }
-    if {![info exists commitrow($id)]} {
+    if {![info exists commitrow($curview,$id)]} {
        puts stderr "spurious id: $id"
        return
     }
-    set l $commitrow($id)
+    set l $commitrow($curview,$id)
     insertmatch $l $id
 }
 
@@ -2165,8 +3053,7 @@ proc findfiles {} {
     set finddidsel 0
     set findinsertpos end
     set id [lindex $displayorder $l]
-    . config -cursor watch
-    settextcursor watch
+    nowbusy find
     set findinprogress 1
     findcont
     update
@@ -2336,7 +3223,7 @@ proc commit_descriptor {p} {
 # append some text to the ctext widget, and make any SHA1 ID
 # that we know about be a clickable link.
 proc appendwithlinks {text} {
-    global ctext commitrow linknum
+    global ctext commitrow linknum curview
 
     set start [$ctext index "end - 1c"]
     $ctext insert end $text
@@ -2346,11 +3233,12 @@ proc appendwithlinks {text} {
        set s [lindex $l 0]
        set e [lindex $l 1]
        set linkid [string range $text $s $e]
-       if {![info exists commitrow($linkid)]} continue
+       if {![info exists commitrow($curview,$linkid)]} continue
        incr e
        $ctext tag add link "$start + $s c" "$start + $e c"
        $ctext tag add link$linknum "$start + $s c" "$start + $e c"
-       $ctext tag bind link$linknum <1> [list selectline $commitrow($linkid) 1]
+       $ctext tag bind link$linknum <1> \
+           [list selectline $commitrow($curview,$linkid) 1]
        incr linknum
     }
     $ctext tag conf link -foreground blue -underline 1
@@ -2378,10 +3266,12 @@ proc selectline {l isnew} {
     global canv canv2 canv3 ctext commitinfo selectedline
     global displayorder linehtag linentag linedtag
     global canvy0 linespc parentlist childlist
-    global cflist currentid sha1entry
+    global currentid sha1entry
     global commentend idtags linknum
-    global mergemax numcommits
+    global mergemax numcommits pending_select
+    global cmitmode
 
+    catch {unset pending_select}
     $canv delete hover
     normalline
     if {$l < 0 || $l >= $numcommits} return
@@ -2451,8 +3341,6 @@ proc selectline {l isnew} {
     $ctext conf -state normal
     $ctext delete 0.0 end
     set linknum 0
-    $ctext mark set fmark.0 0.0
-    $ctext mark gravity fmark.0 left
     set info $commitinfo($id)
     set date [formatdate [lindex $info 2]]
     $ctext insert end "Author: [lindex $info 1]  $date\n"
@@ -2500,9 +3388,10 @@ proc selectline {l isnew} {
     $ctext conf -state disabled
     set commentend [$ctext index "end - 1c"]
 
-    $cflist delete 0 end
-    $cflist insert end "Comments"
-    if {[llength $olds] <= 1} {
+    init_flist "Comments"
+    if {$cmitmode eq "tree"} {
+       gettree $id
+    } elseif {[llength $olds] <= 1} {
        startdiff $id
     } else {
        mergediff $id $l
@@ -2549,24 +3438,34 @@ proc selnextpage {dir} {
 }
 
 proc unselectline {} {
-    global selectedline
+    global selectedline currentid
 
     catch {unset selectedline}
+    catch {unset currentid}
     allcanvs delete secsel
 }
 
+proc reselectline {} {
+    global selectedline
+
+    if {[info exists selectedline]} {
+       selectline $selectedline 0
+    }
+}
+
 proc addtohistory {cmd} {
-    global history historyindex
+    global history historyindex curview
 
+    set elt [list $curview $cmd]
     if {$historyindex > 0
-       && [lindex $history [expr {$historyindex - 1}]] == $cmd} {
+       && [lindex $history [expr {$historyindex - 1}]] == $elt} {
        return
     }
 
     if {$historyindex < [llength $history]} {
-       set history [lreplace $history $historyindex end $cmd]
+       set history [lreplace $history $historyindex end $elt]
     } else {
-       lappend history $cmd
+       lappend history $elt
     }
     incr historyindex
     if {$historyindex > 1} {
@@ -2577,13 +3476,23 @@ proc addtohistory {cmd} {
     .ctop.top.bar.rightbut conf -state disabled
 }
 
+proc godo {elt} {
+    global curview
+
+    set view [lindex $elt 0]
+    set cmd [lindex $elt 1]
+    if {$curview != $view} {
+       showview $view
+    }
+    eval $cmd
+}
+
 proc goback {} {
     global history historyindex
 
     if {$historyindex > 1} {
        incr historyindex -1
-       set cmd [lindex $history [expr {$historyindex - 1}]]
-       eval $cmd
+       godo [lindex $history [expr {$historyindex - 1}]]
        .ctop.top.bar.rightbut conf -state normal
     }
     if {$historyindex <= 1} {
@@ -2597,7 +3506,7 @@ proc goforw {} {
     if {$historyindex < [llength $history]} {
        set cmd [lindex $history $historyindex]
        incr historyindex
-       eval $cmd
+       godo $cmd
        .ctop.top.bar.leftbut conf -state normal
     }
     if {$historyindex >= [llength $history]} {
@@ -2605,14 +3514,101 @@ proc goforw {} {
     }
 }
 
+proc gettree {id} {
+    global treefilelist treeidlist diffids diffmergeid treepending
+
+    set diffids $id
+    catch {unset diffmergeid}
+    if {![info exists treefilelist($id)]} {
+       if {![info exists treepending]} {
+           if {[catch {set gtf [open [concat | git-ls-tree -r $id] r]}]} {
+               return
+           }
+           set treepending $id
+           set treefilelist($id) {}
+           set treeidlist($id) {}
+           fconfigure $gtf -blocking 0
+           fileevent $gtf readable [list gettreeline $gtf $id]
+       }
+    } else {
+       setfilelist $id
+    }
+}
+
+proc gettreeline {gtf id} {
+    global treefilelist treeidlist treepending cmitmode diffids
+
+    while {[gets $gtf line] >= 0} {
+       if {[lindex $line 1] ne "blob"} continue
+       set sha1 [lindex $line 2]
+       set fname [lindex $line 3]
+       lappend treefilelist($id) $fname
+       lappend treeidlist($id) $sha1
+    }
+    if {![eof $gtf]} return
+    close $gtf
+    unset treepending
+    if {$cmitmode ne "tree"} {
+       if {![info exists diffmergeid]} {
+           gettreediffs $diffids
+       }
+    } elseif {$id ne $diffids} {
+       gettree $diffids
+    } else {
+       setfilelist $id
+    }
+}
+
+proc showfile {f} {
+    global treefilelist treeidlist diffids
+    global ctext commentend
+
+    set i [lsearch -exact $treefilelist($diffids) $f]
+    if {$i < 0} {
+       puts "oops, $f not in list for id $diffids"
+       return
+    }
+    set blob [lindex $treeidlist($diffids) $i]
+    if {[catch {set bf [open [concat | git-cat-file blob $blob] r]} err]} {
+       puts "oops, error reading blob $blob: $err"
+       return
+    }
+    fconfigure $bf -blocking 0
+    fileevent $bf readable [list getblobline $bf $diffids]
+    $ctext config -state normal
+    $ctext delete $commentend end
+    $ctext insert end "\n"
+    $ctext insert end "$f\n" filesep
+    $ctext config -state disabled
+    $ctext yview $commentend
+}
+
+proc getblobline {bf id} {
+    global diffids cmitmode ctext
+
+    if {$id ne $diffids || $cmitmode ne "tree"} {
+       catch {close $bf}
+       return
+    }
+    $ctext config -state normal
+    while {[gets $bf line] >= 0} {
+       $ctext insert end "$line\n"
+    }
+    if {[eof $bf]} {
+       # delete last newline
+       $ctext delete "end - 2c" "end - 1c"
+       close $bf
+    }
+    $ctext config -state disabled
+}
+
 proc mergediff {id l} {
     global diffmergeid diffopts mdifffd
-    global difffilestart diffids
+    global diffids
     global parentlist
 
     set diffmergeid $id
     set diffids $id
-    catch {unset difffilestart}
     # this doesn't seem to actually affect anything...
     set env(GIT_DIFF_OPTS) $diffopts
     set cmd [concat | git-diff-tree --no-commit-id --cc $id]
@@ -2647,11 +3643,8 @@ proc getmergediffline {mdf id np} {
        # start of a new file
        $ctext insert end "\n"
        set here [$ctext index "end - 1c"]
-       set i [$cflist index end]
-       $ctext mark set fmark.$i $here
-       $ctext mark gravity fmark.$i left
-       set difffilestart([expr {$i-1}]) $here
-       $cflist insert end $fname
+       lappend difffilestart $here
+       add_flist [list $fname]
        set l [expr {(78 - [string length $fname]) / 2}]
        set pad [string range "----------------------------------------" 1 $l]
        $ctext insert end "$pad $fname $pad\n" filesep
@@ -2721,9 +3714,7 @@ proc startdiff {ids} {
 
 proc addtocflist {ids} {
     global treediffs cflist
-    foreach f $treediffs($ids) {
-       $cflist insert end $f
-    }
+    add_flist $treediffs($ids)
     getblobdiffs $ids
 }
 
@@ -2740,6 +3731,7 @@ proc gettreediffs {ids} {
 
 proc gettreediffline {gdtf ids} {
     global treediff treediffs treepending diffids diffmergeid
+    global cmitmode
 
     set n [gets $gdtf line]
     if {$n < 0} {
@@ -2747,7 +3739,9 @@ proc gettreediffline {gdtf ids} {
        close $gdtf
        set treediffs($ids) $treediff
        unset treepending
-       if {$ids != $diffids} {
+       if {$cmitmode eq "tree"} {
+           gettree $diffids
+       } elseif {$ids != $diffids} {
            if {![info exists diffmergeid]} {
                gettreediffs $diffids
            }
@@ -2762,7 +3756,7 @@ proc gettreediffline {gdtf ids} {
 
 proc getblobdiffs {ids} {
     global diffopts blobdifffd diffids env curdifftag curtagstart
-    global difffilestart nextupdate diffinhdr treediffs
+    global nextupdate diffinhdr treediffs
 
     set env(GIT_DIFF_OPTS) $diffopts
     set cmd [concat | git-diff-tree --no-commit-id -r -p -C $ids]
@@ -2775,11 +3769,23 @@ proc getblobdiffs {ids} {
     set blobdifffd($ids) $bdf
     set curdifftag Comments
     set curtagstart 0.0
-    catch {unset difffilestart}
     fileevent $bdf readable [list getblobdiffline $bdf $diffids]
     set nextupdate [expr {[clock clicks -milliseconds] + 100}]
 }
 
+proc setinlist {var i val} {
+    global $var
+
+    while {[llength [set $var]] < $i} {
+       lappend $var {}
+    }
+    if {[llength [set $var]] == $i} {
+       lappend $var $val
+    } else {
+       lset $var $i $val
+    }
+}
+
 proc getblobdiffline {bdf ids} {
     global diffids blobdifffd ctext curdifftag curtagstart
     global diffnexthead diffnextnote difffilestart
@@ -2803,23 +3809,17 @@ proc getblobdiffline {bdf ids} {
        # start of a new file
        $ctext insert end "\n"
        $ctext tag add $curdifftag $curtagstart end
-       set curtagstart [$ctext index "end - 1c"]
-       set header $newname
        set here [$ctext index "end - 1c"]
-       set i [lsearch -exact $treediffs($diffids) $fname]
+       set curtagstart $here
+       set header $newname
+       set i [lsearch -exact $treediffs($ids) $fname]
        if {$i >= 0} {
-           set difffilestart($i) $here
-           incr i
-           $ctext mark set fmark.$i $here
-           $ctext mark gravity fmark.$i left
+           setinlist difffilestart $i $here
        }
-       if {$newname != $fname} {
-           set i [lsearch -exact $treediffs($diffids) $newname]
+       if {$newname ne $fname} {
+           set i [lsearch -exact $treediffs($ids) $newname]
            if {$i >= 0} {
-               set difffilestart($i) $here
-               incr i
-               $ctext mark set fmark.$i $here
-               $ctext mark gravity fmark.$i left
+               setinlist difffilestart $i $here
            }
        }
        set curdifftag "f:$fname"
@@ -2869,26 +3869,11 @@ proc getblobdiffline {bdf ids} {
 proc nextfile {} {
     global difffilestart ctext
     set here [$ctext index @0,0]
-    for {set i 0} {[info exists difffilestart($i)]} {incr i} {
-       if {[$ctext compare $difffilestart($i) > $here]} {
-           if {![info exists pos]
-               || [$ctext compare $difffilestart($i) < $pos]} {
-               set pos $difffilestart($i)
-           }
+    foreach loc $difffilestart {
+       if {[$ctext compare $loc > $here]} {
+           $ctext yview $loc
        }
     }
-    if {[info exists pos]} {
-       $ctext yview $pos
-    }
-}
-
-proc listboxsel {} {
-    global ctext cflist currentid
-    if {![info exists currentid]} return
-    set sel [lsort [$cflist curselection]]
-    if {$sel eq {}} return
-    set first [lindex $sel 0]
-    catch {$ctext yview fmark.$first}
 }
 
 proc setcoords {} {
@@ -2921,11 +3906,10 @@ proc redisplay {} {
 }
 
 proc incrfont {inc} {
-    global mainfont namefont textfont ctext canv phase
+    global mainfont textfont ctext canv phase
     global stopped entries
     unmarkmatches
     set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
-    set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
     set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
     setcoords
     $ctext conf -font $textfont
@@ -2933,7 +3917,7 @@ proc incrfont {inc} {
     foreach e $entries {
        $e conf -font $mainfont
     }
-    if {$phase == "getcommits"} {
+    if {$phase eq "getcommits"} {
        $canv itemconf textitems -font $mainfont
     }
     redisplay
@@ -2964,7 +3948,7 @@ proc sha1change {n1 n2 op} {
 
 proc gotocommit {} {
     global sha1string currentid commitrow tagids headids
-    global displayorder numcommits
+    global displayorder numcommits curview
 
     if {$sha1string == {}
        || ([info exists currentid] && $sha1string == $currentid)} return
@@ -2990,8 +3974,8 @@ proc gotocommit {} {
            }
        }
     }
-    if {[info exists commitrow($id)]} {
-       selectline $commitrow($id) 1
+    if {[info exists commitrow($curview,$id)]} {
+       selectline $commitrow($curview,$id) 1
        return
     }
     if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
@@ -3066,12 +4050,13 @@ proc linehover {} {
 }
 
 proc clickisonarrow {id y} {
-    global lthickness idrowranges
+    global lthickness
 
+    set ranges [rowranges $id]
     set thresh [expr {2 * $lthickness + 6}]
-    set n [expr {[llength $idrowranges($id)] - 1}]
+    set n [expr {[llength $ranges] - 1}]
     for {set i 1} {$i < $n} {incr i} {
-       set row [lindex $idrowranges($id) $i]
+       set row [lindex $ranges $i]
        if {abs([yc $row] - $y) < $thresh} {
            return $i
        }
@@ -3080,11 +4065,11 @@ proc clickisonarrow {id y} {
 }
 
 proc arrowjump {id n y} {
-    global idrowranges canv
+    global canv
 
     # 1 <-> 2, 3 <-> 4, etc...
     set n [expr {(($n - 1) ^ 1) + 1}]
-    set row [lindex $idrowranges($id) $n]
+    set row [lindex [rowranges $id] $n]
     set yt [yc $row]
     set ymax [lindex [$canv cget -scrollregion] 3]
     if {$ymax eq {} || $ymax <= 0} return
@@ -3098,7 +4083,7 @@ proc arrowjump {id n y} {
 }
 
 proc lineclick {x y id isnew} {
-    global ctext commitinfo childlist commitrow cflist canv thickerline
+    global ctext commitinfo children canv thickerline curview
 
     if {![info exists commitinfo($id)] && ![getcommit $id]} return
     unmarkmatches
@@ -3137,7 +4122,7 @@ proc lineclick {x y id isnew} {
     $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
     set date [formatdate [lindex $info 2]]
     $ctext insert end "\tDate:\t$date\n"
-    set kids [lindex $childlist $commitrow($id)]
+    set kids $children($curview,$id)
     if {$kids ne {}} {
        $ctext insert end "\nChildren:"
        set i 0
@@ -3155,8 +4140,7 @@ proc lineclick {x y id isnew} {
        }
     }
     $ctext conf -state disabled
-
-    $cflist delete 0 end
+    init_flist {}
 }
 
 proc normalline {} {
@@ -3169,9 +4153,9 @@ proc normalline {} {
 }
 
 proc selbyid {id} {
-    global commitrow
-    if {[info exists commitrow($id)]} {
-       selectline $commitrow($id) 1
+    global commitrow curview
+    if {[info exists commitrow($curview,$id)]} {
+       selectline $commitrow($curview,$id) 1
     }
 }
 
@@ -3184,9 +4168,10 @@ proc mstime {} {
 }
 
 proc rowmenu {x y id} {
-    global rowctxmenu commitrow selectedline rowmenuid
+    global rowctxmenu commitrow selectedline rowmenuid curview
 
-    if {![info exists selectedline] || $commitrow($id) eq $selectedline} {
+    if {![info exists selectedline]
+       || $commitrow($curview,$id) eq $selectedline} {
        set state disabled
     } else {
        set state normal
@@ -3214,15 +4199,12 @@ proc diffvssel {dirn} {
 }
 
 proc doseldiff {oldid newid} {
-    global ctext cflist
+    global ctext
     global commitinfo
 
     $ctext conf -state normal
     $ctext delete 0.0 end
-    $ctext mark set fmark.0 0.0
-    $ctext mark gravity fmark.0 left
-    $cflist delete 0 end
-    $cflist insert end "Top"
+    init_flist "Top"
     $ctext insert end "From "
     $ctext tag conf link -foreground blue -underline 1
     $ctext tag bind link <Enter> { %W configure -cursor hand2 }
@@ -3389,14 +4371,15 @@ proc domktag {} {
 }
 
 proc redrawtags {id} {
-    global canv linehtag commitrow idpos selectedline
+    global canv linehtag commitrow idpos selectedline curview
 
-    if {![info exists commitrow($id)]} return
-    drawcmitrow $commitrow($id)
+    if {![info exists commitrow($curview,$id)]} return
+    drawcmitrow $commitrow($curview,$id)
     $canv delete tag.$id
     set xt [eval drawtags $id $idpos($id)]
-    $canv coords $linehtag($commitrow($id)) $xt [lindex $idpos($id) 2]
-    if {[info exists selectedline] && $selectedline == $commitrow($id)} {
+    $canv coords $linehtag($commitrow($curview,$id)) $xt [lindex $idpos($id) 2]
+    if {[info exists selectedline]
+       && $selectedline == $commitrow($curview,$id)} {
        selectline $selectedline 0
     }
 }
@@ -3508,7 +4491,7 @@ proc rereadrefs {} {
 }
 
 proc showtag {tag isnew} {
-    global ctext cflist tagcontents tagids linknum
+    global ctext tagcontents tagids linknum
 
     if {$isnew} {
        addtohistory [list showtag $tag 0]
@@ -3523,7 +4506,7 @@ proc showtag {tag isnew} {
     }
     appendwithlinks $text
     $ctext conf -state disabled
-    $cflist delete 0 end
+    init_flist {}
 }
 
 proc doquit {} {
@@ -3905,13 +4888,13 @@ set fastdate 0
 set uparrowlen 7
 set downarrowlen 7
 set mingaplen 30
+set flistmode "flat"
+set cmitmode "patch"
 
 set colors {green red blue magenta darkgrey brown orange}
 
 catch {source ~/.gitk}
 
-set namefont $mainfont
-
 font create optionfont -family sans-serif -size -12
 
 set revtreeargs {}
@@ -3928,19 +4911,77 @@ foreach arg $argv {
 # check that we can find a .git directory somewhere...
 set gitdir [gitdir]
 if {![file isdirectory $gitdir]} {
-    error_popup "Cannot find the git directory \"$gitdir\"."
+    show_error . "Cannot find the git directory \"$gitdir\"."
     exit 1
 }
 
+set cmdline_files {}
+set i [lsearch -exact $revtreeargs "--"]
+if {$i >= 0} {
+    set cmdline_files [lrange $revtreeargs [expr {$i + 1}] end]
+    set revtreeargs [lrange $revtreeargs 0 [expr {$i - 1}]]
+} elseif {$revtreeargs ne {}} {
+    if {[catch {
+       set f [eval exec git-rev-parse --no-revs --no-flags $revtreeargs]
+       set cmdline_files [split $f "\n"]
+       set n [llength $cmdline_files]
+       set revtreeargs [lrange $revtreeargs 0 end-$n]
+    } err]} {
+       # unfortunately we get both stdout and stderr in $err,
+       # so look for "fatal:".
+       set i [string first "fatal:" $err]
+       if {$i > 0} {
+           set err [string range [expr {$i + 6}] end]
+       }
+       show_error . "Bad arguments to gitk:\n$err"
+       exit 1
+    }
+}
+
 set history {}
 set historyindex 0
 
 set optim_delay 16
 
+set nextviewnum 1
+set curview 0
+set selectedview 0
+set selectedhlview {}
+set viewfiles(0) {}
+set viewperm(0) 0
+set viewargs(0) {}
+
+set cmdlineok 0
 set stopped 0
 set stuffsaved 0
 set patchnum 0
 setcoords
-makewindow $revtreeargs
+makewindow
 readrefs
-getcommits $revtreeargs
+
+if {$cmdline_files ne {} || $revtreeargs ne {}} {
+    # create a view for the files/dirs specified on the command line
+    set curview 1
+    set selectedview 1
+    set nextviewnum 2
+    set viewname(1) "Command line"
+    set viewfiles(1) $cmdline_files
+    set viewargs(1) $revtreeargs
+    set viewperm(1) 0
+    addviewmenu 1
+    .bar.view entryconf 2 -state normal
+    .bar.view entryconf 3 -state normal
+}
+
+if {[info exists permviews]} {
+    foreach v $permviews {
+       set n $nextviewnum
+       incr nextviewnum
+       set viewname($n) [lindex $v 0]
+       set viewfiles($n) [lindex $v 1]
+       set viewargs($n) [lindex $v 2]
+       set viewperm($n) 1
+       addviewmenu $n
+    }
+}
+getcommits
index 861644b..cc7bd1f 100644 (file)
@@ -1223,6 +1223,7 @@ int main(int argc, char **argv)
        int rc = 0;
 
        setup_git_directory();
+       git_config(git_default_config);
 
        while (arg < argc && argv[arg][0] == '-') {
                if (argv[arg][1] == 't') {
@@ -1249,6 +1250,7 @@ int main(int argc, char **argv)
        }
        commit_id = argv[arg];
        url = argv[arg + 1];
+       write_ref_log_details = url;
 
        http_init();
 
index fa9e697..ffa4887 100644 (file)
@@ -208,6 +208,7 @@ int main(int argc, char **argv)
        int arg = 1;
 
        setup_git_directory();
+       git_config(git_default_config);
 
        while (arg < argc && argv[arg][0] == '-') {
                if (argv[arg][1] == 't')
@@ -239,6 +240,7 @@ int main(int argc, char **argv)
                usage(local_pull_usage);
        commit_id = argv[arg];
        path = argv[arg + 1];
+       write_ref_log_details = path;
 
        if (pull(commit_id))
                return 1;
index aaf2b94..2bcf2df 100644 (file)
@@ -3,6 +3,15 @@
 #include "commit.h"
 #include "log-tree.h"
 
+static void show_parents(struct commit *commit, int abbrev)
+{
+       struct commit_list *p;
+       for (p = commit->parents; p ; p = p->next) {
+               struct commit *parent = p->item;
+               printf(" %s", diff_unique_abbrev(parent->object.sha1, abbrev));
+       }
+}
+
 void show_log(struct rev_info *opt, struct log_info *log, const char *sep)
 {
        static char this_header[16384];
@@ -14,7 +23,10 @@ void show_log(struct rev_info *opt, struct log_info *log, const char *sep)
 
        opt->loginfo = NULL;
        if (!opt->verbose_header) {
-               puts(sha1_to_hex(commit->object.sha1));
+               fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit), stdout);
+               if (opt->parents)
+                       show_parents(commit, abbrev_commit);
+               putchar('\n');
                return;
        }
 
@@ -45,7 +57,9 @@ void show_log(struct rev_info *opt, struct log_info *log, const char *sep)
                printf("%s%s",
                       opt->commit_format == CMIT_FMT_ONELINE ? "" : "commit ",
                       diff_unique_abbrev(commit->object.sha1, abbrev_commit));
-               if (parent) 
+               if (opt->parents)
+                       show_parents(commit, abbrev_commit);
+               if (parent)
                        printf(" (from %s)",
                               diff_unique_abbrev(parent->object.sha1,
                                                  abbrev_commit));
index e4ef200..f2b3bc1 100644 (file)
--- a/ls-tree.c
+++ b/ls-tree.c
@@ -142,8 +142,8 @@ int main(int argc, const char **argv)
 
        if (argc < 2)
                usage(ls_tree_usage);
-       if (get_sha1(argv[1], sha1) < 0)
-               usage(ls_tree_usage);
+       if (get_sha1(argv[1], sha1))
+               die("Not a valid object name %s", argv[1]);
 
        pathspec = get_pathspec(prefix, argv + 2);
        tree = parse_tree_indirect(sha1);
index 07f5ab4..4856ca0 100644 (file)
@@ -82,8 +82,9 @@ static struct commit *interesting(struct commit_list *list)
  * commit B.
  *
  *
- * Another pathological example how this thing can fail to mark an ancestor
- * of a merge base as UNINTERESTING without the postprocessing phase.
+ * Another pathological example how this thing used to fail to mark an
+ * ancestor of a merge base as UNINTERESTING before we introduced the
+ * postprocessing phase (mark_reachable_commits).
  *
  *               2
  *               H
@@ -118,7 +119,9 @@ static struct commit *interesting(struct commit_list *list)
  *      D7                     2 3 7 7 3 2 1 2
  *      E7                     2 3 7 7 7 2 1 2
  *
- * and we end up showing E as an interesting merge base.
+ * and we ended up showing E as an interesting merge base.
+ * The postprocessing phase re-injects C and continues traversal
+ * to contaminate D and E.
  */
 
 static int show_all = 0;
@@ -247,10 +250,12 @@ int main(int argc, char **argv)
                        usage(merge_base_usage);
                argc--; argv++;
        }
-       if (argc != 3 ||
-           get_sha1(argv[1], rev1key) ||
-           get_sha1(argv[2], rev2key))
+       if (argc != 3)
                usage(merge_base_usage);
+       if (get_sha1(argv[1], rev1key))
+               die("Not a valid object name %s", argv[1]);
+       if (get_sha1(argv[2], rev2key))
+               die("Not a valid object name %s", argv[2]);
        rev1 = lookup_commit_reference(rev1key);
        rev2 = lookup_commit_reference(rev2key);
        if (!rev1 || !rev2)
index 50528d5..9dcaab7 100644 (file)
@@ -24,16 +24,14 @@ static const char *sha1_to_hex_zero(const unsigned char *sha1)
 
 static void resolve(const char *base, struct name_entry *branch1, struct name_entry *result)
 {
-       char branch1_sha1[50];
-
        /* If it's already branch1, don't bother showing it */
        if (!branch1)
                return;
-       memcpy(branch1_sha1, sha1_to_hex_zero(branch1->sha1), 41);
 
        printf("0 %06o->%06o %s->%s %s%s\n",
                branch1->mode, result->mode,
-               branch1_sha1, sha1_to_hex_zero(result->sha1),
+               sha1_to_hex_zero(branch1->sha1),
+               sha1_to_hex_zero(result->sha1),
                base, result->path);
 }
 
@@ -151,7 +149,7 @@ static void *get_tree_descriptor(struct tree_desc *desc, const char *rev)
        unsigned char sha1[20];
        void *buf;
 
-       if (get_sha1(rev, sha1) < 0)
+       if (get_sha1(rev, sha1))
                die("unknown rev %s", rev);
        buf = fill_tree_descriptor(desc, sha1);
        if (!buf)
index 84ed90d..e575879 100644 (file)
@@ -29,12 +29,12 @@ static int verify_packfile(struct packed_git *p)
        pack_base = p->pack_base;
        SHA1_Update(&ctx, pack_base, pack_size - 20);
        SHA1_Final(sha1, &ctx);
-       if (memcmp(sha1, index_base + index_size - 40, 20))
-               return error("Packfile %s SHA1 mismatch with idx",
-                            p->pack_name);
        if (memcmp(sha1, pack_base + pack_size - 20, 20))
                return error("Packfile %s SHA1 mismatch with itself",
                             p->pack_name);
+       if (memcmp(sha1, index_base + index_size - 40, 20))
+               return error("Packfile %s SHA1 mismatch with idx",
+                            p->pack_name);
 
        /* Make sure everything reachable from idx is valid.  Since we
         * have verified that nr_objects matches between idx and pack,
index c0acc46..77284cf 100644 (file)
@@ -156,7 +156,7 @@ static void prepare_pack_revindex(struct pack_revindex *rix)
 
        rix->revindex = xmalloc(sizeof(unsigned long) * (num_ent + 1));
        for (i = 0; i < num_ent; i++) {
-               long hl = *((long *)(index + 24 * i));
+               unsigned int hl = *((unsigned int *)(index + 24 * i));
                rix->revindex[i] = ntohl(hl);
        }
        /* This knows the pack format -- the 20-byte trailer
@@ -994,6 +994,7 @@ static int type_size_sort(const struct object_entry *a, const struct object_entr
 struct unpacked {
        struct object_entry *entry;
        void *data;
+       struct delta_index *index;
 };
 
 /*
@@ -1004,64 +1005,59 @@ struct unpacked {
  * more importantly, the bigger file is likely the more recent
  * one.
  */
-static int try_delta(struct unpacked *cur, struct unpacked *old, unsigned max_depth)
+static int try_delta(struct unpacked *trg, struct unpacked *src,
+                    struct delta_index *src_index, unsigned max_depth)
 {
-       struct object_entry *cur_entry = cur->entry;
-       struct object_entry *old_entry = old->entry;
-       unsigned long size, oldsize, delta_size, sizediff;
-       long max_size;
+       struct object_entry *trg_entry = trg->entry;
+       struct object_entry *src_entry = src->entry;
+       unsigned long size, src_size, delta_size, sizediff, max_size;
        void *delta_buf;
 
        /* Don't bother doing diffs between different types */
-       if (cur_entry->type != old_entry->type)
+       if (trg_entry->type != src_entry->type)
                return -1;
 
        /* We do not compute delta to *create* objects we are not
         * going to pack.
         */
-       if (cur_entry->preferred_base)
+       if (trg_entry->preferred_base)
                return -1;
 
-       /* If the current object is at pack edge, take the depth the
+       /*
+        * If the current object is at pack edge, take the depth the
         * objects that depend on the current object into account --
         * otherwise they would become too deep.
         */
-       if (cur_entry->delta_child) {
-               if (max_depth <= cur_entry->delta_limit)
+       if (trg_entry->delta_child) {
+               if (max_depth <= trg_entry->delta_limit)
                        return 0;
-               max_depth -= cur_entry->delta_limit;
+               max_depth -= trg_entry->delta_limit;
        }
-
-       size = cur_entry->size;
-       oldsize = old_entry->size;
-       sizediff = oldsize > size ? oldsize - size : size - oldsize;
-
-       if (size < 50)
-               return -1;
-       if (old_entry->depth >= max_depth)
+       if (src_entry->depth >= max_depth)
                return 0;
 
-       /*
-        * NOTE!
-        *
-        * We always delta from the bigger to the smaller, since that's
-        * more space-efficient (deletes don't have to say _what_ they
-        * delete).
-        */
-       max_size = size / 2 - 20;
-       if (cur_entry->delta)
-               max_size = cur_entry->delta_size-1;
+       /* Now some size filtering heuristics. */
+       size = trg_entry->size;
+       max_size = size/2 - 20;
+       max_size = max_size * (max_depth - src_entry->depth) / max_depth;
+       if (max_size == 0)
+               return 0;
+       if (trg_entry->delta && trg_entry->delta_size <= max_size)
+               max_size = trg_entry->delta_size-1;
+       src_size = src_entry->size;
+       sizediff = src_size < size ? size - src_size : 0;
        if (sizediff >= max_size)
                return 0;
-       delta_buf = diff_delta(old->data, oldsize,
-                              cur->data, size, &delta_size, max_size);
+
+       delta_buf = create_delta(src_index, trg->data, size, &delta_size, max_size);
        if (!delta_buf)
                return 0;
-       cur_entry->delta = old_entry;
-       cur_entry->delta_size = delta_size;
-       cur_entry->depth = old_entry->depth + 1;
+
+       trg_entry->delta = src_entry;
+       trg_entry->delta_size = delta_size;
+       trg_entry->depth = src_entry->depth + 1;
        free(delta_buf);
-       return 0;
+       return 1;
 }
 
 static void progress_interval(int signum)
@@ -1109,11 +1105,16 @@ static void find_deltas(struct object_entry **list, int window, int depth)
                         */
                        continue;
 
+               if (entry->size < 50)
+                       continue;
+               free_delta_index(n->index);
+               n->index = NULL;
                free(n->data);
                n->entry = entry;
                n->data = read_sha1_file(entry->sha1, type, &size);
                if (size != entry->size)
-                       die("object %s inconsistent object length (%lu vs %lu)", sha1_to_hex(entry->sha1), size, entry->size);
+                       die("object %s inconsistent object length (%lu vs %lu)",
+                           sha1_to_hex(entry->sha1), size, entry->size);
 
                j = window;
                while (--j > 0) {
@@ -1124,18 +1125,20 @@ static void find_deltas(struct object_entry **list, int window, int depth)
                        m = array + other_idx;
                        if (!m->entry)
                                break;
-                       if (try_delta(n, m, depth) < 0)
+                       if (try_delta(n, m, m->index, depth) < 0)
                                break;
                }
-#if 0
                /* if we made n a delta, and if n is already at max
                 * depth, leaving it in the window is pointless.  we
                 * should evict it first.
-                * ... in theory only; somehow this makes things worse.
                 */
                if (entry->delta && depth <= entry->depth)
                        continue;
-#endif
+
+               n->index = create_delta_index(n->data, size);
+               if (!n->index)
+                       die("out of memory");
+
                idx++;
                if (idx >= window)
                        idx = 0;
@@ -1144,8 +1147,10 @@ static void find_deltas(struct object_entry **list, int window, int depth)
        if (progress)
                fputc('\n', stderr);
 
-       for (i = 0; i < window; ++i)
+       for (i = 0; i < window; ++i) {
+               free_delta_index(array[i].index);
                free(array[i].data);
+       }
        free(array);
 }
 
@@ -1239,6 +1244,7 @@ int main(int argc, char **argv)
 
        setup_git_directory();
 
+       progress = isatty(2);
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
 
@@ -1269,6 +1275,10 @@ int main(int argc, char **argv)
                                        usage(pack_usage);
                                continue;
                        }
+                       if (!strcmp("--progress", arg)) {
+                               progress = 1;
+                               continue;
+                       }
                        if (!strcmp("-q", arg)) {
                                progress = 0;
                                continue;
index d95f0d9..8f318ed 100644 (file)
@@ -13,8 +13,8 @@
 #include <string.h>
 #include "delta.h"
 
-void *patch_delta(void *src_buf, unsigned long src_size,
-                 void *delta_buf, unsigned long delta_size,
+void *patch_delta(const void *src_buf, unsigned long src_size,
+                 const void *delta_buf, unsigned long delta_size,
                  unsigned long *dst_size)
 {
        const unsigned char *data, *top;
index f97f92d..a917ab0 100644 (file)
@@ -552,7 +552,7 @@ int read_cache(void)
 
        active_nr = ntohl(hdr->hdr_entries);
        active_alloc = alloc_nr(active_nr);
-       active_cache = calloc(active_alloc, sizeof(struct cache_entry *));
+       active_cache = xcalloc(active_alloc, sizeof(struct cache_entry *));
 
        offset = sizeof(*hdr);
        for (i = 0; i < active_nr; i++) {
index 26f4f7e..e16e91b 100644 (file)
@@ -12,6 +12,7 @@
 #include <sys/time.h>
 #include <signal.h>
 
+static int reset = 0;
 static int merge = 0;
 static int update = 0;
 static int index_only = 0;
@@ -416,6 +417,10 @@ static void verify_uptodate(struct cache_entry *ce)
                        return;
                errno = 0;
        }
+       if (reset) {
+               ce->ce_flags |= htons(CE_UPDATE);
+               return;
+       }
        if (errno == ENOENT)
                return;
        die("Entry '%s' not uptodate. Cannot merge.", ce->name);
@@ -684,8 +689,14 @@ static int oneway_merge(struct cache_entry **src)
                             merge_size);
 
        if (!a)
-               return 0;
+               return deleted_entry(old, NULL);
        if (old && same(old, a)) {
+               if (reset) {
+                       struct stat st;
+                       if (lstat(old->name, &st) ||
+                           ce_match_stat(old, &st, 1))
+                               old->ce_flags |= htons(CE_UPDATE);
+               }
                return keep_entry(old);
        }
        return merged_entry(a, NULL);
@@ -719,7 +730,7 @@ static struct cache_file cache_file;
 
 int main(int argc, char **argv)
 {
-       int i, newfd, reset, stage = 0;
+       int i, newfd, stage = 0;
        unsigned char sha1[20];
        merge_fn_t fn = NULL;
 
@@ -794,8 +805,8 @@ int main(int argc, char **argv)
                if (1 < index_only + update)
                        usage(read_tree_usage);
 
-               if (get_sha1(arg, sha1) < 0)
-                       usage(read_tree_usage);
+               if (get_sha1(arg, sha1))
+                       die("Not a valid object name %s", arg);
                if (list_tree(sha1) < 0)
                        die("failed to unpack tree object %s", arg);
                stage++;
diff --git a/refs.c b/refs.c
index 03398cc..eeb1196 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -76,8 +76,8 @@ int create_symref(const char *git_HEAD, const char *refs_heads_master)
        char ref[1000];
        int fd, len, written;
 
-#ifdef USE_SYMLINK_HEAD
-       if (!only_use_symrefs) {
+#ifndef NO_SYMLINK_HEAD
+       if (prefer_symlink_refs) {
                unlink(git_HEAD);
                if (!symlink(refs_heads_master, git_HEAD))
                        return 0;
@@ -114,7 +114,7 @@ int read_ref(const char *filename, unsigned char *sha1)
        return -1;
 }
 
-static int do_for_each_ref(const char *base, int (*fn)(const char *path, const unsigned char *sha1))
+static int do_for_each_ref(const char *base, int (*fn)(const char *path, const unsigned char *sha1), int trim)
 {
        int retval = 0;
        DIR *dir = opendir(git_path("%s", base));
@@ -142,11 +142,13 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u
                        namelen = strlen(de->d_name);
                        if (namelen > 255)
                                continue;
+                       if (namelen>5 && !strcmp(de->d_name+namelen-5,".lock"))
+                               continue;
                        memcpy(path + baselen, de->d_name, namelen+1);
                        if (stat(git_path("%s", path), &st) < 0)
                                continue;
                        if (S_ISDIR(st.st_mode)) {
-                               retval = do_for_each_ref(path, fn);
+                               retval = do_for_each_ref(path, fn, trim);
                                if (retval)
                                        break;
                                continue;
@@ -160,7 +162,7 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u
                                      "commit object!", path);
                                continue;
                        }
-                       retval = fn(path, sha1);
+                       retval = fn(path + trim, sha1);
                        if (retval)
                                break;
                }
@@ -180,125 +182,29 @@ int head_ref(int (*fn)(const char *path, const unsigned char *sha1))
 
 int for_each_ref(int (*fn)(const char *path, const unsigned char *sha1))
 {
-       return do_for_each_ref("refs", fn);
-}
-
-static char *ref_file_name(const char *ref)
-{
-       char *base = get_refs_directory();
-       int baselen = strlen(base);
-       int reflen = strlen(ref);
-       char *ret = xmalloc(baselen + 2 + reflen);
-       sprintf(ret, "%s/%s", base, ref);
-       return ret;
-}
-
-static char *ref_lock_file_name(const char *ref)
-{
-       char *base = get_refs_directory();
-       int baselen = strlen(base);
-       int reflen = strlen(ref);
-       char *ret = xmalloc(baselen + 7 + reflen);
-       sprintf(ret, "%s/%s.lock", base, ref);
-       return ret;
+       return do_for_each_ref("refs", fn, 0);
 }
 
-int get_ref_sha1(const char *ref, unsigned char *sha1)
+int for_each_tag_ref(int (*fn)(const char *path, const unsigned char *sha1))
 {
-       const char *filename;
-
-       if (check_ref_format(ref))
-               return -1;
-       filename = git_path("refs/%s", ref);
-       return read_ref(filename, sha1);
+       return do_for_each_ref("refs/tags", fn, 10);
 }
 
-static int lock_ref_file(const char *filename, const char *lock_filename,
-                        const unsigned char *old_sha1)
+int for_each_branch_ref(int (*fn)(const char *path, const unsigned char *sha1))
 {
-       int fd = open(lock_filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
-       unsigned char current_sha1[20];
-       int retval;
-       if (fd < 0) {
-               return error("Couldn't open lock file for %s: %s",
-                            filename, strerror(errno));
-       }
-       retval = read_ref(filename, current_sha1);
-       if (old_sha1) {
-               if (retval) {
-                       close(fd);
-                       unlink(lock_filename);
-                       return error("Could not read the current value of %s",
-                                    filename);
-               }
-               if (memcmp(current_sha1, old_sha1, 20)) {
-                       close(fd);
-                       unlink(lock_filename);
-                       error("The current value of %s is %s",
-                             filename, sha1_to_hex(current_sha1));
-                       return error("Expected %s",
-                                    sha1_to_hex(old_sha1));
-               }
-       } else {
-               if (!retval) {
-                       close(fd);
-                       unlink(lock_filename);
-                       return error("Unexpectedly found a value of %s for %s",
-                                    sha1_to_hex(current_sha1), filename);
-               }
-       }
-       return fd;
+       return do_for_each_ref("refs/heads", fn, 11);
 }
 
-int lock_ref_sha1(const char *ref, const unsigned char *old_sha1)
+int for_each_remote_ref(int (*fn)(const char *path, const unsigned char *sha1))
 {
-       char *filename;
-       char *lock_filename;
-       int retval;
-       if (check_ref_format(ref))
-               return -1;
-       filename = ref_file_name(ref);
-       lock_filename = ref_lock_file_name(ref);
-       retval = lock_ref_file(filename, lock_filename, old_sha1);
-       free(filename);
-       free(lock_filename);
-       return retval;
+       return do_for_each_ref("refs/remotes", fn, 13);
 }
 
-static int write_ref_file(const char *filename,
-                         const char *lock_filename, int fd,
-                         const unsigned char *sha1)
-{
-       char *hex = sha1_to_hex(sha1);
-       char term = '\n';
-       if (write(fd, hex, 40) < 40 ||
-           write(fd, &term, 1) < 1) {
-               error("Couldn't write %s", filename);
-               close(fd);
-               return -1;
-       }
-       close(fd);
-       rename(lock_filename, filename);
-       return 0;
-}
-
-int write_ref_sha1(const char *ref, int fd, const unsigned char *sha1)
+int get_ref_sha1(const char *ref, unsigned char *sha1)
 {
-       char *filename;
-       char *lock_filename;
-       int retval;
-       if (fd < 0)
-               return -1;
        if (check_ref_format(ref))
                return -1;
-       filename = ref_file_name(ref);
-       lock_filename = ref_lock_file_name(ref);
-       if (safe_create_leading_directories(filename))
-               die("unable to create leading directory for %s", filename);
-       retval = write_ref_file(filename, lock_filename, fd, sha1);
-       free(filename);
-       free(lock_filename);
-       return retval;
+       return read_ref(git_path("refs/%s", ref), sha1);
 }
 
 /*
@@ -353,25 +259,255 @@ int check_ref_format(const char *ref)
        }
 }
 
-int write_ref_sha1_unlocked(const char *ref, const unsigned char *sha1)
+static struct ref_lock* verify_lock(struct ref_lock *lock,
+       const unsigned char *old_sha1, int mustexist)
+{
+       char buf[40];
+       int nr, fd = open(lock->ref_file, O_RDONLY);
+       if (fd < 0 && (mustexist || errno != ENOENT)) {
+               error("Can't verify ref %s", lock->ref_file);
+               unlock_ref(lock);
+               return NULL;
+       }
+       nr = read(fd, buf, 40);
+       close(fd);
+       if (nr != 40 || get_sha1_hex(buf, lock->old_sha1) < 0) {
+               error("Can't verify ref %s", lock->ref_file);
+               unlock_ref(lock);
+               return NULL;
+       }
+       if (memcmp(lock->old_sha1, old_sha1, 20)) {
+               error("Ref %s is at %s but expected %s", lock->ref_file,
+                       sha1_to_hex(lock->old_sha1), sha1_to_hex(old_sha1));
+               unlock_ref(lock);
+               return NULL;
+       }
+       return lock;
+}
+
+static struct ref_lock* lock_ref_sha1_basic(const char *path,
+       int plen,
+       const unsigned char *old_sha1, int mustexist)
+{
+       struct ref_lock *lock;
+       struct stat st;
+
+       lock = xcalloc(1, sizeof(struct ref_lock));
+       lock->lock_fd = -1;
+
+       plen = strlen(path) - plen;
+       path = resolve_ref(path, lock->old_sha1, mustexist);
+       if (!path) {
+               unlock_ref(lock);
+               return NULL;
+       }
+
+       lock->ref_file = strdup(path);
+       lock->lock_file = strdup(mkpath("%s.lock", lock->ref_file));
+       lock->log_file = strdup(git_path("logs/%s", lock->ref_file + plen));
+       lock->force_write = lstat(lock->ref_file, &st) && errno == ENOENT;
+
+       if (safe_create_leading_directories(lock->lock_file))
+               die("unable to create directory for %s", lock->lock_file);
+       lock->lock_fd = open(lock->lock_file,
+               O_WRONLY | O_CREAT | O_EXCL, 0666);
+       if (lock->lock_fd < 0) {
+               error("Couldn't open lock file %s: %s",
+                       lock->lock_file, strerror(errno));
+               unlock_ref(lock);
+               return NULL;
+       }
+
+       return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock;
+}
+
+struct ref_lock* lock_ref_sha1(const char *ref,
+       const unsigned char *old_sha1, int mustexist)
 {
-       char *filename;
-       char *lock_filename;
-       int fd;
-       int retval;
        if (check_ref_format(ref))
+               return NULL;
+       return lock_ref_sha1_basic(git_path("refs/%s", ref),
+               5 + strlen(ref), old_sha1, mustexist);
+}
+
+struct ref_lock* lock_any_ref_for_update(const char *ref,
+       const unsigned char *old_sha1, int mustexist)
+{
+       return lock_ref_sha1_basic(git_path("%s", ref),
+               strlen(ref), old_sha1, mustexist);
+}
+
+void unlock_ref (struct ref_lock *lock)
+{
+       if (lock->lock_fd >= 0) {
+               close(lock->lock_fd);
+               unlink(lock->lock_file);
+       }
+       if (lock->ref_file)
+               free(lock->ref_file);
+       if (lock->lock_file)
+               free(lock->lock_file);
+       if (lock->log_file)
+               free(lock->log_file);
+       free(lock);
+}
+
+static int log_ref_write(struct ref_lock *lock,
+       const unsigned char *sha1, const char *msg)
+{
+       int logfd, written, oflags = O_APPEND | O_WRONLY;
+       unsigned maxlen, len;
+       char *logrec;
+       const char *comitter;
+
+       if (log_all_ref_updates) {
+               if (safe_create_leading_directories(lock->log_file) < 0)
+                       return error("unable to create directory for %s",
+                               lock->log_file);
+               oflags |= O_CREAT;
+       }
+
+       logfd = open(lock->log_file, oflags, 0666);
+       if (logfd < 0) {
+               if (!log_all_ref_updates && errno == ENOENT)
+                       return 0;
+               return error("Unable to append to %s: %s",
+                       lock->log_file, strerror(errno));
+       }
+
+       setup_ident();
+       comitter = git_committer_info(1);
+       if (msg) {
+               maxlen = strlen(comitter) + strlen(msg) + 2*40 + 5;
+               logrec = xmalloc(maxlen);
+               len = snprintf(logrec, maxlen, "%s %s %s\t%s\n",
+                       sha1_to_hex(lock->old_sha1),
+                       sha1_to_hex(sha1),
+                       comitter,
+                       msg);
+       } else {
+               maxlen = strlen(comitter) + 2*40 + 4;
+               logrec = xmalloc(maxlen);
+               len = snprintf(logrec, maxlen, "%s %s %s\n",
+                       sha1_to_hex(lock->old_sha1),
+                       sha1_to_hex(sha1),
+                       comitter);
+       }
+       written = len <= maxlen ? write(logfd, logrec, len) : -1;
+       free(logrec);
+       close(logfd);
+       if (written != len)
+               return error("Unable to append to %s", lock->log_file);
+       return 0;
+}
+
+int write_ref_sha1(struct ref_lock *lock,
+       const unsigned char *sha1, const char *logmsg)
+{
+       static char term = '\n';
+
+       if (!lock)
                return -1;
-       filename = ref_file_name(ref);
-       lock_filename = ref_lock_file_name(ref);
-       if (safe_create_leading_directories(filename))
-               die("unable to create leading directory for %s", filename);
-       fd = open(lock_filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
-       if (fd < 0) {
-               error("Writing %s", lock_filename);
-               perror("Open");
+       if (!lock->force_write && !memcmp(lock->old_sha1, sha1, 20)) {
+               unlock_ref(lock);
+               return 0;
        }
-       retval = write_ref_file(filename, lock_filename, fd, sha1);
-       free(filename);
-       free(lock_filename);
-       return retval;
+       if (write(lock->lock_fd, sha1_to_hex(sha1), 40) != 40 ||
+           write(lock->lock_fd, &term, 1) != 1
+               || close(lock->lock_fd) < 0) {
+               error("Couldn't write %s", lock->lock_file);
+               unlock_ref(lock);
+               return -1;
+       }
+       if (log_ref_write(lock, sha1, logmsg) < 0) {
+               unlock_ref(lock);
+               return -1;
+       }
+       if (rename(lock->lock_file, lock->ref_file) < 0) {
+               error("Couldn't set %s", lock->ref_file);
+               unlock_ref(lock);
+               return -1;
+       }
+       lock->lock_fd = -1;
+       unlock_ref(lock);
+       return 0;
+}
+
+int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1)
+{
+       const char *logfile, *logdata, *logend, *rec, *lastgt, *lastrec;
+       char *tz_c;
+       int logfd, tz;
+       struct stat st;
+       unsigned long date;
+       unsigned char logged_sha1[20];
+
+       logfile = git_path("logs/%s", ref);
+       logfd = open(logfile, O_RDONLY, 0);
+       if (logfd < 0)
+               die("Unable to read log %s: %s", logfile, strerror(errno));
+       fstat(logfd, &st);
+       if (!st.st_size)
+               die("Log %s is empty.", logfile);
+       logdata = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, logfd, 0);
+       close(logfd);
+
+       lastrec = NULL;
+       rec = logend = logdata + st.st_size;
+       while (logdata < rec) {
+               if (logdata < rec && *(rec-1) == '\n')
+                       rec--;
+               lastgt = NULL;
+               while (logdata < rec && *(rec-1) != '\n') {
+                       rec--;
+                       if (*rec == '>')
+                               lastgt = rec;
+               }
+               if (!lastgt)
+                       die("Log %s is corrupt.", logfile);
+               date = strtoul(lastgt + 1, &tz_c, 10);
+               if (date <= at_time) {
+                       if (lastrec) {
+                               if (get_sha1_hex(lastrec, logged_sha1))
+                                       die("Log %s is corrupt.", logfile);
+                               if (get_sha1_hex(rec + 41, sha1))
+                                       die("Log %s is corrupt.", logfile);
+                               if (memcmp(logged_sha1, sha1, 20)) {
+                                       tz = strtoul(tz_c, NULL, 10);
+                                       fprintf(stderr,
+                                               "warning: Log %s has gap after %s.\n",
+                                               logfile, show_rfc2822_date(date, tz));
+                               }
+                       } else if (date == at_time) {
+                               if (get_sha1_hex(rec + 41, sha1))
+                                       die("Log %s is corrupt.", logfile);
+                       } else {
+                               if (get_sha1_hex(rec + 41, logged_sha1))
+                                       die("Log %s is corrupt.", logfile);
+                               if (memcmp(logged_sha1, sha1, 20)) {
+                                       tz = strtoul(tz_c, NULL, 10);
+                                       fprintf(stderr,
+                                               "warning: Log %s unexpectedly ended on %s.\n",
+                                               logfile, show_rfc2822_date(date, tz));
+                               }
+                       }
+                       munmap((void*)logdata, st.st_size);
+                       return 0;
+               }
+               lastrec = rec;
+       }
+
+       rec = logdata;
+       while (rec < logend && *rec != '>' && *rec != '\n')
+               rec++;
+       if (rec == logend || *rec == '\n')
+               die("Log %s is corrupt.", logfile);
+       date = strtoul(rec + 1, &tz_c, 10);
+       tz = strtoul(tz_c, NULL, 10);
+       if (get_sha1_hex(logdata, sha1))
+               die("Log %s is corrupt.", logfile);
+       munmap((void*)logdata, st.st_size);
+       fprintf(stderr, "warning: Log %s only goes back to %s.\n",
+               logfile, show_rfc2822_date(date, tz));
+       return 0;
 }
diff --git a/refs.h b/refs.h
index 2625596..6c946ea 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -1,26 +1,42 @@
 #ifndef REFS_H
 #define REFS_H
 
+struct ref_lock {
+       char *ref_file;
+       char *lock_file;
+       char *log_file;
+       unsigned char old_sha1[20];
+       int lock_fd;
+       int force_write;
+};
+
 /*
  * Calls the specified function for each ref file until it returns nonzero,
  * and returns the value
  */
 extern int head_ref(int (*fn)(const char *path, const unsigned char *sha1));
 extern int for_each_ref(int (*fn)(const char *path, const unsigned char *sha1));
+extern int for_each_tag_ref(int (*fn)(const char *path, const unsigned char *sha1));
+extern int for_each_branch_ref(int (*fn)(const char *path, const unsigned char *sha1));
+extern int for_each_remote_ref(int (*fn)(const char *path, const unsigned char *sha1));
 
 /** Reads the refs file specified into sha1 **/
 extern int get_ref_sha1(const char *ref, unsigned char *sha1);
 
-/** Locks ref and returns the fd to give to write_ref_sha1() if the ref
- * has the given value currently; otherwise, returns -1.
- **/
-extern int lock_ref_sha1(const char *ref, const unsigned char *old_sha1);
+/** Locks a "refs/" ref returning the lock on success and NULL on failure. **/
+extern struct ref_lock* lock_ref_sha1(const char *ref, const unsigned char *old_sha1, int mustexist);
+
+/** Locks any ref (for 'HEAD' type refs). */
+extern struct ref_lock* lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int mustexist);
+
+/** Release any lock taken but not written. **/
+extern void unlock_ref (struct ref_lock *lock);
 
-/** Writes sha1 into the refs file specified, locked with the given fd. **/
-extern int write_ref_sha1(const char *ref, int fd, const unsigned char *sha1);
+/** Writes sha1 into the ref specified by the lock. **/
+extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg);
 
-/** Writes sha1 into the refs file specified. **/
-extern int write_ref_sha1_unlocked(const char *ref, const unsigned char *sha1);
+/** Reads log for the value of ref during at_time. **/
+extern int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1);
 
 /** Returns 0 if target has the right format for a ref. **/
 extern int check_ref_format(const char *target);
index c5ebb76..127afd7 100644 (file)
@@ -2,57 +2,83 @@
 #include <regex.h>
 
 static const char git_config_set_usage[] =
-"git-repo-config [ --bool | --int ] [--get | --get-all | --replace-all | --unset | --unset-all] name [value [value_regex]]";
+"git-repo-config [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --unset | --unset-all] name [value [value_regex]] | --list";
 
 static char* key = NULL;
-static char* value = NULL;
+static regex_t* key_regexp = NULL;
 static regex_t* regexp = NULL;
+static int show_keys = 0;
+static int use_key_regexp = 0;
 static int do_all = 0;
 static int do_not_match = 0;
 static int seen = 0;
 static enum { T_RAW, T_INT, T_BOOL } type = T_RAW;
 
+static int show_all_config(const char *key_, const char *value_)
+{
+       if (value_)
+               printf("%s=%s\n", key_, value_);
+       else
+               printf("%s\n", key_);
+       return 0;
+}
+
 static int show_config(const char* key_, const char* value_)
 {
+       char value[256];
+       const char *vptr = value;
+       int dup_error = 0;
+
        if (value_ == NULL)
                value_ = "";
 
-       if (!strcmp(key_, key) &&
-                       (regexp == NULL ||
+       if (!use_key_regexp && strcmp(key_, key))
+               return 0;
+       if (use_key_regexp && regexec(key_regexp, key_, 0, NULL, 0))
+               return 0;
+       if (regexp != NULL &&
                         (do_not_match ^
-                         !regexec(regexp, value_, 0, NULL, 0)))) {
-               if (do_all) {
-                       printf("%s\n", value_);
-                       return 0;
-               }
-               if (seen > 0) {
-                       fprintf(stderr, "More than one value: %s\n", value);
-                       free(value);
-               }
+                         regexec(regexp, value_, 0, NULL, 0)))
+               return 0;
 
-               if (type == T_INT) {
-                       value = malloc(256);
-                       sprintf(value, "%d", git_config_int(key_, value_));
-               } else if (type == T_BOOL) {
-                       value = malloc(256);
-                       sprintf(value, "%s", git_config_bool(key_, value_)
-                                            ? "true" : "false");
-               } else {
-                       value = strdup(value_);
-               }
-               seen++;
+       if (show_keys)
+               printf("%s ", key_);
+       if (seen && !do_all)
+               dup_error = 1;
+       if (type == T_INT)
+               sprintf(value, "%d", git_config_int(key_, value_));
+       else if (type == T_BOOL)
+               vptr = git_config_bool(key_, value_) ? "true" : "false";
+       else
+               vptr = value_;
+       seen++;
+       if (dup_error) {
+               error("More than one value for the key %s: %s",
+                               key_, vptr);
        }
+       else
+               printf("%s\n", vptr);
+
        return 0;
 }
 
 static int get_value(const char* key_, const char* regex_)
 {
-       int i;
-
-       key = malloc(strlen(key_)+1);
-       for (i = 0; key_[i]; i++)
-               key[i] = tolower(key_[i]);
-       key[i] = 0;
+       char *tl;
+
+       key = strdup(key_);
+       for (tl=key+strlen(key)-1; tl >= key && *tl != '.'; --tl)
+               *tl = tolower(*tl);
+       for (tl=key; *tl && *tl != '.'; ++tl)
+               *tl = tolower(*tl);
+
+       if (use_key_regexp) {
+               key_regexp = (regex_t*)malloc(sizeof(regex_t));
+               if (regcomp(key_regexp, key, REG_EXTENDED)) {
+                       fprintf(stderr, "Invalid key pattern: %s\n", key_);
+                       return -1;
+               }
+       }
 
        if (regex_) {
                if (regex_[0] == '!') {
@@ -67,11 +93,7 @@ static int get_value(const char* key_, const char* regex_)
                }
        }
 
-       i = git_config(show_config);
-       if (value) {
-               printf("%s\n", value);
-               free(value);
-       }
+       git_config(show_config);
        free(key);
        if (regexp) {
                regfree(regexp);
@@ -79,9 +101,9 @@ static int get_value(const char* key_, const char* regex_)
        }
 
        if (do_all)
-               return 0;
+               return !seen;
 
-       return seen == 1 ? 0 : 1;
+       return (seen == 1) ? 0 : 1;
 }
 
 int main(int argc, const char **argv)
@@ -93,6 +115,8 @@ int main(int argc, const char **argv)
                        type = T_INT;
                else if (!strcmp(argv[1], "--bool"))
                        type = T_BOOL;
+               else if (!strcmp(argv[1], "--list") || !strcmp(argv[1], "-l"))
+                       return git_config(show_all_config);
                else
                        break;
                argc--;
@@ -112,6 +136,11 @@ int main(int argc, const char **argv)
                else if (!strcmp(argv[1], "--get-all")) {
                        do_all = 1;
                        return get_value(argv[2], NULL);
+               } else if (!strcmp(argv[1], "--get-regexp")) {
+                       show_keys = 1;
+                       use_key_regexp = 1;
+                       do_all = 1;
+                       return get_value(argv[2], NULL);
                } else
 
                        return git_config_set(argv[1], argv[2]);
@@ -125,6 +154,11 @@ int main(int argc, const char **argv)
                else if (!strcmp(argv[1], "--get-all")) {
                        do_all = 1;
                        return get_value(argv[2], argv[3]);
+               } else if (!strcmp(argv[1], "--get-regexp")) {
+                       show_keys = 1;
+                       use_key_regexp = 1;
+                       do_all = 1;
+                       return get_value(argv[2], argv[3]);
                } else if (!strcmp(argv[1], "--replace-all"))
 
                        return git_config_set_multivar(argv[2], argv[3], NULL, 1);
index e956cd5..4e2d9fb 100644 (file)
@@ -36,6 +36,7 @@ static int is_rev_argument(const char *arg)
                "--all",
                "--bisect",
                "--dense",
+               "--branches",
                "--header",
                "--max-age=",
                "--max-count=",
@@ -45,7 +46,9 @@ static int is_rev_argument(const char *arg)
                "--objects-edge",
                "--parents",
                "--pretty",
+               "--remotes",
                "--sparse",
+               "--tags",
                "--topo-order",
                "--date-order",
                "--unpacked",
@@ -165,18 +168,16 @@ int main(int argc, char **argv)
        int i, as_is = 0, verify = 0;
        unsigned char sha1[20];
        const char *prefix = setup_git_directory();
-       
+
        git_config(git_default_config);
 
        for (i = 1; i < argc; i++) {
-               struct stat st;
                char *arg = argv[i];
                char *dotdot;
 
                if (as_is) {
                        if (show_file(arg) && as_is < 2)
-                               if (lstat(arg, &st) < 0)
-                                       die("'%s': %s", arg, strerror(errno));
+                               verify_filename(prefix, arg);
                        continue;
                }
                if (!strcmp(arg,"-n")) {
@@ -257,6 +258,18 @@ int main(int argc, char **argv)
                                for_each_ref(show_reference);
                                continue;
                        }
+                       if (!strcmp(arg, "--branches")) {
+                               for_each_branch_ref(show_reference);
+                               continue;
+                       }
+                       if (!strcmp(arg, "--tags")) {
+                               for_each_tag_ref(show_reference);
+                               continue;
+                       }
+                       if (!strcmp(arg, "--remotes")) {
+                               for_each_remote_ref(show_reference);
+                               continue;
+                       }
                        if (!strcmp(arg, "--show-prefix")) {
                                if (prefix)
                                        puts(prefix);
@@ -342,8 +355,7 @@ int main(int argc, char **argv)
                        continue;
                if (verify)
                        die("Needed a single revision");
-               if (lstat(arg, &st) < 0)
-                       die("'%s': %s", arg, strerror(errno));
+               verify_filename(prefix, arg);
        }
        show_default();
        if (verify && revs_count != 1)
index 113dd5a..2294b16 100644 (file)
@@ -477,6 +477,36 @@ static void handle_all(struct rev_info *revs, unsigned flags)
        for_each_ref(handle_one_ref);
 }
 
+static int add_parents_only(struct rev_info *revs, const char *arg, int flags)
+{
+       unsigned char sha1[20];
+       struct object *it;
+       struct commit *commit;
+       struct commit_list *parents;
+
+       if (*arg == '^') {
+               flags ^= UNINTERESTING;
+               arg++;
+       }
+       if (get_sha1(arg, sha1))
+               return 0;
+       while (1) {
+               it = get_reference(revs, arg, sha1, 0);
+               if (strcmp(it->type, tag_type))
+                       break;
+               memcpy(sha1, ((struct tag*)it)->tagged->sha1, 20);
+       }
+       if (strcmp(it->type, commit_type))
+               return 0;
+       commit = (struct commit *)it;
+       for (parents = commit->parents; parents; parents = parents->next) {
+               it = &parents->item->object;
+               it->flags |= flags;
+               add_pending_object(revs, it, arg);
+       }
+       return 1;
+}
+
 void init_revisions(struct rev_info *revs)
 {
        memset(revs, 0, sizeof(*revs));
@@ -544,7 +574,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                revs->max_count = atoi(arg + 12);
                                continue;
                        }
-                       /* accept -<digit>, like traditilnal "head" */
+                       /* accept -<digit>, like traditional "head" */
                        if ((*arg == '-') && isdigit(arg[1])) {
                                revs->max_count = atoi(arg + 1);
                                continue;
@@ -664,6 +694,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                        }
                        if (!strcmp(arg, "-c")) {
                                revs->diff = 1;
+                               revs->dense_combined_merges = 0;
                                revs->combine_merges = 1;
                                continue;
                        }
@@ -740,39 +771,56 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                include = get_reference(revs, next, sha1, flags);
                                if (!exclude || !include)
                                        die("Invalid revision range %s..%s", arg, next);
+
+                               if (!seen_dashdash) {
+                                       *dotdot = '.';
+                                       verify_non_filename(revs->prefix, arg);
+                               }
                                add_pending_object(revs, exclude, this);
                                add_pending_object(revs, include, next);
                                continue;
                        }
                        *dotdot = '.';
                }
+               dotdot = strstr(arg, "^@");
+               if (dotdot && !dotdot[2]) {
+                       *dotdot = 0;
+                       if (add_parents_only(revs, arg, flags))
+                               continue;
+                       *dotdot = '^';
+               }
                local_flags = 0;
                if (*arg == '^') {
                        local_flags = UNINTERESTING;
                        arg++;
                }
-               if (get_sha1(arg, sha1) < 0) {
-                       struct stat st;
+               if (get_sha1(arg, sha1)) {
                        int j;
 
                        if (seen_dashdash || local_flags)
                                die("bad revision '%s'", arg);
 
-                       /* If we didn't have a "--", all filenames must exist */
-                       for (j = i; j < argc; j++) {
-                               if (lstat(argv[j], &st) < 0)
-                                       die("'%s': %s", argv[j], strerror(errno));
-                       }
+                       /* If we didn't have a "--":
+                        * (1) all filenames must exist;
+                        * (2) all rev-args must not be interpretable
+                        *     as a valid filename.
+                        * but the latter we have checked in the main loop.
+                        */
+                       for (j = i; j < argc; j++)
+                               verify_filename(revs->prefix, argv[j]);
+
                        revs->prune_data = get_pathspec(revs->prefix, argv + i);
                        break;
                }
+               if (!seen_dashdash)
+                       verify_non_filename(revs->prefix, arg);
                object = get_reference(revs, arg, sha1, flags ^ local_flags);
                add_pending_object(revs, object, arg);
        }
        if (def && !revs->pending_objects) {
                unsigned char sha1[20];
                struct object *object;
-               if (get_sha1(def, sha1) < 0)
+               if (get_sha1(def, sha1))
                        die("bad default revision '%s'", def);
                object = get_reference(revs, def, sha1, 0);
                add_pending_object(revs, object, def);
@@ -789,7 +837,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
        }
        if (revs->combine_merges) {
                revs->ignore_merges = 0;
-               if (revs->dense_combined_merges)
+               if (revs->dense_combined_merges &&
+                   (revs->diffopt.output_format != DIFF_FORMAT_DIFFSTAT))
                        revs->diffopt.output_format = DIFF_FORMAT_PATCH;
        }
        revs->diffopt.abbrev = revs->abbrev;
diff --git a/setup.c b/setup.c
index 36ede3d..fe7f884 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -62,6 +62,49 @@ const char *prefix_filename(const char *pfx, int pfx_len, const char *arg)
        return path;
 }
 
+/*
+ * Verify a filename that we got as an argument for a pathspec
+ * entry. Note that a filename that begins with "-" never verifies
+ * as true, because even if such a filename were to exist, we want
+ * it to be preceded by the "--" marker (or we want the user to
+ * use a format like "./-filename")
+ */
+void verify_filename(const char *prefix, const char *arg)
+{
+       const char *name;
+       struct stat st;
+
+       if (*arg == '-')
+               die("bad flag '%s' used after filename", arg);
+       name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg;
+       if (!lstat(name, &st))
+               return;
+       if (errno == ENOENT)
+               die("ambiguous argument '%s': unknown revision or path not in the working tree.\n"
+                   "Use '--' to separate paths from revisions", arg);
+       die("'%s': %s", arg, strerror(errno));
+}
+
+/*
+ * Opposite of the above: the command line did not have -- marker
+ * and we parsed the arg as a refname.  It should not be interpretable
+ * as a filename.
+ */
+void verify_non_filename(const char *prefix, const char *arg)
+{
+       const char *name;
+       struct stat st;
+
+       if (*arg == '-')
+               return; /* flag */
+       name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg;
+       if (!lstat(name, &st))
+               die("ambiguous argument '%s': both revision and filename\n"
+                   "Use '--' to separate filenames from revisions", arg);
+       if (errno != ENOENT)
+               die("'%s': %s", arg, strerror(errno));
+}
+
 const char **get_pathspec(const char *prefix, const char **pathspec)
 {
        const char *entry = *pathspec;
index f2d33af..2230010 100644 (file)
@@ -108,9 +108,10 @@ int safe_create_leading_directories(char *path)
 
 char * sha1_to_hex(const unsigned char *sha1)
 {
-       static char buffer[50];
+       static int bufno;
+       static char hexbuffer[4][50];
        static const char hex[] = "0123456789abcdef";
-       char *buf = buffer;
+       char *buffer = hexbuffer[3 & ++bufno], *buf = buffer;
        int i;
 
        for (i = 0; i < 20; i++) {
@@ -216,6 +217,8 @@ char *sha1_pack_index_name(const unsigned char *sha1)
 struct alternate_object_database *alt_odb_list;
 static struct alternate_object_database **alt_odb_tail;
 
+static void read_info_alternates(const char * alternates, int depth);
+
 /*
  * Prepare alternate object database registry.
  *
@@ -231,14 +234,85 @@ static struct alternate_object_database **alt_odb_tail;
  * SHA1, an extra slash for the first level indirection, and the
  * terminating NUL.
  */
-static void link_alt_odb_entries(const char *alt, const char *ep, int sep,
-                                const char *relative_base)
+static int link_alt_odb_entry(const char * entry, int len, const char * relative_base, int depth)
 {
-       const char *cp, *last;
-       struct alternate_object_database *ent;
+       struct stat st;
        const char *objdir = get_object_directory();
+       struct alternate_object_database *ent;
+       struct alternate_object_database *alt;
+       /* 43 = 40-byte + 2 '/' + terminating NUL */
+       int pfxlen = len;
+       int entlen = pfxlen + 43;
        int base_len = -1;
 
+       if (*entry != '/' && relative_base) {
+               /* Relative alt-odb */
+               if (base_len < 0)
+                       base_len = strlen(relative_base) + 1;
+               entlen += base_len;
+               pfxlen += base_len;
+       }
+       ent = xmalloc(sizeof(*ent) + entlen);
+
+       if (*entry != '/' && relative_base) {
+               memcpy(ent->base, relative_base, base_len - 1);
+               ent->base[base_len - 1] = '/';
+               memcpy(ent->base + base_len, entry, len);
+       }
+       else
+               memcpy(ent->base, entry, pfxlen);
+
+       ent->name = ent->base + pfxlen + 1;
+       ent->base[pfxlen + 3] = '/';
+       ent->base[pfxlen] = ent->base[entlen-1] = 0;
+
+       /* Detect cases where alternate disappeared */
+       if (stat(ent->base, &st) || !S_ISDIR(st.st_mode)) {
+               error("object directory %s does not exist; "
+                     "check .git/objects/info/alternates.",
+                     ent->base);
+               free(ent);
+               return -1;
+       }
+
+       /* Prevent the common mistake of listing the same
+        * thing twice, or object directory itself.
+        */
+       for (alt = alt_odb_list; alt; alt = alt->next) {
+               if (!memcmp(ent->base, alt->base, pfxlen)) {
+                       free(ent);
+                       return -1;
+               }
+       }
+       if (!memcmp(ent->base, objdir, pfxlen)) {
+               free(ent);
+               return -1;
+       }
+
+       /* add the alternate entry */
+       *alt_odb_tail = ent;
+       alt_odb_tail = &(ent->next);
+       ent->next = NULL;
+
+       /* recursively add alternates */
+       read_info_alternates(ent->base, depth + 1);
+
+       ent->base[pfxlen] = '/';
+
+       return 0;
+}
+
+static void link_alt_odb_entries(const char *alt, const char *ep, int sep,
+                                const char *relative_base, int depth)
+{
+       const char *cp, *last;
+
+       if (depth > 5) {
+               error("%s: ignoring alternate object stores, nesting too deep.",
+                               relative_base);
+               return;
+       }
+
        last = alt;
        while (last < ep) {
                cp = last;
@@ -248,60 +322,15 @@ static void link_alt_odb_entries(const char *alt, const char *ep, int sep,
                        last = cp + 1;
                        continue;
                }
-               for ( ; cp < ep && *cp != sep; cp++)
-                       ;
+               while (cp < ep && *cp != sep)
+                       cp++;
                if (last != cp) {
-                       struct stat st;
-                       struct alternate_object_database *alt;
-                       /* 43 = 40-byte + 2 '/' + terminating NUL */
-                       int pfxlen = cp - last;
-                       int entlen = pfxlen + 43;
-
-                       if (*last != '/' && relative_base) {
-                               /* Relative alt-odb */
-                               if (base_len < 0)
-                                       base_len = strlen(relative_base) + 1;
-                               entlen += base_len;
-                               pfxlen += base_len;
-                       }
-                       ent = xmalloc(sizeof(*ent) + entlen);
-
-                       if (*last != '/' && relative_base) {
-                               memcpy(ent->base, relative_base, base_len - 1);
-                               ent->base[base_len - 1] = '/';
-                               memcpy(ent->base + base_len,
-                                      last, cp - last);
-                       }
-                       else
-                               memcpy(ent->base, last, pfxlen);
-
-                       ent->name = ent->base + pfxlen + 1;
-                       ent->base[pfxlen + 3] = '/';
-                       ent->base[pfxlen] = ent->base[entlen-1] = 0;
-
-                       /* Detect cases where alternate disappeared */
-                       if (stat(ent->base, &st) || !S_ISDIR(st.st_mode)) {
-                               error("object directory %s does not exist; "
-                                     "check .git/objects/info/alternates.",
-                                     ent->base);
-                               goto bad;
-                       }
-                       ent->base[pfxlen] = '/';
-
-                       /* Prevent the common mistake of listing the same
-                        * thing twice, or object directory itself.
-                        */
-                       for (alt = alt_odb_list; alt; alt = alt->next)
-                               if (!memcmp(ent->base, alt->base, pfxlen))
-                                       goto bad;
-                       if (!memcmp(ent->base, objdir, pfxlen)) {
-                       bad:
-                               free(ent);
-                       }
-                       else {
-                               *alt_odb_tail = ent;
-                               alt_odb_tail = &(ent->next);
-                               ent->next = NULL;
+                       if ((*last != '/') && depth) {
+                               error("%s: ignoring relative alternate object store %s",
+                                               relative_base, last);
+                       } else {
+                               link_alt_odb_entry(last, cp - last,
+                                               relative_base, depth);
                        }
                }
                while (cp < ep && *cp == sep)
@@ -310,23 +339,14 @@ static void link_alt_odb_entries(const char *alt, const char *ep, int sep,
        }
 }
 
-void prepare_alt_odb(void)
+static void read_info_alternates(const char * relative_base, int depth)
 {
-       char path[PATH_MAX];
        char *map;
-       int fd;
        struct stat st;
-       char *alt;
-
-       alt = getenv(ALTERNATE_DB_ENVIRONMENT);
-       if (!alt) alt = "";
-
-       if (alt_odb_tail)
-               return;
-       alt_odb_tail = &alt_odb_list;
-       link_alt_odb_entries(alt, alt + strlen(alt), ':', NULL);
+       char path[PATH_MAX];
+       int fd;
 
-       sprintf(path, "%s/info/alternates", get_object_directory());
+       sprintf(path, "%s/info/alternates", relative_base);
        fd = open(path, O_RDONLY);
        if (fd < 0)
                return;
@@ -339,11 +359,26 @@ void prepare_alt_odb(void)
        if (map == MAP_FAILED)
                return;
 
-       link_alt_odb_entries(map, map + st.st_size, '\n',
-                            get_object_directory());
+       link_alt_odb_entries(map, map + st.st_size, '\n', relative_base, depth);
+
        munmap(map, st.st_size);
 }
 
+void prepare_alt_odb(void)
+{
+       char *alt;
+
+       alt = getenv(ALTERNATE_DB_ENVIRONMENT);
+       if (!alt) alt = "";
+
+       if (alt_odb_tail)
+               return;
+       alt_odb_tail = &alt_odb_list;
+       link_alt_odb_entries(alt, alt + strlen(alt), ':', NULL, 0);
+
+       read_info_alternates(get_object_directory(), 0);
+}
+
 static char *find_sha1_file(const unsigned char *sha1, struct stat *st)
 {
        char *name = sha1_file_name(sha1);
@@ -1126,7 +1161,7 @@ int find_pack_entry_one(const unsigned char *sha1,
                int mi = (lo + hi) / 2;
                int cmp = memcmp(index + 24 * mi + 4, sha1, 20);
                if (!cmp) {
-                       e->offset = ntohl(*((int*)(index + 24 * mi)));
+                       e->offset = ntohl(*((unsigned int *)(index + 24 * mi)));
                        memcpy(e->sha1, sha1, 20);
                        e->p = p;
                        return 1;
index 345935b..fbbde1c 100644 (file)
@@ -4,6 +4,7 @@
 #include "tree.h"
 #include "blob.h"
 #include "tree-walk.h"
+#include "refs.h"
 
 static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
 {
@@ -245,36 +246,61 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
                "refs/remotes/%.*s/HEAD",
                NULL
        };
-       const char **p;
-       const char *warning = "warning: refname '%.*s' is ambiguous.\n";
-       char *pathname;
-       int already_found = 0;
+       static const char *warning = "warning: refname '%.*s' is ambiguous.\n";
+       const char **p, *pathname;
+       char *real_path = NULL;
+       int refs_found = 0, am;
+       unsigned long at_time = (unsigned long)-1;
        unsigned char *this_result;
        unsigned char sha1_from_ref[20];
 
        if (len == 40 && !get_sha1_hex(str, sha1))
                return 0;
 
+       /* At a given period of time? "@{2 hours ago}" */
+       for (am = 1; am < len - 1; am++) {
+               if (str[am] == '@' && str[am+1] == '{' && str[len-1] == '}') {
+                       int date_len = len - am - 3;
+                       char *date_spec = xmalloc(date_len + 1);
+                       strncpy(date_spec, str + am + 2, date_len);
+                       date_spec[date_len] = 0;
+                       at_time = approxidate(date_spec);
+                       free(date_spec);
+                       len = am;
+                       break;
+               }
+       }
+
        /* Accept only unambiguous ref paths. */
        if (ambiguous_path(str, len))
                return -1;
 
        for (p = fmt; *p; p++) {
-               this_result = already_found ? sha1_from_ref : sha1;
-               pathname = git_path(*p, len, str);
-               if (!read_ref(pathname, this_result)) {
-                       if (warn_ambiguous_refs) {
-                               if (already_found)
-                                       fprintf(stderr, warning, len, str);
-                               already_found++;
-                       }
-                       else
-                               return 0;
+               this_result = refs_found ? sha1_from_ref : sha1;
+               pathname = resolve_ref(git_path(*p, len, str), this_result, 1);
+               if (pathname) {
+                       if (!refs_found++)
+                               real_path = strdup(pathname);
+                       if (!warn_ambiguous_refs)
+                               break;
                }
        }
-       if (already_found)
-               return 0;
-       return -1;
+
+       if (!refs_found)
+               return -1;
+
+       if (warn_ambiguous_refs && refs_found > 1)
+               fprintf(stderr, warning, len, str);
+
+       if (at_time != (unsigned long)-1) {
+               read_ref_at(
+                       real_path + strlen(git_path(".")) - 1,
+                       at_time,
+                       sha1);
+       }
+
+       free(real_path);
+       return 0;
 }
 
 static int get_sha1_1(const char *name, int len, unsigned char *sha1);
@@ -456,19 +482,65 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1)
  */
 int get_sha1(const char *name, unsigned char *sha1)
 {
-       int ret;
+       int ret, bracket_depth;
        unsigned unused;
+       int namelen = strlen(name);
+       const char *cp;
 
        prepare_alt_odb();
-       ret = get_sha1_1(name, strlen(name), sha1);
-       if (ret < 0) {
-               const char *cp = strchr(name, ':');
-               if (cp) {
-                       unsigned char tree_sha1[20];
-                       if (!get_sha1_1(name, cp-name, tree_sha1))
-                               return get_tree_entry(tree_sha1, cp+1, sha1,
-                                                     &unused);
+       ret = get_sha1_1(name, namelen, sha1);
+       if (!ret)
+               return ret;
+       /* sha1:path --> object name of path in ent sha1
+        * :path -> object name of path in index
+        * :[0-3]:path -> object name of path in index at stage
+        */
+       if (name[0] == ':') {
+               int stage = 0;
+               struct cache_entry *ce;
+               int pos;
+               if (namelen < 3 ||
+                   name[2] != ':' ||
+                   name[1] < '0' || '3' < name[1])
+                       cp = name + 1;
+               else {
+                       stage = name[1] - '0';
+                       cp = name + 3;
                }
+               namelen = namelen - (cp - name);
+               if (!active_cache)
+                       read_cache();
+               if (active_nr < 0)
+                       return -1;
+               pos = cache_name_pos(cp, namelen);
+               if (pos < 0)
+                       pos = -pos - 1;
+               while (pos < active_nr) {
+                       ce = active_cache[pos];
+                       if (ce_namelen(ce) != namelen ||
+                           memcmp(ce->name, cp, namelen))
+                               break;
+                       if (ce_stage(ce) == stage) {
+                               memcpy(sha1, ce->sha1, 20);
+                               return 0;
+                       }
+                       pos++;
+               }
+               return -1;
+       }
+       for (cp = name, bracket_depth = 0; *cp; cp++) {
+               if (*cp == '{')
+                       bracket_depth++;
+               else if (bracket_depth && *cp == '}')
+                       bracket_depth--;
+               else if (!bracket_depth && *cp == ':')
+                       break;
+       }
+       if (*cp == ':') {
+               unsigned char tree_sha1[20];
+               if (!get_sha1_1(name, cp-name, tree_sha1))
+                       return get_tree_entry(tree_sha1, cp+1, sha1,
+                                             &unused);
        }
        return ret;
 }
index 24efb65..268c57b 100644 (file)
@@ -5,7 +5,7 @@
 #include "refs.h"
 
 static const char show_branch_usage[] =
-"git-show-branch [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...]";
+"git-show-branch [--dense] [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...]";
 
 static int default_num = 0;
 static int default_alloc = 0;
@@ -527,6 +527,27 @@ static int git_show_branch_config(const char *var, const char *value)
        return git_default_config(var, value);
 }
 
+static int omit_in_dense(struct commit *commit, struct commit **rev, int n)
+{
+       /* If the commit is tip of the named branches, do not
+        * omit it.
+        * Otherwise, if it is a merge that is reachable from only one
+        * tip, it is not that interesting.
+        */
+       int i, flag, count;
+       for (i = 0; i < n; i++)
+               if (rev[i] == commit)
+                       return 0;
+       flag = commit->object.flags;
+       for (i = count = 0; i < n; i++) {
+               if (flag & (1u << (i + REV_SHIFT)))
+                       count++;
+       }
+       if (count == 1)
+               return 1;
+       return 0;
+}
+
 int main(int ac, char **av)
 {
        struct commit *rev[MAX_REVS], *commit;
@@ -548,6 +569,7 @@ int main(int ac, char **av)
        int with_current_branch = 0;
        int head_at = -1;
        int topics = 0;
+       int dense = 1;
 
        setup_git_directory();
        git_config(git_show_branch_config);
@@ -590,6 +612,8 @@ int main(int ac, char **av)
                        lifo = 1;
                else if (!strcmp(arg, "--topics"))
                        topics = 1;
+               else if (!strcmp(arg, "--sparse"))
+                       dense = 0;
                else if (!strcmp(arg, "--date-order"))
                        lifo = 0;
                else
@@ -732,12 +756,15 @@ int main(int ac, char **av)
                shown_merge_point |= is_merge_point;
 
                if (1 < num_rev) {
-                       int is_merge = !!(commit->parents && commit->parents->next);
+                       int is_merge = !!(commit->parents &&
+                                         commit->parents->next);
                        if (topics &&
                            !is_merge_point &&
                            (this_flag & (1u << REV_SHIFT)))
                                continue;
-
+                       if (dense && is_merge &&
+                           omit_in_dense(commit, rev, num_rev))
+                               continue;
                        for (i = 0; i < num_rev; i++) {
                                int mark;
                                if (!(this_flag & (1u << (i + REV_SHIFT))))
index 4eb9e04..e3067b8 100644 (file)
@@ -132,6 +132,7 @@ int main(int argc, char **argv)
        if (!prog) prog = "git-ssh-upload";
 
        setup_git_directory();
+       git_config(git_default_config);
 
        while (arg < argc && argv[arg][0] == '-') {
                if (argv[arg][1] == 't') {
@@ -158,6 +159,7 @@ int main(int argc, char **argv)
        }
        commit_id = argv[arg];
        url = argv[arg + 1];
+       write_ref_log_details = url;
 
        if (setup_connection(&fd_in, &fd_out, prog, url, arg, argv + 1))
                return 1;
index b675a0b..2da6661 100644 (file)
@@ -134,7 +134,7 @@ int main(int argc, char **argv)
        commit_id = argv[arg];
        url = argv[arg + 1];
        if (get_sha1(commit_id, sha1))
-               usage(ssh_push_usage);
+               die("Not a valid object name %s", commit_id);
        memcpy(hex, sha1_to_hex(sha1), sizeof(hex));
        argv[arg] = hex;
 
index 6729a18..cf33989 100755 (executable)
@@ -174,6 +174,27 @@ test_expect_success \
     'git-ls-tree -r output for a known tree.' \
     'diff current expected'
 
+# But with -r -t we can have both.
+test_expect_success \
+    'showing tree with git-ls-tree -r -t' \
+    'git-ls-tree -r -t $tree >current'
+cat >expected <<\EOF
+100644 blob f87290f8eb2cbbea7857214459a0739927eab154   path0
+120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01   path0sym
+040000 tree 58a09c23e2ca152193f2786e06986b7b6712bdbe   path2
+100644 blob 3feff949ed00a62d9f7af97c15cd8a30595e7ac7   path2/file2
+120000 blob d8ce161addc5173867a3c3c730924388daedbc38   path2/file2sym
+040000 tree 21ae8269cacbe57ae09138dcc3a2887f904d02b3   path3
+100644 blob 0aa34cae68d0878578ad119c86ca2b5ed5b28376   path3/file3
+120000 blob 8599103969b43aff7e430efea79ca4636466794f   path3/file3sym
+040000 tree 3c5e5399f3a333eddecce7a9b9465b63f65f51e2   path3/subp3
+100644 blob 00fb5908cb97c2564a9783c0c64087333b3b464f   path3/subp3/file3
+120000 blob 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c   path3/subp3/file3sym
+EOF
+test_expect_success \
+    'git-ls-tree -r output for a known tree.' \
+    'diff current expected'
+
 ################################################################
 rm .git/index
 test_expect_success \
@@ -205,4 +226,32 @@ test_expect_success \
     'no diff after checkout and git-update-index --refresh.' \
     'git-diff-files >current && cmp -s current /dev/null'
 
+################################################################
+P=087704a96baf1c2d1c869a8b084481e121c88b5b
+test_expect_success \
+    'git-commit-tree records the correct tree in a commit.' \
+    'commit0=$(echo NO | git-commit-tree $P) &&
+     tree=$(git show --pretty=raw $commit0 |
+        sed -n -e "s/^tree //p" -e "/^author /q") &&
+     test "z$tree" = "z$P"'
+
+test_expect_success \
+    'git-commit-tree records the correct parent in a commit.' \
+    'commit1=$(echo NO | git-commit-tree $P -p $commit0) &&
+     parent=$(git show --pretty=raw $commit1 |
+        sed -n -e "s/^parent //p" -e "/^author /q") &&
+     test "z$commit0" = "z$parent"'
+
+test_expect_success \
+    'git-commit-tree omits duplicated parent in a commit.' \
+    'commit2=$(echo NO | git-commit-tree $P -p $commit0 -p $commit0) &&
+     parent=$(git show --pretty=raw $commit2 |
+        sed -n -e "s/^parent //p" -e "/^author /q" |
+        sort -u) &&
+     test "z$commit0" = "z$parent" &&
+     numparent=$(git show --pretty=raw $commit2 |
+        sed -n -e "s/^parent //p" -e "/^author /q" |
+        wc -l) &&
+     test $numparent = 1'
+
 test_done
index d0ed242..75e4c9a 100755 (executable)
@@ -37,7 +37,7 @@ compare_change () {
 }
 
 check_cache_at () {
-       clean_if_empty=`git-diff-files "$1"`
+       clean_if_empty=`git-diff-files -- "$1"`
        case "$clean_if_empty" in
        '')  echo "$1: clean" ;;
        ?*)  echo "$1: dirty" ;;
index 861ef4c..4d175d8 100755 (executable)
@@ -20,7 +20,7 @@ compare_change () {
 }
 
 check_cache_at () {
-       clean_if_empty=`git-diff-files "$1"`
+       clean_if_empty=`git-diff-files -- "$1"`
        case "$clean_if_empty" in
        '')  echo "$1: clean" ;;
        ?*)  echo "$1: dirty" ;;
index ab4dd5c..8260d57 100755 (executable)
@@ -229,7 +229,7 @@ test_expect_failure 'invalid key' 'git-repo-config inval.2key blabla'
 test_expect_success 'correct key' 'git-repo-config 123456.a123 987'
 
 test_expect_success 'hierarchical section' \
-       'git-repo-config 1.2.3.alpha beta'
+       'git-repo-config Version.1.2.3eX.Alpha beta'
 
 cat > expect << EOF
 [beta] ; silly comment # another comment
@@ -241,12 +241,30 @@ noIndent= sillyValue ; 'nother silly comment
        NoNewLine = wow2 for me
 [123456]
        a123 = 987
-[1.2.3]
-       alpha = beta
+[Version "1.2.3eX"]
+       Alpha = beta
 EOF
 
 test_expect_success 'hierarchical section value' 'cmp .git/config expect'
 
+cat > expect << EOF
+beta.noindent=sillyValue
+nextsection.nonewline=wow2 for me
+123456.a123=987
+version.1.2.3eX.alpha=beta
+EOF
+
+test_expect_success 'working --list' \
+       'git-repo-config --list > output && cmp output expect'
+
+cat > expect << EOF
+beta.noindent sillyValue
+nextsection.nonewline wow2 for me
+EOF
+
+test_expect_success '--get-regexp' \
+       'git-repo-config --get-regexp in > output && cmp output expect'
+
 cat > .git/config << EOF
 [novalue]
        variable
@@ -255,5 +273,41 @@ EOF
 test_expect_success 'get variable with no value' \
        'git-repo-config --get novalue.variable ^$'
 
+git-repo-config > output 2>&1
+
+test_expect_success 'no arguments, but no crash' \
+       "test $? = 129 && grep usage output"
+
+cat > .git/config << EOF
+[a.b]
+       c = d
+EOF
+
+git-repo-config a.x y
+
+cat > expect << EOF
+[a.b]
+       c = d
+[a]
+       x = y
+EOF
+
+test_expect_success 'new section is partial match of another' 'cmp .git/config expect'
+
+git-repo-config b.x y
+git-repo-config a.b c
+
+cat > expect << EOF
+[a.b]
+       c = d
+[a]
+       x = y
+       b = c
+[b]
+       x = y
+EOF
+
+test_expect_success 'new variable inserts into proper section' 'cmp .git/config expect'
+
 test_done
 
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
new file mode 100755 (executable)
index 0000000..f6b076b
--- /dev/null
@@ -0,0 +1,199 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Shawn Pearce
+#
+
+test_description='Test git-update-ref and basic ref logging'
+. ./test-lib.sh
+
+Z=0000000000000000000000000000000000000000
+A=1111111111111111111111111111111111111111
+B=2222222222222222222222222222222222222222
+C=3333333333333333333333333333333333333333
+D=4444444444444444444444444444444444444444
+E=5555555555555555555555555555555555555555
+F=6666666666666666666666666666666666666666
+m=refs/heads/master
+
+test_expect_success \
+       "create $m" \
+       'git-update-ref $m $A &&
+        test $A = $(cat .git/$m)'
+test_expect_success \
+       "create $m" \
+       'git-update-ref $m $B $A &&
+        test $B = $(cat .git/$m)'
+rm -f .git/$m
+
+test_expect_success \
+       "create $m (by HEAD)" \
+       'git-update-ref HEAD $A &&
+        test $A = $(cat .git/$m)'
+test_expect_success \
+       "create $m (by HEAD)" \
+       'git-update-ref HEAD $B $A &&
+        test $B = $(cat .git/$m)'
+rm -f .git/$m
+
+test_expect_failure \
+       '(not) create HEAD with old sha1' \
+       'git-update-ref HEAD $A $B'
+test_expect_failure \
+       "(not) prior created .git/$m" \
+       'test -f .git/$m'
+rm -f .git/$m
+
+test_expect_success \
+       "create HEAD" \
+       'git-update-ref HEAD $A'
+test_expect_failure \
+       '(not) change HEAD with wrong SHA1' \
+       'git-update-ref HEAD $B $Z'
+test_expect_failure \
+       "(not) changed .git/$m" \
+       'test $B = $(cat .git/$m)'
+rm -f .git/$m
+
+mkdir -p .git/logs/refs/heads
+touch .git/logs/refs/heads/master
+test_expect_success \
+       "create $m (logged by touch)" \
+       'GIT_COMMITTER_DATE="2005-05-26 23:30" \
+        git-update-ref HEAD $A -m "Initial Creation" &&
+        test $A = $(cat .git/$m)'
+test_expect_success \
+       "update $m (logged by touch)" \
+       'GIT_COMMITTER_DATE="2005-05-26 23:31" \
+        git-update-ref HEAD $B $A -m "Switch" &&
+        test $B = $(cat .git/$m)'
+test_expect_success \
+       "set $m (logged by touch)" \
+       'GIT_COMMITTER_DATE="2005-05-26 23:41" \
+        git-update-ref HEAD $A &&
+        test $A = $(cat .git/$m)'
+
+cat >expect <<EOF
+$Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000      Initial Creation
+$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150260 +0000      Switch
+$B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150860 +0000
+EOF
+test_expect_success \
+       "verifying $m's log" \
+       'diff expect .git/logs/$m'
+rm -rf .git/$m .git/logs expect
+
+test_expect_success \
+       'enable core.logAllRefUpdates' \
+       'git-repo-config core.logAllRefUpdates true &&
+        test true = $(git-repo-config --bool --get core.logAllRefUpdates)'
+
+test_expect_success \
+       "create $m (logged by config)" \
+       'GIT_COMMITTER_DATE="2005-05-26 23:32" \
+        git-update-ref HEAD $A -m "Initial Creation" &&
+        test $A = $(cat .git/$m)'
+test_expect_success \
+       "update $m (logged by config)" \
+       'GIT_COMMITTER_DATE="2005-05-26 23:33" \
+        git-update-ref HEAD $B $A -m "Switch" &&
+        test $B = $(cat .git/$m)'
+test_expect_success \
+       "set $m (logged by config)" \
+       'GIT_COMMITTER_DATE="2005-05-26 23:43" \
+        git-update-ref HEAD $A &&
+        test $A = $(cat .git/$m)'
+
+cat >expect <<EOF
+$Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 +0000      Initial Creation
+$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 +0000      Switch
+$B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 +0000
+EOF
+test_expect_success \
+       "verifying $m's log" \
+       'diff expect .git/logs/$m'
+rm -f .git/$m .git/logs/$m expect
+
+git-update-ref $m $D
+cat >.git/logs/$m <<EOF
+$C $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 -0500
+$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 -0500
+$F $Z $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150680 -0500
+$Z $E $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 -0500
+EOF
+
+ed="Thu, 26 May 2005 18:32:00 -0500"
+gd="Thu, 26 May 2005 18:33:00 -0500"
+ld="Thu, 26 May 2005 18:43:00 -0500"
+test_expect_success \
+       'Query "master@{May 25 2005}" (before history)' \
+       'rm -f o e
+        git-rev-parse --verify "master@{May 25 2005}" >o 2>e &&
+        test $C = $(cat o) &&
+        test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"'
+test_expect_success \
+       "Query master@{2005-05-25} (before history)" \
+       'rm -f o e
+        git-rev-parse --verify master@{2005-05-25} >o 2>e &&
+        test $C = $(cat o) &&
+        echo test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"'
+test_expect_success \
+       'Query "master@{May 26 2005 23:31:59}" (1 second before history)' \
+       'rm -f o e
+        git-rev-parse --verify "master@{May 26 2005 23:31:59}" >o 2>e &&
+        test $C = $(cat o) &&
+        test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"'
+test_expect_success \
+       'Query "master@{May 26 2005 23:32:00}" (exactly history start)' \
+       'rm -f o e
+        git-rev-parse --verify "master@{May 26 2005 23:32:00}" >o 2>e &&
+        test $A = $(cat o) &&
+        test "" = "$(cat e)"'
+test_expect_success \
+       'Query "master@{2005-05-26 23:33:01}" (middle of history with gap)' \
+       'rm -f o e
+        git-rev-parse --verify "master@{2005-05-26 23:33:01}" >o 2>e &&
+        test $B = $(cat o) &&
+        test "warning: Log .git/logs/$m has gap after $gd." = "$(cat e)"'
+test_expect_success \
+       'Query "master@{2005-05-26 23:38:00}" (middle of history)' \
+       'rm -f o e
+        git-rev-parse --verify "master@{2005-05-26 23:38:00}" >o 2>e &&
+        test $Z = $(cat o) &&
+        test "" = "$(cat e)"'
+test_expect_success \
+       'Query "master@{2005-05-26 23:43:00}" (exact end of history)' \
+       'rm -f o e
+        git-rev-parse --verify "master@{2005-05-26 23:43:00}" >o 2>e &&
+        test $E = $(cat o) &&
+        test "" = "$(cat e)"'
+test_expect_success \
+       'Query "master@{2005-05-28}" (past end of history)' \
+       'rm -f o e
+        git-rev-parse --verify "master@{2005-05-28}" >o 2>e &&
+        test $D = $(cat o) &&
+        test "warning: Log .git/logs/$m unexpectedly ended on $ld." = "$(cat e)"'
+
+
+rm -f .git/$m .git/logs/$m expect
+
+test_expect_success \
+    'creating initial files' \
+    'cp ../../COPYING COPYING &&
+     git-add COPYING &&
+        GIT_COMMITTER_DATE="2005-05-26 23:30" git-commit -m add -a &&
+        cp ../../Makefile COPYING &&
+        GIT_COMMITTER_DATE="2005-05-26 23:41" git-commit -m change -a'
+
+test_expect_success \
+       'git-cat-file blob master:COPYING (expect Makefile)' \
+       'git-cat-file blob master:COPYING | diff - ../../Makefile'
+test_expect_success \
+       'git-cat-file blob master@{2005-05-26 23:30}:COPYING (expect COPYING)' \
+       'git-cat-file blob "master@{2005-05-26 23:30}:COPYING" \
+         | diff - ../../COPYING'
+test_expect_success \
+       'git-cat-file blob master@{2005-05-26 23:42}:COPYING (expect Makefile)' \
+       'git-cat-file blob "master@{2005-05-26 23:42}:COPYING" \
+         | diff - ../../Makefile'
+
+test_done
diff --git a/t/t2101-update-index-reupdate.sh b/t/t2101-update-index-reupdate.sh
new file mode 100755 (executable)
index 0000000..77aed8d
--- /dev/null
@@ -0,0 +1,82 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Junio C Hamano
+#
+
+test_description='git-update-index --again test.
+'
+
+. ./test-lib.sh
+
+test_expect_success 'update-index --add' \
+       'echo hello world >file1 &&
+        echo goodbye people >file2 &&
+        git-update-index --add file1 file2 &&
+        git-ls-files -s >current &&
+        cmp current - <<\EOF
+100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0      file1
+100644 9db8893856a8a02eaa73470054b7c1c5a7c82e47 0      file2
+EOF'
+
+test_expect_success 'update-index --again' \
+       'rm -f file1 &&
+       echo hello everybody >file2 &&
+       if git-update-index --again
+       then
+               echo should have refused to remove file1
+               exit 1
+       else
+               echo happy - failed as expected
+       fi &&
+        git-ls-files -s >current &&
+        cmp current - <<\EOF
+100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0      file1
+100644 9db8893856a8a02eaa73470054b7c1c5a7c82e47 0      file2
+EOF'
+
+test_expect_success 'update-index --remove --again' \
+       'git-update-index --remove --again &&
+        git-ls-files -s >current &&
+        cmp current - <<\EOF
+100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0      file2
+EOF'
+
+test_expect_success 'first commit' 'git-commit -m initial'
+
+test_expect_success 'update-index again' \
+       'mkdir -p dir1 &&
+       echo hello world >dir1/file3 &&
+       echo goodbye people >file2 &&
+       git-update-index --add file2 dir1/file3 &&
+       echo hello everybody >file2
+       echo happy >dir1/file3 &&
+       git-update-index --again &&
+       git-ls-files -s >current &&
+       cmp current - <<\EOF
+100644 53ab446c3f4e42ce9bb728a0ccb283a101be4979 0      dir1/file3
+100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0      file2
+EOF'
+
+test_expect_success 'update-index --update from subdir' \
+       'echo not so happy >file2 &&
+       cd dir1 &&
+       cat ../file2 >file3 &&
+       git-update-index --again &&
+       cd .. &&
+       git-ls-files -s >current &&
+       cmp current - <<\EOF
+100644 d7fb3f695f06c759dbf3ab00046e7cc2da22d10f 0      dir1/file3
+100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0      file2
+EOF'
+
+test_expect_success 'update-index --update with pathspec' \
+       'echo very happy >file2 &&
+       cat file2 >dir1/file3 &&
+       git-update-index --again dir1/ &&
+       git-ls-files -s >current &&
+       cmp current - <<\EOF
+100644 594fb5bb1759d90998e2bf2a38261ae8e243c760 0      dir1/file3
+100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0      file2
+EOF'
+
+test_done
index 8db329d..9e1544d 100755 (executable)
@@ -28,7 +28,7 @@ cat >expected <<\EOF
 EOF
 test_expect_success \
     'limit to path should show nothing' \
-    'git-diff-index --cached $tree path >current &&
+    'git-diff-index --cached $tree -- path >current &&
      compare_diff_raw current expected'
 
 cat >expected <<\EOF
@@ -36,7 +36,7 @@ cat >expected <<\EOF
 EOF
 test_expect_success \
     'limit to path1 should show path1/file1' \
-    'git-diff-index --cached $tree path1 >current &&
+    'git-diff-index --cached $tree -- path1 >current &&
      compare_diff_raw current expected'
 
 cat >expected <<\EOF
@@ -44,7 +44,7 @@ cat >expected <<\EOF
 EOF
 test_expect_success \
     'limit to path1/ should show path1/file1' \
-    'git-diff-index --cached $tree path1/ >current &&
+    'git-diff-index --cached $tree -- path1/ >current &&
      compare_diff_raw current expected'
 
 cat >expected <<\EOF
@@ -52,14 +52,14 @@ cat >expected <<\EOF
 EOF
 test_expect_success \
     'limit to file0 should show file0' \
-    'git-diff-index --cached $tree file0 >current &&
+    'git-diff-index --cached $tree -- file0 >current &&
      compare_diff_raw current expected'
 
 cat >expected <<\EOF
 EOF
 test_expect_success \
     'limit to file0/ should emit nothing.' \
-    'git-diff-index --cached $tree file0/ >current &&
+    'git-diff-index --cached $tree -- file0/ >current &&
      compare_diff_raw current expected'
 
 test_done
diff --git a/t/t4012-diff-binary.sh b/t/t4012-diff-binary.sh
new file mode 100755 (executable)
index 0000000..bdd95c0
--- /dev/null
@@ -0,0 +1,85 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Junio C Hamano
+#
+
+test_description='Binary diff and apply
+'
+
+. ./test-lib.sh
+
+test_expect_success 'prepare repository' \
+       'echo AIT >a && echo BIT >b && echo CIT >c && echo DIT >d &&
+        git-update-index --add a b c d &&
+        echo git >a &&
+        cat ../test4012.png >b &&
+        echo git >c &&
+        cat b b >d'
+
+test_expect_success 'diff without --binary' \
+       'git-diff | git-apply --stat --summary >current &&
+        cmp current - <<\EOF
+ a |    2 +-
+ b |  Bin
+ c |    2 +-
+ d |  Bin
+ 4 files changed, 2 insertions(+), 2 deletions(-)
+EOF'
+
+test_expect_success 'diff with --binary' \
+       'git-diff --binary | git-apply --stat --summary >current &&
+        cmp current - <<\EOF
+ a |    2 +-
+ b |  Bin
+ c |    2 +-
+ d |  Bin
+ 4 files changed, 2 insertions(+), 2 deletions(-)
+EOF'
+
+# apply needs to be able to skip the binary material correctly
+# in order to report the line number of a corrupt patch.
+test_expect_success 'apply detecting corrupt patch correctly' \
+       'git-diff | sed -e 's/-CIT/xCIT/' >broken &&
+        if git-apply --stat --summary broken 2>detected
+        then
+               echo unhappy - should have detected an error
+               (exit 1)
+        else
+               echo happy
+        fi &&
+        detected=`cat detected` &&
+        detected=`expr "$detected" : "fatal.*at line \\([0-9]*\\)\$"` &&
+        detected=`sed -ne "${detected}p" broken` &&
+        test "$detected" = xCIT'
+
+test_expect_success 'apply detecting corrupt patch correctly' \
+       'git-diff --binary | sed -e 's/-CIT/xCIT/' >broken &&
+        if git-apply --stat --summary broken 2>detected
+        then
+               echo unhappy - should have detected an error
+               (exit 1)
+        else
+               echo happy
+        fi &&
+        detected=`cat detected` &&
+        detected=`expr "$detected" : "fatal.*at line \\([0-9]*\\)\$"` &&
+        detected=`sed -ne "${detected}p" broken` &&
+        test "$detected" = xCIT'
+
+test_expect_success 'initial commit' 'git-commit -a -m initial'
+
+# Try removal (b), modification (d), and creation (e).
+test_expect_success 'diff-index with --binary' \
+       'echo AIT >a && mv b e && echo CIT >c && cat e >d &&
+        git-update-index --add --remove a b c d e &&
+        tree0=`git-write-tree` &&
+        git-diff --cached --binary >current &&
+        git-apply --stat --summary current'
+
+test_expect_success 'apply binary patch' \
+       'git-reset --hard &&
+        git-apply --binary --index <current &&
+        tree1=`git-write-tree` &&
+        test "$tree1" = "$tree0"'
+
+test_done
diff --git a/t/t5700-clone-reference.sh b/t/t5700-clone-reference.sh
new file mode 100755 (executable)
index 0000000..916ee15
--- /dev/null
@@ -0,0 +1,78 @@
+#!/bin/sh
+#
+# Copyright (C) 2006 Martin Waitz <tali@admingilde.org>
+#
+
+test_description='test clone --reference'
+. ./test-lib.sh
+
+base_dir=`pwd`
+
+test_expect_success 'preparing first repository' \
+'test_create_repo A && cd A &&
+echo first > file1 &&
+git add file1 &&
+git commit -m initial'
+
+cd "$base_dir"
+
+test_expect_success 'preparing second repository' \
+'git clone A B && cd B &&
+echo second > file2 &&
+git add file2 &&
+git commit -m addition &&
+git repack -a -d &&
+git prune'
+
+cd "$base_dir"
+
+test_expect_success 'cloning with reference' \
+'git clone -l -s --reference B A C'
+
+cd "$base_dir"
+
+test_expect_success 'existance of info/alternates' \
+'test `wc -l <C/.git/objects/info/alternates` = 2'
+
+cd "$base_dir"
+
+test_expect_success 'pulling from reference' \
+'cd C &&
+git pull ../B'
+
+cd "$base_dir"
+
+test_expect_success 'that reference gets used' \
+'cd C &&
+echo "0 objects, 0 kilobytes" > expected &&
+git count-objects > current &&
+diff expected current'
+
+cd "$base_dir"
+
+test_expect_success 'updating origin' \
+'cd A &&
+echo third > file3 &&
+git add file3 &&
+git commit -m update &&
+git repack -a -d &&
+git prune'
+
+cd "$base_dir"
+
+test_expect_success 'pulling changes from origin' \
+'cd C &&
+git pull origin'
+
+cd "$base_dir"
+
+# the 2 local objects are commit and tree from the merge
+test_expect_success 'that alternate to origin gets used' \
+'cd C &&
+echo "2 objects" > expected &&
+git count-objects | cut -d, -f1 > current &&
+diff expected current'
+
+cd "$base_dir"
+
+test_done
diff --git a/t/t5710-info-alternate.sh b/t/t5710-info-alternate.sh
new file mode 100755 (executable)
index 0000000..097d037
--- /dev/null
@@ -0,0 +1,105 @@
+#!/bin/sh
+#
+# Copyright (C) 2006 Martin Waitz <tali@admingilde.org>
+#
+
+test_description='test transitive info/alternate entries'
+. ./test-lib.sh
+
+# test that a file is not reachable in the current repository
+# but that it is after creating a info/alternate entry
+reachable_via() {
+       alternate="$1"
+       file="$2"
+       if git cat-file -e "HEAD:$file"; then return 1; fi
+       echo "$alternate" >> .git/objects/info/alternate
+       git cat-file -e "HEAD:$file"
+}
+
+test_valid_repo() {
+       git fsck-objects --full > fsck.log &&
+       test `wc -l < fsck.log` = 0
+}
+
+base_dir=`pwd`
+
+test_expect_success 'preparing first repository' \
+'test_create_repo A && cd A &&
+echo "Hello World" > file1 &&
+git add file1 &&
+git commit -m "Initial commit" file1 &&
+git repack -a -d &&
+git prune'
+
+cd "$base_dir"
+
+test_expect_success 'preparing second repository' \
+'git clone -l -s A B && cd B &&
+echo "foo bar" > file2 &&
+git add file2 &&
+git commit -m "next commit" file2 &&
+git repack -a -d -l &&
+git prune'
+
+cd "$base_dir"
+
+test_expect_success 'preparing third repository' \
+'git clone -l -s B C && cd C &&
+echo "Goodbye, cruel world" > file3 &&
+git add file3 &&
+git commit -m "one more" file3 &&
+git repack -a -d -l &&
+git prune'
+
+cd "$base_dir"
+
+test_expect_failure 'creating too deep nesting' \
+'git clone -l -s C D &&
+git clone -l -s D E &&
+git clone -l -s E F &&
+git clone -l -s F G &&
+test_valid_repo'
+
+cd "$base_dir"
+
+test_expect_success 'validity of third repository' \
+'cd C &&
+test_valid_repo'
+
+cd "$base_dir"
+
+test_expect_success 'validity of fourth repository' \
+'cd D &&
+test_valid_repo'
+
+cd "$base_dir"
+
+test_expect_success 'breaking of loops' \
+"echo '$base_dir/B/.git/objects' >> '$base_dir'/A/.git/objects/info/alternates&&
+cd C &&
+test_valid_repo"
+
+cd "$base_dir"
+
+test_expect_failure 'that info/alternates is neccessary' \
+'cd C &&
+rm .git/objects/info/alternates &&
+test_valid_repo'
+
+cd "$base_dir"
+
+test_expect_success 'that relative alternate is possible for current dir' \
+'cd C &&
+echo "../../../B/.git/objects" > .git/objects/info/alternates &&
+test_valid_repo'
+
+cd "$base_dir"
+
+test_expect_failure 'that relative alternate is only possible for current dir' \
+'cd D &&
+test_valid_repo'
+
+cd "$base_dir"
+
+test_done
+
diff --git a/t/test4012.png b/t/test4012.png
new file mode 100644 (file)
index 0000000..7b181d1
Binary files /dev/null and b/t/test4012.png differ
index fc60a90..3308736 100644 (file)
@@ -321,8 +321,8 @@ int main(int argc, char **argv)
                strbuf_append_string(&current_path, "/");
                /* FALLTHROUGH */
        case 2:
-               if (get_sha1(argv[1], sha1) < 0)
-                       usage(tar_tree_usage);
+               if (get_sha1(argv[1], sha1))
+                       die("Not a valid object name %s", argv[1]);
                break;
        default:
                usage(tar_tree_usage);
index 23a8562..ccddf1d 100644 (file)
@@ -27,8 +27,10 @@ int main(int argc, char **argv)
 {
        unsigned char sha1[20];
 
-       if (argc != 2 || get_sha1(argv[1], sha1))
+       if (argc != 2)
                usage("git-unpack-file <sha1>");
+       if (get_sha1(argv[1], sha1))
+               die("Not a valid object name %s", argv[1]);
 
        setup_git_directory();
        git_config(git_default_config);
index 1efac27..3d7e02d 100644 (file)
@@ -6,6 +6,7 @@
 #include "cache.h"
 #include "strbuf.h"
 #include "quote.h"
+#include "tree-walk.h"
 
 /*
  * Default to not allowing changes to the list of files. The
@@ -328,7 +329,7 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
        return 0;
 }
 
-static int chmod_path(int flip, const char *path)
+static void chmod_path(int flip, const char *path)
 {
        int pos;
        struct cache_entry *ce;
@@ -336,21 +337,24 @@ static int chmod_path(int flip, const char *path)
 
        pos = cache_name_pos(path, strlen(path));
        if (pos < 0)
-               return -1;
+               goto fail;
        ce = active_cache[pos];
        mode = ntohl(ce->ce_mode);
        if (!S_ISREG(mode))
-               return -1;
+               goto fail;
        switch (flip) {
        case '+':
                ce->ce_mode |= htonl(0111); break;
        case '-':
                ce->ce_mode &= htonl(~0111); break;
        default:
-               return -1;
+               goto fail;
        }
        active_cache_changed = 1;
-       return 0;
+       report("chmod %cx '%s'", flip, path);
+       return;
+ fail:
+       die("git-update-index: cannot chmod %cx '%s'", flip, path);
 }
 
 static struct cache_file cache_file;
@@ -360,23 +364,26 @@ static void update_one(const char *path, const char *prefix, int prefix_length)
        const char *p = prefix_path(prefix, prefix_length, path);
        if (!verify_path(p)) {
                fprintf(stderr, "Ignoring path %s\n", path);
-               return;
+               goto free_return;
        }
        if (mark_valid_only) {
                if (mark_valid(p))
                        die("Unable to mark file %s", path);
-               return;
+               goto free_return;
        }
 
        if (force_remove) {
                if (remove_file_from_cache(p))
                        die("git-update-index: unable to remove %s", path);
                report("remove '%s'", path);
-               return;
+               goto free_return;
        }
        if (add_file_to_cache(p))
                die("Unable to process file %s", path);
        report("add '%s'", path);
+ free_return:
+       if (p < path || p > path + strlen(path))
+               free((char*)p);
 }
 
 static void read_index_info(int line_termination)
@@ -469,7 +476,174 @@ static void read_index_info(int line_termination)
 }
 
 static const char update_index_usage[] =
-"git-update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--cacheinfo] [--chmod=(+|-)x] [--info-only] [--force-remove] [--stdin] [--index-info] [--ignore-missing] [-z] [--verbose] [--] <file>...";
+"git-update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again] [--ignore-missing] [-z] [--verbose] [--] <file>...";
+
+static unsigned char head_sha1[20];
+static unsigned char merge_head_sha1[20];
+
+static struct cache_entry *read_one_ent(const char *which,
+                                       unsigned char *ent, const char *path,
+                                       int namelen, int stage)
+{
+       unsigned mode;
+       unsigned char sha1[20];
+       int size;
+       struct cache_entry *ce;
+
+       if (get_tree_entry(ent, path, sha1, &mode)) {
+               if (which)
+                       error("%s: not in %s branch.", path, which);
+               return NULL;
+       }
+       if (mode == S_IFDIR) {
+               if (which)
+                       error("%s: not a blob in %s branch.", path, which);
+               return NULL;
+       }
+       size = cache_entry_size(namelen);
+       ce = xcalloc(1, size);
+
+       memcpy(ce->sha1, sha1, 20);
+       memcpy(ce->name, path, namelen);
+       ce->ce_flags = create_ce_flags(namelen, stage);
+       ce->ce_mode = create_ce_mode(mode);
+       return ce;
+}
+
+static int unresolve_one(const char *path)
+{
+       int namelen = strlen(path);
+       int pos;
+       int ret = 0;
+       struct cache_entry *ce_2 = NULL, *ce_3 = NULL;
+
+       /* See if there is such entry in the index. */
+       pos = cache_name_pos(path, namelen);
+       if (pos < 0) {
+               /* If there isn't, either it is unmerged, or
+                * resolved as "removed" by mistake.  We do not
+                * want to do anything in the former case.
+                */
+               pos = -pos-1;
+               if (pos < active_nr) {
+                       struct cache_entry *ce = active_cache[pos];
+                       if (ce_namelen(ce) == namelen &&
+                           !memcmp(ce->name, path, namelen)) {
+                               fprintf(stderr,
+                                       "%s: skipping still unmerged path.\n",
+                                       path);
+                               goto free_return;
+                       }
+               }
+       }
+
+       /* Grab blobs from given path from HEAD and MERGE_HEAD,
+        * stuff HEAD version in stage #2,
+        * stuff MERGE_HEAD version in stage #3.
+        */
+       ce_2 = read_one_ent("our", head_sha1, path, namelen, 2);
+       ce_3 = read_one_ent("their", merge_head_sha1, path, namelen, 3);
+
+       if (!ce_2 || !ce_3) {
+               ret = -1;
+               goto free_return;
+       }
+       if (!memcmp(ce_2->sha1, ce_3->sha1, 20) &&
+           ce_2->ce_mode == ce_3->ce_mode) {
+               fprintf(stderr, "%s: identical in both, skipping.\n",
+                       path);
+               goto free_return;
+       }
+
+       remove_file_from_cache(path);
+       if (add_cache_entry(ce_2, ADD_CACHE_OK_TO_ADD)) {
+               error("%s: cannot add our version to the index.", path);
+               ret = -1;
+               goto free_return;
+       }
+       if (!add_cache_entry(ce_3, ADD_CACHE_OK_TO_ADD))
+               return 0;
+       error("%s: cannot add their version to the index.", path);
+       ret = -1;
+ free_return:
+       free(ce_2);
+       free(ce_3);
+       return ret;
+}
+
+static void read_head_pointers(void)
+{
+       if (read_ref(git_path("HEAD"), head_sha1))
+               die("No HEAD -- no initial commit yet?\n");
+       if (read_ref(git_path("MERGE_HEAD"), merge_head_sha1)) {
+               fprintf(stderr, "Not in the middle of a merge.\n");
+               exit(0);
+       }
+}
+
+static int do_unresolve(int ac, const char **av,
+                       const char *prefix, int prefix_length)
+{
+       int i;
+       int err = 0;
+
+       /* Read HEAD and MERGE_HEAD; if MERGE_HEAD does not exist, we
+        * are not doing a merge, so exit with success status.
+        */
+       read_head_pointers();
+
+       for (i = 1; i < ac; i++) {
+               const char *arg = av[i];
+               const char *p = prefix_path(prefix, prefix_length, arg);
+               err |= unresolve_one(p);
+               if (p < arg || p > arg + strlen(arg))
+                       free((char*)p);
+       }
+       return err;
+}
+
+static int do_reupdate(int ac, const char **av,
+                      const char *prefix, int prefix_length)
+{
+       /* Read HEAD and run update-index on paths that are
+        * merged and already different between index and HEAD.
+        */
+       int pos;
+       int has_head = 1;
+       const char **pathspec = get_pathspec(prefix, av + 1);
+
+       if (read_ref(git_path("HEAD"), head_sha1))
+               /* If there is no HEAD, that means it is an initial
+                * commit.  Update everything in the index.
+                */
+               has_head = 0;
+ redo:
+       for (pos = 0; pos < active_nr; pos++) {
+               struct cache_entry *ce = active_cache[pos];
+               struct cache_entry *old = NULL;
+               int save_nr;
+
+               if (ce_stage(ce) || !ce_path_match(ce, pathspec))
+                       continue;
+               if (has_head)
+                       old = read_one_ent(NULL, head_sha1,
+                                          ce->name, ce_namelen(ce), 0);
+               if (old && ce->ce_mode == old->ce_mode &&
+                   !memcmp(ce->sha1, old->sha1, 20)) {
+                       free(old);
+                       continue; /* unchanged */
+               }
+               /* Be careful.  The working tree may not have the
+                * path anymore, in which case, under 'allow_remove',
+                * or worse yet 'allow_replace', active_nr may decrease.
+                */
+               save_nr = active_nr;
+               update_one(ce->name + prefix_length, prefix, prefix_length);
+               if (save_nr != active_nr)
+                       goto redo;
+       }
+       return 0;
+}
 
 int main(int argc, const char **argv)
 {
@@ -478,6 +652,7 @@ int main(int argc, const char **argv)
        int read_from_stdin = 0;
        const char *prefix = setup_git_directory();
        int prefix_length = prefix ? strlen(prefix) : 0;
+       char set_executable_bit = 0;
 
        git_config(git_default_config);
 
@@ -544,8 +719,7 @@ int main(int argc, const char **argv)
                            !strcmp(path, "--chmod=+x")) {
                                if (argc <= i+1)
                                        die("git-update-index: %s <path>", path);
-                               if (chmod_path(path[8], argv[++i]))
-                                       die("git-update-index: %s cannot chmod %s", path, argv[i]);
+                               set_executable_bit = path[8];
                                continue;
                        }
                        if (!strcmp(path, "--assume-unchanged")) {
@@ -581,6 +755,20 @@ int main(int argc, const char **argv)
                                read_index_info(line_termination);
                                break;
                        }
+                       if (!strcmp(path, "--unresolve")) {
+                               has_errors = do_unresolve(argc - i, argv + i,
+                                                         prefix, prefix_length);
+                               if (has_errors)
+                                       active_cache_changed = 0;
+                               goto finish;
+                       }
+                       if (!strcmp(path, "--again")) {
+                               has_errors = do_reupdate(argc - i, argv + i,
+                                                        prefix, prefix_length);
+                               if (has_errors)
+                                       active_cache_changed = 0;
+                               goto finish;
+                       }
                        if (!strcmp(path, "--ignore-missing")) {
                                not_new = 1;
                                continue;
@@ -594,12 +782,15 @@ int main(int argc, const char **argv)
                        die("unknown option %s", path);
                }
                update_one(path, prefix, prefix_length);
+               if (set_executable_bit)
+                       chmod_path(set_executable_bit, path);
        }
        if (read_from_stdin) {
                struct strbuf buf;
                strbuf_init(&buf);
                while (1) {
                        char *path_name;
+                       const char *p;
                        read_line(&buf, stdin, line_termination);
                        if (buf.eof)
                                break;
@@ -607,11 +798,18 @@ int main(int argc, const char **argv)
                                path_name = unquote_c_style(buf.buf, NULL);
                        else
                                path_name = buf.buf;
-                       update_one(path_name, prefix, prefix_length);
+                       p = prefix_path(prefix, prefix_length, path_name);
+                       update_one(p, NULL, 0);
+                       if (set_executable_bit)
+                               chmod_path(set_executable_bit, p);
+                       if (p < path_name || p > path_name + strlen(path_name))
+                               free((char*) p);
                        if (path_name != buf.buf)
                                free(path_name);
                }
        }
+
+ finish:
        if (active_cache_changed) {
                if (write_cache(newfd, active_cache, active_nr) ||
                    commit_index_file(&cache_file))
index ba4bf51..a1e6bb9 100644 (file)
@@ -1,85 +1,56 @@
 #include "cache.h"
 #include "refs.h"
 
-static const char git_update_ref_usage[] = "git-update-ref <refname> <value> [<oldval>]";
-
-static int re_verify(const char *path, unsigned char *oldsha1, unsigned char *currsha1)
-{
-       char buf[40];
-       int fd = open(path, O_RDONLY), nr;
-       if (fd < 0)
-               return -1;
-       nr = read(fd, buf, 40);
-       close(fd);
-       if (nr != 40 || get_sha1_hex(buf, currsha1) < 0)
-               return -1;
-       return memcmp(oldsha1, currsha1, 20) ? -1 : 0;
-}
+static const char git_update_ref_usage[] =
+"git-update-ref <refname> <value> [<oldval>] [-m <reason>]";
 
 int main(int argc, char **argv)
 {
-       char *hex;
-       const char *refname, *value, *oldval, *path;
-       char *lockpath;
-       unsigned char sha1[20], oldsha1[20], currsha1[20];
-       int fd, written;
+       const char *refname=NULL, *value=NULL, *oldval=NULL, *msg=NULL;
+       struct ref_lock *lock;
+       unsigned char sha1[20], oldsha1[20];
+       int i;
 
        setup_git_directory();
        git_config(git_default_config);
-       if (argc < 3 || argc > 4)
+
+       for (i = 1; i < argc; i++) {
+               if (!strcmp("-m", argv[i])) {
+                       if (i+1 >= argc)
+                               usage(git_update_ref_usage);
+                       msg = argv[++i];
+                       if (!*msg)
+                               die("Refusing to perform update with empty message.");
+                       if (strchr(msg, '\n'))
+                               die("Refusing to perform update with \\n in message.");
+                       continue;
+               }
+               if (!refname) {
+                       refname = argv[i];
+                       continue;
+               }
+               if (!value) {
+                       value = argv[i];
+                       continue;
+               }
+               if (!oldval) {
+                       oldval = argv[i];
+                       continue;
+               }
+       }
+       if (!refname || !value)
                usage(git_update_ref_usage);
 
-       refname = argv[1];
-       value = argv[2];
-       oldval = argv[3];
-       if (get_sha1(value, sha1) < 0)
+       if (get_sha1(value, sha1))
                die("%s: not a valid SHA1", value);
        memset(oldsha1, 0, 20);
-       if (oldval && get_sha1(oldval, oldsha1) < 0)
+       if (oldval && get_sha1(oldval, oldsha1))
                die("%s: not a valid old SHA1", oldval);
 
-       path = resolve_ref(git_path("%s", refname), currsha1, !!oldval);
-       if (!path)
-               die("No such ref: %s", refname);
-
-       if (oldval) {
-               if (memcmp(currsha1, oldsha1, 20))
-                       die("Ref %s is at %s but expected %s", refname, sha1_to_hex(currsha1), sha1_to_hex(oldsha1));
-               /* Nothing to do? */
-               if (!memcmp(oldsha1, sha1, 20))
-                       exit(0);
-       }
-       path = strdup(path);
-       lockpath = mkpath("%s.lock", path);
-       if (safe_create_leading_directories(lockpath) < 0)
-               die("Unable to create all of %s", lockpath);
-
-       fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666);
-       if (fd < 0)
-               die("Unable to create %s", lockpath);
-       hex = sha1_to_hex(sha1);
-       hex[40] = '\n';
-       written = write(fd, hex, 41);
-       close(fd);
-       if (written != 41) {
-               unlink(lockpath);
-               die("Unable to write to %s", lockpath);
-       }
-
-       /*
-        * Re-read the ref after getting the lock to verify
-        */
-       if (oldval && re_verify(path, oldsha1, currsha1) < 0) {
-               unlink(lockpath);
-               die("Ref lock failed");
-       }
-
-       /*
-        * Finally, replace the old ref with the new one
-        */
-       if (rename(lockpath, path) < 0) {
-               unlink(lockpath);
-               die("Unable to create %s", path);
-       }
+       lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL, 0);
+       if (!lock)
+               return 1;
+       if (write_ref_sha1(lock, sha1, msg) < 0)
+               return 1;
        return 0;
 }