Merge branch 'th/diff'
authorJunio C Hamano <junkio@cox.net>
Wed, 5 Jul 2006 23:31:24 +0000 (16:31 -0700)
committerJunio C Hamano <junkio@cox.net>
Wed, 5 Jul 2006 23:31:24 +0000 (16:31 -0700)
* th/diff:
builtin-diff: turn recursive on when defaulting to --patch format.
t4013: note improvements brought by the new output code.
t4013: add format-patch tests.
format-patch: fix diff format option implementation
combine-diff.c: type sanity.
t4013 test updates for new output code.
Fix some more diff options changes.
Fix diff-tree -s
log --raw: Don't descend into subdirectories by default
diff-tree: Use ---\n as a message separator
Print empty line between raw, stat, summary and patch
t4013: add more tests around -c and --cc
whatchanged: Default to DIFF_FORMAT_RAW
Don't xcalloc() struct diffstat_t
Add msg_sep to diff_options
DIFF_FORMAT_RAW is not default anymore
Set default diff output format after parsing command line
Make --raw option available for all diff commands
Merge with_raw, with_stat and summary variables to output_format
t4013: add tests for diff/log family output options.

61 files changed:
Documentation/config.txt
Documentation/git-commit.txt
Makefile
builtin-fmt-merge-msg.c [new file with mode: 0644]
builtin-grep.c
builtin-log.c
builtin-mailinfo.c
builtin-rev-parse.c
builtin.h
cache.h
combine-diff.c
commit.c
config.c
connect.c
contrib/emacs/vc-git.el
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]
csum-file.c
daemon.c
describe.c
diff.c
diff.h
environment.c
fsck-objects.c
git-am.sh
git-annotate.perl
git-checkout.sh
git-clone.sh
git-commit.sh
git-cvsimport.perl
git-fmt-merge-msg.perl [deleted file]
git-merge.sh
git-pull.sh
git-quiltimport.sh
git-rebase.sh
git-repack.sh
git-send-email.perl
git-svnimport.perl
git.c
http-push.c
imap-send.c
name-rev.c
object.c
object.h
pack-objects.c
peek-remote.c
quote.c
revision.c
send-pack.c
sha1_file.c
t/README
t/t4014-format-patch.sh [new file with mode: 0755]
t/t7201-co.sh [new file with mode: 0755]
t/t8001-annotate.sh
t/t9001-send-email.sh
upload-pack.c
index a04c5adf8e522e65fae58ec32db07c46aeebe070..16bdd55233f2f1a11a80f60dfa85b84145c836f6 100644 (file)
@@ -91,6 +91,12 @@ core.warnAmbiguousRefs::
        If true, git will warn you if the ref name you passed it is ambiguous
        and might match multiple refs in the .git/refs/ tree. True by default.
 
+core.compression:
+       An integer -1..9, indicating the compression level for objects that
+       are not in a pack file. -1 is the zlib and git default. 0 means no
+       compression, and 1..9 are various speed/size tradeoffs, 9 being
+       slowest.
+
 alias.*::
        Command aliases for the gitlink:git[1] command wrapper - e.g.
        after defining "alias.last = cat-file commit HEAD", the invocation
index 0fe66f2d0c84003d285fed426181998063e86b96..517a86b238a91a1c853b5fbe331a5cc984e95878 100644 (file)
@@ -15,9 +15,9 @@ SYNOPSIS
 DESCRIPTION
 -----------
 Updates the index file for given paths, or all modified files if
-'-a' is specified, and makes a commit object.  The command
-VISUAL and EDITOR environment variables to edit the commit log
-message.
+'-a' is specified, and makes a commit object.  The command specified
+by either the VISUAL or EDITOR environment variables are used to edit
+the commit log message.
 
 Several environment variable are used during commits.  They are
 documented in gitlink:git-commit-tree[1].
index cde619c498da717ea665430f7d395358d0b1d06e..a78d05ddaa640ad50792c4fb8c78b957eb83400c 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -131,7 +131,7 @@ SCRIPT_SH = \
 
 SCRIPT_PERL = \
        git-archimport.perl git-cvsimport.perl git-relink.perl \
-       git-shortlog.perl git-fmt-merge-msg.perl git-rerere.perl \
+       git-shortlog.perl git-rerere.perl \
        git-annotate.perl git-cvsserver.perl \
        git-svnimport.perl git-mv.perl git-cvsexportcommit.perl \
        git-send-email.perl
@@ -173,7 +173,8 @@ BUILT_INS = git-log$X git-whatchanged$X git-show$X git-update-ref$X \
        git-ls-files$X git-ls-tree$X git-get-tar-commit-id$X \
        git-read-tree$X git-commit-tree$X git-write-tree$X \
        git-apply$X git-show-branch$X git-diff-files$X git-update-index$X \
-       git-diff-index$X git-diff-stages$X git-diff-tree$X git-cat-file$X
+       git-diff-index$X git-diff-stages$X git-diff-tree$X git-cat-file$X \
+       git-fmt-merge-msg$X
 
 # what 'all' will build and 'install' will install, in gitexecdir
 ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
@@ -229,7 +230,7 @@ BUILTIN_OBJS = \
        builtin-apply.o builtin-show-branch.o builtin-diff-files.o \
        builtin-diff-index.o builtin-diff-stages.o builtin-diff-tree.o \
        builtin-cat-file.o builtin-mailsplit.o builtin-stripspace.o \
-       builtin-update-ref.o
+       builtin-update-ref.o builtin-fmt-merge-msg.o
 
 GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
 LIBS = $(GITLIBS) -lz
@@ -587,11 +588,11 @@ git-ssh-push$X: rsh.o
 git-imap-send$X: imap-send.o $(LIB_FILE)
 
 http.o http-fetch.o http-push.o: http.h
-git-http-fetch$X: fetch.o http.o http-fetch.o $(LIB_FILE)
+git-http-fetch$X: fetch.o http.o http-fetch.o $(GITLIBS)
        $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
                $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
 
-git-http-push$X: revision.o http.o http-push.o $(LIB_FILE)
+git-http-push$X: revision.o http.o http-push.o $(GITLIBS)
        $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
                $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
 
diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c
new file mode 100644 (file)
index 0000000..6527482
--- /dev/null
@@ -0,0 +1,355 @@
+#include "cache.h"
+#include "commit.h"
+#include "diff.h"
+#include "revision.h"
+#include "tag.h"
+
+static const char *fmt_merge_msg_usage =
+       "git-fmt-merge-msg [--summary] [--no-summary] [--file <file>]";
+
+static int merge_summary = 0;
+
+static int fmt_merge_msg_config(const char *key, const char *value)
+{
+       if (!strcmp("merge.summary", key))
+               merge_summary = git_config_bool(key, value);
+       return 0;
+}
+
+struct list {
+       char **list;
+       void **payload;
+       unsigned nr, alloc;
+};
+
+static void append_to_list(struct list *list, char *value, void *payload)
+{
+       if (list->nr == list->alloc) {
+               list->alloc += 32;
+               list->list = realloc(list->list, sizeof(char *) * list->alloc);
+               list->payload = realloc(list->payload,
+                               sizeof(char *) * list->alloc);
+       }
+       list->payload[list->nr] = payload;
+       list->list[list->nr++] = value;
+}
+
+static int find_in_list(struct list *list, char *value)
+{
+       int i;
+
+       for (i = 0; i < list->nr; i++)
+               if (!strcmp(list->list[i], value))
+                       return i;
+
+       return -1;
+}
+
+static void free_list(struct list *list)
+{
+       int i;
+
+       if (list->alloc == 0)
+               return;
+
+       for (i = 0; i < list->nr; i++) {
+               free(list->list[i]);
+               if (list->payload[i])
+                       free(list->payload[i]);
+       }
+       free(list->list);
+       free(list->payload);
+       list->nr = list->alloc = 0;
+}
+
+struct src_data {
+       struct list branch, tag, r_branch, generic;
+       int head_status;
+};
+
+static struct list srcs = { NULL, NULL, 0, 0};
+static struct list origins = { NULL, NULL, 0, 0};
+
+static int handle_line(char *line)
+{
+       int i, len = strlen(line);
+       unsigned char *sha1;
+       char *src, *origin;
+       struct src_data *src_data;
+
+       if (len < 43 || line[40] != '\t')
+               return 1;
+
+       if (!strncmp(line + 41, "not-for-merge", 13))
+               return 0;
+
+       if (line[41] != '\t')
+               return 2;
+
+       line[40] = 0;
+       sha1 = xmalloc(20);
+       i = get_sha1(line, sha1);
+       line[40] = '\t';
+       if (i)
+               return 3;
+
+       if (line[len - 1] == '\n')
+               line[len - 1] = 0;
+       line += 42;
+
+       src = strstr(line, " of ");
+       if (src) {
+               *src = 0;
+               src += 4;
+       } else
+               src = "HEAD";
+
+       i = find_in_list(&srcs, src);
+       if (i < 0) {
+               i = srcs.nr;
+               append_to_list(&srcs, strdup(src),
+                               xcalloc(1, sizeof(struct src_data)));
+       }
+       src_data = srcs.payload[i];
+
+       if (!strncmp(line, "branch ", 7)) {
+               origin = strdup(line + 7);
+               append_to_list(&src_data->branch, origin, NULL);
+               src_data->head_status |= 2;
+       } else if (!strncmp(line, "tag ", 4)) {
+               origin = line;
+               append_to_list(&src_data->tag, strdup(origin + 4), NULL);
+               src_data->head_status |= 2;
+       } else if (!strncmp(line, "remote branch ", 14)) {
+               origin = strdup(line + 14);
+               append_to_list(&src_data->r_branch, origin, NULL);
+               src_data->head_status |= 2;
+       } else if (!strcmp(line, "HEAD")) {
+               origin = strdup(src);
+               src_data->head_status |= 1;
+       } else {
+               origin = strdup(src);
+               append_to_list(&src_data->generic, strdup(line), NULL);
+               src_data->head_status |= 2;
+       }
+
+       if (!strcmp(".", src) || !strcmp(src, origin)) {
+               int len = strlen(origin);
+               if (origin[0] == '\'' && origin[len - 1] == '\'') {
+                       char *new_origin = malloc(len - 1);
+                       memcpy(new_origin, origin + 1, len - 2);
+                       new_origin[len - 1] = 0;
+                       origin = new_origin;
+               } else
+                       origin = strdup(origin);
+       } else {
+               char *new_origin = malloc(strlen(origin) + strlen(src) + 5);
+               sprintf(new_origin, "%s of %s", origin, src);
+               origin = new_origin;
+       }
+       append_to_list(&origins, origin, sha1);
+       return 0;
+}
+
+static void print_joined(const char *singular, const char *plural,
+               struct list *list)
+{
+       if (list->nr == 0)
+               return;
+       if (list->nr == 1) {
+               printf("%s%s", singular, list->list[0]);
+       } else {
+               int i;
+               printf("%s", plural);
+               for (i = 0; i < list->nr - 1; i++)
+                       printf("%s%s", i > 0 ? ", " : "", list->list[i]);
+               printf(" and %s", list->list[list->nr - 1]);
+       }
+}
+
+static void shortlog(const char *name, unsigned char *sha1,
+               struct commit *head, struct rev_info *rev, int limit)
+{
+       int i, count = 0;
+       struct commit *commit;
+       struct object *branch;
+       struct list subjects = { NULL, NULL, 0, 0 };
+       int flags = UNINTERESTING | TREECHANGE | SEEN | SHOWN | ADDED;
+
+       branch = deref_tag(parse_object(sha1), sha1_to_hex(sha1), 40);
+       if (!branch || branch->type != TYPE_COMMIT)
+               return;
+
+       setup_revisions(0, NULL, rev, NULL);
+       rev->ignore_merges = 1;
+       add_pending_object(rev, branch, name);
+       add_pending_object(rev, &head->object, "^HEAD");
+       head->object.flags |= UNINTERESTING;
+       prepare_revision_walk(rev);
+       while ((commit = get_revision(rev)) != NULL) {
+               char *oneline, *bol, *eol;
+
+               /* ignore merges */
+               if (commit->parents && commit->parents->next)
+                       continue;
+
+               count++;
+               if (subjects.nr > limit)
+                       continue;
+
+               bol = strstr(commit->buffer, "\n\n");
+               if (!bol) {
+                       append_to_list(&subjects, strdup(sha1_to_hex(
+                                                       commit->object.sha1)),
+                                       NULL);
+                       continue;
+               }
+
+               bol += 2;
+               eol = strchr(bol, '\n');
+
+               if (eol) {
+                       int len = eol - bol;
+                       oneline = malloc(len + 1);
+                       memcpy(oneline, bol, len);
+                       oneline[len] = 0;
+               } else
+                       oneline = strdup(bol);
+               append_to_list(&subjects, oneline, NULL);
+       }
+
+       if (count > limit)
+               printf("\n* %s: (%d commits)\n", name, count);
+       else
+               printf("\n* %s:\n", name);
+
+       for (i = 0; i < subjects.nr; i++)
+               if (i >= limit)
+                       printf("  ...\n");
+               else
+                       printf("  %s\n", subjects.list[i]);
+
+       clear_commit_marks((struct commit *)branch, flags);
+       clear_commit_marks(head, flags);
+       free_commit_list(rev->commits);
+       rev->commits = NULL;
+       rev->pending.nr = 0;
+
+       free_list(&subjects);
+}
+
+int cmd_fmt_merge_msg(int argc, char **argv, char **envp)
+{
+       int limit = 20, i = 0;
+       char line[1024];
+       FILE *in = stdin;
+       const char *sep = "";
+       unsigned char head_sha1[20];
+       const char *head, *current_branch;
+
+       git_config(fmt_merge_msg_config);
+
+       while (argc > 1) {
+               if (!strcmp(argv[1], "--summary"))
+                       merge_summary = 1;
+               else if (!strcmp(argv[1], "--no-summary"))
+                       merge_summary = 0;
+               else if (!strcmp(argv[1], "-F") || !strcmp(argv[1], "--file")) {
+                       if (argc < 2)
+                               die ("Which file?");
+                       if (!strcmp(argv[2], "-"))
+                               in = stdin;
+                       else {
+                               fclose(in);
+                               in = fopen(argv[2], "r");
+                       }
+                       argc--; argv++;
+               } else
+                       break;
+               argc--; argv++;
+       }
+
+       if (argc > 1)
+               usage(fmt_merge_msg_usage);
+
+       /* get current branch */
+       head = strdup(git_path("HEAD"));
+       current_branch = resolve_ref(head, head_sha1, 1);
+       current_branch += strlen(head) - 4;
+       free((char *)head);
+       if (!strncmp(current_branch, "refs/heads/", 11))
+               current_branch += 11;
+
+       while (fgets(line, sizeof(line), in)) {
+               i++;
+               if (line[0] == 0)
+                       continue;
+               if (handle_line(line))
+                       die ("Error in line %d: %s", i, line);
+       }
+
+       printf("Merge ");
+       for (i = 0; i < srcs.nr; i++) {
+               struct src_data *src_data = srcs.payload[i];
+               const char *subsep = "";
+
+               printf(sep);
+               sep = "; ";
+
+               if (src_data->head_status == 1) {
+                       printf(srcs.list[i]);
+                       continue;
+               }
+               if (src_data->head_status == 3) {
+                       subsep = ", ";
+                       printf("HEAD");
+               }
+               if (src_data->branch.nr) {
+                       printf(subsep);
+                       subsep = ", ";
+                       print_joined("branch ", "branches ", &src_data->branch);
+               }
+               if (src_data->r_branch.nr) {
+                       printf(subsep);
+                       subsep = ", ";
+                       print_joined("remote branch ", "remote branches ",
+                                       &src_data->r_branch);
+               }
+               if (src_data->tag.nr) {
+                       printf(subsep);
+                       subsep = ", ";
+                       print_joined("tag ", "tags ", &src_data->tag);
+               }
+               if (src_data->generic.nr) {
+                       printf(subsep);
+                       print_joined("commit ", "commits ", &src_data->generic);
+               }
+               if (strcmp(".", srcs.list[i]))
+                       printf(" of %s", srcs.list[i]);
+       }
+
+       if (!strcmp("master", current_branch))
+               putchar('\n');
+       else
+               printf(" into %s\n", current_branch);
+
+       if (merge_summary) {
+               struct commit *head;
+               struct rev_info rev;
+
+               head = lookup_commit(head_sha1);
+               init_revisions(&rev);
+               rev.commit_format = CMIT_FMT_ONELINE;
+               rev.ignore_merges = 1;
+               rev.limited = 1;
+
+               for (i = 0; i < origins.nr; i++)
+                       shortlog(origins.list[i], origins.payload[i],
+                                       head, &rev, limit);
+       }
+
+       /* No cleanup yet; is standalone anyway */
+
+       return 0;
+}
+
index 2e7986cecefc964f665b10d363569951c1f40293..6973c66704326804def00a22bfdcdb487dcad4c2 100644 (file)
@@ -446,7 +446,7 @@ static int exec_grep(int argc, const char **argv)
 
 static int external_grep(struct grep_opt *opt, const char **paths, int cached)
 {
-       int i, nr, argc, hit, len;
+       int i, nr, argc, hit, len, status;
        const char *argv[MAXARGS+1];
        char randarg[ARGBUF];
        char *argptr = randarg;
@@ -536,12 +536,17 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
                argv[argc++] = name;
                if (argc < MAXARGS)
                        continue;
-               hit += exec_grep(argc, argv);
+               status = exec_grep(argc, argv);
+               if (0 < status)
+                       hit = 1;
                argc = nr;
        }
-       if (argc > nr)
-               hit += exec_grep(argc, argv);
-       return 0;
+       if (argc > nr) {
+               status = exec_grep(argc, argv);
+               if (0 < status)
+                       hit = 1;
+       }
+       return hit;
 }
 
 static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
