Merge branch 'jc/repack'
authorJunio C Hamano <junkio@cox.net>
Thu, 29 Jun 2006 06:43:48 +0000 (23:43 -0700)
committerJunio C Hamano <junkio@cox.net>
Thu, 29 Jun 2006 06:43:48 +0000 (23:43 -0700)
* jc/repack:
git-repack: Be careful when updating the same pack as an existing one.

51 files changed:
Documentation/git-grep.txt
Documentation/git-rebase.txt
Documentation/merge-options.txt
builtin-log.c
builtin-mailinfo.c
builtin-rev-parse.c
cache.h
combine-diff.c
commit.c
connect.c
contrib/git-svn/git-svn.perl
contrib/git-svn/t/lib-git-svn.sh
contrib/git-svn/t/t0000-contrib-git-svn.sh
contrib/git-svn/t/t0001-contrib-git-svn-props.sh
contrib/git-svn/t/t0003-graft-branches.sh [new file with mode: 0644]
contrib/git-svn/t/t0004-follow-parent.sh [new file with mode: 0644]
contrib/git-svn/t/t0005-commit-diff.sh [new file with mode: 0644]
daemon.c
describe.c
diff.c
diff.h
git-am.sh
git-checkout.sh
git-clone.sh
git-commit.sh
git-cvsimport.perl [changed mode: 0644->0755]
git-merge.sh
git-pull.sh
git-quiltimport.sh
git-rebase.sh
git-reset.sh
git.c
http-push.c
imap-send.c
merge-index.c
pkt-line.h
quote.c
sha1_file.c
t/README
t/t3401-rebase-partial.sh
t/t3403-rebase-skip.sh [new file with mode: 0755]
t/t4014-format-patch.sh [new file with mode: 0755]
t/t7201-co.sh [new file with mode: 0755]
upload-pack.c
xdiff/xdiff.h
xdiff/xdiffi.c
xdiff/xdiffi.h
xdiff/xmacros.h
xdiff/xprepare.c
xdiff/xutils.c
xdiff/xutils.h
index 7b810dfda760a01c78667015f7c6214fcaeceae6..62a8e7f222d480046beee5cf735fa92821e26f8d 100644 (file)
@@ -16,7 +16,7 @@ SYNOPSIS
           [-n] [-l | --files-with-matches] [-L | --files-without-match]
           [-c | --count]
           [-A <post-context>] [-B <pre-context>] [-C <context>]
-          [-f <file>] [-e <pattern>]
+          [-f <file>] [-e] <pattern>
           [<tree>...]
           [--] [<path>...]
 
@@ -71,6 +71,11 @@ OPTIONS
 -f <file>::
        Read patterns from <file>, one per line.
 
+-e::
+       The next parameter is the pattern. This option has to be
+       used for patterns starting with - and should be used in
+       scripts passing user input to grep.
+
 `<tree>...`::
        Search blobs in the trees for specified patterns.
 
index c339c4525c500f29d51063a34f1c7d238b37c2e6..9d7bcaa38cc5c13e29c00ddd18c64202e98cb5f8 100644 (file)
@@ -108,7 +108,6 @@ OPTIONS
 
 --skip::
        Restart the rebasing process by skipping the current patch.
-       This does not work with the --merge option.
 
 --merge::
        Use merging strategies to rebase.  When the recursive (default) merge
index 53cc35590debbb7e4d7dfae536be19e59d038560..182cef54be5963ff0f32a24329ca195607413e29 100644 (file)
@@ -6,6 +6,14 @@
        not autocommit, to give the user a chance to inspect and
        further tweak the merge result before committing.
 
+--squash::
+       Produce the working tree and index state as if a real
+       merge happened, but do not actually make a commit or
+       move the `HEAD`, nor record `$GIT_DIR/MERGE_HEAD` to
+       cause the next `git commit` command to create a merge
+       commit.  This allows you to create a single commit on
+       top of the current branch whose effect is the same as
+       merging another branch (or more in case of an octopus).
 
 -s <strategy>, \--strategy=<strategy>::
        Use the given merge strategy; can be supplied more than
index 44d2d136f585a74e462556a4f756c7a297cadd51..f9515a8a4ac8a53e87b6fb9f73e1964350481bfb 100644 (file)
@@ -160,6 +160,65 @@ static void reopen_stdout(struct commit *commit, int nr, int keep_subject)
        freopen(filename, "w", stdout);
 }
 
+static int get_patch_id(struct commit *commit, struct diff_options *options,
+               unsigned char *sha1)
+{
+       diff_tree_sha1(commit->parents->item->object.sha1, commit->object.sha1,
+                       "", options);
+       diffcore_std(options);
+       return diff_flush_patch_id(options, sha1);
+}
+
+static void get_patch_ids(struct rev_info *rev, struct diff_options *options)
+{
+       struct rev_info check_rev;
+       struct commit *commit;
+       struct object *o1, *o2;
+       unsigned flags1, flags2;
+       unsigned char sha1[20];
+
+       if (rev->pending.nr != 2)
+               die("Need exactly one range.");
+
+       o1 = rev->pending.objects[0].item;
+       flags1 = o1->flags;
+       o2 = rev->pending.objects[1].item;
+       flags2 = o2->flags;
+
+       if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING))
+               die("Not a range.");
+
+       diff_setup(options);
+       options->recursive = 1;
+       if (diff_setup_done(options) < 0)
+               die("diff_setup_done failed");
+
+       /* given a range a..b get all patch ids for b..a */
+       init_revisions(&check_rev);
+       o1->flags ^= UNINTERESTING;
+       o2->flags ^= UNINTERESTING;
+       add_pending_object(&check_rev, o1, "o1");
+       add_pending_object(&check_rev, o2, "o2");
+       prepare_revision_walk(&check_rev);
+
+       while ((commit = get_revision(&check_rev)) != NULL) {
+               /* ignore merges */
+               if (commit->parents && commit->parents->next)
+                       continue;
+
+               if (!get_patch_id(commit, options, sha1))
+                       created_object(sha1, xcalloc(1, sizeof(struct object)));
+       }
+
+       /* reset for next revision walk */
+       clear_commit_marks((struct commit *)o1,
+                       SEEN | UNINTERESTING | SHOWN | ADDED);
+       clear_commit_marks((struct commit *)o2,
+                       SEEN | UNINTERESTING | SHOWN | ADDED);
+       o1->flags = flags1;
+       o2->flags = flags2;
+}
+
 int cmd_format_patch(int argc, const char **argv, char **envp)
 {
        struct commit *commit;
@@ -170,6 +229,8 @@ int cmd_format_patch(int argc, const char **argv, char **envp)
        int numbered = 0;
        int start_number = -1;
        int keep_subject = 0;
+       int ignore_if_in_upstream = 0;
+       struct diff_options patch_id_opts;
        char *add_signoff = NULL;
 
        init_revisions(&rev);
@@ -235,6 +296,8 @@ int cmd_format_patch(int argc, const char **argv, char **envp)
                        rev.mime_boundary = git_version_string;
                else if (!strncmp(argv[i], "--attach=", 9))
                        rev.mime_boundary = argv[i] + 9;
+               else if (!strcmp(argv[i], "--ignore-if-in-upstream"))
+                       ignore_if_in_upstream = 1;
                else
                        argv[j++] = argv[i];
        }
@@ -262,14 +325,25 @@ int cmd_format_patch(int argc, const char **argv, char **envp)
                add_head(&rev);
        }
 
