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

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

Yuck.

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

Here is the way I made this commit:

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

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

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

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

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

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

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

     cfcbd3427e67056a00ec832645b057eaf33888d9
     c436eb8cf1efa3fe2c70100ae0cbc48f0feaf5af

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

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

39 files changed:
Documentation/git-am.txt
Documentation/git-apply.txt
Documentation/git-read-tree.txt
Documentation/git-repo-config.txt
Documentation/git-rev-list.txt
Documentation/git-svnimport.txt
Documentation/git-tools.txt [new file with mode: 0644]
Documentation/tutorial.txt
GIT-VERSION-GEN
apply.c
blame.c
contrib/emacs/git.el
contrib/emacs/vc-git.el [new file with mode: 0644]
contrib/git-svn/git-svn.perl
contrib/git-svn/git-svn.txt
contrib/gitview/gitview
count-delta.c
delta.h
diff-delta.c
diffcore-delta.c
diffcore.h
git-am.sh
git-annotate.perl
git-branch.sh
git-commit.sh
git-cvsserver.perl
git-mv.perl
git-send-email.perl
git.c
pack-objects.c
read-tree.c
revision.c
show-branch.c
t/t3600-rm.sh
t/t7001-mv.sh
t/t8001-annotate.sh
tar-tree.c
test-delta.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 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 a158813..9d38657 100644 (file)
@@ -9,6 +9,7 @@ git-svnimport - Import a SVN repository into git
 
 SYNOPSIS
 --------
+[verse]
 'git-svnimport' [ -o <branch-for-HEAD> ] [ -h ] [ -v ] [ -d | -D ]
                [ -C <GIT_repository> ] [ -i ] [ -u ] [-l limit_rev]
                [ -b branch_subdir ] [ -T trunk_subdir ] [ -t tag_subdir ]
