Cauterize dropped or duplicate bits from next.
authorJunio C Hamano <junkio@cox.net>
Sun, 5 Mar 2006 00:18:43 +0000 (16:18 -0800)
committerJunio C Hamano <junkio@cox.net>
Sun, 5 Mar 2006 00:18:43 +0000 (16:18 -0800)
I am very sorry to do this, but without this funky octopus, "git
log --no-merges master..next" will show commits already merged
into "master" forever.

There are some commits on the next branch (which is never to be
rewound) that are reverts of other commits on the next branch.
They are to revert the finer grained delta experiments that
turned out to have undesirable performance effects. Also there
are some other commits that were first done as a merge into
"next" (a pull request based on next) and then cherry picked
into master. Since they are not going to be merged into
"master" ever, they will stay forever in "log master..next".

Yuck.

So this commit records the fact that the commits currently shown
by "git log --no-merges master..next" to be merged into "master"
are already in the master, either because they really are (in
the case of git-cvsserver bits, which needed cherry-picking into
"master"), or because they are fully reverted in "next" (in the
case of finer-grained delta bits).

Here is the way I made this commit:

(1) Inspect "gitk --no-merges --parents master..next"

This shows what git thinks are missing from master. It
shows chain of commits that are already merged and chain of
commits whose net effect should amount to a no-op.

Look at each commits and make sure they are either unwanted
or already merged by cherry-picking.

(2) Record the tip of branches that I do not want. In this
case, the following were unwanted:

cfcbd3427e67056a00ec832645b057eaf33888d9 cvsserver
c436eb8cf1efa3fe2c70100ae0cbc48f0feaf5af diff-delta
38fd0721d0a2a1a723bc28fc0817e3571987b1ef diff-delta
f0bcd511ee3a00b7fd3975a386aa1165c07a0721 cvsserver
2b8d9347aa1a11f1ac13591f89ca9f984d467c77 diff-delta

(3) Shorten the list by finding independent ones from the
above.

$ git show-branch --independent $the $above $tips

cfcbd3427e67056a00ec832645b057eaf33888d9
c436eb8cf1efa3fe2c70100ae0cbc48f0feaf5af

(4) Checkout "master" and cauterize them with "ours" strategy:

$ git merge -s ours "`cat $this-file`" HEAD cfcbd3 c436eb

57 files changed:
Documentation/git-am.txt
Documentation/git-apply.txt
Documentation/git-checkout-index.txt
Documentation/git-cvsserver.txt
Documentation/git-read-tree.txt
Documentation/git-repo-config.txt
Documentation/git-rev-list.txt
Documentation/git-svnimport.txt
Documentation/git-tools.txt [new file with mode: 0644]
Documentation/tutorial.txt
GIT-VERSION-GEN
INSTALL
Makefile
apply.c
blame.c
cache.h
cat-file.c
checkout-index.c
contrib/emacs/git.el
contrib/emacs/vc-git.el [new file with mode: 0644]
contrib/git-svn/git-svn.perl
contrib/git-svn/git-svn.txt
contrib/git-svn/t/t0000-contrib-git-svn.sh
contrib/gitview/gitview
count-delta.c
diff-delta.c
diffcore-break.c
diffcore-delta.c [new file with mode: 0644]
diffcore-rename.c
diffcore.h
epoch.c [deleted file]
epoch.h [deleted file]
git-am.sh
git-annotate.perl
git-archimport.perl
git-branch.sh
git-commit.sh
git-cvsserver.perl
git-mv.perl
git-send-email.perl
git-verify-tag.sh
git.c
pack-objects.c
pager.c [new file with mode: 0644]
read-tree.c
refs.c
rev-list.c
rev-parse.c
revision.c
revision.h
show-branch.c
t/t3600-rm.sh
t/t6001-rev-list-merge-order.sh [deleted file]
t/t7001-mv.sh
t/t8001-annotate.sh [new file with mode: 0755]
tar-tree.c
update-index.c
index 02cabc935e854c2a8b67481588e3e4ed6b853baa..910457d3b388f2c3b1dbe25f5609e3584fb4f113 100644 (file)
@@ -9,7 +9,8 @@ git-am - Apply a series of patches in a mailbox
 SYNOPSIS
 --------
 [verse]
-'git-am' [--signoff] [--dotest=<dir>] [--utf8] [--binary] [--3way] <mbox>...
+'git-am' [--signoff] [--dotest=<dir>] [--utf8] [--binary] [--3way]
+         [--interactive] [--whitespace=<option>] <mbox>...
 'git-am' [--skip | --resolved]
 
 DESCRIPTION
@@ -46,6 +47,10 @@ OPTIONS
        Skip the current patch.  This is only meaningful when
        restarting an aborted patch.
 
+--whitespace=<option>::
+       This flag is passed to the `git-apply` program that applies
+       the patch.
+
 --interactive::
        Run interactively, just like git-applymbox.
 
@@ -80,7 +85,7 @@ names.
 
 SEE ALSO
 --------
-gitlink:git-applymbox[1], gitlink:git-applypatch[1].
+gitlink:git-applymbox[1], gitlink:git-applypatch[1], gitlink:git-apply[1].
 
 
 Author
index 75076b61216c7ee5ce0a55e5a7c578f5f06b6187..1c64a1aa82648db90251816f1b75dbdc9e7186e3 100644 (file)
@@ -11,6 +11,7 @@ SYNOPSIS
 [verse]
 'git-apply' [--stat] [--numstat] [--summary] [--check] [--index] [--apply]
          [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM]
+         [--whitespace=<nowarn|warn|error|error-all|strip>]
          [<patch>...]
 
 DESCRIPTION
@@ -97,6 +98,35 @@ OPTIONS
        result.  This allows binary files to be patched in a
        very limited way.
 
+--whitespace=<option>::
+       When applying a patch, detect a new or modified line
+       that ends with trailing whitespaces (this includes a
+       line that solely consists of whitespaces).  By default,
+       the command outputs warning messages and applies the
+       patch.
+       When `git-apply` is used for statistics and not applying a
+       patch, it defaults to `nowarn`.
+       You can use different `<option>` to control this
+       behaviour:
++
+* `nowarn` turns off the trailing whitespace warning.
+* `warn` outputs warnings for a few such errors, but applies the
+  patch (default).
+* `error` outputs warnings for a few such errors, and refuses
+  to apply the patch.
+* `error-all` is similar to `error` but shows all errors.
+* `strip` outputs warnings for a few such errors, strips out the
+  trailing whitespaces and applies the patch.
+
+
+Configuration
+-------------
+
+apply.whitespace::
+       When no `--whitespace` flag is given from the command
+       line, this configuration item is used as the default.
+
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>
index 2a1e526c6a1a92ea81a14b171ac8200890277fd1..b0b65889ac5e91661e9a68161dda1bb9660c0bb5 100644 (file)
@@ -10,7 +10,9 @@ SYNOPSIS
 --------
 [verse]
 'git-checkout-index' [-u] [-q] [-a] [-f] [-n] [--prefix=<string>]
-                  [--stage=<number>] [--] <file>...
+                  [--stage=<number>]
+                  [-z] [--stdin]
+                  [--] [<file>]\*
 
 DESCRIPTION
 -----------
@@ -45,6 +47,15 @@ OPTIONS
        Instead of checking out unmerged entries, copy out the
        files from named stage.  <number> must be between 1 and 3.
 
+--stdin::
+       Instead of taking list of paths from the command line,
+       read list of paths from the standard input.  Paths are
+       separated by LF (i.e. one path per line) by default.
+
+-z::
+       Only meaningful with `--stdin`; paths are separated with
+       NUL character instead of LF.
+
 --::
        Do not interpret any more arguments as options.
 
@@ -64,7 +75,12 @@ $ find . -name '*.h' -print0 | xargs -0 git-checkout-index -f --
 
 which will force all existing `*.h` files to be replaced with their
 cached copies. If an empty command line implied "all", then this would
-force-refresh everything in the index, which was not the point.
+force-refresh everything in the index, which was not the point.  But
+since git-checkout-index accepts --stdin it would be faster to use:
+
+----------------
+$ find . -name '*.h' -print0 | git-checkout-index -f -z --stdin
+----------------
 
 The `--` is just a good idea when you know the rest will be filenames;
 it will prevent problems with a filename of, for example,  `-a`.
index 88f07ff15dffe2b962ba94aa8a477dd9273d16c2..19c9c51cffec272d675cdaf1b223e2a6dff5dd3b 100644 (file)
@@ -54,6 +54,30 @@ INSTALLATION
    of branches in git).
      $ cvs co -d mylocaldir master
 
+Eclipse CVS Client Notes
+------------------------
+
+To get a checkout with the Eclipse CVS client:
+
+1. Create a new project from CVS checkout, giving it repository and module
+2. Context Menu->Team->Share Project...
+3. Enter the repository and module information again and click Finish
+4. The Synchronize view appears. Untick  "launch commit wizard" to avoid
+committing the .project file, and select HEAD as the tag to synchronize to.
+Update all incoming changes.
+
+Note that most versions of Eclipse ignore CVS_SERVER (which you can set in
+the Preferences->Team->CVS->ExtConnection pane), so you may have to
+rename, alias or symlink git-cvsserver to 'cvs' on the server.
+
+Clients known to work
+---------------------
+
+CVS 1.12.9 on Debian
+CVS 1.11.17 on MacOSX (from Fink package)
+Eclipse 3.0, 3.1.2 on MacOSX (see Eclipse CVS Client Notes)
+TortoiseCVS
+
 Operations supported
 --------------------
 
index 6fbd6d9368a4bd33167c22373614d06463fa2a2e..844cfda8d23e216a090ef94c9b85c186f2d31399 100644 (file)
@@ -8,7 +8,7 @@ git-read-tree - Reads tree information into the index
 
 SYNOPSIS
 --------