@@ -652,6 +657,13 @@ static int grep_object(struct grep_opt *opt, const char **paths,
 static const char builtin_grep_usage[] =
 "git-grep <option>* <rev>* [-e] <pattern> [<path>...]";
 
+static const char emsg_invalid_context_len[] =
+"%s: invalid context length argument";
+static const char emsg_missing_context_len[] =
+"missing context length argument";
+static const char emsg_missing_argument[] =
+"option requires an argument -%s";
+
 int cmd_grep(int argc, const char **argv, char **envp)
 {
        int hit = 0;
@@ -759,7 +771,7 @@ int cmd_grep(int argc, const char **argv, char **envp)
                        case 'A': case 'B': case 'C':
                                if (!arg[2]) {
                                        if (argc <= 1)
-                                               usage(builtin_grep_usage);
+                                               die(emsg_missing_context_len);
                                        scan = *++argv;
                                        argc--;
                                }
@@ -771,7 +783,7 @@ int cmd_grep(int argc, const char **argv, char **envp)
                                break;
                        }
                        if (sscanf(scan, "%u", &num) != 1)
-                               usage(builtin_grep_usage);
+                               die(emsg_invalid_context_len, scan);
                        switch (arg[1]) {
                        case 'A':
                                opt.post_context = num;
@@ -790,7 +802,7 @@ int cmd_grep(int argc, const char **argv, char **envp)
                        int lno = 0;
                        char buf[1024];
                        if (argc <= 1)
-                               usage(builtin_grep_usage);
+                               die(emsg_missing_argument, arg);
                        patterns = fopen(argv[1], "r");
                        if (!patterns)
                                die("'%s': %s", argv[1], strerror(errno));
@@ -815,10 +827,14 @@ int cmd_grep(int argc, const char **argv, char **envp)
                                argc--;
                                continue;
                        }
-                       usage(builtin_grep_usage);
+                       die(emsg_missing_argument, arg);
                }
-               if (!strcmp("--", arg))
+               if (!strcmp("--", arg)) {
+                       /* later processing wants to have this at argv[1] */
+                       argv--;
+                       argc++;
                        break;
+               }
                if (*arg == '-')
                        usage(builtin_grep_usage);
 
index bcd4e5e161d4f8a315aebb8f741b0a1b9870cb71..864c6cd9ea1777bc15b12333b88b2e3a3f013321 100644 (file)
@@ -161,6 +161,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;
@@ -171,6 +230,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];
        }
@@ -265,14 +328,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)
index f12d5e68f6778fbf96c85b81d250cf7f230edf1d..d9e5483bd577733c241c9c166a43ce28ebe80328 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -49,6 +49,7 @@ extern int cmd_cat_file(int argc, const char **argv, char **envp);
 extern int cmd_rev_parse(int argc, const char **argv, char **envp);
 extern int cmd_update_index(int argc, const char **argv, char **envp);
 extern int cmd_update_ref(int argc, const char **argv, char **envp);
+extern int cmd_fmt_merge_msg(int argc, const char **argv, char **envp);
 
 extern int cmd_write_tree(int argc, const char **argv, char **envp);
 extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix);
diff --git a/cache.h b/cache.h
index 87199396a05fbbbb695379119535eb9eefae2514..7b5c91c996fc58d7e5f743bdf07739ac1c1487bb 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -183,6 +183,7 @@ extern int log_all_ref_updates;
 extern int warn_ambiguous_refs;
 extern int shared_repository;
 extern const char *apply_default_whitespace;
+extern int zlib_compression_level;
 
 #define GIT_REPO_VERSION 0
 extern int repository_format_version;
@@ -321,13 +322,17 @@ struct ref {
        char name[FLEX_ARRAY]; /* more */
 };
 
+#define REF_NORMAL     (1u << 0)
+#define REF_HEADS      (1u << 1)
+#define REF_TAGS       (1u << 2)
+
 extern int git_connect(int fd[2], char *url, const char *prog);
 extern int finish_connect(pid_t pid);
 extern int path_match(const char *path, int nr, char **match);
 extern int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
                      int nr_refspec, char **refspec, int all);
 extern int get_ack(int fd, unsigned char *result_sha1);
-extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, int ignore_funny);
+extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, unsigned int flags);
 extern int server_supports(const char *feature);
 
 extern struct packed_git *parse_pack_index(unsigned char *sha1);
index 2fd0ced3957a41d8c383b2487ddfdea77d1b3985..caffb926ea49de03c4ced157736626719439a756 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 ec44827da4afbcf89a5eed37c7f557a7ffdce35d..8445f7dcab3bdf96326b8d6a3e1b8c8a8161cb23 100644 (file)
--- a/config.c
+++ b/config.c
@@ -244,9 +244,9 @@ int git_config_bool(const char *name, const char *value)
                return 1;
        if (!*value)
                return 0;
-       if (!strcasecmp(value, "true"))
+       if (!strcasecmp(value, "true") || !strcasecmp(value, "yes"))
                return 1;
-       if (!strcasecmp(value, "false"))
+       if (!strcasecmp(value, "false") || !strcasecmp(value, "no"))
                return 0;
        return git_config_int(name, value) != 0;
 }
@@ -279,6 +279,16 @@ int git_default_config(const char *var, const char *value)
                return 0;
        }
 