diff --git a/Documentation/git-tools.txt b/Documentation/git-tools.txt
new file mode 100644 (file)
index 0000000..00e57a6
--- /dev/null
@@ -0,0 +1,97 @@
+A short git tools survey
+========================
+
+
+Introduction
+------------
+
+Apart from git contrib/ area there are some others third-party tools
+you may want to look.
+
+This document presents a brief summary of each tool and the corresponding
+link.
+
+
+Alternative/Augmentative Procelains
+-----------------------------------
+
+   - *Cogito* (http://www.kernel.org/pub/software/scm/cogito/)
+
+   Cogito is a version control system layered on top of the git tree history
+   storage system. It aims at seamless user interface and ease of use,
+   providing generally smoother user experience than the "raw" Core GIT
+   itself and indeed many other version control systems.
+
+
+   - *pg* (http://www.spearce.org/category/projects/scm/pg/)
+
+   pg is a shell script wrapper around GIT to help the user manage a set of
+   patches to files. pg is somewhat like quilt or StGIT, but it does have a
+   slightly different feature set.
+
+
+   - *StGit* (http://www.procode.org/stgit/)
+
+   Stacked GIT provides a quilt-like patch management functionality in the
+    GIT environment. You can easily manage your patches in the scope of GIT
+   until they get merged upstream.
+
+
+History Viewers
+---------------
+
+   - *gitk* (shipped with git-core)
+
+   gitk is a simple TK GUI for browsing history of GIT repositories easily.
+
+
+   - *gitview*  (contrib/)
+
+   gitview is a GTK based repository browser for git
+
+
+   - *gitweb* (ftp://ftp.kernel.org/pub/software/scm/gitweb/)
+
+   GITweb provides full-fledged web interface for GIT repositories.
+
+
+   - *qgit* (http://digilander.libero.it/mcostalba/)
+
+   QGit is a git/StGIT GUI viewer built on Qt/C++. QGit could be used
+   to browse history and directory tree, view annotated files, commit
+   changes cherry picking single files or applying patches.
+   Currently it is the fastest and most feature rich among the git
+   viewers and commit tools.
+
+
+
+Foreign SCM interface
+---------------------
+
+   - *git-svn* (contrib/)
+
+   git-svn is a simple conduit for changesets between a single Subversion
+   branch and git.
+
+
+   - *quilt2git / git2quilt* (http://home-tj.org/wiki/index.php/Misc)
+
+   These utilities convert patch series in a quilt repository and commit
+   series in git back and forth.
+
+
+Others
+------
+
+   - *(h)gct* (http://www.cyd.liu.se/users/~freku045/gct/)
+
+   Commit Tool or (h)gct is a GUI enabled commit tool for git and
+   Mercurial (hg). It allows the user to view diffs, select which files
+   to committed (or ignored / reverted) write commit messages and
+   perform the commit itself.
+
+   - *git.el* (contrib/)
+
+   This is an Emacs interface for git. The user interface is modeled on
+   pcl-cvs. It has been developed on Emacs 21 and will probably need some
+   tweaking to work on XEmacs.
index 66680d7..fa79b01 100644 (file)
@@ -309,7 +309,7 @@ git diff HEAD^^ HEAD^
 -------------------------------------
 
 shows the difference between that previous state and the state two
-commits ago.  Also, HEAD~5 can be used as a shorthand for HEAD^^^^^,
+commits ago.  Also, HEAD~5 can be used as a shorthand for HEAD{caret}{caret}{caret}{caret}{caret},
 and more generally HEAD~n can refer to the nth previous commit.
 Commits representing merges have more than one parent, and you can
 specify which parent to follow in that case; see
index 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*'\(.*\)')
diff --git a/apply.c b/apply.c
index 9deb206..c369966 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -32,7 +32,7 @@ static int no_add = 0;
 static int show_index_info = 0;
 static int line_termination = '\n';
 static const char apply_usage[] =
-"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] <patch>...";
+"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] [--whitespace=<nowarn|warn|error|error-all|strip>] <patch>...";
 
 static enum whitespace_eol {
        nowarn_whitespace,
diff --git a/blame.c b/blame.c
index dbce7e2..7308c36 100644 (file)
--- a/blame.c
+++ b/blame.c
@@ -1,4 +1,10 @@
+/*
+ * Copyright (C) 2006, Fredrik Kuivinen <freku045@student.liu.se>
+ */
+
 #include <assert.h>
+#include <time.h>
+#include <sys/time.h>
 
 #include "cache.h"
 #include "refs.h"
 #include "tree.h"
 #include "blob.h"
 #include "diff.h"
+#include "revision.h"
 
 #define DEBUG 0
 
-struct commit** blame_lines;
+struct commit **blame_lines;
 int num_blame_lines;
 
-struct util_info
-{
-    int* line_map;
-    int num_lines;
-    unsigned char sha1[20]; /* blob sha, not commit! */
-    char* buf;
-    unsigned long size;
+struct util_info {
+       int *line_map;
+       unsigned char sha1[20]; /* blob sha, not commit! */
+       char *buf;
+       unsigned long size;
+       int num_lines;
 //    const char* path;
 };
 
-struct chunk
-{
-    int off1, len1; // ---
-    int off2, len2; // +++
+struct chunk {
+       int off1, len1; // ---
+       int off2, len2; // +++
 };
 
-struct patch
-{
-    struct chunk* chunks;
-    int num;
+struct patch {
+       struct chunk *chunks;
+       int num;
 };
 
-static void get_blob(struct commitcommit);
+static void get_blob(struct commit *commit);
 
-int num_get_patch = 0;
-int num_commits = 0;
+/* Only used for statistics */
+static int num_get_patch = 0;
+static int num_commits = 0;
+static int patch_time = 0;
 
-struct patch* get_patch(struct commit* commit, struct commit* other)
+#define TEMPFILE_PATH_LEN 60
+static struct patch *get_patch(struct commit *commit, struct commit *other)
 {
-    struct patch* ret = xmalloc(sizeof(struct patch));
-    ret->chunks = NULL;
-    ret->num = 0;
-
-    struct util_info* info_c = (struct util_info*) commit->object.util;
-    struct util_info* info_o = (struct util_info*) other->object.util;
-
-    if(!memcmp(info_c->sha1, info_o->sha1, 20))
-        return ret;
-
-    get_blob(commit);
-    get_blob(other);
-
-    FILE* fout = fopen("/tmp/git-blame-tmp1", "w");
-    if(!fout)
-        die("fopen tmp1 failed: %s", strerror(errno));
-
-    if(fwrite(info_c->buf, info_c->size, 1, fout) != 1)
-        die("fwrite 1 failed: %s", strerror(errno));
-    fclose(fout);
-
-    fout = fopen("/tmp/git-blame-tmp2", "w");
-    if(!fout)
-        die("fopen tmp2 failed: %s", strerror(errno));
-
-    if(fwrite(info_o->buf, info_o->size, 1, fout) != 1)
-        die("fwrite 2 failed: %s", strerror(errno));
-    fclose(fout);
-
-    FILE* fin = popen("diff -u0 /tmp/git-blame-tmp1 /tmp/git-blame-tmp2", "r");
-    if(!fin)
-        die("popen failed: %s", strerror(errno));
-
-    char buf[1024];
-    while(fgets(buf, sizeof(buf), fin)) {
-        if(buf[0] != '@' || buf[1] != '@')
-            continue;
-
-        if(DEBUG)
-            printf("chunk line: %s", buf);
-        ret->num++;
-        ret->chunks = xrealloc(ret->chunks, sizeof(struct chunk)*ret->num);
-        struct chunk* chunk = &ret->chunks[ret->num-1];
-
-        assert(!strncmp(buf, "@@ -", 4));
-
-        char* start = buf+4;
-        char* sp = index(start, ' ');
-        *sp = '\0';
-        if(index(start, ',')) {
-            int ret = sscanf(start, "%d,%d", &chunk->off1, &chunk->len1);
-            assert(ret == 2);
-        } else {
-            int ret = sscanf(start, "%d", &chunk->off1);
-            assert(ret == 1);
-            chunk->len1 = 1;
-        }
-        *sp = ' ';
-
-        start = sp+1;
-        sp = index(start, ' ');
-        *sp = '\0';
-        if(index(start, ',')) {
-            int ret = sscanf(start, "%d,%d", &chunk->off2, &chunk->len2);
-            assert(ret == 2);
-        } else {
-            int ret = sscanf(start, "%d", &chunk->off2);
-            assert(ret == 1);
-            chunk->len2 = 1;
-        }
-        *sp = ' ';
-
-        if(chunk->off1 > 0)
-            chunk->off1 -= 1;
-        if(chunk->off2 > 0)
-            chunk->off2 -= 1;
-
-        assert(chunk->off1 >= 0);
-        assert(chunk->off2 >= 0);
-    }
-    fclose(fin);
-
-    num_get_patch++;
-    return ret;
+       struct patch *ret;
+       struct util_info *info_c = (struct util_info *)commit->object.util;
+       struct util_info *info_o = (struct util_info *)other->object.util;
+       char tmp_path1[TEMPFILE_PATH_LEN], tmp_path2[TEMPFILE_PATH_LEN];
+       char diff_cmd[TEMPFILE_PATH_LEN*2 + 20];
+       struct timeval tv_start, tv_end;
+       int fd;
+       FILE *fin;
+       char buf[1024];
+
+       ret = xmalloc(sizeof(struct patch));
+       ret->chunks = NULL;
+       ret->num = 0;
+
+       get_blob(commit);
+       get_blob(other);
+
+       gettimeofday(&tv_start, NULL);
+
+       fd = git_mkstemp(tmp_path1, TEMPFILE_PATH_LEN, "git-blame-XXXXXX");
+       if (fd < 0)
+               die("unable to create temp-file: %s", strerror(errno));
+
+       if (xwrite(fd, info_c->buf, info_c->size) != info_c->size)
+               die("write failed: %s", strerror(errno));
+       close(fd);
+
+       fd = git_mkstemp(tmp_path2, TEMPFILE_PATH_LEN, "git-blame-XXXXXX");
+       if (fd < 0)
+               die("unable to create temp-file: %s", strerror(errno));
+
+       if (xwrite(fd, info_o->buf, info_o->size) != info_o->size)
+               die("write failed: %s", strerror(errno));
+       close(fd);
+
+       sprintf(diff_cmd, "diff -u0 %s %s", tmp_path1, tmp_path2);
+       fin = popen(diff_cmd, "r");
+       if (!fin)
+               die("popen failed: %s", strerror(errno));
+
+       while (fgets(buf, sizeof(buf), fin)) {
+               struct chunk *chunk;
+               char *start, *sp;
+
+               if (buf[0] != '@' || buf[1] != '@')
+                       continue;
+
+               if (DEBUG)
+                       printf("chunk line: %s", buf);
+               ret->num++;
+               ret->chunks = xrealloc(ret->chunks,
+                                      sizeof(struct chunk) * ret->num);
+               chunk = &ret->chunks[ret->num - 1];
+
+               assert(!strncmp(buf, "@@ -", 4));
+
+               start = buf + 4;
+               sp = index(start, ' ');
+               *sp = '\0';
+               if (index(start, ',')) {
+                       int ret =
+                           sscanf(start, "%d,%d", &chunk->off1, &chunk->len1);
+                       assert(ret == 2);
+               } else {
+                       int ret = sscanf(start, "%d", &chunk->off1);
+                       assert(ret == 1);
+                       chunk->len1 = 1;
+               }
+               *sp = ' ';
+
+               start = sp + 1;
+               sp = index(start, ' ');
+               *sp = '\0';
+               if (index(start, ',')) {
+                       int ret =
+                           sscanf(start, "%d,%d", &chunk->off2, &chunk->len2);
+                       assert(ret == 2);
+               } else {
+                       int ret = sscanf(start, "%d", &chunk->off2);
+                       assert(ret == 1);
+                       chunk->len2 = 1;
+               }
+               *sp = ' ';
+
+               if (chunk->len1 == 0)
+                       chunk->off1++;
+               if (chunk->len2 == 0)
+                       chunk->off2++;
+
+               if (chunk->off1 > 0)
+                       chunk->off1--;
+               if (chunk->off2 > 0)
+                       chunk->off2--;
+
+               assert(chunk->off1 >= 0);
+               assert(chunk->off2 >= 0);
+       }
+       pclose(fin);
+       unlink(tmp_path1);
+       unlink(tmp_path2);
+
+       gettimeofday(&tv_end, NULL);
+       patch_time += 1000000 * (tv_end.tv_sec - tv_start.tv_sec) +
+               tv_end.tv_usec - tv_start.tv_usec;
+
+       num_get_patch++;
+       return ret;
 }
 
-void free_patch(struct patch* p)
+static void free_patch(struct patch *p)
 {
-    free(p->chunks);
-    free(p);
+       free(p->chunks);
+       free(p);
 }
 
-static int get_blob_sha1_internal(unsigned char *sha1, const char *base, int baselen,
-                                  const char *pathname, unsigned mode, int stage);
-
+static int get_blob_sha1_internal(unsigned char *sha1, const char *base,
+                                 int baselen, const char *pathname,
+                                 unsigned mode, int stage);
 
 static unsigned char blob_sha1[20];
-static int get_blob_sha1(struct tree* t, const char* pathname, unsigned char* sha1)
+static int get_blob_sha1(struct tree *t, const char *pathname,
+                        unsigned char *sha1)
 {
-    const char *pathspec[2];
-    pathspec[0] = pathname;
-    pathspec[1] = NULL;
-    memset(blob_sha1, 0, sizeof(blob_sha1));
-    read_tree_recursive(t, "", 0, 0, pathspec, get_blob_sha1_internal);
-
-    int i;
-    for(i = 0; i < 20; i++) {
-        if(blob_sha1[i] != 0)
-            break;
-    }
-
-    if(i == 20)
-        return -1;
-
-    memcpy(sha1, blob_sha1, 20);
-    return 0;
+       int i;
+       const char *pathspec[2];
+       pathspec[0] = pathname;
+       pathspec[1] = NULL;
+       memset(blob_sha1, 0, sizeof(blob_sha1));
+       read_tree_recursive(t, "", 0, 0, pathspec, get_blob_sha1_internal);
+
+       for (i = 0; i < 20; i++) {
+               if (blob_sha1[i] != 0)
+                       break;
+       }
+
+       if (i == 20)
+               return -1;
+
+       memcpy(sha1, blob_sha1, 20);
+       return 0;
 }
 
-static int get_blob_sha1_internal(unsigned char *sha1, const char *base, int baselen,
-                                  const char *pathname, unsigned mode, int stage)
+static int get_blob_sha1_internal(unsigned char *sha1, const char *base,
+                                 int baselen, const char *pathname,
+                                 unsigned mode, int stage)
 {
-//    printf("Got blob: %s base: '%s' baselen: %d pathname: '%s' mode: %o stage: %d\n",
-//           sha1_to_hex(sha1), base, baselen, pathname, mode, stage);
-
-    if(S_ISDIR(mode))
-        return READ_TREE_RECURSIVE;
+       if (S_ISDIR(mode))
+               return READ_TREE_RECURSIVE;
 
-    memcpy(blob_sha1, sha1, 20);
-    return -1;
+       memcpy(blob_sha1, sha1, 20);
+       return -1;
 }
 
-static void get_blob(struct commitcommit)
+static void get_blob(struct commit *commit)
 {
-    struct util_info* info = commit->object.util;
-    char type[20];
+       struct util_info *info = commit->object.util;
+       char type[20];
 
-    if(info->buf)
-        return;
+       if (info->buf)
+               return;
 
-    info->buf = read_sha1_file(info->sha1, type, &info->size);
-    assert(!strcmp(type, "blob"));
+       info->buf = read_sha1_file(info->sha1, type, &info->size);
+
+       assert(!strcmp(type, "blob"));
 }
 
-void print_patch(struct patch* p)
+/* For debugging only */
+static void print_patch(struct patch *p)
 {
-    printf("Num chunks: %d\n", p->num);
-    int i;
-    for(i = 0; i < p->num; i++) {
-        printf("%d,%d %d,%d\n", p->chunks[i].off1, p->chunks[i].len1, p->chunks[i].off2, p->chunks[i].len2);
-    }
+       int i;
+       printf("Num chunks: %d\n", p->num);
+       for (i = 0; i < p->num; i++) {
+               printf("%d,%d %d,%d\n", p->chunks[i].off1, p->chunks[i].len1,
+                      p->chunks[i].off2, p->chunks[i].len2);
+       }
 }
 
+/* For debugging only */
+static void print_map(struct commit *cmit, struct commit *other)
+{
+       struct util_info *util = cmit->object.util;
+       struct util_info *util2 = other->object.util;
+
+       int i;
+       int max =
+           util->num_lines >
+           util2->num_lines ? util->num_lines : util2->num_lines;
+       int num;
+
+       for (i = 0; i < max; i++) {
+               printf("i: %d ", i);
+               num = -1;
+
+               if (i < util->num_lines) {
+                       num = util->line_map[i];
+                       printf("%d\t", num);
+               } else
+                       printf("\t");
+
+               if (i < util2->num_lines) {
+                       int num2 = util2->line_map[i];
+                       printf("%d\t", num2);
+                       if (num != -1 && num2 != num)
+                               printf("---");
+               } else
+                       printf("\t");
+
+               printf("\n");
+       }
+}
 
 // p is a patch from commit to other.
-void fill_line_map(struct commit* commit, struct commit* other, struct patch* p)
+static void fill_line_map(struct commit *commit, struct commit *other,
+                         struct patch *p)
 {
-    int num_lines = ((struct util_info*) commit->object.util)->num_lines;
-    int* line_map = ((struct util_info*) commit->object.util)->line_map;
-    int num_lines2 = ((struct util_info*) other->object.util)->num_lines;
-    int* line_map2 = ((struct util_info*) other->object.util)->line_map;
-    int cur_chunk = 0;
-    int i1, i2;
-
-    if(p->num && DEBUG)
-        print_patch(p);
-
-    for(i1 = 0; i1 < num_lines; i1++)
-        line_map[i1] = -1;
-
-    if(DEBUG)
-        printf("num lines 1: %d num lines 2: %d\n", num_lines, num_lines2);
-
-    for(i1 = 0, i2 = 0; i1 < num_lines; i1++, i2++) {
-        if(DEBUG > 1)
-            printf("%d %d\n", i1, i2);
-
-        if(i2 >= num_lines2)
-            break;
-
-        line_map[i1] = line_map2[i2];
-
-        struct chunk* chunk = NULL;
-        if(cur_chunk < p->num)
-            chunk = &p->chunks[cur_chunk];
-
-        if(chunk && chunk->off1 == i1) {
-            i2 = chunk->off2;
-
-            if(chunk->len1 > 0)
-                i1 += chunk->len1-1;
-            if(chunk->len2 > 0)
-                i2 += chunk->len2-1;
-            cur_chunk++;
-        }
-    }
+       struct util_info *util = commit->object.util;
+       struct util_info *util2 = other->object.util;
+       int *map = util->line_map;
+       int *map2 = util2->line_map;
+       int cur_chunk = 0;
+       int i1, i2;
+
+       if (p->num && DEBUG)
+               print_patch(p);
+
+       if (DEBUG)
+               printf("num lines 1: %d num lines 2: %d\n", util->num_lines,
+                      util2->num_lines);
+
+       for (i1 = 0, i2 = 0; i1 < util->num_lines; i1++, i2++) {
+               struct chunk *chunk = NULL;
+               if (cur_chunk < p->num)
+                       chunk = &p->chunks[cur_chunk];
+
+               if (chunk && chunk->off1 == i1) {
+                       if (DEBUG && i2 != chunk->off2)
+                               printf("i2: %d off2: %d\n", i2, chunk->off2);
+
+                       assert(i2 == chunk->off2);
+
+                       i1--;
+                       i2--;
+                       if (chunk->len1 > 0)
+                               i1 += chunk->len1;
+
+                       if (chunk->len2 > 0)
+                               i2 += chunk->len2;
+
+                       cur_chunk++;
+               } else {
+                       if (i2 >= util2->num_lines)
+                               break;
+
+                       if (map[i1] != map2[i2] && map[i1] != -1) {
+                               if (DEBUG)
+                                       printf("map: i1: %d %d %p i2: %d %d %p\n",
+                                              i1, map[i1],
+                                              i1 != -1 ? blame_lines[map[i1]] : NULL,
+                                              i2, map2[i2],
+                                              i2 != -1 ? blame_lines[map2[i2]] : NULL);
+                               if (map2[i2] != -1 &&
+                                   blame_lines[map[i1]] &&
+                                   !blame_lines[map2[i2]])
+                                       map[i1] = map2[i2];
+                       }
+
+                       if (map[i1] == -1 && map2[i2] != -1)
+                               map[i1] = map2[i2];
+               }
+
+               if (DEBUG > 1)
+                       printf("l1: %d l2: %d i1: %d i2: %d\n",
+                              map[i1], map2[i2], i1, i2);
+       }
 }
 
-int map_line(struct commit* commit, int line)
+static int map_line(struct commit *commit, int line)
 {
-    struct util_info* info = commit->object.util;
-    assert(line >= 0 && line < info->num_lines);
-    return info->line_map[line];
+       struct util_info *info = commit->object.util;
+       assert(line >= 0 && line < info->num_lines);
+       return info->line_map[line];
 }
 
-int fill_util_info(struct commit* commit, const char* path)
+static int fill_util_info(struct commit *commit, const char *path)
 {
-    if(commit->object.util)
-        return 0;
-
-    struct util_info* util = xmalloc(sizeof(struct util_info));
-    util->buf = NULL;
-    util->size = 0;
-    util->num_lines = -1;
-    util->line_map = NULL;
-
-    commit->object.util = util;
-
-    if(get_blob_sha1(commit->tree, path, util->sha1))
-        return -1;
-
-    return 0;
+       struct util_info *util;
+       if (commit->object.util)
+               return 0;
+
+       util = xmalloc(sizeof(struct util_info));
+
+       if (get_blob_sha1(commit->tree, path, util->sha1)) {
+               free(util);
+               return 1;
+       } else {
+               util->buf = NULL;
+               util->size = 0;
+               util->line_map = NULL;
+               util->num_lines = -1;
+               commit->object.util = util;
+               return 0;
+       }
 }
 
-void alloc_line_map(struct commit* commit)
+static void alloc_line_map(struct commit *commit)
 {
-    struct util_info* util = commit->object.util;
-
-    if(util->line_map)
-        return;
+       struct util_info *util = commit->object.util;
+       int i;
 
-    get_blob(commit);
+       if (util->line_map)
+               return;
 
-    int i;
-    util->num_lines = 0;
-    for(i = 0; i < util->size; i++) {
-        if(util->buf[i] == '\n')
-            util->num_lines++;
-    }
-    util->line_map = xmalloc(sizeof(int)*util->num_lines);
-}
+       get_blob(commit);
 
-void copy_line_map(struct commit* dst, struct commit* src)
-{
-    struct util_info* u_dst = dst->object.util;
-    struct util_info* u_src = src->object.util;
+       util->num_lines = 0;
+       for (i = 0; i < util->size; i++) {
+               if (util->buf[i] == '\n')
+                       util->num_lines++;
+       }
+       if(util->buf[util->size - 1] != '\n')
+               util->num_lines++;
 
-    u_dst->line_map = u_src->line_map;
-    u_dst->num_lines = u_src->num_lines;
-    u_dst->buf = u_src->buf;
-    u_dst->size = u_src->size;
-}
+       util->line_map = xmalloc(sizeof(int) * util->num_lines);
 
-void process_commits(struct commit_list* list, const char* path)
-{
-    int i;
-
-    while(list) {
-        struct commit* commit = pop_commit(&list);
-        struct commit_list* parents;
-        struct util_info* info;
-
-        info = commit->object.util;
-        num_commits++;
-        if(DEBUG)
-            printf("\nProcessing commit: %d %s\n", num_commits, sha1_to_hex(commit->object.sha1));
-        for(parents = commit->parents;
-            parents != NULL; parents = parents->next) {
-            struct commit* parent = parents->item;
-
-            if(parse_commit(parent) < 0)
-                die("parse_commit error");
-
-            if(DEBUG)
-                printf("parent: %s\n", sha1_to_hex(parent->object.sha1));
-
-            if(fill_util_info(parent, path))
-                continue;
-
-            // Temporarily assign everything to the parent.
-            int num_blame = 0;
-            for(i = 0; i < num_blame_lines; i++) {
-                if(blame_lines[i] == commit) {
-                    num_blame++;
-                    blame_lines[i] = parent;
-                }
-            }
-
-            if(num_blame == 0)
-                continue;
-
-            struct patch* patch = get_patch(parent, commit);
-            if(patch->num == 0) {
-                copy_line_map(parent, commit);
-            } else {
-                alloc_line_map(parent);
-                fill_line_map(parent, commit, patch);
-            }
-
-            for(i = 0; i < patch->num; i++) {
-                int l;
-                for(l = 0; l < patch->chunks[i].len2; l++) {
-                    int mapped_line = map_line(commit, patch->chunks[i].off2 + l);
-                    if(mapped_line != -1 && blame_lines[mapped_line] == parent)
-                        blame_lines[mapped_line] = commit;
-                }
-            }
-            free_patch(patch);
-        }
-    }
+       for (i = 0; i < util->num_lines; i++)
+               util->line_map[i] = -1;
 }
 
-#define SEEN 1
-struct commit_list* get_commit_list(struct commit* commit, const char* pathname)
+static void init_first_commit(struct commit* commit, const char* filename)
 {
-    struct commit_list* ret = NULL;
-    struct commit_list* process = NULL;
-    unsigned char sha1[20];
-
-    commit_list_insert(commit, &process);
-
-    while(process) {
-        struct commit* com = pop_commit(&process);
-        if(com->object.flags & SEEN)
-            continue;
+       struct util_info* util;
+       int i;
 
-        com->object.flags |= SEEN;
-        commit_list_insert(com, &ret);
-        struct commit_list* parents;
+       if (fill_util_info(commit, filename))
+               die("fill_util_info failed");
 
-        parse_commit(com);
+       alloc_line_map(commit);
 
-        for(parents = com->parents;
-            parents != NULL; parents = parents->next) {
-            struct commit* parent = parents->item;
+       util = commit->object.util;
+       num_blame_lines = util->num_lines;
 
-            parse_commit(parent);
+       for (i = 0; i < num_blame_lines; i++)
+               util->line_map[i] = i;
+}
 
-            if(!get_blob_sha1(parent->tree, pathname, sha1))
-                commit_list_insert(parent, &process);
-        }
-    }
 
-    return ret;
+static void process_commits(struct rev_info *rev, const char *path,
+                           struct commit** initial)
+{
+       int i;
+       struct util_info* util;
+       int lines_left;
+       int *blame_p;
+       int *new_lines;
+       int new_lines_len;
+
+       struct commit* commit = get_revision(rev);
+       assert(commit);
+       init_first_commit(commit, path);
+
+       util = commit->object.util;
+       num_blame_lines = util->num_lines;
+       blame_lines = xmalloc(sizeof(struct commit *) * num_blame_lines);
+       for (i = 0; i < num_blame_lines; i++)
+               blame_lines[i] = NULL;
+
+       lines_left = num_blame_lines;
+       blame_p = xmalloc(sizeof(int) * num_blame_lines);
+       new_lines = xmalloc(sizeof(int) * num_blame_lines);
+       do {
+               struct commit_list *parents;
+               int num_parents;
+               struct util_info *util;
+
+               if (DEBUG)
+                       printf("\nProcessing commit: %d %s\n", num_commits,
+                              sha1_to_hex(commit->object.sha1));
+
+               if (lines_left == 0)
+                       return;
+
+               num_commits++;
+               memset(blame_p, 0, sizeof(int) * num_blame_lines);
+               new_lines_len = 0;
+               num_parents = 0;
+               for (parents = commit->parents;
+                    parents != NULL; parents = parents->next)
+                       num_parents++;
+
+               if(num_parents == 0)
+                       *initial = commit;
+
+               if(fill_util_info(commit, path))
+                       continue;
+
+               alloc_line_map(commit);
+               util = commit->object.util;
+
+               for (parents = commit->parents;
+                    parents != NULL; parents = parents->next) {
+                       struct commit *parent = parents->item;
+                       struct patch *patch;
+
+                       if (parse_commit(parent) < 0)
+                               die("parse_commit error");
+
+                       if (DEBUG)
+                               printf("parent: %s\n",
+                                      sha1_to_hex(parent->object.sha1));
+
+                       if(fill_util_info(parent, path)) {
+                               num_parents--;
+                               continue;
+                       }
+
+                       patch = get_patch(parent, commit);
+                        alloc_line_map(parent);
+                        fill_line_map(parent, commit, patch);
+
+                        for (i = 0; i < patch->num; i++) {
+                            int l;
+                            for (l = 0; l < patch->chunks[i].len2; l++) {
+                                int mapped_line =
+                                    map_line(commit, patch->chunks[i].off2 + l);
+                                if (mapped_line != -1) {
+                                    blame_p[mapped_line]++;
+                                    if (blame_p[mapped_line] == num_parents)
+                                        new_lines[new_lines_len++] = mapped_line;
+                                }
+                            }
+                       }
+                        free_patch(patch);
+               }
+
+               if (DEBUG)
+                       printf("parents: %d\n", num_parents);
+
+               for (i = 0; i < new_lines_len; i++) {
+                       int mapped_line = new_lines[i];
+                       if (blame_lines[mapped_line] == NULL) {
+                               blame_lines[mapped_line] = commit;
+                               lines_left--;
+                               if (DEBUG)
+                                       printf("blame: mapped: %d i: %d\n",
+                                              mapped_line, i);
+                       }
+               }
+       } while ((commit = get_revision(rev)) != NULL);
 }
 
 int main(int argc, const char **argv)
 {
-    unsigned char sha1[20];
-    struct commit *commit;
-    const char* filename;
-    int i;
-
-    setup_git_directory();
-
-    if (argc != 3)
-        die("Usage: blame commit-ish file");
-
-    if (get_sha1(argv[1], sha1))
-        die("get_sha1 failed");
-
-    commit = lookup_commit_reference(sha1);
-
-    filename = argv[2];
-
-    struct commit_list* list = get_commit_list(commit, filename);
-    sort_in_topological_order(&list, 1);
-
-    if(fill_util_info(commit, filename)) {
-        printf("%s not found in %s\n", filename, argv[1]);
-        return 0;
-    }
-    alloc_line_map(commit);
-
-    struct util_info* util = commit->object.util;
-    num_blame_lines = util->num_lines;
-    blame_lines = xmalloc(sizeof(struct commit*)*num_blame_lines);
-
-
-    for(i = 0; i < num_blame_lines; i++) {
-        blame_lines[i] = commit;
-
-        ((struct util_info*) commit->object.util)->line_map[i] = i;
-    }
-
-    process_commits(list, filename);
-
-    for(i = 0; i < num_blame_lines; i++) {
-        printf("%d %s\n", i+1-1, sha1_to_hex(blame_lines[i]->object.sha1));
-//        printf("%d %s\n", i+1-1, find_unique_abbrev(blame_lines[i]->object.sha1, 6));
-    }
-
-    if(DEBUG) {
-        printf("num get patch: %d\n", num_get_patch);
-        printf("num commits: %d\n", num_commits);
-    }
-
-    return 0;
+       int i;
+       struct commit *initial = NULL;
+       unsigned char sha1[20];
+       const char* filename;
+       int num_args;
+       const char* args[10];
+       struct rev_info rev;
+
+       setup_git_directory();
+
+       if (argc != 3)
+               die("Usage: blame commit-ish file");
+
+
+       filename = argv[2];
+
+       {
+               struct commit* commit;
+               if (get_sha1(argv[1], sha1))
+                       die("get_sha1 failed");
+               commit = lookup_commit_reference(sha1);
+
+               if (fill_util_info(commit, filename)) {
+                       printf("%s not found in %s\n", filename, argv[1]);
+                       return 1;
+               }
+       }
+
+       num_args = 0;
+       args[num_args++] = NULL;
+       args[num_args++] = "--topo-order";
+       args[num_args++] = "--remove-empty";
+       args[num_args++] = argv[1];
+       args[num_args++] = "--";
+       args[num_args++] = filename;
+       args[num_args] = NULL;
+
+       setup_revisions(num_args, args, &rev, "HEAD");
+       prepare_revision_walk(&rev);
+       process_commits(&rev, filename, &initial);
+
+       for (i = 0; i < num_blame_lines; i++) {
+               struct commit *c = blame_lines[i];
+               if (!c)
+                       c = initial;
+
+               printf("%d %.8s\n", i, sha1_to_hex(c->object.sha1));
+// printf("%d %s\n", i, find_unique_abbrev(blame_lines[i]->object.sha1, 6));
+       }
+
+       if (DEBUG) {
+               printf("num get patch: %d\n", num_get_patch);
+               printf("num commits: %d\n", num_commits);
+               printf("patch time: %f\n", patch_time / 1000000.0);
+               printf("initial: %s\n", sha1_to_hex(initial->object.sha1));
+       }
+
+       return 0;
 }
index 8f23477..5135e36 100644 (file)
 ;;  - hook into file save (after-save-hook)
 ;;  - diff against other branch
 ;;  - renaming files from the status buffer
-;;  - support for appending signed-off-by
 ;;  - creating tags
 ;;  - fetch/pull
 ;;  - switching branches
 ;;  - revlist browser
 ;;  - git-show-branch browser
-;;  - customize support
 ;;  - menus
 ;;
 
 (require 'ewoc)
 
 
-;;;; Faces
+;;;; Customizations
 ;;;; ------------------------------------------------------------
 
+(defgroup git nil
+  "Git user interface")
+
+(defcustom git-committer-name nil
+  "User name to use for commits.
+The default is to fall back to `add-log-full-name' and then `user-full-name'."
+  :group 'git
+  :type '(choice (const :tag "Default" nil)
+                 (string :tag "Name")))
+
+(defcustom git-committer-email nil
+  "Email address to use for commits.
+The default is to fall back to `add-log-mailing-address' and then `user-mail-address'."
+  :group 'git
+  :type '(choice (const :tag "Default" nil)
+                 (string :tag "Email")))
+
+(defcustom git-commits-coding-system 'utf-8
+  "Default coding system for the log message of git commits."
+  :group 'git
+  :type 'coding-system)
+
+(defcustom git-append-signed-off-by nil
+  "Whether to append a Signed-off-by line to the commit message before editing."
+  :group 'git
+  :type 'boolean)
+
+(defcustom git-per-dir-ignore-file ".gitignore"
+  "Name of the per-directory ignore file."
+  :group 'git
+  :type 'string)
+
 (defface git-status-face
   '((((class color) (background light)) (:foreground "purple")))
-  "Git mode face used to highlight added and modified files.")
+  "Git mode face used to highlight added and modified files."
+  :group 'git)
 
 (defface git-unmerged-face
   '((((class color) (background light)) (:foreground "red" :bold t)))
-  "Git mode face used to highlight unmerged files.")
+  "Git mode face used to highlight unmerged files."
+  :group 'git)
 
 (defface git-unknown-face
   '((((class color) (background light)) (:foreground "goldenrod" :bold t)))
-  "Git mode face used to highlight unknown files.")
+  "Git mode face used to highlight unknown files."
+  :group 'git)
 
 (defface git-uptodate-face
   '((((class color) (background light)) (:foreground "grey60")))
-  "Git mode face used to highlight up-to-date files.")
+  "Git mode face used to highlight up-to-date files."
+  :group 'git)
 
 (defface git-ignored-face
   '((((class color) (background light)) (:foreground "grey60")))
-  "Git mode face used to highlight ignored files.")
+  "Git mode face used to highlight ignored files."
+  :group 'git)
 
 (defface git-mark-face
   '((((class color) (background light)) (:foreground "red" :bold t)))
-  "Git mode face used for the file marks.")
+  "Git mode face used for the file marks."
+  :group 'git)
 
 (defface git-header-face
   '((((class color) (background light)) (:foreground "blue")))
-  "Git mode face used for commit headers.")
+  "Git mode face used for commit headers."
+  :group 'git)
 
 (defface git-separator-face
   '((((class color) (background light)) (:foreground "brown")))
-  "Git mode face used for commit separator.")
+  "Git mode face used for commit separator."
+  :group 'git)
 
 (defface git-permission-face
   '((((class color) (background light)) (:foreground "green" :bold t)))
-  "Git mode face used for permission changes.")
-
-(defvar git-committer-name nil
-  "*User name to use for commits.
-If not set, fall back to `add-log-full-name' and then `user-full-name'.")
-
-(defvar git-committer-email nil
-  "*Email address to use for commits.
-If not set, fall back to `add-log-mailing-address' and then `user-mail-address'.")
-
-(defvar git-commits-coding-system 'utf-8
-  "Default coding system for git commits.")
-
-(defconst git-log-msg-separator "--- log message follows this line ---")
-
-(defconst git-per-dir-ignore-file ".gitignore"
-  "Name of the per-directory ignore file.")
+  "Git mode face used for permission changes."
+  :group 'git)
 
 
 ;;;; Utilities
 ;;;; ------------------------------------------------------------
 
+(defconst git-log-msg-separator "--- log message follows this line ---")
+
 (defun git-get-env-strings (env)
   "Build a list of NAME=VALUE strings from a list of environment strings."
   (mapcar (lambda (entry) (concat (car entry) "=" (cdr entry))) env))
@@ -213,14 +238,19 @@ If not set, fall back to `add-log-mailing-address' and then `user-mail-address'.
   "Add a file name to the ignore file in its directory."
   (let* ((fullname (expand-file-name file))
          (dir (file-name-directory fullname))
-         (name (file-name-nondirectory fullname)))
+         (name (file-name-nondirectory fullname))
+         (ignore-name (expand-file-name git-per-dir-ignore-file dir))
+         (created (not (file-exists-p ignore-name))))
   (save-window-excursion
-    (set-buffer (find-file-noselect (expand-file-name git-per-dir-ignore-file dir)))
+    (set-buffer (find-file-noselect ignore-name))
     (goto-char (point-max))
     (unless (zerop (current-column)) (insert "\n"))
     (insert name "\n")
     (sort-lines nil (point-min) (point-max))
-    (save-buffer))))
+    (save-buffer))
+  (when created
+    (git-run-command nil nil "update-index" "--info-only" "--add" "--" (file-relative-name ignore-name)))
+  (git-add-status-file (if created 'added 'modified) (file-relative-name ignore-name))))
 
 
 ;;;; Wrappers for basic git commands
@@ -272,7 +302,7 @@ If not set, fall back to `add-log-mailing-address' and then `user-mail-address'.
     (with-current-buffer buffer
       (goto-char (point-min))
       (if
-          (setq log-start (re-search-forward (concat "^" git-log-msg-separator "\n") nil t))
+          (setq log-start (re-search-forward (concat "^" (regexp-quote git-log-msg-separator) "\n") nil t))
           (save-restriction
             (narrow-to-region (point-min) log-start)
             (goto-char (point-min))
@@ -388,9 +418,9 @@ If not set, fall back to `add-log-mailing-address' and then `user-mail-address'.
   (propertize
    (if (or (not old-perm)
            (not new-perm)
-           (eq 0 (logand #O111 (logxor old-perm new-perm))))
+           (eq 0 (logand ?\111 (logxor old-perm new-perm))))
        "  "
-     (if (eq 0 (logand #O111 old-perm)) "+x" "-x"))
+     (if (eq 0 (logand ?\111 old-perm)) "+x" "-x"))
   'face 'git-permission-face))
 
 (defun git-fileinfo-prettyprint (info)
@@ -787,7 +817,8 @@ If not set, fall back to `add-log-mailing-address' and then `user-mail-address'.
   (unless git-status (error "Not in git-status buffer."))
   (let ((buffer (get-buffer-create "*git-commit*"))
         (merge-heads (git-get-merge-heads))
-        (dir default-directory))
+        (dir default-directory)
+        (sign-off git-append-signed-off-by))
     (with-current-buffer buffer
       (when (eq 0 (buffer-size))
         (cd dir)
@@ -804,9 +835,18 @@ If not set, fall back to `add-log-mailing-address' and then `user-mail-address'.
           'face 'git-header-face)
          (propertize git-log-msg-separator 'face 'git-separator-face)
          "\n")
-        (when (and merge-heads (file-readable-p ".git/MERGE_MSG"))
-          (insert-file-contents ".git/MERGE_MSG"))))
-      (log-edit #'git-do-commit nil #'git-log-edit-files buffer)))
+        (cond ((and merge-heads (file-readable-p ".git/MERGE_MSG"))
+               (insert-file-contents ".git/MERGE_MSG"))
+              (sign-off
+               (insert (format "\n\nSigned-off-by: %s <%s>\n"
+                               (git-get-committer-name) (git-get-committer-email)))))))
+    (let ((log-edit-font-lock-keywords
+           `(("^\\(Author:\\|Date:\\|Parent:\\|Signed-off-by:\\)\\(.*\\)"
+              (1 font-lock-keyword-face)
+              (2 font-lock-function-name-face))
+             (,(concat "^\\(" (regexp-quote git-log-msg-separator) "\\)$")
+              (1 font-lock-comment-face)))))
+      (log-edit #'git-do-commit nil #'git-log-edit-files buffer))))
 
 (defun git-find-file ()
   "Visit the current file in its own buffer."
@@ -891,7 +931,7 @@ If not set, fall back to `add-log-mailing-address' and then `user-mail-address'.
     (define-key map "d"    diff-map)
     (define-key map "="   'git-diff-file)
     (define-key map "f"   'git-find-file)
-    (define-key map [RET] 'git-find-file)
+    (define-key map "\r"  'git-find-file)
     (define-key map "g"   'git-refresh-status)
     (define-key map "i"   'git-ignore-file)
     (define-key map "l"   'git-log-file)
@@ -937,6 +977,7 @@ Commands:
     (erase-buffer)
   (let ((status (ewoc-create 'git-fileinfo-prettyprint "" "")))
     (set (make-local-variable 'git-status) status))
+  (set (make-local-variable 'list-buffers-directory) default-directory)
   (run-hooks 'git-status-mode-hook)))
 
 (defun git-status (dir)
@@ -946,8 +987,8 @@ Commands:
   (if (file-directory-p (concat (file-name-as-directory dir) ".git"))
       (let ((buffer (create-file-buffer (expand-file-name "*git-status*" dir))))
         (switch-to-buffer buffer)
-        (git-status-mode)
         (cd dir)
+        (git-status-mode)
         (git-refresh-status)
         (goto-char (point-min)))
     (message "%s is not a git working tree." dir)))
diff --git a/contrib/emacs/vc-git.el b/contrib/emacs/vc-git.el
new file mode 100644 (file)
index 0000000..2453cdc
--- /dev/null
@@ -0,0 +1,135 @@
+;;; vc-git.el --- VC backend for the git version control system
+
+;; Copyright (C) 2006 Alexandre Julliard
+
+;; This program is free software; you can redistribute it and/or
+;; modify it under the terms of the GNU General Public License as
+;; published by the Free Software Foundation; either version 2 of
+;; the License, or (at your option) any later version.
+;;
+;; This program is distributed in the hope that it will be
+;; useful, but WITHOUT ANY WARRANTY; without even the implied
+;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+;; PURPOSE.  See the GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public
+;; License along with this program; if not, write to the Free
+;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+;; MA 02111-1307 USA
+
+;;; Commentary:
+
+;; This file contains a VC backend for the git version control
+;; system.
+;;
+;; To install: put this file on the load-path and add GIT to the list
+;; of supported backends in `vc-handled-backends'.
+;;
+;; TODO
+;;  - changelog generation
+;;  - working with revisions other than HEAD
+;;
+
+(defvar git-commits-coding-system 'utf-8
+  "Default coding system for git commits.")
+
+(defun vc-git--run-command-string (file &rest args)
+  "Run a git command on FILE and return its output as string."
+  (let* ((ok t)
+         (str (with-output-to-string
+                (with-current-buffer standard-output
+                  (unless (eq 0 (apply #'call-process "git" nil '(t nil) nil
+                                       (append args (list (file-relative-name file)))))
+                    (setq ok nil))))))
+    (and ok str)))
+
+(defun vc-git--run-command (file &rest args)
+  "Run a git command on FILE, discarding any output."
+  (let ((name (file-relative-name file)))
+    (eq 0 (apply #'call-process "git" nil (get-buffer "*Messages") nil (append args (list name))))))
+
+(defun vc-git-registered (file)
+  "Check whether FILE is registered with git."
+  (with-temp-buffer
+    (let* ((dir (file-name-directory file))
+           (name (file-relative-name file dir)))
+      (when dir (cd dir))
+      (and (eq 0 (call-process "git" nil '(t nil) nil "ls-files" "-c" "-z" "--" name))
+           (let ((str (buffer-string)))
+             (and (> (length str) (length name))
+                  (string= (substring str 0 (1+ (length name))) (concat name "\0"))))))))
+
+(defun vc-git-state (file)
+  "git-specific version of `vc-state'."
+  (let ((diff (vc-git--run-command-string file "diff-index" "-z" "HEAD" "--")))
+    (if (and diff (string-match ":[0-7]\\{6\\} [0-7]\\{6\\} [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} [ADMU]\0[^\0]+\0" diff))
+        'edited
+      'up-to-date)))
+
+(defun vc-git-workfile-version (file)
+  "git-specific version of `vc-workfile-version'."
+  (let ((str (with-output-to-string
+               (with-current-buffer standard-output
+                 (call-process "git" nil '(t nil) nil "symbolic-ref" "HEAD")))))
+    (if (string-match "^\\(refs/heads/\\)?\\(.+\\)$" str)
+        (match-string 2 str)
+      str)))
+
+(defun vc-git-revert (file &optional contents-done)
+  "Revert FILE to the version stored in the git repository."
+  (if contents-done
+      (vc-git--run-command file "update-index" "--")
+    (vc-git--run-command file "checkout" "HEAD")))
+
+(defun vc-git-checkout-model (file)
+  'implicit)
+
+(defun vc-git-workfile-unchanged-p (file)
+  (let ((sha1 (vc-git--run-command-string file "hash-object" "--"))
+        (head (vc-git--run-command-string file "ls-tree" "-z" "HEAD" "--")))
+    (and head
+         (string-match "[0-7]\\{6\\} blob \\([0-9a-f]\\{40\\}\\)\t[^\0]+\0" head)
+         (string= (car (split-string sha1 "\n")) (match-string 1 head)))))
+
+(defun vc-git-register (file &optional rev comment)
+  "Register FILE into the git version-control system."
+  (vc-git--run-command file "update-index" "--add" "--"))
+
+(defun vc-git-print-log (file)
+  (let ((name (file-relative-name file))
+        (coding-system-for-read git-commits-coding-system))
+    (vc-do-command nil 'async "git" name "rev-list" "--pretty" "HEAD" "--")))
+
+(defun vc-git-diff (file &optional rev1 rev2)
+  (let ((name (file-relative-name file)))
+    (if (and rev1 rev2)
+        (vc-do-command "*vc-diff*" 0 "git" name "diff-tree" "-p" rev1 rev2 "--")
+      (vc-do-command "*vc-diff*" 0 "git" name "diff-index" "-p" (or rev1 "HEAD") "--"))
+    ; git-diff-index doesn't set exit status like diff does
+    (if (vc-git-workfile-unchanged-p file) 0 1)))
+
+(defun vc-git-checkin (file rev comment)
+  (let ((coding-system-for-write git-commits-coding-system))
+    (vc-git--run-command file "commit" "-m" comment "--only" "--")))
+
+(defun vc-git-checkout (file &optional editable rev destfile)
+  (vc-git--run-command file "checkout" (or rev "HEAD")))
+
+(defun vc-git-annotate-command (file buf &optional rev)
+  ; FIXME: rev is ignored
+  (let ((name (file-relative-name file)))
+    (call-process "git" nil buf nil "annotate" name)))
+
+(defun vc-git-annotate-time ()
+  (and (re-search-forward "[0-9a-f]+\t(.*\t\\([0-9]+\\)-\\([0-9]+\\)-\\([0-9]+\\) \\([0-9]+\\):\\([0-9]+\\):\\([0-9]+\\) \\([-+0-9]+\\)\t[0-9]+)" nil t)
+       (vc-annotate-convert-time
+        (apply #'encode-time (mapcar (lambda (match) (string-to-number (match-string match))) '(6 5 4 3 2 1 7))))))
+
+;; Not really useful since we can't do anything with the revision yet
+;;(defun vc-annotate-extract-revision-at-line ()
+;;  (save-excursion
+;;    (move-beginning-of-line 1)
+;;    (and (looking-at "[0-9a-f]+")
+;;         (buffer-substring (match-beginning 0) (match-end 0)))))
+
+(provide 'vc-git)
index 0e092c5..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,28 +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, $_upgrade);
-
-GetOptions(    'revision|r=s' => \$_revision,
-               'no-ignore-externals' => \$_no_ignore_ext,
-               'stdin|' => \$_stdin,
-               'edit|e' => \$_edit,
-               'rmdir' => \$_rmdir,
-               'upgrade' => \$_upgrade,
-               '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++) {
@@ -64,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;
@@ -85,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;
 }
@@ -105,7 +118,6 @@ 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");
@@ -141,7 +153,7 @@ sub rebuild {
 
                # if we merged or otherwise started elsewhere, this is
                # how we break out of it
-               next if (defined $repo_uuid && ($uuid ne $repo_uuid));
+               next if (defined $SVN_UUID && ($uuid ne $SVN_UUID));
                next if (defined $SVN_URL && ($url ne $SVN_URL));
 
                print "r$rev = $c\n";
@@ -150,7 +162,7 @@ sub rebuild {
                                croak "SVN repository location required: $url\n";
                        }
                        $SVN_URL ||= $url;
-                       $repo_uuid ||= setup_git_svn();
+                       $SVN_UUID ||= setup_git_svn();
                        $latest = $rev;
                }
                assert_revision_eq_or_unknown($rev, $c);
@@ -215,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 $!;
@@ -233,6 +242,9 @@ 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;
 }
 
@@ -243,7 +255,7 @@ sub commit {
                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;
                        }
                }
@@ -265,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";
@@ -312,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);
@@ -512,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) {
@@ -523,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);
@@ -610,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') {
@@ -624,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 $!;
                        }
                }
@@ -637,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;
@@ -728,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;
@@ -827,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:
@@ -852,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 $!;
@@ -890,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/) {
@@ -975,6 +1025,41 @@ sub check_upgrade_needed {
        }
 }
 
+# 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:
@@ -999,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 4102deb..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.
@@ -116,8 +116,38 @@ OPTIONS
        They are both passed directly to git-diff-tree see
        git-diff-tree(1) for more information.
 
+ADVANCED OPTIONS
+----------------
+-b<refname>::
+--branch <refname>::
+       Used with 'fetch' or 'commit'.
+
+       This can be used to join arbitrary git branches to remotes/git-svn
+       on new commits where the tree object is equivalent.
+
+       When used with different GIT_SVN_ID values, tags and branches in
+       SVN can be tracked this way, as can some merges where the heads
+       end up having completely equivalent content.  This can even be
+       used to track branches across multiple SVN _repositories_.
+
+       This option may be specified multiple times, once for each
+       branch.
+
+-i<GIT_SVN_ID>::
+--id <GIT_SVN_ID>::
+       This sets GIT_SVN_ID (instead of using the environment).  See
+       the section on "Tracking Multiple Repositories or Branches" for
+       more information on using GIT_SVN_ID.
+
 COMPATIBILITY OPTIONS
 ---------------------
+--upgrade::
+       Only used with the 'rebuild' command.
+
+       Run this if you used an old version of git-svn that used
+       'git-svn-HEAD' instead of 'remotes/git-svn' as the branch
+       for tracking the remote.
+
 --no-ignore-externals::
        Only used with the 'fetch' and 'rebuild' command.
 
@@ -162,7 +192,7 @@ Tracking and contributing to an Subversion managed-project:
        git-svn commit remotes/git-svn..my-branch
 # Something is committed to SVN, pull the latest into your branch::
        git-svn fetch && git pull . remotes/git-svn
-# Append svn:ignore settings to the default git exclude file:
+# Append svn:ignore settings to the default git exclude file::
        git-svn show-ignore >> .git/info/exclude
 
 DESIGN PHILOSOPHY
index de9f3f3..781badb 100755 (executable)
@@ -798,7 +798,7 @@ class GitView:
                        button.set_relief(gtk.RELIEF_NONE)
                        button.set_sensitive(True)
                        button.connect("clicked", self._show_clicked_cb,
-                                       child_id, commit.commit_sha1)
+                                       child_id, commit.commit_sha1, self.encoding)
                        hbox.pack_start(button, expand=False, fill=True)
                        button.show()
 
index 3ee3a0c..058a2aa 100644 (file)
@@ -3,74 +3,11 @@
  * The delta-parsing part is almost straight copy of patch-delta.c
  * which is (C) 2005 Nicolas Pitre <nico@cam.org>.
  */
-#include "cache.h"
-#include "delta.h"
-#include "count-delta.h"
 #include <stdlib.h>
 #include <string.h>
 #include <limits.h>
-
-struct span {
-       struct span *next;
-       unsigned long ofs;
-       unsigned long end;
-};
-
-static void touch_range(struct span **span,
-                       unsigned long ofs, unsigned long end)
-{
-       struct span *e = *span;
-       struct span *p = NULL;
-
-       while (e && e->ofs <= ofs) {
-               again:
-               if (ofs < e->end) {
-                       while (e->end < end) {
-                               if (e->next && e->next->ofs <= end) {
-                                       e->end = e->next->ofs;
-                                       e = e->next;
-                               }
-                               else {
-                                       e->end = end;
-                                       return;
-                               }
-                       }
-                       return;
-               }
-               p = e;
-               e = e->next;
-       }
-       if (e && e->ofs <= end) {
-               e->ofs = ofs;
-               goto again;
-       }
-       else {
-               e = xmalloc(sizeof(*e));
-               e->ofs = ofs;
-               e->end = end;
-               if (p) {
-                       e->next = p->next;
-                       p->next = e;
-               }
-               else {
-                       e->next = *span;
-                       *span = e;
-               }
-       }
-}
-
-static unsigned long count_range(struct span *s)
-{
-       struct span *t;
-       unsigned long sz = 0;
-       while (s) {
-               t = s;
-               sz += s->end - s->ofs;
-               s = s->next;
-               free(t);
-       }
-       return sz;
-}
+#include "delta.h"
+#include "count-delta.h"
 
 /*
  * NOTE.  We do not _interpret_ delta fully.  As an approximation, we
@@ -84,11 +21,10 @@ static unsigned long count_range(struct span *s)
 int count_delta(void *delta_buf, unsigned long delta_size,
                unsigned long *src_copied, unsigned long *literal_added)
 {
-       unsigned long added_literal;
+       unsigned long copied_from_source, added_literal;
        const unsigned char *data, *top;
        unsigned char cmd;
        unsigned long src_size, dst_size, out;
-       struct span *span = NULL;
 
        if (delta_size < DELTA_SIZE_MIN)
                return -1;
@@ -99,7 +35,7 @@ int count_delta(void *delta_buf, unsigned long delta_size,
        src_size = get_delta_hdr_size(&data);
        dst_size = get_delta_hdr_size(&data);
 
-       added_literal = out = 0;
+       added_literal = copied_from_source = out = 0;
        while (data < top) {
                cmd = *data++;
                if (cmd & 0x80) {
@@ -113,7 +49,7 @@ int count_delta(void *delta_buf, unsigned long delta_size,
                        if (cmd & 0x40) cp_size |= (*data++ << 16);
                        if (cp_size == 0) cp_size = 0x10000;
 
-                       touch_range(&span, cp_off, cp_off+cp_size);
+                       copied_from_source += cp_size;
                        out += cp_size;
                } else {
                        /* write literal into dst */
@@ -123,8 +59,6 @@ int count_delta(void *delta_buf, unsigned long delta_size,
                }
        }
 
-       *src_copied = count_range(span);
-
        /* sanity check */
        if (data != top || out != dst_size)
                return -1;
@@ -132,6 +66,7 @@ int count_delta(void *delta_buf, unsigned long delta_size,
        /* delete size is what was _not_ copied from source.
         * edit size is that and literal additions.
         */
+       *src_copied = copied_from_source;
        *literal_added = added_literal;
        return 0;
 }
diff --git a/delta.h b/delta.h
index 00fef0b..a15350d 100644 (file)
--- a/delta.h
+++ b/delta.h
@@ -4,8 +4,7 @@
 /* handling of delta buffers */
 extern void *diff_delta(void *from_buf, unsigned long from_size,
                        void *to_buf, unsigned long to_size,
-                       unsigned long *delta_size, unsigned long max_size,
-                       void **from_index);
+                       unsigned long *delta_size, unsigned long max_size);
 extern void *patch_delta(void *src_buf, unsigned long src_size,
                         void *delta_buf, unsigned long delta_size,
                         unsigned long *dst_size);
index dcd3f55..2ed5984 100644 (file)
 
 #include <stdlib.h>
 #include <string.h>
+#include <zlib.h>
 #include "delta.h"
 
 
+/* block size: min = 16, max = 64k, power of 2 */
+#define BLK_SIZE 16
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+
+#define GR_PRIME 0x9e370001
+#define HASH(v, shift) (((unsigned int)(v) * GR_PRIME) >> (shift))
+
 struct index {
        const unsigned char *ptr;
+       unsigned int val;
        struct index *next;
 };
 
 static struct index ** delta_index(const unsigned char *buf,
                                   unsigned long bufsize,
-                                  unsigned long trg_bufsize)
+                                  unsigned int *hash_shift)
 {
-       unsigned long hsize;
-       unsigned int i, hshift, hlimit, *hash_count;
+       unsigned int hsize, hshift, entries, blksize, i;
        const unsigned char *data;
        struct index *entry, **hash;
        void *mem;
 
        /* determine index hash size */
-       hsize = bufsize / 4;
-       for (i = 8; (1 << i) < hsize && i < 24; i += 2);
+       entries = (bufsize + BLK_SIZE - 1) / BLK_SIZE;
+       hsize = entries / 4;
+       for (i = 4; (1 << i) < hsize && i < 16; i++);
        hsize = 1 << i;
-       hshift = (i - 8) / 2;
+       hshift = 32 - i;
+       *hash_shift = hshift;
 
-       /*
-        * Allocate lookup index.  Note the first hash pointer
-        * is used to store the hash shift value.
-        */
-       mem = malloc((1 + hsize) * sizeof(*hash) + bufsize * sizeof(*entry));
+       /* allocate lookup index */
+       mem = malloc(hsize * sizeof(*hash) + entries * sizeof(*entry));
        if (!mem)
                return NULL;
        hash = mem;
-       *hash++ = (void *)hshift;
-       entry = mem + (1 + hsize) * sizeof(*hash);
+       entry = mem + hsize * sizeof(*hash);
        memset(hash, 0, hsize * sizeof(*hash));
 
-       /* allocate an array to count hash entries */
-       hash_count = calloc(hsize, sizeof(*hash_count));
-       if (!hash_count) {
-               free(hash);
-               return NULL;
-       }
-
-       /* then populate the index */
-       data = buf + bufsize - 2;
-       while (data > buf) {
-               entry->ptr = --data;
-               i = data[0] ^ ((data[1] ^ (data[2] << hshift)) << hshift);
+       /* then populate it */
+       data = buf + entries * BLK_SIZE - BLK_SIZE;
+       blksize = bufsize - (data - buf);
+       while (data >= buf) {
+               unsigned int val = adler32(0, data, blksize);
+               i = HASH(val, hshift);
+               entry->ptr = data;
+               entry->val = val;
                entry->next = hash[i];
                hash[i] = entry++;
-               hash_count[i]++;
+               blksize = BLK_SIZE;
+               data -= BLK_SIZE;
        }
 
-       /*
-        * Determine a limit on the number of entries in the same hash
-        * bucket.  This guard us against patological data sets causing
-        * really bad hash distribution with most entries in the same hash
-        * bucket that would bring us to O(m*n) computing costs (m and n
-        * corresponding to reference and target buffer sizes).
-        *
-        * The more the target buffer is large, the more it is important to
-        * have small entry lists for each hash buckets.  With such a limit
-        * the cost is bounded to something more like O(m+n).
-        */
-       hlimit = (1 << 26) / trg_bufsize;
-       if (hlimit < 16)
-               hlimit = 16;
-
-       /*
-        * Now make sure none of the hash buckets has more entries than
-        * we're willing to test.  Otherwise we short-circuit the entry
-        * list uniformly to still preserve a good repartition across
-        * the reference buffer.
-        */
-       for (i = 0; i < hsize; i++) {
-               if (hash_count[i] < hlimit)
-                       continue;
-               entry = hash[i];
-               do {
-                       struct index *keep = entry;
-                       int skip = hash_count[i] / hlimit / 2;
-                       do {
-                               entry = entry->next;
-                       } while(--skip && entry);
-                       keep->next = entry;
-               } while(entry);
-       }
-       free(hash_count);
-
-       return hash-1;
+       return hash;
 }
 
 /* provide the size of the copy opcode given the block offset and size */
@@ -123,8 +91,7 @@ static struct index ** delta_index(const unsigned char *buf,
 void *diff_delta(void *from_buf, unsigned long from_size,
                 void *to_buf, unsigned long to_size,
                 unsigned long *delta_size,
-                unsigned long max_size,
-                void **from_index)
+                unsigned long max_size)
 {
        unsigned int i, outpos, outsize, inscnt, hash_shift;
        const unsigned char *ref_data, *ref_top, *data, *top;
@@ -133,16 +100,9 @@ void *diff_delta(void *from_buf, unsigned long from_size,
 
        if (!from_size || !to_size)
                return NULL;
-       if (from_index && *from_index) {
-               hash = *from_index;
-       } else {
-               hash = delta_index(from_buf, from_size, to_size);
-               if (!hash)
-                       return NULL;
-               if (from_index)
-                       *from_index = hash;
-       }
-       hash_shift = (unsigned int)(*hash++);
+       hash = delta_index(from_buf, from_size, &hash_shift);
+       if (!hash)
+               return NULL;
 
        outpos = 0;
        outsize = 8192;
@@ -150,8 +110,7 @@ void *diff_delta(void *from_buf, unsigned long from_size,
                outsize = max_size + MAX_OP_SIZE + 1;
        out = malloc(outsize);
        if (!out) {
-               if (!from_index)
-                       free(hash-1);
+               free(hash);
                return NULL;
        }
 
@@ -182,25 +141,29 @@ void *diff_delta(void *from_buf, unsigned long from_size,
 
        while (data < top) {
                unsigned int moff = 0, msize = 0;
-               if (data + 3 <= top) {
-                       i = data[0] ^ ((data[1] ^ (data[2] << hash_shift)) << hash_shift);
-                       for (entry = hash[i]; entry; entry = entry->next) {
-                               const unsigned char *ref = entry->ptr;
-                               const unsigned char *src = data;
-                               unsigned int ref_size = ref_top - ref;
-                               if (ref_size > top - src)
-                                       ref_size = top - src;
-                               if (ref_size > 0x10000)
-                                       ref_size = 0x10000;
-                               if (ref_size <= msize)
+               unsigned int blksize = MIN(top - data, BLK_SIZE);
+               unsigned int val = adler32(0, data, blksize);
+               i = HASH(val, hash_shift);
+               for (entry = hash[i]; entry; entry = entry->next) {
+                       const unsigned char *ref = entry->ptr;
+                       const unsigned char *src = data;
+                       unsigned int ref_size = ref_top - ref;
+                       if (entry->val != val)
+                               continue;
+                       if (ref_size > top - src)
+                               ref_size = top - src;
+                       while (ref_size && *src++ == *ref) {
+                               ref++;
+                               ref_size--;
+                       }
+                       ref_size = ref - entry->ptr;
+                       if (ref_size > msize) {
+                               /* this is our best match so far */
+                               moff = entry->ptr - ref_data;
+                               msize = ref_size;
+                               if (msize >= 0x10000) {
+                                       msize = 0x10000;
                                        break;
-                               if (*ref != *src)
-                                       continue;
-                               while (ref_size-- && *++src == *++ref);
-                               if (msize < ref - entry->ptr) {
-                                       /* this is our best match so far */
-                                       msize = ref - entry->ptr;
-                                       moff = entry->ptr - ref_data;
                                }
                        }
                }
@@ -252,8 +215,7 @@ void *diff_delta(void *from_buf, unsigned long from_size,
                                out = realloc(out, outsize);
                        if (!out) {
                                free(tmp);
-                               if (!from_index)
-                                       free(hash-1);
+                               free(hash);
                                return NULL;
                        }
                }
@@ -262,8 +224,7 @@ void *diff_delta(void *from_buf, unsigned long from_size,
        if (inscnt)
                out[outpos - inscnt - 1] = inscnt;
 
-       if (!from_index)
-               free(hash-1);
+       free(hash);
        *delta_size = outpos;
        return out;
 }
index d03787b..1e6a691 100644 (file)
@@ -1,88 +1,32 @@
 #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)
+#include "delta.h"
+#include "count-delta.h"
+
+static int diffcore_count_changes_1(void *src, unsigned long src_size,
+                                   void *dst, unsigned long dst_size,
+                                   unsigned long delta_limit,
+                                   unsigned long *src_copied,
+                                   unsigned long *literal_added)
 {
-       const unsigned char *eobuf = buf + size;
-       struct linehash *line = NULL;
-       int alloc = 0, used = 0;
+       void *delta;
+       unsigned long delta_size;
+
+       delta = diff_delta(src, src_size,
+                          dst, dst_size,
+                          &delta_size, delta_limit);
+       if (!delta)
+               /* If delta_limit is exceeded, we have too much differences */
+               return -1;
 
-       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++;
+       /* Estimate the edit size by interpreting delta. */
+       if (count_delta(delta, delta_size, src_copied, literal_added)) {
+               free(delta);
+               return -1;
        }
-       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;
+       free(delta);
+       return 0;
 }
 
 int diffcore_count_changes(void *src, unsigned long src_size,
@@ -91,38 +35,9 @@ int diffcore_count_changes(void *src, unsigned long src_size,
                           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;
+       return diffcore_count_changes_1(src, src_size,
+                                       dst, dst_size,
+                                       delta_limit,
+                                       src_copied,
+                                       literal_added);
 }
index 7f9889d..dba4f17 100644 (file)
@@ -18,7 +18,7 @@
 #define MAX_SCORE 60000.0
 #define DEFAULT_RENAME_SCORE 30000 /* rename/copy similarity minimum (50%) */
 #define DEFAULT_BREAK_SCORE  30000 /* minimum for break to happen (50%)*/
-#define DEFAULT_MERGE_SCORE  45000 /* maximum for break-merge to happen (75%)*/
+#define DEFAULT_MERGE_SCORE  48000 /* maximum for break-merge to happen (80%)*/
 
 #define MINIMUM_BREAK_SIZE     400 /* do not break a file smaller than this */
 
index ab133fb..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
 
index cd476c7..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,9 +28,10 @@ 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=s" => \$rev_file);
@@ -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 {
@@ -411,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 abae4e7..7d3f78e 100755 (executable)
@@ -87,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>)
 {
@@ -139,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;
@@ -152,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} ) )
@@ -160,6 +199,8 @@ sub req_Root
     } else {
         $log->nofile();
     }
+
+    return 1;
 }
 
 # Global_option option \n
@@ -579,6 +620,11 @@ sub req_co
     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 checkout: Updating $checkout_path\n";
@@ -586,6 +632,43 @@ sub req_co
     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} )
     {
         # Don't want to check out deleted files
@@ -593,6 +676,17 @@ 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";
 
@@ -604,24 +698,10 @@ sub req_co
             print "M U $checkout_path/$git->{name}\n";
         }
 
-       if (length($git->{dir}) && $git->{dir} ne './' 
-           && $git->{dir} ne $lastdir && !exists($seendirs{$git->{dir}})) {
-
-           # Eclipse seems to need the Clear-sticky command
-           # to prepare the 'Entries' file for the new directory.
-           print "Clear-sticky $checkout_path/$git->{dir}\n";
-           print $state->{CVSROOT} . "/$module/$git->{dir}\n";
-           print "Clear-static-directory $checkout_path/$git->{dir}\n";
-           print $state->{CVSROOT} . "/$module/$git->{dir}\n";
-           print "E cvs checkout: Updating /$checkout_path/$git->{dir}\n";
-           $lastdir = $git->{dir};
-           $seendirs{$git->{dir}} = 1;
-       }
-
-        # 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";
+       # 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} ) and $git->{dir} ne "./" ? $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";
@@ -862,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";
index fe9c40e..75aa8fe 100755 (executable)
@@ -62,9 +62,17 @@ 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;
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:
diff --git a/git.c b/git.c
index bf68dac..a547dbd 100644 (file)
--- a/git.c
+++ b/git.c
@@ -264,20 +264,7 @@ static int cmd_log(int argc, char **argv, char **envp)
        argc = setup_revisions(argc, argv, &rev, "HEAD");
        while (1 < argc) {
                char *arg = argv[1];
-               /* accept -<digit>, like traditilnal "head" */
-               if ((*arg == '-') && isdigit(arg[1])) {
-                       rev.max_count = atoi(arg + 1);
-               }
-               else if (!strcmp(arg, "-n")) {
-                       if (argc < 2)
-                               die("-n requires an argument");
-                       rev.max_count = atoi(argv[2]);
-                       argc--; argv++;
-               }
-               else if (!strncmp(arg,"-n",2)) {
-                       rev.max_count = atoi(arg + 2);
-               }
-               else if (!strncmp(arg, "--pretty", 8)) {
+               if (!strncmp(arg, "--pretty", 8)) {
                        commit_format = get_commit_format(arg + 8);
                        if (commit_format == CMIT_FMT_ONELINE)
                                commit_prefix = "";
index d6a3463..136a7f5 100644 (file)
@@ -204,7 +204,7 @@ static void *delta_against(void *buf, unsigned long size, struct object_entry *e
        if (!otherbuf)
                die("unable to read %s", sha1_to_hex(entry->delta->sha1));
         delta_buf = diff_delta(otherbuf, othersize,
-                              buf, size, &delta_size, 0, NULL);
+                              buf, size, &delta_size, 0);
         if (!delta_buf || delta_size != entry->delta_size)
                die("delta size changed");
         free(buf);
@@ -810,7 +810,6 @@ static int type_size_sort(const struct object_entry *a, const struct object_entr
 struct unpacked {
        struct object_entry *entry;
        void *data;
-       void **delta_index;
 };
 
 /*
@@ -892,8 +891,7 @@ static int try_delta(struct unpacked *cur, struct unpacked *old, unsigned max_de
        if (sizediff >= max_size)
                return -1;
        delta_buf = diff_delta(old->data, oldsize,
-                              cur->data, size, &delta_size,
-                              max_size, old->delta_index);
+                              cur->data, size, &delta_size, max_size);
        if (!delta_buf)
                return 0;
        cur_entry->delta = old_entry;
@@ -950,7 +948,6 @@ static void find_deltas(struct object_entry **list, int window, int depth)
                         */
                        continue;
 
-               free(n->delta_index);
                free(n->data);
                n->entry = entry;
                n->data = read_sha1_file(entry->sha1, type, &size);
@@ -977,10 +974,8 @@ static void find_deltas(struct object_entry **list, int window, int depth)
        if (progress)
                fputc('\n', stderr);
 
-       for (i = 0; i < window; ++i) {
-               free(array[i].delta_index);
+       for (i = 0; i < window; ++i)
                free(array[i].data);
-       }
        free(array);
 }
 
index c56b572..be29b3f 100644 (file)
@@ -706,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;
 
index 4885871..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;
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
index cae1794..172908a 100755 (executable)
@@ -50,7 +50,8 @@ test_expect_success \
 test_expect_success \
     'merge-setup part 2' \
     'git checkout -b branch2 master &&
-     sed -i -e "s/2A quick brown/4A quick brown lazy dog/" file &&
+     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 \
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 89eb68e..1be8ee0 100644 (file)
@@ -63,7 +63,7 @@ int main(int argc, char *argv[])
        if (argv[1][1] == 'd')
                out_buf = diff_delta(from_buf, from_size,
                                     data_buf, data_size,
-                                    &out_size, 0, NULL);
+                                    &out_size, 0);
        else
                out_buf = patch_delta(from_buf, from_size,
                                      data_buf, data_size,
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;