+       if (ignore_if_in_upstream)
+               get_patch_ids(&rev, &patch_id_opts);
+
        if (!use_stdout)
                realstdout = fdopen(dup(1), "w");
 
        prepare_revision_walk(&rev);
        while ((commit = get_revision(&rev)) != NULL) {
+               unsigned char sha1[20];
+
                /* ignore merges */
                if (commit->parents && commit->parents->next)
                        continue;
+
+               if (ignore_if_in_upstream &&
+                               !get_patch_id(commit, &patch_id_opts, sha1) &&
+                               lookup_object(sha1))
+                       continue;
+
                nr++;
                list = realloc(list, nr * sizeof(list[0]));
                list[nr - 1] = commit;
index 821642a7af97e7afa13a187af5ec68c8a67ae93c..3e40747cf57ed4a8f7f20d83510ae08bcfcbd33a 100644 (file)
@@ -165,7 +165,7 @@ static int handle_subject(char *line)
 
 static int slurp_attr(const char *line, const char *name, char *attr)
 {
-       char *ends, *ap = strcasestr(line, name);
+       const char *ends, *ap = strcasestr(line, name);
        size_t sz;
 
        if (!ap) {
index b27a6d382b7540d1d9f6fc98784646458b217af6..5f5ade45aec781df107d4a49a4a554575ac278bd 100644 (file)
@@ -329,7 +329,7 @@ int cmd_rev_parse(int argc, const char **argv, char **envp)
                dotdot = strstr(arg, "..");
                if (dotdot) {
                        unsigned char end[20];
-                       char *next = dotdot + 2;
+                       const char *next = dotdot + 2;
                        const char *this = arg;
                        *dotdot = 0;
                        if (!*next)
diff --git a/cache.h b/cache.h
index 14358a872cdb2d8d663dad389897cfa0731d821d..87199396a05fbbbb695379119535eb9eefae2514 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -181,7 +181,6 @@ extern int assume_unchanged;
 extern int prefer_symlink_refs;
 extern int log_all_ref_updates;
 extern int warn_ambiguous_refs;
-extern int diff_rename_limit_default;
 extern int shared_repository;
 extern const char *apply_default_whitespace;
 
index 64b20cce2427fcf0cd9565f5e24efce78abb0088..22542217ee6aeba5058d02d99b27e20ca894af7f 100644 (file)
@@ -205,7 +205,8 @@ static void consume_line(void *state_, char *line, unsigned long len)
 }
 
 static void combine_diff(const unsigned char *parent, mmfile_t *result_file,
-                        struct sline *sline, int cnt, int n, int num_parent)
+                        struct sline *sline, unsigned int cnt, int n,
+                        int num_parent)
 {
        unsigned int p_lno, lno;
        unsigned long nmask = (1UL << n);
@@ -293,7 +294,7 @@ static unsigned long find_next(struct sline *sline,
                               unsigned long mark,
                               unsigned long i,
                               unsigned long cnt,
-                              int uninteresting)
+                              int look_for_uninteresting)
 {
        /* We have examined up to i-1 and are about to look at i.
         * Find next interesting or uninteresting line.  Here,
@@ -303,7 +304,7 @@ static unsigned long find_next(struct sline *sline,
         * that are surrounded by interesting() ones.
         */
        while (i <= cnt)
-               if (uninteresting
+               if (look_for_uninteresting
                    ? !(sline[i].flag & mark)
                    : (sline[i].flag & mark))
                        return i;
@@ -489,7 +490,7 @@ static int make_hunks(struct sline *sline, unsigned long cnt,
        return has_interesting;
 }
 
-static void show_parent_lno(struct sline *sline, unsigned long l0, unsigned long l1, unsigned long cnt, int n)
+static void show_parent_lno(struct sline *sline, unsigned long l0, unsigned long l1, int n)
 {
        l0 = sline[l0].p_lno[n];
        l1 = sline[l1].p_lno[n];
@@ -523,7 +524,7 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent)
                        rlines--; /* pointing at the last delete hunk */
                for (i = 0; i <= num_parent; i++) putchar(combine_marker);
                for (i = 0; i < num_parent; i++)
-                       show_parent_lno(sline, lno, hunk_end, cnt, i);
+                       show_parent_lno(sline, lno, hunk_end, i);
                printf(" +%lu,%lu ", lno+1, rlines);
                for (i = 0; i <= num_parent; i++) putchar(combine_marker);
                putchar('\n');
@@ -619,18 +620,18 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
                if (0 <= (fd = open(elem->path, O_RDONLY)) &&
                    !fstat(fd, &st)) {
                        int len = st.st_size;
-                       int cnt = 0;
+                       int sz = 0;
 
                        elem->mode = canon_mode(st.st_mode);
                        result_size = len;
                        result = xmalloc(len + 1);
-                       while (cnt < len) {
-                               int done = xread(fd, result+cnt, len-cnt);
+                       while (sz < len) {
+                               int done = xread(fd, result+sz, len-sz);
                                if (done == 0)
                                        break;
                                if (done < 0)
                                        die("read error '%s'", elem->path);
-                               cnt += done;
+                               sz += done;
                        }
                        result[len] = 0;
                }
@@ -645,7 +646,7 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
                        close(fd);
        }
 
-       for (cnt = 0, cp = result; cp - result < result_size; cp++) {
+       for (cnt = 0, cp = result; cp < result + result_size; cp++) {
                if (*cp == '\n')
                        cnt++;
        }
@@ -658,7 +659,7 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
                sline[lno].lost_tail = &sline[lno].lost_head;
                sline[lno].flag = 0;
        }
-       for (lno = 0, cp = result; cp - result < result_size; cp++) {
+       for (lno = 0, cp = result; cp < result + result_size; cp++) {
                if (*cp == '\n') {
                        sline[lno].len = cp - sline[lno].bol;
                        lno++;
@@ -739,9 +740,9 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
        }
        free(result);
 
-       for (i = 0; i < cnt; i++) {
-               if (sline[i].lost_head) {
-                       struct lline *ll = sline[i].lost_head;
+       for (lno = 0; lno < cnt; lno++) {
+               if (sline[lno].lost_head) {
+                       struct lline *ll = sline[lno].lost_head;
                        while (ll) {
                                struct lline *tmp = ll;
                                ll = ll->next;
index 946615d2ad5c364fe63b201aa9a9574737804c38..e51ffa1c6cf5948c0e6c6d0b905fc868f4464ccf 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -236,6 +236,7 @@ static struct commit_graft *lookup_commit_graft(const unsigned char *sha1)
 
 int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size)
 {
+       char *tail = buffer;
        char *bufptr = buffer;
        unsigned char parent[20];
        struct commit_list **pptr;
@@ -245,9 +246,10 @@ int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size)
        if (item->object.parsed)
                return 0;
        item->object.parsed = 1;
-       if (memcmp(bufptr, "tree ", 5))
+       tail += size;
+       if (tail <= bufptr + 5 || memcmp(bufptr, "tree ", 5))
                return error("bogus commit object %s", sha1_to_hex(item->object.sha1));
-       if (get_sha1_hex(bufptr + 5, parent) < 0)
+       if (tail <= bufptr + 45 || get_sha1_hex(bufptr + 5, parent) < 0)
                return error("bad tree pointer in commit %s",
                             sha1_to_hex(item->object.sha1));
        item->tree = lookup_tree(parent);
@@ -257,10 +259,12 @@ int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size)
        pptr = &item->parents;
 
        graft = lookup_commit_graft(item->object.sha1);
-       while (!memcmp(bufptr, "parent ", 7)) {
+       while (bufptr + 48 < tail && !memcmp(bufptr, "parent ", 7)) {
                struct commit *new_parent;
 
-               if (get_sha1_hex(bufptr + 7, parent) || bufptr[47] != '\n')
+               if (tail <= bufptr + 48 ||
+                   get_sha1_hex(bufptr + 7, parent) ||
+                   bufptr[47] != '\n')
                        return error("bad parents in commit %s", sha1_to_hex(item->object.sha1));
                bufptr += 48;
                if (graft)
@@ -543,7 +547,7 @@ static int add_merge_info(enum cmit_fmt fmt, char *buf, const struct commit *com
                const char *hex = abbrev
                        ? find_unique_abbrev(p->object.sha1, abbrev)
                        : sha1_to_hex(p->object.sha1);
-               char *dots = (abbrev && strlen(hex) != 40) ? "..." : "";
+               const char *dots = (abbrev && strlen(hex) != 40) ? "..." : "";
                parent = parent->next;
 
                offset += sprintf(buf + offset, " %s%s", hex, dots);
index db7342e4d26baa50dd944ebf06fb8b66fea59aaa..cb4656d79d3bd2d22f719724f4c393b6ac70f633 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -8,6 +8,7 @@
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <netdb.h>
+#include <signal.h>
 
 static char *server_capabilities = NULL;
 
@@ -329,7 +330,7 @@ static int git_tcp_connect_sock(char *host)
 {
        int sockfd = -1;
        char *colon, *end;
-       char *port = STR(DEFAULT_GIT_PORT);
+       const char *port = STR(DEFAULT_GIT_PORT);
        struct addrinfo hints, *ai0, *ai;
        int gai;
 
@@ -450,8 +451,7 @@ static int git_tcp_connect_sock(char *host)
 #endif /* NO_IPV6 */
 
 
-static void git_tcp_connect(int fd[2],
-                           const char *prog, char *host, char *path)
+static void git_tcp_connect(int fd[2], char *host)
 {
        int sockfd = git_tcp_connect_sock(host);
 
@@ -521,10 +521,9 @@ static int git_use_proxy(const char *host)
        return (git_proxy_command && *git_proxy_command);
 }
 
-static void git_proxy_connect(int fd[2],
-                             const char *prog, char *host, char *path)
+static void git_proxy_connect(int fd[2], char *host)
 {
-       char *port = STR(DEFAULT_GIT_PORT);
+       const char *port = STR(DEFAULT_GIT_PORT);
        char *colon, *end;
        int pipefd[2][2];
        pid_t pid;
@@ -642,9 +641,9 @@ int git_connect(int fd[2], char *url, const char *prog)
                 */
                char *target_host = strdup(host);
                if (git_use_proxy(host))
-                       git_proxy_connect(fd, prog, host, path);
+                       git_proxy_connect(fd, host);
                else
-                       git_tcp_connect(fd, prog, host, path);
+                       git_tcp_connect(fd, host);
                /*
                 * Separate original protocol components prog and path
                 * from extended components with a NUL byte.
index 08c30103f5c247559e15ebd4bb20d7177889ed88..b3d3f479da727241034001491ad0e9b3b999335a 100755 (executable)
@@ -19,6 +19,7 @@
 # make sure the svn binary gives consistent output between locales and TZs:
 $ENV{TZ} = 'UTC';
 $ENV{LC_ALL} = 'C';
+$| = 1; # unbuffer STDOUT
 
 # If SVN:: library support is added, please make the dependencies
 # optional and preserve the capability to use the command-line client.
@@ -34,6 +35,8 @@
 use IPC::Open3;
 use Memoize;
 memoize('revisions_eq');
+memoize('cmt_metadata');
+memoize('get_commit_time');
 
 my ($SVN_PATH, $SVN, $SVN_LOG, $_use_lib);
 $_use_lib = 1 unless $ENV{GIT_SVN_NO_LIB};
@@ -43,7 +46,8 @@
 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, $_cp_similarity, $_cp_remote,
-       $_repack, $_repack_nr, $_repack_flags,
+       $_repack, $_repack_nr, $_repack_flags, $_q,
+       $_message, $_file, $_follow_parent, $_no_metadata,
        $_template, $_shared, $_no_default_regex, $_no_graft_copy,
        $_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit,
        $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m);
 
 my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext,
                'branch|b=s' => \@_branch_from,
+               'follow-parent|follow' => \$_follow_parent,
                'branch-all-refs|B' => \$_branch_all_refs,
                'authors-file|A=s' => \$_authors,
                'repack:i' => \$_repack,
+               'no-metadata' => \$_no_metadata,
+               'quiet|q' => \$_q,
                'repack-flags|repack-args|repack-opts=s' => \$_repack_flags);
 
 my ($_trunk, $_tags, $_branches);
                'tags|t=s' => \$_tags,
                'branches|b=s' => \$_branches );
 my %init_opts = ( 'template=s' => \$_template, 'shared' => \$_shared );
+my %cmt_opts = ( 'edit|e' => \$_edit,
+               'rmdir' => \$_rmdir,
+               'find-copies-harder' => \$_find_copies_harder,
+               'l=i' => \$_l,
+               'copy-similarity|C=i'=> \$_cp_similarity
+);
 
 # yes, 'native' sets "\n".  Patches to fix this for non-*nix systems welcome:
 my %EOL = ( CR => "\015", LF => "\012", CRLF => "\015\012", native => "\012" );
                          " (requires URL argument)",
                          \%init_opts ],
        commit => [ \&commit, "Commit git revisions to SVN",
-                       {       'stdin|' => \$_stdin,
-                               'edit|e' => \$_edit,
-                               'rmdir' => \$_rmdir,
-                               'find-copies-harder' => \$_find_copies_harder,
-                               'l=i' => \$_l,
-                               'copy-similarity|C=i'=> \$_cp_similarity,
-                               %fc_opts,
-                       } ],
+                       {       'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ],
        'show-ignore' => [ \&show_ignore, "Show svn:ignore listings",
                        { 'revision|r=i' => \$_revision } ],
        rebuild => [ \&rebuild, "Rebuild git-svn metadata (after git clone)",
@@ -91,6 +97,8 @@
        'graft-branches' => [ \&graft_branches,
                        'Detect merges/branches from already imported history',
                        { 'merge-rx|m' => \@_opt_m,
+                         'branch|b=s' => \@_branch_from,
+                         'branch-all-refs|B' => \$_branch_all_refs,
                          'no-default-regex' => \$_no_default_regex,
                          'no-graft-copy' => \$_no_graft_copy } ],
        'multi-init' => [ \&multi_init,
                          'show-commit' => \$_show_commit,
                          'authors-file|A=s' => \$_authors,
                        } ],
+       'commit-diff' => [ \&commit_diff, 'Commit a diff between two trees',
+                       { 'message|m=s' => \$_message,
+                         'file|F=s' => \$_file,
+                       %cmt_opts } ],
 );
 
 my $cmd;
 init_vars();
 load_authors() if $_authors;
 load_all_refs() if $_branch_all_refs;
-svn_compat_check();
+svn_compat_check() unless $_use_lib;
 migration_check() unless $cmd =~ /^(?:init|rebuild|multi-init)$/;
 $cmd{$cmd}->[0]->(@ARGV);
 exit 0;
@@ -379,7 +391,8 @@ sub fetch_lib {
                        # performance sucks with it enabled, so it's much
                        # faster to fetch revision ranges instead of relying
                        # on the limiter.
-                       $SVN_LOG->get_log( '/'.$SVN_PATH, $min, $max, 0, 1, 1,
+                       libsvn_get_log($SVN_LOG, '/'.$SVN_PATH,
+                                       $min, $max, 0, 1, 1,
                                sub {
                                        my $log_msg;
                                        if ($last_commit) {
@@ -479,11 +492,7 @@ sub commit_lib {
        my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : ();
        my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$";
 
-       if (defined $LC_ALL) {
-               $ENV{LC_ALL} = $LC_ALL;
-       } else {
-               delete $ENV{LC_ALL};
-       }
+       set_svn_commit_env();
        foreach my $c (@revs) {
                my $log_msg = get_commit_message($c, $commit_msg);
 
@@ -589,13 +598,14 @@ sub graft_branches {
        my $l_map = read_url_paths();
        my @re = map { qr/$_/is } @_opt_m if @_opt_m;
        unless ($_no_default_regex) {
-               push @re, (     qr/\b(?:merge|merging|merged)\s+(\S.+)/is,
-                               qr/\b(?:from|of)\s+(\S.+)/is );
+               push @re, (qr/\b(?:merge|merging|merged)\s+with\s+([\w\.\-]+)/i,
+                       qr/\b(?:merge|merging|merged)\s+([\w\.\-]+)/i,
+                       qr/\b(?:from|of)\s+([\w\.\-]+)/i );
        }
        foreach my $u (keys %$l_map) {
                if (@re) {
                        foreach my $p (keys %{$l_map->{$u}}) {
-                               graft_merge_msg($grafts,$l_map,$u,$p);
+                               graft_merge_msg($grafts,$l_map,$u,$p,@re);
                        }
                }
                unless ($_no_graft_copy) {
@@ -606,6 +616,7 @@ sub graft_branches {
                        }
                }
        }
+       graft_tree_joins($grafts);
 
        write_grafts($grafts, $comments, $gr_file);
        unlink "$gr_file~$gr_sha1" if $gr_sha1;
@@ -716,6 +727,55 @@ sub show_log {
        print '-' x72,"\n" unless $_incremental || $_oneline;
 }
 
+sub commit_diff_usage {
+       print STDERR "Usage: $0 commit-diff <tree-ish> <tree-ish> [<URL>]\n";
+       exit 1
+}
+
+sub commit_diff {
+       if (!$_use_lib) {
+               print STDERR "commit-diff must be used with SVN libraries\n";
+               exit 1;
+       }
+       my $ta = shift or commit_diff_usage();
+       my $tb = shift or commit_diff_usage();
+       if (!eval { $SVN_URL = shift || file_to_s("$GIT_SVN_DIR/info/url") }) {
+               print STDERR "Needed URL or usable git-svn id command-line\n";
+               commit_diff_usage();
+       }
+       if (defined $_message && defined $_file) {
+               print STDERR "Both --message/-m and --file/-F specified ",
+                               "for the commit message.\n",
+                               "I have no idea what you mean\n";
+               exit 1;
+       }
+       if (defined $_file) {
+               $_message = file_to_s($_message);
+       } else {
+               $_message ||= get_commit_message($tb,
+                                       "$GIT_DIR/.svn-commit.tmp.$$")->{msg};
+       }
+       my $repo;
+       ($repo, $SVN_PATH) = repo_path_split($SVN_URL);
+       $SVN_LOG ||= libsvn_connect($repo);
+       $SVN ||= libsvn_connect($repo);
+       my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : ();
+       my $ed = SVN::Git::Editor->new({        r => $SVN->get_latest_revnum,
+                                               ra => $SVN, c => $tb,
+                                               svn_path => $SVN_PATH
+                                       },
+                               $SVN->get_commit_editor($_message,
+                                       sub {print "Committed $_[0]\n"},@lock)
+                               );
+       my $mods = libsvn_checkout_tree($ta, $tb, $ed);
+       if (@$mods == 0) {
+               print "No changes\n$ta == $tb\n";
+               $ed->abort_edit;
+       } else {
+               $ed->close_edit;
+       }
+}
+
 ########################### utility functions #########################
 
 sub cmt_showable {
@@ -768,35 +828,19 @@ sub fetch_child_id {
        my $id = shift;
        print "Fetching $id\n";
        my $ref = "$GIT_DIR/refs/remotes/$id";
-       my $ca = file_to_s($ref) if (-r $ref);
-       defined(my $pid = fork) or croak $!;
+       defined(my $pid = open my $fh, '-|') or croak $!;
        if (!$pid) {
+               $_repack = undef;
                $GIT_SVN = $ENV{GIT_SVN_ID} = $id;
                init_vars();
                fetch(@_);
                exit 0;
        }
-       waitpid $pid, 0;
-       croak $? if $?;
-       return unless $_repack || -r $ref;
-
-       my $cb = file_to_s($ref);
-
-       defined($pid = open my $fh, '-|') or croak $!;
-       my $url = file_to_s("$GIT_DIR/svn/$id/info/url");
-       $url = qr/\Q$url\E/;
-       if (!$pid) {
-               exec qw/git-rev-list --pretty=raw/,
-                               $ca ? "$ca..$cb" : $cb or croak $!;
-       }
        while (<$fh>) {
-               if (/^    git-svn-id: $url\@\d+ [a-f0-9\-]+$/) {
-                       check_repack();
-               } elsif (/^    git-svn-id: \S+\@\d+ [a-f0-9\-]+$/) {
-                       last;
-               }
+               print $_;
+               check_repack() if (/^r\d+ = $sha1/);
        }
-       close $fh;
+       close $fh or croak $?;
 }
 
 sub rec_fetch {
@@ -878,6 +922,77 @@ sub common_prefix {
        return '';
 }
 
+# grafts set here are 'stronger' in that they're based on actual tree
+# matches, and won't be deleted from merge-base checking in write_grafts()
+sub graft_tree_joins {
+       my $grafts = shift;
+       map_tree_joins() if (@_branch_from && !%tree_map);
+       return unless %tree_map;
+
+       git_svn_each(sub {
+               my $i = shift;
+               defined(my $pid = open my $fh, '-|') or croak $!;
+               if (!$pid) {
+                       exec qw/git-rev-list --pretty=raw/,
+                                       "refs/remotes/$i" or croak $!;
+               }
+               while (<$fh>) {
+                       next unless /^commit ($sha1)$/o;
+                       my $c = $1;
+                       my ($t) = (<$fh> =~ /^tree ($sha1)$/o);
+                       next unless $tree_map{$t};
+
+                       my $l;
+                       do {
+                               $l = readline $fh;
+                       } until ($l =~ /^committer (?:.+) (\d+) ([\-\+]?\d+)$/);
+
+                       my ($s, $tz) = ($1, $2);
+                       if ($tz =~ s/^\+//) {
+                               $s += tz_to_s_offset($tz);
+                       } elsif ($tz =~ s/^\-//) {
+                               $s -= tz_to_s_offset($tz);
+                       }
+
+                       my ($url_a, $r_a, $uuid_a) = cmt_metadata($c);
+
+                       foreach my $p (@{$tree_map{$t}}) {
+                               next if $p eq $c;
+                               my $mb = eval {
+                                       safe_qx('git-merge-base', $c, $p)
+                               };
+                               next unless ($@ || $?);
+                               if (defined $r_a) {
+                                       # see if SVN says it's a relative
+                                       my ($url_b, $r_b, $uuid_b) =
+                                                       cmt_metadata($p);
+                                       next if (defined $url_b &&
+                                                       defined $url_a &&
+                                                       ($url_a eq $url_b) &&
+                                                       ($uuid_a eq $uuid_b));
+                                       if ($uuid_a eq $uuid_b) {
+                                               if ($r_b < $r_a) {
+                                                       $grafts->{$c}->{$p} = 2;
+                                                       next;
+                                               } elsif ($r_b > $r_a) {
+                                                       $grafts->{$p}->{$c} = 2;
+                                                       next;
+                                               }
+                                       }
+                               }
+                               my $ct = get_commit_time($p);
+                               if ($ct < $s) {
+                                       $grafts->{$c}->{$p} = 2;
+                               } elsif ($ct > $s) {
+                                       $grafts->{$p}->{$c} = 2;
+                               }
+                               # what should we do when $ct == $s ?
+                       }
+               }
+               close $fh or croak $?;
+       });
+}
+
 # this isn't funky-filename safe, but good enough for now...
 sub graft_file_copy_cmd {
        my ($grafts, $l_map, $u) = @_;
@@ -924,7 +1039,7 @@ sub graft_file_copy_lib {
        $SVN::Error::handler = \&libsvn_skip_unknown_revs;
        while (1) {
                my $pool = SVN::Pool->new;
-               $SVN_LOG->get_log( "/$path", $min, $max, 0, 1, 1,
+               libsvn_get_log($SVN_LOG, "/$path", $min, $max, 0, 1, 1,
                        sub {
                                libsvn_graft_file_copies($grafts, $tree_paths,
                                                        $path, @_);
@@ -956,7 +1071,7 @@ sub process_merge_msg_matches {
                my $re = qr/\Q$w\E/i;
                foreach (keys %{$l_map->{$u}}) {
                        if (/$re/) {
-                               push @strong, $_;
+                               push @strong, $l_map->{$u}->{$_};
                                last;
                        }
                }
@@ -965,7 +1080,7 @@ sub process_merge_msg_matches {
                $re = qr/\Q$w\E/i;
                foreach (keys %{$l_map->{$u}}) {
                        if (/$re/) {
-                               push @strong, $_;
+                               push @strong, $l_map->{$u}->{$_};
                                last;
                        }
                }
@@ -978,7 +1093,7 @@ sub process_merge_msg_matches {
                return unless defined $rev;
        }
        foreach my $m (@strong) {
-               my ($r0, $s0) = find_rev_before($rev, $m);
+               my ($r0, $s0) = find_rev_before($rev, $m, 1);
                $grafts->{$c->{c}}->{$s0} = 1 if defined $s0;
        }
 }
@@ -1340,12 +1455,12 @@ sub libsvn_checkout_tree {
        foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {
                my $f = $m->{chg};
                if (defined $o{$f}) {
-                       $ed->$f($m);
+                       $ed->$f($m, $_q);
                } else {
                        croak "Invalid change type: $f\n";
                }
        }
-       $ed->rmdirs if $_rmdir;
+       $ed->rmdirs($_q) if $_rmdir;
        return $mods;
 }
 
@@ -1392,7 +1507,6 @@ sub get_commit_message {
        my %log_msg = ( msg => '' );
        open my $msg, '>', $commit_msg or croak $!;
 
-       print "commit: $commit\n";
        chomp(my $type = `git-cat-file -t $commit`);
        if ($type eq 'commit') {
                my $pid = open my $msg_fh, '-|';
@@ -1429,6 +1543,14 @@ sub get_commit_message {
        return \%log_msg;
 }
 
+sub set_svn_commit_env {
+       if (defined $LC_ALL) {
+               $ENV{LC_ALL} = $LC_ALL;
+       } else {
+               delete $ENV{LC_ALL};
+       }
+}
+
 sub svn_commit_tree {
        my ($last, $commit) = @_;
        my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$";
@@ -1436,11 +1558,7 @@ sub svn_commit_tree {
        my ($oneline) = ($log_msg->{msg} =~ /([^\n\r]+)/);
        print "Committing $commit: $oneline\n";
 
-       if (defined $LC_ALL) {
-               $ENV{LC_ALL} = $LC_ALL;
-       } else {
-               delete $ENV{LC_ALL};
-       }
+       set_svn_commit_env();
        my @ci_output = safe_qx(qw(svn commit -F),$commit_msg);
        $ENV{LC_ALL} = 'C';
        unlink $commit_msg;
@@ -1789,8 +1907,34 @@ sub git_commit {
                croak $? if $?;
                restore_index($index);
        }
+
+       # just in case we clobber the existing ref, we still want that ref
+       # as our parent:
+       if (my $cur = eval { file_to_s("$GIT_DIR/refs/remotes/$GIT_SVN") }) {
+               push @tmp_parents, $cur;
+       }
+
        if (exists $tree_map{$tree}) {
-               push @tmp_parents, @{$tree_map{$tree}};
+               foreach my $p (@{$tree_map{$tree}}) {
+                       my $skip;
+                       foreach (@tmp_parents) {
+                               # see if a common parent is found
+                               my $mb = eval {
+                                       safe_qx('git-merge-base', $_, $p)
+                               };
+                               next if ($@ || $?);
+                               $skip = 1;
+                               last;
+                       }
+                       next if $skip;
+                       my ($url_p, $r_p, $uuid_p) = cmt_metadata($p);
+                       next if (($SVN_UUID eq $uuid_p) &&
+                                               ($log_msg->{revision} > $r_p));
+                       next if (defined $url_p && defined $SVN_URL &&
+                                               ($SVN_UUID eq $uuid_p) &&
+                                               ($url_p eq $SVN_URL));
+                       push @tmp_parents, $p;
+               }
        }
        foreach (@tmp_parents) {
                next if $seen_parent{$_};
@@ -1800,31 +1944,26 @@ sub git_commit {
                last if @exec_parents > 16;
        }
 
-       defined(my $pid = open my $out_fh, '-|') or croak $!;
-       if ($pid == 0) {
-               my $msg_fh = IO::File->new_tmpfile or croak $!;
-               print $msg_fh $log_msg->{msg}, "\ngit-svn-id: ",
-                                       "$SVN_URL\@$log_msg->{revision}",
+       set_commit_env($log_msg);
+       my @exec = ('git-commit-tree', $tree);
+       push @exec, '-p', $_  foreach @exec_parents;
+       defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec))
+                                                               or croak $!;
+       print $msg_fh $log_msg->{msg} or croak $!;
+       unless ($_no_metadata) {
+               print $msg_fh "\ngit-svn-id: $SVN_URL\@$log_msg->{revision}",
                                        " $SVN_UUID\n" or croak $!;
-               $msg_fh->flush == 0 or croak $!;
-               seek $msg_fh, 0, 0 or croak $!;
-               set_commit_env($log_msg);
-               my @exec = ('git-commit-tree',$tree);
-               push @exec, '-p', $_  foreach @exec_parents;
-               open STDIN, '<&', $msg_fh or croak $!;
-               exec @exec or croak $!;
        }
+       $msg_fh->flush == 0 or croak $!;
+       close $msg_fh or croak $!;
        chomp(my $commit = do { local $/; <$out_fh> });
-       close $out_fh or croak $?;
+       close $out_fh or croak $!;
+       waitpid $pid, 0;
+       croak $? if $?;
        if ($commit !~ /^$sha1$/o) {
-               croak "Failed to commit, invalid sha1: $commit\n";
+               die "Failed to commit, invalid sha1: $commit\n";
        }
-       my @update_ref = ('git-update-ref',"refs/remotes/$GIT_SVN",$commit);
-       if (my $primary_parent = shift @exec_parents) {
-               quiet_run(qw/git-rev-parse --verify/,"refs/remotes/$GIT_SVN^0");
-               push @update_ref, $primary_parent unless $?;
-       }
-       sys(@update_ref);
+       sys('git-update-ref',"refs/remotes/$GIT_SVN",$commit);
        revdb_set($REVDB, $log_msg->{revision}, $commit);
 
        # this output is read via pipe, do not change:
@@ -1909,6 +2048,11 @@ sub safe_qx {
 }
 
 sub svn_compat_check {
+       if ($_follow_parent) {
+               print STDERR 'E: --follow-parent functionality is only ',
+                               "available when SVN libraries are used\n";
+               exit 1;
+       }
        my @co_help = safe_qx(qw(svn co -h));
        unless (grep /ignore-externals/,@co_help) {
                print STDERR "W: Installed svn version does not support ",
@@ -2118,6 +2262,7 @@ sub init_vars {
        $GIT_SVN_INDEX = "$GIT_SVN_DIR/index";
        $SVN_URL = undef;
        $SVN_WC = "$GIT_SVN_DIR/tree";
+       %tree_map = ();
 }
 
 # convert GetOpt::Long specs for use by git-repo-config
@@ -2185,6 +2330,7 @@ sub write_grafts {
                        print $fh $_ foreach @{$comments->{$c}};
                }
                my $p = $grafts->{$c};
+               my %x; # real parents
                delete $p->{$c}; # commits are not self-reproducing...
                my $pid = open my $ch, '-|';
                defined $pid or croak $!;
@@ -2192,13 +2338,41 @@ sub write_grafts {
                        exec(qw/git-cat-file commit/, $c) or croak $!;
                }
                while (<$ch>) {
-                       if (/^parent ([a-f\d]{40})/) {
-                               $p->{$1} = 1;
+                       if (/^parent ($sha1)/) {
+                               $x{$1} = $p->{$1} = 1;
                        } else {
-                               last unless /^\S/i;
+                               last unless /^\S/;
                        }
                }
                close $ch; # breaking the pipe
+
+               # if real parents are the only ones in the grafts, drop it
+               next if join(' ',sort keys %$p) eq join(' ',sort keys %x);
+
+               my (@ip, @jp, $mb);
+               my %del = %x;
+               @ip = @jp = keys %$p;
+               foreach my $i (@ip) {
+                       next if $del{$i} || $p->{$i} == 2;
+                       foreach my $j (@jp) {
+                               next if $i eq $j || $del{$j} || $p->{$j} == 2;
+                               $mb = eval { safe_qx('git-merge-base',$i,$j) };
+                               next unless $mb;
+                               chomp $mb;
+                               next if $x{$mb};
+                               if ($mb eq $j) {
+                                       delete $p->{$i};
+                                       $del{$i} = 1;
+                               } elsif ($mb eq $i) {
+                                       delete $p->{$j};
+                                       $del{$j} = 1;
+                               }
+                       }
+               }
+
+               # if real parents are the only ones in the grafts, drop it
+               next if join(' ',sort keys %$p) eq join(' ',sort keys %x);
+
                print $fh $c, ' ', join(' ', sort keys %$p),"\n";
        }
        if ($comments->{'END'}) {
@@ -2207,6 +2381,28 @@ sub write_grafts {
        close $fh or croak $!;
 }
 
+sub read_url_paths_all {
+       my ($l_map, $pfx, $p) = @_;
+       my @dir;
+       foreach (<$p/*>) {
+               if (-r "$_/info/url") {
+                       $pfx .= '/' if $pfx && $pfx !~ m!/$!;
+                       my $id = $pfx . basename $_;
+                       my $url = file_to_s("$_/info/url");
+                       my ($u, $p) = repo_path_split($url);
+                       $l_map->{$u}->{$p} = $id;
+               } elsif (-d $_) {
+                       push @dir, $_;
+               }
+       }
+       foreach (@dir) {
+               my $x = $_;
+               $x =~ s!^\Q$GIT_DIR\E/svn/!!o;
+               read_url_paths_all($l_map, $x, $_);
+       }
+}
+
+# this one only gets ids that have been imported, not new ones
 sub read_url_paths {
        my $l_map = {};
        git_svn_each(sub { my $x = shift;
@@ -2218,7 +2414,7 @@ sub read_url_paths {
 }
 
 sub extract_metadata {
-       my $id = shift;
+       my $id = shift or return (undef, undef, undef);
        my ($url, $rev, $uuid) = ($id =~ /^git-svn-id:\s(\S+?)\@(\d+)
                                                        \s([a-f\d\-]+)$/x);
        if (!$rev || !$uuid || !$url) {
@@ -2229,6 +2425,31 @@ sub extract_metadata {
        return ($url, $rev, $uuid);
 }
 
+sub cmt_metadata {
+       return extract_metadata((grep(/^git-svn-id: /,
+               safe_qx(qw/git-cat-file commit/, shift)))[-1]);
+}
+
+sub get_commit_time {
+       my $cmt = shift;
+       defined(my $pid = open my $fh, '-|') or croak $!;
+       if (!$pid) {
+               exec qw/git-rev-list --pretty=raw -n1/, $cmt or croak $!;
+       }
+       while (<$fh>) {
+               /^committer\s(?:.+) (\d+) ([\-\+]?\d+)$/ or next;
+               my ($s, $tz) = ($1, $2);
+               if ($tz =~ s/^\+//) {
+                       $s += tz_to_s_offset($tz);
+               } elsif ($tz =~ s/^\-//) {
+                       $s -= tz_to_s_offset($tz);
+               }
+               close $fh;
+               return $s;
+       }
+       die "Can't get commit time for commit: $cmt\n";
+}
+
 sub tz_to_s_offset {
        my ($tz) = @_;
        $tz =~ s/(\d\d)$//;
@@ -2358,8 +2579,8 @@ sub libsvn_load {
        return unless $_use_lib;
        $_use_lib = eval {
                require SVN::Core;
-               if ($SVN::Core::VERSION lt '1.2.1') {
-                       die "Need SVN::Core 1.2.1 or better ",
+               if ($SVN::Core::VERSION lt '1.1.0') {
+                       die "Need SVN::Core 1.1.0 or better ",
                                        "(got $SVN::Core::VERSION) ",
                                        "Falling back to command-line svn\n";
                }
@@ -2392,9 +2613,14 @@ sub libsvn_get_file {
        my $pool = SVN::Pool->new;
        defined($pid = open3($in, $out, '>&STDERR',
                                qw/git-hash-object -w --stdin/)) or croak $!;
-       my ($r, $props) = $SVN->get_file($f, $rev, $in, $pool);
+       # redirect STDOUT for SVN 1.1.x compatibility
+       open my $stdout, '>&', \*STDOUT or croak $!;
+       open STDOUT, '>&', $in or croak $!;
+       my ($r, $props) = $SVN->get_file($f, $rev, \*STDOUT, $pool);
        $in->flush == 0 or croak $!;
+       open STDOUT, '>&', $stdout or croak $!;
        close $in or croak $!;
+       close $stdout or croak $!;
        $pool->clear;
        chomp($hash = do { local $/; <$out> });
        close $out or croak $!;
@@ -2460,6 +2686,7 @@ sub libsvn_fetch {
                my $m = $paths->{$f}->action();
                $f =~ s#^/+##;
                if ($m =~ /^[DR]$/) {
+                       print "\t$m\t$f\n" unless $_q;
                        process_rm($gui, $last_commit, $f);
                        next if $m eq 'D';
                        # 'R' can be file replacements, too, right?
@@ -2468,14 +2695,17 @@ sub libsvn_fetch {
                my $t = $SVN->check_path($f, $rev, $pool);
                if ($t == $SVN::Node::file) {
                        if ($m =~ /^[AMR]$/) {
-                               push @amr, $f;
+                               push @amr, [ $m, $f ];
                        } else {
                                die "Unrecognized action: $m, ($f r$rev)\n";
                        }
                }
                $pool->clear;
        }
-       libsvn_get_file($gui, $_, $rev) foreach (@amr);
+       foreach (@amr) {
+               print "\t$_->[0]\t$_->[1]\n" unless $_q;
+               libsvn_get_file($gui, $_->[1], $rev)
+       }
        close $gui or croak $?;
        return libsvn_log_entry($rev, $author, $date, $msg, [$last_commit]);
 }
@@ -2491,8 +2721,29 @@ sub svn_grab_base_rev {
        chomp(my $c = do { local $/; <$fh> });
        close $fh;
        if (defined $c && length $c) {
-               my ($url, $rev, $uuid) = extract_metadata((grep(/^git-svn-id: /,
-                       safe_qx(qw/git-cat-file commit/, $c)))[-1]);
+               my ($url, $rev, $uuid) = cmt_metadata($c);
+               return ($rev, $c) if defined $rev;
+       }
+       if ($_no_metadata) {
+               my $offset = -41; # from tail
+               my $rl;
+               open my $fh, '<', $REVDB or
+                       die "--no-metadata specified and $REVDB not readable\n";
+               seek $fh, $offset, 2;
+               $rl = readline $fh;
+               defined $rl or return (undef, undef);
+               chomp $rl;
+               while ($c ne $rl && tell $fh != 0) {
+                       $offset -= 41;
+                       seek $fh, $offset, 2;
+                       $rl = readline $fh;
+                       defined $rl or return (undef, undef);
+                       chomp $rl;
+               }
+               my $rev = tell $fh;
+               croak $! if ($rev < -1);
+               $rev =  ($rev - 41) / 41;
+               close $fh or croak $!;
                return ($rev, $c);
        }
        return (undef, undef);
@@ -2527,6 +2778,7 @@ sub libsvn_traverse {
                if ($t == $SVN::Node::dir) {
                        libsvn_traverse($gui, $cwd, $d, $rev);
                } elsif ($t == $SVN::Node::file) {
+                       print "\tA\t$cwd/$d\n" unless $_q;
                        libsvn_get_file($gui, "$cwd/$d", $rev);
                }
        }
@@ -2566,7 +2818,8 @@ sub revisions_eq {
        if ($_use_lib) {
                # should be OK to use Pool here (r1 - r0) should be small
                my $pool = SVN::Pool->new;
-               $SVN->get_log("/$path", $r0, $r1, 0, 1, 1, sub {$nr++},$pool);
+               libsvn_get_log($SVN, "/$path", $r0, $r1,
+                               0, 1, 1, sub {$nr++}, $pool);
                $pool->clear;
        } else {
                my ($url, undef) = repo_path_split($SVN_URL);
@@ -2589,15 +2842,45 @@ sub libsvn_find_parent_branch {
        print STDERR  "Found possible branch point: ",
                                "$branch_from => $svn_path, $r\n";
        $branch_from =~ s#^/##;
-       my $l_map = read_url_paths();
+       my $l_map = {};
+       read_url_paths_all($l_map, '', "$GIT_DIR/svn");
        my $url = $SVN->{url};
        defined $l_map->{$url} or return;
-       my $id = $l_map->{$url}->{$branch_from} or return;
+       my $id = $l_map->{$url}->{$branch_from};
+       if (!defined $id && $_follow_parent) {
+               print STDERR "Following parent: $branch_from\@$r\n";
+               # auto create a new branch and follow it
+               $id = basename($branch_from);
+               $id .= '@'.$r if -r "$GIT_DIR/svn/$id";
+               while (-r "$GIT_DIR/svn/$id") {
+                       # just grow a tail if we're not unique enough :x
+                       $id .= '-';
+               }
+       }
+       return unless defined $id;
+
        my ($r0, $parent) = find_rev_before($r,$id,1);
+       if ($_follow_parent && (!defined $r0 || !defined $parent)) {
+               defined(my $pid = fork) or croak $!;
+               if (!$pid) {
+                       $GIT_SVN = $ENV{GIT_SVN_ID} = $id;
+                       init_vars();
+                       $SVN_URL = "$url/$branch_from";
+                       $SVN_LOG = $SVN = undef;
+                       setup_git_svn();
+                       # we can't assume SVN_URL exists at r+1:
+                       $_revision = "0:$r";
+                       fetch_lib();
+                       exit 0;
+               }
+               waitpid $pid, 0;
+               croak $? if $?;
+               ($r0, $parent) = find_rev_before($r,$id,1);
+       }
        return unless (defined $r0 && defined $parent);
        if (revisions_eq($branch_from, $r0, $r)) {
                unlink $GIT_SVN_INDEX;
-               print STDERR "Found branch parent: $parent\n";
+               print STDERR "Found branch parent: ($GIT_SVN) $parent\n";
                sys(qw/git-read-tree/, $parent);
                return libsvn_fetch($parent, $paths, $rev,
                                        $author, $date, $msg);
@@ -2606,6 +2889,14 @@ sub libsvn_find_parent_branch {
        return undef;
 }
 
+sub libsvn_get_log {
+       my ($ra, @args) = @_;
+       if ($SVN::Core::VERSION le '1.2.0') {
+               splice(@args, 3, 1);
+       }
+       $ra->get_log(@args);
+}
+
 sub libsvn_new_tree {
        if (my $log_entry = libsvn_find_parent_branch(@_)) {
                return $log_entry;
@@ -2639,6 +2930,10 @@ sub find_graft_path_parents {
                my $i = $tree_paths->{$x};
                my ($r, $parent) = find_rev_before($r0, $i, 1);
                if (defined $r && defined $parent && revisions_eq($x,$r,$r0)) {
+                       my ($url_b, undef, $uuid_b) = cmt_metadata($c);
+                       my ($url_a, undef, $uuid_a) = cmt_metadata($parent);
+                       next if ($url_a && $url_b && $url_a eq $url_b &&
+                                                       $uuid_b eq $uuid_a);
                        $grafts->{$c}->{$parent} = 1;
                }
        }
@@ -2820,7 +3115,7 @@ sub url_path {
 }
 
 sub rmdirs {
-       my ($self) = @_;
+       my ($self, $q) = @_;
        my $rm = $self->{rm};
        delete $rm->{''}; # we never delete the url we're tracking
        return unless %$rm;
@@ -2861,6 +3156,7 @@ sub rmdirs {
        foreach my $d (sort { $b =~ tr#/#/# <=> $a =~ tr#/#/# } keys %$rm) {
                $self->close_directory($bat->{$d}, $p);
                my ($dn) = ($d =~ m#^(.*?)/?(?:[^/]+)$#);
+               print "\tD+\t/$d/\n" unless $q;
                $self->SUPER::delete_entry($d, $r, $bat->{$dn}, $p);
                delete $bat->{$d};
        }
@@ -2901,21 +3197,23 @@ sub ensure_path {
 }
 
 sub A {
-       my ($self, $m) = @_;
+       my ($self, $m, $q) = @_;
        my ($dir, $file) = split_path($m->{file_b});
        my $pbat = $self->ensure_path($dir);
        my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
                                        undef, -1);
+       print "\tA\t$m->{file_b}\n" unless $q;
        $self->chg_file($fbat, $m);
        $self->close_file($fbat,undef,$self->{pool});
 }
 
 sub C {
-       my ($self, $m) = @_;
+       my ($self, $m, $q) = @_;
        my ($dir, $file) = split_path($m->{file_b});
        my $pbat = $self->ensure_path($dir);
        my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
                                $self->url_path($m->{file_a}), $self->{r});
+       print "\tC\t$m->{file_a} => $m->{file_b}\n" unless $q;
        $self->chg_file($fbat, $m);
        $self->close_file($fbat,undef,$self->{pool});
 }
@@ -2929,11 +3227,12 @@ sub delete_entry {
 }
 
 sub R {
-       my ($self, $m) = @_;
+       my ($self, $m, $q) = @_;
        my ($dir, $file) = split_path($m->{file_b});
        my $pbat = $self->ensure_path($dir);
        my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
                                $self->url_path($m->{file_a}), $self->{r});
+       print "\tR\t$m->{file_a} => $m->{file_b}\n" unless $q;
        $self->chg_file($fbat, $m);
        $self->close_file($fbat,undef,$self->{pool});
 
@@ -2943,11 +3242,12 @@ sub R {
 }
 
 sub M {
-       my ($self, $m) = @_;
+       my ($self, $m, $q) = @_;
        my ($dir, $file) = split_path($m->{file_b});
        my $pbat = $self->ensure_path($dir);
        my $fbat = $self->open_file($self->repo_path($m->{file_b}),
                                $pbat,$self->{r},$self->{pool});
+       print "\t$m->{chg}\t$m->{file_b}\n" unless $q;
        $self->chg_file($fbat, $m);
        $self->close_file($fbat,undef,$self->{pool});
 }
@@ -2996,9 +3296,10 @@ sub chg_file {
 }
 
 sub D {
-       my ($self, $m) = @_;
+       my ($self, $m, $q) = @_;
        my ($dir, $file) = split_path($m->{file_b});
        my $pbat = $self->ensure_path($dir);
+       print "\tD\t$m->{file_b}\n" unless $q;
        $self->delete_entry($m->{file_b}, $pbat);
 }
 
@@ -3052,6 +3353,16 @@ sub abort_edit {
 }
 ;
 
+# retval of read_url_paths{,_all}();
+$l_map = {
+       # repository root url
+       'https://svn.musicpd.org' => {
+               # repository path               # GIT_SVN_ID
+               'mpd/trunk'             =>      'trunk',
+               'mpd/tags/0.11.5'       =>      'tags/0.11.5',
+       },
+}
+
 Notes:
        I don't trust the each() function on unless I created %hash myself
        because the internal iterator may not have started at base.
index 2843258fc478d9d18d563aa6ac3428e73e431f53..d7f972a0c8fd0163f97c2bc4cd29fe2d1e001002 100644 (file)
@@ -33,7 +33,13 @@ svnrepo=$PWD/svnrepo
 
 set -e
 
-svnadmin create $svnrepo
+if svnadmin create --help | grep fs-type >/dev/null
+then
+       svnadmin create --fs-type fsfs "$svnrepo"
+else
+       svnadmin create "$svnrepo"
+fi
+
 svnrepo="file://$svnrepo/test-git-svn"
 
 
index 443d5183670dbaf837f1aba37384fca084454496..b482bb64c06df7b27f355c53cac4b9017a130c53 100644 (file)
@@ -5,6 +5,16 @@
 
 test_description='git-svn tests'
 GIT_SVN_LC_ALL=$LC_ALL
+
+case "$LC_ALL" in
+*.UTF-8)
+       have_utf8=t
+       ;;
+*)
+       have_utf8=
+       ;;
+esac
+
 . ./lib-git-svn.sh
 
 mkdir import
@@ -173,7 +183,7 @@ then
 fi
 
 
-if test -n "$GIT_SVN_LC_ALL" && echo $GIT_SVN_LC_ALL | grep -q '\.UTF-8$'
+if test "$have_utf8" = t
 then
        name="commit with UTF-8 message: locale: $GIT_SVN_LC_ALL"
        echo '# hello' >> exec-2.sh
@@ -203,7 +213,7 @@ fi
 
 name='check imported tree checksums expected tree checksums'
 rm -f expected
-if test -n "$GIT_SVN_LC_ALL" && echo $GIT_SVN_LC_ALL | grep -q '\.UTF-8$'
+if test "$have_utf8" = t
 then
        echo tree f735671b89a7eb30cab1d8597de35bd4271ab813 > expected
 fi
index 54e0ed7353919a7c3adaa0a96c39c1975a223145..a5a235f100709f503887e9a69c5b510e0b04534f 100644 (file)
@@ -21,8 +21,8 @@ a_empty_crlf=
 
 cd import
        cat >> kw.c <<\EOF
-/* Make it look like somebody copied a file from CVS into SVN: */
-/* $Id: kw.c,v 1.1.1.1 1994/03/06 00:00:00 eric Exp $ */
+/* Somebody prematurely put a keyword into this file */
+/* $Id$ */
 EOF
 
        printf "Hello\r\nWorld\r\n" > crlf
diff --git a/contrib/git-svn/t/t0003-graft-branches.sh b/contrib/git-svn/t/t0003-graft-branches.sh
new file mode 100644 (file)
index 0000000..cc62d4e
--- /dev/null
@@ -0,0 +1,63 @@
+test_description='git-svn graft-branches'
+. ./lib-git-svn.sh
+
+test_expect_success 'initialize repo' "
+       mkdir import &&
+       cd import &&
+       mkdir -p trunk branches tags &&
+       echo hello > trunk/readme &&
+       svn import -m 'import for git-svn' . $svnrepo &&
+       cd .. &&
+       svn cp -m 'tag a' $svnrepo/trunk $svnrepo/tags/a &&
+       svn cp -m 'branch a' $svnrepo/trunk $svnrepo/branches/a &&
+       svn co $svnrepo wc &&
+       cd wc &&
+       echo feedme >> branches/a/readme &&
+       svn commit -m hungry &&
+       svn up &&
+       cd trunk &&
+       svn merge -r3:4 $svnrepo/branches/a &&
+       svn commit -m 'merge with a' &&
+       cd ../.. &&
+       svn log -v $svnrepo &&
+       git-svn init -i trunk $svnrepo/trunk &&
+       git-svn init -i a $svnrepo/branches/a &&
+       git-svn init -i tags/a $svnrepo/tags/a &&
+       git-svn fetch -i tags/a &&
+       git-svn fetch -i a &&
+       git-svn fetch -i trunk
+       "
+
+r1=`git-rev-list remotes/trunk | tail -n1`
+r2=`git-rev-list remotes/tags/a | tail -n1`
+r3=`git-rev-list remotes/a | tail -n1`
+r4=`git-rev-list remotes/a | head -n1`
+r5=`git-rev-list remotes/trunk | head -n1`
+
+test_expect_success 'test graft-branches regexes and copies' "
+       test -n "$r1" &&
+       test -n "$r2" &&
+       test -n "$r3" &&
+       test -n "$r4" &&
+       test -n "$r5" &&
+       git-svn graft-branches &&
+       grep '^$r2 $r1' $GIT_DIR/info/grafts &&
+       grep '^$r3 $r1' $GIT_DIR/info/grafts &&
+       grep '^$r5 ' $GIT_DIR/info/grafts | grep '$r4' | grep '$r1'
+       "
+
+test_debug 'gitk --all & sleep 1'
+
+test_expect_success 'test graft-branches with tree-joins' "
+       rm $GIT_DIR/info/grafts &&
+       git-svn graft-branches --no-default-regex --no-graft-copy -B &&
+       grep '^$r3 ' $GIT_DIR/info/grafts | grep '$r1' | grep '$r2' &&
+       grep '^$r2 $r1' $GIT_DIR/info/grafts &&
+       grep '^$r5 ' $GIT_DIR/info/grafts | grep '$r1' | grep '$r4'
+       "
+
+# the result of this is kinda funky, we have a strange history and
+# this is just a test :)
+test_debug 'gitk --all &'
+
+test_done
diff --git a/contrib/git-svn/t/t0004-follow-parent.sh b/contrib/git-svn/t/t0004-follow-parent.sh
new file mode 100644 (file)
index 0000000..01488ff
--- /dev/null
@@ -0,0 +1,44 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Eric Wong
+#
+
+test_description='git-svn --follow-parent fetching'
+. ./lib-git-svn.sh
+
+if test -n "$GIT_SVN_NO_LIB" && test "$GIT_SVN_NO_LIB" -ne 0
+then
+       echo 'Skipping: --follow-parent needs SVN libraries'
+       test_done
+       exit 0
+fi
+
+test_expect_success 'initialize repo' "
+       mkdir import &&
+       cd import &&
+       mkdir -p trunk &&
+       echo hello > trunk/readme &&
+       svn import -m 'initial' . $svnrepo &&
+       cd .. &&
+       svn co $svnrepo wc &&
+       cd wc &&
+       echo world >> trunk/readme &&
+       svn commit -m 'another commit' &&
+       svn up &&
+       svn mv -m 'rename to thunk' trunk thunk &&
+       svn up &&
+       echo goodbye >> thunk/readme &&
+       svn commit -m 'bye now' &&
+       cd ..
+       "
+
+test_expect_success 'init and fetch --follow-parent a moved directory' "
+       git-svn init -i thunk $svnrepo/thunk &&
+       git-svn fetch --follow-parent -i thunk &&
+       git-rev-parse --verify refs/remotes/trunk &&
+       test '$?' -eq '0'
+       "
+
+test_debug 'gitk --all &'
+
+test_done
diff --git a/contrib/git-svn/t/t0005-commit-diff.sh b/contrib/git-svn/t/t0005-commit-diff.sh
new file mode 100644 (file)
index 0000000..f994b72
--- /dev/null
@@ -0,0 +1,41 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Eric Wong
+test_description='git-svn commit-diff'
+. ./lib-git-svn.sh
+
+if test -n "$GIT_SVN_NO_LIB" && test "$GIT_SVN_NO_LIB" -ne 0
+then
+       echo 'Skipping: commit-diff needs SVN libraries'
+       test_done
+       exit 0
+fi
+
+test_expect_success 'initialize repo' "
+       mkdir import &&
+       cd import &&
+       echo hello > readme &&
+       svn import -m 'initial' . $svnrepo &&
+       cd .. &&
+       echo hello > readme &&
+       git update-index --add readme &&
+       git commit -a -m 'initial' &&
+       echo world >> readme &&
+       git commit -a -m 'another'
+       "
+
+head=`git rev-parse --verify HEAD^0`
+prev=`git rev-parse --verify HEAD^1`
+
+# the internals of the commit-diff command are the same as the regular
+# commit, so only a basic test of functionality is needed since we've
+# already tested commit extensively elsewhere
+
+test_expect_success 'test the commit-diff command' "
+       test -n '$prev' && test -n '$head' &&
+       git-svn commit-diff '$prev' '$head' '$svnrepo' &&
+       svn co $svnrepo wc &&
+       cmp readme wc/readme
+       "
+
+test_done
index 1ba4d669da346abb5dab86ea7842a7ad4d3e1cde..e096bd7ef638273b525848fbe0018863871ea93d 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -35,7 +35,7 @@ static char *base_path = NULL;
  * after ~user/.  E.g. a request to git://host/~alice/frotz would
  * go to /home/alice/pub_git/frotz with --user-path=pub_git.
  */
-static char *user_path = NULL;
+static const char *user_path = NULL;
 
 /* Timeout, and initial timeout */
 static unsigned int timeout = 0;
@@ -472,7 +472,7 @@ static void child_handler(int signo)
                        children_reaped = reaped + 1;
                        /* XXX: Custom logging, since we don't wanna getpid() */
                        if (verbose) {
-                               char *dead = "";
+                               const char *dead = "";
                                if (!WIFEXITED(status) || WEXITSTATUS(status) > 0)
                                        dead = " (with error)";
                                if (log_syslog)
index aa3434a4cbfe62563b7621ddd7e72be3adbea1cc..8e68d5df3303ed75c2fbb9c8b3d1025785a622c7 100644 (file)
@@ -97,7 +97,7 @@ static int compare_names(const void *_a, const void *_b)
        return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1;
 }
 
-static void describe(char *arg, int last_one)
+static void describe(const char *arg, int last_one)
 {
        unsigned char sha1[20];
        struct commit *cmit;
diff --git a/diff.c b/diff.c
index 834b166064bc503c8c4a16222e9b05c3ada8f457..5a71489a471568fb3aa51af5d74fa269ec4fadf1 100644 (file)
--- a/diff.c
+++ b/diff.c
 
 static int use_size_cache;
 
-int diff_rename_limit_default = -1;
-
-int git_diff_config(const char *var, const char *value)
-{
-       if (!strcmp(var, "diff.renamelimit")) {
-               diff_rename_limit_default = git_config_int(var, value);
-               return 0;
-       }
-
-       return git_default_config(var, value);
-}
+static int diff_rename_limit_default = -1;
+static int diff_use_color_default = 0;
 
 enum color_diff {
        DIFF_RESET = 0,
@@ -51,9 +42,6 @@ enum color_diff {
 #define COLOR_CYAN    "\033[36m"
 #define COLOR_WHITE   "\033[37m"
 
-#define COLOR_CYANBG  "\033[46m"
-#define COLOR_GRAYBG  "\033[47m"       // Good for xterm
-
 static const char *diff_colors[] = {
        [DIFF_RESET]    = COLOR_RESET,
        [DIFF_PLAIN]    = COLOR_NORMAL,
@@ -63,6 +51,83 @@ static const char *diff_colors[] = {
        [DIFF_FILE_NEW] = COLOR_GREEN,
 };
 
+static int parse_diff_color_slot(const char *var, int ofs)
+{
+       if (!strcasecmp(var+ofs, "plain"))
+               return DIFF_PLAIN;
+       if (!strcasecmp(var+ofs, "meta"))
+               return DIFF_METAINFO;
+       if (!strcasecmp(var+ofs, "frag"))
+               return DIFF_FRAGINFO;
+       if (!strcasecmp(var+ofs, "old"))
+               return DIFF_FILE_OLD;
+       if (!strcasecmp(var+ofs, "new"))
+               return DIFF_FILE_NEW;
+       die("bad config variable '%s'", var);
+}
+
+static const char *parse_diff_color_value(const char *value, const char *var)
+{
+       if (!strcasecmp(value, "normal"))
+               return COLOR_NORMAL;
+       if (!strcasecmp(value, "bold"))
+               return COLOR_BOLD;
+       if (!strcasecmp(value, "dim"))
+               return COLOR_DIM;
+       if (!strcasecmp(value, "ul"))
+               return COLOR_UL;
+       if (!strcasecmp(value, "blink"))
+               return COLOR_BLINK;
+       if (!strcasecmp(value, "reverse"))
+               return COLOR_REVERSE;
+       if (!strcasecmp(value, "reset"))
+               return COLOR_RESET;
+       if (!strcasecmp(value, "black"))
+               return COLOR_BLACK;
+       if (!strcasecmp(value, "red"))
+               return COLOR_RED;
+       if (!strcasecmp(value, "green"))
+               return COLOR_GREEN;
+       if (!strcasecmp(value, "yellow"))
+               return COLOR_YELLOW;
+       if (!strcasecmp(value, "blue"))
+               return COLOR_BLUE;
+       if (!strcasecmp(value, "magenta"))
+               return COLOR_MAGENTA;
+       if (!strcasecmp(value, "cyan"))
+               return COLOR_CYAN;
+       if (!strcasecmp(value, "white"))
+               return COLOR_WHITE;
+       die("bad config value '%s' for variable '%s'", value, var);
+}
+
+int git_diff_config(const char *var, const char *value)
+{
+       if (!strcmp(var, "diff.renamelimit")) {
+               diff_rename_limit_default = git_config_int(var, value);
+               return 0;
+       }
+       if (!strcmp(var, "diff.color")) {
+               if (!value)
+                       diff_use_color_default = 1; /* bool */
+               else if (!strcasecmp(value, "auto"))
+                       diff_use_color_default = isatty(1);
+               else if (!strcasecmp(value, "never"))
+                       diff_use_color_default = 0;
+               else if (!strcasecmp(value, "always"))
+                       diff_use_color_default = 1;
+               else
+                       diff_use_color_default = git_config_bool(var, value);
+               return 0;
+       }
+       if (!strncmp(var, "diff.color.", 11)) {
+               int slot = parse_diff_color_slot(var, 11);
+               diff_colors[slot] = parse_diff_color_value(value, var);
+               return 0;
+       }
+       return git_default_config(var, value);
+}
+
 static char *quote_one(const char *str)
 {
        int needlen;
@@ -678,7 +743,7 @@ static void builtin_diff(const char *name_a,
                memset(&ecbdata, 0, sizeof(ecbdata));
                ecbdata.label_path = lbl;
                ecbdata.color_diff = o->color_diff;
-               xpp.flags = XDF_NEED_MINIMAL;
+               xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
                xecfg.ctxlen = o->context;
                xecfg.flags = XDL_EMIT_FUNCNAMES;
                if (!diffopts)
@@ -703,6 +768,7 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
                             struct diff_filespec *one,
                             struct diff_filespec *two,
                             struct diffstat_t *diffstat,
+                            struct diff_options *o,
                             int complete_rewrite)
 {
        mmfile_t mf1, mf2;
@@ -732,7 +798,7 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
                xdemitconf_t xecfg;
                xdemitcb_t ecb;
 
-               xpp.flags = XDF_NEED_MINIMAL;
+               xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
                xecfg.ctxlen = 0;
                xecfg.flags = 0;
                ecb.outf = xdiff_outf;
@@ -1317,7 +1383,7 @@ static void run_diffstat(struct diff_filepair *p, struct diff_options *o,
 
        if (DIFF_PAIR_UNMERGED(p)) {
                /* unmerged */
-               builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat, 0);
+               builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat, o, 0);
                return;
        }
 
@@ -1329,7 +1395,7 @@ static void run_diffstat(struct diff_filepair *p, struct diff_options *o,
 
        if (p->status == DIFF_STATUS_MODIFIED && p->score)
                complete_rewrite = 1;
-       builtin_diffstat(name, other, p->one, p->two, diffstat, complete_rewrite);
+       builtin_diffstat(name, other, p->one, p->two, diffstat, o, complete_rewrite);
 }
 
 static void run_checkdiff(struct diff_filepair *p, struct diff_options *o)
@@ -1362,6 +1428,7 @@ void diff_setup(struct diff_options *options)
 
        options->change = diff_change;
        options->add_remove = diff_addremove;
+       options->color_diff = diff_use_color_default;
 }
 
 int diff_setup_done(struct diff_options *options)
@@ -1534,6 +1601,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        }
        else if (!strcmp(arg, "--color"))
                options->color_diff = 1;
+       else if (!strcmp(arg, "-w") || !strcmp(arg, "--ignore-all-space"))
+               options->xdl_opts |= XDF_IGNORE_WHITESPACE;
+       else if (!strcmp(arg, "-b") || !strcmp(arg, "--ignore-space-change"))
+               options->xdl_opts |= XDF_IGNORE_WHITESPACE_CHANGE;
        else
                return 0;
        return 1;
@@ -2033,6 +2104,145 @@ static void diff_summary(struct diff_filepair *p)
        }
 }
 
+struct patch_id_t {
+       struct xdiff_emit_state xm;
+       SHA_CTX *ctx;
+       int patchlen;
+};
+
+static int remove_space(char *line, int len)
+{
+       int i;
+        char *dst = line;
+        unsigned char c;
+
+        for (i = 0; i < len; i++)
+                if (!isspace((c = line[i])))
+                        *dst++ = c;
+
+        return dst - line;
+}
+
+static void patch_id_consume(void *priv, char *line, unsigned long len)
+{
+       struct patch_id_t *data = priv;
+       int new_len;
+
+       /* Ignore line numbers when computing the SHA1 of the patch */
+       if (!strncmp(line, "@@ -", 4))
+               return;
+
+       new_len = remove_space(line, len);
+
+       SHA1_Update(data->ctx, line, new_len);
+       data->patchlen += new_len;
+}
+
+/* returns 0 upon success, and writes result into sha1 */
+static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1)
+{
+       struct diff_queue_struct *q = &diff_queued_diff;
+       int i;
+       SHA_CTX ctx;
+       struct patch_id_t data;
+       char buffer[PATH_MAX * 4 + 20];
+
+       SHA1_Init(&ctx);
+       memset(&data, 0, sizeof(struct patch_id_t));
+       data.ctx = &ctx;
+       data.xm.consume = patch_id_consume;
+
+       for (i = 0; i < q->nr; i++) {
+               xpparam_t xpp;
+               xdemitconf_t xecfg;
+               xdemitcb_t ecb;
+               mmfile_t mf1, mf2;
+               struct diff_filepair *p = q->queue[i];
+               int len1, len2;
+
+               if (p->status == 0)
+                       return error("internal diff status error");
+               if (p->status == DIFF_STATUS_UNKNOWN)
+                       continue;
+               if (diff_unmodified_pair(p))
+                       continue;
+               if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
+                   (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
+                       continue;
+               if (DIFF_PAIR_UNMERGED(p))
+                       continue;
+
+               diff_fill_sha1_info(p->one);
+               diff_fill_sha1_info(p->two);
+               if (fill_mmfile(&mf1, p->one) < 0 ||
+                               fill_mmfile(&mf2, p->two) < 0)
+                       return error("unable to read files to diff");
+
+               /* Maybe hash p->two? into the patch id? */
+               if (mmfile_is_binary(&mf2))
+                       continue;
+
+               len1 = remove_space(p->one->path, strlen(p->one->path));
+               len2 = remove_space(p->two->path, strlen(p->two->path));
+               if (p->one->mode == 0)
+                       len1 = snprintf(buffer, sizeof(buffer),
+                                       "diff--gita/%.*sb/%.*s"
+                                       "newfilemode%06o"
+                                       "---/dev/null"
+                                       "+++b/%.*s",
+                                       len1, p->one->path,
+                                       len2, p->two->path,
+                                       p->two->mode,
+                                       len2, p->two->path);
+               else if (p->two->mode == 0)
+                       len1 = snprintf(buffer, sizeof(buffer),
+                                       "diff--gita/%.*sb/%.*s"
+                                       "deletedfilemode%06o"
+                                       "---a/%.*s"
+                                       "+++/dev/null",
+                                       len1, p->one->path,
+                                       len2, p->two->path,
+                                       p->one->mode,
+                                       len1, p->one->path);
+               else
+                       len1 = snprintf(buffer, sizeof(buffer),
+                                       "diff--gita/%.*sb/%.*s"
+                                       "---a/%.*s"
+                                       "+++b/%.*s",
+                                       len1, p->one->path,
+                                       len2, p->two->path,
+                                       len1, p->one->path,
+                                       len2, p->two->path);
+               SHA1_Update(&ctx, buffer, len1);
+
+               xpp.flags = XDF_NEED_MINIMAL;
+               xecfg.ctxlen = 3;
+               xecfg.flags = XDL_EMIT_FUNCNAMES;
+               ecb.outf = xdiff_outf;
+               ecb.priv = &data;
+               xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
+       }
+
+       SHA1_Final(sha1, &ctx);
+       return 0;
+}
+
+int diff_flush_patch_id(struct diff_options *options, unsigned char *sha1)
+{
+       struct diff_queue_struct *q = &diff_queued_diff;
+       int i;
+       int result = diff_get_patch_id(options, sha1);
+
+       for (i = 0; i < q->nr; i++)
+               diff_free_filepair(q->queue[i]);
+
+       free(q->queue);
+       q->queue = NULL;
+       q->nr = q->alloc = 0;
+
+       return result;
+}
+
 void diff_flush(struct diff_options *options)
 {
        struct diff_queue_struct *q = &diff_queued_diff;
diff --git a/diff.h b/diff.h
index de9de574e78693d27ff911d882030a59407a99d5..d5068af7d16a0905edfeaa24276461c05437f3be 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -46,6 +46,7 @@ struct diff_options {
        int setup;
        int abbrev;
        const char *stat_sep;
+       long xdl_opts;
 
        int nr_paths;
        const char **paths;
@@ -183,4 +184,6 @@ extern int run_diff_files(struct rev_info *revs, int silent_on_removed);
 
 extern int run_diff_index(struct rev_info *revs, int cached);
 
+extern int diff_flush_patch_id(struct diff_options *, unsigned char *);
+
 #endif /* DIFF_H */
index 4232e27411036e339f8102e45b7cb699c808a798..679045a540c60e747074a9127bb7003f8a510b1c 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -97,7 +97,7 @@ while case "$#" in 0) break;; esac
 do
        case "$1" in
        -d=*|--d=*|--do=*|--dot=*|--dote=*|--dotes=*|--dotest=*)
-       dotest=`expr "$1" : '-[^=]*=\(.*\)'`; shift ;;
+       dotest=`expr "z$1" : 'z-[^=]*=\(.*\)'`; shift ;;
        -d|--d|--do|--dot|--dote|--dotes|--dotest)
        case "$#" in 1) usage ;; esac; shift
        dotest="$1"; shift;;
index 77c25938091b75f51eb3cc5922d67db6333e1e74..5613bfc403f7a4208d2a85de73ec33229e5a3f10 100755 (executable)
@@ -150,8 +150,7 @@ else
        # Match the index to the working tree, and do a three-way.
        git diff-files --name-only | git update-index --remove --stdin &&
        work=`git write-tree` &&
-       git read-tree --reset $new &&
-       git checkout-index -f -u -q -a &&
+       git read-tree --reset -u $new &&
        git read-tree -m -u --aggressive $old $new $work || exit
 
        if result=`git write-tree 2>/dev/null`
index 6fa0daaacf4c677890768037c128d1d5e967a254..6a14b2591136d0bbfbec333c2304a7f003134ac2 100755 (executable)
@@ -133,7 +133,7 @@ while
        *,--reference)
                shift; reference="$1" ;;
        *,--reference=*)
-               reference=`expr "$1" : '--reference=\(.*\)'` ;;
+               reference=`expr "z$1" : 'z--reference=\(.*\)'` ;;
        *,-o|*,--or|*,--ori|*,--orig|*,--origi|*,--origin)
                case "$2" in
                '')
index 128db6c52a878a421a67c658dfd175f774c24fea..7e50cf399b70a5ec26824f0bf5b5210dbe06aedc 100755 (executable)
@@ -223,13 +223,13 @@ do
   -F*|-f*)
       no_edit=t
       log_given=t$log_given
-      logfile=`expr "$1" : '-[Ff]\(.*\)'`
+      logfile=`expr "z$1" : 'z-[Ff]\(.*\)'`
       shift
       ;;
   --F=*|--f=*|--fi=*|--fil=*|--file=*)
       no_edit=t
       log_given=t$log_given
-      logfile=`expr "$1" : '-[^=]*=\(.*\)'`
+      logfile=`expr "z$1" : 'z-[^=]*=\(.*\)'`
       shift
       ;;
   -a|--a|--al|--all)
@@ -237,7 +237,7 @@ do
       shift
       ;;
   --au=*|--aut=*|--auth=*|--autho=*|--author=*)
-      force_author=`expr "$1" : '-[^=]*=\(.*\)'`
+      force_author=`expr "z$1" : 'z-[^=]*=\(.*\)'`
       shift
       ;;
   --au|--aut|--auth|--autho|--author)
@@ -277,11 +277,11 @@ $1"
       log_given=m$log_given
       if test "$log_message" = ''
       then
-          log_message=`expr "$1" : '-m\(.*\)'`
+          log_message=`expr "z$1" : 'z-m\(.*\)'`
       else
           log_message="$log_message
 
-`expr "$1" : '-m\(.*\)'`"
+`expr "z$1" : 'z-m\(.*\)'`"
       fi
       no_edit=t
       shift
@@ -290,11 +290,11 @@ $1"
       log_given=m$log_given
       if test "$log_message" = ''
       then
-          log_message=`expr "$1" : '-[^=]*=\(.*\)'`
+          log_message=`expr "z$1" : 'z-[^=]*=\(.*\)'`
       else
           log_message="$log_message
 
-`expr "$1" : '-[^=]*=\(.*\)'`"
+`expr "z$1" : 'zq-[^=]*=\(.*\)'`"
       fi
       no_edit=t
       shift
@@ -321,7 +321,7 @@ $1"
   --reedit-me=*|--reedit-mes=*|--reedit-mess=*|--reedit-messa=*|\
   --reedit-messag=*|--reedit-message=*)
       log_given=t$log_given
-      use_commit=`expr "$1" : '-[^=]*=\(.*\)'`
+      use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'`
       no_edit=
       shift
       ;;
@@ -346,7 +346,7 @@ $1"
   --reuse-mes=*|--reuse-mess=*|--reuse-messa=*|--reuse-messag=*|\
   --reuse-message=*)
       log_given=t$log_given
-      use_commit=`expr "$1" : '-[^=]*=\(.*\)'`
+      use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'`
       no_edit=t
       shift
       ;;
@@ -566,6 +566,9 @@ then
 elif test -f "$GIT_DIR/MERGE_HEAD" && test -f "$GIT_DIR/MERGE_MSG"
 then
        cat "$GIT_DIR/MERGE_MSG"
+elif test -f "$GIT_DIR/SQUASH_MSG"
+then
+       cat "$GIT_DIR/SQUASH_MSG"
 fi | git-stripspace >"$GIT_DIR"/COMMIT_EDITMSG
 
 case "$signoff" in
@@ -663,7 +666,7 @@ else
 fi
 if [ "$?" != "0" -a ! -f "$GIT_DIR/MERGE_HEAD" -a -z "$amend" ]
 then
-       rm -f "$GIT_DIR/COMMIT_EDITMSG"
+       rm -f "$GIT_DIR/COMMIT_EDITMSG" "$GIT_DIR/SQUASH_MSG"
        run_status
        exit 1
 fi
@@ -734,7 +737,7 @@ else
        false
 fi
 ret="$?"
-rm -f "$GIT_DIR/COMMIT_MSG" "$GIT_DIR/COMMIT_EDITMSG"
+rm -f "$GIT_DIR/COMMIT_MSG" "$GIT_DIR/COMMIT_EDITMSG" "$GIT_DIR/SQUASH_MSG"
 if test -d "$GIT_DIR/rr-cache"
 then
        git-rerere
old mode 100644 (file)
new mode 100755 (executable)
index f3daa6c..e5a00a1
@@ -17,7 +17,7 @@
 use warnings;
 use Getopt::Std;
 use File::Spec;
-use File::Temp qw(tempfile);
+use File::Temp qw(tempfile tmpnam);
 use File::Path qw(mkpath);
 use File::Basename qw(basename dirname);
 use Time::Local;
@@ -467,13 +467,7 @@ ($$)
 $orig_git_index = $ENV{GIT_INDEX_FILE} if exists $ENV{GIT_INDEX_FILE};
 
 my %index; # holds filenames of one index per branch
-{   # init with an index for origin
-    my ($fh, $fn) = tempfile('gitXXXXXX', SUFFIX => '.idx',
-                            DIR => File::Spec->tmpdir());
-    close ($fh);
-    $index{$opt_o} = $fn;
-}
-$ENV{GIT_INDEX_FILE} = $index{$opt_o};
+
 unless(-d $git_dir) {
        system("git-init-db");
        die "Cannot init the GIT db at $git_tree: $?\n" if $?;
@@ -500,17 +494,6 @@ ($$)
        $orig_branch = $last_branch;
        $tip_at_start = `git-rev-parse --verify HEAD`;
 
-       # populate index
-       unless ($index{$last_branch}) {
-           my ($fh, $fn) = tempfile('gitXXXXXX', SUFFIX => '.idx',
-                                    DIR => File::Spec->tmpdir());
-           close ($fh);
-           $index{$last_branch} = $fn;
-       }
-       $ENV{GIT_INDEX_FILE} = $index{$last_branch};
-       system('git-read-tree', $last_branch);
-       die "read-tree failed: $?\n" if $?;
-
        # Get the last import timestamps
        opendir(D,"$git_dir/refs/heads");
        while(defined(my $head = readdir(D))) {
@@ -627,6 +610,27 @@ ()
 $ignorebranch{'#CVSPS_NO_BRANCH'} = 1;
 
 sub commit {
+       if ($branch eq $opt_o && !$index{branch} && !get_headref($branch, $git_dir)) {
+           # looks like an initial commit
+           # use the index primed by git-init-db
+           $ENV{GIT_INDEX_FILE} = '.git/index';
+           $index{$branch} = '.git/index';
+       } else {
+           # use an index per branch to speed up
+           # imports of projects with many branches
+           unless ($index{$branch}) {
+               $index{$branch} = tmpnam();
+               $ENV{GIT_INDEX_FILE} = $index{$branch};
+               if ($ancestor) {
+                   system("git-read-tree", $ancestor);
+               } else {
+                   system("git-read-tree", $branch);
+               }
+               die "read-tree failed: $?\n" if $?;
+           }
+       }
+        $ENV{GIT_INDEX_FILE} = $index{$branch};
+
        update_index(@old, @new);
        @old = @new = ();
        my $tree = write_tree();
@@ -815,20 +819,6 @@ sub commit {
                        close(H)
                                or die "Could not write branch $branch: $!";
                }
-               if(($ancestor || $branch) ne $last_branch) {
-                       print "Switching from $last_branch to $branch\n" if $opt_v;
-                       unless ($index{$branch}) {
-                           my ($fh, $fn) = tempfile('gitXXXXXX', SUFFIX => '.idx',
-                                                    DIR => File::Spec->tmpdir());
-                           close ($fh);
-                           $index{$branch} = $fn;
-                           $ENV{GIT_INDEX_FILE} = $index{$branch};
-                           system("git-read-tree", $branch);
-                           die "read-tree failed: $?\n" if $?;
-                       } else {
-                           $ENV{GIT_INDEX_FILE} = $index{$branch};
-                       }
-               }
                $last_branch = $branch if $branch ne $last_branch;
                $state = 9;
        } elsif($state == 8) {
@@ -892,7 +882,9 @@ sub commit {
 commit() if $branch and $state != 11;
 
 foreach my $git_index (values %index) {
-    unlink($git_index);
+    if ($git_index ne '.git/index') {
+       unlink($git_index);
+    }
 }
 
 if (defined $orig_git_index) {
index da5657eb40516430d0302eea2e15bac6fa699b55..24e3b507ef1f907e7146a2653189f45a92db08fd 100755 (executable)
@@ -3,8 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
-
-USAGE='[-n] [--no-commit] [-s <strategy>]... <merge-message> <head> <remote>+'
+USAGE='[-n] [--no-commit] [--squash] [-s <strategy>]... <merge-message> <head> <remote>+'
 . git-sh-setup
 
 LF='
@@ -42,20 +41,49 @@ restorestate() {
        fi
 }
 
+finish_up_to_date () {
+       case "$squash" in
+       t)
+               echo "$1 (nothing to squash)" ;;
+       '')
+               echo "$1" ;;
+       esac
+       dropsave
+}
+
+squash_message () {
+       echo Squashed commit of the following:
+       echo
+       git-log --no-merges ^"$head" $remote
+}
+
 finish () {
        test '' = "$2" || echo "$2"
-       case "$merge_msg" in
-       '')
-               echo "No merge message -- not updating HEAD"
+       case "$squash" in
+       t)
+               echo "Squash commit -- not updating HEAD"
+               squash_message >"$GIT_DIR/SQUASH_MSG"
                ;;
-       *)
-               git-update-ref HEAD "$1" "$head" || exit 1
+       '')
+               case "$merge_msg" in
+               '')
+                       echo "No merge message -- not updating HEAD"
+                       ;;
+               *)
+                       git-update-ref HEAD "$1" "$head" || exit 1
+                       ;;
+               esac
                ;;
        esac
