Merge branch 'fk/blame' into next
authorJunio C Hamano <junkio@cox.net>
Sat, 4 Mar 2006 08:31:46 +0000 (00:31 -0800)
committerJunio C Hamano <junkio@cox.net>
Sat, 4 Mar 2006 08:31:46 +0000 (00:31 -0800)
* fk/blame:
  git-blame, take 2
  Merge part of 'lt/rev-list' into 'fk/blame'
  Add git-blame, a tool for assigning blame.

48 files changed:
Documentation/git-am.txt
Documentation/git-apply.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]
GIT-VERSION-GEN
Makefile
apply.c
cache.h
cat-file.c
combine-diff.c
contrib/git-svn/git-svn.perl
contrib/git-svn/git-svn.txt
contrib/git-svn/t/t0000-contrib-git-svn.sh
contrib/gitview/gitview
diff-delta.c
diff.c
diffcore-break.c
diffcore-delta.c [new file with mode: 0644]
diffcore-rename.c
diffcore.h
environment.c
git-am.sh
git-annotate.perl
git-branch.sh
git-commit.sh
git-cvsserver.perl
git-format-patch.sh
git-mv.perl
git-send-email.perl
git-svnimport.perl
git-verify-tag.sh
git.c
pack-objects.c
read-tree.c
refs.c
rev-list.c
revision.c
revision.h
show-branch.c
t/t3600-rm.sh
t/t7001-mv.sh
t/t8001-annotate.sh [new file with mode: 0755]
tar-tree.c
update-index.c

index 02cabc9..910457d 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 75076b6..1c64a1a 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 88f07ff..19c9c51 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 6fbd6d9..844cfda 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 33fcde4..00efde5 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 5b306d6..8255ae1 100644 (file)
@@ -18,7 +18,7 @@ SYNOPSIS
             [ \--all ]
             [ \--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.
index 5c543d5..9d38657 100644 (file)
@@ -9,11 +9,13 @@ 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 ]
-                       [ -s start_chg ] [ -m ] [ -M regex ]
-                       <SVN_repository_URL> [ <path> ]
+               [ -C <GIT_repository> ] [ -i ] [ -u ] [-l limit_rev]
+               [ -b branch_subdir ] [ -T trunk_subdir ] [ -t tag_subdir ]
+               [ -s start_chg ] [ -m ] [ -r ] [ -M regex ]
+               [ -I <ignorefile_name> ] [ -A <author_file> ]
+               <SVN_repository_URL> [ <path> ]
 
 
 DESCRIPTION
@@ -65,6 +67,27 @@ When importing incrementally, you might need to edit the .git/svn2git file.
        Prepend 'rX: ' to commit messages, where X is the imported
        subversion revision.
 
+-I <ignorefile_name>::
+       Import the svn:ignore directory property to files with this
+       name in each directory. (The Subversion and GIT ignore
+       syntaxes are similar enough that using the Subversion patterns
+       directly with "-I .gitignore" will almost always just work.)
+
+-A <author_file>::
+       Read a file with lines on the form
+
+         username = User's Full Name <email@addr.es>
+
+       and use "User's Full Name <email@addr.es>" as the GIT
+       author and committer for Subversion commits made by
+       "username". If encountering a commit made by a user not in the
+       list, abort.
+
+       For convenience, this data is saved to $GIT_DIR/svn-authors
+       each time the -A option is provided, and read from that same
+       file each time git-svnimport is run with an existing GIT
+       repository without -A.
+
 -m::
        Attempt to detect merges based on the commit message. This option
        will enable default regexes that try to capture the name source
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 1056b7c..d6d1ae0 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*'\(.*\)')
index b67d1ab..b6d8804 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -196,7 +196,8 @@ LIB_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 \
@@ -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
diff --git a/apply.c b/apply.c
index 244718c..c369966 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -32,7 +32,57 @@ 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,
+       warn_on_whitespace,
+       error_on_whitespace,
+       strip_whitespace,
+} new_whitespace = warn_on_whitespace;
+static int whitespace_error = 0;
+static int squelch_whitespace_errors = 5;
+static int applied_after_stripping = 0;
+static const char *patch_input_file = NULL;
+
+static void parse_whitespace_option(const char *option)
+{
+       if (!option) {
+               new_whitespace = warn_on_whitespace;
+               return;
+       }
+       if (!strcmp(option, "warn")) {
+               new_whitespace = warn_on_whitespace;
+               return;
+       }
+       if (!strcmp(option, "nowarn")) {
+               new_whitespace = nowarn_whitespace;
+               return;
+       }
+       if (!strcmp(option, "error")) {
+               new_whitespace = error_on_whitespace;
+               return;
+       }
+       if (!strcmp(option, "error-all")) {
+               new_whitespace = error_on_whitespace;
+               squelch_whitespace_errors = 0;
+               return;
+       }
+       if (!strcmp(option, "strip")) {
+               new_whitespace = strip_whitespace;
+               return;
+       }
+       die("unrecognized whitespace option '%s'", option);
+}
+
+static void set_default_whitespace_mode(const char *whitespace_option)
+{
+       if (!whitespace_option && !apply_default_whitespace) {
+               new_whitespace = (apply
+                                 ? warn_on_whitespace
+                                 : nowarn_whitespace);
+       }
+}
 
 /*
  * For "diff-stat" like behaviour, we keep track of the biggest change
@@ -815,6 +865,25 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s
                        oldlines--;
                        break;
                case '+':
+                       /*
+                        * We know len is at least two, since we have a '+' and
+                        * we checked that the last character was a '\n' above.
+                        * That is, an addition of an empty line would check
+                        * the '+' here.  Sneaky...
+                        */
+                       if ((new_whitespace != nowarn_whitespace) &&
+                           isspace(line[len-2])) {
+                               whitespace_error++;
+                               if (squelch_whitespace_errors &&
+                                   squelch_whitespace_errors <
+                                   whitespace_error)
+                                       ;
+                               else {
+                                       fprintf(stderr, "Adds trailing whitespace.\n%s:%d:%.*s\n",
+                                               patch_input_file,
+                                               linenr, len-2, line+1);
+                               }
+                       }
                        added++;
                        newlines--;
                        break;
@@ -1092,6 +1161,28 @@ struct buffer_desc {
        unsigned long alloc;
 };
 