-'git-read-tree' (<tree-ish> | [[-m | --reset] [-u | -i]] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
+'git-read-tree' (<tree-ish> | [[-m [--aggressive]| --reset] [-u | -i]] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
 
 
 DESCRIPTION
@@ -50,6 +50,19 @@ OPTIONS
        trees that are not directly related to the current
        working tree status into a temporary index file.
 
+--aggressive::
+       Usually a three-way merge by `git-read-tree` resolves
+       the merge for really trivial cases and leaves other
+       cases unresolved in the index, so that Porcelains can
+       implement different merge policies.  This flag makes the
+       command to resolve a few more cases internally:
++
+* when one side removes a path and the other side leaves the path
+  unmodified.  The resolution is to remove that path.
+* when both sides remove a path.  The resolution is to remove that path.
+* when both sides adds a path identically.  The resolution
+  is to add that path.
+
 <tree-ish#>::
        The id of the tree object(s) to be read/merged.
 
index 33fcde452a3e6e795b93848acc7c4a4656ee1bf0..00efde5f0fbbae009706fbc8b4cb88b4a6f16d80 100644 (file)
@@ -8,6 +8,7 @@ git-repo-config - Get and set options in .git/config.
 
 SYNOPSIS
 --------
+[verse]
 'git-repo-config' [type] name [value [value_regex]]
 'git-repo-config' [type] --replace-all name [value [value_regex]]
 'git-repo-config' [type] --get name [value_regex]
index 1c6146c7643ef273888b76def0891b8a7ad51c98..8255ae1bceee562140179704352a7e4f9cd83d99 100644 (file)
@@ -16,9 +16,9 @@ SYNOPSIS
             [ \--no-merges ]
             [ \--remove-empty ]
             [ \--all ]
-            [ [ \--merge-order [ \--show-breaks ] ] | [ \--topo-order ] ]
+            [ \--topo-order ]
             [ \--parents ]
-            [ \--objects [ \--unpacked ] ]
+            [ [\--objects | \--objects-edge] [ \--unpacked ] ]
             [ \--pretty | \--header ]
             [ \--bisect ]
             <commit>... [ \-- <paths>... ]
@@ -53,6 +53,14 @@ OPTIONS
        which I need to download if I have the commit object 'bar', but
        not 'foo'".
 
+--objects-edge::
+       Similar to `--objects`, but also print the IDs of
+       excluded commits refixed with a `-` character.  This is
+       used by `git-pack-objects` to build 'thin' pack, which
+       records objects in deltified form based on objects
+       contained in these excluded commits to reduce network
+       traffic.
+
 --unpacked::
        Only useful with `--objects`; print the object IDs that
        are not in packs.
@@ -94,57 +102,10 @@ OPTIONS
        topological order (i.e. descendant commits are shown
        before their parents).
 
---merge-order::
-       When specified the commit history is decomposed into a unique
-       sequence of minimal, non-linear epochs and maximal, linear epochs.
-       Non-linear epochs are then linearised by sorting them into merge
-       order, which is described below.
-+
-Maximal, linear epochs correspond to periods of sequential development.
-Minimal, non-linear epochs correspond to periods of divergent development
-followed by a converging merge. The theory of epochs is described in more
-detail at
-link:http://blackcubes.dyndns.org/epoch/[http://blackcubes.dyndns.org/epoch/].
-+
-The merge order for a non-linear epoch is defined as a linearisation for which
-the following invariants are true:
-+
-    1. if a commit P is reachable from commit N, commit P sorts after commit N
-       in the linearised list.
-    2. if Pi and Pj are any two parents of a merge M (with i < j), then any
-       commit N, such that N is reachable from Pj but not reachable from Pi,
-       sorts before all commits reachable from Pi.
-+
-Invariant 1 states that later commits appear before earlier commits they are
-derived from.
-+
-Invariant 2 states that commits unique to "later" parents in a merge, appear
-before all commits from "earlier" parents of a merge.
-
---show-breaks::
-       Each item of the list is output with a 2-character prefix consisting
-       of one of: (|), (^), (=) followed by a space.
-+
-Commits marked with (=) represent the boundaries of minimal, non-linear epochs
-and correspond either to the start of a period of divergent development or to
-the end of such a period.
-+
-Commits marked with (|) are direct parents of commits immediately preceding
-the marked commit in the list.
-+
-Commits marked with (^) are not parents of the immediately preceding commit.
-These "breaks" represent necessary discontinuities implied by trying to
-represent an arbitrary DAG in a linear form.
-+
-`--show-breaks` is only valid if `--merge-order` is also specified.
-
-
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>
 
-Original *--merge-order* logic by Jon Seymour <jon.seymour@gmail.com>
-
 Documentation
 --------------
 Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
index a1588138ea5887e9aa785a21481cc29e790f6dab..9d3865719c37b9cef435b6a5b77afe3ff9aa7b40 100644 (file)
@@ -9,6 +9,7 @@ git-svnimport - Import a SVN repository into git
 
 SYNOPSIS
 --------
+[verse]
 'git-svnimport' [ -o <branch-for-HEAD> ] [ -h ] [ -v ] [ -d | -D ]
                [ -C <GIT_repository> ] [ -i ] [ -u ] [-l limit_rev]
                [ -b branch_subdir ] [ -T trunk_subdir ] [ -t tag_subdir ]
diff --git a/Documentation/git-tools.txt b/Documentation/git-tools.txt
new file mode 100644 (file)
index 0000000..00e57a6
--- /dev/null
@@ -0,0 +1,97 @@
+A short git tools survey
+========================
+
+
+Introduction
+------------
+
+Apart from git contrib/ area there are some others third-party tools
+you may want to look.
+
+This document presents a brief summary of each tool and the corresponding
+link.
+
+
+Alternative/Augmentative Procelains
+-----------------------------------
+
+   - *Cogito* (http://www.kernel.org/pub/software/scm/cogito/)
+
+   Cogito is a version control system layered on top of the git tree history
+   storage system. It aims at seamless user interface and ease of use,
+   providing generally smoother user experience than the "raw" Core GIT
+   itself and indeed many other version control systems.
+
+
+   - *pg* (http://www.spearce.org/category/projects/scm/pg/)
+
+   pg is a shell script wrapper around GIT to help the user manage a set of
+   patches to files. pg is somewhat like quilt or StGIT, but it does have a
+   slightly different feature set.
+
+
+   - *StGit* (http://www.procode.org/stgit/)
+
+   Stacked GIT provides a quilt-like patch management functionality in the
+    GIT environment. You can easily manage your patches in the scope of GIT
+   until they get merged upstream.
+
+
+History Viewers
+---------------
+
+   - *gitk* (shipped with git-core)
+
+   gitk is a simple TK GUI for browsing history of GIT repositories easily.
+
+
+   - *gitview*  (contrib/)
+
+   gitview is a GTK based repository browser for git
+
+
+   - *gitweb* (ftp://ftp.kernel.org/pub/software/scm/gitweb/)
+
+   GITweb provides full-fledged web interface for GIT repositories.
+
+
+   - *qgit* (http://digilander.libero.it/mcostalba/)
+
+   QGit is a git/StGIT GUI viewer built on Qt/C++. QGit could be used
+   to browse history and directory tree, view annotated files, commit
+   changes cherry picking single files or applying patches.
+   Currently it is the fastest and most feature rich among the git
+   viewers and commit tools.
+
+
+
+Foreign SCM interface
+---------------------
+
+   - *git-svn* (contrib/)
+
+   git-svn is a simple conduit for changesets between a single Subversion
+   branch and git.
+
+
+   - *quilt2git / git2quilt* (http://home-tj.org/wiki/index.php/Misc)
+
+   These utilities convert patch series in a quilt repository and commit
+   series in git back and forth.
+
+
+Others
+------
+
+   - *(h)gct* (http://www.cyd.liu.se/users/~freku045/gct/)
+
+   Commit Tool or (h)gct is a GUI enabled commit tool for git and
+   Mercurial (hg). It allows the user to view diffs, select which files
+   to committed (or ignored / reverted) write commit messages and
+   perform the commit itself.
+
+   - *git.el* (contrib/)
+
+   This is an Emacs interface for git. The user interface is modeled on
+   pcl-cvs. It has been developed on Emacs 21 and will probably need some
+   tweaking to work on XEmacs.
index 66680d76bd8bc196bbbfdf98923b6be6354dea0d..fa79b016c77a7f37ae1e289eb42bb008174eda81 100644 (file)
@@ -309,7 +309,7 @@ git diff HEAD^^ HEAD^
 -------------------------------------
 
 shows the difference between that previous state and the state two
-commits ago.  Also, HEAD~5 can be used as a shorthand for HEAD^^^^^,
+commits ago.  Also, HEAD~5 can be used as a shorthand for HEAD{caret}{caret}{caret}{caret}{caret},
 and more generally HEAD~n can refer to the nth previous commit.
 Commits representing merges have more than one parent, and you can
 specify which parent to follow in that case; see
index 1056b7c817f8578348120f64ad7047754bf688df..d6d1ae0338085d8d3ea567d8007f05ab5f2ee554 100755 (executable)
@@ -7,8 +7,11 @@ DEF_VER=v1.2.GIT
 # (included in release tarballs), then default
 if VN=$(git-describe --abbrev=4 HEAD 2>/dev/null); then
        VN=$(echo "$VN" | sed -e 's/-/./g');
-else
+elif test -f version
+then
        VN=$(cat version) || VN="$DEF_VER"
+else
+       VN="$DEF_VER"
 fi
 
 VN=$(expr "$VN" : v*'\(.*\)')
diff --git a/INSTALL b/INSTALL
index 433449fd8e1b6521a65084cadf3dc17ec8f0c45a..63af8eccf3ce115a992cc3f1dc812e5842e3f75f 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -40,9 +40,7 @@ Issues of note:
 
          If you don't have openssl, you can use one of the SHA1 libraries
          that come with git (git includes the one from Mozilla, and has
-         its own PowerPC-optimized one too - see the Makefile), and you
-         can avoid the bignum support by excising git-rev-list support
-         for "--merge-order" (by hand).
+         its own PowerPC and ARM optimized ones too - see the Makefile).
 
        - "libcurl" and "curl" executable.  git-http-fetch and
          git-fetch use them.  If you do not use http
index 5e93f278fc8c8e438d9613e18e52386febaf276b..b6d8804d4b0cac60d1dd4943c5b9a791f26e006f 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -6,8 +6,8 @@ all:
 # on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default
 # choice) has very fast version optimized for i586.
 #
-# Define NO_OPENSSL environment variable if you do not have OpenSSL. You will
-# miss out git-rev-list --merge-order. This also implies MOZILLA_SHA1.
+# Define NO_OPENSSL environment variable if you do not have OpenSSL.
+# This also implies MOZILLA_SHA1.
 #
 # Define NO_CURL if you do not have curl installed.  git-http-pull and
 # git-http-push are not built, and you cannot use http:// and https://
@@ -191,12 +191,13 @@ LIB_FILE=libgit.a
 
 LIB_H = \
        blob.h cache.h commit.h count-delta.h csum-file.h delta.h \
-       diff.h epoch.h object.h pack.h pkt-line.h quote.h refs.h \
+       diff.h object.h pack.h pkt-line.h quote.h refs.h \
        run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h
 
 DIFF_OBJS = \
        diff.o diffcore-break.o diffcore-order.o diffcore-pathspec.o \
-       diffcore-pickaxe.o diffcore-rename.o tree-diff.o combine-diff.o
+       diffcore-pickaxe.o diffcore-rename.o tree-diff.o combine-diff.o \
+       diffcore-delta.o
 
 LIB_OBJS = \
        blob.o commit.o connect.o count-delta.o csum-file.o \
@@ -205,7 +206,7 @@ LIB_OBJS = \
        quote.o read-cache.o refs.o run-command.o \
        server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
        tag.o tree.o usage.o config.o environment.o ctype.o copy.o \
-       fetch-clone.o revision.o \
+       fetch-clone.o revision.o pager.o \
        $(DIFF_OBJS)
 
 LIBS = $(LIB_FILE)
@@ -223,11 +224,15 @@ ifeq ($(uname_S),Darwin)
        NEEDS_SSL_WITH_CRYPTO = YesPlease
        NEEDS_LIBICONV = YesPlease
        ## fink
-       ALL_CFLAGS += -I/sw/include
-       ALL_LDFLAGS += -L/sw/lib
+       ifeq ($(shell test -d /sw/lib && echo y),y)
+               ALL_CFLAGS += -I/sw/include
+               ALL_LDFLAGS += -L/sw/lib
+       endif
        ## darwinports
-       ALL_CFLAGS += -I/opt/local/include
-       ALL_LDFLAGS += -L/opt/local/lib
+       ifeq ($(shell test -d /opt/local/lib && echo y),y)
+               ALL_CFLAGS += -I/opt/local/include
+               ALL_LDFLAGS += -L/opt/local/lib
+       endif
 endif
 ifeq ($(uname_S),SunOS)
        NEEDS_SOCKET = YesPlease
@@ -324,7 +329,6 @@ ifndef NO_CURL
 endif
 
 ifndef NO_OPENSSL
-       LIB_OBJS += epoch.o
        OPENSSL_LIBSSL = -lssl
        ifdef OPENSSLDIR
                # Again this may be problematic -- gcc does not always want -R.
@@ -450,7 +454,7 @@ strip: $(PROGRAMS) git$X
 
 git$X: git.c $(LIB_FILE)
        $(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \
-               $(CFLAGS) $(COMPAT_CFLAGS) -o $@ $(filter %.c,$^) $(LIB_FILE)
+               $(ALL_CFLAGS) -o $@ $(filter %.c,$^) $(LIB_FILE) $(LIBS)
 
 $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
        rm -f $@
diff --git a/apply.c b/apply.c
index 9deb206faa5259e4f4f0a458ddc14dd4762da10b..c3699668671706d35b84d78cfa6ccee675f684b4 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -32,7 +32,7 @@ static int no_add = 0;
 static int show_index_info = 0;
 static int line_termination = '\n';
 static const char apply_usage[] =
-"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] <patch>...";
+"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] [--whitespace=<nowarn|warn|error|error-all|strip>] <patch>...";
 
 static enum whitespace_eol {
        nowarn_whitespace,
diff --git a/blame.c b/blame.c
index 1e655466a8f1e23a14bfeb8d198a74bd4eebe47b..7308c36d232e362b64dcb5b2bc60fbe3014bdb28 100644 (file)
--- a/blame.c
+++ b/blame.c
@@ -1,4 +1,10 @@
+/*
+ * Copyright (C) 2006, Fredrik Kuivinen <freku045@student.liu.se>
+ */
+
 #include <assert.h>
+#include <time.h>
+#include <sys/time.h>
 
 #include "cache.h"
 #include "refs.h"
 #include "commit.h"
 #include "tree.h"
 #include "blob.h"
-#include "epoch.h"
 #include "diff.h"
+#include "revision.h"
 
 #define DEBUG 0
 
-struct commit** blame_lines;
+struct commit **blame_lines;
 int num_blame_lines;
 
-struct util_info
-{
-    int* line_map;
-    int num_lines;
-    unsigned char sha1[20]; /* blob sha, not commit! */
-    char* buf;
-    unsigned long size;
+struct util_info {
+       int *line_map;
+       unsigned char sha1[20]; /* blob sha, not commit! */
+       char *buf;
+       unsigned long size;
+       int num_lines;
 //    const char* path;
 };
 
-struct chunk
-{
-    int off1, len1; // ---
-    int off2, len2; // +++
+struct chunk {
+       int off1, len1; // ---
+       int off2, len2; // +++
 };
 
-struct patch
-{
-    struct chunk* chunks;
-    int num;
+struct patch {
+       struct chunk *chunks;
+       int num;
 };
 
-static void get_blob(struct commitcommit);
+static void get_blob(struct commit *commit);
 
-int num_get_patch = 0;
-int num_commits = 0;
+/* Only used for statistics */
+static int num_get_patch = 0;
+static int num_commits = 0;
+static int patch_time = 0;
 
-struct patch* get_patch(struct commit* commit, struct commit* other)
+#define TEMPFILE_PATH_LEN 60
+static struct patch *get_patch(struct commit *commit, struct commit *other)
 {
-    struct patch* ret = xmalloc(sizeof(struct patch));
-    ret->chunks = NULL;
-    ret->num = 0;
-
-    struct util_info* info_c = (struct util_info*) commit->object.util;
-    struct util_info* info_o = (struct util_info*) other->object.util;
-
-    if(!memcmp(info_c->sha1, info_o->sha1, 20))
-        return ret;
-
-    get_blob(commit);
-    get_blob(other);
-
-    FILE* fout = fopen("/tmp/git-blame-tmp1", "w");
-    if(!fout)
-        die("fopen tmp1 failed: %s", strerror(errno));
-
-    if(fwrite(info_c->buf, info_c->size, 1, fout) != 1)
-        die("fwrite 1 failed: %s", strerror(errno));
-    fclose(fout);
-
-    fout = fopen("/tmp/git-blame-tmp2", "w");
-    if(!fout)
-        die("fopen tmp2 failed: %s", strerror(errno));
-
-    if(fwrite(info_o->buf, info_o->size, 1, fout) != 1)
-        die("fwrite 2 failed: %s", strerror(errno));
-    fclose(fout);
-
-    FILE* fin = popen("diff -u0 /tmp/git-blame-tmp1 /tmp/git-blame-tmp2", "r");
-    if(!fin)
-        die("popen failed: %s", strerror(errno));
-
-    char buf[1024];
-    while(fgets(buf, sizeof(buf), fin)) {
-        if(buf[0] != '@' || buf[1] != '@')
-            continue;
-
-        if(DEBUG)
-            printf("chunk line: %s", buf);
-        ret->num++;
-        ret->chunks = xrealloc(ret->chunks, sizeof(struct chunk)*ret->num);
-        struct chunk* chunk = &ret->chunks[ret->num-1];
-
-        assert(!strncmp(buf, "@@ -", 4));
-
-        char* start = buf+4;
-        char* sp = index(start, ' ');
-        *sp = '\0';
-        if(index(start, ',')) {
-            int ret = sscanf(start, "%d,%d", &chunk->off1, &chunk->len1);
-            assert(ret == 2);
-        } else {
-            int ret = sscanf(start, "%d", &chunk->off1);
-            assert(ret == 1);
-            chunk->len1 = 1;
-        }
-        *sp = ' ';
-
-        start = sp+1;
-        sp = index(start, ' ');
-        *sp = '\0';
-        if(index(start, ',')) {
-            int ret = sscanf(start, "%d,%d", &chunk->off2, &chunk->len2);
-            assert(ret == 2);
-        } else {
-            int ret = sscanf(start, "%d", &chunk->off2);
-            assert(ret == 1);
-            chunk->len2 = 1;
-        }
-        *sp = ' ';
-
-        if(chunk->off1 > 0)
-            chunk->off1 -= 1;
-        if(chunk->off2 > 0)
-            chunk->off2 -= 1;
-
-        assert(chunk->off1 >= 0);
-        assert(chunk->off2 >= 0);
-    }
-    fclose(fin);
-
-    num_get_patch++;
-    return ret;
+       struct patch *ret;
+       struct util_info *info_c = (struct util_info *)commit->object.util;
+       struct util_info *info_o = (struct util_info *)other->object.util;
+       char tmp_path1[TEMPFILE_PATH_LEN], tmp_path2[TEMPFILE_PATH_LEN];
+       char diff_cmd[TEMPFILE_PATH_LEN*2 + 20];
+       struct timeval tv_start, tv_end;
+       int fd;
+       FILE *fin;
+       char buf[1024];
+
+       ret = xmalloc(sizeof(struct patch));
+       ret->chunks = NULL;
+       ret->num = 0;
+
+       get_blob(commit);
+       get_blob(other);
+
+       gettimeofday(&tv_start, NULL);
+
+       fd = git_mkstemp(tmp_path1, TEMPFILE_PATH_LEN, "git-blame-XXXXXX");
+       if (fd < 0)
+               die("unable to create temp-file: %s", strerror(errno));
+
+       if (xwrite(fd, info_c->buf, info_c->size) != info_c->size)
+               die("write failed: %s", strerror(errno));
+       close(fd);
+
+       fd = git_mkstemp(tmp_path2, TEMPFILE_PATH_LEN, "git-blame-XXXXXX");
+       if (fd < 0)
+               die("unable to create temp-file: %s", strerror(errno));
+
+       if (xwrite(fd, info_o->buf, info_o->size) != info_o->size)
+               die("write failed: %s", strerror(errno));
+       close(fd);
+
+       sprintf(diff_cmd, "diff -u0 %s %s", tmp_path1, tmp_path2);
+       fin = popen(diff_cmd, "r");
+       if (!fin)
+               die("popen failed: %s", strerror(errno));
+
+       while (fgets(buf, sizeof(buf), fin)) {
+               struct chunk *chunk;
+               char *start, *sp;
+
+               if (buf[0] != '@' || buf[1] != '@')
+                       continue;
+
+               if (DEBUG)
+                       printf("chunk line: %s", buf);
+               ret->num++;
+               ret->chunks = xrealloc(ret->chunks,
+                                      sizeof(struct chunk) * ret->num);
+               chunk = &ret->chunks[ret->num - 1];
+
+               assert(!strncmp(buf, "@@ -", 4));
+
+               start = buf + 4;
+               sp = index(start, ' ');
+               *sp = '\0';
+               if (index(start, ',')) {
+                       int ret =
+                           sscanf(start, "%d,%d", &chunk->off1, &chunk->len1);
+                       assert(ret == 2);
+               } else {
+                       int ret = sscanf(start, "%d", &chunk->off1);
+                       assert(ret == 1);
+                       chunk->len1 = 1;
+               }
+               *sp = ' ';
+
+               start = sp + 1;
+               sp = index(start, ' ');
+               *sp = '\0';
+               if (index(start, ',')) {
+                       int ret =
+                           sscanf(start, "%d,%d", &chunk->off2, &chunk->len2);
+                       assert(ret == 2);
+               } else {
+                       int ret = sscanf(start, "%d", &chunk->off2);
+                       assert(ret == 1);
+                       chunk->len2 = 1;
+               }
+               *sp = ' ';
+
+               if (chunk->len1 == 0)
+                       chunk->off1++;
+               if (chunk->len2 == 0)
+                       chunk->off2++;
+
+               if (chunk->off1 > 0)
+                       chunk->off1--;
+               if (chunk->off2 > 0)
+                       chunk->off2--;
+
+               assert(chunk->off1 >= 0);
+               assert(chunk->off2 >= 0);
+       }
+       pclose(fin);
+       unlink(tmp_path1);
+       unlink(tmp_path2);
+
+       gettimeofday(&tv_end, NULL);
+       patch_time += 1000000 * (tv_end.tv_sec - tv_start.tv_sec) +
+               tv_end.tv_usec - tv_start.tv_usec;
+
+       num_get_patch++;
+       return ret;
 }
 
-void free_patch(struct patch* p)
+static void free_patch(struct patch *p)
 {
-    free(p->chunks);
-    free(p);
+       free(p->chunks);
+       free(p);
 }
 
-static int get_blob_sha1_internal(unsigned char *sha1, const char *base, int baselen,
-                                  const char *pathname, unsigned mode, int stage);
-
+static int get_blob_sha1_internal(unsigned char *sha1, const char *base,
+                                 int baselen, const char *pathname,
+                                 unsigned mode, int stage);
 
 static unsigned char blob_sha1[20];
-static int get_blob_sha1(struct tree* t, const char* pathname, unsigned char* sha1)
+static int get_blob_sha1(struct tree *t, const char *pathname,
+                        unsigned char *sha1)
 {
-    const char *pathspec[2];
-    pathspec[0] = pathname;
-    pathspec[1] = NULL;
-    memset(blob_sha1, 0, sizeof(blob_sha1));
-    read_tree_recursive(t, "", 0, 0, pathspec, get_blob_sha1_internal);
-
-    int i;
-    for(i = 0; i < 20; i++) {
-        if(blob_sha1[i] != 0)
-            break;
-    }
-
-    if(i == 20)
-        return -1;
-
-    memcpy(sha1, blob_sha1, 20);
-    return 0;
+       int i;
+       const char *pathspec[2];
+       pathspec[0] = pathname;
+       pathspec[1] = NULL;
+       memset(blob_sha1, 0, sizeof(blob_sha1));
+       read_tree_recursive(t, "", 0, 0, pathspec, get_blob_sha1_internal);
+
+       for (i = 0; i < 20; i++) {
+               if (blob_sha1[i] != 0)
+                       break;
+       }
+
+       if (i == 20)
+               return -1;
+
+       memcpy(sha1, blob_sha1, 20);
+       return 0;
 }
 
-static int get_blob_sha1_internal(unsigned char *sha1, const char *base, int baselen,
-                                  const char *pathname, unsigned mode, int stage)
+static int get_blob_sha1_internal(unsigned char *sha1, const char *base,
+                                 int baselen, const char *pathname,
+                                 unsigned mode, int stage)
 {
-//    printf("Got blob: %s base: '%s' baselen: %d pathname: '%s' mode: %o stage: %d\n",
-//           sha1_to_hex(sha1), base, baselen, pathname, mode, stage);
-
-    if(S_ISDIR(mode))
-        return READ_TREE_RECURSIVE;
+       if (S_ISDIR(mode))
+               return READ_TREE_RECURSIVE;
 
-    memcpy(blob_sha1, sha1, 20);
-    return -1;
+       memcpy(blob_sha1, sha1, 20);
+       return -1;
 }
 
-static void get_blob(struct commitcommit)
+static void get_blob(struct commit *commit)
 {
-    struct util_info* info = commit->object.util;
-    char type[20];
+       struct util_info *info = commit->object.util;
+       char type[20];
 
-    if(info->buf)
-        return;
+       if (info->buf)
+               return;
 
-    info->buf = read_sha1_file(info->sha1, type, &info->size);
-    assert(!strcmp(type, "blob"));
+       info->buf = read_sha1_file(info->sha1, type, &info->size);
+
+       assert(!strcmp(type, "blob"));
 }
 
-void print_patch(struct patch* p)
+/* For debugging only */
+static void print_patch(struct patch *p)
 {
-    printf("Num chunks: %d\n", p->num);
-    int i;
-    for(i = 0; i < p->num; i++) {
-        printf("%d,%d %d,%d\n", p->chunks[i].off1, p->chunks[i].len1, p->chunks[i].off2, p->chunks[i].len2);
-    }
+       int i;
+       printf("Num chunks: %d\n", p->num);
+       for (i = 0; i < p->num; i++) {
+               printf("%d,%d %d,%d\n", p->chunks[i].off1, p->chunks[i].len1,
+                      p->chunks[i].off2, p->chunks[i].len2);
+       }
 }
 
+/* For debugging only */
+static void print_map(struct commit *cmit, struct commit *other)
+{
+       struct util_info *util = cmit->object.util;
+       struct util_info *util2 = other->object.util;
+
+       int i;
+       int max =
+           util->num_lines >
+           util2->num_lines ? util->num_lines : util2->num_lines;
+       int num;
+
+       for (i = 0; i < max; i++) {
+               printf("i: %d ", i);
+               num = -1;
+
+               if (i < util->num_lines) {
+                       num = util->line_map[i];
+                       printf("%d\t", num);
+               } else
+                       printf("\t");
+
+               if (i < util2->num_lines) {
+                       int num2 = util2->line_map[i];
+                       printf("%d\t", num2);
+                       if (num != -1 && num2 != num)
+                               printf("---");
+               } else
+                       printf("\t");
+
+               printf("\n");
+       }
+}
 
 // p is a patch from commit to other.
-void fill_line_map(struct commit* commit, struct commit* other, struct patch* p)
+static void fill_line_map(struct commit *commit, struct commit *other,
+                         struct patch *p)
 {
-    int num_lines = ((struct util_info*) commit->object.util)->num_lines;
-    int* line_map = ((struct util_info*) commit->object.util)->line_map;
-    int num_lines2 = ((struct util_info*) other->object.util)->num_lines;
-    int* line_map2 = ((struct util_info*) other->object.util)->line_map;
-    int cur_chunk = 0;
-    int i1, i2;
-
-    if(p->num && DEBUG)
-        print_patch(p);
-
-    for(i1 = 0; i1 < num_lines; i1++)
-        line_map[i1] = -1;
-
-    if(DEBUG)
-        printf("num lines 1: %d num lines 2: %d\n", num_lines, num_lines2);
-
-    for(i1 = 0, i2 = 0; i1 < num_lines; i1++, i2++) {
-        if(DEBUG > 1)
-            printf("%d %d\n", i1, i2);
-
-        if(i2 >= num_lines2)
-            break;
-
-        line_map[i1] = line_map2[i2];
-
-        struct chunk* chunk = NULL;
-        if(cur_chunk < p->num)
-            chunk = &p->chunks[cur_chunk];
-
-        if(chunk && chunk->off1 == i1) {
-            i2 = chunk->off2;
-
-            if(chunk->len1 > 0)
-                i1 += chunk->len1-1;
-            if(chunk->len2 > 0)
-                i2 += chunk->len2-1;
-            cur_chunk++;
-        }
-    }
+       struct util_info *util = commit->object.util;
+       struct util_info *util2 = other->object.util;
+       int *map = util->line_map;
+       int *map2 = util2->line_map;
+       int cur_chunk = 0;
+       int i1, i2;
+
+       if (p->num && DEBUG)
+               print_patch(p);
+
+       if (DEBUG)
+               printf("num lines 1: %d num lines 2: %d\n", util->num_lines,
+                      util2->num_lines);
+
+       for (i1 = 0, i2 = 0; i1 < util->num_lines; i1++, i2++) {
+               struct chunk *chunk = NULL;
+               if (cur_chunk < p->num)
+                       chunk = &p->chunks[cur_chunk];
+
+               if (chunk && chunk->off1 == i1) {
+                       if (DEBUG && i2 != chunk->off2)
+                               printf("i2: %d off2: %d\n", i2, chunk->off2);
+
+                       assert(i2 == chunk->off2);
+
+                       i1--;
+                       i2--;
+                       if (chunk->len1 > 0)
+                               i1 += chunk->len1;
+
+                       if (chunk->len2 > 0)
+                               i2 += chunk->len2;
+
+                       cur_chunk++;
+               } else {
+                       if (i2 >= util2->num_lines)
+                               break;
+
+                       if (map[i1] != map2[i2] && map[i1] != -1) {
+                               if (DEBUG)
+                                       printf("map: i1: %d %d %p i2: %d %d %p\n",
+                                              i1, map[i1],
+                                              i1 != -1 ? blame_lines[map[i1]] : NULL,
+                                              i2, map2[i2],
+                                              i2 != -1 ? blame_lines[map2[i2]] : NULL);
+                               if (map2[i2] != -1 &&
+                                   blame_lines[map[i1]] &&
+                                   !blame_lines[map2[i2]])
+                                       map[i1] = map2[i2];
+                       }
+
+                       if (map[i1] == -1 && map2[i2] != -1)
+                               map[i1] = map2[i2];
+               }
+
+               if (DEBUG > 1)
+                       printf("l1: %d l2: %d i1: %d i2: %d\n",
+                              map[i1], map2[i2], i1, i2);
+       }
 }
 
-int map_line(struct commit* commit, int line)
+static int map_line(struct commit *commit, int line)
 {
-    struct util_info* info = commit->object.util;
-    assert(line >= 0 && line < info->num_lines);
-    return info->line_map[line];
+       struct util_info *info = commit->object.util;
+       assert(line >= 0 && line < info->num_lines);
+       return info->line_map[line];
 }
 
-int fill_util_info(struct commit* commit, const char* path)
+static int fill_util_info(struct commit *commit, const char *path)
 {
-    if(commit->object.util)
-        return 0;
-
-    struct util_info* util = xmalloc(sizeof(struct util_info));
-    util->buf = NULL;
-    util->size = 0;
-    util->num_lines = -1;
-    util->line_map = NULL;
-
-    commit->object.util = util;
-
-    if(get_blob_sha1(commit->tree, path, util->sha1))
-        return -1;
-
-    return 0;
+       struct util_info *util;
+       if (commit->object.util)
+               return 0;
+
+       util = xmalloc(sizeof(struct util_info));
+
+       if (get_blob_sha1(commit->tree, path, util->sha1)) {
+               free(util);
+               return 1;
+       } else {
+               util->buf = NULL;
+               util->size = 0;
+               util->line_map = NULL;
+               util->num_lines = -1;
+               commit->object.util = util;
+               return 0;
+       }
 }
 
-void alloc_line_map(struct commit* commit)
+static void alloc_line_map(struct commit *commit)
 {
-    struct util_info* util = commit->object.util;
-
-    if(util->line_map)
-        return;
+       struct util_info *util = commit->object.util;
+       int i;
 
-    get_blob(commit);
+       if (util->line_map)
+               return;
 
-    int i;
-    util->num_lines = 0;
-    for(i = 0; i < util->size; i++) {
-        if(util->buf[i] == '\n')
-            util->num_lines++;
-    }
-    util->line_map = xmalloc(sizeof(int)*util->num_lines);
-}
+       get_blob(commit);
 
-void copy_line_map(struct commit* dst, struct commit* src)
-{
-    struct util_info* u_dst = dst->object.util;
-    struct util_info* u_src = src->object.util;
+       util->num_lines = 0;
+       for (i = 0; i < util->size; i++) {
+               if (util->buf[i] == '\n')
+                       util->num_lines++;
+       }
+       if(util->buf[util->size - 1] != '\n')
+               util->num_lines++;
 
-    u_dst->line_map = u_src->line_map;
-    u_dst->num_lines = u_src->num_lines;
-    u_dst->buf = u_src->buf;
-    u_dst->size = u_src->size;
-}
+       util->line_map = xmalloc(sizeof(int) * util->num_lines);
 
-void process_commits(struct commit_list* list, const char* path)
-{
-    int i;
-
-    while(list) {
-        struct commit* commit = pop_commit(&list);
-        struct commit_list* parents;
-        struct util_info* info;
-
-        info = commit->object.util;
-        num_commits++;
-        if(DEBUG)
-            printf("\nProcessing commit: %d %s\n", num_commits, sha1_to_hex(commit->object.sha1));
-        for(parents = commit->parents;
-            parents != NULL; parents = parents->next) {
-            struct commit* parent = parents->item;
-
-            if(parse_commit(parent) < 0)
-                die("parse_commit error");
-
-            if(DEBUG)
-                printf("parent: %s\n", sha1_to_hex(parent->object.sha1));
-
-            if(fill_util_info(parent, path))
-                continue;
-
-            // Temporarily assign everything to the parent.
-            int num_blame = 0;
-            for(i = 0; i < num_blame_lines; i++) {
-                if(blame_lines[i] == commit) {
-                    num_blame++;
-                    blame_lines[i] = parent;
-                }
-            }
-
-            if(num_blame == 0)
-                continue;
-
-            struct patch* patch = get_patch(parent, commit);
-            if(patch->num == 0) {
-                copy_line_map(parent, commit);
-            } else {
-                alloc_line_map(parent);
-                fill_line_map(parent, commit, patch);
-            }
-
-            for(i = 0; i < patch->num; i++) {
-                int l;
-                for(l = 0; l < patch->chunks[i].len2; l++) {
-                    int mapped_line = map_line(commit, patch->chunks[i].off2 + l);
-                    if(mapped_line != -1 && blame_lines[mapped_line] == parent)
-                        blame_lines[mapped_line] = commit;
-                }
-            }
-            free_patch(patch);
-        }
-    }
+       for (i = 0; i < util->num_lines; i++)
+               util->line_map[i] = -1;
 }
 
-#define SEEN 1
-struct commit_list* get_commit_list(struct commit* commit, const char* pathname)
+static void init_first_commit(struct commit* commit, const char* filename)
 {
-    struct commit_list* ret = NULL;
-    struct commit_list* process = NULL;
-    unsigned char sha1[20];
-
-    commit_list_insert(commit, &process);
-
-    while(process) {
-        struct commit* com = pop_commit(&process);
-        if(com->object.flags & SEEN)
-            continue;
+       struct util_info* util;
+       int i;
 
-        com->object.flags |= SEEN;
-        commit_list_insert(com, &ret);
-        struct commit_list* parents;
+       if (fill_util_info(commit, filename))
+               die("fill_util_info failed");
 
-        parse_commit(com);
+       alloc_line_map(commit);
 
-        for(parents = com->parents;
-            parents != NULL; parents = parents->next) {
-            struct commit* parent = parents->item;
+       util = commit->object.util;
+       num_blame_lines = util->num_lines;
 
-            parse_commit(parent);
+       for (i = 0; i < num_blame_lines; i++)
+               util->line_map[i] = i;
+}
 
-            if(!get_blob_sha1(parent->tree, pathname, sha1))
-                commit_list_insert(parent, &process);
-        }
-    }
 
-    return ret;
+static void process_commits(struct rev_info *rev, const char *path,
+                           struct commit** initial)
+{
+       int i;
+       struct util_info* util;
+       int lines_left;
+       int *blame_p;
+       int *new_lines;
+       int new_lines_len;
+
+       struct commit* commit = get_revision(rev);
+       assert(commit);
+       init_first_commit(commit, path);
+
+       util = commit->object.util;
+       num_blame_lines = util->num_lines;
+       blame_lines = xmalloc(sizeof(struct commit *) * num_blame_lines);
+       for (i = 0; i < num_blame_lines; i++)
+               blame_lines[i] = NULL;
+
+       lines_left = num_blame_lines;
+       blame_p = xmalloc(sizeof(int) * num_blame_lines);
+       new_lines = xmalloc(sizeof(int) * num_blame_lines);
+       do {
+               struct commit_list *parents;
+               int num_parents;
+               struct util_info *util;
+
+               if (DEBUG)
+                       printf("\nProcessing commit: %d %s\n", num_commits,
+                              sha1_to_hex(commit->object.sha1));
+
+               if (lines_left == 0)
+                       return;
+
+               num_commits++;
+               memset(blame_p, 0, sizeof(int) * num_blame_lines);
+               new_lines_len = 0;
+               num_parents = 0;
+               for (parents = commit->parents;
+                    parents != NULL; parents = parents->next)
+                       num_parents++;
+
+               if(num_parents == 0)
+                       *initial = commit;
+
+               if(fill_util_info(commit, path))
+                       continue;
+
+               alloc_line_map(commit);
+               util = commit->object.util;
+
+               for (parents = commit->parents;
+                    parents != NULL; parents = parents->next) {
+                       struct commit *parent = parents->item;
+                       struct patch *patch;
+
+                       if (parse_commit(parent) < 0)
+                               die("parse_commit error");
+
+                       if (DEBUG)
+                               printf("parent: %s\n",
+                                      sha1_to_hex(parent->object.sha1));
+
+                       if(fill_util_info(parent, path)) {
+                               num_parents--;
+                               continue;
+                       }
+
+                       patch = get_patch(parent, commit);
+                        alloc_line_map(parent);
+                        fill_line_map(parent, commit, patch);
+
+                        for (i = 0; i < patch->num; i++) {
+                            int l;
+                            for (l = 0; l < patch->chunks[i].len2; l++) {
+                                int mapped_line =
+                                    map_line(commit, patch->chunks[i].off2 + l);
+                                if (mapped_line != -1) {
+                                    blame_p[mapped_line]++;
+                                    if (blame_p[mapped_line] == num_parents)
+                                        new_lines[new_lines_len++] = mapped_line;
+                                }
+                            }
+                       }
+                        free_patch(patch);
+               }
+
+               if (DEBUG)
+                       printf("parents: %d\n", num_parents);
+
+               for (i = 0; i < new_lines_len; i++) {
+                       int mapped_line = new_lines[i];
+                       if (blame_lines[mapped_line] == NULL) {
+                               blame_lines[mapped_line] = commit;
+                               lines_left--;
+                               if (DEBUG)
+                                       printf("blame: mapped: %d i: %d\n",
+                                              mapped_line, i);
+                       }
+               }
+       } while ((commit = get_revision(rev)) != NULL);
 }
 
 int main(int argc, const char **argv)
 {
-    unsigned char sha1[20];
-    struct commit *commit;
-    const char* filename;
-    int i;
-
-    setup_git_directory();
-
-    if (argc != 3)
-        die("Usage: blame commit-ish file");
-
-    if (get_sha1(argv[1], sha1))
-        die("get_sha1 failed");
-
-    commit = lookup_commit_reference(sha1);
-
-    filename = argv[2];
-
-    struct commit_list* list = get_commit_list(commit, filename);
-    sort_in_topological_order(&list, 1);
-
-    if(fill_util_info(commit, filename)) {
-        printf("%s not found in %s\n", filename, argv[1]);
-        return 0;
-    }
-    alloc_line_map(commit);
-
-    struct util_info* util = commit->object.util;
-    num_blame_lines = util->num_lines;
-    blame_lines = xmalloc(sizeof(struct commit*)*num_blame_lines);
-
-
-    for(i = 0; i < num_blame_lines; i++) {
-        blame_lines[i] = commit;
-
-        ((struct util_info*) commit->object.util)->line_map[i] = i;
-    }
-
-    process_commits(list, filename);
-
-    for(i = 0; i < num_blame_lines; i++) {
-        printf("%d %s\n", i+1-1, sha1_to_hex(blame_lines[i]->object.sha1));
-//        printf("%d %s\n", i+1-1, find_unique_abbrev(blame_lines[i]->object.sha1, 6));
-    }
-
-    if(DEBUG) {
-        printf("num get patch: %d\n", num_get_patch);
-        printf("num commits: %d\n", num_commits);
-    }
-
-    return 0;
+       int i;
+       struct commit *initial = NULL;
+       unsigned char sha1[20];
+       const char* filename;
+       int num_args;
+       const char* args[10];
+       struct rev_info rev;
+
+       setup_git_directory();
+
+       if (argc != 3)
+               die("Usage: blame commit-ish file");
+
+
+       filename = argv[2];
+
+       {
+               struct commit* commit;
+               if (get_sha1(argv[1], sha1))
+                       die("get_sha1 failed");
+               commit = lookup_commit_reference(sha1);
+
+               if (fill_util_info(commit, filename)) {
+                       printf("%s not found in %s\n", filename, argv[1]);
+                       return 1;
+               }
+       }
+
+       num_args = 0;
+       args[num_args++] = NULL;
+       args[num_args++] = "--topo-order";
+       args[num_args++] = "--remove-empty";
+       args[num_args++] = argv[1];
+       args[num_args++] = "--";
+       args[num_args++] = filename;
+       args[num_args] = NULL;
+
+       setup_revisions(num_args, args, &rev, "HEAD");
+       prepare_revision_walk(&rev);
+       process_commits(&rev, filename, &initial);
+
+       for (i = 0; i < num_blame_lines; i++) {
+               struct commit *c = blame_lines[i];
+               if (!c)
+                       c = initial;
+
+               printf("%d %.8s\n", i, sha1_to_hex(c->object.sha1));
+// printf("%d %s\n", i, find_unique_abbrev(blame_lines[i]->object.sha1, 6));
+       }
+
+       if (DEBUG) {
+               printf("num get patch: %d\n", num_get_patch);
+               printf("num commits: %d\n", num_commits);
+               printf("patch time: %f\n", patch_time / 1000000.0);
+               printf("initial: %s\n", sha1_to_hex(initial->object.sha1));
+       }
+
+       return 0;
 }
diff --git a/cache.h b/cache.h
index 0d3b244ddd52ada5f08fdb495237c1760778a289..8dc1de16e4270ed138007be6576259f97c159e2a 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -354,4 +354,7 @@ extern int copy_fd(int ifd, int ofd);
 extern int receive_unpack_pack(int fd[2], const char *me, int quiet);
 extern int receive_keep_pack(int fd[2], const char *me, int quiet);
 
+/* pager.c */
+extern void setup_pager(void);
+
 #endif /* CACHE_H */
index 96d66b43043ee1e2381bfb80bb2bc1c7063ecc38..1a613f3ee5ab4197fe18355ae65f2c9226b348cf 100644 (file)
@@ -4,6 +4,92 @@
  * Copyright (C) Linus Torvalds, 2005
  */
 #include "cache.h"
+#include "exec_cmd.h"
+
+static void flush_buffer(const char *buf, unsigned long size)
+{
+       while (size > 0) {
+               long ret = xwrite(1, buf, size);
+               if (ret < 0) {
+                       /* Ignore epipe */
+                       if (errno == EPIPE)
+                               break;
+                       die("git-cat-file: %s", strerror(errno));
+               } else if (!ret) {
+                       die("git-cat-file: disk full?");
+               }
+               size -= ret;
+               buf += ret;
+       }
+}
+
+static int pprint_tag(const unsigned char *sha1, const char *buf, unsigned long size)
+{
+       /* the parser in tag.c is useless here. */
+       const char *endp = buf + size;
+       const char *cp = buf;
+
+       while (cp < endp) {
+               char c = *cp++;
+               if (c != '\n')
+                       continue;
+               if (7 <= endp - cp && !memcmp("tagger ", cp, 7)) {
+                       const char *tagger = cp;
+
+                       /* Found the tagger line.  Copy out the contents
+                        * of the buffer so far.
+                        */
+                       flush_buffer(buf, cp - buf);
+
+                       /*
+                        * Do something intelligent, like pretty-printing
+                        * the date.
+                        */
+                       while (cp < endp) {
+                               if (*cp++ == '\n') {
+                                       /* tagger to cp is a line
+                                        * that has ident and time.
+                                        */
+                                       const char *sp = tagger;
+                                       char *ep;
+                                       unsigned long date;
+                                       long tz;
+                                       while (sp < cp && *sp != '>')
+                                               sp++;
+                                       if (sp == cp) {
+                                               /* give up */
+                                               flush_buffer(tagger,
+                                                            cp - tagger);
+                                               break;
+                                       }
+                                       while (sp < cp &&
+                                              !('0' <= *sp && *sp <= '9'))
+                                               sp++;
+                                       flush_buffer(tagger, sp - tagger);
+                                       date = strtoul(sp, &ep, 10);
+                                       tz = strtol(ep, NULL, 10);
+                                       sp = show_date(date, tz);
+                                       flush_buffer(sp, strlen(sp));
+                                       xwrite(1, "\n", 1);
+                                       break;
+                               }
+                       }
+                       break;
+               }
+               if (cp < endp && *cp == '\n')
+                       /* end of header */
+                       break;
+       }
+       /* At this point, we have copied out the header up to the end of
+        * the tagger line and cp points at one past \n.  It could be the
+        * next header line after the tagger line, or it could be another
+        * \n that marks the end of the headers.  We need to copy out the
+        * remainder as is.
+        */
+       if (cp < endp)
+               flush_buffer(cp, endp - cp);
+       return 0;
+}
 
 int main(int argc, char **argv)
 {
@@ -15,7 +101,7 @@ int main(int argc, char **argv)
 
        setup_git_directory();
        if (argc != 3 || get_sha1(argv[2], sha1))
-               usage("git-cat-file [-t|-s|-e|<type>] <sha1>");
+               usage("git-cat-file [-t|-s|-e|-p|<type>] <sha1>");
 
        opt = 0;
        if ( argv[1][0] == '-' ) {
@@ -43,6 +129,23 @@ int main(int argc, char **argv)
        case 'e':
                return !has_sha1_file(sha1);
 
+       case 'p':
+               if (get_sha1(argv[2], sha1) ||
+                   sha1_object_info(sha1, type, NULL))
+                       die("Not a valid object name %s", argv[2]);
+
+               /* custom pretty-print here */
+               if (!strcmp(type, "tree"))
+                       return execl_git_cmd("ls-tree", argv[2], NULL);
+
+               buf = read_sha1_file(sha1, type, &size);
+               if (!buf)
+                       die("Cannot read object %s", argv[2]);
+               if (!strcmp(type, "tag"))
+                       return pprint_tag(sha1, buf, size);
+
+               /* otherwise just spit out the data */
+               break;
        case 0:
                buf = read_object_with_reference(sha1, argv[1], &size, NULL);
                break;
@@ -54,18 +157,6 @@ int main(int argc, char **argv)
        if (!buf)
                die("git-cat-file %s: bad file", argv[2]);
 
-       while (size > 0) {
-               long ret = xwrite(1, buf, size);
-               if (ret < 0) {
-                       /* Ignore epipe */
-                       if (errno == EPIPE)
-                               break;
-                       die("git-cat-file: %s", strerror(errno));
-               } else if (!ret) {
-                       die("git-cat-file: disk full?");
-               }
-               size -= ret;
-               buf += ret;
-       }
+       flush_buffer(buf, size);
        return 0;
 }
index 957b4a86b07062b4cafaf9006bca6f7767b46ecf..f54c606414c27a7fc42a557fb0dee438b2abffec 100644 (file)
  *
  *     find . -name '*.h' -print0 | xargs -0 git-checkout-index -f --
  *
+ * or:
+ *
+ *     find . -name '*.h' -print0 | git-checkout-index -f -z --stdin
+ *
  * which will force all existing *.h files to be replaced with
  * their cached copies. If an empty command line implied "all",
  * then this would force-refresh everything in the cache, which
@@ -33,6 +37,8 @@
  * but get used to it in scripting!).
  */
 #include "cache.h"
+#include "strbuf.h"
+#include "quote.h"
 
 static const char *prefix;
 static int prefix_length;
@@ -114,6 +120,8 @@ int main(int argc, char **argv)
        int i;
        int newfd = -1;
        int all = 0;
+       int read_from_stdin = 0;
+       int line_termination = '\n';
 
        prefix = setup_git_directory();
        git_config(git_default_config);
@@ -156,6 +164,17 @@ int main(int argc, char **argv)
                                die("cannot open index.lock file.");
                        continue;
                }
+               if (!strcmp(arg, "-z")) {
+                       line_termination = 0;
+                       continue;
+               }
+               if (!strcmp(arg, "--stdin")) {
+                       if (i != argc - 1)
+                               die("--stdin must be at the end");
+                       read_from_stdin = 1;
+                       i++; /* do not consider arg as a file name */
+                       break;
+               }
                if (!strncmp(arg, "--prefix=", 9)) {
                        state.base_dir = arg+9;
                        state.base_dir_len = strlen(state.base_dir);
@@ -191,9 +210,31 @@ int main(int argc, char **argv)
 
                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));
        }
 
+       if (read_from_stdin) {
+               struct strbuf buf;
+               if (all)
+                       die("git-checkout-index: don't mix '--all' and '--stdin'");
+               strbuf_init(&buf);
+               while (1) {
+                       char *path_name;
+                       read_line(&buf, stdin, line_termination);
+                       if (buf.eof)
+                               break;
+                       if (line_termination && buf.buf[0] == '"')
+                               path_name = unquote_c_style(buf.buf, NULL);
+                       else
+                               path_name = buf.buf;
+                       checkout_file(prefix_path(prefix, prefix_length, path_name));
+                       if (path_name != buf.buf)
+                               free(path_name);
+               }
+       }
+
        if (all)
                checkout_all();
 
index 8f234772b0f1f872a86074c73bb2a6e7ded177ec..5135e361be46af0b4ffce03fc8582a7a6ad25970 100644 (file)
 ;;  - hook into file save (after-save-hook)
 ;;  - diff against other branch
 ;;  - renaming files from the status buffer
-;;  - support for appending signed-off-by
 ;;  - creating tags
 ;;  - fetch/pull
 ;;  - switching branches
 ;;  - revlist browser
 ;;  - git-show-branch browser
-;;  - customize support
 ;;  - menus
 ;;
 
 (require 'ewoc)
 
 
-;;;; Faces
+;;;; Customizations
 ;;;; ------------------------------------------------------------
 
+(defgroup git nil
+  "Git user interface")
+
+(defcustom git-committer-name nil
+  "User name to use for commits.
+The default is to fall back to `add-log-full-name' and then `user-full-name'."
+  :group 'git
+  :type '(choice (const :tag "Default" nil)
+                 (string :tag "Name")))
+
+(defcustom git-committer-email nil
+  "Email address to use for commits.
+The default is to fall back to `add-log-mailing-address' and then `user-mail-address'."
+  :group 'git
+  :type '(choice (const :tag "Default" nil)
+                 (string :tag "Email")))
+
+(defcustom git-commits-coding-system 'utf-8
+  "Default coding system for the log message of git commits."
+  :group 'git
+  :type 'coding-system)
+
+(defcustom git-append-signed-off-by nil
+  "Whether to append a Signed-off-by line to the commit message before editing."
+  :group 'git
+  :type 'boolean)
+
+(defcustom git-per-dir-ignore-file ".gitignore"
+  "Name of the per-directory ignore file."
+  :group 'git
+  :type 'string)
+
 (defface git-status-face
   '((((class color) (background light)) (:foreground "purple")))
-  "Git mode face used to highlight added and modified files.")
+  "Git mode face used to highlight added and modified files."
+  :group 'git)
 
 (defface git-unmerged-face
   '((((class color) (background light)) (:foreground "red" :bold t)))
-  "Git mode face used to highlight unmerged files.")
+  "Git mode face used to highlight unmerged files."
+  :group 'git)
 
 (defface git-unknown-face
   '((((class color) (background light)) (:foreground "goldenrod" :bold t)))
-  "Git mode face used to highlight unknown files.")
+  "Git mode face used to highlight unknown files."
+  :group 'git)
 
 (defface git-uptodate-face
   '((((class color) (background light)) (:foreground "grey60")))
-  "Git mode face used to highlight up-to-date files.")
+  "Git mode face used to highlight up-to-date files."
+  :group 'git)
 
 (defface git-ignored-face
   '((((class color) (background light)) (:foreground "grey60")))
-  "Git mode face used to highlight ignored files.")
+  "Git mode face used to highlight ignored files."
+  :group 'git)
 
 (defface git-mark-face
   '((((class color) (background light)) (:foreground "red" :bold t)))
-  "Git mode face used for the file marks.")
+  "Git mode face used for the file marks."
+  :group 'git)
 
 (defface git-header-face
   '((((class color) (background light)) (:foreground "blue")))
-  "Git mode face used for commit headers.")
+  "Git mode face used for commit headers."
+  :group 'git)
 
 (defface git-separator-face
   '((((class color) (background light)) (:foreground "brown")))
-  "Git mode face used for commit separator.")
+  "Git mode face used for commit separator."
+  :group 'git)
 
 (defface git-permission-face
   '((((class color) (background light)) (:foreground "green" :bold t)))
-  "Git mode face used for permission changes.")
-
-(defvar git-committer-name nil
-  "*User name to use for commits.
-If not set, fall back to `add-log-full-name' and then `user-full-name'.")
-
-(defvar git-committer-email nil
-  "*Email address to use for commits.
-If not set, fall back to `add-log-mailing-address' and then `user-mail-address'.")
-
-(defvar git-commits-coding-system 'utf-8
-  "Default coding system for git commits.")
-
-(defconst git-log-msg-separator "--- log message follows this line ---")
-
-(defconst git-per-dir-ignore-file ".gitignore"
-  "Name of the per-directory ignore file.")
+  "Git mode face used for permission changes."
+  :group 'git)
 
 
 ;;;; Utilities
 ;;;; ------------------------------------------------------------
 
+(defconst git-log-msg-separator "--- log message follows this line ---")
+
 (defun git-get-env-strings (env)
   "Build a list of NAME=VALUE strings from a list of environment strings."
   (mapcar (lambda (entry) (concat (car entry) "=" (cdr entry))) env))
@@ -213,14 +238,19 @@ If not set, fall back to `add-log-mailing-address' and then `user-mail-address'.
   "Add a file name to the ignore file in its directory."
   (let* ((fullname (expand-file-name file))
          (dir (file-name-directory fullname))
-         (name (file-name-nondirectory fullname)))
+         (name (file-name-nondirectory fullname))
+         (ignore-name (expand-file-name git-per-dir-ignore-file dir))
+         (created (not (file-exists-p ignore-name))))
   (save-window-excursion
-    (set-buffer (find-file-noselect (expand-file-name git-per-dir-ignore-file dir)))
+    (set-buffer (find-file-noselect ignore-name))
     (goto-char (point-max))
     (unless (zerop (current-column)) (insert "\n"))
     (insert name "\n")
     (sort-lines nil (point-min) (point-max))
-    (save-buffer))))
+    (save-buffer))
+  (when created
+    (git-run-command nil nil "update-index" "--info-only" "--add" "--" (file-relative-name ignore-name)))
+  (git-add-status-file (if created 'added 'modified) (file-relative-name ignore-name))))
 
 
 ;;;; Wrappers for basic git commands
@@ -272,7 +302,7 @@ If not set, fall back to `add-log-mailing-address' and then `user-mail-address'.
     (with-current-buffer buffer
       (goto-char (point-min))
       (if
-          (setq log-start (re-search-forward (concat "^" git-log-msg-separator "\n") nil t))
+          (setq log-start (re-search-forward (concat "^" (regexp-quote git-log-msg-separator) "\n") nil t))
           (save-restriction
             (narrow-to-region (point-min) log-start)
             (goto-char (point-min))
@@ -388,9 +418,9 @@ If not set, fall back to `add-log-mailing-address' and then `user-mail-address'.
   (propertize
    (if (or (not old-perm)
            (not new-perm)
-           (eq 0 (logand #O111 (logxor old-perm new-perm))))
+           (eq 0 (logand ?\111 (logxor old-perm new-perm))))
        "  "
-     (if (eq 0 (logand #O111 old-perm)) "+x" "-x"))
+     (if (eq 0 (logand ?\111 old-perm)) "+x" "-x"))
   'face 'git-permission-face))
 
 (defun git-fileinfo-prettyprint (info)
@@ -787,7 +817,8 @@ If not set, fall back to `add-log-mailing-address' and then `user-mail-address'.
   (unless git-status (error "Not in git-status buffer."))
   (let ((buffer (get-buffer-create "*git-commit*"))
         (merge-heads (git-get-merge-heads))
-        (dir default-directory))
+        (dir default-directory)
+        (sign-off git-append-signed-off-by))
     (with-current-buffer buffer
       (when (eq 0 (buffer-size))
         (cd dir)
@@ -804,9 +835,18 @@ If not set, fall back to `add-log-mailing-address' and then `user-mail-address'.
           'face 'git-header-face)
          (propertize git-log-msg-separator 'face 'git-separator-face)
          "\n")
-        (when (and merge-heads (file-readable-p ".git/MERGE_MSG"))
-          (insert-file-contents ".git/MERGE_MSG"))))
-      (log-edit #'git-do-commit nil #'git-log-edit-files buffer)))
+        (cond ((and merge-heads (file-readable-p ".git/MERGE_MSG"))
+               (insert-file-contents ".git/MERGE_MSG"))
+              (sign-off
+               (insert (format "\n\nSigned-off-by: %s <%s>\n"
+                               (git-get-committer-name) (git-get-committer-email)))))))
+    (let ((log-edit-font-lock-keywords
+           `(("^\\(Author:\\|Date:\\|Parent:\\|Signed-off-by:\\)\\(.*\\)"
+              (1 font-lock-keyword-face)
+              (2 font-lock-function-name-face))
+             (,(concat "^\\(" (regexp-quote git-log-msg-separator) "\\)$")
+              (1 font-lock-comment-face)))))
+      (log-edit #'git-do-commit nil #'git-log-edit-files buffer))))
 
 (defun git-find-file ()
   "Visit the current file in its own buffer."
@@ -891,7 +931,7 @@ If not set, fall back to `add-log-mailing-address' and then `user-mail-address'.
     (define-key map "d"    diff-map)
     (define-key map "="   'git-diff-file)
     (define-key map "f"   'git-find-file)
-    (define-key map [RET] 'git-find-file)
+    (define-key map "\r"  'git-find-file)
     (define-key map "g"   'git-refresh-status)
     (define-key map "i"   'git-ignore-file)
     (define-key map "l"   'git-log-file)
@@ -937,6 +977,7 @@ Commands:
     (erase-buffer)
   (let ((status (ewoc-create 'git-fileinfo-prettyprint "" "")))
     (set (make-local-variable 'git-status) status))
+  (set (make-local-variable 'list-buffers-directory) default-directory)
   (run-hooks 'git-status-mode-hook)))
 
 (defun git-status (dir)
@@ -946,8 +987,8 @@ Commands:
   (if (file-directory-p (concat (file-name-as-directory dir) ".git"))
       (let ((buffer (create-file-buffer (expand-file-name "*git-status*" dir))))
         (switch-to-buffer buffer)
-        (git-status-mode)
         (cd dir)
+        (git-status-mode)
         (git-refresh-status)
         (goto-char (point-min)))
     (message "%s is not a git working tree." dir)))
diff --git a/contrib/emacs/vc-git.el b/contrib/emacs/vc-git.el
new file mode 100644 (file)
index 0000000..2453cdc
--- /dev/null
@@ -0,0 +1,135 @@
+;;; vc-git.el --- VC backend for the git version control system
+
+;; Copyright (C) 2006 Alexandre Julliard
+
+;; 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., 59 Temple Place, Suite 330, Boston,
+;; MA 02111-1307 USA
+
+;;; Commentary:
+
+;; This file contains a VC backend for the git version control
+;; system.
+;;
+;; To install: put this file on the load-path and add GIT to the list
+;; of supported backends in `vc-handled-backends'.
+;;
+;; TODO
+;;  - changelog generation
+;;  - working with revisions other than HEAD
+;;
+
+(defvar git-commits-coding-system 'utf-8
+  "Default coding system for git commits.")
+
+(defun vc-git--run-command-string (file &rest args)
+  "Run a git command on FILE and return its output as string."
+  (let* ((ok t)
+         (str (with-output-to-string
+                (with-current-buffer standard-output
+                  (unless (eq 0 (apply #'call-process "git" nil '(t nil) nil
+                                       (append args (list (file-relative-name file)))))
+                    (setq ok nil))))))
+    (and ok str)))
+
+(defun vc-git--run-command (file &rest args)
+  "Run a git command on FILE, discarding any output."
+  (let ((name (file-relative-name file)))
+    (eq 0 (apply #'call-process "git" nil (get-buffer "*Messages") nil (append args (list name))))))
+
+(defun vc-git-registered (file)
+  "Check whether FILE is registered with git."
+  (with-temp-buffer
+    (let* ((dir (file-name-directory file))
+           (name (file-relative-name file dir)))
+      (when dir (cd dir))
+      (and (eq 0 (call-process "git" nil '(t nil) nil "ls-files" "-c" "-z" "--" name))
+           (let ((str (buffer-string)))
+             (and (> (length str) (length name))
+                  (string= (substring str 0 (1+ (length name))) (concat name "\0"))))))))
+
+(defun vc-git-state (file)
+  "git-specific version of `vc-state'."
+  (let ((diff (vc-git--run-command-string file "diff-index" "-z" "HEAD" "--")))
+    (if (and diff (string-match ":[0-7]\\{6\\} [0-7]\\{6\\} [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} [ADMU]\0[^\0]+\0" diff))
+        'edited
+      'up-to-date)))
+
+(defun vc-git-workfile-version (file)
+  "git-specific version of `vc-workfile-version'."
+  (let ((str (with-output-to-string
+               (with-current-buffer standard-output
+                 (call-process "git" nil '(t nil) nil "symbolic-ref" "HEAD")))))
+    (if (string-match "^\\(refs/heads/\\)?\\(.+\\)$" str)
+        (match-string 2 str)
+      str)))
+
+(defun vc-git-revert (file &optional contents-done)
+  "Revert FILE to the version stored in the git repository."
+  (if contents-done
+      (vc-git--run-command file "update-index" "--")
+    (vc-git--run-command file "checkout" "HEAD")))
+
+(defun vc-git-checkout-model (file)
+  'implicit)
+
+(defun vc-git-workfile-unchanged-p (file)
+  (let ((sha1 (vc-git--run-command-string file "hash-object" "--"))
+        (head (vc-git--run-command-string file "ls-tree" "-z" "HEAD" "--")))
+    (and head
+         (string-match "[0-7]\\{6\\} blob \\([0-9a-f]\\{40\\}\\)\t[^\0]+\0" head)
+         (string= (car (split-string sha1 "\n")) (match-string 1 head)))))
+
+(defun vc-git-register (file &optional rev comment)
+  "Register FILE into the git version-control system."
+  (vc-git--run-command file "update-index" "--add" "--"))
+
+(defun vc-git-print-log (file)
+  (let ((name (file-relative-name file))
+        (coding-system-for-read git-commits-coding-system))
+    (vc-do-command nil 'async "git" name "rev-list" "--pretty" "HEAD" "--")))
+
+(defun vc-git-diff (file &optional rev1 rev2)
+  (let ((name (file-relative-name file)))
+    (if (and rev1 rev2)
+        (vc-do-command "*vc-diff*" 0 "git" name "diff-tree" "-p" rev1 rev2 "--")
+      (vc-do-command "*vc-diff*" 0 "git" name "diff-index" "-p" (or rev1 "HEAD") "--"))
+    ; git-diff-index doesn't set exit status like diff does
+    (if (vc-git-workfile-unchanged-p file) 0 1)))
+
+(defun vc-git-checkin (file rev comment)
+  (let ((coding-system-for-write git-commits-coding-system))
+    (vc-git--run-command file "commit" "-m" comment "--only" "--")))
+
+(defun vc-git-checkout (file &optional editable rev destfile)
+  (vc-git--run-command file "checkout" (or rev "HEAD")))
+
+(defun vc-git-annotate-command (file buf &optional rev)
+  ; FIXME: rev is ignored
+  (let ((name (file-relative-name file)))
+    (call-process "git" nil buf nil "annotate" name)))
+
+(defun vc-git-annotate-time ()
+  (and (re-search-forward "[0-9a-f]+\t(.*\t\\([0-9]+\\)-\\([0-9]+\\)-\\([0-9]+\\) \\([0-9]+\\):\\([0-9]+\\):\\([0-9]+\\) \\([-+0-9]+\\)\t[0-9]+)" nil t)
+       (vc-annotate-convert-time
+        (apply #'encode-time (mapcar (lambda (match) (string-to-number (match-string match))) '(6 5 4 3 2 1 7))))))
+
+;; Not really useful since we can't do anything with the revision yet
+;;(defun vc-annotate-extract-revision-at-line ()
+;;  (save-excursion
+;;    (move-beginning-of-line 1)
+;;    (and (looking-at "[0-9a-f]+")
+;;         (buffer-substring (match-beginning 0) (match-end 0)))))
+
+(provide 'vc-git)
index 0b7416526dfd3dd7a77e55a9963eaac8aacb6023..3c860e458c2894c0addf008e4f011abb68e8b562 100755 (executable)
@@ -4,19 +4,12 @@
 use warnings;
 use strict;
 use vars qw/   $AUTHOR $VERSION
-               $SVN_URL $SVN_INFO $SVN_WC
+               $SVN_URL $SVN_INFO $SVN_WC $SVN_UUID
                $GIT_SVN_INDEX $GIT_SVN
                $GIT_DIR $REV_DIR/;
 $AUTHOR = 'Eric Wong <normalperson@yhbt.net>';
 $VERSION = '0.10.0';
 $GIT_DIR = $ENV{GIT_DIR} || "$ENV{PWD}/.git";
-$GIT_SVN = $ENV{GIT_SVN_ID} || 'git-svn';
-$GIT_SVN_INDEX = "$GIT_DIR/$GIT_SVN/index";
-$ENV{GIT_DIR} ||= $GIT_DIR;
-$SVN_URL = undef;
-$REV_DIR = "$GIT_DIR/$GIT_SVN/revs";
-$SVN_WC = "$GIT_DIR/$GIT_SVN/tree";
-
 # make sure the svn binary gives consistent output between locales and TZs:
 $ENV{TZ} = 'UTC';
 $ENV{LC_ALL} = 'C';
@@ -24,6 +17,7 @@
 # If SVN:: library support is added, please make the dependencies
 # optional and preserve the capability to use the command-line client.
 # use eval { require SVN::... } to make it lazy load
+# We don't use any modules not in the standard Perl distribution:
 use Carp qw/croak/;
 use IO::File qw//;
 use File::Basename qw/dirname basename/;
 use File::Spec qw//;
 use POSIX qw/strftime/;
 my $sha1 = qr/[a-f\d]{40}/;
-my $sha1_short = qr/[a-f\d]{6,40}/;
+my $sha1_short = qr/[a-f\d]{4,40}/;
 my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit,
-       $_find_copies_harder, $_l, $_version);
-
-GetOptions(    'revision|r=s' => \$_revision,
-               'no-ignore-externals' => \$_no_ignore_ext,
-               'stdin|' => \$_stdin,
-               'edit|e' => \$_edit,
-               'rmdir' => \$_rmdir,
-               'help|H|h' => \$_help,
-               'find-copies-harder' => \$_find_copies_harder,
-               'l=i' => \$_l,
-               'version|V' => \$_version,
-               'no-stop-on-copy' => \$_no_stop_copy );
+       $_find_copies_harder, $_l, $_version, $_upgrade, $_authors);
+my (@_branch_from, %tree_map, %users);
+
+my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext,
+               'branch|b=s' => \@_branch_from,
+               'authors-file|A=s' => \$_authors );
 my %cmd = (
-       fetch => [ \&fetch, "Download new revisions from SVN" ],
-       init => [ \&init, "Initialize and fetch (import)"],
-       commit => [ \&commit, "Commit git revisions to SVN" ],
-       'show-ignore' => [ \&show_ignore, "Show svn:ignore listings" ],
-       rebuild => [ \&rebuild, "Rebuild git-svn metadata (after git clone)" ],
-       help => [ \&usage, "Show help" ],
+       fetch => [ \&fetch, "Download new revisions from SVN",
+                       { 'revision|r=s' => \$_revision, %fc_opts } ],
+       init => [ \&init, "Initialize and fetch (import)", { } ],
+       commit => [ \&commit, "Commit git revisions to SVN",
+                       {       'stdin|' => \$_stdin,
+                               'edit|e' => \$_edit,
+                               'rmdir' => \$_rmdir,
+                               'find-copies-harder' => \$_find_copies_harder,
+                               'l=i' => \$_l,
+                               %fc_opts,
+                       } ],
+       'show-ignore' => [ \&show_ignore, "Show svn:ignore listings", { } ],
+       rebuild => [ \&rebuild, "Rebuild git-svn metadata (after git clone)",
+                       { 'no-ignore-externals' => \$_no_ignore_ext,
+                         'upgrade' => \$_upgrade } ],
 );
 my $cmd;
 for (my $i = 0; $i < @ARGV; $i++) {
        }
 };
 
-# we may be called as git-svn-(command), or git-svn(command).
-foreach (keys %cmd) {
-       if (/git\-svn\-?($_)(?:\.\w+)?$/) {
-               $cmd = $1;
-               last;
-       }
-}
+my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd);
+
+GetOptions(%opts, 'help|H|h' => \$_help,
+               'version|V' => \$_version,
+               'id|i=s' => \$GIT_SVN) or exit 1;
+
+$GIT_SVN ||= $ENV{GIT_SVN_ID} || 'git-svn';
+$GIT_SVN_INDEX = "$GIT_DIR/$GIT_SVN/index";
+$ENV{GIT_DIR} ||= $GIT_DIR;
+$SVN_URL = undef;
+$REV_DIR = "$GIT_DIR/$GIT_SVN/revs";
+$SVN_WC = "$GIT_DIR/$GIT_SVN/tree";
+
 usage(0) if $_help;
 version() if $_version;
-usage(1) unless (defined $cmd);
+usage(1) unless defined $cmd;
+load_authors() if $_authors;
 svn_check_ignore_externals();
 $cmd{$cmd}->[0]->(@ARGV);
 exit 0;
@@ -84,15 +88,25 @@ sub usage {
        print $fd <<"";
 git-svn - bidirectional operations between a single Subversion tree and git
 Usage: $0 <command> [options] [arguments]\n
-Available commands:
+
+       print $fd "Available commands:\n" unless $cmd;
 
        foreach (sort keys %cmd) {
-               print $fd '  ',pack('A10',$_),$cmd{$_}->[1],"\n";
+               next if $cmd && $cmd ne $_;
+               print $fd '  ',pack('A13',$_),$cmd{$_}->[1],"\n";
+               foreach (keys %{$cmd{$_}->[2]}) {
+                       # prints out arguments as they should be passed:
+                       my $x = s#=s$## ? '<arg>' : s#=i$## ? '<num>' : '';
+                       print $fd ' ' x 17, join(', ', map { length $_ > 1 ?
+                                                       "--$_" : "-$_" }
+                                               split /\|/,$_)," $x\n";
+               }
        }
        print $fd <<"";
-\nGIT_SVN_ID may be set in the environment to an arbitrary identifier if
-you're tracking multiple SVN branches/repositories in one git repository
-and want to keep them separate.
+\nGIT_SVN_ID may be set in the environment or via the --id/-i switch to an
+arbitrary identifier if you're tracking multiple SVN branches/repositories in
+one git repository and want to keep them separate.  See git-svn(1) for more
+information.
 
        exit $exit;
 }
@@ -104,15 +118,19 @@ sub version {
 
 sub rebuild {
        $SVN_URL = shift or undef;
-       my $repo_uuid;
        my $newest_rev = 0;
+       if ($_upgrade) {
+               sys('git-update-ref',"refs/remotes/$GIT_SVN","$GIT_SVN-HEAD");
+       } else {
+               check_upgrade_needed();
+       }
 
        my $pid = open(my $rev_list,'-|');
        defined $pid or croak $!;
        if ($pid == 0) {
-               exec("git-rev-list","$GIT_SVN-HEAD") or croak $!;
+               exec("git-rev-list","refs/remotes/$GIT_SVN") or croak $!;
        }
-       my $first;
+       my $latest;
        while (<$rev_list>) {
                chomp;
                my $c = $_;
@@ -132,18 +150,20 @@ sub rebuild {
                                        "$c, $id\n";
                        }
                }
+
+               # if we merged or otherwise started elsewhere, this is
+               # how we break out of it
+               next if (defined $SVN_UUID && ($uuid ne $SVN_UUID));
+               next if (defined $SVN_URL && ($url ne $SVN_URL));
+
                print "r$rev = $c\n";
-               unless (defined $first) {
+               unless (defined $latest) {
                        if (!$SVN_URL && !$url) {
                                croak "SVN repository location required: $url\n";
                        }
                        $SVN_URL ||= $url;
-                       $repo_uuid = setup_git_svn();
-                       $first = $rev;
-               }
-               if ($uuid ne $repo_uuid) {
-                       croak "Repository UUIDs do not match!\ngot: $uuid\n",
-                                               "expected: $repo_uuid\n";
+                       $SVN_UUID ||= setup_git_svn();
+                       $latest = $rev;
                }
                assert_revision_eq_or_unknown($rev, $c);
                sys('git-update-ref',"$GIT_SVN/revs/$rev",$c);
@@ -151,7 +171,7 @@ sub rebuild {
        }
        close $rev_list or croak $?;
        if (!chdir $SVN_WC) {
-               my @svn_co = ('svn','co',"-r$first");
+               my @svn_co = ('svn','co',"-r$latest");
                push @svn_co, '--ignore-externals' unless $_no_ignore_ext;
                sys(@svn_co, $SVN_URL, $SVN_WC);
                chdir $SVN_WC or croak $!;
@@ -168,6 +188,13 @@ sub rebuild {
                exec('git-write-tree');
        }
        waitpid $pid, 0;
+
+       if ($_upgrade) {
+               print STDERR <<"";
+Keeping deprecated refs/head/$GIT_SVN-HEAD for now.  Please remove it
+when you have upgraded your tools and habits to use refs/remotes/$GIT_SVN
+
+       }
 }
 
 sub init {
@@ -180,6 +207,7 @@ sub init {
 
 sub fetch {
        my (@parents) = @_;
+       check_upgrade_needed();
        $SVN_URL ||= file_to_s("$GIT_DIR/$GIT_SVN/info/url");
        my @log_args = -d $SVN_WC ? ($SVN_WC) : ($SVN_URL);
        unless ($_revision) {
@@ -199,9 +227,6 @@ sub fetch {
                sys(@svn_co, $SVN_URL, $SVN_WC);
                chdir $SVN_WC or croak $!;
                $last_commit = git_commit($base, @parents);
-               unless (-f "$GIT_DIR/refs/heads/master") {
-                       sys(qw(git-update-ref refs/heads/master),$last_commit);
-               }
                assert_svn_wc_clean($base->{revision}, $last_commit);
        } else {
                chdir $SVN_WC or croak $!;
@@ -217,16 +242,20 @@ sub fetch {
                $last_commit = git_commit($log_msg, $last_commit, @parents);
        }
        assert_svn_wc_clean($last_rev, $last_commit);
+       unless (-e "$GIT_DIR/refs/heads/master") {
+               sys(qw(git-update-ref refs/heads/master),$last_commit);
+       }
        return pop @$svn_log;
 }
 
 sub commit {
        my (@commits) = @_;
+       check_upgrade_needed();
        if ($_stdin || !@commits) {
                print "Reading from stdin...\n";
                @commits = ();
                while (<STDIN>) {
-                       if (/\b([a-f\d]{6,40})\b/) {
+                       if (/\b($sha1_short)\b/o) {
                                unshift @commits, $1;
                        }
                }
@@ -248,7 +277,6 @@ sub commit {
        chdir $SVN_WC or croak $!;
        my $svn_current_rev =  svn_info('.')->{'Last Changed Rev'};
        foreach my $c (@revs) {
-               print "Committing $c\n";
                my $mods = svn_checkout_tree($svn_current_rev, $c);
                if (scalar @$mods == 0) {
                        print "Skipping, no changes detected\n";
@@ -295,24 +323,31 @@ sub setup_git_svn {
        mkpath(["$GIT_DIR/$GIT_SVN/info"]);
        mkpath([$REV_DIR]);
        s_to_file($SVN_URL,"$GIT_DIR/$GIT_SVN/info/url");
-       my $uuid = svn_info($SVN_URL)->{'Repository UUID'} or
+       $SVN_UUID = svn_info($SVN_URL)->{'Repository UUID'} or
                                        croak "Repository UUID unreadable\n";
-       s_to_file($uuid,"$GIT_DIR/$GIT_SVN/info/uuid");
+       s_to_file($SVN_UUID,"$GIT_DIR/$GIT_SVN/info/uuid");
 
        open my $fd, '>>', "$GIT_DIR/$GIT_SVN/info/exclude" or croak $!;
        print $fd '.svn',"\n";
        close $fd or croak $!;
-       return $uuid;
+       return $SVN_UUID;
 }
 
 sub assert_svn_wc_clean {
        my ($svn_rev, $treeish) = @_;
        croak "$svn_rev is not an integer!\n" unless ($svn_rev =~ /^\d+$/);
        croak "$treeish is not a sha1!\n" unless ($treeish =~ /^$sha1$/o);
-       my $svn_info = svn_info('.');
-       if ($svn_rev != $svn_info->{'Last Changed Rev'}) {
-               croak "Expected r$svn_rev, got r",
-                               $svn_info->{'Last Changed Rev'},"\n";
+       my $lcr = svn_info('.')->{'Last Changed Rev'};
+       if ($svn_rev != $lcr) {
+               print STDERR "Checking for copy-tree ... ";
+               # use
+               my @diff = grep(/^Index: /,(safe_qx(qw(svn diff),
+                                               "-r$lcr:$svn_rev")));
+               if (@diff) {
+                       croak "Nope!  Expected r$svn_rev, got r$lcr\n";
+               } else {
+                       print STDERR "OK!\n";
+               }
        }
        my @status = grep(!/^Performing status on external/,(`svn status`));
        @status = grep(!/^\s*$/,@status);
@@ -495,7 +530,7 @@ sub svn_checkout_tree {
        my ($svn_rev, $treeish) = @_;
        my $from = file_to_s("$REV_DIR/$svn_rev");
        assert_svn_wc_clean($svn_rev,$from);
-       print "diff-tree '$from' '$treeish'\n";
+       print "diff-tree $from $treeish\n";
        my $pid = open my $diff_fh, '-|';
        defined $pid or croak $!;
        if ($pid == 0) {
@@ -506,7 +541,7 @@ sub svn_checkout_tree {
        }
        my $mods = parse_diff_tree($diff_fh);
        unless (@$mods) {
-               # git can do empty commits, SVN doesn't allow it...
+               # git can do empty commits, but SVN doesn't allow it...
                return $mods;
        }
        my ($rm, $add) = precommit_check($mods);
@@ -593,7 +628,7 @@ sub svn_commit_tree {
        my ($svn_rev, $commit) = @_;
        my $commit_msg = "$GIT_DIR/$GIT_SVN/.svn-commit.tmp.$$";
        my %log_msg = ( msg => '' );
-       open my $msg, '>', $commit_msg  or croak $!;
+       open my $msg, '>', $commit_msg or croak $!;
 
        chomp(my $type = `git-cat-file -t $commit`);
        if ($type eq 'commit') {
@@ -607,8 +642,10 @@ sub svn_commit_tree {
                while (<$msg_fh>) {
                        if (!$in_msg) {
                                $in_msg = 1 if (/^\s*$/);
+                       } elsif (/^git-svn-id: /) {
+                               # skip this, we regenerate the correct one
+                               # on re-fetch anyways
                        } else {
-                               $log_msg{msg} .= $_;
                                print $msg $_ or croak $!;
                        }
                }
@@ -620,6 +657,15 @@ sub svn_commit_tree {
                my $editor = $ENV{VISUAL} || $ENV{EDITOR} || 'vi';
                system($editor, $commit_msg);
        }
+
+       # file_to_s removes all trailing newlines, so just use chomp() here:
+       open $msg, '<', $commit_msg or croak $!;
+       { local $/; chomp($log_msg{msg} = <$msg>); }
+       close $msg or croak $!;
+
+       my ($oneline) = ($log_msg{msg} =~ /([^\n\r]+)/);
+       print "Committing $commit: $oneline\n";
+
        my @ci_output = safe_qx(qw(svn commit -F),$commit_msg);
        my ($committed) = grep(/^Committed revision \d+\./,@ci_output);
        unlink $commit_msg;
@@ -711,6 +757,10 @@ sub svn_log_raw {
                                        author => $author,
                                        lines => $lines,
                                        msg => '' );
+                       if (defined $_authors && ! defined $users{$author}) {
+                               die "Author: $author not defined in ",
+                                               "$_authors file\n";
+                       }
                        push @svn_log, \%log_msg;
                        $state = 'msg_start';
                        next;
@@ -810,9 +860,9 @@ sub git_commit {
        my ($log_msg, @parents) = @_;
        assert_revision_unknown($log_msg->{revision});
        my $out_fh = IO::File->new_tmpfile or croak $!;
-       my $info = svn_info('.');
-       my $uuid = $info->{'Repository UUID'};
-       defined $uuid or croak "Unable to get Repository UUID\n";
+       $SVN_UUID ||= svn_info('.')->{'Repository UUID'};
+
+       map_tree_joins() if (@_branch_from && !%tree_map);
 
        # commit parents can be conditionally bound to a particular
        # svn revision via: "svn_revno=commit_sha1", filter them out here:
@@ -835,19 +885,26 @@ sub git_commit {
                git_addremove();
                chomp(my $tree = `git-write-tree`);
                croak if $?;
+               if (exists $tree_map{$tree}) {
+                       my %seen_parent = map { $_ => 1 } @exec_parents;
+                       foreach (@{$tree_map{$tree}}) {
+                               # MAXPARENT is defined to 16 in commit-tree.c:
+                               if ($seen_parent{$_} || @exec_parents > 16) {
+                                       next;
+                               }
+                               push @exec_parents, $_;
+                               $seen_parent{$_} = 1;
+                       }
+               }
                my $msg_fh = IO::File->new_tmpfile or croak $!;
                print $msg_fh $log_msg->{msg}, "\ngit-svn-id: ",
                                        "$SVN_URL\@$log_msg->{revision}",
-                                       " $uuid\n" or croak $!;
+                                       " $SVN_UUID\n" or croak $!;
                $msg_fh->flush == 0 or croak $!;
                seek $msg_fh, 0, 0 or croak $!;
 
-               $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} =
-                                               $log_msg->{author};
-               $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} =
-                                               $log_msg->{author}."\@$uuid";
-               $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} =
-                                               $log_msg->{date};
+               set_commit_env($log_msg);
+
                my @exec = ('git-commit-tree',$tree);
                push @exec, '-p', $_  foreach @exec_parents;
                open STDIN, '<&', $msg_fh or croak $!;
@@ -863,7 +920,7 @@ sub git_commit {
        if ($commit !~ /^$sha1$/o) {
                croak "Failed to commit, invalid sha1: $commit\n";
        }
-       my @update_ref = ('git-update-ref',"refs/heads/$GIT_SVN-HEAD",$commit);
+       my @update_ref = ('git-update-ref',"refs/remotes/$GIT_SVN",$commit);
        if (my $primary_parent = shift @exec_parents) {
                push @update_ref, $primary_parent;
        }
@@ -873,6 +930,16 @@ sub git_commit {
        return $commit;
 }
 
+sub set_commit_env {
+       my ($log_msg) = @_;
+       my $author = $log_msg->{author};
+       my ($name,$email) = defined $users{$author} ?  @{$users{$author}}
+                               : ($author,"$author\@$SVN_UUID");
+       $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name;
+       $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email;
+       $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_msg->{date};
+}
+
 sub apply_mod_line_blob {
        my $m = shift;
        if ($m->{mode_b} =~ /^120/) {
@@ -936,6 +1003,63 @@ sub svn_check_ignore_externals {
                $_no_ignore_ext = 1;
        }
 }
+
+sub check_upgrade_needed {
+       my $old = eval {
+               my $pid = open my $child, '-|';
+               defined $pid or croak $!;
+               if ($pid == 0) {
+                       close STDERR;
+                       exec('git-rev-parse',"$GIT_SVN-HEAD") or croak $?;
+               }
+               my @ret = (<$child>);
+               close $child or croak $?;
+               die $? if $?; # just in case close didn't error out
+               return wantarray ? @ret : join('',@ret);
+       };
+       return unless $old;
+       my $head = eval { safe_qx('git-rev-parse',"refs/remotes/$GIT_SVN") };
+       if ($@ || !$head) {
+               print STDERR "Please run: $0 rebuild --upgrade\n";
+               exit 1;
+       }
+}
+
+# fills %tree_map with a reverse mapping of trees to commits.  Useful
+# for finding parents to commit on.
+sub map_tree_joins {
+       foreach my $br (@_branch_from) {
+               my $pid = open my $pipe, '-|';
+               defined $pid or croak $!;
+               if ($pid == 0) {
+                       exec(qw(git-rev-list --pretty=raw), $br) or croak $?;
+               }
+               while (<$pipe>) {
+                       if (/^commit ($sha1)$/o) {
+                               my $commit = $1;
+                               my ($tree) = (<$pipe> =~ /^tree ($sha1)$/o);
+                               unless (defined $tree) {
+                                       die "Failed to parse commit $commit\n";
+                               }
+                               push @{$tree_map{$tree}}, $commit;
+                       }
+               }
+               close $pipe or croak $?;
+       }
+}
+
+# '<svn username> = real-name <email address>' mapping based on git-svnimport:
+sub load_authors {
+       open my $authors, '<', $_authors or die "Can't open $_authors $!\n";
+       while (<$authors>) {
+               chomp;
+               next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/;
+               my ($user, $name, $email) = ($1, $2, $3);
+               $users{$user} = [$name, $email];
+       }
+       close $authors or croak $!;
+}
+
 __END__
 
 Data structures:
@@ -960,7 +1084,7 @@ sub svn_check_ignore_externals {
        mode_a => first column of diff-index output, no leading ':',
        mode_b => second column of diff-index output,
        sha1_b => sha1sum of the final blob,
-       chg => change type [MCRAD],
+       chg => change type [MCRADT],
        file_a => original file name of a file (iff chg is 'C' or 'R')
        file_b => new/current file name of a file (any chg)
 }
index b29073997c00944322ccc78ad5df0e1df0fc3f8f..8e9a971a8522569b58d4552a2ec1c0218c474ae2 100644 (file)
@@ -27,7 +27,7 @@ For importing svn, git-svnimport is potentially more powerful when
 operating on repositories organized under the recommended
 trunk/branch/tags structure, and should be faster, too.
 
-git-svn completely ignores the very limited view of branching that
+git-svn mostly ignores the very limited view of branching that
 Subversion has.  This allows git-svn to be much easier to use,
 especially on repositories that are not organized in a manner that
 git-svnimport is designed for.
@@ -41,12 +41,12 @@ init::
 
 fetch::
        Fetch unfetched revisions from the SVN_URL we are tracking.
-       refs/heads/git-svn-HEAD will be updated to the latest revision.
+       refs/heads/remotes/git-svn will be updated to the latest revision.
 
-       Note: You should never attempt to modify the git-svn-HEAD branch
-       outside of git-svn.  Instead, create a branch from git-svn-HEAD
+       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 git-svn-HEAD.
+       to write git commits back to remotes/git-svn.
 
 commit::
        Commit specified commit or tree objects to SVN.  This relies on
@@ -116,8 +116,38 @@ OPTIONS
        They are both passed directly to git-diff-tree see
        git-diff-tree(1) for more information.
 
+ADVANCED OPTIONS
+----------------
+-b<refname>::
+--branch <refname>::
+       Used with 'fetch' or 'commit'.
+
+       This can be used to join arbitrary git branches to remotes/git-svn
+       on new commits where the tree object is equivalent.
+
+       When used with different GIT_SVN_ID values, tags and branches in
+       SVN can be tracked this way, as can some merges where the heads
+       end up having completely equivalent content.  This can even be
+       used to track branches across multiple SVN _repositories_.
+
+       This option may be specified multiple times, once for each
+       branch.
+
+-i<GIT_SVN_ID>::
+--id <GIT_SVN_ID>::
+       This sets GIT_SVN_ID (instead of using the environment).  See
+       the section on "Tracking Multiple Repositories or Branches" for
+       more information on using GIT_SVN_ID.
+
 COMPATIBILITY OPTIONS
 ---------------------
+--upgrade::
+       Only used with the 'rebuild' command.
+
+       Run this if you used an old version of git-svn that used
+       'git-svn-HEAD' instead of 'remotes/git-svn' as the branch
+       for tracking the remote.
+
 --no-ignore-externals::
        Only used with the 'fetch' and 'rebuild' command.
 
@@ -155,14 +185,14 @@ Tracking and contributing to an Subversion managed-project:
 # Fetch remote revisions::
        git-svn fetch
 # Create your own branch to hack on::
-       git checkout -b my-branch git-svn-HEAD
+       git checkout -b my-branch remotes/git-svn
 # Commit only the git commits you want to SVN::
        git-svn commit <tree-ish> [<tree-ish_2> ...]
 # Commit all the git commits from my-branch that don't exist in SVN::
-       git-svn commit git-svn-HEAD..my-branch
+       git-svn commit remotes/git-svn..my-branch
 # Something is committed to SVN, pull the latest into your branch::
-       git-svn fetch && git pull . git-svn-HEAD
-# Append svn:ignore settings to the default git exclude file:
+       git-svn fetch && git pull . remotes/git-svn
+# Append svn:ignore settings to the default git exclude file::
        git-svn show-ignore >> .git/info/exclude
 
 DESIGN PHILOSOPHY
@@ -184,8 +214,8 @@ SVN repositories via one git repository.  Simply set the GIT_SVN_ID
 environment variable to a name other other than "git-svn" (the default)
 and git-svn will ignore the contents of the $GIT_DIR/git-svn directory
 and instead do all of its work in $GIT_DIR/$GIT_SVN_ID for that
-invocation.  The interface branch will be $GIT_SVN_ID-HEAD, instead of
-git-svn-HEAD.  Any $GIT_SVN_ID-HEAD branch should never be modified
+invocation.  The interface branch will be remotes/$GIT_SVN_ID, instead of
+remotes/git-svn.  Any remotes/$GIT_SVN_ID branch should never be modified
 by the user outside of git-svn commands.
 
 ADDITIONAL FETCH ARGUMENTS
index 181dfe008b6ab6240f132b335deff179faf18d14..80ad3573db3e5ea7befaacb14f95a0b87f0741ec 100644 (file)
@@ -71,14 +71,14 @@ test_expect_success \
 
 
 name='try a deep --rmdir with a commit'
-git checkout -b mybranch git-svn-HEAD
+git checkout -b mybranch remotes/git-svn
 mv dir/a/b/c/d/e/file dir/file
 cp dir/file file
 git update-index --add --remove dir/a/b/c/d/e/file dir/file file
 git commit -m "$name"
 
 test_expect_success "$name" \
-    "git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch &&
+    "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch &&
      test -d $SVN_TREE/dir && test ! -d $SVN_TREE/dir/a"
 
 
@@ -91,13 +91,13 @@ git update-index --add dir/file/file
 git commit -m "$name"
 
 test_expect_code 1 "$name" \
-    'git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch' \
+    'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch' \
     || true
 
 
 name='detect node change from directory to file #1'
 rm -rf dir $GIT_DIR/index
-git checkout -b mybranch2 git-svn-HEAD
+git checkout -b mybranch2 remotes/git-svn
 mv bar/zzz zzz
 rm -rf bar
 mv zzz bar
@@ -106,13 +106,13 @@ git update-index --add -- bar
 git commit -m "$name"
 
 test_expect_code 1 "$name" \
-    'git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch2' \
+    'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch2' \
     || true
 
 
 name='detect node change from file to directory #2'
 rm -f $GIT_DIR/index
-git checkout -b mybranch3 git-svn-HEAD
+git checkout -b mybranch3 remotes/git-svn
 rm bar/zzz
 git-update-index --remove bar/zzz
 mkdir bar/zzz
@@ -121,13 +121,13 @@ git-update-index --add bar/zzz/yyy
 git commit -m "$name"
 
 test_expect_code 1 "$name" \
-    'git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch3' \
+    'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch3' \
     || true
 
 
 name='detect node change from directory to file #2'
 rm -f $GIT_DIR/index
-git checkout -b mybranch4 git-svn-HEAD
+git checkout -b mybranch4 remotes/git-svn
 rm -rf dir
 git update-index --remove -- dir/file
 touch dir
@@ -136,19 +136,19 @@ git update-index --add -- dir
 git commit -m "$name"
 
 test_expect_code 1 "$name" \
-    'git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch4' \
+    'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch4' \
     || true
 
 
 name='remove executable bit from a file'
 rm -f $GIT_DIR/index
-git checkout -b mybranch5 git-svn-HEAD
+git checkout -b mybranch5 remotes/git-svn
 chmod -x exec.sh
 git update-index exec.sh
 git commit -m "$name"
 
 test_expect_success "$name" \
-    "git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch5 &&
+    "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
      test ! -x $SVN_TREE/exec.sh"
 
 
@@ -158,7 +158,7 @@ git update-index exec.sh
 git commit -m "$name"
 
 test_expect_success "$name" \
-    "git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch5 &&
+    "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
      test -x $SVN_TREE/exec.sh"
 
 
@@ -170,7 +170,7 @@ git update-index exec.sh
 git commit -m "$name"
 
 test_expect_success "$name" \
-    "git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch5 &&
+    "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
      test -L $SVN_TREE/exec.sh"
 
 
@@ -182,7 +182,7 @@ git update-index --add bar/zzz exec-2.sh
 git commit -m "$name"
 
 test_expect_success "$name" \
-    "git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch5 &&
+    "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
      test -x $SVN_TREE/bar/zzz &&
      test -L $SVN_TREE/exec-2.sh"
 
@@ -196,7 +196,7 @@ git update-index exec-2.sh
 git commit -m "$name"
 
 test_expect_success "$name" \
-    "git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch5 &&
+    "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
      test -f $SVN_TREE/exec-2.sh &&
      test ! -L $SVN_TREE/exec-2.sh &&
      diff -u help $SVN_TREE/exec-2.sh"
@@ -207,9 +207,9 @@ name='test fetch functionality (svn => git) with alternate GIT_SVN_ID'
 GIT_SVN_ID=alt
 export GIT_SVN_ID
 test_expect_success "$name" \
-    "git-svn init $svnrepo && git-svn fetch -v &&
-     git-rev-list --pretty=raw git-svn-HEAD | grep ^tree | uniq > a &&
-     git-rev-list --pretty=raw alt-HEAD | grep ^tree | uniq > b &&
+    "git-svn init $svnrepo && git-svn fetch &&
+     git-rev-list --pretty=raw remotes/git-svn | grep ^tree | uniq > a &&
+     git-rev-list --pretty=raw remotes/alt | grep ^tree | uniq > b &&
      diff -u a b"
 
 test_done
index 048caf6f86b555ee0cbf598f91a50408f594f82b..781badbc5ba456008b6ae2b01dc970b5bd2d9c6c 100755 (executable)
@@ -239,20 +239,23 @@ class CellRendererGraph(gtk.GenericCellRenderer):
                                box_size / 4, 0, 2 * math.pi)
 
 
+               self.set_colour(ctx, colour, 0.0, 0.5)
+               ctx.stroke_preserve()
+
+               self.set_colour(ctx, colour, 0.5, 1.0)
+               ctx.fill_preserve()
+
                if (len(names) != 0):
                        name = " "
                        for item in names:
                                name = name + item + " "
 
-                       ctx.select_font_face("Monospace")
                        ctx.set_font_size(13)
-                       ctx.text_path(name)
-
-               self.set_colour(ctx, colour, 0.0, 0.5)
-               ctx.stroke_preserve()
-
-               self.set_colour(ctx, colour, 0.5, 1.0)
-               ctx.fill()
+                       if (flags & 1):
+                               self.set_colour(ctx, colour, 0.5, 1.0)
+                       else:
+                               self.set_colour(ctx, colour, 0.0, 0.5)
+                       ctx.show_text(name)
 
 class Commit:
        """ This represent a commit object obtained after parsing the git-rev-list
@@ -365,7 +368,7 @@ class DiffWindow:
                save_menu.connect("activate", self.save_menu_response, "save")
                save_menu.show()
                menu_bar.append(save_menu)
-               vbox.pack_start(menu_bar, False, False, 2)
+               vbox.pack_start(menu_bar, expand=False, fill=True)
                menu_bar.show()
 
                scrollwin = gtk.ScrolledWindow()
@@ -479,19 +482,10 @@ class GitView:
 
        def construct(self):
                """Construct the window contents."""
+               vbox = gtk.VBox()
                paned = gtk.VPaned()
                paned.pack1(self.construct_top(), resize=False, shrink=True)
                paned.pack2(self.construct_bottom(), resize=False, shrink=True)
-               self.window.add(paned)
-               paned.show()
-
-
-       def construct_top(self):
-               """Construct the top-half of the window."""
-               vbox = gtk.VBox(spacing=6)
-               vbox.set_border_width(12)
-               vbox.show()
-
                menu_bar = gtk.MenuBar()
                menu_bar.set_pack_direction(gtk.PACK_DIRECTION_RTL)
                help_menu = gtk.MenuItem("Help")
@@ -503,11 +497,23 @@ class GitView:
                help_menu.set_submenu(menu)
                help_menu.show()
                menu_bar.append(help_menu)
-               vbox.pack_start(menu_bar, False, False, 2)
                menu_bar.show()
+               vbox.pack_start(menu_bar, expand=False, fill=True)
+               vbox.pack_start(paned, expand=True, fill=True)
+               self.window.add(vbox)
+               paned.show()
+               vbox.show()
+
+
+       def construct_top(self):
+               """Construct the top-half of the window."""
+               vbox = gtk.VBox(spacing=6)
+               vbox.set_border_width(12)
+               vbox.show()
+
 
                scrollwin = gtk.ScrolledWindow()
-               scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+               scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
                scrollwin.set_shadow_type(gtk.SHADOW_IN)
                vbox.pack_start(scrollwin, expand=True, fill=True)
                scrollwin.show()
@@ -792,7 +798,7 @@ class GitView:
                        button.set_relief(gtk.RELIEF_NONE)
                        button.set_sensitive(True)
                        button.connect("clicked", self._show_clicked_cb,
-                                       child_id, commit.commit_sha1)
+                                       child_id, commit.commit_sha1, self.encoding)
                        hbox.pack_start(button, expand=False, fill=True)
                        button.show()
 
index 3ee3a0ccf1b92f3f8838ca2e728bde9f60d1b8e8..058a2aadb1475801ea7573837867fa79bf1766c1 100644 (file)
@@ -3,74 +3,11 @@
  * The delta-parsing part is almost straight copy of patch-delta.c
  * which is (C) 2005 Nicolas Pitre <nico@cam.org>.
  */
-#include "cache.h"
-#include "delta.h"
-#include "count-delta.h"
 #include <stdlib.h>
 #include <string.h>
 #include <limits.h>
-
-struct span {
-       struct span *next;
-       unsigned long ofs;
-       unsigned long end;
-};
-
-static void touch_range(struct span **span,
-                       unsigned long ofs, unsigned long end)
-{
-       struct span *e = *span;
-       struct span *p = NULL;
-
-       while (e && e->ofs <= ofs) {
-               again:
-               if (ofs < e->end) {
-                       while (e->end < end) {
-                               if (e->next && e->next->ofs <= end) {
-                                       e->end = e->next->ofs;
-                                       e = e->next;
-                               }
-                               else {
-                                       e->end = end;
-                                       return;
-                               }
-                       }
-                       return;
-               }
-               p = e;
-               e = e->next;
-       }
-       if (e && e->ofs <= end) {
-               e->ofs = ofs;
-               goto again;
-       }
-       else {
-               e = xmalloc(sizeof(*e));
-               e->ofs = ofs;
-               e->end = end;
-               if (p) {
-                       e->next = p->next;
-                       p->next = e;
-               }
-               else {
-                       e->next = *span;
-                       *span = e;
-               }
-       }
-}
-
-static unsigned long count_range(struct span *s)
-{
-       struct span *t;
-       unsigned long sz = 0;
-       while (s) {
-               t = s;
-               sz += s->end - s->ofs;
-               s = s->next;
-               free(t);
-       }
-       return sz;
-}
+#include "delta.h"
+#include "count-delta.h"
 
 /*
  * NOTE.  We do not _interpret_ delta fully.  As an approximation, we
@@ -84,11 +21,10 @@ static unsigned long count_range(struct span *s)
 int count_delta(void *delta_buf, unsigned long delta_size,
                unsigned long *src_copied, unsigned long *literal_added)
 {
-       unsigned long added_literal;
+       unsigned long copied_from_source, added_literal;
        const unsigned char *data, *top;
        unsigned char cmd;
        unsigned long src_size, dst_size, out;
-       struct span *span = NULL;
 
        if (delta_size < DELTA_SIZE_MIN)
                return -1;
@@ -99,7 +35,7 @@ int count_delta(void *delta_buf, unsigned long delta_size,
        src_size = get_delta_hdr_size(&data);
        dst_size = get_delta_hdr_size(&data);
 
-       added_literal = out = 0;
+       added_literal = copied_from_source = out = 0;
        while (data < top) {
                cmd = *data++;
                if (cmd & 0x80) {
@@ -113,7 +49,7 @@ int count_delta(void *delta_buf, unsigned long delta_size,
                        if (cmd & 0x40) cp_size |= (*data++ << 16);
                        if (cp_size == 0) cp_size = 0x10000;
 
-                       touch_range(&span, cp_off, cp_off+cp_size);
+                       copied_from_source += cp_size;
                        out += cp_size;
                } else {
                        /* write literal into dst */
@@ -123,8 +59,6 @@ int count_delta(void *delta_buf, unsigned long delta_size,
                }
        }
 
-       *src_copied = count_range(span);
-
        /* sanity check */
        if (data != top || out != dst_size)
                return -1;
@@ -132,6 +66,7 @@ int count_delta(void *delta_buf, unsigned long delta_size,
        /* delete size is what was _not_ copied from source.
         * edit size is that and literal additions.
         */
+       *src_copied = copied_from_source;
        *literal_added = added_literal;
        return 0;
 }
index b7190ea47a9c249b108fe5b590e18ba7c27f4176..2ed5984b1c360194d53fd287962d7857e19a097c 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))
+
+#define GR_PRIME 0x9e370001
+#define HASH(v, shift) (((unsigned int)(v) * GR_PRIME) >> (shift))
+
 struct index {
        const unsigned char *ptr;
+       unsigned int val;
        struct index *next;
 };
 
 static struct index ** delta_index(const unsigned char *buf,
                                   unsigned long bufsize,
-                                  unsigned long trg_bufsize,
                                   unsigned int *hash_shift)
 {
-       unsigned long hsize;
-       unsigned int i, hshift, hlimit, *hash_count;
+       unsigned int hsize, hshift, entries, blksize, i;
        const unsigned char *data;
        struct index *entry, **hash;
        void *mem;
 
        /* determine index hash size */
-       hsize = bufsize / 4;
-       for (i = 8; (1 << i) < hsize && i < 24; i += 2);
+       entries = (bufsize + BLK_SIZE - 1) / BLK_SIZE;
+       hsize = entries / 4;
+       for (i = 4; (1 << i) < hsize && i < 16; i++);
        hsize = 1 << i;
-       hshift = (i - 8) / 2;
+       hshift = 32 - i;
        *hash_shift = hshift;
 
        /* allocate lookup index */
-       mem = malloc(hsize * sizeof(*hash) + bufsize * sizeof(*entry));
+       mem = malloc(hsize * sizeof(*hash) + entries * sizeof(*entry));
        if (!mem)
                return NULL;
        hash = mem;
        entry = mem + hsize * sizeof(*hash);
        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);
-               return NULL;
-       }
-
-       /* then populate the index */
-       data = buf + bufsize - 2;
-       while (data > buf) {
-               entry->ptr = --data;
-               i = data[0] ^ ((data[1] ^ (data[2] << hshift)) << hshift);
+       /* then populate it */
+       data = buf + entries * BLK_SIZE - BLK_SIZE;
+       blksize = bufsize - (data - buf);
+       while (data >= buf) {
+               unsigned int val = adler32(0, data, blksize);
+               i = HASH(val, hshift);
+               entry->ptr = data;
+               entry->val = val;
                entry->next = hash[i];
                hash[i] = entry++;
-               hash_count[i]++;
+               blksize = BLK_SIZE;
+               data -= BLK_SIZE;
        }
 
-       /*
-        * Determine a limit on the number of entries in the same hash
-        * bucket.  This guard us against patological data sets causing
-        * really bad hash distribution with most entries in the same hash
-        * 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 < 16)
-               hlimit = 16;
-
-       /*
-        * Now make sure none of the hash buckets has more entries than
-        * we're willing to test.  Otherwise we cull the entry list to
-        * limit identical three byte prefixes to still preserve a good
-        * repartition across the reference buffer.
-        */
-       for (i = 0; i < hsize; i++) {
-               struct index **list, *bucket, *remaining;
-               int cnt;
-               if (hash_count[i] < hlimit)
-                       continue;
-
-               bucket = NULL;
-               list = &bucket;
-               remaining = hash[i];
-               cnt = 0;
-               while (cnt < hlimit && remaining) {
-                       struct index *this = remaining, *that;
-                       remaining = remaining->next;
-                       for (that = bucket; that; that = that->next) {
-                               if (!memcmp(that->ptr, this->ptr, 3))
-                                       break;
-                       }
-                       if (that)
-                               continue; /* discard */
-                       cnt++;
-                       *list = this;
-                       list = &(this->next);
-                       this->next = NULL;
-               }
-               hash[i] = bucket;
-       }
-       free(hash_count);
-
        return hash;
 }
 
@@ -143,7 +100,7 @@ void *diff_delta(void *from_buf, unsigned long from_size,
 
        if (!from_size || !to_size)
                return NULL;
-       hash = delta_index(from_buf, from_size, to_size, &hash_shift);
+       hash = delta_index(from_buf, from_size, &hash_shift);
        if (!hash)
                return NULL;
 
@@ -184,25 +141,29 @@ void *diff_delta(void *from_buf, unsigned long from_size,
 
        while (data < top) {
                unsigned int moff = 0, msize = 0;
-               if (data + 3 <= top) {
-                       i = data[0] ^ ((data[1] ^ (data[2] << hash_shift)) << 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 (ref_size > top - src)
-                                       ref_size = top - src;
-                               if (ref_size > 0x10000)
-                                       ref_size = 0x10000;
-                               if (ref_size <= msize)
+               unsigned int blksize = MIN(top - data, BLK_SIZE);
+               unsigned int val = adler32(0, data, blksize);
+               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;
+                       while (ref_size && *src++ == *ref) {
+                               ref++;
+                               ref_size--;
+                       }
+                       ref_size = ref - entry->ptr;
+                       if (ref_size > msize) {
+                               /* this is our best match so far */
+                               moff = entry->ptr - ref_data;
+                               msize = ref_size;
+                               if (msize >= 0x10000) {
+                                       msize = 0x10000;
                                        break;
-                               if (*ref != *src)
-                                       continue;
-                               while (ref_size-- && *++src == *++ref);
-                               if (msize < ref - entry->ptr) {
-                                       /* this is our best match so far */
-                                       msize = ref - entry->ptr;
-                                       moff = entry->ptr - ref_data;
                                }
                        }
                }
index c57513a4fa83c58c5040ead38c52765051cd13f5..0fc2b860bef30ee5a37b90df835e66f0dc74ab70 100644 (file)
@@ -4,8 +4,6 @@
 #include "cache.h"
 #include "diff.h"
 #include "diffcore.h"
-#include "delta.h"
-#include "count-delta.h"
 
 static int should_break(struct diff_filespec *src,
                        struct diff_filespec *dst,
@@ -47,7 +45,6 @@ static int should_break(struct diff_filespec *src,
         * The value we return is 1 if we want the pair to be broken,
         * or 0 if we do not.
         */
-       void *delta;
        unsigned long delta_size, base_size, src_copied, literal_added;
        int to_break = 0;
 
@@ -58,6 +55,10 @@ static int should_break(struct diff_filespec *src,
        if (!S_ISREG(src->mode) || !S_ISREG(dst->mode))
                return 0; /* leave symlink rename alone */
 
+       if (src->sha1_valid && dst->sha1_valid &&
+           !memcmp(src->sha1, dst->sha1, 20))
+               return 0; /* they are the same */
+
        if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0))
                return 0; /* error but caught downstream */
 
@@ -65,19 +66,11 @@ static int should_break(struct diff_filespec *src,
        if (base_size < MINIMUM_BREAK_SIZE)
                return 0; /* we do not break too small filepair */
 
-       delta = diff_delta(src->data, src->size,
-                          dst->data, dst->size,
-                          &delta_size, 0);
-       if (!delta)
-               return 0; /* error but caught downstream */
-
-       /* Estimate the edit size by interpreting delta. */
-       if (count_delta(delta, delta_size,
-                       &src_copied, &literal_added)) {
-               free(delta);
-               return 0; /* we cannot tell */
-       }
-       free(delta);
+       if (diffcore_count_changes(src->data, src->size,
+                                  dst->data, dst->size,
+                                  0,
+                                  &src_copied, &literal_added))
+               return 0;
 
        /* Compute merge-score, which is "how much is removed
         * from the source material".  The clean-up stage will
diff --git a/diffcore-delta.c b/diffcore-delta.c
new file mode 100644 (file)
index 0000000..1e6a691
--- /dev/null
@@ -0,0 +1,43 @@
+#include "cache.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "delta.h"
+#include "count-delta.h"
+
+static int diffcore_count_changes_1(void *src, unsigned long src_size,
+                                   void *dst, unsigned long dst_size,
+                                   unsigned long delta_limit,
+                                   unsigned long *src_copied,
+                                   unsigned long *literal_added)
+{
+       void *delta;
+       unsigned long delta_size;
+
+       delta = diff_delta(src, src_size,
+                          dst, dst_size,
+                          &delta_size, delta_limit);
+       if (!delta)
+               /* If delta_limit is exceeded, we have too much differences */
+               return -1;
+
+       /* Estimate the edit size by interpreting delta. */
+       if (count_delta(delta, delta_size, src_copied, literal_added)) {
+               free(delta);
+               return -1;
+       }
+       free(delta);
+       return 0;
+}
+
+int diffcore_count_changes(void *src, unsigned long src_size,
+                          void *dst, unsigned long dst_size,
+                          unsigned long delta_limit,
+                          unsigned long *src_copied,
+                          unsigned long *literal_added)
+{
+       return diffcore_count_changes_1(src, src_size,
+                                       dst, dst_size,
+                                       delta_limit,
+                                       src_copied,
+                                       literal_added);
+}
index ffd126af0d2cd3539e0f186e83c7671132758e6b..55cf1c37f344628eb06c40393295f288fb186a50 100644 (file)
@@ -4,8 +4,6 @@
 #include "cache.h"
 #include "diff.h"
 #include "diffcore.h"
-#include "delta.h"
-#include "count-delta.h"
 
 /* Table of rename/copy destinations */
 
@@ -135,7 +133,6 @@ static int estimate_similarity(struct diff_filespec *src,
         * match than anything else; the destination does not even
         * call into this function in that case.
         */
-       void *delta;
        unsigned long delta_size, base_size, src_copied, literal_added;
        unsigned long delta_limit;
        int score;
@@ -165,28 +162,13 @@ static int estimate_similarity(struct diff_filespec *src,
        if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0))
                return 0; /* error but caught downstream */
 
-       delta_limit = base_size * (MAX_SCORE-minimum_score) / MAX_SCORE;
-       delta = diff_delta(src->data, src->size,
-                          dst->data, dst->size,
-                          &delta_size, delta_limit);
-       if (!delta)
-               /* If delta_limit is exceeded, we have too much differences */
-               return 0;
-
-       /* A delta that has a lot of literal additions would have
-        * big delta_size no matter what else it does.
-        */
-       if (base_size * (MAX_SCORE-minimum_score) < delta_size * MAX_SCORE) {
-               free(delta);
-               return 0;
-       }
 
-       /* Estimate the edit size by interpreting delta. */
-       if (count_delta(delta, delta_size, &src_copied, &literal_added)) {
-               free(delta);
+       delta_limit = base_size * (MAX_SCORE-minimum_score) / MAX_SCORE;
+       if (diffcore_count_changes(src->data, src->size,
+                                  dst->data, dst->size,
+                                  delta_limit,
+                                  &src_copied, &literal_added))
                return 0;
-       }
-       free(delta);
 
        /* Extent of damage */
        if (src->size + literal_added < src_copied)
index 91d6c631e69a1f628fa65feecab1ee8b1fb73fad..dba4f17658e6b3b7e1853ca024f8cfe9eae9c92e 100644 (file)
@@ -18,7 +18,7 @@
 #define MAX_SCORE 60000.0
 #define DEFAULT_RENAME_SCORE 30000 /* rename/copy similarity minimum (50%) */
 #define DEFAULT_BREAK_SCORE  30000 /* minimum for break to happen (50%)*/
-#define DEFAULT_MERGE_SCORE  45000 /* maximum for break-merge to happen (75%)*/
+#define DEFAULT_MERGE_SCORE  48000 /* maximum for break-merge to happen (80%)*/
 
 #define MINIMUM_BREAK_SIZE     400 /* do not break a file smaller than this */
 
@@ -101,4 +101,10 @@ void diff_debug_queue(const char *, struct diff_queue_struct *);
 #define diff_debug_queue(a,b) do {} while(0)
 #endif
 
+extern int diffcore_count_changes(void *src, unsigned long src_size,
+                                 void *dst, unsigned long dst_size,
+                                 unsigned long delta_limit,
+                                 unsigned long *src_copied,
+                                 unsigned long *literal_added);
+
 #endif
diff --git a/epoch.c b/epoch.c
deleted file mode 100644 (file)
index 0f37492..0000000
--- a/epoch.c
+++ /dev/null
@@ -1,640 +0,0 @@
-/*
- * Copyright (c) 2005, Jon Seymour
- *
- * For more information about epoch theory on which this module is based,
- * refer to http://blackcubes.dyndns.org/epoch/. That web page defines
- * terms such as "epoch" and "minimal, non-linear epoch" and provides rationales
- * for some of the algorithms used here.
- *
- */
-#include <stdlib.h>
-
-/* Provides arbitrary precision integers required to accurately represent
- * fractional mass: */
-#include <openssl/bn.h>
-
-#include "cache.h"
-#include "commit.h"
-#include "revision.h"
-#include "epoch.h"
-
-struct fraction {
-       BIGNUM numerator;
-       BIGNUM denominator;
-};
-
-#define HAS_EXACTLY_ONE_PARENT(n) ((n)->parents && !(n)->parents->next)
-
-static BN_CTX *context = NULL;
-static struct fraction *one = NULL;
-static struct fraction *zero = NULL;
-
-static BN_CTX *get_BN_CTX(void)
-{
-       if (!context) {
-               context = BN_CTX_new();
-       }
-       return context;
-}
-
-static struct fraction *new_zero(void)
-{
-       struct fraction *result = xmalloc(sizeof(*result));
-       BN_init(&result->numerator);
-       BN_init(&result->denominator);
-       BN_zero(&result->numerator);
-       BN_one(&result->denominator);
-       return result;
-}
-
-static void clear_fraction(struct fraction *fraction)
-{
-       BN_clear(&fraction->numerator);
-       BN_clear(&fraction->denominator);
-}
-
-static struct fraction *divide(struct fraction *result, struct fraction *fraction, int divisor)
-{
-       BIGNUM bn_divisor;
-
-       BN_init(&bn_divisor);
-       BN_set_word(&bn_divisor, divisor);
-
-       BN_copy(&result->numerator, &fraction->numerator);
-       BN_mul(&result->denominator, &fraction->denominator, &bn_divisor, get_BN_CTX());
-
-       BN_clear(&bn_divisor);
-       return result;
-}
-
-static struct fraction *init_fraction(struct fraction *fraction)
-{
-       BN_init(&fraction->numerator);
-       BN_init(&fraction->denominator);
-       BN_zero(&fraction->numerator);
-       BN_one(&fraction->denominator);
-       return fraction;
-}
-
-static struct fraction *get_one(void)
-{
-       if (!one) {
-               one = new_zero();
-               BN_one(&one->numerator);
-       }
-       return one;
-}
-
-static struct fraction *get_zero(void)
-{
-       if (!zero) {
-               zero = new_zero();
-       }
-       return zero;
-}
-
-static struct fraction *copy(struct fraction *to, struct fraction *from)
-{
-       BN_copy(&to->numerator, &from->numerator);
-       BN_copy(&to->denominator, &from->denominator);
-       return to;
-}
-
-static struct fraction *add(struct fraction *result, struct fraction *left, struct fraction *right)
-{
-       BIGNUM a, b, gcd;
-
-       BN_init(&a);
-       BN_init(&b);
-       BN_init(&gcd);
-
-       BN_mul(&a, &left->numerator, &right->denominator, get_BN_CTX());
-       BN_mul(&b, &left->denominator, &right->numerator, get_BN_CTX());
-       BN_mul(&result->denominator, &left->denominator, &right->denominator, get_BN_CTX());
-       BN_add(&result->numerator, &a, &b);
-
-       BN_gcd(&gcd, &result->denominator, &result->numerator, get_BN_CTX());
-       BN_div(&result->denominator, NULL, &result->denominator, &gcd, get_BN_CTX());
-       BN_div(&result->numerator, NULL, &result->numerator, &gcd, get_BN_CTX());
-
-       BN_clear(&a);
-       BN_clear(&b);
-       BN_clear(&gcd);
-
-       return result;
-}
-
-static int compare(struct fraction *left, struct fraction *right)
-{
-       BIGNUM a, b;
-       int result;
-
-       BN_init(&a);
-       BN_init(&b);
-
-       BN_mul(&a, &left->numerator, &right->denominator, get_BN_CTX());
-       BN_mul(&b, &left->denominator, &right->numerator, get_BN_CTX());
-
-       result = BN_cmp(&a, &b);
-
-       BN_clear(&a);
-       BN_clear(&b);
-
-       return result;
-}
-
-struct mass_counter {
-       struct fraction seen;
-       struct fraction pending;
-};
-
-static struct mass_counter *new_mass_counter(struct commit *commit, struct fraction *pending)
-{
-       struct mass_counter *mass_counter = xmalloc(sizeof(*mass_counter));
-       memset(mass_counter, 0, sizeof(*mass_counter));
-
-       init_fraction(&mass_counter->seen);
-       init_fraction(&mass_counter->pending);
-
-       copy(&mass_counter->pending, pending);
-       copy(&mass_counter->seen, get_zero());
-
-       if (commit->object.util) {
-               die("multiple attempts to initialize mass counter for %s",
-                   sha1_to_hex(commit->object.sha1));
-       }
-
-       commit->object.util = mass_counter;
-
-       return mass_counter;
-}
-
-static void free_mass_counter(struct mass_counter *counter)
-{
-       clear_fraction(&counter->seen);
-       clear_fraction(&counter->pending);
-       free(counter);
-}
-
-/*
- * Finds the base commit of a list of commits.
- *
- * One property of the commit being searched for is that every commit reachable
- * from the base commit is reachable from the commits in the starting list only
- * via paths that include the base commit.
- *
- * This algorithm uses a conservation of mass approach to find the base commit.
- *
- * We start by injecting one unit of mass into the graph at each
- * of the commits in the starting list. Injecting mass into a commit
- * is achieved by adding to its pending mass counter and, if it is not already
- * enqueued, enqueuing the commit in a list of pending commits, in latest
- * commit date first order.
- *
- * The algorithm then proceeds to visit each commit in the pending queue.
- * Upon each visit, the pending mass is added to the mass already seen for that
- * commit and then divided into N equal portions, where N is the number of
- * parents of the commit being visited. The divided portions are then injected
- * into each of the parents.
- *
- * The algorithm continues until we discover a commit which has seen all the
- * mass originally injected or until we run out of things to do.
- *
- * If we find a commit that has seen all the original mass, we have found
- * the common base of all the commits in the starting list.
- *
- * The algorithm does _not_ depend on accurate timestamps for correct operation.
- * However, reasonably sane (e.g. non-random) timestamps are required in order
- * to prevent an exponential performance characteristic. The occasional
- * timestamp inaccuracy will not dramatically affect performance but may
- * result in more nodes being processed than strictly necessary.
- *
- * This procedure sets *boundary to the address of the base commit. It returns
- * non-zero if, and only if, there was a problem parsing one of the
- * commits discovered during the traversal.
- */
-static int find_base_for_list(struct commit_list *list, struct commit **boundary)
-{
-       int ret = 0;
-       struct commit_list *cleaner = NULL;
-       struct commit_list *pending = NULL;
-       struct fraction injected;
-       init_fraction(&injected);
-       *boundary = NULL;
-
-       for (; list; list = list->next) {
-               struct commit *item = list->item;
-
-               if (!item->object.util) {
-                       new_mass_counter(list->item, get_one());
-                       add(&injected, &injected, get_one());
-
-                       commit_list_insert(list->item, &cleaner);
-                       commit_list_insert(list->item, &pending);
-               }
-       }
-
-       while (!*boundary && pending && !ret) {
-               struct commit *latest = pop_commit(&pending);
-               struct mass_counter *latest_node = (struct mass_counter *) latest->object.util;
-               int num_parents;
-
-               if ((ret = parse_commit(latest)))
-                       continue;
-               add(&latest_node->seen, &latest_node->seen, &latest_node->pending);
-
-               num_parents = count_parents(latest);
-               if (num_parents) {
-                       struct fraction distribution;
-                       struct commit_list *parents;
-
-                       divide(init_fraction(&distribution), &latest_node->pending, num_parents);
-
-                       for (parents = latest->parents; parents; parents = parents->next) {
-                               struct commit *parent = parents->item;
-                               struct mass_counter *parent_node = (struct mass_counter *) parent->object.util;
-
-                               if (!parent_node) {
-                                       parent_node = new_mass_counter(parent, &distribution);
-                                       insert_by_date(parent, &pending);
-                                       commit_list_insert(parent, &cleaner);
-                               } else {
-                                       if (!compare(&parent_node->pending, get_zero()))
-                                               insert_by_date(parent, &pending);
-                                       add(&parent_node->pending, &parent_node->pending, &distribution);
-                               }
-                       }
-
-                       clear_fraction(&distribution);
-               }
-
-               if (!compare(&latest_node->seen, &injected))
-                       *boundary = latest;
-               copy(&latest_node->pending, get_zero());
-       }
-
-       while (cleaner) {
-               struct commit *next = pop_commit(&cleaner);
-               free_mass_counter((struct mass_counter *) next->object.util);
-               next->object.util = NULL;
-       }
-
-       if (pending)
-               free_commit_list(pending);
-
-       clear_fraction(&injected);
-       return ret;
-}
-
-
-/*
- * Finds the base of an minimal, non-linear epoch, headed at head, by
- * applying the find_base_for_list to a list consisting of the parents
- */
-static int find_base(struct commit *head, struct commit **boundary)
-{
-       int ret = 0;
-       struct commit_list *pending = NULL;
-       struct commit_list *next;
-
-       for (next = head->parents; next; next = next->next) {
-               commit_list_insert(next->item, &pending);
-       }
-       ret = find_base_for_list(pending, boundary);
-       free_commit_list(pending);
-
-       return ret;
-}
-
-/*
- * This procedure traverses to the boundary of the first epoch in the epoch
- * sequence of the epoch headed at head_of_epoch. This is either the end of
- * the maximal linear epoch or the base of a minimal non-linear epoch.
- *
- * The queue of pending nodes is sorted in reverse date order and each node
- * is currently in the queue at most once.
- */
-static int find_next_epoch_boundary(struct commit *head_of_epoch, struct commit **boundary)
-{
-       int ret;
-       struct commit *item = head_of_epoch;
-
-       ret = parse_commit(item);
-       if (ret)
-               return ret;
-
-       if (HAS_EXACTLY_ONE_PARENT(item)) {
-               /*
-                * We are at the start of a maximimal linear epoch.
-                * Traverse to the end.
-                */
-               while (HAS_EXACTLY_ONE_PARENT(item) && !ret) {
-                       item = item->parents->item;
-                       ret = parse_commit(item);
-               }
-               *boundary = item;
-
-       } else {
-               /*
-                * Otherwise, we are at the start of a minimal, non-linear
-                * epoch - find the common base of all parents.
-                */
-               ret = find_base(item, boundary);
-       }
-
-       return ret;
-}
-
-/*
- * Returns non-zero if parent is known to be a parent of child.
- */
-static int is_parent_of(struct commit *parent, struct commit *child)
-{
-       struct commit_list *parents;
-       for (parents = child->parents; parents; parents = parents->next) {
-               if (!memcmp(parent->object.sha1, parents->item->object.sha1,
-                           sizeof(parents->item->object.sha1)))
-                       return 1;
-       }
-       return 0;
-}
-
-/*
- * Pushes an item onto the merge order stack. If the top of the stack is
- * marked as being a possible "break", we check to see whether it actually
- * is a break.
- */
-static void push_onto_merge_order_stack(struct commit_list **stack, struct commit *item)
-{
-       struct commit_list *top = *stack;
-       if (top && (top->item->object.flags & DISCONTINUITY)) {
-               if (is_parent_of(top->item, item)) {
-                       top->item->object.flags &= ~DISCONTINUITY;
-               }
-       }
-       commit_list_insert(item, stack);
-}
-
-/*
- * Marks all interesting, visited commits reachable from this commit
- * as uninteresting. We stop recursing when we reach the epoch boundary,
- * an unvisited node or a node that has already been marking uninteresting.
- *
- * This doesn't actually mark all ancestors between the start node and the
- * epoch boundary uninteresting, but does ensure that they will eventually
- * be marked uninteresting when the main sort_first_epoch() traversal
- * eventually reaches them.
- */
-static void mark_ancestors_uninteresting(struct commit *commit)
-{
-       unsigned int flags = commit->object.flags;
-       int visited = flags & VISITED;
-       int boundary = flags & BOUNDARY;
-       int uninteresting = flags & UNINTERESTING;
-       struct commit_list *next;
-
-       commit->object.flags |= UNINTERESTING;
-
-       /*
-        * We only need to recurse if
-        *      we are not on the boundary and
-        *      we have not already been marked uninteresting and
-        *      we have already been visited.
-        *
-        * The main sort_first_epoch traverse will mark unreachable
-        * all uninteresting, unvisited parents as they are visited
-        * so there is no need to duplicate that traversal here.
-        *
-        * Similarly, if we are already marked uninteresting
-        * then either all ancestors have already been marked
-        * uninteresting or will be once the sort_first_epoch
-        * traverse reaches them.
-        */
-
-       if (uninteresting || boundary || !visited)
-               return;
-
-       for (next = commit->parents; next; next = next->next)
-               mark_ancestors_uninteresting(next->item);
-}
-
-/*
- * Sorts the nodes of the first epoch of the epoch sequence of the epoch headed at head
- * into merge order.
- */
-static void sort_first_epoch(struct commit *head, struct commit_list **stack)
-{
-       struct commit_list *parents;
-
-       head->object.flags |= VISITED;
-
-       /*
-        * TODO: By sorting the parents in a different order, we can alter the
-        * merge order to show contemporaneous changes in parallel branches
-        * occurring after "local" changes. This is useful for a developer
-        * when a developer wants to see all changes that were incorporated
-        * into the same merge as her own changes occur after her own
-        * changes.
-        */
-
-       for (parents = head->parents; parents; parents = parents->next) {
-               struct commit *parent = parents->item;
-
-               if (head->object.flags & UNINTERESTING) {
-                       /*
-                        * Propagates the uninteresting bit to all parents.
-                        * if we have already visited this parent, then
-                        * the uninteresting bit will be propagated to each
-                        * reachable commit that is still not marked
-                        * uninteresting and won't otherwise be reached.
-                        */
-                       mark_ancestors_uninteresting(parent);
-               }
-
-               if (!(parent->object.flags & VISITED)) {
-                       if (parent->object.flags & BOUNDARY) {
-                               if (*stack) {
-                                       die("something else is on the stack - %s",
-                                           sha1_to_hex((*stack)->item->object.sha1));
-                               }
-                               push_onto_merge_order_stack(stack, parent);
-                               parent->object.flags |= VISITED;
-
-                       } else {
-                               sort_first_epoch(parent, stack);
-                               if (parents) {
-                                       /*
-                                        * This indicates a possible
-                                        * discontinuity it may not be be
-                                        * actual discontinuity if the head
-                                        * of parent N happens to be the tail
-                                        * of parent N+1.
-                                        *
-                                        * The next push onto the stack will
-                                        * resolve the question.
-                                        */
-                                       (*stack)->item->object.flags |= DISCONTINUITY;
-                               }
-                       }
-               }
-       }
-
-       push_onto_merge_order_stack(stack, head);
-}
-
-/*
- * Emit the contents of the stack.
- *
- * The stack is freed and replaced by NULL.
- *
- * Sets the return value to STOP if no further output should be generated.
- */
-static int emit_stack(struct commit_list **stack, emitter_func emitter, int include_last)
-{
-       unsigned int seen = 0;
-       int action = CONTINUE;
-
-       while (*stack && (action != STOP)) {
-               struct commit *next = pop_commit(stack);
-               seen |= next->object.flags;
-               if (*stack || include_last) {
-                       if (!*stack) 
-                               next->object.flags |= BOUNDARY;
-                       action = emitter(next);
-               }
-       }
-
-       if (*stack) {
-               free_commit_list(*stack);
-               *stack = NULL;
-       }
-
-       return (action == STOP || (seen & UNINTERESTING)) ? STOP : CONTINUE;
-}
-
-/*
- * Sorts an arbitrary epoch into merge order by sorting each epoch
- * of its epoch sequence into order.
- *
- * Note: this algorithm currently leaves traces of its execution in the
- * object flags of nodes it discovers. This should probably be fixed.
- */
-static int sort_in_merge_order(struct commit *head_of_epoch, emitter_func emitter)
-{
-       struct commit *next = head_of_epoch;
-       int ret = 0;
-       int action = CONTINUE;
-
-       ret = parse_commit(head_of_epoch);
-
-       next->object.flags |= BOUNDARY;
-
-       while (next && next->parents && !ret && (action != STOP)) {
-               struct commit *base = NULL;
-
-               ret = find_next_epoch_boundary(next, &base);
-               if (ret)
-                       return ret;
-               next->object.flags |= BOUNDARY;
-               if (base)
-                       base->object.flags |= BOUNDARY;
-
-               if (HAS_EXACTLY_ONE_PARENT(next)) {
-                       while (HAS_EXACTLY_ONE_PARENT(next)
-                              && (action != STOP)
-                              && !ret) {
-                               if (next->object.flags & UNINTERESTING) {
-                                       action = STOP;
-                               } else {
-                                       action = emitter(next);
-                               }
-                               if (action != STOP) {
-                                       next = next->parents->item;
-                                       ret = parse_commit(next);
-                               }
-                       }
-
-               } else {
-                       struct commit_list *stack = NULL;
-                       sort_first_epoch(next, &stack);
-                       action = emit_stack(&stack, emitter, (base == NULL));
-                       next = base;
-               }
-       }
-
-       if (next && (action != STOP) && !ret) {
-               emitter(next);
-       }
-
-       return ret;
-}
-
-/*
- * Sorts the nodes reachable from a starting list in merge order, we
- * first find the base for the starting list and then sort all nodes
- * in this subgraph using the sort_first_epoch algorithm. Once we have
- * reached the base we can continue sorting using sort_in_merge_order.
- */
-int sort_list_in_merge_order(struct commit_list *list, emitter_func emitter)
-{
-       struct commit_list *stack = NULL;
-       struct commit *base;
-       int ret = 0;
-       int action = CONTINUE;
-       struct commit_list *reversed = NULL;
-
-       for (; list; list = list->next)
-               commit_list_insert(list->item, &reversed);
-
-       if (!reversed)
-               return ret;
-       else if (!reversed->next) {
-               /*
-                * If there is only one element in the list, we can sort it
-                * using sort_in_merge_order.
-                */
-               base = reversed->item;
-       } else {
-               /*
-                * Otherwise, we search for the base of the list.
-                */
-               ret = find_base_for_list(reversed, &base);
-               if (ret)
-                       return ret;
-               if (base)
-                       base->object.flags |= BOUNDARY;
-
-               while (reversed) {
-                       struct commit * next = pop_commit(&reversed);
-
-                       if (!(next->object.flags & VISITED) && next!=base) {
-                               sort_first_epoch(next, &stack);
-                               if (reversed) {
-                                       /*
-                                        * If we have more commits 
-                                        * to push, then the first
-                                        * push for the next parent may 
-                                        * (or may * not) represent a 
-                                        * discontinuity with respect
-                                        * to the parent currently on 
-                                        * the top of the stack.
-                                        *
-                                        * Mark it for checking here, 
-                                        * and check it with the next 
-                                        * push. See sort_first_epoch()
-                                        * for more details.
-                                        */
-                                       stack->item->object.flags |= DISCONTINUITY;
-                               }
-                       }
-               }
-
-               action = emit_stack(&stack, emitter, (base==NULL));
-       }
-
-       if (base && (action != STOP)) {
-               ret = sort_in_merge_order(base, emitter);
-       }
-
-       return ret;
-}
diff --git a/epoch.h b/epoch.h
deleted file mode 100644 (file)
index 3756009..0000000
--- a/epoch.h
+++ /dev/null
@@ -1,20 +0,0 @@
-#ifndef EPOCH_H
-#define EPOCH_H
-
-
-// return codes for emitter_func
-#define STOP     0
-#define CONTINUE 1
-#define DO       2
-typedef int (*emitter_func) (struct commit *); 
-
-int sort_list_in_merge_order(struct commit_list *list, emitter_func emitter);
-
-/* Low bits are used by rev-list */
-#define BOUNDARY        (1u<<11)
-#define VISITED         (1u<<12)
-#define DISCONTINUITY   (1u<<13)
-#define LAST_EPOCH_FLAG (1u<<14)
-
-
-#endif /* EPOCH_H */
index 7cc4ae5a30ed3a56cff9def63e351f957cad6666..eab4aa891e522539bb145c9702200d3a788e650c 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -2,7 +2,8 @@
 #
 # Copyright (c) 2005, 2006 Junio C Hamano
 
-USAGE='[--signoff] [--dotest=<dir>] [--utf8] [--binary] [--3way] <mbox>
+USAGE='[--signoff] [--dotest=<dir>] [--utf8] [--binary] [--3way]
+  [--interactive] [--whitespace=<option>] <mbox>...
   or, when resuming [--skip | --resolved]'
 . git-sh-setup
 
@@ -100,7 +101,7 @@ fall_back_3way () {
 }
 
 prec=4
-dotest=.dotest sign= utf8= keep= skip= interactive= resolved= binary=
+dotest=.dotest sign= utf8= keep= skip= interactive= resolved= binary= ws=
 
 while case "$#" in 0) break;; esac
 do
@@ -133,6 +134,9 @@ do
        --sk|--ski|--skip)
        skip=t; shift ;;
 
+       --whitespace=*)
+       ws=$1; shift ;;
+
        --)
        shift; break ;;
        -*)
@@ -171,10 +175,11 @@ else
                exit 1
        }
 
-       # -b, -s, -u and -k flags are kept for the resuming session after
-       # a patch failure.
+       # -b, -s, -u, -k and --whitespace flags are kept for the
+       # resuming session after a patch failure.
        # -3 and -i can and must be given when resuming.
        echo "$binary" >"$dotest/binary"
+       echo " $ws" >"$dotest/whitespace"
        echo "$sign" >"$dotest/sign"
        echo "$utf8" >"$dotest/utf8"
        echo "$keep" >"$dotest/keep"
@@ -202,6 +207,7 @@ if test "$(cat "$dotest/keep")" = t
 then
        keep=-k
 fi
+ws=`cat "$dotest/whitespace"`
 if test "$(cat "$dotest/sign")" = t
 then
        SIGNOFF=`git-var GIT_COMMITTER_IDENT | sed -e '
@@ -355,7 +361,7 @@ do
 
        case "$resolved" in
        '')
-               git-apply $binary --index "$dotest/patch"
+               git-apply $binary --index $ws "$dotest/patch"
                apply_status=$?
                ;;
        t)
index f9c2c6caf5d2127d59cfd91d7bbecc2bc1466daf..d93ee19c7e7ff42762071c9df6f31da5dfa0fe80 100755 (executable)
@@ -15,6 +15,8 @@ ()
        print STDERR 'Usage: ${\basename $0} [-s] [-S revs-file] file [ revision ]
        -l, --long
                        Show long rev (Defaults off)
+       -t, --time
+                       Show raw timestamp (Defaults off)
        -r, --rename
                        Follow renames (Defaults on).
        -S, --rev-file revs-file
@@ -26,12 +28,13 @@ ()
        exit(1);
 }
 
-our ($help, $longrev, $rename, $starting_rev, $rev_file) = (0, 0, 1);
+our ($help, $longrev, $rename, $rawtime, $starting_rev, $rev_file) = (0, 0, 1);
 
 my $rc = GetOptions(   "long|l" => \$longrev,
+                       "time|t" => \$rawtime,
                        "help|h" => \$help,
                        "rename|r" => \$rename,
-                       "rev-file|S" => \$rev_file);
+                       "rev-file|S=s" => \$rev_file);
 if (!$rc or $help) {
        usage();
 }
@@ -125,7 +128,7 @@ ()
        }
 
        printf("%s\t(%10s\t%10s\t%d)%s\n", $rev, $committer,
-               format_date($date), $i++, $output);
+               format_date($date), ++$i, $output);
 }
 
 sub init_claim {
@@ -174,7 +177,8 @@ sub git_rev_list {
 
        my $revlist;
        if ($rev_file) {
-               open($revlist, '<' . $rev_file);
+               open($revlist, '<' . $rev_file)
+                   or die "Failed to open $rev_file : $!";
        } else {
                $revlist = open_pipe("git-rev-list","--parents","--remove-empty",$rev,"--",$file)
                        or die "Failed to exec git-rev-list: $!";
@@ -304,6 +308,12 @@ sub _git_diff_parse {
                        }
                        $ri++;
 
+               } elsif (m/^\\/) {
+                       ;
+                       # Skip \No newline at end of file.
+                       # But this can be internationalized, so only look
+                       # for an initial \
+
                } else {
                        if (substr($_,1) ne get_line($slines,$ri) ) {
                                die sprintf("Line %d (%d) does not match:\n|%s\n|%s\n%s => %s\n",
@@ -404,8 +414,10 @@ sub git_commit_info {
 }
 
 sub format_date {
+       if ($rawtime) {
+               return $_[0];
+       }
        my ($timestamp, $timezone) = split(' ', $_[0]);
-
        return strftime("%Y-%m-%d %H:%M:%S " . $timezone, gmtime($timestamp));
 }
 
index 6792624d4674bc94f7e13af0799b4687cc9199eb..740bc1fd52286dfb486570bf6ea727e9cbaefbfc 100755 (executable)
@@ -928,7 +928,7 @@ sub find_parents {
 
        # now walk up to the mergepoint collecting what patches we have
        my $branchtip = git_rev_parse($ps->{branch});
-       my @ancestors = `git-rev-list --merge-order $branchtip ^$mergebase`;
+       my @ancestors = `git-rev-list --topo-order $branchtip ^$mergebase`;
        my %have; # collected merges this branch has
        foreach my $merge (@{$ps->{merges}}) {
            $have{$merge} = 1;
@@ -951,7 +951,7 @@ sub find_parents {
        # see what the remote branch has - these are the merges we 
        # will want to have in a consecutive series from the mergebase
        my $otherbranchtip = git_rev_parse($branch);
-       my @needraw = `git-rev-list --merge-order $otherbranchtip ^$mergebase`;
+       my @needraw = `git-rev-list --topo-order $otherbranchtip ^$mergebase`;
        my @need;
        foreach my $needps (@needraw) {         # get the psets
            $needps = commitid2pset($needps);
index 6ac961e6d18b8419abdaa5ed57a55ef58f7bc0fb..663a3a370c8889e2ba78d627811c652d41e7971b 100755 (executable)
@@ -48,6 +48,12 @@ If you are sure you want to delete it, run 'git branch -D $branch_name'."
     exit 0
 }
 
+ls_remote_branches () {
+    git-rev-parse --symbolic --all |
+    sed -ne 's|^refs/\(remotes/\)|\1|p' |
+    sort
+}
+
 force=
 while case "$#,$1" in 0,*) break ;; *,-*) ;; *) break ;; esac
 do
@@ -56,6 +62,10 @@ do
                delete_branch "$@"
                exit
                ;;
+       -r)
+               ls_remote_branches
+               exit
+               ;;
        -f)
                force="$1"
                ;;
index f7ee1aadee1eb947603dadbd6ec077dab07f6a19..d9ec1f14d9ec3336c4c772d0c03f30d5f1511d56 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Linus Torvalds
 # Copyright (c) 2006 Junio C Hamano
 
-USAGE='[-a] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit>] [-e] [--author <author>] [[-i | -o] <path>...]'
+USAGE='[-a] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit>) [--amend] [-e] [--author <author>] [[-i | -o] <path>...]'
 SUBDIRECTORY_OK=Yes
 . git-sh-setup
 
@@ -64,6 +64,22 @@ run_status () {
        # We always show status for the whole tree.
        cd "$TOP"
 
+       IS_INITIAL="$initial_commit"
+       REFERENCE=HEAD
+       case "$amend" in
+       t)
+               # If we are amending the initial commit, there
+               # is no HEAD^1.
+               if git-rev-parse --verify "HEAD^1" >/dev/null 2>&1
+               then
+                       REFERENCE="HEAD^1"
+                       IS_INITIAL=
+               else
+                       IS_INITIAL=t
+               fi
+               ;;
+       esac
+
        # If TMP_INDEX is defined, that means we are doing
        # "--only" partial commit, and that index file is used
        # to build the tree for the commit.  Otherwise, if
@@ -85,10 +101,10 @@ run_status () {
        *)  echo "# On branch $branch" ;;
        esac
 
-       if test -z "$initial_commit"
+       if test -z "$IS_INITIAL"
        then
            git-diff-index -M --cached --name-status \
-               --diff-filter=MDTCRA HEAD |
+               --diff-filter=MDTCRA $REFERENCE |
            sed -e '
                    s/\\/\\\\/g
                    s/ /\\ /g
@@ -147,7 +163,7 @@ run_status () {
 
        if test -n "$verbose"
        then
-           git-diff-index --cached -M -p --diff-filter=MDTCRA HEAD
+           git-diff-index --cached -M -p --diff-filter=MDTCRA $REFERENCE
        fi
        case "$committable" in
        0)
@@ -173,6 +189,7 @@ also=
 only=
 logfile=
 use_commit=
+amend=
 no_edit=
 log_given=
 log_message=
@@ -254,6 +271,12 @@ do
       verify=
       shift
       ;;
+  --a|--am|--ame|--amen|--amend)
+      amend=t
+      log_given=t$log_given
+      use_commit=HEAD
+      shift
+      ;;
   -c)
       case "$#" in 1) usage ;; esac
       shift
@@ -328,6 +351,15 @@ done
 ################################################################
 # Sanity check options
 
+case "$amend,$initial_commit" in
+t,t)
+  die "You do not have anything to amend." ;;
+t,)
+  if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
+    die "You are in the middle of a merge -- cannot amend."
+  fi ;;
+esac
+
 case "$log_given" in
 tt*)
   die "Only one of -c/-C/-F/-m can be used." ;;
@@ -559,13 +591,18 @@ if test -z "$initial_commit"
 then
        if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
                PARENTS="-p HEAD "`sed -e 's/^/-p /' "$GIT_DIR/MERGE_HEAD"`
+       elif test -n "$amend"; then
+               PARENTS=$(git-cat-file commit HEAD |
+                       sed -n -e '/^$/q' -e 's/^parent /-p /p')
        fi
+       current=$(git-rev-parse --verify HEAD)
 else
        if [ -z "$(git-ls-files)" ]; then
                echo >&2 Nothing to commit
                exit 1
        fi
        PARENTS=""
+       current=
 fi
 
 {
index d20d1a8c4ba5a4e8d17fd47b1cca000fcf84dda5..7d3f78e375066c27f0881a92c23fda8d5c8d6dff 100755 (executable)
@@ -53,6 +53,7 @@
     'Entry'           => \&req_Entry,
     'Modified'        => \&req_Modified,
     'Unchanged'       => \&req_Unchanged,
+    'Questionable'    => \&req_Questionable,
     'Argument'        => \&req_Argument,
     'Argumentx'       => \&req_Argument,
     'expand-modules'  => \&req_expandmodules,
@@ -63,6 +64,7 @@
     'ci'              => \&req_ci,
     'diff'            => \&req_diff,
     'log'             => \&req_log,
+    'rlog'            => \&req_log,
     'tag'             => \&req_CATCHALL,
     'status'          => \&req_status,
     'admin'           => \&req_CATCHALL,
 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
+# main loop
+if (@ARGV && $ARGV[0] eq 'pserver') {
+    my $line = <STDIN>; chomp $line;
+    unless( $line eq 'BEGIN AUTH REQUEST') {
+       die "E Do not understand $line - expecting BEGIN AUTH REQUEST\n";
+    }
+    $line = <STDIN>; chomp $line;
+    req_Root('root', $line) # reuse Root
+       or die "E Invalid root $line \n";
+    $line = <STDIN>; chomp $line;
+    unless ($line eq 'anonymous') {
+       print "E Only anonymous user allowed via pserver\n";
+       print "I HATE YOU\n";
+    }
+    $line = <STDIN>; chomp $line;    # validate the password?
+    $line = <STDIN>; chomp $line;
+    unless ($line eq 'END AUTH REQUEST') {
+       die "E Do not understand $line -- expecting END AUTH REQUEST\n";
+    }
+    print "I LOVE YOU\n";
+    # and now back to our regular programme...
+}
+
 # Keep going until the client closes the connection
 while (<STDIN>)
 {
@@ -137,8 +164,21 @@ sub req_Root
     $state->{CVSROOT} = $data;
 
     $ENV{GIT_DIR} = $state->{CVSROOT} . "/";
+    unless (-d $ENV{GIT_DIR} && -e $ENV{GIT_DIR}.'HEAD') {
+       print "E $ENV{GIT_DIR} does not seem to be a valid GIT repository\n";
+        print "E \n";
+        print "error 1 $ENV{GIT_DIR} is not a valid repository\n";
+       return 0;
+    }
 
-    foreach my $line ( `git-var -l` )
+    my @gitvars = `git-var -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 \n";
+        print "error 1 - problem executing git-var\n";
+       return 0;
+    }
+    foreach my $line ( @gitvars )
     {
         next unless ( $line =~ /^(.*?)\.(.*?)=(.*)$/ );
         $cfg->{$1}{$2} = $3;
@@ -150,6 +190,7 @@ sub req_Root
         print "E the repo config file needs a [gitcvs] section added, and the parameter 'enabled' set to 1\n";
         print "E \n";
         print "error 1 GITCVS emulation disabled\n";
+        return 0;
     }
 
     if ( defined ( $cfg->{gitcvs}{logfile} ) )
@@ -158,6 +199,8 @@ sub req_Root
     } else {
         $log->nofile();
     }
+
+    return 1;
 }
 
 # Global_option option \n
@@ -459,6 +502,22 @@ 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
@@ -553,8 +612,62 @@ sub req_co
     my $updater = GITCVS::updater->new($state->{CVSROOT}, $module, $log);
     $updater->update();
 
+    $checkout_path =~ s|/$||; # get rid of trailing slashes
+
+    # Eclipse seems to need the Clear-sticky command
+    # to prepare the 'Entries' file for the new directory.
+    print "Clear-sticky $checkout_path/\n";
+    print $state->{CVSROOT} . "/$module/\n";
+    print "Clear-static-directory $checkout_path/\n";
+    print $state->{CVSROOT} . "/$module/\n";
+    print "Clear-sticky $checkout_path/\n"; # yes, twice
+    print $state->{CVSROOT} . "/$module/\n";
+    print "Template $checkout_path/\n";
+    print $state->{CVSROOT} . "/$module/\n";
+    print "0\n";
+
     # instruct the client that we're checking out to $checkout_path
-    print "E cvs server: updating $checkout_path\n";
+    print "E cvs checkout: Updating $checkout_path\n";
+
+    my %seendirs = ();
+    my $lastdir ='';
+
+    # recursive
+    sub prepdir {
+       my ($dir, $repodir, $remotedir, $seendirs) = @_;
+       my $parent = dirname($dir);
+       $dir       =~ s|/+$||;
+       $repodir   =~ s|/+$||;
+       $remotedir =~ s|/+$||;
+       $parent    =~ s|/+$||;
+       $log->debug("announcedir $dir, $repodir, $remotedir" );
+
+       if ($parent eq '.' || $parent eq './') {
+           $parent = '';
+       }
+       # recurse to announce unseen parents first
+       if (length($parent) && !exists($seendirs->{$parent})) {
+           prepdir($parent, $repodir, $remotedir, $seendirs);
+       }
+       # Announce that we are going to modify at the parent level
+       if ($parent) {
+           print "E cvs checkout: Updating $remotedir/$parent\n";
+       } else {
+           print "E cvs checkout: Updating $remotedir\n";
+       }
+       print "Clear-sticky $remotedir/$parent/\n";
+       print "$repodir/$parent/\n";
+
+       print "Clear-static-directory $remotedir/$dir/\n";
+       print "$repodir/$dir/\n";
+       print "Clear-sticky $remotedir/$parent/\n"; # yes, twice
+       print "$repodir/$parent/\n";
+       print "Template $remotedir/$dir/\n";
+       print "$repodir/$dir/\n";
+       print "0\n";
+
+       $seendirs->{$dir} = 1;
+    }
 
     foreach my $git ( @{$updater->gethead} )
     {
@@ -563,25 +676,32 @@ sub req_co
 
         ( $git->{name}, $git->{dir} ) = filenamesplit($git->{name});
 
+       if (length($git->{dir}) && $git->{dir} ne './'
+           && $git->{dir} ne $lastdir ) {
+           unless (exists($seendirs{$git->{dir}})) {
+               prepdir($git->{dir}, $state->{CVSROOT} . "/$module/",
+                       $checkout_path, \%seendirs);
+               $lastdir = $git->{dir};
+               $seendirs{$git->{dir}} = 1;
+           }
+           print "E cvs checkout: Updating /$checkout_path/$git->{dir}\n";
+       }
+
         # modification time of this file
         print "Mod-time $git->{modified}\n";
 
         # print some information to the client
-        print "MT +updated\n";
-        print "MT text U\n";
         if ( defined ( $git->{dir} ) and $git->{dir} ne "./" )
         {
-            print "MT fname $checkout_path/$git->{dir}$git->{name}\n";
+            print "M U $checkout_path/$git->{dir}$git->{name}\n";
         } else {
-            print "MT fname $checkout_path/$git->{name}\n";
+            print "M U $checkout_path/$git->{name}\n";
         }
-        print "MT newline\n";
-        print "MT -updated\n";
 
-        # instruct client we're sending a file to put in this path
-        print "Created $checkout_path/" . ( defined ( $git->{dir} ) ? $git->{dir} . "/" : "" ) . "\n";
+       # instruct client we're sending a file to put in this path
+       print "Created $checkout_path/" . ( defined ( $git->{dir} ) and $git->{dir} ne "./" ? $git->{dir} . "/" : "" ) . "\n";
 
-        print $state->{CVSROOT} . "/$module/" . ( defined ( $git->{dir} ) ? $git->{dir} . "/" : "" ) . "$git->{name}\n";
+       print $state->{CVSROOT} . "/$module/" . ( defined ( $git->{dir} ) and $git->{dir} ne "./" ? $git->{dir} . "/" : "" ) . "$git->{name}\n";
 
         # this is an "entries" line
         print "/$git->{name}/1.$git->{revision}///\n";
@@ -612,6 +732,26 @@ sub req_update
 
     argsplit("update");
 
+    #
+    # It may just be a client exploring the available heads/modukles
+    # 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.
+    #
+    if ($state->{module} eq '') {
+        print "E cvs update: Updating .\n";
+       opendir HEADS, $state->{CVSROOT} . '/refs/heads';
+       while (my $head = readdir(HEADS)) {
+           if (-f $state->{CVSROOT} . '/refs/heads/' . $head) {
+               print "E cvs update: New directory `$head'\n";
+           }
+       }
+       closedir HEADS;
+       print "ok\n";
+       return 1;
+    }
+
+
     # Grab a handle to the SQLite db and do any necessary updates
     my $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log);
 
@@ -657,8 +797,27 @@ sub req_update
 
         #$log->debug("Target revision is $meta->{revision}, current working revision is $wrev");
 
-        # Files are up to date if the working copy and repo copy have the same revision, and the working copy is unmodified _and_ the user hasn't specified -C
-        next if ( defined ( $wrev ) and defined($meta->{revision}) and $wrev == $meta->{revision} and $state->{entries}{$filename}{unchanged} and not exists ( $state->{opt}{C} ) );
+        # Files are up to date if the working copy and repo copy have the same revision,
+        # and the working copy is unmodified _and_ the user hasn't specified -C
+        next if ( defined ( $wrev )
+                  and defined($meta->{revision})
+                  and $wrev == $meta->{revision}
+                  and $state->{entries}{$filename}{unchanged}
+                  and not exists ( $state->{opt}{C} ) );
+
+        # If the working copy and repo copy have the same revision,
+        # but the working copy is modified, tell the client it's modified
+        if ( defined ( $wrev )
+             and defined($meta->{revision})
+             and $wrev == $meta->{revision}
+             and not exists ( $state->{opt}{C} ) )
+        {
+            $log->info("Tell the client the file is modified");
+            print "MT text U\n";
+            print "MT fname $filename\n";
+            print "MT newline\n";
+            next;
+        }
 
         if ( $meta->{filehash} eq "deleted" )
         {
@@ -670,7 +829,8 @@ sub req_update
             print "Removed $dirpart\n";
             print "$filepart\n";
         }
-        elsif ( not defined ( $state->{entries}{$filename}{modified_hash} ) or $state->{entries}{$filename}{modified_hash} eq $oldmeta->{filehash} )
+        elsif ( not defined ( $state->{entries}{$filename}{modified_hash} )
+               or $state->{entries}{$filename}{modified_hash} eq $oldmeta->{filehash} )
         {
             $log->info("Updating '$filename'");
             # normal update, just send the new revision (either U=Update, or A=Add, or R=Remove)
@@ -706,6 +866,7 @@ sub req_update
             # transmit file
             transmitfile($meta->{filehash});
         } else {
+            $log->info("Updating '$filename'");
             my ( $filepart, $dirpart ) = filenamesplit($meta->{name});
 
             my $dir = tempdir( DIR => $TEMP_DIR, CLEANUP => 1 ) . "/";
@@ -781,6 +942,12 @@ sub req_ci
 
     $log->info("req_ci : " . ( defined($data) ? $data : "[NULL]" ));
 
+    if ( @ARGV && $ARGV[0] eq 'pserver')
+    {
+        print "error 1 pserver access cannot commit\n";
+        exit;
+    }
+
     if ( -e $state->{CVSROOT} . "/index" )
     {
         print "error 1 Index already exists in git repo\n";
@@ -2271,7 +2438,7 @@ sub gethead
 
     return $self->{gethead_cache} if ( defined ( $self->{gethead_cache} ) );
 
-    my $db_query = $self->{dbh}->prepare_cached("SELECT name, filehash, mode, revision, modified, commithash, author FROM head",{},1);
+    my $db_query = $self->{dbh}->prepare_cached("SELECT name, filehash, mode, revision, modified, commithash, author FROM head ORDER BY name ASC",{},1);
     $db_query->execute();
 
     my $tree = [];
index 2ea852c9185743d5b84523698cbffaaffb238543..75aa8feeb6201c54e55bfaca0cc178c53891e776 100755 (executable)
@@ -19,25 +19,26 @@ ()
        exit(1);
 }
 
-my $GIT_DIR = `git rev-parse --git-dir`;
-exit 1 if $?; # rev-parse would have given "not a git dir" message.
-chomp($GIT_DIR);
-
 our ($opt_n, $opt_f, $opt_h, $opt_k, $opt_v);
 getopts("hnfkv") || usage;
 usage() if $opt_h;
 @ARGV >= 1 or usage;
 
+my $GIT_DIR = `git rev-parse --git-dir`;
+exit 1 if $?; # rev-parse would have given "not a git dir" message.
+chomp($GIT_DIR);
+
 my (@srcArgs, @dstArgs, @srcs, @dsts);
 my ($src, $dst, $base, $dstDir);
 
+# remove any trailing slash in arguments
+for (@ARGV) { s/\/*$//; }
+
 my $argCount = scalar @ARGV;
 if (-d $ARGV[$argCount-1]) {
        $dstDir = $ARGV[$argCount-1];
-       # remove any trailing slash
-       $dstDir =~ s/\/$//;
        @srcArgs = @ARGV[0..$argCount-2];
-       
+
        foreach $src (@srcArgs) {
                $base = $src;
                $base =~ s/^.*\///;
@@ -46,10 +47,14 @@ ()
        }
 }
 else {
-    if ($argCount != 2) {
+    if ($argCount < 2) {
+       print "Error: need at least two arguments\n";
+       exit(1);
+    }
+    if ($argCount > 2) {
        print "Error: moving to directory '"
            . $ARGV[$argCount-1]
-           . "' not possible; not exisiting\n";
+           . "' not possible; not existing\n";
        exit(1);
     }
     @srcArgs = ($ARGV[0]);
@@ -57,6 +62,24 @@ ()
     $dstDir = "";
 }
 
+my $subdir_prefix = `git rev-parse --show-prefix`;
+chomp($subdir_prefix);
+
+# run in git base directory, so that git-ls-files lists all revisioned files
+chdir "$GIT_DIR/..";
+
+# normalize paths, needed to compare against versioned files and update-index
+# also, this is nicer to end-users by doing ".//a/./b/.//./c" ==> "a/b/c"
+for (@srcArgs, @dstArgs) {
+    # prepend git prefix as we run from base directory
+    $_ = $subdir_prefix.$_;
+    s|^\./||;
+    s|/\./|/| while (m|/\./|);
+    s|//+|/|g;
+    # Also "a/b/../c" ==> "a/c"
+    1 while (s,(^|/)[^/]+/\.\./,$1,);
+}
+
 my (@allfiles,@srcfiles,@dstfiles);
 my $safesrc;
 my (%overwritten, %srcForDst);
index b0d095b4e95bb665602d3527068341f41ccdf66a..7c8d51223fd940d8e7ccfebe90046bf32f90e327 100755 (executable)
@@ -54,7 +54,7 @@
                    "compose" => \$compose,
                    "quiet" => \$quiet,
                    "suppress-from" => \$suppress_from,
-                   "no-signed-off-cc" => \$no_signed_off_cc,
+                   "no-signed-off-cc|no-signed-off-by-cc" => \$no_signed_off_cc,
         );
 
 # Now, let's fill any that aren't set in with defaults:
index 726b1e706ba247ee5b17e1312d7195119a690da2..36f171b3028a3460c1c51f9c1f5747a9a0d2d850 100755 (executable)
@@ -4,9 +4,21 @@ USAGE='<tag>'
 SUBDIRECTORY_OK='Yes'
 . git-sh-setup
 
+verbose=
+while case $# in 0) break;; esac
+do
+       case "$1" in
+       -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
+               verbose=t ;;
+       *)
+               break ;;
+       esac
+       shift
+done
+
 if [ "$#" != "1" ]
 then
-  usage
+       usage
 fi
 
 type="$(git-cat-file -t "$1" 2>/dev/null)" ||
@@ -15,6 +27,13 @@ type="$(git-cat-file -t "$1" 2>/dev/null)" ||
 test "$type" = tag ||
        die "$1: cannot verify a non-tag object of type $type."
 
+case "$verbose" in
+t)
+       git-cat-file -p "$1" |
+       sed -n -e '/^-----BEGIN PGP SIGNATURE-----/q' -e p
+       ;;
+esac
+
 git-cat-file tag "$1" >"$GIT_DIR/.tmp-vtag" || exit 1
 cat "$GIT_DIR/.tmp-vtag" |
 sed '/-----BEGIN PGP/Q' |
diff --git a/git.c b/git.c
index 993cd0d4904be11edb6234e47d10376733928740..a547dbd9136c70e49340814d1d57e92ec94c4c3d 100644 (file)
--- a/git.c
+++ b/git.c
 #include "git-compat-util.h"
 #include "exec_cmd.h"
 
+#include "cache.h"
+#include "commit.h"
+#include "revision.h"
+
 #ifndef PATH_MAX
 # define PATH_MAX 4096
 #endif
@@ -245,6 +249,80 @@ static int cmd_help(int argc, char **argv, char **envp)
        return 0;
 }
 
+#define LOGSIZE (65536)
+
+static int cmd_log(int argc, char **argv, char **envp)
+{
+       struct rev_info rev;
+       struct commit *commit;
+       char *buf = xmalloc(LOGSIZE);
+       static enum cmit_fmt commit_format = CMIT_FMT_DEFAULT;
+       int abbrev = DEFAULT_ABBREV;
+       int show_parents = 0;
+       const char *commit_prefix = "commit ";
+
+       argc = setup_revisions(argc, argv, &rev, "HEAD");
+       while (1 < argc) {
+               char *arg = argv[1];
+               if (!strncmp(arg, "--pretty", 8)) {
+                       commit_format = get_commit_format(arg + 8);
+                       if (commit_format == CMIT_FMT_ONELINE)
+                               commit_prefix = "";
+               }
+               else if (!strcmp(arg, "--parents")) {
+                       show_parents = 1;
+               }
+               else if (!strcmp(arg, "--no-abbrev")) {
+                       abbrev = 0;
+               }
+               else if (!strncmp(arg, "--abbrev=", 9)) {
+                       abbrev = strtoul(arg + 9, NULL, 10);
+                       if (abbrev && abbrev < MINIMUM_ABBREV)
+                               abbrev = MINIMUM_ABBREV;
+                       else if (40 < abbrev)
+                               abbrev = 40;
+               }
+               else
+                       die("unrecognized argument: %s", arg);
+               argc--; argv++;
+       }
+
+       prepare_revision_walk(&rev);
+       setup_pager();
+       while ((commit = get_revision(&rev)) != NULL) {
+               printf("%s%s", commit_prefix,
+                      sha1_to_hex(commit->object.sha1));
+               if (show_parents) {
+                       struct commit_list *parents = commit->parents;
+                       while (parents) {
+                               struct object *o = &(parents->item->object);
+                               parents = parents->next;
+                               if (o->flags & TMP_MARK)
+                                       continue;
+                               printf(" %s", sha1_to_hex(o->sha1));
+                               o->flags |= TMP_MARK;
+                       }
+                       /* TMP_MARK is a general purpose flag that can
+                        * be used locally, but the user should clean
+                        * things up after it is done with them.
+                        */
+                       for (parents = commit->parents;
+                            parents;
+                            parents = parents->next)
+                               parents->item->object.flags &= ~TMP_MARK;
+               }
+               if (commit_format == CMIT_FMT_ONELINE)
+                       putchar(' ');
+               else
+                       putchar('\n');
+               pretty_print_commit(commit_format, commit, ~0, buf,
+                                   LOGSIZE, abbrev);
+               printf("%s\n", buf);
+       }
+       free(buf);
+       return 0;
+}
+
 #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
 
 static void handle_internal_command(int argc, char **argv, char **envp)
@@ -256,6 +334,7 @@ static void handle_internal_command(int argc, char **argv, char **envp)
        } commands[] = {
                { "version", cmd_version },
                { "help", cmd_help },
+               { "log", cmd_log },
        };
        int i;
 
index 21ee572f48287b36628a027ab265ab58b85c90af..136a7f5aad61e8a90866ee7d0b4bc22e387f0396 100644 (file)
@@ -99,7 +99,7 @@ static int reused_delta = 0;
 
 static int pack_revindex_ix(struct packed_git *p)
 {
-       unsigned long ui = (unsigned long)(long)p;
+       unsigned long ui = (unsigned long)p;
        int i;
 
        ui = ui ^ (ui >> 16); /* defeat structure alignment */
diff --git a/pager.c b/pager.c
new file mode 100644 (file)
index 0000000..1364e15
--- /dev/null
+++ b/pager.c
@@ -0,0 +1,48 @@
+#include "cache.h"
+
+/*
+ * This is split up from the rest of git so that we might do
+ * something different on Windows, for example.
+ */
+
+static void run_pager(void)
+{
+       const char *prog = getenv("PAGER");
+       if (!prog)
+               prog = "less";
+       setenv("LESS", "-S", 0);
+       execlp(prog, prog, NULL);
+}
+
+void setup_pager(void)
+{
+       pid_t pid;
+       int fd[2];
+
+       if (!isatty(1))
+               return;
+       if (pipe(fd) < 0)
+               return;
+       pid = fork();
+       if (pid < 0) {
+               close(fd[0]);
+               close(fd[1]);
+               return;
+       }
+
+       /* return in the child */
+       if (!pid) {
+               dup2(fd[1], 1);
+               close(fd[0]);
+               close(fd[1]);
+               return;
+       }
+
+       /* The original process turns into the PAGER */
+       dup2(fd[0], 0);
+       close(fd[0]);
+       close(fd[1]);
+
+       run_pager();
+       exit(255);
+}
index f39fe5ca653db7a4972cf99e790991c3d88495d0..be29b3fe11a370e53d3dce6a43dfccdc0e22ae2c 100644 (file)
@@ -560,9 +560,11 @@ static int threeway_merge(struct cache_entry **stages)
                 */
                if ((head_deleted && remote_deleted) ||
                    (head_deleted && remote && remote_match) ||
-                   (remote_deleted && head && head_match))
+                   (remote_deleted && head && head_match)) {
+                       if (index)
+                               return deleted_entry(index, index);
                        return 0;
-
+               }
                /*
                 * Added in both, identically.
                 */
@@ -704,7 +706,7 @@ static int read_cache_unmerged(void)
        return deleted;
 }
 
-static const char read_tree_usage[] = "git-read-tree (<sha> | -m [-u | -i] <sha1> [<sha2> [<sha3>]])";
+static const char read_tree_usage[] = "git-read-tree (<sha> | -m [--aggressive] [-u | -i] <sha1> [<sha2> [<sha3>]])";
 
 static struct cache_file cache_file;
 
diff --git a/refs.c b/refs.c
index 826ae7ade7b7ebab01f4446ccb135c6f90c5a3a2..982ebf8ae53f0d64d419e820eb541624c0dec449 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -151,10 +151,15 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u
                                        break;
                                continue;
                        }
-                       if (read_ref(git_path("%s", path), sha1) < 0)
+                       if (read_ref(git_path("%s", path), sha1) < 0) {
+                               fprintf(stderr, "%s points nowhere!", path);
                                continue;
-                       if (!has_sha1_file(sha1))
+                       }
+                       if (!has_sha1_file(sha1)) {
+                               fprintf(stderr, "%s does not point to a valid "
+                                               "commit object!", path);
                                continue;
+                       }
                        retval = fn(path, sha1);
                        if (retval)
                                break;
index 2e80930b2cec2e20be00b5d7751af6a6626efdc2..8e4d83efba369c5ce925fd1e22d36a768917147b 100644 (file)
@@ -4,16 +4,12 @@
 #include "commit.h"
 #include "tree.h"
 #include "blob.h"
-#include "epoch.h"
 #include "diff.h"
 #include "revision.h"
 
-/* bits #0 and #1 in revision.h */
+/* bits #0-4 in revision.h */
 
-#define COUNTED                (1u << 2)
-#define SHOWN          (1u << 3)
-#define TREECHANGE     (1u << 4)
-#define TMP_MARK       (1u << 5) /* for isolated cases; clean after use */
+#define COUNTED                (1u<<5)
 
 static const char rev_list_usage[] =
 "git-rev-list [OPTION] <commit-id>... [ -- paths... ]\n"
@@ -26,7 +22,6 @@ static const char rev_list_usage[] =
 "    --remove-empty\n"
 "    --all\n"
 "  ordering output:\n"
-"    --merge-order [ --show-breaks ]\n"
 "    --topo-order\n"
 "    --date-order\n"
 "  formatting output:\n"
@@ -48,22 +43,9 @@ static int show_parents = 0;
 static int hdr_termination = 0;
 static const char *commit_prefix = "";
 static enum cmit_fmt commit_format = CMIT_FMT_RAW;
-static int merge_order = 0;
-static int show_breaks = 0;
-static int stop_traversal = 0;
-static int no_merges = 0;
 
 static void show_commit(struct commit *commit)
 {
-       commit->object.flags |= SHOWN;
-       if (show_breaks) {
-               commit_prefix = "| ";
-               if (commit->object.flags & DISCONTINUITY) {
-                       commit_prefix = "^ ";     
-               } else if (commit->object.flags & BOUNDARY) {
-                       commit_prefix = "= ";
-               } 
-        }                      
        printf("%s%s", commit_prefix, sha1_to_hex(commit->object.sha1));
        if (show_parents) {
                struct commit_list *parents = commit->parents;
@@ -97,73 +79,6 @@ static void show_commit(struct commit *commit)
        fflush(stdout);
 }
 
-static int rewrite_one(struct commit **pp)
-{
-       for (;;) {
-               struct commit *p = *pp;
-               if (p->object.flags & (TREECHANGE | UNINTERESTING))
-                       return 0;
-               if (!p->parents)
-                       return -1;
-               *pp = p->parents->item;
-       }
-}
-
-static void rewrite_parents(struct commit *commit)
-{
-       struct commit_list **pp = &commit->parents;
-       while (*pp) {
-               struct commit_list *parent = *pp;
-               if (rewrite_one(&parent->item) < 0) {
-                       *pp = parent->next;
-                       continue;
-               }
-               pp = &parent->next;
-       }
-}
-
-static int filter_commit(struct commit * commit)
-{
-       if (stop_traversal && (commit->object.flags & BOUNDARY))
-               return STOP;
-       if (commit->object.flags & (UNINTERESTING|SHOWN))
-               return CONTINUE;
-       if (revs.min_age != -1 && (commit->date > revs.min_age))
-               return CONTINUE;
-       if (revs.max_age != -1 && (commit->date < revs.max_age)) {
-               stop_traversal=1;
-               return CONTINUE;
-       }
-       if (no_merges && (commit->parents && commit->parents->next))
-               return CONTINUE;
-       if (revs.paths && revs.dense) {
-               if (!(commit->object.flags & TREECHANGE))
-                       return CONTINUE;
-               rewrite_parents(commit);
-       }
-       return DO;
-}
-
-static int process_commit(struct commit * commit)
-{
-       int action=filter_commit(commit);
-
-       if (action == STOP) {
-               return STOP;
-       }
-
-       if (action == CONTINUE) {
-               return CONTINUE;
-       }
-
-       if (revs.max_count != -1 && !revs.max_count--)
-               return STOP;
-
-       show_commit(commit);
-
-       return CONTINUE;
-}
-
 static struct object_list **process_blob(struct blob *blob,
                                         struct object_list **p,
                                         struct name_path *path,
@@ -213,17 +128,16 @@ static struct object_list **process_tree(struct tree *tree,
        return p;
 }
 
-static void show_commit_list(struct commit_list *list)
+static void show_commit_list(struct rev_info *revs)
 {
+       struct commit *commit;
        struct object_list *objects = NULL, **p = &objects, *pending;
-       while (list) {
-               struct commit *commit = pop_most_recent_commit(&list, SEEN);
 
+       while ((commit = get_revision(revs)) != NULL) {
                p = process_tree(commit->tree, p, NULL, "");
-               if (process_commit(commit) == STOP)
-                       break;
+               show_commit(commit);
        }
-       for (pending = revs.pending_objects; pending; pending = pending->next) {
+       for (pending = revs->pending_objects; pending; pending = pending->next) {
                struct object *obj = pending->item;
                const char *name = pending->name;
                if (obj->flags & (UNINTERESTING | SEEN))
@@ -259,19 +173,6 @@ static void show_commit_list(struct commit_list *list)
        }
 }
 
-static int everybody_uninteresting(struct commit_list *orig)
-{
-       struct commit_list *list = orig;
-       while (list) {
-               struct commit *commit = list->item;
-               list = list->next;
-               if (commit->object.flags & UNINTERESTING)
-                       continue;
-               return 0;
-       }
-       return 1;
-}
-
 /*
  * This is a truly stupid algorithm, but it's only
  * used for bisection, and we just don't care enough.
@@ -379,224 +280,12 @@ static void mark_edges_uninteresting(struct commit_list *list)
        }
 }
 
-#define TREE_SAME      0
-#define TREE_NEW       1
-#define TREE_DIFFERENT 2
-static int tree_difference = TREE_SAME;
-
-static void file_add_remove(struct diff_options *options,
-                   int addremove, unsigned mode,
-                   const unsigned char *sha1,
-                   const char *base, const char *path)
-{
-       int diff = TREE_DIFFERENT;
-
-       /*
-        * Is it an add of a new file? It means that
-        * the old tree didn't have it at all, so we
-        * will turn "TREE_SAME" -> "TREE_NEW", but
-        * leave any "TREE_DIFFERENT" alone (and if
-        * it already was "TREE_NEW", we'll keep it
-        * "TREE_NEW" of course).
-        */
-       if (addremove == '+') {
-               diff = tree_difference;
-               if (diff != TREE_SAME)
-                       return;
-               diff = TREE_NEW;
-       }
-       tree_difference = diff;
-}
-
-static void file_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)
-{
-       tree_difference = TREE_DIFFERENT;
-}
-
-static struct diff_options diff_opt = {
-       .recursive = 1,
-       .add_remove = file_add_remove,
-       .change = file_change,
-};
-
-static int compare_tree(struct tree *t1, struct tree *t2)
-{
-       if (!t1)
-               return TREE_NEW;
-       if (!t2)
-               return TREE_DIFFERENT;
-       tree_difference = TREE_SAME;
-       if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "", &diff_opt) < 0)
-               return TREE_DIFFERENT;
-       return tree_difference;
-}
-
-static int same_tree_as_empty(struct tree *t1)
-{
-       int retval;
-       void *tree;
-       struct tree_desc empty, real;
-
-       if (!t1)
-               return 0;
-
-       tree = read_object_with_reference(t1->object.sha1, "tree", &real.size, NULL);
-       if (!tree)
-               return 0;
-       real.buf = tree;
-
-       empty.buf = "";
-       empty.size = 0;
-
-       tree_difference = 0;
-       retval = diff_tree(&empty, &real, "", &diff_opt);
-       free(tree);
-
-       return retval >= 0 && !tree_difference;
-}
-
-static void try_to_simplify_commit(struct commit *commit)
-{
-       struct commit_list **pp, *parent;
-
-       if (!commit->tree)
-               return;
-
-       if (!commit->parents) {
-               if (!same_tree_as_empty(commit->tree))
-                       commit->object.flags |= TREECHANGE;
-               return;
-       }
-
-       pp = &commit->parents;
-       while ((parent = *pp) != NULL) {
-               struct commit *p = parent->item;
-
-               if (p->object.flags & UNINTERESTING) {
-                       pp = &parent->next;
-                       continue;
-               }
-
-               parse_commit(p);
-               switch (compare_tree(p->tree, commit->tree)) {
-               case TREE_SAME:
-                       parent->next = NULL;
-                       commit->parents = parent;
-                       return;
-
-               case TREE_NEW:
-                       if (revs.remove_empty_trees && same_tree_as_empty(p->tree)) {
-                               *pp = parent->next;
-                               continue;
-                       }
-               /* fallthrough */
-               case TREE_DIFFERENT:
-                       pp = &parent->next;
-                       continue;
-               }
-               die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1));
-       }
-       commit->object.flags |= TREECHANGE;
-}
-
-static void add_parents_to_list(struct commit *commit, struct commit_list **list)
-{
-       struct commit_list *parent = commit->parents;
-
-       /*
-        * If the commit is uninteresting, don't try to
-        * prune parents - we want the maximal uninteresting
-        * set.
-        *
-        * Normally we haven't parsed the parent
-        * yet, so we won't have a parent of a parent
-        * here. However, it may turn out that we've
-        * reached this commit some other way (where it
-        * wasn't uninteresting), in which case we need
-        * to mark its parents recursively too..
-        */
-       if (commit->object.flags & UNINTERESTING) {
-               while (parent) {
-                       struct commit *p = parent->item;
-                       parent = parent->next;
-                       parse_commit(p);
-                       p->object.flags |= UNINTERESTING;
-                       if (p->parents)
-                               mark_parents_uninteresting(p);
-                       if (p->object.flags & SEEN)
-                               continue;
-                       p->object.flags |= SEEN;
-                       insert_by_date(p, list);
-               }
-               return;
-       }
-
-       /*
-        * Ok, the commit wasn't uninteresting. Try to
-        * simplify the commit history and find the parent
-        * that has no differences in the path set if one exists.
-        */
-       if (revs.paths)
-               try_to_simplify_commit(commit);
-
-       parent = commit->parents;
-       while (parent) {
-               struct commit *p = parent->item;
-
-               parent = parent->next;
-
-               parse_commit(p);
-               if (p->object.flags & SEEN)
-                       continue;
-               p->object.flags |= SEEN;
-               insert_by_date(p, list);
-       }
-}
-
-static struct commit_list *limit_list(struct commit_list *list)
-{
-       struct commit_list *newlist = NULL;
-       struct commit_list **p = &newlist;
-       while (list) {
-               struct commit_list *entry = list;
-               struct commit *commit = list->item;
-               struct object *obj = &commit->object;
-
-               list = list->next;
-               free(entry);
-
-               if (revs.max_age != -1 && (commit->date < revs.max_age))
-                       obj->flags |= UNINTERESTING;
-               if (revs.unpacked && has_sha1_pack(obj->sha1))
-                       obj->flags |= UNINTERESTING;
-               add_parents_to_list(commit, &list);
-               if (obj->flags & UNINTERESTING) {
-                       mark_parents_uninteresting(commit);
-                       if (everybody_uninteresting(list))
-                               break;
-                       continue;
-               }
-               if (revs.min_age != -1 && (commit->date > revs.min_age))
-                       continue;
-               p = &commit_list_insert(commit, p)->next;
-       }
-       if (revs.tree_objects)
-               mark_edges_uninteresting(newlist);
-       if (bisect_list)
-               newlist = find_bisection(newlist);
-       return newlist;
-}
-
 int main(int argc, const char **argv)
 {
        struct commit_list *list;
        int i;
 
-       argc = setup_revisions(argc, argv, &revs);
+       argc = setup_revisions(argc, argv, &revs, NULL);
 
        for (i = 1 ; i < argc; i++) {
                const char *arg = argv[i];
@@ -642,10 +331,6 @@ int main(int argc, const char **argv)
                                commit_prefix = "commit ";
                        continue;
                }
-               if (!strncmp(arg, "--no-merges", 11)) {
-                       no_merges = 1;
-                       continue;
-               }
                if (!strcmp(arg, "--parents")) {
                        show_parents = 1;
                        continue;
@@ -654,14 +339,6 @@ int main(int argc, const char **argv)
                        bisect_list = 1;
                        continue;
                }
-               if (!strcmp(arg, "--merge-order")) {
-                       merge_order = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--show-breaks")) {
-                       show_breaks = 1;
-                       continue;
-               }
                usage(rev_list_usage);
 
        }
@@ -672,33 +349,17 @@ int main(int argc, const char **argv)
            (!(revs.tag_objects||revs.tree_objects||revs.blob_objects) && !revs.pending_objects))
                usage(rev_list_usage);
 
-       if (revs.paths)
-               diff_tree_setup_paths(revs.paths);
+       prepare_revision_walk(&revs);
+       if (revs.tree_objects)
+               mark_edges_uninteresting(revs.commits);
+
+       if (bisect_list)
+               revs.commits = find_bisection(revs.commits);
 
        save_commit_buffer = verbose_header;
        track_object_refs = 0;
 
-       if (!merge_order) {             
-               sort_by_date(&list);
-               if (list && !revs.limited && revs.max_count == 1 &&
-                   !revs.tag_objects && !revs.tree_objects && !revs.blob_objects) {
-                       show_commit(list->item);
-                       return 0;
-               }
-               if (revs.limited)
-                       list = limit_list(list);
-               if (revs.topo_order)
-                       sort_in_topological_order(&list, revs.lifo);
-               show_commit_list(list);
-       } else {
-#ifndef NO_OPENSSL
-               if (sort_list_in_merge_order(list, &process_commit)) {
-                       die("merge order sort failed\n");
-               }
-#else
-               die("merge order sort unsupported, OpenSSL not linked");
-#endif
-       }
+       show_commit_list(&revs);
 
        return 0;
 }
index 610eacb35a2ebc5b64b48ae02367543fa2fbb67a..f90e999e607d238fbfe17e84f286570c04844100 100644 (file)
@@ -39,14 +39,12 @@ static int is_rev_argument(const char *arg)
                "--header",
                "--max-age=",
                "--max-count=",
-               "--merge-order",
                "--min-age=",
                "--no-merges",
                "--objects",
                "--objects-edge",
                "--parents",
                "--pretty",
-               "--show-breaks",
                "--sparse",
                "--topo-order",
                "--date-order",
index 67ff4de2d1e70e061aebc834e7b9632c8d7bf6cb..a3df8100763136e8936b8e3b4c647d5b83cc7239 100644 (file)
@@ -3,6 +3,7 @@
 #include "blob.h"
 #include "tree.h"
 #include "commit.h"
+#include "diff.h"
 #include "refs.h"
 #include "revision.h"
 
@@ -183,6 +184,232 @@ static struct commit *get_commit_reference(struct rev_info *revs, const char *na
        die("%s is unknown object", name);
 }
 
+static int everybody_uninteresting(struct commit_list *orig)
+{
+       struct commit_list *list = orig;
+       while (list) {
+               struct commit *commit = list->item;
+               list = list->next;
+               if (commit->object.flags & UNINTERESTING)
+                       continue;
+               return 0;
+       }
+       return 1;
+}
+
+#define TREE_SAME      0
+#define TREE_NEW       1
+#define TREE_DIFFERENT 2
+static int tree_difference = TREE_SAME;
+
+static void file_add_remove(struct diff_options *options,
+                   int addremove, unsigned mode,
+                   const unsigned char *sha1,
+                   const char *base, const char *path)
+{
+       int diff = TREE_DIFFERENT;
+
+       /*
+        * Is it an add of a new file? It means that
+        * the old tree didn't have it at all, so we
+        * will turn "TREE_SAME" -> "TREE_NEW", but
+        * leave any "TREE_DIFFERENT" alone (and if
+        * it already was "TREE_NEW", we'll keep it
+        * "TREE_NEW" of course).
+        */
+       if (addremove == '+') {
+               diff = tree_difference;
+               if (diff != TREE_SAME)
+                       return;
+               diff = TREE_NEW;
+       }
+       tree_difference = diff;
+}
+
+static void file_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)
+{
+       tree_difference = TREE_DIFFERENT;
+}
+
+static struct diff_options diff_opt = {
+       .recursive = 1,
+       .add_remove = file_add_remove,
+       .change = file_change,
+};
+
+static int compare_tree(struct tree *t1, struct tree *t2)
+{
+       if (!t1)
+               return TREE_NEW;
+       if (!t2)
+               return TREE_DIFFERENT;
+       tree_difference = TREE_SAME;
+       if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "", &diff_opt) < 0)
+               return TREE_DIFFERENT;
+       return tree_difference;
+}
+
+static int same_tree_as_empty(struct tree *t1)
+{
+       int retval;
+       void *tree;
+       struct tree_desc empty, real;
+
+       if (!t1)
+               return 0;
+
+       tree = read_object_with_reference(t1->object.sha1, "tree", &real.size, NULL);
+       if (!tree)
+               return 0;
+       real.buf = tree;
+
+       empty.buf = "";
+       empty.size = 0;
+
+       tree_difference = 0;
+       retval = diff_tree(&empty, &real, "", &diff_opt);
+       free(tree);
+
+       return retval >= 0 && !tree_difference;
+}
+
+static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
+{
+       struct commit_list **pp, *parent;
+
+       if (!commit->tree)
+               return;
+
+       if (!commit->parents) {
+               if (!same_tree_as_empty(commit->tree))
+                       commit->object.flags |= TREECHANGE;
+               return;
+       }
+
+       pp = &commit->parents;
+       while ((parent = *pp) != NULL) {
+               struct commit *p = parent->item;
+
+               if (p->object.flags & UNINTERESTING) {
+                       pp = &parent->next;
+                       continue;
+               }
+
+               parse_commit(p);
+               switch (compare_tree(p->tree, commit->tree)) {
+               case TREE_SAME:
+                       parent->next = NULL;
+                       commit->parents = parent;
+                       return;
+
+               case TREE_NEW:
+                       if (revs->remove_empty_trees && same_tree_as_empty(p->tree)) {
+                               *pp = parent->next;
+                               continue;
+                       }
+               /* fallthrough */
+               case TREE_DIFFERENT:
+                       pp = &parent->next;
+                       continue;
+               }
+               die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1));
+       }
+       commit->object.flags |= TREECHANGE;
+}
+
+static void add_parents_to_list(struct rev_info *revs, struct commit *commit, struct commit_list **list)
+{
+       struct commit_list *parent = commit->parents;
+
+       /*
+        * If the commit is uninteresting, don't try to
+        * prune parents - we want the maximal uninteresting
+        * set.
+        *
+        * Normally we haven't parsed the parent
+        * yet, so we won't have a parent of a parent
+        * here. However, it may turn out that we've
+        * reached this commit some other way (where it
+        * wasn't uninteresting), in which case we need
+        * to mark its parents recursively too..
+        */
+       if (commit->object.flags & UNINTERESTING) {
+               while (parent) {
+                       struct commit *p = parent->item;
+                       parent = parent->next;
+                       parse_commit(p);
+                       p->object.flags |= UNINTERESTING;
+                       if (p->parents)
+                               mark_parents_uninteresting(p);
+                       if (p->object.flags & SEEN)
+                               continue;
+                       p->object.flags |= SEEN;
+                       insert_by_date(p, list);
+               }
+               return;
+       }
+
+       /*
+        * Ok, the commit wasn't uninteresting. Try to
+        * simplify the commit history and find the parent
+        * that has no differences in the path set if one exists.
+        */
+       if (revs->paths)
+               try_to_simplify_commit(revs, commit);
+
+       parent = commit->parents;
+       while (parent) {
+               struct commit *p = parent->item;
+
+               parent = parent->next;
+
+               parse_commit(p);
+               if (p->object.flags & SEEN)
+                       continue;
+               p->object.flags |= SEEN;
+               insert_by_date(p, list);
+       }
+}
+
+static void limit_list(struct rev_info *revs)
+{
+       struct commit_list *list = revs->commits;
+       struct commit_list *newlist = NULL;
+       struct commit_list **p = &newlist;
+
+       if (revs->paths)
+               diff_tree_setup_paths(revs->paths);
+
+       while (list) {
+               struct commit_list *entry = list;
+               struct commit *commit = list->item;
+               struct object *obj = &commit->object;
+
+               list = list->next;
+               free(entry);
+
+               if (revs->max_age != -1 && (commit->date < revs->max_age))
+                       obj->flags |= UNINTERESTING;
+               if (revs->unpacked && has_sha1_pack(obj->sha1))
+                       obj->flags |= UNINTERESTING;
+               add_parents_to_list(revs, commit, &list);
+               if (obj->flags & UNINTERESTING) {
+                       mark_parents_uninteresting(commit);
+                       if (everybody_uninteresting(list))
+                               break;
+                       continue;
+               }
+               if (revs->min_age != -1 && (commit->date > revs->min_age))
+                       continue;
+               p = &commit_list_insert(commit, p)->next;
+       }
+       revs->commits = newlist;
+}
+
 static void add_one_commit(struct commit *commit, struct rev_info *revs)
 {
        if (!commit || (commit->object.flags & SEEN))
@@ -212,13 +439,13 @@ static void handle_all(struct rev_info *revs, unsigned flags)
  * Parse revision information, filling in the "rev_info" structure,
  * and removing the used arguments from the argument list.
  *
- * Returns the number of arguments left ("new argc").
+ * Returns the number of arguments left that weren't recognized
+ * (which are also moved to the head of the argument list)
  */
-int setup_revisions(int argc, const char **argv, struct rev_info *revs)
+int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def)
 {
        int i, flags, seen_dashdash;
-       const char *def = NULL;
-       const char **unrecognized = argv+1;
+       const char **unrecognized = argv + 1;
        int left = 1;
 
        memset(revs, 0, sizeof(*revs));
@@ -255,6 +482,21 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs)
                                revs->max_count = atoi(arg + 12);
                                continue;
                        }
+                       /* accept -<digit>, like traditilnal "head" */
+                       if ((*arg == '-') && isdigit(arg[1])) {
+                               revs->max_count = atoi(arg + 1);
+                               continue;
+                       }
+                       if (!strcmp(arg, "-n")) {
+                               if (argc <= i + 1)
+                                       die("-n requires an argument");
+                               revs->max_count = atoi(argv[++i]);
+                               continue;
+                       }
+                       if (!strncmp(arg,"-n",2)) {
+                               revs->max_count = atoi(arg + 2);
+                               continue;
+                       }
                        if (!strncmp(arg, "--max-age=", 10)) {
                                revs->max_age = atoi(arg + 10);
                                revs->limited = 1;
@@ -265,6 +507,26 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs)
                                revs->limited = 1;
                                continue;
                        }
+                       if (!strncmp(arg, "--since=", 8)) {
+                               revs->max_age = approxidate(arg + 8);
+                               revs->limited = 1;
+                               continue;
+                       }
+                       if (!strncmp(arg, "--after=", 8)) {
+                               revs->max_age = approxidate(arg + 8);
+                               revs->limited = 1;
+                               continue;
+                       }
+                       if (!strncmp(arg, "--before=", 9)) {
+                               revs->min_age = approxidate(arg + 9);
+                               revs->limited = 1;
+                               continue;
+                       }
+                       if (!strncmp(arg, "--until=", 8)) {
+                               revs->min_age = approxidate(arg + 8);
+                               revs->limited = 1;
+                               continue;
+                       }
                        if (!strcmp(arg, "--all")) {
                                handle_all(revs, flags);
                                continue;
@@ -302,6 +564,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs)
                                revs->remove_empty_trees = 1;
                                continue;
                        }
+                       if (!strncmp(arg, "--no-merges", 11)) {
+                               revs->no_merges = 1;
+                               continue;
+                       }
                        if (!strcmp(arg, "--objects")) {
                                revs->tag_objects = 1;
                                revs->tree_objects = 1;
@@ -378,6 +644,79 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs)
        }
        if (revs->paths)
                revs->limited = 1;
-       *unrecognized = NULL;
        return left;
 }
+
+void prepare_revision_walk(struct rev_info *revs)
+{
+       sort_by_date(&revs->commits);
+       if (revs->limited)
+               limit_list(revs);
+       if (revs->topo_order)
+               sort_in_topological_order(&revs->commits, revs->lifo);
+}
+
+static int rewrite_one(struct commit **pp)
+{
+       for (;;) {
+               struct commit *p = *pp;
+               if (p->object.flags & (TREECHANGE | UNINTERESTING))
+                       return 0;
+               if (!p->parents)
+                       return -1;
+               *pp = p->parents->item;
+       }
+}
+
+static void rewrite_parents(struct commit *commit)
+{
+       struct commit_list **pp = &commit->parents;
+       while (*pp) {
+               struct commit_list *parent = *pp;
+               if (rewrite_one(&parent->item) < 0) {
+                       *pp = parent->next;
+                       continue;
+               }
+               pp = &parent->next;
+       }
+}
+
+struct commit *get_revision(struct rev_info *revs)
+{
+       struct commit_list *list = revs->commits;
+       struct commit *commit;
+
+       if (!list)
+               return NULL;
+
+       /* Check the max_count ... */
+       commit = list->item;
+       switch (revs->max_count) {
+       case -1:
+               break;
+       case 0:
+               return NULL;
+       default:
+               revs->max_count--;
+       }
+
+       do {
+               commit = pop_most_recent_commit(&revs->commits, SEEN);
+               if (commit->object.flags & (UNINTERESTING|SHOWN))
+                       continue;
+               if (revs->min_age != -1 && (commit->date > revs->min_age))
+                       continue;
+               if (revs->max_age != -1 && (commit->date < revs->max_age))
+                       return NULL;
+               if (revs->no_merges && commit->parents && commit->parents->next)
+                       continue;
+               if (revs->paths && revs->dense) {
+                       if (!(commit->object.flags & TREECHANGE))
+                               continue;
+                       rewrite_parents(commit);
+               }
+               commit->object.flags |= SHOWN;
+               return commit;
+       } while (revs->commits);
+       return NULL;
+}
index a22f198515f9cce097bad41cb64139eff7459540..31e8f615677634a92992df8404c86a8c2184ff2a 100644 (file)
@@ -3,6 +3,9 @@
 
 #define SEEN           (1u<<0)
 #define UNINTERESTING   (1u<<1)
+#define TREECHANGE     (1u<<2)
+#define SHOWN          (1u<<3)
+#define TMP_MARK       (1u<<4) /* for isolated cases; clean after use */
 
 struct rev_info {
        /* Starting list */
@@ -15,6 +18,7 @@ struct rev_info {
 
        /* Traversal flags */
        unsigned int    dense:1,
+                       no_merges:1,
                        remove_empty_trees:1,
                        lifo:1,
                        topo_order:1,
@@ -32,7 +36,10 @@ struct rev_info {
 };
 
 /* revision.c */
-extern int setup_revisions(int argc, const char **argv, struct rev_info *revs);
+extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def);
+extern void prepare_revision_walk(struct rev_info *revs);
+extern struct commit *get_revision(struct rev_info *revs);
+
 extern void mark_parents_uninteresting(struct commit *commit);
 extern void mark_tree_uninteresting(struct tree *tree);
 
index 5a86ae2f9ed3159d082158c04bea51c39cb4477f..24efb65e622d8dfe44db22e5bc23c9f8cc328109 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 ] [<refs>...]";
+"git-show-branch [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...]";
 
 static int default_num = 0;
 static int default_alloc = 0;
@@ -547,6 +547,7 @@ int main(int ac, char **av)
        int shown_merge_point = 0;
        int with_current_branch = 0;
        int head_at = -1;
+       int topics = 0;
 
        setup_git_directory();
        git_config(git_show_branch_config);
@@ -587,6 +588,8 @@ int main(int ac, char **av)
                        independent = 1;
                else if (!strcmp(arg, "--topo-order"))
                        lifo = 1;
+               else if (!strcmp(arg, "--topics"))
+                       topics = 1;
                else if (!strcmp(arg, "--date-order"))
                        lifo = 0;
                else
@@ -724,11 +727,17 @@ int main(int ac, char **av)
        while (seen) {
                struct commit *commit = pop_one_commit(&seen);
                int this_flag = commit->object.flags;
+               int is_merge_point = ((this_flag & all_revs) == all_revs);
 
-               shown_merge_point |= ((this_flag & all_revs) == all_revs);
+               shown_merge_point |= is_merge_point;
 
                if (1 < num_rev) {
                        int is_merge = !!(commit->parents && commit->parents->next);
+                       if (topics &&
+                           !is_merge_point &&
+                           (this_flag & (1u << REV_SHIFT)))
+                               continue;
+
                        for (i = 0; i < num_rev; i++) {
                                int mark;
                                if (!(this_flag & (1u << (i + REV_SHIFT))))
index cabfadd56d03d3af26cdf890813945641cd90fad..d1947e11c1a245dc8c9c201d0c115027f8adc54b 100755 (executable)
@@ -8,11 +8,20 @@ test_description='Test of the various options to git-rm.'
 . ./test-lib.sh
 
 # Setup some files to be removed, some with funny characters
-touch -- foo bar baz 'space embedded' 'tab     embedded' 'newline
-embedded' -q
-git-add -- foo bar baz 'space embedded' 'tab   embedded' 'newline
-embedded' -q
-git-commit -m "add files"
+touch -- foo bar baz 'space embedded' -q
+git-add -- foo bar baz 'space embedded' -q
+git-commit -m "add normal files"
+test_tabs=y
+if touch -- 'tab       embedded' 'newline
+embedded'
+then
+git-add -- 'tab        embedded' 'newline
+embedded'
+git-commit -m "add files with tabs and newlines"
+else
+    say 'Your filesystem does not allow tabs in filenames.'
+    test_tabs=n
+fi
 
 test_expect_success \
     'Pre-check that foo exists and is in index before git-rm foo' \
@@ -42,16 +51,18 @@ test_expect_success \
     'Test that "git-rm -- -q" succeeds (remove a file that looks like an option)' \
     'git-rm -- -q'
 
-test_expect_success \
+test "$test_tabs" = y && test_expect_success \
     "Test that \"git-rm -f\" succeeds with embedded space, tab, or newline characters." \
     "git-rm -f 'space embedded' 'tab   embedded' 'newline
 embedded'"
 
+if test "$test_tabs" = y; then
 chmod u-w .
 test_expect_failure \
     'Test that "git-rm -f" fails if its rm fails' \
     'git-rm -f baz'
 chmod u+w .
+fi
 
 test_expect_success \
     'When the rm in "git-rm -f" fails, it should not remove the file from the index' \
diff --git a/t/t6001-rev-list-merge-order.sh b/t/t6001-rev-list-merge-order.sh
deleted file mode 100755 (executable)
index 7724e8a..0000000
+++ /dev/null
@@ -1,462 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005 Jon Seymour
-#
-
-test_description='Tests git-rev-list --merge-order functionality'
-
-. ./test-lib.sh
-. ../t6000lib.sh # t6xxx specific functions
-
-# test-case specific test function
-check_adjacency()
-{
-    read previous
-    echo "= $previous"
-    while read next
-    do
-        if ! (git-cat-file commit $previous | grep "^parent $next" >/dev/null)
-        then
-            echo "^ $next"
-        else
-            echo "| $next"
-        fi
-        previous=$next
-    done
-}
-
-list_duplicates()
-{
-    "$@" | sort | uniq -d
-}
-
-grep_stderr()
-{
-    args=$1
-    shift 1
-    "$@" 2>&1 | grep "$args"
-}
-
-date >path0
-git-update-index --add path0
-save_tag tree git-write-tree
-on_committer_date "1971-08-16 00:00:00" hide_error save_tag root unique_commit root tree
-on_committer_date "1971-08-16 00:00:01" save_tag l0 unique_commit l0 tree -p root
-on_committer_date "1971-08-16 00:00:02" save_tag l1 unique_commit l1 tree -p l0
-on_committer_date "1971-08-16 00:00:03" save_tag l2 unique_commit l2 tree -p l1
-on_committer_date "1971-08-16 00:00:04" save_tag a0 unique_commit a0 tree -p l2
-on_committer_date "1971-08-16 00:00:05" save_tag a1 unique_commit a1 tree -p a0
-on_committer_date "1971-08-16 00:00:06" save_tag b1 unique_commit b1 tree -p a0
-on_committer_date "1971-08-16 00:00:07" save_tag c1 unique_commit c1 tree -p b1
-on_committer_date "1971-08-16 00:00:08" as_author foobar@example.com save_tag b2 unique_commit b2 tree -p b1
-on_committer_date "1971-08-16 00:00:09" save_tag b3 unique_commit b2 tree -p b2
-on_committer_date "1971-08-16 00:00:10" save_tag c2 unique_commit c2 tree -p c1 -p b2
-on_committer_date "1971-08-16 00:00:11" save_tag c3 unique_commit c3 tree -p c2
-on_committer_date "1971-08-16 00:00:12" save_tag a2 unique_commit a2 tree -p a1
-on_committer_date "1971-08-16 00:00:13" save_tag a3 unique_commit a3 tree -p a2
-on_committer_date "1971-08-16 00:00:14" save_tag b4 unique_commit b4 tree -p b3 -p a3
-on_committer_date "1971-08-16 00:00:15" save_tag a4 unique_commit a4 tree -p a3 -p b4 -p c3
-on_committer_date "1971-08-16 00:00:16" save_tag l3 unique_commit l3 tree -p a4
-on_committer_date "1971-08-16 00:00:17" save_tag l4 unique_commit l4 tree -p l3
-on_committer_date "1971-08-16 00:00:18" save_tag l5 unique_commit l5 tree -p l4
-on_committer_date "1971-08-16 00:00:19" save_tag m1 unique_commit m1 tree -p a4 -p c3
-on_committer_date "1971-08-16 00:00:20" save_tag m2 unique_commit m2 tree -p c3 -p a4
-on_committer_date "1971-08-16 00:00:21" hide_error save_tag alt_root unique_commit alt_root tree
-on_committer_date "1971-08-16 00:00:22" save_tag r0 unique_commit r0 tree -p alt_root
-on_committer_date "1971-08-16 00:00:23" save_tag r1 unique_commit r1 tree -p r0
-on_committer_date "1971-08-16 00:00:24" save_tag l5r1 unique_commit l5r1 tree -p l5 -p r1
-on_committer_date "1971-08-16 00:00:25" save_tag r1l5 unique_commit r1l5 tree -p r1 -p l5
-
-
-#
-# note: as of 20/6, it isn't possible to create duplicate parents, so this
-# can't be tested.
-#
-#on_committer_date "1971-08-16 00:00:20" save_tag m3 unique_commit m3 tree -p c3 -p a4 -p c3
-hide_error save_tag e1 as_author e@example.com unique_commit e1 tree
-save_tag e2 as_author e@example.com unique_commit e2 tree -p e1
-save_tag f1 as_author f@example.com unique_commit f1 tree -p e1
-save_tag e3 as_author e@example.com unique_commit e3 tree -p e2
-save_tag f2 as_author f@example.com unique_commit f2 tree -p f1
-save_tag e4 as_author e@example.com unique_commit e4 tree -p e3 -p f2
-save_tag e5 as_author e@example.com unique_commit e5 tree -p e4
-save_tag f3 as_author f@example.com unique_commit f3 tree -p f2
-save_tag f4 as_author f@example.com unique_commit f4 tree -p f3
-save_tag e6 as_author e@example.com unique_commit e6 tree -p e5 -p f4
-save_tag f5 as_author f@example.com unique_commit f5 tree -p f4
-save_tag f6 as_author f@example.com unique_commit f6 tree -p f5 -p e6
-save_tag e7 as_author e@example.com unique_commit e7 tree -p e6
-save_tag e8 as_author e@example.com unique_commit e8 tree -p e7
-save_tag e9 as_author e@example.com unique_commit e9 tree -p e8
-save_tag f7 as_author f@example.com unique_commit f7 tree -p f6
-save_tag f8 as_author f@example.com unique_commit f8 tree -p f7
-save_tag f9 as_author f@example.com unique_commit f9 tree -p f8
-save_tag e10 as_author e@example.com unique_commit e1 tree -p e9 -p f8
-
-hide_error save_tag g0 unique_commit g0 tree
-save_tag g1 unique_commit g1 tree -p g0
-save_tag h1 unique_commit g2 tree -p g0
-save_tag g2 unique_commit g3 tree -p g1 -p h1
-save_tag h2 unique_commit g4 tree -p g2
-save_tag g3 unique_commit g5 tree -p g2
-save_tag g4 unique_commit g6 tree -p g3 -p h2
-
-git-update-ref HEAD $(tag l5)
-
-test_output_expect_success 'rev-list has correct number of entries' 'git-rev-list HEAD | wc -l | tr -d \" \"' <<EOF
-19
-EOF
-
-if git-rev-list --merge-order HEAD 2>&1 | grep 'OpenSSL not linked' >/dev/null
-then
-    test_expect_success 'skipping merge-order test' :
-    test_done
-    exit
-fi
-
-normal_adjacency_count=$(git-rev-list HEAD | check_adjacency | grep -c "\^" | tr -d ' ')
-merge_order_adjacency_count=$(git-rev-list --merge-order HEAD | check_adjacency | grep -c "\^" | tr -d ' ')
-test_expect_success '--merge-order produces as many or fewer discontinuities' '[ $merge_order_adjacency_count -le $normal_adjacency_count ]'
-test_output_expect_success 'simple merge order' 'git-rev-list --merge-order --show-breaks HEAD' <<EOF
-= l5
-| l4
-| l3
-= a4
-| c3
-| c2
-| c1
-^ b4
-| b3
-| b2
-| b1
-^ a3
-| a2
-| a1
-= a0
-| l2
-| l1
-| l0
-= root
-EOF
-
-test_output_expect_success 'two diamonds merge order (g6)' 'git-rev-list --merge-order --show-breaks g4' <<EOF
-= g4
-| h2
-^ g3
-= g2
-| h1
-^ g1
-= g0
-EOF
-
-test_output_expect_success 'multiple heads' 'git-rev-list --merge-order a3 b3 c3' <<EOF
-c3
-c2
-c1
-b3
-b2
-b1
-a3
-a2
-a1
-a0
-l2
-l1
-l0
-root
-EOF
-
-test_output_expect_success 'multiple heads, prune at a1' 'git-rev-list --merge-order a3 b3 c3 ^a1' <<EOF
-c3
-c2
-c1
-b3
-b2
-b1
-a3
-a2
-EOF
-
-test_output_expect_success 'multiple heads, prune at l1' 'git-rev-list --merge-order a3 b3 c3 ^l1' <<EOF
-c3
-c2
-c1
-b3
-b2
-b1
-a3
-a2
-a1
-a0
-l2
-EOF
-
-test_output_expect_success 'cross-epoch, head at l5, prune at l1' 'git-rev-list --merge-order l5 ^l1' <<EOF
-l5
-l4
-l3
-a4
-c3
-c2
-c1
-b4
-b3
-b2
-b1
-a3
-a2
-a1
-a0
-l2
-EOF
-
-test_output_expect_success 'duplicated head arguments' 'git-rev-list --merge-order l5 l5 ^l1' <<EOF
-l5
-l4
-l3
-a4
-c3
-c2
-c1
-b4
-b3
-b2
-b1
-a3
-a2
-a1
-a0
-l2
-EOF
-
-test_output_expect_success 'prune near merge' 'git-rev-list --merge-order a4 ^c3' <<EOF
-a4
-b4
-b3
-a3
-a2
-a1
-EOF
-
-test_output_expect_success "head has no parent" 'git-rev-list --merge-order --show-breaks root' <<EOF
-= root
-EOF
-
-test_output_expect_success "two nodes - one head, one base" 'git-rev-list --merge-order --show-breaks l0' <<EOF
-= l0
-= root
-EOF
-
-test_output_expect_success "three nodes one head, one internal, one base" 'git-rev-list --merge-order --show-breaks l1' <<EOF
-= l1
-| l0
-= root
-EOF
-
-test_output_expect_success "linear prune l2 ^root" 'git-rev-list --merge-order --show-breaks l2 ^root' <<EOF
-^ l2
-| l1
-| l0
-EOF
-
-test_output_expect_success "linear prune l2 ^l0" 'git-rev-list --merge-order --show-breaks l2 ^l0' <<EOF
-^ l2
-| l1
-EOF
-
-test_output_expect_success "linear prune l2 ^l1" 'git-rev-list --merge-order --show-breaks l2 ^l1' <<EOF
-^ l2
-EOF
-
-test_output_expect_success "linear prune l5 ^a4" 'git-rev-list --merge-order --show-breaks l5 ^a4' <<EOF
-^ l5
-| l4
-| l3
-EOF
-
-test_output_expect_success "linear prune l5 ^l3" 'git-rev-list --merge-order --show-breaks l5 ^l3' <<EOF
-^ l5
-| l4
-EOF
-
-test_output_expect_success "linear prune l5 ^l4" 'git-rev-list --merge-order --show-breaks l5 ^l4' <<EOF
-^ l5
-EOF
-
-test_output_expect_success "max-count 10 - merge order" 'git-rev-list --merge-order --show-breaks --max-count=10 l5' <<EOF
-= l5
-| l4
-| l3
-= a4
-| c3
-| c2
-| c1
-^ b4
-| b3
-| b2
-EOF
-
-test_output_expect_success "max-count 10 - non merge order" 'git-rev-list --max-count=10 l5' <<EOF
-l5
-l4
-l3
-a4
-b4
-a3
-a2
-c3
-c2
-b3
-EOF
-
-test_output_expect_success '--max-age=c3, no --merge-order' "git-rev-list --max-age=$(commit_date c3) l5" <<EOF
-l5
-l4
-l3
-a4
-b4
-a3
-a2
-c3
-EOF
-
-test_output_expect_success '--max-age=c3, --merge-order' "git-rev-list --merge-order --max-age=$(commit_date c3) l5" <<EOF
-l5
-l4
-l3
-a4
-c3
-b4
-a3
-a2
-EOF
-
-test_output_expect_success 'one specified head reachable from another a4, c3, --merge-order' "list_duplicates git-rev-list --merge-order a4 c3" <<EOF
-EOF
-
-test_output_expect_success 'one specified head reachable from another c3, a4, --merge-order' "list_duplicates git-rev-list --merge-order c3 a4" <<EOF
-EOF
-
-test_output_expect_success 'one specified head reachable from another a4, c3, no --merge-order' "list_duplicates git-rev-list a4 c3" <<EOF
-EOF
-
-test_output_expect_success 'one specified head reachable from another c3, a4, no --merge-order' "list_duplicates git-rev-list c3 a4" <<EOF
-EOF
-
-test_output_expect_success 'graph with c3 and a4 parents of head' "list_duplicates git-rev-list m1" <<EOF
-EOF
-
-test_output_expect_success 'graph with a4 and c3 parents of head' "list_duplicates git-rev-list m2" <<EOF
-EOF
-
-test_expect_success "head ^head --merge-order" 'git-rev-list --merge-order --show-breaks a3 ^a3' <<EOF
-EOF
-
-#
-# can't test this now - duplicate parents can't be created
-#
-#test_output_expect_success 'duplicate parents' 'git-rev-list --parents --merge-order --show-breaks m3' <<EOF
-#= m3 c3 a4 c3
-#| a4 c3 b4 a3
-#| b4 a3 b3
-#| b3 b2
-#^ a3 a2
-#| a2 a1
-#| a1 a0
-#^ c3 c2
-#| c2 b2 c1
-#| b2 b1
-#^ c1 b1
-#| b1 a0
-#= a0 l2
-#| l2 l1
-#| l1 l0
-#| l0 root
-#= root
-#EOF
-
-test_expect_success "head ^head no --merge-order" 'git-rev-list a3 ^a3' <<EOF
-EOF
-
-test_output_expect_success 'simple merge order (l5r1)' 'git-rev-list --merge-order --show-breaks l5r1' <<EOF
-= l5r1
-| r1
-| r0
-| alt_root
-^ l5
-| l4
-| l3
-| a4
-| c3
-| c2
-| c1
-^ b4
-| b3
-| b2
-| b1
-^ a3
-| a2
-| a1
-| a0
-| l2
-| l1
-| l0
-= root
-EOF
-
-test_output_expect_success 'simple merge order (r1l5)' 'git-rev-list --merge-order --show-breaks r1l5' <<EOF
-= r1l5
-| l5
-| l4
-| l3
-| a4
-| c3
-| c2
-| c1
-^ b4
-| b3
-| b2
-| b1
-^ a3
-| a2
-| a1
-| a0
-| l2
-| l1
-| l0
-| root
-^ r1
-| r0
-= alt_root
-EOF
-
-test_output_expect_success "don't print things unreachable from one branch" "git-rev-list a3 ^b3 --merge-order" <<EOF
-a3
-a2
-a1
-EOF
-
-test_output_expect_success "--merge-order a4 l3" "git-rev-list --merge-order a4 l3" <<EOF
-l3
-a4
-c3
-c2
-c1
-b4
-b3
-b2
-b1
-a3
-a2
-a1
-a0
-l2
-l1
-l0
-root
-EOF
-
-#
-#
-
-test_done
index 43d74c502e60ac55236fa6e36dcf429d38c3b01b..811a4797a58dbc3411f885535b90e6df99f06334 100755 (executable)
@@ -11,17 +11,31 @@ test_expect_success \
      git-commit -m add -a'
 
 test_expect_success \
-    'moving the file' \
+    'moving the file out of subdirectory' \
     'cd path0 && git-mv COPYING ../path1/COPYING'
 
 # in path0 currently
 test_expect_success \
     'commiting the change' \
-    'cd .. && git-commit -m move -a'
+    'cd .. && git-commit -m move-out -a'
 
 test_expect_success \
     'checking the commit' \
     'git-diff-tree -r -M --name-status  HEAD^ HEAD | \
     grep -E "^R100.+path0/COPYING.+path1/COPYING"'
 
+test_expect_success \
+    'moving the file back into subdirectory' \
+    'cd path0 && git-mv ../path1/COPYING COPYING'
+
+# in path0 currently
+test_expect_success \
+    'commiting the change' \
+    'cd .. && git-commit -m move-in -a'
+
+test_expect_success \
+    'checking the commit' \
+    'git-diff-tree -r -M --name-status  HEAD^ HEAD | \
+    grep -E "^R100.+path1/COPYING.+path0/COPYING"'
+
 test_done
diff --git a/t/t8001-annotate.sh b/t/t8001-annotate.sh
new file mode 100755 (executable)
index 0000000..172908a
--- /dev/null
@@ -0,0 +1,90 @@
+#!/bin/sh
+
+test_description='git-annotate'
+. ./test-lib.sh
+
+test_expect_success \
+    'prepare reference tree' \
+    'echo "1A quick brown fox jumps over the" >file &&
+     echo "lazy dog" >>file &&
+     git add file
+     GIT_AUTHOR_NAME="A" git commit -a -m "Initial."'
+
+test_expect_success \
+    'check all lines blamed on A' \
+    '[ $(git annotate file | awk "{print \$3}" | grep -c "A") == 2 ]'
+
+test_expect_success \
+    'Setup new lines blamed on B' \
+    'echo "2A quick brown fox jumps over the" >>file &&
+     echo "lazy dog" >> file &&
+     GIT_AUTHOR_NAME="B" git commit -a -m "Second."'
+
+test_expect_success \
+    'Two lines blamed on A' \
+    '[ $(git annotate file | awk "{print \$3}" | grep -c "A") == 2 ]'
+
+test_expect_success \
+    'Two lines blamed on B' \
+    '[ $(git annotate file | awk "{print \$3}" | grep -c "B") == 2 ]'
+
+test_expect_success \
+    'merge-setup part 1' \
+    'git checkout -b branch1 master &&
+     echo "3A slow green fox jumps into the" >> file &&
+     echo "well." >> file &&
+     GIT_AUTHOR_NAME="B1" git commit -a -m "Branch1-1"'
+
+test_expect_success \
+    'Two lines blamed on A' \
+    '[ $(git annotate file | awk "{print \$3}" | grep -c "^A$") == 2 ]'
+
+test_expect_success \
+    'Two lines blamed on B' \
+    '[ $(git annotate file | awk "{print \$3}" | grep -c "^B$") == 2 ]'
+
+test_expect_success \
+    'Two lines blamed on B1' \
+    '[ $(git annotate file | awk "{print \$3}" | grep -c "^B1$") == 2 ]'
+
+test_expect_success \
+    'merge-setup part 2' \
+    'git checkout -b branch2 master &&
+     sed -e "s/2A quick brown/4A quick brown lazy dog/" < file > file.new &&
+     mv file.new file &&
+     GIT_AUTHOR_NAME="B2" git commit -a -m "Branch2-1"'
+
+test_expect_success \
+    'Two lines blamed on A' \
+    '[ $(git annotate file | awk "{print \$3}" | grep -c "^A$") == 2 ]'
+
+test_expect_success \
+    'One line blamed on B' \
+    '[ $(git annotate file | awk "{print \$3}" | grep -c "^B$") == 1 ]'
+
+test_expect_success \
+    'One line blamed on B2' \
+    '[ $(git annotate file | awk "{print \$3}" | grep -c "^B2$") == 1 ]'
+
+
+test_expect_success \
+    'merge-setup part 3' \
+    'git pull . branch1'
+
+test_expect_success \
+    'Two lines blamed on A' \
+    '[ $(git annotate file | awk "{print \$3}" | grep -c "^A$") == 2 ]'
+
+test_expect_success \
+    'One line blamed on B' \
+    '[ $(git annotate file | awk "{print \$3}" | grep -c "^B$") == 1 ]'
+
+test_expect_success \
+    'Two lines blamed on B1' \
+    '[ $(git annotate file | awk "{print \$3}" | grep -c "^B1$") == 2 ]'
+
+test_expect_success \
+    'One line blamed on B2' \
+    '[ $(git annotate file | awk "{print \$3}" | grep -c "^B2$") == 1 ]'
+
+test_done
index e85a1ed6605459fdbb60e3b24022e7e250b93a91..e478e13e283a9d9687d0b32e180558995f2b920d 100644 (file)
@@ -304,9 +304,11 @@ static void write_header(const unsigned char *sha1, char typeflag, const char *b
        }
 
        if (S_ISDIR(mode))
-               mode |= 0755;   /* GIT doesn't store permissions of dirs */
-       if (S_ISLNK(mode))
-               mode |= 0777;   /* ... nor of symlinks */
+               mode |= 0777;
+       else if (S_ISREG(mode))
+               mode |= (mode & 0100) ? 0777 : 0666;
+       else if (S_ISLNK(mode))
+               mode |= 0777;
        sprintf(&header[100], "%07o", mode & 07777);
 
        /* XXX: should we provide more meaningful info here? */
@@ -391,7 +393,7 @@ int main(int argc, char **argv)
                usage(tar_tree_usage);
        }
 
-       commit = lookup_commit_reference(sha1);
+       commit = lookup_commit_reference_gently(sha1, 1);
        if (commit) {
                write_global_extended_header(commit->object.sha1);
                archive_time = commit->date;
index ce1db38d16928d8b5085cb6e177f40fff9a978cf..797245ab276543edc5d7605b57f5175278436234 100644 (file)
@@ -577,9 +577,11 @@ int main(int argc, const char **argv)
                                break;
                        }
                        if (!strcmp(path, "--index-info")) {
+                               if (i != argc - 1)
+                                       die("--index-info must be at the end");
                                allow_add = allow_replace = allow_remove = 1;
                                read_index_info(line_termination);
-                               continue;
+                               break;
                        }
                        if (!strcmp(path, "--ignore-missing")) {
                                not_new = 1;