-
-       case "$no_summary" in
+       case "$1" in
        '')
-               git-diff-tree --stat --summary -M "$head" "$1"
+               ;;
+       ?*)
+               case "$no_summary" in
+               '')
+                       git-diff-tree --stat --summary -M "$head" "$1"
+                       ;;
+               esac
                ;;
        esac
 }
@@ -66,6 +94,8 @@ do
        -n|--n|--no|--no-|--no-s|--no-su|--no-sum|--no-summ|\
                --no-summa|--no-summar|--no-summary)
                no_summary=t ;;
+       --sq|--squ|--squa|--squas|--squash)
+               squash=t no_commit=t ;;
        --no-c|--no-co|--no-com|--no-comm|--no-commi|--no-commit)
                no_commit=t ;;
        -s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\
@@ -73,7 +103,7 @@ do
        -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy)
                case "$#,$1" in
                *,*=*)
-                       strategy=`expr "$1" : '-[^=]*=\(.*\)'` ;;
+                       strategy=`expr "z$1" : 'z-[^=]*=\(.*\)'` ;;
                1,*)
                        usage ;;
                *)
@@ -152,8 +182,7 @@ f,*)
 ?,1,"$1",*)
        # If head can reach all the merge then we are up to date.
        # but first the most common case of merging one remote.