+static int apply_line(char *output, const char *patch, int plen)
+{
+       /* plen is number of bytes to be copied from patch,
+        * starting at patch+1 (patch[0] is '+').  Typically
+        * patch[plen] is '\n'.
+        */
+       int add_nl_to_tail = 0;
+       if ((new_whitespace == strip_whitespace) &&
+           1 < plen && isspace(patch[plen-1])) {
+               if (patch[plen] == '\n')
+                       add_nl_to_tail = 1;
+               plen--;
+               while (0 < plen && isspace(patch[plen]))
+                       plen--;
+               applied_after_stripping++;
+       }
+       memcpy(output, patch + 1, plen);
+       if (add_nl_to_tail)
+               output[plen++] = '\n';
+       return plen;
+}
+
 static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag)
 {
        char *buf = desc->buffer;
@@ -1127,10 +1218,9 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag)
                                break;
                /* Fall-through for ' ' */
                case '+':
-                       if (*patch != '+' || !no_add) {
-                               memcpy(new + newsize, patch + 1, plen);
-                               newsize += plen;
-                       }
+                       if (*patch != '+' || !no_add)
+                               newsize += apply_line(new + newsize, patch,
+                                                     plen);
                        break;
                case '@': case '\\':
                        /* Ignore it, we already handled it */
@@ -1699,7 +1789,7 @@ static int use_patch(struct patch *p)
        return 1;
 }
 
-static int apply_patch(int fd)
+static int apply_patch(int fd, const char *filename)
 {
        int newfd;
        unsigned long offset, size;
@@ -1707,6 +1797,7 @@ static int apply_patch(int fd)
        struct patch *list = NULL, **listp = &list;
        int skipped_patch = 0;
 
+       patch_input_file = filename;
        if (!buffer)
                return -1;
        offset = 0;
@@ -1733,6 +1824,9 @@ static int apply_patch(int fd)
        }
 
        newfd = -1;
+       if (whitespace_error && (new_whitespace == error_on_whitespace))
+               apply = 0;
+
        write_index = check_index && apply;
        if (write_index)
                newfd = hold_index_file_for_update(&cache_file, get_index_file());
@@ -1769,17 +1863,28 @@ static int apply_patch(int fd)
        return 0;
 }
 
+static int git_apply_config(const char *var, const char *value)
+{
+       if (!strcmp(var, "apply.whitespace")) {
+               apply_default_whitespace = strdup(value);
+               return 0;
+       }
+       return git_default_config(var, value);
+}
+
+
 int main(int argc, char **argv)
 {
        int i;
        int read_stdin = 1;
+       const char *whitespace_option = NULL;
 
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
                int fd;
 
                if (!strcmp(arg, "-")) {
-                       apply_patch(0);
+                       apply_patch(0, "<stdin>");
                        read_stdin = 0;
                        continue;
                }
@@ -1839,11 +1944,18 @@ int main(int argc, char **argv)
                        line_termination = 0;
                        continue;
                }
+               if (!strncmp(arg, "--whitespace=", 13)) {
+                       whitespace_option = arg + 13;
+                       parse_whitespace_option(arg + 13);
+                       continue;
+               }
 
                if (check_index && prefix_length < 0) {
                        prefix = setup_git_directory();
                        prefix_length = prefix ? strlen(prefix) : 0;
-                       git_config(git_default_config);
+                       git_config(git_apply_config);
+                       if (!whitespace_option && apply_default_whitespace)
+                               parse_whitespace_option(apply_default_whitespace);
                }
                if (0 < prefix_length)
                        arg = prefix_filename(prefix, prefix_length, arg);
@@ -1852,10 +1964,38 @@ int main(int argc, char **argv)
                if (fd < 0)
                        usage(apply_usage);
                read_stdin = 0;
-               apply_patch(fd);
+               set_default_whitespace_mode(whitespace_option);
+               apply_patch(fd, arg);
                close(fd);
        }
+       set_default_whitespace_mode(whitespace_option);
        if (read_stdin)
-               apply_patch(0);
+               apply_patch(0, "<stdin>");
+       if (whitespace_error) {
+               if (squelch_whitespace_errors &&
+                   squelch_whitespace_errors < whitespace_error) {
+                       int squelched =
+                               whitespace_error - squelch_whitespace_errors;
+                       fprintf(stderr, "warning: squelched %d whitespace error%s\n",
+                               squelched,
+                               squelched == 1 ? "" : "s");
+               }
+               if (new_whitespace == error_on_whitespace)
+                       die("%d line%s add%s trailing whitespaces.",
+                           whitespace_error,
+                           whitespace_error == 1 ? "" : "s",
+                           whitespace_error == 1 ? "s" : "");
+               if (applied_after_stripping)
+                       fprintf(stderr, "warning: %d line%s applied after"
+                               " stripping trailing whitespaces.\n",
+                               applied_after_stripping,
+                               applied_after_stripping == 1 ? "" : "s");
+               else if (whitespace_error)
+                       fprintf(stderr, "warning: %d line%s add%s trailing"
+                               " whitespaces.\n",
+                               whitespace_error,
+                               whitespace_error == 1 ? "" : "s",
+                               whitespace_error == 1 ? "s" : "");
+       }
        return 0;
 }
diff --git a/cache.h b/cache.h
index 3af6b86..8dc1de1 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -161,11 +161,13 @@ extern int hold_index_file_for_update(struct cache_file *, const char *path);
 extern int commit_index_file(struct cache_file *);
 extern void rollback_index_file(struct cache_file *);
 
+/* Environment bits from configuration mechanism */
 extern int trust_executable_bit;
 extern int assume_unchanged;
 extern int only_use_symrefs;
 extern int diff_rename_limit_default;
 extern int shared_repository;
+extern const char *apply_default_whitespace;
 
 #define GIT_REPO_VERSION 0
 extern int repository_format_version;
index 96d66b4..1a613f3 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 d812600..a23894d 100644 (file)
@@ -621,7 +621,8 @@ static void reuse_combine_diff(struct sline *sline, unsigned long cnt,
 }
 
 static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