+       if (!strcmp(var, "core.compression")) {
+               int level = git_config_int(var, value);
+               if (level == -1)
+                       level = Z_DEFAULT_COMPRESSION;
+               else if (level < 0 || level > Z_BEST_COMPRESSION)
+                       die("bad zlib compression level %d", level);
+               zlib_compression_level = level;
+               return 0;
+       }
+
        if (!strcmp(var, "user.name")) {
                strlcpy(git_default_name, value, sizeof(git_default_name));
                return 0;
index 66e78a29054a121c92beca3d997105f0137c170d..4422a0d8d38c225c6c4716ce8ff826bc1acbd981 100644 (file)
--- a/connect.c
+++ b/connect.c
 
 static char *server_capabilities = NULL;
 
+static int check_ref(const char *name, int len, unsigned int flags)
+{
+       if (!flags)
+               return 1;
+
+       if (len > 45 || memcmp(name, "refs/", 5))
+               return 0;
+
+       /* Skip the "refs/" part */
+       name += 5;
+       len -= 5;
+
+       /* REF_NORMAL means that we don't want the magic fake tag refs */
+       if ((flags & REF_NORMAL) && check_ref_format(name) < 0)
+               return 0;
+
+       /* REF_HEADS means that we want regular branch heads */
+       if ((flags & REF_HEADS) && !memcmp(name, "heads/", 6))
+               return 1;
+
+       /* REF_TAGS means that we want tags */
+       if ((flags & REF_TAGS) && !memcmp(name, "tags/", 5))
+               return 1;
+
+       /* All type bits clear means that we are ok with anything */
+       return !(flags & ~REF_NORMAL);
+}
+
 /*
  * Read all the refs from the other end
  */
 struct ref **get_remote_heads(int in, struct ref **list,
-                             int nr_match, char **match, int ignore_funny)
+                             int nr_match, char **match,
+                             unsigned int flags)
 {
        *list = NULL;
        for (;;) {
@@ -43,10 +72,8 @@ struct ref **get_remote_heads(int in, struct ref **list,
                        server_capabilities = strdup(name + name_len + 1);
                }
 
-               if (ignore_funny && 45 < len && !memcmp(name, "refs/", 5) &&
-                   check_ref_format(name + 5))
+               if (!check_ref(name, name_len, flags))
                        continue;
-
                if (nr_match && !path_match(name, nr_match, match))
                        continue;
                ref = xcalloc(1, sizeof(*ref) + len - 40);
@@ -328,9 +355,9 @@ static enum protocol get_protocol(const char *name)
  */
 static int git_tcp_connect_sock(char *host)
 {
-       int sockfd = -1;
+       int sockfd = -1, saved_errno = 0;
        char *colon, *end;
-       char *port = STR(DEFAULT_GIT_PORT);
+       const char *port = STR(DEFAULT_GIT_PORT);
        struct addrinfo hints, *ai0, *ai;
        int gai;
 
@@ -362,9 +389,12 @@ static int git_tcp_connect_sock(char *host)
        for (ai0 = ai; ai; ai = ai->ai_next) {
                sockfd = socket(ai->ai_family,
                                ai->ai_socktype, ai->ai_protocol);
-               if (sockfd < 0)
+               if (sockfd < 0) {
+                       saved_errno = errno;
                        continue;
+               }
                if (connect(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) {
+                       saved_errno = errno;
                        close(sockfd);
                        sockfd = -1;
                        continue;
@@ -375,7 +405,7 @@ static int git_tcp_connect_sock(char *host)
        freeaddrinfo(ai0);
 
        if (sockfd < 0)
-               die("unable to connect a socket (%s)", strerror(errno));
+               die("unable to connect a socket (%s)", strerror(saved_errno));
 
        return sockfd;
 }
@@ -387,7 +417,7 @@ static int git_tcp_connect_sock(char *host)
  */
 static int git_tcp_connect_sock(char *host)
 {
-       int sockfd = -1;
+       int sockfd = -1, saved_errno = 0;
        char *colon, *end;
        char *port = STR(DEFAULT_GIT_PORT), *ep;
        struct hostent *he;
@@ -426,8 +456,10 @@ static int git_tcp_connect_sock(char *host)
 
        for (ap = he->h_addr_list; *ap; ap++) {
                sockfd = socket(he->h_addrtype, SOCK_STREAM, 0);
-               if (sockfd < 0)
+               if (sockfd < 0) {
+                       saved_errno = errno;
                        continue;
+               }
 
                memset(&sa, 0, sizeof sa);
                sa.sin_family = he->h_addrtype;
@@ -435,6 +467,7 @@ static int git_tcp_connect_sock(char *host)
                memcpy(&sa.sin_addr, *ap, he->h_length);
 
                if (connect(sockfd, (struct sockaddr *)&sa, sizeof sa) < 0) {
+                       saved_errno = errno;
                        close(sockfd);
                        sockfd = -1;
                        continue;
@@ -443,7 +476,7 @@ static int git_tcp_connect_sock(char *host)
        }
 
        if (sockfd < 0)
-               die("unable to connect a socket (%s)", strerror(errno));
+               die("unable to connect a socket (%s)", strerror(saved_errno));
 
        return sockfd;
 }
@@ -451,8 +484,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);
 
@@ -522,10 +554,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;
@@ -643,9 +674,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 2453cdcfae1f168df4533745e5730215be2af3c7..3f6ed699f0848cab6709e736e245b968e963978c 100644 (file)
   "Register FILE into the git version-control system."
   (vc-git--run-command file "update-index" "--add" "--"))
 
-(defun vc-git-print-log (file)
+(defun vc-git-print-log (file &optional buffer)
   (let ((name (file-relative-name file))
         (coding-system-for-read git-commits-coding-system))
-    (vc-do-command nil 'async "git" name "rev-list" "--pretty" "HEAD" "--")))
+    (vc-do-command buffer 'async "git" name "rev-list" "--pretty" "HEAD" "--")))
 
-(defun vc-git-diff (file &optional rev1 rev2)
-  (let ((name (file-relative-name file)))
+(defun vc-git-diff (file &optional rev1 rev2 buffer)
+  (let ((name (file-relative-name file))
+        (buf (or buffer "*vc-diff*")))
     (if (and rev1 rev2)
-        (vc-do-command "*vc-diff*" 0 "git" name "diff-tree" "-p" rev1 rev2 "--")
-      (vc-do-command "*vc-diff*" 0 "git" name "diff-index" "-p" (or rev1 "HEAD") "--"))
+        (vc-do-command buf 0 "git" name "diff-tree" "-p" rev1 rev2 "--")
+      (vc-do-command buf 0 "git" name "diff-index" "-p" (or rev1 "HEAD") "--"))
     ; git-diff-index doesn't set exit status like diff does
     (if (vc-git-workfile-unchanged-p file) 0 1)))
 
index 08c30103f5c247559e15ebd4bb20d7177889ed88..8bc4188e03376e04cb412bcafa01c97a29042d4a 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;
@@ -252,9 +264,19 @@ sub rebuild {
 }
 
 sub init {
-       $SVN_URL = shift or die "SVN repository location required " .
+       my $url = shift or die "SVN repository location required " .
                                "as a command-line argument\n";
-       $SVN_URL =~ s!/+$!!; # strip trailing slash
+       $url =~ s!/+$!!; # strip trailing slash
+
+       if (my $repo_path = shift) {
+               unless (-d $repo_path) {
+                       mkpath([$repo_path]);
+               }
+               $GIT_DIR = $ENV{GIT_DIR} = $repo_path . "/.git";
+               init_vars();
+       }
+
+       $SVN_URL = $url;
        unless (-d $GIT_DIR) {
                my @init_db = ('git-init-db');
                push @init_db, "--template=$_template" if defined $_template;
@@ -379,7 +401,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 +502,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 +608,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 +626,7 @@ sub graft_branches {
                        }
                }
        }
+       graft_tree_joins($grafts);
 
        write_grafts($grafts, $comments, $gr_file);
        unlink "$gr_file~$gr_sha1" if $gr_sha1;
@@ -716,6 +737,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 +838,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 +932,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 +1049,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 +1081,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 +1090,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 +1103,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 +1465,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 +1517,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 +1553,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 +1568,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 +1917,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 +1954,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 +2058,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 +2272,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 +2340,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 +2348,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 +2391,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 +2424,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 +2435,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 +2589,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";
                }
@@ -2386,15 +2617,20 @@ sub libsvn_connect {
 sub libsvn_get_file {
        my ($gui, $f, $rev) = @_;
        my $p = $f;
-       return unless ($p =~ s#^\Q$SVN_PATH\E/?##);
+       return unless ($p =~ s#^\Q$SVN_PATH\E/##);
 
        my ($hash, $pid, $in, $out);
        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 +2696,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 +2705,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 +2731,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 +2788,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 +2828,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 +2852,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 +2899,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 +2940,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 +3125,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 +3166,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 +3207,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 +3237,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 +3252,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 +3306,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 +3363,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 ebaad0397f0bce50db7542abb904ba42b6173344..6a7b40fd09ea9aa365d70dc8019f83e481192c07 100644 (file)
@@ -122,7 +122,7 @@ int sha1write_compressed(struct sha1file *f, void *in, unsigned int size)
        void *out;
 
        memset(&stream, 0, sizeof(stream));
-       deflateInit(&stream, Z_DEFAULT_COMPRESSION);
+       deflateInit(&stream, zlib_compression_level);
        maxsize = deflateBound(&stream, size);
        out = xmalloc(maxsize);
 
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 1c131ff4dcdab52abec6f182e1a9310820532189..507e4019e8bc2764daaf31ce76112ab509895704 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -583,7 +583,7 @@ static unsigned char *deflate_it(char *data,
        z_stream stream;
 
        memset(&stream, 0, sizeof(stream));
-       deflateInit(&stream, Z_BEST_COMPRESSION);
+       deflateInit(&stream, zlib_compression_level);
        bound = deflateBound(&stream, size);
        deflated = xmalloc(bound);
        stream.next_out = deflated;
@@ -2095,6 +2095,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;
+}
+
 static int is_summary_empty(const struct diff_queue_struct *q)
 {
        int i;
diff --git a/diff.h b/diff.h
index 729cd02510cfb3d39646475131ee523f03def8fb..d5573947b3295a00258cec2cb07905da5a5b8292 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -188,4 +188,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 3de8eb3b2a2359a9f8a9f702076292f1e1429df3..43823ff7d650a95dbb06e74397bdc5b7e5c00528 100644 (file)
@@ -20,6 +20,7 @@ int repository_format_version = 0;
 char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8";
 int shared_repository = PERM_UMASK;
 const char *apply_default_whitespace = NULL;
+int zlib_compression_level = Z_DEFAULT_COMPRESSION;
 
 static char *git_dir, *git_object_dir, *git_index_file, *git_refs_dir,
        *git_graft_file;
index 769bb2a6a75f34192cfd7d6f99ea8f6efbfdce7e..ef54a8a411b01bb4f6becd24c68fad3fcbd15211 100644 (file)
@@ -60,12 +60,13 @@ static int objwarning(struct object *obj, const char *err, ...)
 
 static void check_connectivity(void)
 {
-       int i;
+       int i, max;
 
        /* Look up all the requirements, warn about missing objects.. */
-       for (i = 0; i < obj_allocs; i++) {
+       max = get_max_object_index();
+       for (i = 0; i < max; i++) {
                const struct object_refs *refs;
-               struct object *obj = objs[i];
+               struct object *obj = get_indexed_object(i);
 
                if (!obj)
                        continue;
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 a6a7a482cdcf34e3a1d545c7d5deab599a7c0d2f..6db2f48241d0f393e43413ddb52d0986c523e372 100755 (executable)
@@ -102,10 +102,10 @@ ()
 push @revqueue, $head;
 init_claim( defined $starting_rev ? $head : 'dirty');
 unless (defined $starting_rev) {
-       my $diff = open_pipe("git","diff","-R", "HEAD", "--",$filename)
+       my $diff = open_pipe("git","diff","HEAD", "--",$filename)
                or die "Failed to call git diff to check for dirty state: $!";
 
-       _git_diff_parse($diff, $head, "dirty", (
+       _git_diff_parse($diff, [$head], "dirty", (
                                'author' => gitvar_name("GIT_AUTHOR_IDENT"),
                                'author_date' => sprintf("%s +0000",time()),
                                )
@@ -154,14 +154,13 @@ sub handle_rev {
 
                my %revinfo = git_commit_info($rev);
 
-               foreach my $p (@{$revs{$rev}{'parents'}}) {
-
-                       git_diff_parse($p, $rev, %revinfo);
-                       push @revqueue, $p;
-               }
+               if (exists $revs{$rev}{parents} &&
+                   scalar @{$revs{$rev}{parents}} != 0) {
 
+                       git_diff_parse($revs{$rev}{'parents'}, $rev, %revinfo);
+                       push @revqueue, @{$revs{$rev}{'parents'}};
 
-               if (scalar @{$revs{$rev}{parents}} == 0) {
+               } else {
                        # We must be at the initial rev here, so claim everything that is left.
                        for (my $i = 0; $i < @{$revs{$rev}{lines}}; $i++) {
                                if (ref ${$revs{$rev}{lines}}[$i] eq '' || ${$revs{$rev}{lines}}[$i][1] eq '') {
@@ -252,89 +251,171 @@ sub git_find_parent {
 # Get a diff between the current revision and a parent.
 # Record the commit information that results.
 sub git_diff_parse {
-       my ($parent, $rev, %revinfo) = @_;
+       my ($parents, $rev, %revinfo) = @_;
 
-       my $diff = open_pipe("git-diff-tree","-M","-p",$rev,$parent,"--",
-                       $revs{$rev}{'filename'}, $revs{$parent}{'filename'})
+       my @filenames = ( $revs{$rev}{'filename'} );
+       foreach my $parent (@$parents) {
+               push @filenames, $revs{$parent}{'filename'};
+       }
+
+       my $diff = open_pipe("git-diff-tree","-M","-p","-c",$rev,"--",
+                               @filenames )
                or die "Failed to call git-diff for annotation: $!";
 
-       _git_diff_parse($diff, $parent, $rev, %revinfo);
+       _git_diff_parse($diff, $parents, $rev, %revinfo);
 
        close($diff);
 }
 
 sub _git_diff_parse {
-       my ($diff, $parent, $rev, %revinfo) = @_;
+       my ($diff, $parents, $rev, %revinfo) = @_;
+
+       my $ri = 0;
 
-       my ($ri, $pi) = (0,0);
        my $slines = $revs{$rev}{'lines'};
-       my @plines;
+       my (%plines, %pi);
 
        my $gotheader = 0;
        my ($remstart);
-       my ($hunk_start, $hunk_index);
+       my $parent_count = @$parents;
+
+       my $diff_header_regexp = "^@";
+       $diff_header_regexp .= "@" x @$parents;
+       $diff_header_regexp .= ' -\d+,\d+' x @$parents;
+       $diff_header_regexp .= ' \+(\d+),\d+';
+
+       my %claim_regexps;
+       my $allparentplus = '^' . '\\+' x @$parents . '(.*)$';
+
+       {
+               my $i = 0;
+               foreach my $parent (@$parents) {
+
+                       $pi{$parent} = 0;
+                       my $r = '^' . '.' x @$parents . '(.*)$';
+                       my $p = $r;
+                       substr($p,$i+1, 1) = '\\+';
+
+                       my $m = $r;
+                       substr($m,$i+1, 1) = '-';
+
+                       $claim_regexps{$parent}{plus} = $p;
+                       $claim_regexps{$parent}{minus} = $m;
+
+                       $plines{$parent} = [];
+
+                       $i++;
+               }
+       }
+
+       DIFF:
        while(<$diff>) {
                chomp;
-               if (m/^@@ -(\d+),(\d+) \+(\d+),(\d+)/) {
-                       $remstart = $1;
-                       # Adjust for 0-based arrays
-                       $remstart--;
-                       # Reinit hunk tracking.
-                       $hunk_start = $remstart;
-                       $hunk_index = 0;
+               if (m/$diff_header_regexp/) {
+                       $remstart = $1 - 1;
+                       # (0-based arrays)
+
                        $gotheader = 1;
 
-                       for (my $i = $ri; $i < $remstart; $i++) {
-                               $plines[$pi++] = $slines->[$i];
-                               $ri++;
+                       printf("Copying from %d to %d\n", $ri, $remstart);
+                       foreach my $parent (@$parents) {
+                               for (my $i = $ri; $i < $remstart; $i++) {
+                                       $plines{$parent}[$pi{$parent}++] = $slines->[$i];
+                               }
                        }
-                       next;
-               } elsif (!$gotheader) {
-                       next;
-               }
+                       $ri = $remstart;
 
-               if (m/^\+(.*)$/) {
-                       my $line = $1;
-                       $plines[$pi++] = [ $line, '', '', '', 0 ];
-                       next;
+                       next DIFF;
 
-               } elsif (m/^-(.*)$/) {
-                       my $line = $1;
-                       if (get_line($slines, $ri) eq $line) {
-                               # Found a match, claim
-                               claim_line($ri, $rev, $slines, %revinfo);
-                       } else {
-                               die sprintf("Sync error: %d/%d\n|%s\n|%s\n%s => %s\n",
-                                               $ri, $hunk_start + $hunk_index,
-                                               $line,
-                                               get_line($slines, $ri),
-                                               $rev, $parent);
-                       }
-                       $ri++;
+               } elsif (!$gotheader) {
+                       # Skip over the leadin.
+                       next DIFF;
+               }
 
-               } elsif (m/^\\/) {
+               if (m/^\\/) {
                        ;
                        # Skip \No newline at end of file.
                        # But this can be internationalized, so only look
                        # for an initial \
 
                } else {
-                       if (substr($_,1) ne get_line($slines,$ri) ) {
-                               die sprintf("Line %d (%d) does not match:\n|%s\n|%s\n%s => %s\n",
-                                               $hunk_start + $hunk_index, $ri,
-                                               substr($_,1),
-                                               get_line($slines,$ri),
-                                               $rev, $parent);
+                       my %claims = ();
+                       my $negclaim = 0;
+                       my $allclaimed = 0;
+                       my $line;
+
+                       if (m/$allparentplus/) {
+                               claim_line($ri, $rev, $slines, %revinfo);
+                               $allclaimed = 1;
+
+                       }
+
+                       PARENT:
+                       foreach my $parent (keys %claim_regexps) {
+                               my $m = $claim_regexps{$parent}{minus};
+                               my $p = $claim_regexps{$parent}{plus};
+
+                               if (m/$m/) {
+                                       $line = $1;
+                                       $plines{$parent}[$pi{$parent}++] = [ $line, '', '', '', 0 ];
+                                       $negclaim++;
+
+                               } elsif (m/$p/) {
+                                       $line = $1;
+                                       if (get_line($slines, $ri) eq $line) {
+                                               # Found a match, claim
+                                               $claims{$parent}++;
+
+                                       } else {
+                                               die sprintf("Sync error: %d\n|%s\n|%s\n%s => %s\n",
+                                                               $ri, $line,
+                                                               get_line($slines, $ri),
+                                                               $rev, $parent);
+                                       }
+                               }
+                       }
+
+                       if (%claims) {
+                               foreach my $parent (@$parents) {
+                                       next if $claims{$parent} || $allclaimed;
+                                       $plines{$parent}[$pi{$parent}++] = $slines->[$ri];
+                                           #[ $line, '', '', '', 0 ];
+                               }
+                               $ri++;
+
+                       } elsif ($negclaim) {
+                               next DIFF;
+
+                       } else {
+                               if (substr($_,scalar @$parents) ne get_line($slines,$ri) ) {
+                                       foreach my $parent (@$parents) {
+                                               printf("parent %s is on line %d\n", $parent, $pi{$parent});
+                                       }
+
+                                       die sprintf("Line %d, does not match:\n|%s|\n|%s|\n%s\n",
+                                                   $ri,
+                                               substr($_,scalar @$parents),
+                                               get_line($slines,$ri), $rev);
+                               }
+                               foreach my $parent (@$parents) {
+                                       $plines{$parent}[$pi{$parent}++] = $slines->[$ri];
+                               }
+                               $ri++;
                        }
-                       $plines[$pi++] = $slines->[$ri++];
                }
-               $hunk_index++;
        }
+
        for (my $i = $ri; $i < @{$slines} ; $i++) {
-               push @plines, $slines->[$ri++];
+               foreach my $parent (@$parents) {
+                       push @{$plines{$parent}}, $slines->[$ri];
+               }
+               $ri++;
+       }
+
+       foreach my $parent (@$parents) {
+               $revs{$parent}{lines} = $plines{$parent};
        }
 
-       $revs{$parent}{lines} = \@plines;
        return;
 }
 
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 d7f3ade493762140c9a5a888ac67f8435e737f10..22c4ce86c3cc5b35fab27bbe9e75dbe334f58534 100755 (executable)
@@ -29,7 +29,7 @@ THIS_INDEX="$GIT_DIR/index"
 NEXT_INDEX="$GIT_DIR/next-index$$"
 rm -f "$NEXT_INDEX"
 save_index () {
-       cp "$THIS_INDEX" "$NEXT_INDEX"
+       cp -p "$THIS_INDEX" "$NEXT_INDEX"
 }
 
 report () {
@@ -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
       ;;
index 50f5d9642a17eef42d9a28e36201a808e655f54f..e5a00a12857036d01e2e0dd18510df511378854f 100755 (executable)
@@ -467,11 +467,6 @@ ($$)
 $orig_git_index = $ENV{GIT_INDEX_FILE} if exists $ENV{GIT_INDEX_FILE};
 
 my %index; # holds filenames of one index per branch
-$index{$opt_o} = tmpnam();
-
-$ENV{GIT_INDEX_FILE} = $index{$opt_o};
-system("git-read-tree", $opt_o);
-die "read-tree failed: $?\n" if $?;
 
 unless(-d $git_dir) {
        system("git-init-db");
@@ -499,14 +494,6 @@ ($$)
        $orig_branch = $last_branch;
        $tip_at_start = `git-rev-parse --verify HEAD`;
 
-       # populate index
-       unless ($index{$last_branch}) {
-           $index{$last_branch} = tmpnam();
-       }
-       $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))) {
@@ -623,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();
@@ -811,30 +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}) {
-                           $index{$branch} = tmpnam();
-                           $ENV{GIT_INDEX_FILE} = $index{$branch};
-                           system("git-read-tree", $branch);
-                           die "read-tree failed: $?\n" if $?;
-                       }
-                       # just in case
-                       $ENV{GIT_INDEX_FILE} = $index{$branch};
-                       if ($ancestor) {
-                           print "have ancestor $ancestor" if $opt_v;
-                           system("git-read-tree", $ancestor);
-                           die "read-tree failed: $?\n" if $?;
-                       }
-               } else {
-                       # just in case
-                       unless ($index{$branch}) {
-                           $index{$branch} = tmpnam();
-                           $ENV{GIT_INDEX_FILE} = $index{$branch};
-                           system("git-read-tree", $branch);
-                           die "read-tree failed: $?\n" if $?;
-                       }
-               }
                $last_branch = $branch if $branch ne $last_branch;
                $state = 9;
        } elsif($state == 8) {
@@ -898,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) {
diff --git a/git-fmt-merge-msg.perl b/git-fmt-merge-msg.perl
deleted file mode 100755 (executable)
index 5986e54..0000000
+++ /dev/null
@@ -1,173 +0,0 @@
-#!/usr/bin/perl -w
-#
-# Copyright (c) 2005 Junio C Hamano
-#
-# Read .git/FETCH_HEAD and make a human readable merge message
-# by grouping branches and tags together to form a single line.
-
-use strict;
-
-my @src;
-my %src;
-sub andjoin {
-       my ($label, $labels, $stuff) = @_;
-       my $l = scalar @$stuff;
-       my $m = '';
-       if ($l == 0) {
-               return ();
-       }
-       if ($l == 1) {
-               $m = "$label$stuff->[0]";
-       }
-       else {
-               $m = ("$labels" .
-                     join (', ', @{$stuff}[0..$l-2]) .
-                     " and $stuff->[-1]");
-       }
-       return ($m);
-}
-
-sub repoconfig {
-       my ($val) = qx{git-repo-config --get merge.summary};
-       return $val;
-}
-
-sub current_branch {
-       my ($bra) = qx{git-symbolic-ref HEAD};
-       chomp($bra);
-       $bra =~ s|^refs/heads/||;
-       if ($bra ne 'master') {
-               $bra = " into $bra";
-       } else {
-               $bra = "";
-       }
-       return $bra;
-}
-
-sub shortlog {
-       my ($tip) = @_;
-       my @result;
-       foreach ( qx{git-log --no-merges --topo-order --pretty=oneline $tip ^HEAD} ) {
-               s/^[0-9a-f]{40}\s+//;
-               push @result, $_;
-       }
-       die "git-log failed\n" if $?;
-       return @result;
-}
-
-my @origin = ();
-while (<>) {
-       my ($bname, $tname, $gname, $src, $sha1, $origin);
-       chomp;
-       s/^([0-9a-f]*)  //;
-       $sha1 = $1;
-       next if (/^not-for-merge/);
-       s/^     //;
-       if (s/ of (.*)$//) {
-               $src = $1;
-       } else {
-               # Pulling HEAD
-               $src = $_;
-               $_ = 'HEAD';
-       }
-       if (! exists $src{$src}) {
-               push @src, $src;
-               $src{$src} = {
-                       BRANCH => [],
-                       TAG => [],
-                       R_BRANCH => [],
-                       GENERIC => [],
-                       # &1 == has HEAD.
-                       # &2 == has others.
-                       HEAD_STATUS => 0,
-               };
-       }
-       if (/^branch (.*)$/) {
-               $origin = $1;
-               push @{$src{$src}{BRANCH}}, $1;
-               $src{$src}{HEAD_STATUS} |= 2;
-       }
-       elsif (/^tag (.*)$/) {
-               $origin = $_;
-               push @{$src{$src}{TAG}}, $1;
-               $src{$src}{HEAD_STATUS} |= 2;
-       }
-       elsif (/^remote branch (.*)$/) {
-               $origin = $1;
-               push @{$src{$src}{R_BRANCH}}, $1;
-               $src{$src}{HEAD_STATUS} |= 2;
-       }
-       elsif (/^HEAD$/) {
-               $origin = $src;
-               $src{$src}{HEAD_STATUS} |= 1;
-       }
-       else {
-               push @{$src{$src}{GENERIC}}, $_;
-               $src{$src}{HEAD_STATUS} |= 2;
-               $origin = $src;
-       }
-       if ($src eq '.' || $src eq $origin) {
-               $origin =~ s/^'(.*)'$/$1/;
-               push @origin, [$sha1, "$origin"];
-       }
-       else {
-               push @origin, [$sha1, "$origin of $src"];
-       }
-}
-
-my @msg;
-for my $src (@src) {
-       if ($src{$src}{HEAD_STATUS} == 1) {
-               # Only HEAD is fetched, nothing else.
-               push @msg, $src;
-               next;
-       }
-       my @this;
-       if ($src{$src}{HEAD_STATUS} == 3) {
-               # HEAD is fetched among others.
-               push @this, andjoin('', '', ['HEAD']);
-       }
-       push @this, andjoin("branch ", "branches ",
-                          $src{$src}{BRANCH});
-       push @this, andjoin("remote branch ", "remote branches ",
-                          $src{$src}{R_BRANCH});
-       push @this, andjoin("tag ", "tags ",
-                          $src{$src}{TAG});
-       push @this, andjoin("commit ", "commits ",
-                           $src{$src}{GENERIC});
-       my $this = join(', ', @this);
-       if ($src ne '.') {
-               $this .= " of $src";
-       }
-       push @msg, $this;
-}
-
-my $into = current_branch();
-
-print "Merge ", join("; ", @msg), $into, "\n";
-
-if (!repoconfig) {
-       exit(0);
-}
-
-# We limit the merge message to the latst 20 or so per each branch.
-my $limit = 20;
-
-for (@origin) {
-       my ($sha1, $name) = @$_;
-       my @log = shortlog($sha1);
-       if ($limit + 1 <= @log) {
-               print "\n* $name: (" . scalar(@log) . " commits)\n";
-       }
-       else {
-               print "\n* $name:\n";
-       }
-       my $cnt = 0;
-       for my $log (@log) {
-               if ($limit < ++$cnt) {
-                       print "  ...\n";
-                       last;
-               }
-               print "  $log";
-       }
-}
index fc25f8dda01ecf4733165308d9fb1fa80dffbe0c..24e3b507ef1f907e7146a2653189f45a92db08fd 100755 (executable)
@@ -103,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 ;;
                *)
index aa8c2080927661cca281fca543dc5f0808f678e3..076785c96b30b4fd8ab944324fd53db7c584567a 100755 (executable)
@@ -24,7 +24,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 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 9ad1c44d4816f8c4f52eddc525bc24cd6bfe92d2..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,13 +49,18 @@ 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
+               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
-               git-commit -C "`cat $dotest/current`"
        else
                printf "Already applied: %0${prec}d" $msgnum
        fi
@@ -87,11 +87,11 @@ call_merge () {
                ;;
        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:" \
@@ -179,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 eb75c8cda95dabe190cfe9ddba0c6bd7072615f2..640ad8d90b9a9afd00506a9697af1d3e560e1255 100755 (executable)
@@ -54,9 +54,24 @@ else
        fi
        mkdir -p "$PACKDIR" || exit
 
-       mv .tmp-pack-$name.pack "$PACKDIR/pack-$name.pack" &&
-       mv .tmp-pack-$name.idx  "$PACKDIR/pack-$name.idx" ||
-       exit
+       for sfx in pack idx
+       do
+               if test -f "$PACKDIR/pack-$name.$sfx"
+               then
+                       mv -f "$PACKDIR/pack-$name.$sfx" \
+                               "$PACKDIR/old-pack-$name.$sfx"
+               fi
+       done &&
+       mv -f .tmp-pack-$name.pack "$PACKDIR/pack-$name.pack" &&
+       mv -f .tmp-pack-$name.idx  "$PACKDIR/pack-$name.idx" &&
+       test -f "$PACKDIR/pack-$name.pack" &&
+       test -f "$PACKDIR/pack-$name.idx" || {
+               echo >&2 "Couldn't replace the existing pack with updated one."
+               echo >&2 "The original set of packs have been saved as"
+               echo >&2 "old-pack-$name.{pack,idx} in $PACKDIR."
+               exit 1
+       }
+       rm -f "$PACKDIR/old-pack-$name.pack" "$PACKDIR/old-pack-$name.idx"
 fi
 
 if test "$remove_redundant" = t
index c5d9e733512ddd4d266c85d4f5cdec4a7d74fa56..b04b8f40e92f55ecc76dd8c2f239e4960096d2dc 100755 (executable)
 use Getopt::Long;
 use Data::Dumper;
 
+package FakeTerm;
+sub new {
+       my ($class, $reason) = @_;
+       return bless \$reason, shift;
+}
+sub readline {
+       my $self = shift;
+       die "Cannot use readline on FakeTerm: $$self";
+}
+package main;
+
 # most mail servers generate the Date: header, but not all...
 $ENV{LC_ALL} = 'C';
 use POSIX qw/strftime/;
 # Example reply to:
 #$initial_reply_to = ''; #<20050203173208.GA23964@foobar.com>';
 
-my $term = new Term::ReadLine 'git-send-email';
+my $term = eval {
+       new Term::ReadLine 'git-send-email';
+};
+if ($@) {
+       $term = new FakeTerm "$@: going non-interactive";
+}
 
 # Begin by accumulating all the variables (defined above), that we will end up
 # needing, first, from the command line:
index 38ac732ca9b677a5647a96db44f7133b47db0648..26dc45479532a78febb511bf36017d6106513e70 100755 (executable)
@@ -534,7 +534,7 @@ sub commit {
        my($author_name,$author_email,$dest);
        my(@old,@new,@parents);
 
-       if (not defined $author) {
+       if (not defined $author or $author eq "") {
                $author_name = $author_email = "unknown";
        } elsif (defined $users_file) {
                die "User $author is not listed in $users_file\n"
diff --git a/git.c b/git.c
index 94e9a4a4b98acbd9e08943dbbe57616bd60b28e9..256730112e56e19e5df70f2bef4e3efdb1a5e362 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;
 }
 
@@ -184,7 +187,8 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
                { "mailinfo", cmd_mailinfo },
                { "stripspace", cmd_stripspace },
                { "update-index", cmd_update_index },
-               { "update-ref", cmd_update_ref }
+               { "update-ref", cmd_update_ref },
+               { "fmt-merge-msg", cmd_fmt_merge_msg }
        };
        int i;
 
@@ -206,7 +210,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 +316,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..f761584d7edf25147b731405c2996478c7177935 100644 (file)
@@ -492,7 +492,7 @@ static void start_put(struct transfer_request *request)
 
        /* Set it up */
        memset(&stream, 0, sizeof(stream));
-       deflateInit(&stream, Z_BEST_COMPRESSION);
+       deflateInit(&stream, zlib_compression_level);
        size = deflateBound(&stream, len + hdrlen);
        request->buffer.buffer = xmalloc(size);
 
@@ -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 3a5ac35d163bae5431c9cad8c9e31918c2734241..6a23f2d8a2d87c46ac9e1ec7bcb36e540967f93b 100644 (file)
@@ -234,12 +234,15 @@ int main(int argc, char **argv)
                                fwrite(p_start, p - p_start, 1, stdout);
                }
        } else if (all) {
-               int i;
+               int i, max;
 
-               for (i = 0; i < obj_allocs; i++)
-                       if (objs[i])
-                               printf("%s %s\n", sha1_to_hex(objs[i]->sha1),
-                                               get_rev_name(objs[i]));
+               max = get_max_object_index();
+               for (i = 0; i < max; i++) {
+                       struct object * obj = get_indexed_object(i);
+                       if (!obj)
+                               continue;
+                       printf("%s %s\n", sha1_to_hex(obj->sha1), get_rev_name(obj));
+               }
        } else {
                int i;
                for (i = 0; i < revs.nr; i++)
index 37784cee9a90a7e5b44dd660d6b7ea9191b59486..37277f94384fff1320381a370dd4d6c91a4ded2c 100644 (file)
--- a/object.c
+++ b/object.c
@@ -5,79 +5,97 @@
 #include "commit.h"
 #include "tag.h"
 
-struct object **objs;
-static int nr_objs;
-int obj_allocs;
+static struct object **obj_hash;
+static int nr_objs, obj_hash_size;
+
+unsigned int get_max_object_index(void)
+{
+       return obj_hash_size;
+}
+
+struct object *get_indexed_object(unsigned int idx)
+{
+       return obj_hash[idx];
+}
 
 const char *type_names[] = {
        "none", "blob", "tree", "commit", "bad"
 };
 
+static unsigned int hash_obj(struct object *obj, unsigned int n)
+{
+       unsigned int hash = *(unsigned int *)obj->sha1;
+       return hash % n;
+}
+
+static void insert_obj_hash(struct object *obj, struct object **hash, unsigned int size)
+{
+       int j = hash_obj(obj, size);
+
+       while (hash[j]) {
+               j++;
+               if (j >= size)
+                       j = 0;
+       }
+       hash[j] = obj;
+}
+
 static int hashtable_index(const unsigned char *sha1)
 {
        unsigned int i;
        memcpy(&i, sha1, sizeof(unsigned int));
-       return (int)(i % obj_allocs);
+       return (int)(i % obj_hash_size);
 }
 
-static int find_object(const unsigned char *sha1)
+struct object *lookup_object(const unsigned char *sha1)
 {
        int i;
+       struct object *obj;
 
-       if (!objs)
-               return -1;
+       if (!obj_hash)
+               return NULL;
 
        i = hashtable_index(sha1);
-       while (objs[i]) {
-               if (memcmp(sha1, objs[i]->sha1, 20) == 0)
-                       return i;
+       while ((obj = obj_hash[i]) != NULL) {
+               if (!memcmp(sha1, obj->sha1, 20))
+                       break;
                i++;
-               if (i == obj_allocs)
+               if (i == obj_hash_size)
                        i = 0;
        }
-       return -1 - i;
+       return obj;
 }
 
-struct object *lookup_object(const unsigned char *sha1)
+static void grow_object_hash(void)
 {
-       int pos = find_object(sha1);
-       if (pos >= 0)
-               return objs[pos];
-       return NULL;
+       int i;
+       int new_hash_size = obj_hash_size < 32 ? 32 : 2 * obj_hash_size;
+       struct object **new_hash;
+
+       new_hash = calloc(new_hash_size, sizeof(struct object *));
+       for (i = 0; i < obj_hash_size; i++) {
+               struct object *obj = obj_hash[i];
+               if (!obj)
+                       continue;
+               insert_obj_hash(obj, new_hash, new_hash_size);
+       }
+       free(obj_hash);
+       obj_hash = new_hash;
+       obj_hash_size = new_hash_size;
 }
 
 void created_object(const unsigned char *sha1, struct object *obj)
 {
-       int pos;
-
        obj->parsed = 0;
-       memcpy(obj->sha1, sha1, 20);
-       obj->type = TYPE_NONE;
        obj->used = 0;
+       obj->type = TYPE_NONE;
+       obj->flags = 0;
+       memcpy(obj->sha1, sha1, 20);
 
-       if (obj_allocs - 1 <= nr_objs * 2) {
-               int i, count = obj_allocs;
-               obj_allocs = (obj_allocs < 32 ? 32 : 2 * obj_allocs);
-               objs = xrealloc(objs, obj_allocs * sizeof(struct object *));
-               memset(objs + count, 0, (obj_allocs - count)
-                               * sizeof(struct object *));
-               for (i = 0; i < obj_allocs; i++)
-                       if (objs[i]) {
-                               int j = find_object(objs[i]->sha1);
-                               if (j != i) {
-                                       j = -1 - j;
-                                       objs[j] = objs[i];
-                                       objs[i] = NULL;
-                               }
-                       }
-       }
-
-       pos = find_object(sha1);
-       if (pos >= 0)
-               die("Inserting %s twice\n", sha1_to_hex(sha1));
-       pos = -pos-1;
+       if (obj_hash_size - 1 <= nr_objs * 2)
+               grow_object_hash();
 
-       objs[pos] = obj;
+       insert_obj_hash(obj, obj_hash, obj_hash_size);
        nr_objs++;
 }
 
index 6f23a9a18099cbc9fdc827f53843b1157dd10935..e0125e154fd970209d30138858f31ef2017651ac 100644 (file)
--- a/object.h
+++ b/object.h
@@ -40,10 +40,11 @@ struct object {
 };
 
 extern int track_object_refs;
-extern int obj_allocs;
-extern struct object **objs;
 extern const char *type_names[];
 
+extern unsigned int get_max_object_index(void);
+extern struct object *get_indexed_object(unsigned int);
+
 static inline const char *typename(unsigned int type)
 {
        return type_names[type > TYPE_TAG ? TYPE_BAD : type];
index bed2497b7974bed96a6ac9478807059cb8de17cb..b486ea528afed6acb19955fdfae770ec6037ff3f 100644 (file)
@@ -970,11 +970,12 @@ struct unpacked {
  * one.
  */
 static int try_delta(struct unpacked *trg, struct unpacked *src,
-                    struct delta_index *src_index, unsigned max_depth)
+                    unsigned max_depth)
 {
        struct object_entry *trg_entry = trg->entry;
        struct object_entry *src_entry = src->entry;
-       unsigned long size, src_size, delta_size, sizediff, max_size;
+       unsigned long trg_size, src_size, delta_size, sizediff, max_size, sz;
+       char type[10];
        void *delta_buf;
 
        /* Don't bother doing diffs between different types */
@@ -987,6 +988,14 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
        if (trg_entry->preferred_base)
                return -1;
 
+       /*
+        * We do not bother to try a delta that we discarded
+        * on an earlier try, but only when reusing delta data.
+        */
+       if (!no_reuse_delta && trg_entry->in_pack &&
+           trg_entry->in_pack == src_entry->in_pack)
+               return 0;
+
        /*
         * If the current object is at pack edge, take the depth the
         * objects that depend on the current object into account --
@@ -1001,19 +1010,38 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
                return 0;
 
        /* Now some size filtering heuristics. */
-       size = trg_entry->size;
-       max_size = size/2 - 20;
+       trg_size = trg_entry->size;
+       max_size = trg_size/2 - 20;
        max_size = max_size * (max_depth - src_entry->depth) / max_depth;
        if (max_size == 0)
                return 0;
        if (trg_entry->delta && trg_entry->delta_size <= max_size)
                max_size = trg_entry->delta_size-1;
        src_size = src_entry->size;
-       sizediff = src_size < size ? size - src_size : 0;
+       sizediff = src_size < trg_size ? trg_size - src_size : 0;
        if (sizediff >= max_size)
                return 0;
 
-       delta_buf = create_delta(src_index, trg->data, size, &delta_size, max_size);
+       /* Load data if not already done */
+       if (!trg->data) {
+               trg->data = read_sha1_file(trg_entry->sha1, type, &sz);
+               if (sz != trg_size)
+                       die("object %s inconsistent object length (%lu vs %lu)",
+                           sha1_to_hex(trg_entry->sha1), sz, trg_size);
+       }
+       if (!src->data) {
+               src->data = read_sha1_file(src_entry->sha1, type, &sz);
+               if (sz != src_size)
+                       die("object %s inconsistent object length (%lu vs %lu)",
+                           sha1_to_hex(src_entry->sha1), sz, src_size);
+       }
+       if (!src->index) {
+               src->index = create_delta_index(src->data, src_size);
+               if (!src->index)
+                       die("out of memory");
+       }
+
+       delta_buf = create_delta(src->index, trg->data, trg_size, &delta_size, max_size);
        if (!delta_buf)
                return 0;
 
@@ -1046,8 +1074,6 @@ static void find_deltas(struct object_entry **list, int window, int depth)
        while (--i >= 0) {
                struct object_entry *entry = list[i];
                struct unpacked *n = array + idx;
-               unsigned long size;
-               char type[10];
                int j;
 
                if (!entry->preferred_base)
@@ -1074,11 +1100,8 @@ static void find_deltas(struct object_entry **list, int window, int depth)
                free_delta_index(n->index);
                n->index = NULL;
                free(n->data);
+               n->data = NULL;
                n->entry = entry;
-               n->data = read_sha1_file(entry->sha1, type, &size);
-               if (size != entry->size)
-                       die("object %s inconsistent object length (%lu vs %lu)",
-                           sha1_to_hex(entry->sha1), size, entry->size);
 
                j = window;
                while (--j > 0) {
@@ -1089,7 +1112,7 @@ static void find_deltas(struct object_entry **list, int window, int depth)
                        m = array + other_idx;
                        if (!m->entry)
                                break;
-                       if (try_delta(n, m, m->index, depth) < 0)
+                       if (try_delta(n, m, depth) < 0)
                                break;
                }
                /* if we made n a delta, and if n is already at max
@@ -1099,10 +1122,6 @@ static void find_deltas(struct object_entry **list, int window, int depth)
                if (entry->delta && depth <= entry->depth)
                        continue;
 
-               n->index = create_delta_index(n->data, size);
-               if (!n->index)
-                       die("out of memory");
-
                idx++;
                if (idx >= window)
                        idx = 0;
index a90cf2206925ff51b7e8c799b22c8e6cb09c7fc2..2b30980b04e68a84bd300d647e80a34c7d3e621f 100644 (file)
@@ -7,11 +7,11 @@ static const char peek_remote_usage[] =
 "git-peek-remote [--exec=upload-pack] [host:]directory";
 static const char *exec = "git-upload-pack";
 
-static int peek_remote(int fd[2])
+static int peek_remote(int fd[2], unsigned flags)
 {
        struct ref *ref;
 
-       get_remote_heads(fd[0], &ref, 0, NULL, 0);
+       get_remote_heads(fd[0], &ref, 0, NULL, flags);
        packet_flush(fd[1]);
 
        while (ref) {
@@ -28,6 +28,7 @@ int main(int argc, char **argv)
        int fd[2];
        pid_t pid;
        int nongit = 0;
+       unsigned flags = 0;
 
        setup_git_directory_gently(&nongit);
 
@@ -35,22 +36,35 @@ int main(int argc, char **argv)
                char *arg = argv[i];
 
                if (*arg == '-') {
-                       if (!strncmp("--exec=", arg, 7))
+                       if (!strncmp("--exec=", arg, 7)) {
                                exec = arg + 7;
-                       else
-                               usage(peek_remote_usage);
-                       continue;
+                               continue;
+                       }
+                       if (!strcmp("--tags", arg)) {
+                               flags |= REF_TAGS;
+                               continue;
+                       }
+                       if (!strcmp("--heads", arg)) {
+                               flags |= REF_HEADS;
+                               continue;
+                       }
+                       if (!strcmp("--refs", arg)) {
+                               flags |= REF_NORMAL;
+                               continue;
+                       }
+                       usage(peek_remote_usage);
                }
                dest = arg;
                break;
        }
+
        if (!dest || i != argc - 1)
                usage(peek_remote_usage);
 
        pid = git_connect(fd, dest, exec);
        if (pid < 0)
                return 1;
-       ret = peek_remote(fd);
+       ret = peek_remote(fd, flags);
        close(fd[0]);
        close(fd[1]);
        finish_connect(pid);
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 ae4ca8200367a919ed5166a96624abbbe8fef15c..ab89c22417cace2ee56cd88a7135967690be586a 100644 (file)
@@ -280,7 +280,7 @@ int rev_same_tree_as_empty(struct rev_info *revs, struct tree *t1)
 static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
 {
        struct commit_list **pp, *parent;
-       int tree_changed = 0;
+       int tree_changed = 0, tree_same = 0;
 
        if (!commit->tree)
                return;
@@ -298,6 +298,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
                parse_commit(p);
                switch (rev_compare_tree(revs, p->tree, commit->tree)) {
                case REV_TREE_SAME:
+                       tree_same = 1;
                        if (!revs->simplify_history || (p->object.flags & UNINTERESTING)) {
                                /* Even if a merge with an uninteresting
                                 * side branch brought the entire change
@@ -334,7 +335,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
                }
                die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1));
        }
-       if (tree_changed)
+       if (tree_changed && !tree_same)
                commit->object.flags |= TREECHANGE;
 }
 
@@ -895,6 +896,8 @@ static int rewrite_one(struct rev_info *revs, struct commit **pp)
                struct commit *p = *pp;
                if (!revs->limited)
                        add_parents_to_list(revs, p, &revs->commits);
+               if (p->parents && p->parents->next)
+                       return 0;
                if (p->object.flags & (TREECHANGE | UNINTERESTING))
                        return 0;
                if (!p->parents)
@@ -987,8 +990,15 @@ struct commit *get_revision(struct rev_info *revs)
                    commit->parents && commit->parents->next)
                        continue;
                if (revs->prune_fn && revs->dense) {
-                       if (!(commit->object.flags & TREECHANGE))
-                               continue;
+                       /* Commit without changes? */
+                       if (!(commit->object.flags & TREECHANGE)) {
+                               /* drop merges unless we want parenthood */
+                               if (!revs->parents)
+                                       continue;
+                               /* non-merge - always ignore it */
+                               if (!commit->parents || !commit->parents->next)
+                                       continue;
+                       }
                        if (revs->parents)
                                rewrite_parents(revs, commit);
                }
index af93b11f238e4583f6e7fb9dab5a2224acf4ee1f..4019a4b98155841a9d335bc5a1ebdc7d114c5a4d 100644 (file)
@@ -239,7 +239,7 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
        int expect_status_report = 0;
 
        /* No funny business with the matcher */
-       remote_tail = get_remote_heads(in, &remote_refs, 0, NULL, 1);
+       remote_tail = get_remote_heads(in, &remote_refs, 0, NULL, REF_NORMAL);
        get_local_heads();
 
        /* Does the other end support the reporting? */
index c80528b506e98c1e1dae463fa08159677a2a9473..bc3580844023c9ae0f833ea5799c05887e1d88aa 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 = "";
@@ -1458,7 +1458,7 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha
 
        /* Set it up */
        memset(&stream, 0, sizeof(stream));
-       deflateInit(&stream, Z_BEST_COMPRESSION);
+       deflateInit(&stream, zlib_compression_level);
        size = deflateBound(&stream, len+hdrlen);
        compressed = xmalloc(size);
 
@@ -1511,7 +1511,7 @@ static void *repack_object(const unsigned char *sha1, unsigned long *objsize)
 
        /* Set it up */
        memset(&stream, 0, sizeof(stream));
-       deflateInit(&stream, Z_BEST_COMPRESSION);
+       deflateInit(&stream, zlib_compression_level);
        size = deflateBound(&stream, len + hdrlen);
        buf = xmalloc(size);
 
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.
 
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 2496397da3c335997779bae43ba37ec6d32810de..3a6490e8f864b3b3193a7b86d54a0e8d81d0dd20 100755 (executable)
@@ -6,4 +6,10 @@ test_description='git-annotate'
 PROG='git annotate'
 . ../annotate-tests.sh
 
+test_expect_success \
+    'Annotating an old revision works' \
+    '[ $(git annotate file master | awk "{print \$3}" | grep -c "^A$") -eq 2 ] && \
+     [ $(git annotate file master | awk "{print \$3}" | grep -c "^B$") -eq 2 ]'
+
+
 test_done
index a61da1efbdb6ac547c1d1167a4caa849b1ee4f9b..e9ea33c18d8e0ffa2612e52748bbab4bf13ef513 100755 (executable)
@@ -25,10 +25,13 @@ test_expect_success \
      git add fake.sendmail
      GIT_AUTHOR_NAME="A" git commit -a -m "Second."'
 
-test_expect_success \
-    'Extract patches and send' \
-    'git format-patch -n HEAD^1
-     git send-email -from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" ./0001*txt'
+test_expect_success 'Extract patches' '
+    patches=`git format-patch -n HEAD^1`
+'
+
+test_expect_success 'Send patches' '
+     git send-email -from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" $patches 2>errors
+'
 
 cat >expected <<\EOF
 !nobody@example.com!
index 7b86f6965b5306f2162a9076f6ab3bbc1cc32a4b..b18eb9ba0dbe8b5020aaaa698d98a4d1b76aa49c 100644 (file)
@@ -1,3 +1,6 @@
+#include <signal.h>
+#include <sys/wait.h>
+#include <sys/poll.h>
 #include "cache.h"
 #include "refs.h"
 #include "pkt-line.h"
@@ -5,9 +8,6 @@
 #include "object.h"
 #include "commit.h"
 #include "exec_cmd.h"
-#include <signal.h>
-#include <sys/poll.h>
-#include <sys/wait.h>
 
 static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=nn] <dir>";
 
@@ -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)