-       echo "Already up-to-date."
-       dropsave
+       finish_up_to_date "Already up-to-date."
        exit 0
        ;;
 ?,1,"$head",*)
@@ -205,8 +234,7 @@ f,*)
        done
        if test "$up_to_date" = t
        then
-               echo "Already up-to-date. Yeeah!"
-               dropsave
+               finish_up_to_date "Already up-to-date. Yeeah!"
                exit 0
        fi
        ;;
@@ -310,11 +338,17 @@ case "$best_strategy" in
        git-merge-$best_strategy $common -- "$head_arg" "$@"
        ;;
 esac
-for remote
-do
-       echo $remote
-done >"$GIT_DIR/MERGE_HEAD"
-echo "$merge_msg" >"$GIT_DIR/MERGE_MSG"
+
+if test "$squash" = t
+then
+       finish
+else
+       for remote
+       do
+               echo $remote
+       done >"$GIT_DIR/MERGE_HEAD"
+       echo "$merge_msg" >"$GIT_DIR/MERGE_MSG"
+fi
 
 if test "$merge_was_ok" = t
 then
index bdd3939923bb905d561eb3ca88bda729cc93660d..076785c96b30b4fd8ab944324fd53db7c584567a 100755 (executable)
@@ -8,7 +8,7 @@ USAGE='[-n | --no-summary] [--no-commit] [-s strategy]... [<fetch-options>] <rep
 LONG_USAGE='Fetch one or more remote refs and merge it/them into the current HEAD.'
 . git-sh-setup
 