-                          int dense, const char *header)
+                          int dense, const char *header,
+                          struct diff_options *opt)
 {
        unsigned long size, cnt, lno;
        char *result, *cp, *ep;
@@ -631,6 +632,7 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
        char ourtmp_buf[TMPPATHLEN];
        char *ourtmp = ourtmp_buf;
        int working_tree_file = !memcmp(elem->sha1, null_sha1, 20);
+       int abbrev = opt->full_index ? 40 : DEFAULT_ABBREV;
 
        /* Read the result of merge first */
        if (!working_tree_file) {
@@ -724,7 +726,7 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
 
                if (header) {
                        shown_header++;
-                       puts(header);
+                       printf("%s%c", header, opt->line_termination);
                }
                printf("diff --%s ", dense ? "cc" : "combined");
                if (quote_c_style(elem->path, NULL, NULL, 0))
@@ -735,10 +737,10 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
                printf("index ");
                for (i = 0; i < num_parent; i++) {
                        abb = find_unique_abbrev(elem->parent[i].sha1,
-                                                DEFAULT_ABBREV);
+                                                abbrev);
                        printf("%s%s", i ? "," : "", abb);
                }
-               abb = find_unique_abbrev(elem->sha1, DEFAULT_ABBREV);
+               abb = find_unique_abbrev(elem->sha1, abbrev);
                printf("..%s\n", abb);
 
                if (mode_differs) {
@@ -797,7 +799,7 @@ static void show_raw_diff(struct combine_diff_path *p, int num_parent, const cha
                inter_name_termination = 0;
 
        if (header)
-               puts(header);
+               printf("%s%c", header, line_termination);
 
        for (i = 0; i < num_parent; i++) {
                if (p->parent[i].mode)
@@ -862,7 +864,7 @@ int show_combined_diff(struct combine_diff_path *p,
 
        default:
        case DIFF_FORMAT_PATCH:
-               return show_patch_diff(p, num_parent, dense, header);
+               return show_patch_diff(p, num_parent, dense, header, opt);
        }
 }
 
index 0b74165..3c860e4 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 @@ $ENV{LC_ALL} = 'C';
 # 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/;
@@ -32,27 +26,30 @@ use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev/;
 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++) {
@@ -63,16 +60,23 @@ 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 @@ diff-index line ($m hash)
        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 b4b7789..8e9a971 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,7 +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 remotes/git-svn branch
+       outside of git-svn.  Instead, create a branch from remotes/git-svn
+       and work on that branch.  Use the 'commit' command (see below)
+       to write git commits back to remotes/git-svn.
 
 commit::
        Commit specified commit or tree objects to SVN.  This relies on
@@ -111,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.
 
@@ -150,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 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
@@ -179,7 +214,9 @@ 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.
+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 181dfe0..80ad357 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 4e3847d..781badb 100755 (executable)
@@ -162,7 +162,7 @@ class CellRendererGraph(gtk.GenericCellRenderer):
                        for item in names:
                                names_len += len(item)
 
-               width = box_size * (cols + 1 ) + names_len 
+               width = box_size * (cols + 1 ) + names_len
                height = box_size
 
                # FIXME I have no idea how to use cell_area properly
@@ -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
@@ -261,11 +264,11 @@ class Commit:
        children_sha1 = {}
 
        def __init__(self, commit_lines):
-               self.message            = ""
+               self.message            = ""
                self.author             = ""
-               self.date               = ""
-               self.committer          = ""
-               self.commit_date        = ""
+               self.date               = ""
+               self.committer          = ""
+               self.commit_date        = ""
                self.commit_sha1        = ""
                self.parent_sha1        = [ ]
                self.parse_commit(commit_lines)
@@ -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()
@@ -391,7 +394,7 @@ class DiffWindow:
                sourceview.show()
 
 
-       def set_diff(self, commit_sha1, parent_sha1):
+       def set_diff(self, commit_sha1, parent_sha1, encoding):
                """Set the differences showed by this window.
                Compares the two trees and populates the window with the
                differences.
@@ -401,7 +404,7 @@ class DiffWindow:
                        return
 
                fp = os.popen("git diff-tree -p " + parent_sha1 + " " + commit_sha1)
-               self.buffer.set_text(fp.read())
+               self.buffer.set_text(unicode(fp.read(), encoding).encode('utf-8'))
                fp.close()
                self.window.show()
 
@@ -426,10 +429,11 @@ class GitView:
 
        def __init__(self, with_diff=0):
                self.with_diff = with_diff
-               self.window =   gtk.Window(gtk.WINDOW_TOPLEVEL)
+               self.window =   gtk.Window(gtk.WINDOW_TOPLEVEL)
                self.window.set_border_width(0)
                self.window.set_title("Git repository browser")
 
+               self.get_encoding()
                self.get_bt_sha1()
 
                # Use three-quarters of the screen by default
@@ -468,22 +472,20 @@ class GitView:
                        self.bt_sha1[sha1].append(name)
                fp.close()
 
+       def get_encoding(self):
+               fp = os.popen("git repo-config --get i18n.commitencoding")
+               self.encoding=string.strip(fp.readline())
+               fp.close()
+               if (self.encoding == ""):
+                       self.encoding = "utf-8"
+
 
        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")
@@ -495,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()
@@ -683,7 +697,7 @@ class GitView:
                self.revid_label.set_text(revid_label)
                self.committer_label.set_text(committer)
                self.timestamp_label.set_text(timestamp)
-               self.message_buffer.set_text(message)
+               self.message_buffer.set_text(unicode(message, self.encoding).encode('utf-8'))
 
                for widget in self.parents_widgets:
                        self.table.remove(widget)
@@ -728,7 +742,7 @@ class GitView:
                        button.set_relief(gtk.RELIEF_NONE)
                        button.set_sensitive(True)
                        button.connect("clicked", self._show_clicked_cb,
-                                       commit.commit_sha1, parent_id)
+                                       commit.commit_sha1, parent_id, self.encoding)
                        hbox.pack_start(button, expand=False, fill=True)
                        button.show()
 
@@ -784,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()
 
@@ -870,15 +884,15 @@ class GitView:
 
                # Reset nodepostion
                if (last_nodepos > 5):
-                       last_nodepos = -1 
+                       last_nodepos = -1
 
                # Add the incomplete lines of the last cell in this
                try:
                        colour = self.colours[commit.commit_sha1]
                except KeyError:
                        self.colours[commit.commit_sha1] = last_colour+1
-                       last_colour = self.colours[commit.commit_sha1] 
-                       colour =   self.colours[commit.commit_sha1] 
+                       last_colour = self.colours[commit.commit_sha1]
+                       colour =   self.colours[commit.commit_sha1]
 
                try:
                        node_pos = self.nodepos[commit.commit_sha1]
@@ -910,7 +924,7 @@ class GitView:
                                self.colours[parent_id] = last_colour+1
                                last_colour = self.colours[parent_id]
                                self.nodepos[parent_id] = last_nodepos+1
-                               last_nodepos = self.nodepos[parent_id] 
+                               last_nodepos = self.nodepos[parent_id]
 
                        in_line.append((node_pos, self.nodepos[parent_id],
                                                self.colours[parent_id]))
@@ -946,7 +960,7 @@ class GitView:
                        try:
                                next_commit = self.commits[index+1]
                                if (next_commit.commit_sha1 == sha1 and pos != int(pos)):
-                               # join the line back to the node point 
+                               # join the line back to the node point
                                # This need to be done only if we modified it
                                        in_line.append((pos, pos-0.5, self.colours[sha1]))
                                        continue;
@@ -967,10 +981,10 @@ class GitView:
 
                self.treeview.grab_focus()
 
-       def _show_clicked_cb(self, widget,  commit_sha1, parent_sha1):
+       def _show_clicked_cb(self, widget,  commit_sha1, parent_sha1, encoding):
                """Callback for when the show button for a parent is clicked."""
                window = DiffWindow()
-               window.set_diff(commit_sha1, parent_sha1)
+               window.set_diff(commit_sha1, parent_sha1, encoding)
                self.treeview.grab_focus()
 
 if __name__ == "__main__":
index c2f656a..2ed5984 100644 (file)
@@ -19,8 +19,9 @@
  */
 
 #include <stdlib.h>
+#include <string.h>
+#include <zlib.h>
 #include "delta.h"
-#include "zlib.h"
 
 
 /* block size: min = 16, max = 64k, power of 2 */
 #define MIN(a, b) ((a) < (b) ? (a) : (b))
 
 #define GR_PRIME 0x9e370001
-#define HASH(v, b) (((unsigned int)(v) * GR_PRIME) >> (32 - (b)))
-       
-static unsigned int hashbits(unsigned int size)
-{
-       unsigned int val = 1, bits = 0;
-       while (val < size && bits < 32) {
-               val <<= 1;
-               bits++;
-       }
-       return bits ? bits: 1;
-}
-
-typedef struct s_chanode {
-       struct s_chanode *next;
-       int icurr;
-} chanode_t;
-
-typedef struct s_chastore {
-       int isize, nsize;
-       chanode_t *ancur;
-} chastore_t;
-
-static void cha_init(chastore_t *cha, int isize, int icount)
-{
-       cha->isize = isize;
-       cha->nsize = icount * isize;
-       cha->ancur = NULL;
-}
-
-static void *cha_alloc(chastore_t *cha)
-{
-       chanode_t *ancur;
-       void *data;
+#define HASH(v, shift) (((unsigned int)(v) * GR_PRIME) >> (shift))
 
-       ancur = cha->ancur;
-       if (!ancur || ancur->icurr == cha->nsize) {
-               ancur = malloc(sizeof(chanode_t) + cha->nsize);
-               if (!ancur)
-                       return NULL;
-               ancur->icurr = 0;
-               ancur->next = cha->ancur;
-               cha->ancur = ancur;
-       }
-
-       data = (void *)ancur + sizeof(chanode_t) + ancur->icurr;
-       ancur->icurr += cha->isize;
-       return data;
-}
-
-static void cha_free(chastore_t *cha)
-{
-       chanode_t *cur = cha->ancur;
-       while (cur) {
-               chanode_t *tmp = cur;
-               cur = cur->next;
-               free(tmp);
-       }
-}
-
-typedef struct s_bdrecord {
-       struct s_bdrecord *next;
-       unsigned int fp;
+struct index {
        const unsigned char *ptr;
-} bdrecord_t;
-
-typedef struct s_bdfile {
-       chastore_t cha;
-       unsigned int fphbits;
-       bdrecord_t **fphash;
-} bdfile_t;
+       unsigned int val;
+       struct index *next;
+};
 
-static int delta_prepare(const unsigned char *buf, int bufsize, bdfile_t *bdf)
+static struct index ** delta_index(const unsigned char *buf,
+                                  unsigned long bufsize,
+                                  unsigned int *hash_shift)
 {
-       unsigned int fphbits;
-       int i, hsize;
-       const unsigned char *data, *top;
-       bdrecord_t *brec;
-       bdrecord_t **fphash;
-
-       fphbits = hashbits(bufsize / BLK_SIZE + 1);
-       hsize = 1 << fphbits;
-       fphash = malloc(hsize * sizeof(bdrecord_t *));
-       if (!fphash)
-               return -1;
-       for (i = 0; i < hsize; i++)
-               fphash[i] = NULL;
-       cha_init(&bdf->cha, sizeof(bdrecord_t), hsize / 4 + 1);
-
-       top = buf + bufsize;
-       data = buf + (bufsize / BLK_SIZE) * BLK_SIZE;
-       if (data == top)
+       unsigned int hsize, hshift, entries, blksize, i;
+       const unsigned char *data;
+       struct index *entry, **hash;
+       void *mem;
+
+       /* determine index hash size */
+       entries = (bufsize + BLK_SIZE - 1) / BLK_SIZE;
+       hsize = entries / 4;
+       for (i = 4; (1 << i) < hsize && i < 16; i++);
+       hsize = 1 << i;
+       hshift = 32 - i;
+       *hash_shift = hshift;
+
+       /* allocate lookup index */
+       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));
+
+       /* 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++;
+               blksize = BLK_SIZE;
                data -= BLK_SIZE;
+       }
 
-       for ( ; data >= buf; data -= BLK_SIZE) {
-               brec = cha_alloc(&bdf->cha);
-               if (!brec) {
-                       cha_free(&bdf->cha);
-                       free(fphash);
-                       return -1;
-               }
-               brec->fp = adler32(0, data, MIN(BLK_SIZE, top - data));
-               brec->ptr = data;
-               i = HASH(brec->fp, fphbits);
-               brec->next = fphash[i];
-               fphash[i] = brec;
-       }
-
-       bdf->fphbits = fphbits;
-       bdf->fphash = fphash;
-
-       return 0;
-}
-
-static void delta_cleanup(bdfile_t *bdf)
-{
-       free(bdf->fphash);
-       cha_free(&bdf->cha);
+       return hash;
 }
 
+/* provide the size of the copy opcode given the block offset and size */
 #define COPYOP_SIZE(o, s) \
     (!!(o & 0xff) + !!(o & 0xff00) + !!(o & 0xff0000) + !!(o & 0xff000000) + \
      !!(s & 0xff) + !!(s & 0xff00) + 1)
 
+/* the maximum size for any opcode */
+#define MAX_OP_SIZE COPYOP_SIZE(0xffffffff, 0xffffffff)
+
 void *diff_delta(void *from_buf, unsigned long from_size,
                 void *to_buf, unsigned long to_size,
                 unsigned long *delta_size,
                 unsigned long max_size)
 {
-       int i, outpos, outsize, inscnt, csize, msize, moff;
-       unsigned int fp;
-       const unsigned char *ref_data, *ref_top, *data, *top, *ptr1, *ptr2;
-       unsigned char *out, *orig;
-       bdrecord_t *brec;
-       bdfile_t bdf;
+       unsigned int i, outpos, outsize, inscnt, hash_shift;
+       const unsigned char *ref_data, *ref_top, *data, *top;
+       unsigned char *out;
+       struct index *entry, **hash;
 
-       if (!from_size || !to_size || delta_prepare(from_buf, from_size, &bdf))
+       if (!from_size || !to_size)
+               return NULL;
+       hash = delta_index(from_buf, from_size, &hash_shift);
+       if (!hash)
                return NULL;
-       
+
        outpos = 0;
        outsize = 8192;
+       if (max_size && outsize >= max_size)
+               outsize = max_size + MAX_OP_SIZE + 1;
        out = malloc(outsize);
        if (!out) {
-               delta_cleanup(&bdf);
+               free(hash);
                return NULL;
        }
 
@@ -199,28 +138,32 @@ void *diff_delta(void *from_buf, unsigned long from_size,
        }
 
        inscnt = 0;
-       moff = 0;
-       while (data < top) {
-               msize = 0;
-               fp = adler32(0, data, MIN(top - data, BLK_SIZE));
-               i = HASH(fp, bdf.fphbits);
-               for (brec = bdf.fphash[i]; brec; brec = brec->next) {
-                       if (brec->fp == fp) {
-                               csize = ref_top - brec->ptr;
-                               if (csize > top - data)
-                                       csize = top - data;
-                               for (ptr1 = brec->ptr, ptr2 = data; 
-                                    csize && *ptr1 == *ptr2;
-                                    csize--, ptr1++, ptr2++);
 
-                               csize = ptr1 - brec->ptr;
-                               if (csize > msize) {
-                                       moff = brec->ptr - ref_data;
-                                       msize = csize;
-                                       if (msize >= 0x10000) {
-                                               msize = 0x10000;
-                                               break;
-                                       }
+       while (data < top) {
+               unsigned int moff = 0, msize = 0;
+               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;
                                }
                        }
                }
@@ -235,13 +178,15 @@ void *diff_delta(void *from_buf, unsigned long from_size,
                                inscnt = 0;
                        }
                } else {
+                       unsigned char *op;
+
                        if (inscnt) {
                                out[outpos - inscnt - 1] = inscnt;
                                inscnt = 0;
                        }
 
                        data += msize;
-                       orig = out + outpos++;
+                       op = out + outpos++;
                        i = 0x80;
 
                        if (moff & 0xff) { out[outpos++] = moff; i |= 0x01; }
@@ -256,23 +201,21 @@ void *diff_delta(void *from_buf, unsigned long from_size,
                        msize >>= 8;
                        if (msize & 0xff) { out[outpos++] = msize; i |= 0x20; }
 
-                       *orig = i;
-               }
-
-               if (max_size && outpos > max_size) {
-                       free(out);
-                       delta_cleanup(&bdf);
-                       return NULL;
+                       *op = i;
                }
 
-               /* next time around the largest possible output is 1 + 4 + 3 */
-               if (outpos > outsize - 8) {
+               if (outpos >= outsize - MAX_OP_SIZE) {
                        void *tmp = out;
                        outsize = outsize * 3 / 2;
-                       out = realloc(out, outsize);
+                       if (max_size && outsize >= max_size)
+                               outsize = max_size + MAX_OP_SIZE + 1;
+                       if (max_size && outpos > max_size)
+                               out = NULL;
+                       else
+                               out = realloc(out, outsize);
                        if (!out) {
                                free(tmp);
-                               delta_cleanup(&bdf);
+                               free(hash);
                                return NULL;
                        }
                }
@@ -281,7 +224,7 @@ void *diff_delta(void *from_buf, unsigned long from_size,
        if (inscnt)
                out[outpos - inscnt - 1] = inscnt;
 
-       delta_cleanup(&bdf);
+       free(hash);
        *delta_size = outpos;
        return out;
 }
diff --git a/diff.c b/diff.c
index 804c08c..c0548ee 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -178,11 +178,12 @@ static void emit_rewrite_diff(const char *name_a,
                copy_file('+', temp[1].name);
 }
 
-static void builtin_diff(const char *name_a,
+static const char *builtin_diff(const char *name_a,
                         const char *name_b,
                         struct diff_tempfile *temp,
                         const char *xfrm_msg,
-                        int complete_rewrite)
+                        int complete_rewrite,
+                        const char **args)
 {
        int i, next_at, cmd_size;
        const char *const diff_cmd = "diff -L%s -L%s";
@@ -242,19 +243,24 @@ static void builtin_diff(const char *name_a,
                }
                if (xfrm_msg && xfrm_msg[0])
                        puts(xfrm_msg);
+               /*
+                * we do not run diff between different kind
+                * of objects.
+                */
                if (strncmp(temp[0].mode, temp[1].mode, 3))
-                       /* we do not run diff between different kind
-                        * of objects.
-                        */
-                       exit(0);
+                       return NULL;
                if (complete_rewrite) {
-                       fflush(NULL);
                        emit_rewrite_diff(name_a, name_b, temp);
-                       exit(0);
+                       return NULL;
                }
        }
-       fflush(NULL);
-       execlp("/bin/sh","sh", "-c", cmd, NULL);
+
+       /* This is disgusting */
+       *args++ = "sh";
+       *args++ = "-c";
+       *args++ = cmd;
+       *args = NULL;
+       return "/bin/sh";
 }
 
 struct diff_filespec *alloc_filespec(const char *path)
@@ -559,6 +565,40 @@ static void remove_tempfile_on_signal(int signo)
        raise(signo);
 }
 
+static int spawn_prog(const char *pgm, const char **arg)
+{
+       pid_t pid;
+       int status;
+
+       fflush(NULL);
+       pid = fork();
+       if (pid < 0)
+               die("unable to fork");
+       if (!pid) {
+               execvp(pgm, (char *const*) arg);
+               exit(255);
+       }
+
+       while (waitpid(pid, &status, 0) < 0) {
+               if (errno == EINTR)
+                       continue;
+               return -1;
+       }
+
+       /* Earlier we did not check the exit status because
+        * diff exits non-zero if files are different, and
+        * we are not interested in knowing that.  It was a
+        * mistake which made it harder to quit a diff-*
+        * session that uses the git-apply-patch-script as
+        * the GIT_EXTERNAL_DIFF.  A custom GIT_EXTERNAL_DIFF
+        * should also exit non-zero only when it wants to
+        * abort the entire diff-* session.
+        */
+       if (WIFEXITED(status) && !WEXITSTATUS(status))
+               return 0;
+       return -1;
+}
+
 /* An external diff command takes:
  *
  * diff-cmd name infile1 infile1-sha1 infile1-mode \
@@ -573,9 +613,9 @@ static void run_external_diff(const char *pgm,
                              const char *xfrm_msg,
                              int complete_rewrite)
 {
+       const char *spawn_arg[10];
        struct diff_tempfile *temp = diff_temp;
-       pid_t pid;
-       int status;
+       int retval;
        static int atexit_asked = 0;
        const char *othername;
 
@@ -592,59 +632,41 @@ static void run_external_diff(const char *pgm,
                signal(SIGINT, remove_tempfile_on_signal);
        }
 
-       fflush(NULL);
-       pid = fork();
-       if (pid < 0)
-               die("unable to fork");
-       if (!pid) {
-               if (pgm) {
-                       if (one && two) {
-                               const char *exec_arg[10];
-                               const char **arg = &exec_arg[0];
-                               *arg++ = pgm;
-                               *arg++ = name;
-                               *arg++ = temp[0].name;
-                               *arg++ = temp[0].hex;
-                               *arg++ = temp[0].mode;
-                               *arg++ = temp[1].name;
-                               *arg++ = temp[1].hex;
-                               *arg++ = temp[1].mode;
-                               if (other) {
-                                       *arg++ = other;
-                                       *arg++ = xfrm_msg;
-                               }
-                               *arg = NULL;
-                               execvp(pgm, (char *const*) exec_arg);
+       if (pgm) {
+               const char **arg = &spawn_arg[0];
+               if (one && two) {
+                       *arg++ = pgm;
+                       *arg++ = name;
+                       *arg++ = temp[0].name;
+                       *arg++ = temp[0].hex;
+                       *arg++ = temp[0].mode;
+                       *arg++ = temp[1].name;
+                       *arg++ = temp[1].hex;
+                       *arg++ = temp[1].mode;
+                       if (other) {
+                               *arg++ = other;
+                               *arg++ = xfrm_msg;
                        }
-                       else
-                               execlp(pgm, pgm, name, NULL);
+               } else {
+                       *arg++ = pgm;
+                       *arg++ = name;
                }
-               /*
-                * otherwise we use the built-in one.
-                */
-               if (one && two)
-                       builtin_diff(name, othername, temp, xfrm_msg,
-                                    complete_rewrite);
-               else
+               *arg = NULL;
+       } else {
+               if (one && two) {
+                       pgm = builtin_diff(name, othername, temp, xfrm_msg, complete_rewrite, spawn_arg);
+               } else
                        printf("* Unmerged path %s\n", name);
-               exit(0);
        }
-       if (waitpid(pid, &status, 0) < 0 ||
-           !WIFEXITED(status) || WEXITSTATUS(status)) {
-               /* Earlier we did not check the exit status because
-                * diff exits non-zero if files are different, and
-                * we are not interested in knowing that.  It was a
-                * mistake which made it harder to quit a diff-*
-                * session that uses the git-apply-patch-script as
-                * the GIT_EXTERNAL_DIFF.  A custom GIT_EXTERNAL_DIFF
-                * should also exit non-zero only when it wants to
-                * abort the entire diff-* session.
-                */
-               remove_tempfile();
+
+       retval = 0;
+       if (pgm)
+               retval = spawn_prog(pgm, spawn_arg);
+       remove_tempfile();
+       if (retval) {
                fprintf(stderr, "external diff died, stopping at %s.\n", name);
                exit(1);
        }
-       remove_tempfile();
 }
 
 static void diff_fill_sha1_info(struct diff_filespec *one)
index c57513a..0fc2b86 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..d03787b
--- /dev/null
@@ -0,0 +1,128 @@
+#include "cache.h"
+#include "diff.h"
+#include "diffcore.h"
+
+struct linehash {
+       unsigned long bytes;
+       unsigned long hash;
+};
+
+static unsigned long hash_extended_line(const unsigned char **buf_p,
+                                       unsigned long left)
+{
+       /* An extended line is zero or more whitespace letters (including LF)
+        * followed by one non whitespace letter followed by zero or more
+        * non LF, and terminated with by a LF (or EOF).
+        */
+       const unsigned char *bol = *buf_p;
+       const unsigned char *buf = bol;
+       unsigned long hashval = 0;
+       while (left) {
+               unsigned c = *buf++;
+               if (!c)
+                       goto binary;
+               left--;
+               if (' ' < c) {
+                       hashval = c;
+                       break;
+               }
+       }
+       while (left) {
+               unsigned c = *buf++;
+               if (!c)
+                       goto binary;
+               left--;
+               if (c == '\n')
+                       break;
+               if (' ' < c)
+                       hashval = hashval * 11 + c;
+       }
+       *buf_p = buf;
+       return hashval;
+
+ binary:
+       *buf_p = NULL;
+       return 0;
+}
+
+static int linehash_compare(const void *a_, const void *b_)
+{
+       struct linehash *a = (struct linehash *) a_;
+       struct linehash *b = (struct linehash *) b_;
+       if (a->hash < b->hash) return -1;
+       if (a->hash > b->hash) return 1;
+       return 0;
+}
+
+static struct linehash *hash_lines(const unsigned char *buf,
+                                  unsigned long size)
+{
+       const unsigned char *eobuf = buf + size;
+       struct linehash *line = NULL;
+       int alloc = 0, used = 0;
+
+       while (buf < eobuf) {
+               const unsigned char *ptr = buf;
+               unsigned long hash = hash_extended_line(&buf, eobuf-ptr);
+               if (!buf) {
+                       free(line);
+                       return NULL;
+               }
+               if (alloc <= used) {
+                       alloc = alloc_nr(alloc);
+                       line = xrealloc(line, sizeof(*line) * alloc);
+               }
+               line[used].bytes = buf - ptr;
+               line[used].hash = hash;
+               used++;
+       }
+       qsort(line, used, sizeof(*line), linehash_compare);
+
+       /* Terminate the list */
+       if (alloc <= used)
+               line = xrealloc(line, sizeof(*line) * (used+1));
+       line[used].bytes = line[used].hash = 0;
+       return line;
+}
+
+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)
+{
+       struct linehash *src_lines, *dst_lines;
+       unsigned long sc, la;
+
+       src_lines = hash_lines(src, src_size);
+       if (!src_lines)
+               return -1;
+       dst_lines = hash_lines(dst, dst_size);
+       if (!dst_lines) {
+               free(src_lines);
+               return -1;
+       }
+       sc = la = 0;
+       while (src_lines->bytes && dst_lines->bytes) {
+               int cmp = linehash_compare(src_lines, dst_lines);
+               if (!cmp) {
+                       sc += src_lines->bytes;
+                       src_lines++;
+                       dst_lines++;
+                       continue;
+               }
+               if (cmp < 0) {
+                       src_lines++;
+                       continue;
+               }
+               la += dst_lines->bytes;
+               dst_lines++;
+       }
+       while (dst_lines->bytes) {
+               la += dst_lines->bytes;
+               dst_lines++;
+       }
+       *src_copied = sc;
+       *literal_added = la;
+       return 0;
+}
index ffd126a..625b589 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,42 +162,23 @@ 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 */
+       if (diffcore_count_changes(src->data, src->size,
+                                  dst->data, dst->size,
+                                  delta_limit,
+                                  &src_copied, &literal_added))
                return 0;
 
-       /* A delta that has a lot of literal additions would have
-        * big delta_size no matter what else it does.
+       /* How similar are they?
+        * what percentage of material in dst are from source?
         */
-       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);
-               return 0;
-       }
-       free(delta);
-
-       /* Extent of damage */
-       if (src->size + literal_added < src_copied)
-               delta_size = 0;
+       if (dst->size < src_copied)
+               score = MAX_SCORE;
+       else if (!dst->size)
+               score = 0; /* should not happen */
        else
-               delta_size = (src->size - src_copied) + literal_added;
-
-       /*
-        * Now we will give some score to it.  100% edit gets 0 points
-        * and 0% edit gets MAX_SCORE points.
-        */
-       score = MAX_SCORE - (MAX_SCORE * delta_size / base_size); 
-       if (score < 0) return 0;
-       if (MAX_SCORE < score) return MAX_SCORE;
+               score = src_copied * MAX_SCORE / dst->size;
        return score;
 }
 
index 12cd816..dba4f17 100644 (file)
@@ -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
index 251e53c..16c08f0 100644 (file)
@@ -17,6 +17,7 @@ int only_use_symrefs = 0;
 int repository_format_version = 0;
 char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8";
 int shared_repository = 0;
+const char *apply_default_whitespace = NULL;
 
 static char *git_dir, *git_object_dir, *git_index_file, *git_refs_dir,
        *git_graft_file;
index 7cc4ae5..eab4aa8 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 f9c2c6c..d93ee19 100755 (executable)
@@ -15,6 +15,8 @@ sub usage() {
        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 @@ sub usage() {
        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 @@ foreach my $l (@filelines) {
        }
 
        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 6ac961e..663a3a3 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 f7ee1aa..d9ec1f1 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 d20d1a8..7d3f78e 100755 (executable)
@@ -53,6 +53,7 @@ my $methods = {
     '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 @@ my $methods = {
     'ci'              => \&req_ci,
     'diff'            => \&req_diff,
     'log'             => \&req_log,
+    'rlog'            => \&req_log,
     'tag'             => \&req_CATCHALL,
     'status'          => \&req_status,
     'admin'           => \&req_CATCHALL,
@@ -85,6 +87,31 @@ $log->info("--------------- STARTING -----------------");
 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 eb75de4..2bd2639 100755 (executable)
@@ -174,7 +174,7 @@ titleScript='
 process_one () {
        perl -w -e '
 my ($keep_subject, $num, $signoff, $commsg) = @ARGV;
-my ($signoff_pattern, $done_header, $done_subject, $signoff_seen,
+my ($signoff_pattern, $done_header, $done_subject, $done_separator, $signoff_seen,
     $last_was_signoff);
 
 if ($signoff) {
@@ -228,6 +228,11 @@ while (<FH>) {
        $done_subject = 1;
        next;
     }
+    unless ($done_separator) {
+        print "\n";
+        $done_separator = 1;
+        next if (/^$/);
+    }
 
     $last_was_signoff = 0;
     if (/Signed-off-by:/i) {
index 2ea852c..75aa8fe 100755 (executable)
@@ -19,25 +19,26 @@ EOT
        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 @@ if (-d $ARGV[$argCount-1]) {
        }
 }
 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 @@ else {
     $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 b0d095b..7c8d512 100755 (executable)
@@ -54,7 +54,7 @@ my $rc = GetOptions("from=s" => \$from,
                    "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 ee2940f..639aa41 100755 (executable)
@@ -13,6 +13,7 @@
 use strict;
 use warnings;
 use Getopt::Std;
+use File::Copy;
 use File::Spec;
 use File::Temp qw(tempfile);
 use File::Path qw(mkpath);
@@ -29,19 +30,21 @@ die "Need SVN:Core 1.2.1 or better" if $SVN::Core::VERSION lt "1.2.1";
 $SIG{'PIPE'}="IGNORE";
 $ENV{'TZ'}="UTC";
 
-our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T,$opt_b,$opt_r,$opt_s,$opt_l,$opt_d,$opt_D);
+our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T,
+    $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D);
 
 sub usage() {
        print STDERR <<END;
 Usage: ${\basename $0}     # fetch/update GIT from SVN
        [-o branch-for-HEAD] [-h] [-v] [-l max_rev]
        [-C GIT_repository] [-t tagname] [-T trunkname] [-b branchname]
-       [-d|-D] [-i] [-u] [-r] [-s start_chg] [-m] [-M regex] [SVN_URL]
+       [-d|-D] [-i] [-u] [-r] [-I ignorefilename] [-s start_chg]
+       [-m] [-M regex] [-A author_file] [SVN_URL]
 END
        exit(1);
 }
 
-getopts("b:C:dDhil:mM:o:rs:t:T:uv") or usage();
+getopts("A:b:C:dDhiI:l:mM:o:rs:t:T:uv") or usage();
 usage if $opt_h;
 
 my $tag_name = $opt_t || "tags";
@@ -66,6 +69,25 @@ if ($opt_M) {
        push (@mergerx, qr/$opt_M/);
 }
 
+# Absolutize filename now, since we will have chdir'ed by the time we
+# get around to opening it.
+$opt_A = File::Spec->rel2abs($opt_A) if $opt_A;
+
+our %users = ();
+our $users_file = undef;
+sub read_users($) {
+       $users_file = File::Spec->rel2abs(@_);
+       die "Cannot open $users_file\n" unless -f $users_file;
+       open(my $authors,$users_file);
+       while(<$authors>) {
+               chomp;
+               next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/;
+               (my $user,my $name,my $email) = ($1,$2,$3);
+               $users{$user} = [$name,$email];
+       }
+       close($authors);
+}
+
 select(STDERR); $|=1; select(STDOUT);
 
 
@@ -112,16 +134,40 @@ sub file {
                    DIR => File::Spec->tmpdir(), UNLINK => 1);
 
        print "... $rev $path ...\n" if $opt_v;
-       my $pool = SVN::Pool->new();
-       eval { $self->{'svn'}->get_file($path,$rev,$fh,$pool); };
-       $pool->clear;
+       my (undef, $properties);
+       eval { (undef, $properties)
+                  = $self->{'svn'}->get_file($path,$rev,$fh); };
        if($@) {
                return undef if $@ =~ /Attempted to get checksum/;
                die $@;
        }
+       my $mode;
+       if (exists $properties->{'svn:executable'}) {
+               $mode = '0755';
+       } else {
+               $mode = '0644';
+       }
        close ($fh);
 
-       return $name;
+       return ($name, $mode);
+}
+
+sub ignore {
+       my($self,$path,$rev) = @_;
+
+       print "... $rev $path ...\n" if $opt_v;
+       my (undef,undef,$properties)
+           = $self->{'svn'}->get_dir($path,$rev,undef);
+       if (exists $properties->{'svn:ignore'}) {
+               my ($fh, $name) = tempfile('gitsvn.XXXXXX',
+                                          DIR => File::Spec->tmpdir(),
+                                          UNLINK => 1);
+               print $fh $properties->{'svn:ignore'};
+               close($fh);
+               return $name;
+       } else {
+               return undef;
+       }
 }
 
 package main;
@@ -263,6 +309,14 @@ EOM
 -d $git_dir
        or die "Could not create git subdir ($git_dir).\n";
 
+my $default_authors = "$git_dir/svn-authors";
+if ($opt_A) {
+       read_users($opt_A);
+       copy($opt_A,$default_authors) or die "Copy failed: $!";
+} else {
+       read_users($default_authors) if -f $default_authors;
+}
+
 open BRANCHES,">>", "$git_dir/svn2git";
 
 sub node_kind($$$) {
@@ -296,7 +350,7 @@ sub get_file($$$) {
        my $svnpath = revert_split_path($branch,$path);
 
        # now get it
-       my $name;
+       my ($name,$mode);
        if($opt_d) {
                my($req,$res);
 
@@ -316,8 +370,9 @@ sub get_file($$$) {
                        return undef if $res->code == 301; # directory?
                        die $res->status_line." at $url\n";
                }
+               $mode = '0644'; # can't obtain mode via direct http request?
        } else {
-               $name = $svn->file("$svnpath",$rev);
+               ($name,$mode) = $svn->file("$svnpath",$rev);
                return undef unless defined $name;
        }
 
@@ -331,10 +386,37 @@ sub get_file($$$) {
        chomp $sha;
        close $F;
        unlink $name;
-       my $mode = "0644"; # SV does not seem to store any file modes
        return [$mode, $sha, $path];
 }
 
+sub get_ignore($$$$$) {
+       my($new,$old,$rev,$branch,$path) = @_;
+
+       return unless $opt_I;
+       my $svnpath = revert_split_path($branch,$path);
+       my $name = $svn->ignore("$svnpath",$rev);
+       if ($path eq '/') {
+               $path = $opt_I;
+       } else {
+               $path = File::Spec->catfile($path,$opt_I);
+       }
+       if (defined $name) {
+               my $pid = open(my $F, '-|');
+               die $! unless defined $pid;
+               if (!$pid) {
+                       exec("git-hash-object", "-w", $name)
+                           or die "Cannot create object: $!\n";
+               }
+               my $sha = <$F>;
+               chomp $sha;
+               close $F;
+               unlink $name;
+               push(@$new,['0644',$sha,$path]);
+       } else {
+               push(@$old,$path);
+       }
+}
+
 sub split_path($$) {
        my($rev,$path) = @_;
        my $branch;
@@ -431,6 +513,10 @@ sub commit {
 
        if (not defined $author) {
                $author_name = $author_email = "unknown";
+       } elsif (defined $users_file) {
+               die "User $author is not listed in $users_file\n"
+                   unless exists $users{$author};
+               ($author_name,$author_email) = @{$users{$author}};
        } elsif ($author =~ /^(.*?)\s+<(.*)>$/) {
                ($author_name, $author_email) = ($1, $2);
        } else {
@@ -540,6 +626,9 @@ sub commit {
                                                my $opath = $action->[3];
                                                print STDERR "$revision: $branch: could not fetch '$opath'\n";
                                        }
+                               } elsif ($node_kind eq $SVN::Node::dir) {
+                                       get_ignore(\@new, \@old, $revision,
+                                                  $branch,$path);
                                }
                        } elsif ($action->[0] eq "D") {
                                push(@old,$path);
@@ -548,6 +637,9 @@ sub commit {
                                if ($node_kind eq $SVN::Node::file) {
                                        my $f = get_file($revision,$branch,$path);
                                        push(@new,$f) if $f;
+                               } elsif ($node_kind eq $SVN::Node::dir) {
+                                       get_ignore(\@new, \@old, $revision,
+                                                  $branch,$path);
                                }
                        } else {
                                die "$revision: unknown action '".$action->[0]."' for $path\n";
index 726b1e7..36f171b 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 b0da6b1..a547dbd 100644 (file)
--- a/git.c
+++ b/git.c
@@ -256,12 +256,67 @@ 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) {
-               pretty_print_commit(CMIT_FMT_DEFAULT, commit, ~0, buf, LOGSIZE, 18);
+               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);
index 21ee572..136a7f5 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 */
index f39fe5c..be29b3f 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 826ae7a..982ebf8 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 6af8d86..8e4d83e 100644 (file)
@@ -7,10 +7,9 @@
 #include "diff.h"
 #include "revision.h"
 
-/* bits #0-3 in revision.h */
+/* bits #0-4 in revision.h */
 
-#define COUNTED                (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"
index c84f146..a3df810 100644 (file)
@@ -482,6 +482,21 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                revs->max_count = atoi(arg + 12);
                                continue;
                        }
+                       /* accept -<digit>, like traditilnal "head" */
+                       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;
@@ -492,6 +507,26 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                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;
index 0043c16..31e8f61 100644 (file)
@@ -5,6 +5,7 @@
 #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 */
index 5a86ae2..24efb65 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 cabfadd..d1947e1 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' \
index 43d74c5..811a479 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 e85a1ed..e478e13 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 ce1db38..797245a 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;