-strategy_args= no_summary= no_commit=
+strategy_args= no_summary= no_commit= squash=
 while case "$#,$1" in 0) break ;; *,-*) ;; *) break ;; esac
 do
        case "$1" in
@@ -17,12 +17,14 @@ do
                no_summary=-n ;;
        --no-c|--no-co|--no-com|--no-comm|--no-commi|--no-commit)
                no_commit=--no-commit ;;
+       --sq|--squ|--squa|--squas|--squash)
+               squash=--squash ;;
        -s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\
                --strateg=*|--strategy=*|\
        -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy)
                case "$#,$1" in
                *,*=*)
-                       strategy=`expr "$1" : '-[^=]*=\(.*\)'` ;;
+                       strategy=`expr "z$1" : 'z-[^=]*=\(.*\)'` ;;
                1,*)
                        usage ;;
                *)
@@ -100,4 +102,5 @@ case "$strategy_args" in
 esac
 
 merge_name=$(git-fmt-merge-msg <"$GIT_DIR/FETCH_HEAD") || exit
-git-merge $no_summary $no_commit $strategy_args "$merge_name" HEAD $merge_head
+git-merge $no_summary $no_commit $squash $strategy_args \
+       "$merge_name" HEAD $merge_head
index 12d9d0cbc9e0c5df0f1094f3ff4235d8657c896e..86b51abd21748c19235a703b1b523525d05383cd 100755 (executable)
@@ -9,7 +9,7 @@ while case "$#" in 0) break;; esac
 do
        case "$1" in
        --au=*|--aut=*|--auth=*|--autho=*|--author=*)
-               quilt_author=$(expr "$1" : '-[^=]*\(.*\)')
+               quilt_author=$(expr "z$1" : 'z-[^=]*\(.*\)')
                shift
                ;;
 
@@ -26,7 +26,7 @@ do
                ;;
 
        --pa=*|--pat=*|--patc=*|--patch=*|--patche=*|--patches=*)
-               QUILT_PATCHES=$(expr "$1" : '-[^=]*\(.*\)')
+               QUILT_PATCHES=$(expr "z$1" : 'z-[^=]*\(.*\)')
                shift
                ;;
 
index 91594775e6aad4ebe924506e779c51ea5333ad37..3945e067141ec1bc87456179a59de8f692bdf9fa 100755 (executable)
@@ -34,11 +34,6 @@ When you have resolved this problem run \"git rebase --continue\".
 If you would prefer to skip this patch, instead run \"git rebase --skip\".
 To restore the original branch and stop rebasing run \"git rebase --abort\".
 "
-
-MRESOLVEMSG="
-When you have resolved this problem run \"git rebase --continue\".
-To restore the original branch and stop rebasing run \"git rebase --abort\".
-"
 unset newbase
 strategy=recursive
 do_merge=
@@ -54,20 +49,26 @@ continue_merge () {
        then
                echo "You still have unmerged paths in your index"
                echo "did you forget update-index?"
-               die "$MRESOLVEMSG"
+               die "$RESOLVEMSG"
        fi
 
        if test -n "`git-diff-index HEAD`"
        then
-               git-commit -C "`cat $dotest/current`"
+               if ! git-commit -C "`cat $dotest/current`"
+               then
+                       echo "Commit failed, please do not call \"git commit\""
+                       echo "directly, but instead do one of the following: "
+                       die "$RESOLVEMSG"
+               fi
+               printf "Committed: %0${prec}d" $msgnum
        else
-               echo "Previous merge succeeded automatically"
+               printf "Already applied: %0${prec}d" $msgnum
        fi
+       echo ' '`git-rev-list --pretty=oneline -1 HEAD | \
+                               sed 's/^[a-f0-9]\+ //'`
 
        prev_head=`git-rev-parse HEAD^0`
-
        # save the resulting commit so we can read-tree on it later
-       echo "$prev_head" > "$dotest/cmt.$msgnum.result"
        echo "$prev_head" > "$dotest/prev_head"
 
        # onto the next patch:
@@ -82,15 +83,15 @@ call_merge () {
        rv=$?
        case "$rv" in
        0)
-               git-commit -C "$cmt" || die "commit failed: $MRESOLVEMSG"
+               return
                ;;
        1)
                test -d "$GIT_DIR/rr-cache" && git-rerere
-               die "$MRESOLVEMSG"
+               die "$RESOLVEMSG"
                ;;
        2)
                echo "Strategy: $rv $strategy failed, try another" 1>&2
-               die "$MRESOLVEMSG"
+               die "$RESOLVEMSG"
                ;;
        *)
                die "Unknown exit code ($rv) from command:" \
@@ -100,23 +101,6 @@ call_merge () {
 }
 
 finish_rb_merge () {
-       set -e
-
-       msgnum=1
-       echo "Finalizing rebased commits..."
-       git-reset --hard "`cat $dotest/onto`"
-       end="`cat $dotest/end`"
-       while test "$msgnum" -le "$end"
-       do
-               git-read-tree `cat "$dotest/cmt.$msgnum.result"`
-               git-checkout-index -q -f -u -a
-               git-commit -C "`cat $dotest/cmt.$msgnum`"
-
-               printf "Committed %0${prec}d" $msgnum
-               echo ' '`git-rev-list --pretty=oneline -1 HEAD | \
-                                       sed 's/^[a-f0-9]\+ //'`
-               msgnum=$(($msgnum + 1))
-       done
        rm -r "$dotest"
        echo "All done."
 }
@@ -153,7 +137,18 @@ do
        --skip)
                if test -d "$dotest"
                then
-                       die "--skip is not supported when using --merge"
+                       prev_head="`cat $dotest/prev_head`"
+                       end="`cat $dotest/end`"
+                       msgnum="`cat $dotest/msgnum`"
+                       msgnum=$(($msgnum + 1))
+                       onto="`cat $dotest/onto`"
+                       while test "$msgnum" -le "$end"
+                       do
+                               call_merge "$msgnum"
+                               continue_merge
+                       done
+                       finish_rb_merge
+                       exit
                fi
                git am -3 --skip --resolvemsg="$RESOLVEMSG"
                exit
@@ -184,7 +179,7 @@ do
        -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy)
                case "$#,$1" in
                *,*=*)
-                       strategy=`expr "$1" : '-[^=]*=\(.*\)'` ;;
+                       strategy=`expr "z$1" : 'z-[^=]*=\(.*\)'` ;;
                1,*)
                        usage ;;
                *)
index 296f3b779b5889e4f9e69f994587d70503e8b25e..46451d0d64bdff2c0061d100c8d3b3bd38c21454 100755 (executable)
@@ -61,4 +61,4 @@ case "$reset_type" in
        ;;
 esac
 
-rm -f "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/rr-cache/MERGE_RR"
+rm -f "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/rr-cache/MERGE_RR" "$GIT_DIR/SQUASH_MSG"
diff --git a/git.c b/git.c
index 94e9a4a4b98acbd9e08943dbbe57616bd60b28e9..ca8961f0732bacc4efdb3afe9abc2e829ff69ee5 100644 (file)
--- a/git.c
+++ b/git.c
@@ -16,7 +16,8 @@
 
 static void prepend_to_path(const char *dir, int len)
 {
-       char *path, *old_path = getenv("PATH");
+       const char *old_path = getenv("PATH");
+       char *path;
        int path_len = len;
 
        if (!old_path)
@@ -99,7 +100,7 @@ static int split_cmdline(char *cmdline, const char ***argv)
 
 static int handle_alias(int *argcp, const char ***argv)
 {
-       int nongit = 0, ret = 0;
+       int nongit = 0, ret = 0, saved_errno = errno;
        const char *subdir;
 
        subdir = setup_git_directory_gently(&nongit);
@@ -137,6 +138,8 @@ static int handle_alias(int *argcp, const char ***argv)
        if (subdir)
                chdir(subdir);
 
+       errno = saved_errno;
+
        return ret;
 }
 
@@ -206,7 +209,6 @@ int main(int argc, const char **argv, char **envp)
 {
        const char *cmd = argv[0];
        char *slash = strrchr(cmd, '/');
-       char git_command[PATH_MAX + 1];
        const char *exec_path = NULL;
        int done_alias = 0;
 
@@ -313,7 +315,7 @@ int main(int argc, const char **argv, char **envp)
                cmd_usage(0, exec_path, "'%s' is not a git-command", cmd);
 
        fprintf(stderr, "Failed to run command '%s': %s\n",
-               git_command, strerror(errno));
+               cmd, strerror(errno));
 
        return 1;
 }
index 3c89a17496dcc72fa8773a0308281364bbf647a8..e281f70e544d1e59c47f61ce14a728ba5ef44a44 100644 (file)
@@ -1274,7 +1274,7 @@ xml_cdata(void *userData, const XML_Char *s, int len)
        strlcpy(ctx->cdata, s, len + 1);
 }
 
-static struct remote_lock *lock_remote(char *path, long timeout)
+static struct remote_lock *lock_remote(const char *path, long timeout)
 {
        struct active_request_slot *slot;
        struct slot_results results;
@@ -2130,7 +2130,7 @@ static int remote_exists(const char *path)
        return -1;
 }
 
-static void fetch_symref(char *path, char **symref, unsigned char *sha1)
+static void fetch_symref(const char *path, char **symref, unsigned char *sha1)
 {
        char *url;
        struct buffer buffer;
index 94e39cd94cb26b2147e214ce4032121df871834e..65c71c602db022f65660b1c29f54ddd59a3b0363 100644 (file)
@@ -242,7 +242,7 @@ socket_read( Socket_t *sock, char *buf, int len )
 }
 
 static int
-socket_write( Socket_t *sock, char *buf, int len )
+socket_write( Socket_t *sock, const char *buf, int len )
 {
        int n = write( sock->fd, buf, len );
        if (n != len) {
index 190e12fb7ceeb012a063f8c97b574e9650d95cc1..0498a6f45e53947e356c6a390869d3f8194f05b7 100644 (file)
@@ -1,5 +1,6 @@
 #include <sys/types.h>
 #include <sys/wait.h>
+#include <signal.h>
 
 #include "cache.h"
 
index 9abef24de36119a56a9bee48e49c2c8fccae32fc..9df653f6f5afe720870658d7093bddbf3e66beaf 100644 (file)
@@ -1,6 +1,8 @@
 #ifndef PKTLINE_H
 #define PKTLINE_H
 
+#include "git-compat-util.h"
+
 /*
  * Silly packetized line writing interface
  */
diff --git a/quote.c b/quote.c
index dcc23266109ce30e3fd098f3c76799b67b75a332..1910d000a5d288734aaea24da617481223ff9f56 100644 (file)
--- a/quote.c
+++ b/quote.c
@@ -13,7 +13,7 @@
  *  a!b      ==> a'\!'b    ==> 'a'\!'b'
  */
 #undef EMIT
-#define EMIT(x) ( (++len < n) && (*bp++ = (x)) )
+#define EMIT(x) do { if (++len < n) *bp++ = (x); } while(0)
 
 static inline int need_bs_quote(char c)
 {
index c80528b506e98c1e1dae463fa08159677a2a9473..817963045b81cef36bf3a9c76b7399a70226a181 100644 (file)
@@ -343,7 +343,7 @@ static void read_info_alternates(const char * relative_base, int depth)
 
 void prepare_alt_odb(void)
 {
-       char *alt;
+       const char *alt;
 
        alt = getenv(ALTERNATE_DB_ENVIRONMENT);
        if (!alt) alt = "";
index ac5a3ac563407c2450452689bde66962b0fa6b2e..c5db5804df767bb55d5c9972b18faf4dc35e899c 100644 (file)
--- a/t/README
+++ b/t/README
@@ -73,6 +73,7 @@ First digit tells the family:
        4 - the diff commands
        5 - the pull and exporting commands
        6 - the revision tree commands (even e.g. merge-base)
+       7 - the porcelainish commands concerning the working tree
 
 Second digit tells the particular command we are testing.
 
index 32dc9c5e74e80e2483ae0bf49f3dfd549c6d8471..360a67060e1d1fd501fed7729712213360c456b2 100755 (executable)
@@ -37,7 +37,9 @@ test_expect_success \
 test_expect_success \
     'pick top patch from topic branch into master' \
     'git-cherry-pick my-topic-branch^0 &&
-     git-checkout -f my-topic-branch
+     git-checkout -f my-topic-branch &&
+     git-branch master-merge master &&
+     git-branch my-topic-branch-merge my-topic-branch
 '
 
 test_debug \
@@ -50,4 +52,13 @@ test_expect_success \
     'rebase topic branch against new master and check git-am did not get halted' \
     'git-rebase master && test ! -d .dotest'
 
+if test -z "$no_python"
+then
+    test_expect_success \
+       'rebase --merge topic branch that was partially merged upstream' \
+       'git-checkout -f my-topic-branch-merge &&
+        git-rebase --merge master-merge &&
+        test ! -d .git/.dotest-merge'
+fi
+
 test_done
diff --git a/t/t3403-rebase-skip.sh b/t/t3403-rebase-skip.sh
new file mode 100755 (executable)
index 0000000..8ab63c5
--- /dev/null
@@ -0,0 +1,61 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Eric Wong
+#
+
+test_description='git rebase --merge --skip tests'
+
+. ./test-lib.sh
+
+# we assume the default git-am -3 --skip strategy is tested independently
+# and always works :)
+
+if test "$no_python"; then
+       echo "Skipping: no python => no recursive merge"
+       test_done
+       exit 0
+fi
+
+test_expect_success setup '
+       echo hello > hello &&
+       git add hello &&
+       git commit -m "hello" &&
+       git branch skip-reference &&
+
+       echo world >> hello &&
+       git commit -a -m "hello world" &&
+       echo goodbye >> hello &&
+       git commit -a -m "goodbye" &&
+
+       git checkout -f skip-reference &&
+       echo moo > hello &&
+       git commit -a -m "we should skip this" &&
+       echo moo > cow &&
+       git add cow &&
+       git commit -m "this should not be skipped" &&
+       git branch pre-rebase skip-reference &&
+       git branch skip-merge skip-reference
+       '
+
+test_expect_failure 'rebase with git am -3 (default)' 'git rebase master'
+
+test_expect_success 'rebase --skip with am -3' '
+       git reset --hard HEAD &&
+       git rebase --skip
+       '
+test_expect_success 'checkout skip-merge' 'git checkout -f skip-merge'
+
+test_expect_failure 'rebase with --merge' 'git rebase --merge master'
+
+test_expect_success 'rebase --skip with --merge' '
+       git reset --hard HEAD &&
+       git rebase --skip
+       '
+
+test_expect_success 'merge and reference trees equal' \
+       'test -z "`git-diff-tree skip-merge skip-reference`"'
+
+test_debug 'gitk --all & sleep 1'
+
+test_done
+
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
new file mode 100755 (executable)
index 0000000..4795872
--- /dev/null
@@ -0,0 +1,69 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Junio C Hamano
+#
+
+test_description='Format-patch skipping already incorporated patches'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+       for i in 1 2 3 4 5 6 7 8 9 10; do echo "$i"; done >file &&
+       git add file &&
+       git commit -m Initial &&
+       git checkout -b side &&
+
+       for i in 1 2 5 6 A B C 7 8 9 10; do echo "$i"; done >file &&
+       git update-index file &&
+       git commit -m "Side change #1" &&
+
+       for i in D E F; do echo "$i"; done >>file &&
+       git update-index file &&
+       git commit -m "Side change #2" &&
+       git tag C2 &&
+
+       for i in 5 6 1 2 3 A 4 B C 7 8 9 10 D E F; do echo "$i"; done >file &&
+       git update-index file &&
+       git commit -m "Side change #3" &&
+
+       git checkout master &&
+       git diff-tree -p C2 | git apply --index &&
+       git commit -m "Master accepts moral equivalent of #2"
+
+'
+
+test_expect_success "format-patch --ignore-if-in-upstream" '
+
+       git format-patch --stdout master..side >patch0 &&
+       cnt=`grep "^From " patch0 | wc -l` &&
+       test $cnt = 3
+
+'
+
+test_expect_success "format-patch --ignore-if-in-upstream" '
+
+       git format-patch --stdout \
+               --ignore-if-in-upstream master..side >patch1 &&
+       cnt=`grep "^From " patch1 | wc -l` &&
+       test $cnt = 2
+
+'
+
+test_expect_success "format-patch result applies" '
+
+       git checkout -b rebuild-0 master &&
+       git am -3 patch0 &&
+       cnt=`git rev-list master.. | wc -l` &&
+       test $cnt = 2
+'
+
+test_expect_success "format-patch --ignore-if-in-upstream result applies" '
+
+       git checkout -b rebuild-1 master &&
+       git am -3 patch1 &&
+       cnt=`git rev-list master.. | wc -l` &&
+       test $cnt = 2
+'
+
+test_done
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
new file mode 100755 (executable)
index 0000000..b64e8b7
--- /dev/null
@@ -0,0 +1,72 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Junio C Hamano
+#
+
+test_description='git-checkout tests.'
+
+. ./test-lib.sh
+
+fill () {
+       for i
+       do
+               echo "$i"
+       done
+}
+
+test_expect_success setup '
+
+       fill 1 2 3 4 5 >one &&
+       fill a b c d e >two &&
+       git add one two &&
+       git commit -m "Initial A one, A two" &&
+
+       git checkout -b side &&
+       fill 1 2 3 >one &&
+       fill A B C D E >three &&
+       rm -f two &&
+       git update-index --add --remove one two three &&
+       git commit -m "Side M one, D two, A three" &&
+
+       git checkout master
+'
+
+test_expect_success "checkout with dirty tree without -m" '
+
+       fill 0 1 2 3 4 5 >one &&
+       if git checkout side
+       then
+               echo Not happy
+               false
+       else
+               echo "happy - failed correctly"
+       fi
+
+'
+
+test_expect_success "checkout -m with dirty tree" '
+
+       git checkout -f master &&
+       git clean &&
+
+       fill 0 1 2 3 4 5 >one &&
+       git checkout -m side &&
+
+       fill "  master" "* side" >expect.branch &&
+       git branch >current.branch &&
+       diff expect.branch current.branch &&
+
+       fill "M one" "A three" "D       two" >expect.master &&
+       git diff --name-status master >current.master &&
+       diff expect.master current.master &&
+
+       fill "M one" >expect.side &&
+       git diff --name-status side >current.side &&
+       diff expect.side current.side &&
+
+       : >expect.index &&
+       git diff --cached >current.index &&
+       diff expect.index current.index
+'
+
+test_done
index 7b86f6965b5306f2162a9076f6ab3bbc1cc32a4b..2b70c3dcb4e3b1e73d7d5a088ae73de17540b6b7 100644 (file)
@@ -95,8 +95,8 @@ static void create_pack_file(void)
                int i;
                int args;
                const char **argv;
+               const char **p;
                char *buf;
-               char **p;
 
                if (create_full_pack) {
                        args = 10;
@@ -441,7 +441,7 @@ static int receive_needs(void)
 
 static int send_ref(const char *refname, const unsigned char *sha1)
 {
-       static char *capabilities = "multi_ack thin-pack side-band";
+       static const char *capabilities = "multi_ack thin-pack side-band";
        struct object *o = parse_object(sha1);
 
        if (!o)
index 2540e8a2ccc6781d23a42c03dadc050a3e233c39..2ce10b4c0d18aca467d3fb7a47badefa8c7c956f 100644 (file)
@@ -29,6 +29,9 @@ extern "C" {
 
 
 #define XDF_NEED_MINIMAL (1 << 1)
+#define XDF_IGNORE_WHITESPACE (1 << 2)
+#define XDF_IGNORE_WHITESPACE_CHANGE (1 << 3)
+#define XDF_WHITESPACE_FLAGS (XDF_IGNORE_WHITESPACE | XDF_IGNORE_WHITESPACE_CHANGE)
 
 #define XDL_PATCH_NORMAL '-'
 #define XDL_PATCH_REVERSE '+'
index b95ade2c1b58a48a91bf9a87c7a3893559580225..ed7ad2041c6f81df8b0191374c5b0e4e91683a70 100644 (file)
@@ -45,7 +45,7 @@ static long xdl_split(unsigned long const *ha1, long off1, long lim1,
                      long *kvdf, long *kvdb, int need_min, xdpsplit_t *spl,
                      xdalgoenv_t *xenv);
 static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, long chg2);
-static int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo);
+static int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags);
 
 
 
@@ -397,7 +397,7 @@ static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1,
 }
 
 
-static int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo) {
+static int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) {
        long ix, ixo, ixs, ixref, grpsiz, nrec = xdf->nrec;
        char *rchg = xdf->rchg, *rchgo = xdfo->rchg;
        xrecord_t **recs = xdf->recs;
@@ -440,7 +440,7 @@ static int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo) {
                         * the group.
                         */
                        while (ixs > 0 && recs[ixs - 1]->ha == recs[ix - 1]->ha &&
-                              XDL_RECMATCH(recs[ixs - 1], recs[ix - 1])) {
+                              xdl_recmatch(recs[ixs - 1]->ptr, recs[ixs - 1]->size, recs[ix - 1]->ptr, recs[ix - 1]->size, flags)) {
                                rchg[--ixs] = 1;
                                rchg[--ix] = 0;
 
@@ -468,7 +468,7 @@ static int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo) {
                         * the group.
                         */
                        while (ix < nrec && recs[ixs]->ha == recs[ix]->ha &&
-                              XDL_RECMATCH(recs[ixs], recs[ix])) {
+                              xdl_recmatch(recs[ixs]->ptr, recs[ixs]->size, recs[ix]->ptr, recs[ix]->size, flags)) {
                                rchg[ixs++] = 0;
                                rchg[ix++] = 1;
 
@@ -546,8 +546,8 @@ int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
 
                return -1;
        }
-       if (xdl_change_compact(&xe.xdf1, &xe.xdf2) < 0 ||
-           xdl_change_compact(&xe.xdf2, &xe.xdf1) < 0 ||
+       if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 ||
+           xdl_change_compact(&xe.xdf2, &xe.xdf1, xpp->flags) < 0 ||
            xdl_build_script(&xe, &xscr) < 0) {
 
                xdl_free_env(&xe);
index dd8f3c986be9cefa8ac61d54ec9646bbc62234fd..d3b72716b58273169904a4351f02fab7ab2fc6e9 100644 (file)
@@ -55,6 +55,5 @@ void xdl_free_script(xdchange_t *xscr);
 int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
                  xdemitconf_t const *xecfg);
 
-
 #endif /* #if !defined(XDIFFI_H) */
 
index 78f02603b8abde66cb170ca867345cbb70660f60..4c2fde80c143b3aaf086dc36c1a050851f83feaa 100644 (file)
@@ -33,7 +33,6 @@
 #define XDL_ISDIGIT(c) ((c) >= '0' && (c) <= '9')
 #define XDL_HASHLONG(v, b) (((unsigned long)(v) * GR_PRIME) >> ((CHAR_BIT * sizeof(unsigned long)) - (b)))
 #define XDL_PTRFREE(p) do { if (p) { xdl_free(p); (p) = NULL; } } while (0)
-#define XDL_RECMATCH(r1, r2) ((r1)->size == (r2)->size && memcmp((r1)->ptr, (r2)->ptr, (r1)->size) == 0)
 #define XDL_LE32_PUT(p, v) \
 do { \
        unsigned char *__p = (unsigned char *) (p); \
index add5a75c77f1164703138a3cc781d87bd0d72b82..1be7b3195059bebffe83d17301f3ff0fb261799e 100644 (file)
@@ -43,12 +43,13 @@ typedef struct s_xdlclassifier {
        xdlclass_t **rchash;
        chastore_t ncha;
        long count;
+       long flags;
 } xdlclassifier_t;
 
 
 
 
-static int xdl_init_classifier(xdlclassifier_t *cf, long size);
+static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags);
 static void xdl_free_classifier(xdlclassifier_t *cf);
 static int xdl_classify_record(xdlclassifier_t *cf, xrecord_t **rhash, unsigned int hbits,
                               xrecord_t *rec);
@@ -63,9 +64,11 @@ static int xdl_optimize_ctxs(xdfile_t *xdf1, xdfile_t *xdf2);
 
 
 
-static int xdl_init_classifier(xdlclassifier_t *cf, long size) {
+static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags) {
        long i;
 
+       cf->flags = flags;
+
        cf->hbits = xdl_hashbits((unsigned int) size);
        cf->hsize = 1 << cf->hbits;
 
@@ -103,8 +106,9 @@ static int xdl_classify_record(xdlclassifier_t *cf, xrecord_t **rhash, unsigned
        line = rec->ptr;
        hi = (long) XDL_HASHLONG(rec->ha, cf->hbits);
        for (rcrec = cf->rchash[hi]; rcrec; rcrec = rcrec->next)
-               if (rcrec->ha == rec->ha && rcrec->size == rec->size &&
-                   !memcmp(line, rcrec->line, rec->size))
+               if (rcrec->ha == rec->ha &&
+                               xdl_recmatch(rcrec->line, rcrec->size,
+                                       rec->ptr, rec->size, cf->flags))
                        break;
 
        if (!rcrec) {
@@ -173,7 +177,7 @@ static int xdl_prepare_ctx(mmfile_t *mf, long narec, xpparam_t const *xpp,
                                top = blk + bsize;
                        }
                        prev = cur;
-                       hav = xdl_hash_record(&cur, top);
+                       hav = xdl_hash_record(&cur, top, xpp->flags);
                        if (nrec >= narec) {
                                narec *= 2;
                                if (!(rrecs = (xrecord_t **) xdl_realloc(recs, narec * sizeof(xrecord_t *)))) {
@@ -268,7 +272,7 @@ int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
        enl1 = xdl_guess_lines(mf1) + 1;
        enl2 = xdl_guess_lines(mf2) + 1;
 
-       if (xdl_init_classifier(&cf, enl1 + enl2 + 1) < 0) {
+       if (xdl_init_classifier(&cf, enl1 + enl2 + 1, xpp->flags) < 0) {
 
                return -1;
        }
index f91b4034751df72996afbe6b4b04a9f05638d322..f7bdd395ad1f69067654a754b4d60fcc7c359275 100644 (file)
@@ -186,12 +186,61 @@ long xdl_guess_lines(mmfile_t *mf) {
        return nl + 1;
 }
 
+int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
+{
+       int i1, i2;
+
+       if (flags & XDF_IGNORE_WHITESPACE) {
+               for (i1 = i2 = 0; i1 < s1 && i2 < s2; i1++, i2++) {
+                       if (isspace(l1[i1]))
+                               while (isspace(l1[i1]) && i1 < s1)
+                                       i1++;
+                       else if (isspace(l2[i2]))
+                               while (isspace(l2[i2]) && i2 < s2)
+                                       i2++;
+                       else if (l1[i1] != l2[i2])
+                               return l2[i2] - l1[i1];
+               }
+               if (i1 >= s1)
+                       return 1;
+               else if (i2 >= s2)
+                       return -1;
+       } else if (flags & XDF_IGNORE_WHITESPACE_CHANGE) {
+               for (i1 = i2 = 0; i1 < s1 && i2 < s2; i1++, i2++) {
+                       if (isspace(l1[i1])) {
+                               if (!isspace(l2[i2]))
+                                       return -1;
+                               while (isspace(l1[i1]) && i1 < s1)
+                                       i1++;
+                               while (isspace(l2[i2]) && i2 < s2)
+                                       i2++;
+                       } else if (l1[i1] != l2[i2])
+                               return l2[i2] - l1[i1];
+               }
+               if (i1 >= s1)
+                       return 1;
+               else if (i2 >= s2)
+                       return -1;
+       } else
+               return s1 == s2 && !memcmp(l1, l2, s1);
+
+       return 0;
+}
 
-unsigned long xdl_hash_record(char const **data, char const *top) {
+unsigned long xdl_hash_record(char const **data, char const *top, long flags) {
        unsigned long ha = 5381;
        char const *ptr = *data;
 
        for (; ptr < top && *ptr != '\n'; ptr++) {
+               if (isspace(*ptr) && (flags & XDF_WHITESPACE_FLAGS)) {
+                       while (ptr < top && isspace(*ptr) && ptr[1] != '\n')
+                               ptr++;
+                       if (flags & XDF_IGNORE_WHITESPACE_CHANGE) {
+                               ha += (ha << 5);
+                               ha ^= (unsigned long) ' ';
+                       }
+                       continue;
+               }
                ha += (ha << 5);
                ha ^= (unsigned long) *ptr;
        }
index 08691a2447126a8a8f96bb8a0369f21524c58c39..70d8b9838a7333bc5a1edb93cf0e9abdbcf146cc 100644 (file)
@@ -34,7 +34,8 @@ void *xdl_cha_alloc(chastore_t *cha);
 void *xdl_cha_first(chastore_t *cha);
 void *xdl_cha_next(chastore_t *cha);
 long xdl_guess_lines(mmfile_t *mf);
-unsigned long xdl_hash_record(char const **data, char const *top);
+int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags);
+unsigned long xdl_hash_record(char const **data, char const *top, long flags);
 unsigned int xdl_hashbits(unsigned int size);
 int xdl_num_out(char *out, long val);
 long xdl_atol(char const *str, char const **next);