summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/RelNotes-1.5.6.4.txt43
-rw-r--r--Documentation/SubmittingPatches2
-rw-r--r--Documentation/git-am.txt4
-rw-r--r--Documentation/git-cherry-pick.txt10
-rw-r--r--Documentation/git-rebase.txt2
-rw-r--r--Documentation/git-revert.txt16
-rw-r--r--Documentation/pretty-formats.txt2
-rw-r--r--Documentation/user-manual.txt2
-rw-r--r--Makefile2
-rw-r--r--alias.c54
-rw-r--r--attr.c15
-rw-r--r--builtin-commit-tree.c71
-rw-r--r--builtin-describe.c2
-rw-r--r--builtin-fmt-merge-msg.c155
-rw-r--r--builtin-grep.c27
-rw-r--r--builtin-init-db.c14
-rw-r--r--builtin-merge-recursive.c8
-rw-r--r--builtin-merge.c1156
-rw-r--r--builtin-read-tree.c24
-rw-r--r--builtin-remote.c39
-rw-r--r--builtin-tag.c10
-rw-r--r--builtin.h8
-rw-r--r--cache-tree.c2
-rw-r--r--cache-tree.h2
-rw-r--r--cache.h3
-rw-r--r--commit.c143
-rw-r--r--commit.h4
-rw-r--r--compat/mingw.c22
-rw-r--r--compat/mingw.h3
-rw-r--r--config.c11
-rwxr-xr-xcontrib/completion/git-completion.bash20
-rw-r--r--contrib/emacs/git.el8
-rwxr-xr-xcontrib/examples/git-merge.sh (renamed from git-merge.sh)0
-rw-r--r--convert.c4
-rw-r--r--diff-lib.c8
-rw-r--r--diff.c11
-rw-r--r--diff.h9
-rw-r--r--exec_cmd.c10
-rw-r--r--exec_cmd.h2
-rwxr-xr-xgit-add--interactive.perl2
-rwxr-xr-xgit-am.sh6
-rw-r--r--git-compat-util.h6
-rwxr-xr-xgit-cvsserver.perl2
-rwxr-xr-xgit-quiltimport.sh2
-rwxr-xr-xgit-rebase--interactive.sh4
-rwxr-xr-xgit-rebase.sh47
-rw-r--r--git.c54
-rw-r--r--help.c22
-rw-r--r--index-pack.c142
-rw-r--r--parse-options.c8
-rw-r--r--pretty.c27
-rw-r--r--read-cache.c31
-rw-r--r--revision.c34
-rw-r--r--t/Makefile2
-rwxr-xr-xt/aggregate-results.sh10
-rw-r--r--t/lib-git-svn.sh2
-rwxr-xr-xt/t3401-rebase-partial.sh4
-rwxr-xr-xt/t3404-rebase-interactive.sh8
-rwxr-xr-xt/t3407-rebase-abort.sh4
-rwxr-xr-xt/t4150-am.sh22
-rwxr-xr-xt/t6021-merge-criss-cross.sh4
-rwxr-xr-xt/t7600-merge.sh78
-rwxr-xr-xt/t7601-merge-pull-config.sh129
-rwxr-xr-xt/t7602-merge-octopus-many.sh52
-rwxr-xr-xt/t7603-merge-reduce-heads.sh63
-rwxr-xr-xt/t7604-merge-custom-message.sh37
-rwxr-xr-xt/t7605-merge-resolve.sh46
-rwxr-xr-xt/t9106-git-svn-commit-diff-clobber.sh2
-rw-r--r--tree-diff.c27
69 files changed, 2354 insertions, 451 deletions
diff --git a/Documentation/RelNotes-1.5.6.4.txt b/Documentation/RelNotes-1.5.6.4.txt
new file mode 100644
index 0000000000..130418864e
--- /dev/null
+++ b/Documentation/RelNotes-1.5.6.4.txt
@@ -0,0 +1,43 @@
+GIT v1.5.6.4 Release Notes
+==========================
+
+Fixes since v1.5.6.3
+--------------------
+
+* Various commands could overflow its internal buffer on a platform
+ with small PATH_MAX value in a repository that has contents with
+ long pathnames.
+
+* There wasn't a way to make --pretty=format:%<> specifiers to honor
+ .mailmap name rewriting for authors and committers. Now you can with
+ %aN and %cN.
+
+* Bash completion wasted too many cycles; this has been optimized to be
+ usable again.
+
+* Bash completion lost ref part when completing something like "git show
+ pu:Makefile".
+
+* "git-cvsserver" did not clean up its temporary working area after annotate
+ request.
+
+* "git-daemon" called syslog() from its signal handler, which was a
+ no-no.
+
+* "git-fetch" into an empty repository used to remind that the fetch will
+ be huge by saying "no common commits", but this was an unnecessary
+ noise; it is already known by the user anyway.
+
+* "git-mailinfo" (hence "git-am") did not correctly handle in-body [PATCH]
+ line to override the commit title taken from the mail Subject header.
+
+* "git-rebase -i -p" lost parents that are not involved in the history
+ being rewritten.
+
+Contains other various documentation fixes.
+
+--
+exec >/var/tmp/1
+echo O=$(git describe maint)
+O=v1.5.6.3-21-gebcce31
+git shortlog --no-merges $O..maint
diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches
index b1164753e1..fdfa536441 100644
--- a/Documentation/SubmittingPatches
+++ b/Documentation/SubmittingPatches
@@ -301,7 +301,7 @@ If it does not apply correctly, there can be various reasons.
patch appropriately.
* Your MUA corrupted your patch; "am" would complain that
- the patch does not apply. Look at .dotest/ subdirectory and
+ the patch does not apply. Look at .git/rebase/ subdirectory and
see what 'patch' file contains and check for the common
corruption patterns mentioned above.
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 1395c7d986..2d7f162594 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -140,9 +140,9 @@ aborts in the middle,. You can recover from this in one of two ways:
the index file to bring it in a state that the patch should
have produced. Then run the command with '--resolved' option.
-The command refuses to process new mailboxes while `.dotest`
+The command refuses to process new mailboxes while `.git/rebase`
directory exists, so if you decide to start over from scratch,
-run `rm -f -r .dotest` before running the command with mailbox
+run `rm -f -r .git/rebase` before running the command with mailbox
names.
Before any patches are applied, ORIG_HEAD is set to the tip of the
diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt
index a691173ba1..50fb3d5d54 100644
--- a/Documentation/git-cherry-pick.txt
+++ b/Documentation/git-cherry-pick.txt
@@ -58,14 +58,14 @@ OPTIONS
Usually the command automatically creates a commit with
a commit log message stating which commit was
cherry-picked. This flag applies the change necessary
- to cherry-pick the named commit to your working tree,
- but does not make the commit. In addition, when this
- option is used, your working tree does not have to match
+ to cherry-pick the named commit to your working tree
+ and the index, but does not make the commit. In addition,
+ when this option is used, your index does not have to match
the HEAD commit. The cherry-pick is done against the
- beginning state of your working tree.
+ beginning state of your index.
+
This is useful when cherry-picking more than one commits'
-effect to your working tree in a row.
+effect to your index in a row.
-s::
--signoff::
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index e30f6a6982..51afc87e17 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -39,7 +39,7 @@ It is possible that a merge failure will prevent this process from being
completely automatic. You will have to resolve any such merge failure
and run `git rebase --continue`. Another option is to bypass the commit
that caused the merge failure with `git rebase --skip`. To restore the
-original <branch> and remove the .dotest working files, use the command
+original <branch> and remove the .git/rebase working files, use the command
`git rebase --abort` instead.
Assume the following history exists and the current branch is "topic":
diff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt
index 5411edca96..271850f511 100644
--- a/Documentation/git-revert.txt
+++ b/Documentation/git-revert.txt
@@ -43,16 +43,16 @@ OPTIONS
-n::
--no-commit::
Usually the command automatically creates a commit with
- a commit log message stating which commit was reverted.
- This flag applies the change necessary to revert the
- named commit to your working tree, but does not make the
- commit. In addition, when this option is used, your
- working tree does not have to match the HEAD commit.
- The revert is done against the beginning state of your
- working tree.
+ a commit log message stating which commit was
+ reverted. This flag applies the change necessary
+ to revert the named commit to your working tree
+ and the index, but does not make the commit. In addition,
+ when this option is used, your index does not have to match
+ the HEAD commit. The revert is done against the
+ beginning state of your index.
+
This is useful when reverting more than one commits'
-effect to your working tree in a row.
+effect to your index in a row.
-s::
--signoff::
diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt
index 69e6d2fa44..c11d495771 100644
--- a/Documentation/pretty-formats.txt
+++ b/Documentation/pretty-formats.txt
@@ -101,6 +101,7 @@ The placeholders are:
- '%P': parent hashes
- '%p': abbreviated parent hashes
- '%an': author name
+- '%aN': author name (respecting .mailmap)
- '%ae': author email
- '%ad': author date
- '%aD': author date, RFC2822 style
@@ -108,6 +109,7 @@ The placeholders are:
- '%at': author date, UNIX timestamp
- '%ai': author date, ISO 8601 format
- '%cn': committer name
+- '%cN': committer name (respecting .mailmap)
- '%ce': committer email
- '%cd': committer date
- '%cD': committer date, RFC2822 style
diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt
index 92d400753d..8761ee7e7d 100644
--- a/Documentation/user-manual.txt
+++ b/Documentation/user-manual.txt
@@ -2431,7 +2431,7 @@ $ git rebase origin
-------------------------------------------------
This will remove each of your commits from mywork, temporarily saving
-them as patches (in a directory named ".dotest"), update mywork to
+them as patches (in a directory named ".git/rebase"), update mywork to
point at the latest version of origin, then apply each of the saved
patches to the new mywork. The result will look like:
diff --git a/Makefile b/Makefile
index de151638bb..9b52071b73 100644
--- a/Makefile
+++ b/Makefile
@@ -240,7 +240,6 @@ SCRIPT_SH += git-lost-found.sh
SCRIPT_SH += git-merge-octopus.sh
SCRIPT_SH += git-merge-one-file.sh
SCRIPT_SH += git-merge-resolve.sh
-SCRIPT_SH += git-merge.sh
SCRIPT_SH += git-mergetool.sh
SCRIPT_SH += git-parse-remote.sh
SCRIPT_SH += git-pull.sh
@@ -515,6 +514,7 @@ BUILTIN_OBJS += builtin-ls-remote.o
BUILTIN_OBJS += builtin-ls-tree.o
BUILTIN_OBJS += builtin-mailinfo.o
BUILTIN_OBJS += builtin-mailsplit.o
+BUILTIN_OBJS += builtin-merge.o
BUILTIN_OBJS += builtin-merge-base.o
BUILTIN_OBJS += builtin-merge-file.o
BUILTIN_OBJS += builtin-merge-ours.o
diff --git a/alias.c b/alias.c
index 995f3e6a0a..ccb1108c94 100644
--- a/alias.c
+++ b/alias.c
@@ -21,3 +21,57 @@ char *alias_lookup(const char *alias)
git_config(alias_lookup_cb, NULL);
return alias_val;
}
+
+int split_cmdline(char *cmdline, const char ***argv)
+{
+ int src, dst, count = 0, size = 16;
+ char quoted = 0;
+
+ *argv = xmalloc(sizeof(char*) * size);
+
+ /* split alias_string */
+ (*argv)[count++] = cmdline;
+ for (src = dst = 0; cmdline[src];) {
+ char c = cmdline[src];
+ if (!quoted && isspace(c)) {
+ cmdline[dst++] = 0;
+ while (cmdline[++src]
+ && isspace(cmdline[src]))
+ ; /* skip */
+ if (count >= size) {
+ size += 16;
+ *argv = xrealloc(*argv, sizeof(char*) * size);
+ }
+ (*argv)[count++] = cmdline + dst;
+ } else if (!quoted && (c == '\'' || c == '"')) {
+ quoted = c;
+ src++;
+ } else if (c == quoted) {
+ quoted = 0;
+ src++;
+ } else {
+ if (c == '\\' && quoted != '\'') {
+ src++;
+ c = cmdline[src];
+ if (!c) {
+ free(*argv);
+ *argv = NULL;
+ return error("cmdline ends with \\");
+ }
+ }
+ cmdline[dst++] = c;
+ src++;
+ }
+ }
+
+ cmdline[dst] = 0;
+
+ if (quoted) {
+ free(*argv);
+ *argv = NULL;
+ return error("unclosed quote");
+ }
+
+ return count;
+}
+
diff --git a/attr.c b/attr.c
index 0fb47d3434..17f6a4dca5 100644
--- a/attr.c
+++ b/attr.c
@@ -459,7 +459,9 @@ static void prepare_attr_stack(const char *path, int dirlen)
{
struct attr_stack *elem, *info;
int len;
- char pathbuf[PATH_MAX];
+ struct strbuf pathbuf;
+
+ strbuf_init(&pathbuf, dirlen+2+strlen(GITATTRIBUTES_FILE));
/*
* At the bottom of the attribute stack is the built-in
@@ -510,13 +512,14 @@ static void prepare_attr_stack(const char *path, int dirlen)
len = strlen(attr_stack->origin);
if (dirlen <= len)
break;
- memcpy(pathbuf, path, dirlen);
- memcpy(pathbuf + dirlen, "/", 2);
- cp = strchr(pathbuf + len + 1, '/');
+ strbuf_reset(&pathbuf);
+ strbuf_add(&pathbuf, path, dirlen);
+ strbuf_addch(&pathbuf, '/');
+ cp = strchr(pathbuf.buf + len + 1, '/');
strcpy(cp + 1, GITATTRIBUTES_FILE);
- elem = read_attr(pathbuf, 0);
+ elem = read_attr(pathbuf.buf, 0);
*cp = '\0';
- elem->origin = strdup(pathbuf);
+ elem->origin = strdup(pathbuf.buf);
elem->prev = attr_stack;
attr_stack = elem;
debug_push(elem);
diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c
index 3881f6c2f5..7a9a309be0 100644
--- a/builtin-commit-tree.c
+++ b/builtin-commit-tree.c
@@ -45,41 +45,19 @@ static const char commit_utf8_warn[] =
"You may want to amend it after fixing the message, or set the config\n"
"variable i18n.commitencoding to the encoding your project uses.\n";
-int cmd_commit_tree(int argc, const char **argv, const char *prefix)
+int commit_tree(const char *msg, unsigned char *tree,
+ struct commit_list *parents, unsigned char *ret)
{
- int i;
- struct commit_list *parents = NULL;
- unsigned char tree_sha1[20];
- unsigned char commit_sha1[20];
- struct strbuf buffer;
int encoding_is_utf8;
+ struct strbuf buffer;
- git_config(git_default_config, NULL);
-
- if (argc < 2)
- usage(commit_tree_usage);
- if (get_sha1(argv[1], tree_sha1))
- die("Not a valid object name %s", argv[1]);
-
- check_valid(tree_sha1, OBJ_TREE);
- for (i = 2; i < argc; i += 2) {
- unsigned char sha1[20];
- const char *a, *b;
- a = argv[i]; b = argv[i+1];
- if (!b || strcmp(a, "-p"))
- usage(commit_tree_usage);
-
- if (get_sha1(b, sha1))
- die("Not a valid object name %s", b);
- check_valid(sha1, OBJ_COMMIT);
- new_parent(lookup_commit(sha1), &parents);
- }
+ check_valid(tree, OBJ_TREE);
/* Not having i18n.commitencoding is the same as having utf-8 */
encoding_is_utf8 = is_encoding_utf8(git_commit_encoding);
strbuf_init(&buffer, 8192); /* should avoid reallocs for the headers */
- strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree_sha1));
+ strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree));
/*
* NOTE! This ordering means that the same exact tree merged with a
@@ -102,14 +80,47 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
strbuf_addch(&buffer, '\n');
/* And add the comment */
- if (strbuf_read(&buffer, 0, 0) < 0)
- die("git-commit-tree: read returned %s", strerror(errno));
+ strbuf_addstr(&buffer, msg);
/* And check the encoding */
if (encoding_is_utf8 && !is_utf8(buffer.buf))
fprintf(stderr, commit_utf8_warn);
- if (!write_sha1_file(buffer.buf, buffer.len, commit_type, commit_sha1)) {
+ return write_sha1_file(buffer.buf, buffer.len, commit_type, ret);
+}
+
+int cmd_commit_tree(int argc, const char **argv, const char *prefix)
+{
+ int i;
+ struct commit_list *parents = NULL;
+ unsigned char tree_sha1[20];
+ unsigned char commit_sha1[20];
+ struct strbuf buffer = STRBUF_INIT;
+
+ git_config(git_default_config, NULL);
+
+ if (argc < 2)
+ usage(commit_tree_usage);
+ if (get_sha1(argv[1], tree_sha1))
+ die("Not a valid object name %s", argv[1]);
+
+ for (i = 2; i < argc; i += 2) {
+ unsigned char sha1[20];
+ const char *a, *b;
+ a = argv[i]; b = argv[i+1];
+ if (!b || strcmp(a, "-p"))
+ usage(commit_tree_usage);
+
+ if (get_sha1(b, sha1))
+ die("Not a valid object name %s", b);
+ check_valid(sha1, OBJ_COMMIT);
+ new_parent(lookup_commit(sha1), &parents);
+ }
+
+ if (strbuf_read(&buffer, 0, 0) < 0)
+ die("git-commit-tree: read returned %s", strerror(errno));
+
+ if (!commit_tree(buffer.buf, tree_sha1, parents, commit_sha1)) {
printf("%s\n", sha1_to_hex(commit_sha1));
return 0;
}
diff --git a/builtin-describe.c b/builtin-describe.c
index e515f9ca9b..62a19056ef 100644
--- a/builtin-describe.c
+++ b/builtin-describe.c
@@ -20,7 +20,7 @@ static int tags; /* But allow any tags if --tags is specified */
static int longformat;
static int abbrev = DEFAULT_ABBREV;
static int max_candidates = 10;
-const char *pattern = NULL;
+static const char *pattern;
static int always;
struct commit_name {
diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c
index b892621ab5..dbd7d2ddae 100644
--- a/builtin-fmt-merge-msg.c
+++ b/builtin-fmt-merge-msg.c
@@ -159,23 +159,24 @@ static int handle_line(char *line)
}
static void print_joined(const char *singular, const char *plural,
- struct list *list)
+ struct list *list, struct strbuf *out)
{
if (list->nr == 0)
return;
if (list->nr == 1) {
- printf("%s%s", singular, list->list[0]);
+ strbuf_addf(out, "%s%s", singular, list->list[0]);
} else {
int i;
- printf("%s", plural);
+ strbuf_addstr(out, 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]);
+ strbuf_addf(out, "%s%s", i > 0 ? ", " : "", list->list[i]);
+ strbuf_addf(out, " 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)
+ struct commit *head, struct rev_info *rev, int limit,
+ struct strbuf *out)
{
int i, count = 0;
struct commit *commit;
@@ -232,15 +233,15 @@ static void shortlog(const char *name, unsigned char *sha1,
}
if (count > limit)
- printf("\n* %s: (%d commits)\n", name, count);
+ strbuf_addf(out, "\n* %s: (%d commits)\n", name, count);
else
- printf("\n* %s:\n", name);
+ strbuf_addf(out, "\n* %s:\n", name);
for (i = 0; i < subjects.nr; i++)
if (i >= limit)
- printf(" ...\n");
+ strbuf_addf(out, " ...\n");
else
- printf(" %s\n", subjects.list[i]);
+ strbuf_addf(out, " %s\n", subjects.list[i]);
clear_commit_marks((struct commit *)branch, flags);
clear_commit_marks(head, flags);
@@ -251,43 +252,13 @@ static void shortlog(const char *name, unsigned char *sha1,
free_list(&subjects);
}
-int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
-{
- int limit = 20, i = 0;
+int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) {
+ int limit = 20, i = 0, pos = 0;
char line[1024];
- FILE *in = stdin;
- const char *sep = "";
+ char *p = line, *sep = "";
unsigned char head_sha1[20];
const char *current_branch;
- git_config(fmt_merge_msg_config, NULL);
-
- while (argc > 1) {
- if (!strcmp(argv[1], "--log") || !strcmp(argv[1], "--summary"))
- merge_summary = 1;
- else if (!strcmp(argv[1], "--no-log")
- || !strcmp(argv[1], "--no-summary"))
- merge_summary = 0;
- else if (!strcmp(argv[1], "-F") || !strcmp(argv[1], "--file")) {
- if (argc < 3)
- die ("Which file?");
- if (!strcmp(argv[2], "-"))
- in = stdin;
- else {
- fclose(in);
- in = fopen(argv[2], "r");
- if (!in)
- die("cannot open %s", argv[2]);
- }
- argc--; argv++;
- } else
- break;
- argc--; argv++;
- }
-
- if (argc > 1)
- usage(fmt_merge_msg_usage);
-
/* get current branch */
current_branch = resolve_ref("HEAD", head_sha1, 1, NULL);
if (!current_branch)
@@ -295,75 +266,127 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
if (!prefixcmp(current_branch, "refs/heads/"))
current_branch += 11;
- while (fgets(line, sizeof(line), in)) {
+ /* get a line */
+ while (pos < in->len) {
+ int len;
+ char *newline;
+
+ p = in->buf + pos;
+ newline = strchr(p, '\n');
+ len = newline ? newline - p : strlen(p);
+ pos += len + !!newline;
i++;
- if (line[0] == 0)
- continue;
- if (handle_line(line))
- die ("Error in line %d: %s", i, line);
+ p[len] = 0;
+ if (handle_line(p))
+ die ("Error in line %d: %.*s", i, len, p);
}
- printf("Merge ");
+ strbuf_addstr(out, "Merge ");
for (i = 0; i < srcs.nr; i++) {
struct src_data *src_data = srcs.payload[i];
const char *subsep = "";
- printf(sep);
+ strbuf_addstr(out, sep);
sep = "; ";
if (src_data->head_status == 1) {
- printf(srcs.list[i]);
+ strbuf_addstr(out, srcs.list[i]);
continue;
}
if (src_data->head_status == 3) {
subsep = ", ";
- printf("HEAD");
+ strbuf_addstr(out, "HEAD");
}
if (src_data->branch.nr) {
- printf(subsep);
+ strbuf_addstr(out, subsep);
subsep = ", ";
- print_joined("branch ", "branches ", &src_data->branch);
+ print_joined("branch ", "branches ", &src_data->branch,
+ out);
}
if (src_data->r_branch.nr) {
- printf(subsep);
+ strbuf_addstr(out, subsep);
subsep = ", ";
print_joined("remote branch ", "remote branches ",
- &src_data->r_branch);
+ &src_data->r_branch, out);
}
if (src_data->tag.nr) {
- printf(subsep);
+ strbuf_addstr(out, subsep);
subsep = ", ";
- print_joined("tag ", "tags ", &src_data->tag);
+ print_joined("tag ", "tags ", &src_data->tag, out);
}
if (src_data->generic.nr) {
- printf(subsep);
- print_joined("commit ", "commits ", &src_data->generic);
+ strbuf_addstr(out, subsep);
+ print_joined("commit ", "commits ", &src_data->generic,
+ out);
}
if (strcmp(".", srcs.list[i]))
- printf(" of %s", srcs.list[i]);
+ strbuf_addf(out, " of %s", srcs.list[i]);
}
if (!strcmp("master", current_branch))
- putchar('\n');
+ strbuf_addch(out, '\n');
else
- printf(" into %s\n", current_branch);
+ strbuf_addf(out, " into %s\n", current_branch);
if (merge_summary) {
struct commit *head;
struct rev_info rev;
head = lookup_commit(head_sha1);
- init_revisions(&rev, prefix);
+ init_revisions(&rev, NULL);
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);
+ head, &rev, limit, out);
}
+ return 0;
+}
+
+int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
+{
+ FILE *in = stdin;
+ struct strbuf input, output;
+ int ret;
+
+ git_config(fmt_merge_msg_config, NULL);
+
+ while (argc > 1) {
+ if (!strcmp(argv[1], "--log") || !strcmp(argv[1], "--summary"))
+ merge_summary = 1;
+ else if (!strcmp(argv[1], "--no-log")
+ || !strcmp(argv[1], "--no-summary"))
+ merge_summary = 0;
+ else if (!strcmp(argv[1], "-F") || !strcmp(argv[1], "--file")) {
+ if (argc < 3)
+ die ("Which file?");
+ if (!strcmp(argv[2], "-"))
+ in = stdin;
+ else {
+ fclose(in);
+ in = fopen(argv[2], "r");
+ if (!in)
+ die("cannot open %s", argv[2]);
+ }
+ argc--; argv++;
+ } else
+ break;
+ argc--; argv++;
+ }
+
+ if (argc > 1)
+ usage(fmt_merge_msg_usage);
- /* No cleanup yet; is standalone anyway */
+ strbuf_init(&input, 0);
+ if (strbuf_read(&input, fileno(in), 0) < 0)
+ die("could not read input file %s", strerror(errno));
+ strbuf_init(&output, 0);
+ ret = fmt_merge_msg(merge_summary, &input, &output);
+ if (ret)
+ return ret;
+ printf("%s", output.buf);
return 0;
}
diff --git a/builtin-grep.c b/builtin-grep.c
index ef299108f5..647535061c 100644
--- a/builtin-grep.c
+++ b/builtin-grep.c
@@ -427,33 +427,35 @@ static int grep_tree(struct grep_opt *opt, const char **paths,
struct name_entry entry;
char *down;
int tn_len = strlen(tree_name);
- char *path_buf = xmalloc(PATH_MAX + tn_len + 100);
+ struct strbuf pathbuf;
+
+ strbuf_init(&pathbuf, PATH_MAX + tn_len);
if (tn_len) {
- tn_len = sprintf(path_buf, "%s:", tree_name);
- down = path_buf + tn_len;
- strcat(down, base);
- }
- else {
- down = path_buf;
- strcpy(down, base);
+ strbuf_add(&pathbuf, tree_name, tn_len);
+ strbuf_addch(&pathbuf, ':');
+ tn_len = pathbuf.len;
}
- len = strlen(path_buf);
+ strbuf_addstr(&pathbuf, base);
+ len = pathbuf.len;
while (tree_entry(tree, &entry)) {
- strcpy(path_buf + len, entry.path);
+ int te_len = tree_entry_len(entry.path, entry.sha1);
+ pathbuf.len = len;
+ strbuf_add(&pathbuf, entry.path, te_len);
if (S_ISDIR(entry.mode))
/* Match "abc/" against pathspec to
* decide if we want to descend into "abc"
* directory.
*/
- strcpy(path_buf + len + tree_entry_len(entry.path, entry.sha1), "/");
+ strbuf_addch(&pathbuf, '/');
+ down = pathbuf.buf + tn_len;
if (!pathspec_matches(paths, down))
;
else if (S_ISREG(entry.mode))
- hit |= grep_sha1(opt, entry.sha1, path_buf, tn_len);
+ hit |= grep_sha1(opt, entry.sha1, pathbuf.buf, tn_len);
else if (S_ISDIR(entry.mode)) {
enum object_type type;
struct tree_desc sub;
@@ -469,6 +471,7 @@ static int grep_tree(struct grep_opt *opt, const char **paths,
free(data);
}
}
+ strbuf_release(&pathbuf);
return hit;
}
diff --git a/builtin-init-db.c b/builtin-init-db.c
index e23b8438c7..5ba213a595 100644
--- a/builtin-init-db.c
+++ b/builtin-init-db.c
@@ -115,18 +115,8 @@ static void copy_templates(const char *template_dir)
if (!template_dir)
template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT);
- if (!template_dir) {
- /*
- * if the hard-coded template is relative, it is
- * interpreted relative to the exec_dir
- */
- template_dir = DEFAULT_GIT_TEMPLATE_DIR;
- if (!is_absolute_path(template_dir)) {
- struct strbuf d = STRBUF_INIT;
- strbuf_addf(&d, "%s/%s", git_exec_path(), template_dir);
- template_dir = strbuf_detach(&d, NULL);
- }
- }
+ if (!template_dir)
+ template_dir = system_path(DEFAULT_GIT_TEMPLATE_DIR);
strcpy(template_path, template_dir);
template_len = strlen(template_path);
if (template_path[template_len-1] != '/') {
diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c
index 43bf6aa45e..3731853f83 100644
--- a/builtin-merge-recursive.c
+++ b/builtin-merge-recursive.c
@@ -42,14 +42,6 @@ static struct tree *shift_tree_object(struct tree *one, struct tree *two)
* - *(int *)commit->object.sha1 set to the virtual id.
*/
-static unsigned commit_list_count(const struct commit_list *l)
-{
- unsigned c = 0;
- for (; l; l = l->next )
- c++;
- return c;
-}
-
static struct commit *make_virtual_commit(struct tree *tree, const char *comment)
{
struct commit *commit = xcalloc(1, sizeof(struct commit));
diff --git a/builtin-merge.c b/builtin-merge.c
new file mode 100644
index 0000000000..129b4e62dd
--- /dev/null
+++ b/builtin-merge.c
@@ -0,0 +1,1156 @@
+/*
+ * Builtin "git merge"
+ *
+ * Copyright (c) 2008 Miklos Vajna <vmiklos@frugalware.org>
+ *
+ * Based on git-merge.sh by Junio C Hamano.
+ */
+
+#include "cache.h"
+#include "parse-options.h"
+#include "builtin.h"
+#include "run-command.h"
+#include "diff.h"
+#include "refs.h"
+#include "commit.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "unpack-trees.h"
+#include "cache-tree.h"
+#include "dir.h"
+#include "utf8.h"
+#include "log-tree.h"
+#include "color.h"
+#include "rerere.h"
+
+#define DEFAULT_TWOHEAD (1<<0)
+#define DEFAULT_OCTOPUS (1<<1)
+#define NO_FAST_FORWARD (1<<2)
+#define NO_TRIVIAL (1<<3)
+
+struct strategy {
+ const char *name;
+ unsigned attr;
+};
+
+static const char * const builtin_merge_usage[] = {
+ "git-merge [options] <remote>...",
+ "git-merge [options] <msg> HEAD <remote>",
+ NULL
+};
+
+static int show_diffstat = 1, option_log, squash;
+static int option_commit = 1, allow_fast_forward = 1;
+static int allow_trivial = 1, have_message;
+static struct strbuf merge_msg;
+static struct commit_list *remoteheads;
+static unsigned char head[20], stash[20];
+static struct strategy **use_strategies;
+static size_t use_strategies_nr, use_strategies_alloc;
+static const char *branch;
+
+static struct strategy all_strategy[] = {
+ { "recur", NO_TRIVIAL },
+ { "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL },
+ { "octopus", DEFAULT_OCTOPUS },
+ { "resolve", 0 },
+ { "stupid", 0 },
+ { "ours", NO_FAST_FORWARD | NO_TRIVIAL },
+ { "subtree", NO_FAST_FORWARD | NO_TRIVIAL },
+};
+
+static const char *pull_twohead, *pull_octopus;
+
+static int option_parse_message(const struct option *opt,
+ const char *arg, int unset)
+{
+ struct strbuf *buf = opt->value;
+
+ if (unset)
+ strbuf_setlen(buf, 0);
+ else {
+ strbuf_addf(buf, "%s\n\n", arg);
+ have_message = 1;
+ }
+ return 0;
+}
+
+static struct strategy *get_strategy(const char *name)
+{
+ int i;
+
+ if (!name)
+ return NULL;
+
+ for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
+ if (!strcmp(name, all_strategy[i].name))
+ return &all_strategy[i];
+ return NULL;
+}
+
+static void append_strategy(struct strategy *s)
+{
+ ALLOC_GROW(use_strategies, use_strategies_nr + 1, use_strategies_alloc);
+ use_strategies[use_strategies_nr++] = s;
+}
+
+static int option_parse_strategy(const struct option *opt,
+ const char *name, int unset)
+{
+ int i;
+ struct strategy *s;
+
+ if (unset)
+ return 0;
+
+ s = get_strategy(name);
+
+ if (s)
+ append_strategy(s);
+ else {
+ struct strbuf err;
+ strbuf_init(&err, 0);
+ for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
+ strbuf_addf(&err, " %s", all_strategy[i].name);
+ fprintf(stderr, "Could not find merge strategy '%s'.\n", name);
+ fprintf(stderr, "Available strategies are:%s.\n", err.buf);
+ exit(1);
+ }
+ return 0;
+}
+
+static int option_parse_n(const struct option *opt,
+ const char *arg, int unset)
+{
+ show_diffstat = unset;
+ return 0;
+}
+
+static struct option builtin_merge_options[] = {
+ { OPTION_CALLBACK, 'n', NULL, NULL, NULL,
+ "do not show a diffstat at the end of the merge",
+ PARSE_OPT_NOARG, option_parse_n },
+ OPT_BOOLEAN(0, "stat", &show_diffstat,
+ "show a diffstat at the end of the merge"),
+ OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"),
+ OPT_BOOLEAN(0, "log", &option_log,
+ "add list of one-line log to merge commit message"),
+ OPT_BOOLEAN(0, "squash", &squash,
+ "create a single commit instead of doing a merge"),
+ OPT_BOOLEAN(0, "commit", &option_commit,
+ "perform a commit if the merge succeeds (default)"),
+ OPT_BOOLEAN(0, "ff", &allow_fast_forward,
+ "allow fast forward (default)"),
+ OPT_CALLBACK('s', "strategy", &use_strategies, "strategy",
+ "merge strategy to use", option_parse_strategy),
+ OPT_CALLBACK('m', "message", &merge_msg, "message",
+ "message to be used for the merge commit (if any)",
+ option_parse_message),
+ OPT_END()
+};
+
+/* Cleans up metadata that is uninteresting after a succeeded merge. */
+static void drop_save(void)
+{
+ unlink(git_path("MERGE_HEAD"));
+ unlink(git_path("MERGE_MSG"));
+}
+
+static void save_state(void)
+{
+ int len;
+ struct child_process cp;
+ struct strbuf buffer = STRBUF_INIT;
+ const char *argv[] = {"stash", "create", NULL};
+
+ memset(&cp, 0, sizeof(cp));
+ cp.argv = argv;
+ cp.out = -1;
+ cp.git_cmd = 1;
+
+ if (start_command(&cp))
+ die("could not run stash.");
+ len = strbuf_read(&buffer, cp.out, 1024);
+ close(cp.out);
+
+ if (finish_command(&cp) || len < 0)
+ die("stash failed");
+ else if (!len)
+ return;
+ strbuf_setlen(&buffer, buffer.len-1);
+ if (get_sha1(buffer.buf, stash))
+ die("not a valid object: %s", buffer.buf);
+}
+
+static void reset_hard(unsigned const char *sha1, int verbose)
+{
+ int i = 0;
+ const char *args[6];
+
+ args[i++] = "read-tree";
+ if (verbose)
+ args[i++] = "-v";
+ args[i++] = "--reset";
+ args[i++] = "-u";
+ args[i++] = sha1_to_hex(sha1);
+ args[i] = NULL;
+
+ if (run_command_v_opt(args, RUN_GIT_CMD))
+ die("read-tree failed");
+}
+
+static void restore_state(void)
+{
+ struct strbuf sb;
+ const char *args[] = { "stash", "apply", NULL, NULL };
+
+ if (is_null_sha1(stash))
+ return;
+
+ reset_hard(head, 1);
+
+ strbuf_init(&sb, 0);
+ args[2] = sha1_to_hex(stash);
+
+ /*
+ * It is OK to ignore error here, for example when there was
+ * nothing to restore.
+ */
+ run_command_v_opt(args, RUN_GIT_CMD);
+
+ strbuf_release(&sb);
+ refresh_cache(REFRESH_QUIET);
+}
+
+/* This is called when no merge was necessary. */
+static void finish_up_to_date(const char *msg)
+{
+ printf("%s%s\n", squash ? " (nothing to squash)" : "", msg);
+ drop_save();
+}
+
+static void squash_message(void)
+{
+ struct rev_info rev;
+ struct commit *commit;
+ struct strbuf out;
+ struct commit_list *j;
+ int fd;
+
+ printf("Squash commit -- not updating HEAD\n");
+ fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666);
+ if (fd < 0)
+ die("Could not write to %s", git_path("SQUASH_MSG"));
+
+ init_revisions(&rev, NULL);
+ rev.ignore_merges = 1;
+ rev.commit_format = CMIT_FMT_MEDIUM;
+
+ commit = lookup_commit(head);
+ commit->object.flags |= UNINTERESTING;
+ add_pending_object(&rev, &commit->object, NULL);
+
+ for (j = remoteheads; j; j = j->next)
+ add_pending_object(&rev, &j->item->object, NULL);
+
+ setup_revisions(0, NULL, &rev, NULL);
+ if (prepare_revision_walk(&rev))
+ die("revision walk setup failed");
+
+ strbuf_init(&out, 0);
+ strbuf_addstr(&out, "Squashed commit of the following:\n");
+ while ((commit = get_revision(&rev)) != NULL) {
+ strbuf_addch(&out, '\n');
+ strbuf_addf(&out, "commit %s\n",
+ sha1_to_hex(commit->object.sha1));
+ pretty_print_commit(rev.commit_format, commit, &out, rev.abbrev,
+ NULL, NULL, rev.date_mode, 0);
+ }
+ write(fd, out.buf, out.len);
+ close(fd);
+ strbuf_release(&out);
+}
+
+static int run_hook(const char *name)
+{
+ struct child_process hook;
+ const char *argv[3], *env[2];
+ char index[PATH_MAX];
+
+ argv[0] = git_path("hooks/%s", name);
+ if (access(argv[0], X_OK) < 0)
+ return 0;
+
+ snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", get_index_file());
+ env[0] = index;
+ env[1] = NULL;
+
+ if (squash)
+ argv[1] = "1";
+ else
+ argv[1] = "0";
+ argv[2] = NULL;
+
+ memset(&hook, 0, sizeof(hook));
+ hook.argv = argv;
+ hook.no_stdin = 1;
+ hook.stdout_to_stderr = 1;
+ hook.env = env;
+
+ return run_command(&hook);
+}
+
+static void finish(const unsigned char *new_head, const char *msg)
+{
+ struct strbuf reflog_message;
+
+ strbuf_init(&reflog_message, 0);
+ if (!msg)
+ strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION"));
+ else {
+ printf("%s\n", msg);
+ strbuf_addf(&reflog_message, "%s: %s",
+ getenv("GIT_REFLOG_ACTION"), msg);
+ }
+ if (squash) {
+ squash_message();
+ } else {
+ if (!merge_msg.len)
+ printf("No merge message -- not updating HEAD\n");
+ else {
+ const char *argv_gc_auto[] = { "gc", "--auto", NULL };
+ update_ref(reflog_message.buf, "HEAD",
+ new_head, head, 0,
+ DIE_ON_ERR);
+ /*
+ * We ignore errors in 'gc --auto', since the
+ * user should see them.
+ */
+ run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+ }
+ }
+ if (new_head && show_diffstat) {
+ struct diff_options opts;
+ diff_setup(&opts);
+ opts.output_format |=
+ DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
+ opts.detect_rename = DIFF_DETECT_RENAME;
+ if (diff_use_color_default > 0)
+ DIFF_OPT_SET(&opts, COLOR_DIFF);
+ if (diff_setup_done(&opts) < 0)
+ die("diff_setup_done failed");
+ diff_tree_sha1(head, new_head, "", &opts);
+ diffcore_std(&opts);
+ diff_flush(&opts);
+ }
+
+ /* Run a post-merge hook */
+ run_hook("post-merge");
+
+ strbuf_release(&reflog_message);
+}
+
+/* Get the name for the merge commit's message. */
+static void merge_name(const char *remote, struct strbuf *msg)
+{
+ struct object *remote_head;
+ unsigned char branch_head[20], buf_sha[20];
+ struct strbuf buf;
+ const char *ptr;
+ int len, early;
+
+ memset(branch_head, 0, sizeof(branch_head));
+ remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
+ if (!remote_head)
+ die("'%s' does not point to a commit", remote);
+
+ strbuf_init(&buf, 0);
+ strbuf_addstr(&buf, "refs/heads/");
+ strbuf_addstr(&buf, remote);
+ resolve_ref(buf.buf, branch_head, 0, 0);
+
+ if (!hashcmp(remote_head->sha1, branch_head)) {
+ strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
+ sha1_to_hex(branch_head), remote);
+ return;
+ }
+
+ /* See if remote matches <name>^^^.. or <name>~<number> */
+ for (len = 0, ptr = remote + strlen(remote);
+ remote < ptr && ptr[-1] == '^';
+ ptr--)
+ len++;
+ if (len)
+ early = 1;
+ else {
+ early = 0;
+ ptr = strrchr(remote, '~');
+ if (ptr) {
+ int seen_nonzero = 0;
+
+ len++; /* count ~ */
+ while (*++ptr && isdigit(*ptr)) {
+ seen_nonzero |= (*ptr != '0');
+ len++;
+ }
+ if (*ptr)
+ len = 0; /* not ...~<number> */
+ else if (seen_nonzero)
+ early = 1;
+ else if (len == 1)
+ early = 1; /* "name~" is "name~1"! */
+ }
+ }
+ if (len) {
+ struct strbuf truname = STRBUF_INIT;
+ strbuf_addstr(&truname, "refs/heads/");
+ strbuf_addstr(&truname, remote);
+ strbuf_setlen(&truname, len+11);
+ if (resolve_ref(truname.buf, buf_sha, 0, 0)) {
+ strbuf_addf(msg,
+ "%s\t\tbranch '%s'%s of .\n",
+ sha1_to_hex(remote_head->sha1),
+ truname.buf,
+ (early ? " (early part)" : ""));
+ return;
+ }
+ }
+
+ if (!strcmp(remote, "FETCH_HEAD") &&
+ !access(git_path("FETCH_HEAD"), R_OK)) {
+ FILE *fp;
+ struct strbuf line;
+ char *ptr;
+
+ strbuf_init(&line, 0);
+ fp = fopen(git_path("FETCH_HEAD"), "r");
+ if (!fp)
+ die("could not open %s for reading: %s",
+ git_path("FETCH_HEAD"), strerror(errno));
+ strbuf_getline(&line, fp, '\n');
+ fclose(fp);
+ ptr = strstr(line.buf, "\tnot-for-merge\t");
+ if (ptr)
+ strbuf_remove(&line, ptr-line.buf+1, 13);
+ strbuf_addbuf(msg, &line);
+ strbuf_release(&line);
+ return;
+ }
+ strbuf_addf(msg, "%s\t\tcommit '%s'\n",
+ sha1_to_hex(remote_head->sha1), remote);
+}
+
+int git_merge_config(const char *k, const char *v, void *cb)
+{
+ if (branch && !prefixcmp(k, "branch.") &&
+ !prefixcmp(k + 7, branch) &&
+ !strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
+ const char **argv;
+ int argc;
+ char *buf;
+
+ buf = xstrdup(v);
+ argc = split_cmdline(buf, &argv);
+ argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
+ memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
+ argc++;
+ parse_options(argc, argv, builtin_merge_options,
+ builtin_merge_usage, 0);
+ free(buf);
+ }
+
+ if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
+ show_diffstat = git_config_bool(k, v);
+ else if (!strcmp(k, "pull.twohead"))
+ return git_config_string(&pull_twohead, k, v);
+ else if (!strcmp(k, "pull.octopus"))
+ return git_config_string(&pull_octopus, k, v);
+ else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary"))
+ option_log = git_config_bool(k, v);
+ return git_diff_ui_config(k, v, cb);
+}
+
+static int read_tree_trivial(unsigned char *common, unsigned char *head,
+ unsigned char *one)
+{
+ int i, nr_trees = 0;
+ struct tree *trees[MAX_UNPACK_TREES];
+ struct tree_desc t[MAX_UNPACK_TREES];
+ struct unpack_trees_options opts;
+
+ memset(&opts, 0, sizeof(opts));
+ opts.head_idx = 2;
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
+ opts.update = 1;
+ opts.verbose_update = 1;
+ opts.trivial_merges_only = 1;
+ opts.merge = 1;
+ trees[nr_trees] = parse_tree_indirect(common);
+ if (!trees[nr_trees++])
+ return -1;
+ trees[nr_trees] = parse_tree_indirect(head);
+ if (!trees[nr_trees++])
+ return -1;
+ trees[nr_trees] = parse_tree_indirect(one);
+ if (!trees[nr_trees++])
+ return -1;
+ opts.fn = threeway_merge;
+ cache_tree_free(&active_cache_tree);
+ for (i = 0; i < nr_trees; i++) {
+ parse_tree(trees[i]);
+ init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+ }
+ if (unpack_trees(nr_trees, t, &opts))
+ return -1;
+ return 0;
+}
+
+static void write_tree_trivial(unsigned char *sha1)
+{
+ if (write_cache_as_tree(sha1, 0, NULL))
+ die("git write-tree failed to write a tree");
+}
+
+static int try_merge_strategy(const char *strategy, struct commit_list *common,
+ const char *head_arg)
+{
+ const char **args;
+ int i = 0, ret;
+ struct commit_list *j;
+ struct strbuf buf;
+
+ args = xmalloc((4 + commit_list_count(common) +
+ commit_list_count(remoteheads)) * sizeof(char *));
+ strbuf_init(&buf, 0);
+ strbuf_addf(&buf, "merge-%s", strategy);
+ args[i++] = buf.buf;
+ for (j = common; j; j = j->next)
+ args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+ args[i++] = "--";
+ args[i++] = head_arg;
+ for (j = remoteheads; j; j = j->next)
+ args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+ args[i] = NULL;
+ ret = run_command_v_opt(args, RUN_GIT_CMD);
+ strbuf_release(&buf);
+ i = 1;
+ for (j = common; j; j = j->next)
+ free((void *)args[i++]);
+ i += 2;
+ for (j = remoteheads; j; j = j->next)
+ free((void *)args[i++]);
+ free(args);
+ return -ret;
+}
+
+static void count_diff_files(struct diff_queue_struct *q,
+ struct diff_options *opt, void *data)
+{
+ int *count = data;
+
+ (*count) += q->nr;
+}
+
+static int count_unmerged_entries(void)
+{
+ const struct index_state *state = &the_index;
+ int i, ret = 0;
+
+ for (i = 0; i < state->cache_nr; i++)
+ if (ce_stage(state->cache[i]))
+ ret++;
+
+ return ret;
+}
+
+static int checkout_fast_forward(unsigned char *head, unsigned char *remote)
+{
+ struct tree *trees[MAX_UNPACK_TREES];
+ struct unpack_trees_options opts;
+ struct tree_desc t[MAX_UNPACK_TREES];
+ int i, fd, nr_trees = 0;
+ struct dir_struct dir;
+ struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+
+ if (read_cache_unmerged())
+ die("you need to resolve your current index first");
+
+ fd = hold_locked_index(lock_file, 1);
+
+ memset(&trees, 0, sizeof(trees));
+ memset(&opts, 0, sizeof(opts));
+ memset(&t, 0, sizeof(t));
+ dir.show_ignored = 1;
+ dir.exclude_per_dir = ".gitignore";
+ opts.dir = &dir;
+
+ opts.head_idx = 1;
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
+ opts.update = 1;
+ opts.verbose_update = 1;
+ opts.merge = 1;
+ opts.fn = twoway_merge;
+
+ trees[nr_trees] = parse_tree_indirect(head);
+ if (!trees[nr_trees++])
+ return -1;
+ trees[nr_trees] = parse_tree_indirect(remote);
+ if (!trees[nr_trees++])
+ return -1;
+ for (i = 0; i < nr_trees; i++) {
+ parse_tree(trees[i]);
+ init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
+ }
+ if (unpack_trees(nr_trees, t, &opts))
+ return -1;
+ if (write_cache(fd, active_cache, active_nr) ||
+ commit_locked_index(lock_file))
+ die("unable to write new index file");
+ return 0;
+}
+
+static void split_merge_strategies(const char *string, struct strategy **list,
+ int *nr, int *alloc)
+{
+ char *p, *q, *buf;
+
+ if (!string)
+ return;
+
+ buf = xstrdup(string);
+ q = buf;
+ for (;;) {
+ p = strchr(q, ' ');
+ if (!p) {
+ ALLOC_GROW(*list, *nr + 1, *alloc);
+ (*list)[(*nr)++].name = xstrdup(q);
+ free(buf);
+ return;
+ } else {
+ *p = '\0';
+ ALLOC_GROW(*list, *nr + 1, *alloc);
+ (*list)[(*nr)++].name = xstrdup(q);
+ q = ++p;
+ }
+ }
+}
+
+static void add_strategies(const char *string, unsigned attr)
+{
+ struct strategy *list = NULL;
+ int list_alloc = 0, list_nr = 0, i;
+
+ memset(&list, 0, sizeof(list));
+ split_merge_strategies(string, &list, &list_nr, &list_alloc);
+ if (list != NULL) {
+ for (i = 0; i < list_nr; i++) {
+ struct strategy *s;
+
+ s = get_strategy(list[i].name);
+ if (s)
+ append_strategy(s);
+ }
+ return;
+ }
+ for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
+ if (all_strategy[i].attr & attr)
+ append_strategy(&all_strategy[i]);
+
+}
+
+static int merge_trivial(void)
+{
+ unsigned char result_tree[20], result_commit[20];
+ struct commit_list parent;
+
+ write_tree_trivial(result_tree);
+ printf("Wonderful.\n");
+ parent.item = remoteheads->item;
+ parent.next = NULL;
+ commit_tree(merge_msg.buf, result_tree, &parent, result_commit);
+ finish(result_commit, "In-index merge");
+ drop_save();
+ return 0;
+}
+
+static int finish_automerge(struct commit_list *common,
+ unsigned char *result_tree,
+ const char *wt_strategy)
+{
+ struct commit_list *parents = NULL, *j;
+ struct strbuf buf = STRBUF_INIT;
+ unsigned char result_commit[20];
+
+ free_commit_list(common);
+ if (allow_fast_forward) {
+ parents = remoteheads;
+ commit_list_insert(lookup_commit(head), &parents);
+ parents = reduce_heads(parents);
+ } else {
+ struct commit_list **pptr = &parents;
+
+ pptr = &commit_list_insert(lookup_commit(head),
+ pptr)->next;
+ for (j = remoteheads; j; j = j->next)
+ pptr = &commit_list_insert(j->item, pptr)->next;
+ }
+ free_commit_list(remoteheads);
+ strbuf_addch(&merge_msg, '\n');
+ commit_tree(merge_msg.buf, result_tree, parents, result_commit);
+ strbuf_addf(&buf, "Merge made by %s.", wt_strategy);
+ finish(result_commit, buf.buf);
+ strbuf_release(&buf);
+ drop_save();
+ return 0;
+}
+
+static int suggest_conflicts(void)
+{
+ FILE *fp;
+ int pos;
+
+ fp = fopen(git_path("MERGE_MSG"), "a");
+ if (!fp)
+ die("Could open %s for writing", git_path("MERGE_MSG"));
+ fprintf(fp, "\nConflicts:\n");
+ for (pos = 0; pos < active_nr; pos++) {
+ struct cache_entry *ce = active_cache[pos];
+
+ if (ce_stage(ce)) {
+ fprintf(fp, "\t%s\n", ce->name);
+ while (pos + 1 < active_nr &&
+ !strcmp(ce->name,
+ active_cache[pos + 1]->name))
+ pos++;
+ }
+ }
+ fclose(fp);
+ rerere();
+ printf("Automatic merge failed; "
+ "fix conflicts and then commit the result.\n");
+ return 1;
+}
+
+static struct commit *is_old_style_invocation(int argc, const char **argv)
+{
+ struct commit *second_token = NULL;
+ if (argc > 1) {
+ unsigned char second_sha1[20];
+
+ if (get_sha1(argv[1], second_sha1))
+ return NULL;
+ second_token = lookup_commit_reference_gently(second_sha1, 0);
+ if (!second_token)
+ die("'%s' is not a commit", argv[1]);
+ if (hashcmp(second_token->object.sha1, head))
+ return NULL;
+ }
+ return second_token;
+}
+
+static int evaluate_result(void)
+{
+ int cnt = 0;
+ struct rev_info rev;
+
+ if (read_cache() < 0)
+ die("failed to read the cache");
+
+ /* Check how many files differ. */
+ init_revisions(&rev, "");
+ setup_revisions(0, NULL, &rev, NULL);
+ rev.diffopt.output_format |=
+ DIFF_FORMAT_CALLBACK;
+ rev.diffopt.format_callback = count_diff_files;
+ rev.diffopt.format_callback_data = &cnt;
+ run_diff_files(&rev, 0);
+
+ /*
+ * Check how many unmerged entries are
+ * there.
+ */
+ cnt += count_unmerged_entries();
+
+ return cnt;
+}
+
+int cmd_merge(int argc, const char **argv, const char *prefix)
+{
+ unsigned char result_tree[20];
+ struct strbuf buf;
+ const char *head_arg;
+ int flag, head_invalid = 0, i;
+ int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
+ struct commit_list *common = NULL;
+ const char *best_strategy = NULL, *wt_strategy = NULL;
+ struct commit_list **remotes = &remoteheads;
+
+ setup_work_tree();
+ if (unmerged_cache())
+ die("You are in the middle of a conflicted merge.");
+
+ /*
+ * Check if we are _not_ on a detached HEAD, i.e. if there is a
+ * current branch.
+ */
+ branch = resolve_ref("HEAD", head, 0, &flag);
+ if (branch && !prefixcmp(branch, "refs/heads/"))
+ branch += 11;
+ if (is_null_sha1(head))
+ head_invalid = 1;
+
+ git_config(git_merge_config, NULL);
+
+ /* for color.ui */
+ if (diff_use_color_default == -1)
+ diff_use_color_default = git_use_color_default;
+
+ argc = parse_options(argc, argv, builtin_merge_options,
+ builtin_merge_usage, 0);
+
+ if (squash) {
+ if (!allow_fast_forward)
+ die("You cannot combine --squash with --no-ff.");
+ option_commit = 0;
+ }
+
+ if (!argc)
+ usage_with_options(builtin_merge_usage,
+ builtin_merge_options);
+
+ /*
+ * This could be traditional "merge <msg> HEAD <commit>..." and
+ * the way we can tell it is to see if the second token is HEAD,
+ * but some people might have misused the interface and used a
+ * committish that is the same as HEAD there instead.
+ * Traditional format never would have "-m" so it is an
+ * additional safety measure to check for it.
+ */
+ strbuf_init(&buf, 0);
+
+ if (!have_message && is_old_style_invocation(argc, argv)) {
+ strbuf_addstr(&merge_msg, argv[0]);
+ head_arg = argv[1];
+ argv += 2;
+ argc -= 2;
+ } else if (head_invalid) {
+ struct object *remote_head;
+ /*
+ * If the merged head is a valid one there is no reason
+ * to forbid "git merge" into a branch yet to be born.
+ * We do the same for "git pull".
+ */
+ if (argc != 1)
+ die("Can merge only exactly one commit into "
+ "empty head");
+ remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT);
+ if (!remote_head)
+ die("%s - not something we can merge", argv[0]);
+ update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0,
+ DIE_ON_ERR);
+ reset_hard(remote_head->sha1, 0);
+ return 0;
+ } else {
+ struct strbuf msg;
+
+ /* We are invoked directly as the first-class UI. */
+ head_arg = "HEAD";
+
+ /*
+ * All the rest are the commits being merged;
+ * prepare the standard merge summary message to
+ * be appended to the given message. If remote
+ * is invalid we will die later in the common
+ * codepath so we discard the error in this
+ * loop.
+ */
+ strbuf_init(&msg, 0);
+ for (i = 0; i < argc; i++)
+ merge_name(argv[i], &msg);
+ fmt_merge_msg(option_log, &msg, &merge_msg);
+ if (merge_msg.len)
+ strbuf_setlen(&merge_msg, merge_msg.len-1);
+ }
+
+ if (head_invalid || !argc)
+ usage_with_options(builtin_merge_usage,
+ builtin_merge_options);
+
+ strbuf_addstr(&buf, "merge");
+ for (i = 0; i < argc; i++)
+ strbuf_addf(&buf, " %s", argv[i]);
+ setenv("GIT_REFLOG_ACTION", buf.buf, 0);
+ strbuf_reset(&buf);
+
+ for (i = 0; i < argc; i++) {
+ struct object *o;
+
+ o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
+ if (!o)
+ die("%s - not something we can merge", argv[i]);
+ remotes = &commit_list_insert(lookup_commit(o->sha1),
+ remotes)->next;
+
+ strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
+ setenv(buf.buf, argv[i], 1);
+ strbuf_reset(&buf);
+ }
+
+ if (!use_strategies) {
+ if (!remoteheads->next)
+ add_strategies(pull_twohead, DEFAULT_TWOHEAD);
+ else
+ add_strategies(pull_octopus, DEFAULT_OCTOPUS);
+ }
+
+ for (i = 0; i < use_strategies_nr; i++) {
+ if (use_strategies[i]->attr & NO_FAST_FORWARD)
+ allow_fast_forward = 0;
+ if (use_strategies[i]->attr & NO_TRIVIAL)
+ allow_trivial = 0;
+ }
+
+ if (!remoteheads->next)
+ common = get_merge_bases(lookup_commit(head),
+ remoteheads->item, 1);
+ else {
+ struct commit_list *list = remoteheads;
+ commit_list_insert(lookup_commit(head), &list);
+ common = get_octopus_merge_bases(list);
+ free(list);
+ }
+
+ update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0,
+ DIE_ON_ERR);
+
+ if (!common)
+ ; /* No common ancestors found. We need a real merge. */
+ else if (!remoteheads->next && !common->next &&
+ common->item == remoteheads->item) {
+ /*
+ * If head can reach all the merge then we are up to date.
+ * but first the most common case of merging one remote.
+ */
+ finish_up_to_date("Already up-to-date.");
+ return 0;
+ } else if (allow_fast_forward && !remoteheads->next &&
+ !common->next &&
+ !hashcmp(common->item->object.sha1, head)) {
+ /* Again the most common case of merging one remote. */
+ struct strbuf msg;
+ struct object *o;
+ char hex[41];
+
+ strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV));
+
+ printf("Updating %s..%s\n",
+ hex,
+ find_unique_abbrev(remoteheads->item->object.sha1,
+ DEFAULT_ABBREV));
+ refresh_cache(REFRESH_QUIET);
+ strbuf_init(&msg, 0);
+ strbuf_addstr(&msg, "Fast forward");
+ if (have_message)
+ strbuf_addstr(&msg,
+ " (no commit created; -m option ignored)");
+ o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
+ 0, NULL, OBJ_COMMIT);
+ if (!o)
+ return 1;
+
+ if (checkout_fast_forward(head, remoteheads->item->object.sha1))
+ return 1;
+
+ finish(o->sha1, msg.buf);
+ drop_save();
+ return 0;
+ } else if (!remoteheads->next && common->next)
+ ;
+ /*
+ * We are not doing octopus and not fast forward. Need
+ * a real merge.
+ */
+ else if (!remoteheads->next && !common->next && option_commit) {
+ /*
+ * We are not doing octopus, not fast forward, and have
+ * only one common.
+ */
+ refresh_cache(REFRESH_QUIET);
+ if (allow_trivial) {
+ /* See if it is really trivial. */
+ git_committer_info(IDENT_ERROR_ON_NO_NAME);
+ printf("Trying really trivial in-index merge...\n");
+ if (!read_tree_trivial(common->item->object.sha1,
+ head, remoteheads->item->object.sha1))
+ return merge_trivial();
+ printf("Nope.\n");
+ }
+ } else {
+ /*
+ * An octopus. If we can reach all the remote we are up
+ * to date.
+ */
+ int up_to_date = 1;
+ struct commit_list *j;
+
+ for (j = remoteheads; j; j = j->next) {
+ struct commit_list *common_one;
+
+ /*
+ * Here we *have* to calculate the individual
+ * merge_bases again, otherwise "git merge HEAD^
+ * HEAD^^" would be missed.
+ */
+ common_one = get_merge_bases(lookup_commit(head),
+ j->item, 1);
+ if (hashcmp(common_one->item->object.sha1,
+ j->item->object.sha1)) {
+ up_to_date = 0;
+ break;
+ }
+ }
+ if (up_to_date) {
+ finish_up_to_date("Already up-to-date. Yeeah!");
+ return 0;
+ }
+ }
+
+ /* We are going to make a new commit. */
+ git_committer_info(IDENT_ERROR_ON_NO_NAME);
+
+ /*
+ * At this point, we need a real merge. No matter what strategy
+ * we use, it would operate on the index, possibly affecting the
+ * working tree, and when resolved cleanly, have the desired
+ * tree in the index -- this means that the index must be in
+ * sync with the head commit. The strategies are responsible
+ * to ensure this.
+ */
+ if (use_strategies_nr != 1) {
+ /*
+ * Stash away the local changes so that we can try more
+ * than one.
+ */
+ save_state();
+ } else {
+ memcpy(stash, null_sha1, 20);
+ }
+
+ for (i = 0; i < use_strategies_nr; i++) {
+ int ret;
+ if (i) {
+ printf("Rewinding the tree to pristine...\n");
+ restore_state();
+ }
+ if (use_strategies_nr != 1)
+ printf("Trying merge strategy %s...\n",
+ use_strategies[i]->name);
+ /*
+ * Remember which strategy left the state in the working
+ * tree.
+ */
+ wt_strategy = use_strategies[i]->name;
+
+ ret = try_merge_strategy(use_strategies[i]->name,
+ common, head_arg);
+ if (!option_commit && !ret) {
+ merge_was_ok = 1;
+ /*
+ * This is necessary here just to avoid writing
+ * the tree, but later we will *not* exit with
+ * status code 1 because merge_was_ok is set.
+ */
+ ret = 1;
+ }
+
+ if (ret) {
+ /*
+ * The backend exits with 1 when conflicts are
+ * left to be resolved, with 2 when it does not
+ * handle the given merge at all.
+ */
+ if (ret == 1) {
+ int cnt = evaluate_result();
+
+ if (best_cnt <= 0 || cnt <= best_cnt) {
+ best_strategy = use_strategies[i]->name;
+ best_cnt = cnt;
+ }
+ }
+ if (merge_was_ok)
+ break;
+ else
+ continue;
+ }
+
+ /* Automerge succeeded. */
+ write_tree_trivial(result_tree);
+ automerge_was_ok = 1;
+ break;
+ }
+
+ /*
+ * If we have a resulting tree, that means the strategy module
+ * auto resolved the merge cleanly.
+ */
+ if (automerge_was_ok)
+ return finish_automerge(common, result_tree, wt_strategy);
+
+ /*
+ * Pick the result from the best strategy and have the user fix
+ * it up.
+ */
+ if (!best_strategy) {
+ restore_state();
+ if (use_strategies_nr > 1)
+ fprintf(stderr,
+ "No merge strategy handled the merge.\n");
+ else
+ fprintf(stderr, "Merge with strategy %s failed.\n",
+ use_strategies[0]->name);
+ return 2;
+ } else if (best_strategy == wt_strategy)
+ ; /* We already have its result in the working tree. */
+ else {
+ printf("Rewinding the tree to pristine...\n");
+ restore_state();
+ printf("Using the %s to prepare resolving by hand.\n",
+ best_strategy);
+ try_merge_strategy(best_strategy, common, head_arg);
+ }
+
+ if (squash)
+ finish(NULL, NULL);
+ else {
+ int fd;
+ struct commit_list *j;
+
+ for (j = remoteheads; j; j = j->next)
+ strbuf_addf(&buf, "%s\n",
+ sha1_to_hex(j->item->object.sha1));
+ fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666);
+ if (fd < 0)
+ die("Could open %s for writing",
+ git_path("MERGE_HEAD"));
+ if (write_in_full(fd, buf.buf, buf.len) != buf.len)
+ die("Could not write to %s", git_path("MERGE_HEAD"));
+ close(fd);
+ strbuf_addch(&merge_msg, '\n');
+ fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
+ if (fd < 0)
+ die("Could open %s for writing", git_path("MERGE_MSG"));
+ if (write_in_full(fd, merge_msg.buf, merge_msg.len) !=
+ merge_msg.len)
+ die("Could not write to %s", git_path("MERGE_MSG"));
+ close(fd);
+ }
+
+ if (merge_was_ok) {
+ fprintf(stderr, "Automatic merge went well; "
+ "stopped before committing as requested\n");
+ return 0;
+ } else
+ return suggest_conflicts();
+}
diff --git a/builtin-read-tree.c b/builtin-read-tree.c
index 5a09e17f1a..72a6de302f 100644
--- a/builtin-read-tree.c
+++ b/builtin-read-tree.c
@@ -29,30 +29,6 @@ static int list_tree(unsigned char *sha1)
return 0;
}
-static int read_cache_unmerged(void)
-{
- int i;
- struct cache_entry **dst;
- struct cache_entry *last = NULL;
-
- read_cache();
- dst = active_cache;
- for (i = 0; i < active_nr; i++) {
- struct cache_entry *ce = active_cache[i];
- if (ce_stage(ce)) {
- remove_name_hash(ce);
- if (last && !strcmp(ce->name, last->name))
- continue;
- cache_tree_invalidate_path(active_cache_tree, ce->name);
- last = ce;
- continue;
- }
- *dst++ = ce;
- }
- active_nr = dst - active_cache;
- return !!last;
-}
-
static void prime_cache_tree_rec(struct cache_tree *it, struct tree *tree)
{
struct tree_desc desc;
diff --git a/builtin-remote.c b/builtin-remote.c
index 145dd8568c..1491354a9d 100644
--- a/builtin-remote.c
+++ b/builtin-remote.c
@@ -29,12 +29,6 @@ static inline int postfixcmp(const char *string, const char *postfix)
return strcmp(string + len1 - len2, postfix);
}
-static inline const char *skip_prefix(const char *name, const char *prefix)
-{
- return !name ? "" :
- prefixcmp(name, prefix) ? name : name + strlen(prefix);
-}
-
static int opt_parse_track(const struct option *opt, const char *arg, int not)
{
struct path_list *list = opt->value;
@@ -182,12 +176,18 @@ static int config_read_branches(const char *key, const char *value, void *cb)
info->remote = xstrdup(value);
} else {
char *space = strchr(value, ' ');
- value = skip_prefix(value, "refs/heads/");
+ const char *ptr = skip_prefix(value, "refs/heads/");
+ if (ptr)
+ value = ptr;
while (space) {
char *merge;
merge = xstrndup(value, space - value);
path_list_append(merge, &info->merge);
- value = skip_prefix(space + 1, "refs/heads/");
+ ptr = skip_prefix(space + 1, "refs/heads/");
+ if (ptr)
+ value = ptr;
+ else
+ value = space + 1;
space = strchr(value, ' ');
}
path_list_append(xstrdup(value), &info->merge);
@@ -219,7 +219,12 @@ static int handle_one_branch(const char *refname,
refspec.dst = (char *)refname;
if (!remote_find_tracking(states->remote, &refspec)) {
struct path_list_item *item;
- const char *name = skip_prefix(refspec.src, "refs/heads/");
+ const char *name, *ptr;
+ ptr = skip_prefix(refspec.src, "refs/heads/");
+ if (ptr)
+ name = ptr;
+ else
+ name = refspec.src;
/* symbolic refs pointing nowhere were handled already */
if ((flags & REF_ISSYMREF) ||
unsorted_path_list_has_path(&states->tracked,
@@ -248,6 +253,7 @@ static int get_ref_states(const struct ref *ref, struct ref_states *states)
struct path_list *target = &states->tracked;
unsigned char sha1[20];
void *util = NULL;
+ const char *ptr;
if (!ref->peer_ref || read_ref(ref->peer_ref->name, sha1))
target = &states->new;
@@ -256,8 +262,10 @@ static int get_ref_states(const struct ref *ref, struct ref_states *states)
if (hashcmp(sha1, ref->new_sha1))
util = &states;
}
- path_list_append(skip_prefix(ref->name, "refs/heads/"),
- target)->util = util;
+ ptr = skip_prefix(ref->name, "refs/heads/");
+ if (!ptr)
+ ptr = ref->name;
+ path_list_append(ptr, target)->util = util;
}
free_refs(fetch_map);
@@ -522,10 +530,15 @@ static int show(int argc, const char **argv)
"es" : "");
for (i = 0; i < states.remote->push_refspec_nr; i++) {
struct refspec *spec = states.remote->push + i;
+ const char *p = "", *q = "";
+ if (spec->src)
+ p = skip_prefix(spec->src, "refs/heads/");
+ if (spec->dst)
+ q = skip_prefix(spec->dst, "refs/heads/");
printf(" %s%s%s%s", spec->force ? "+" : "",
- skip_prefix(spec->src, "refs/heads/"),
+ p ? p : spec->src,
spec->dst ? ":" : "",
- skip_prefix(spec->dst, "refs/heads/"));
+ q ? q : spec->dst);
}
printf("\n");
}
diff --git a/builtin-tag.c b/builtin-tag.c
index 3c97c696a5..a70922b21c 100644
--- a/builtin-tag.c
+++ b/builtin-tag.c
@@ -202,6 +202,7 @@ static int do_sign(struct strbuf *buffer)
const char *args[4];
char *bracket;
int len;
+ int i, j;
if (!*signingkey) {
if (strlcpy(signingkey, git_committer_info(IDENT_ERROR_ON_NO_NAME),
@@ -241,6 +242,15 @@ static int do_sign(struct strbuf *buffer)
if (finish_command(&gpg) || !len || len < 0)
return error("gpg failed to sign the tag");
+ /* Strip CR from the line endings, in case we are on Windows. */
+ for (i = j = 0; i < buffer->len; i++)
+ if (buffer->buf[i] != '\r') {
+ if (i != j)
+ buffer->buf[j] = buffer->buf[i];
+ j++;
+ }
+ strbuf_setlen(buffer, j);
+
return 0;
}
diff --git a/builtin.h b/builtin.h
index b460b2da6f..0e605d4f4a 100644
--- a/builtin.h
+++ b/builtin.h
@@ -2,6 +2,9 @@
#define BUILTIN_H
#include "git-compat-util.h"
+#include "strbuf.h"
+#include "cache.h"
+#include "commit.h"
extern const char git_version_string[];
extern const char git_usage_string[];
@@ -11,6 +14,10 @@ extern void list_common_cmds_help(void);
extern void help_unknown_cmd(const char *cmd);
extern void prune_packed_objects(int);
extern int read_line_with_nul(char *buf, int size, FILE *file);
+extern int fmt_merge_msg(int merge_summary, struct strbuf *in,
+ struct strbuf *out);
+extern int commit_tree(const char *msg, unsigned char *tree,
+ struct commit_list *parents, unsigned char *ret);
extern int cmd_add(int argc, const char **argv, const char *prefix);
extern int cmd_annotate(int argc, const char **argv, const char *prefix);
@@ -57,6 +64,7 @@ extern int cmd_ls_tree(int argc, const char **argv, const char *prefix);
extern int cmd_ls_remote(int argc, const char **argv, const char *prefix);
extern int cmd_mailinfo(int argc, const char **argv, const char *prefix);
extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);
+extern int cmd_merge(int argc, const char **argv, const char *prefix);
extern int cmd_merge_base(int argc, const char **argv, const char *prefix);
extern int cmd_merge_ours(int argc, const char **argv, const char *prefix);
extern int cmd_merge_file(int argc, const char **argv, const char *prefix);
diff --git a/cache-tree.c b/cache-tree.c
index 73cb340707..5f8ee87bb1 100644
--- a/cache-tree.c
+++ b/cache-tree.c
@@ -507,7 +507,7 @@ struct cache_tree *cache_tree_read(const char *buffer, unsigned long size)
return read_one(&buffer, &size);
}
-struct cache_tree *cache_tree_find(struct cache_tree *it, const char *path)
+static struct cache_tree *cache_tree_find(struct cache_tree *it, const char *path)
{
while (*path) {
const char *slash;
diff --git a/cache-tree.h b/cache-tree.h
index 44aad426d3..cf8b790874 100644
--- a/cache-tree.h
+++ b/cache-tree.h
@@ -28,8 +28,6 @@ struct cache_tree *cache_tree_read(const char *buffer, unsigned long size);
int cache_tree_fully_valid(struct cache_tree *);
int cache_tree_update(struct cache_tree *, struct cache_entry **, int, int, int);
-struct cache_tree *cache_tree_find(struct cache_tree *, const char *);
-
#define WRITE_TREE_UNREADABLE_INDEX (-1)
#define WRITE_TREE_UNMERGED_INDEX (-2)
#define WRITE_TREE_PREFIX_ERROR (-3)
diff --git a/cache.h b/cache.h
index a8254e2f9e..a779d9207c 100644
--- a/cache.h
+++ b/cache.h
@@ -254,6 +254,7 @@ static inline void remove_name_hash(struct cache_entry *ce)
#define read_cache() read_index(&the_index)
#define read_cache_from(path) read_index_from(&the_index, (path))
+#define read_cache_unmerged() read_index_unmerged(&the_index)
#define write_cache(newfd, cache, entries) write_index(&the_index, (newfd))
#define discard_cache() discard_index(&the_index)
#define unmerged_cache() unmerged_index(&the_index)
@@ -356,6 +357,7 @@ extern int init_db(const char *template_dir, unsigned int flags);
/* Initialize and use the cache information */
extern int read_index(struct index_state *);
extern int read_index_from(struct index_state *, const char *path);
+extern int read_index_unmerged(struct index_state *);
extern int write_index(const struct index_state *, int newfd);
extern int discard_index(struct index_state *);
extern int unmerged_index(const struct index_state *);
@@ -837,5 +839,6 @@ int report_path_error(const char *ps_matched, const char **pathspec, int prefix_
void overlay_tree_on_cache(const char *tree_name, const char *prefix);
char *alias_lookup(const char *alias);
+int split_cmdline(char *cmdline, const char ***argv);
#endif /* CACHE_H */
diff --git a/commit.c b/commit.c
index e2d8624d9c..5148ec5527 100644
--- a/commit.c
+++ b/commit.c
@@ -325,6 +325,14 @@ struct commit_list *commit_list_insert(struct commit *item, struct commit_list *
return new_list;
}
+unsigned commit_list_count(const struct commit_list *l)
+{
+ unsigned c = 0;
+ for (; l; l = l->next )
+ c++;
+ return c;
+}
+
void free_commit_list(struct commit_list *list)
{
while (list) {
@@ -525,26 +533,34 @@ static struct commit *interesting(struct commit_list *list)
return NULL;
}
-static struct commit_list *merge_bases(struct commit *one, struct commit *two)
+static struct commit_list *merge_bases_many(struct commit *one, int n, struct commit **twos)
{
struct commit_list *list = NULL;
struct commit_list *result = NULL;
+ int i;
- if (one == two)
- /* We do not mark this even with RESULT so we do not
- * have to clean it up.
- */
- return commit_list_insert(one, &result);
+ for (i = 0; i < n; i++) {
+ if (one == twos[i])
+ /*
+ * We do not mark this even with RESULT so we do not
+ * have to clean it up.
+ */
+ return commit_list_insert(one, &result);
+ }
if (parse_commit(one))
return NULL;
- if (parse_commit(two))
- return NULL;
+ for (i = 0; i < n; i++) {
+ if (parse_commit(twos[i]))
+ return NULL;
+ }
one->object.flags |= PARENT1;
- two->object.flags |= PARENT2;
insert_by_date(one, &list);
- insert_by_date(two, &list);
+ for (i = 0; i < n; i++) {
+ twos[i]->object.flags |= PARENT2;
+ insert_by_date(twos[i], &list);
+ }
while (interesting(list)) {
struct commit *commit;
@@ -592,21 +608,53 @@ static struct commit_list *merge_bases(struct commit *one, struct commit *two)
return result;
}
-struct commit_list *get_merge_bases(struct commit *one,
- struct commit *two, int cleanup)
+struct commit_list *get_octopus_merge_bases(struct commit_list *in)
+{
+ struct commit_list *i, *j, *k, *ret = NULL;
+ struct commit_list **pptr = &ret;
+
+ for (i = in; i; i = i->next) {
+ if (!ret)
+ pptr = &commit_list_insert(i->item, pptr)->next;
+ else {
+ struct commit_list *new = NULL, *end = NULL;
+
+ for (j = ret; j; j = j->next) {
+ struct commit_list *bases;
+ bases = get_merge_bases(i->item, j->item, 1);
+ if (!new)
+ new = bases;
+ else
+ end->next = bases;
+ for (k = bases; k; k = k->next)
+ end = k;
+ }
+ ret = new;
+ }
+ }
+ return ret;
+}
+
+struct commit_list *get_merge_bases_many(struct commit *one,
+ int n,
+ struct commit **twos,
+ int cleanup)
{
struct commit_list *list;
struct commit **rslt;
struct commit_list *result;
int cnt, i, j;
- result = merge_bases(one, two);
- if (one == two)
- return result;
+ result = merge_bases_many(one, n, twos);
+ for (i = 0; i < n; i++) {
+ if (one == twos[i])
+ return result;
+ }
if (!result || !result->next) {
if (cleanup) {
clear_commit_marks(one, all_flags);
- clear_commit_marks(two, all_flags);
+ for (i = 0; i < n; i++)
+ clear_commit_marks(twos[i], all_flags);
}
return result;
}
@@ -624,12 +672,13 @@ struct commit_list *get_merge_bases(struct commit *one,
free_commit_list(result);
clear_commit_marks(one, all_flags);
- clear_commit_marks(two, all_flags);
+ for (i = 0; i < n; i++)
+ clear_commit_marks(twos[i], all_flags);
for (i = 0; i < cnt - 1; i++) {
for (j = i+1; j < cnt; j++) {
if (!rslt[i] || !rslt[j])
continue;
- result = merge_bases(rslt[i], rslt[j]);
+ result = merge_bases_many(rslt[i], 1, &rslt[j]);
clear_commit_marks(rslt[i], all_flags);
clear_commit_marks(rslt[j], all_flags);
for (list = result; list; list = list->next) {
@@ -651,6 +700,12 @@ struct commit_list *get_merge_bases(struct commit *one,
return result;
}
+struct commit_list *get_merge_bases(struct commit *one, struct commit *two,
+ int cleanup)
+{
+ return get_merge_bases_many(one, 1, &two, cleanup);
+}
+
int in_merge_bases(struct commit *commit, struct commit **reference, int num)
{
struct commit_list *bases, *b;
@@ -670,3 +725,55 @@ int in_merge_bases(struct commit *commit, struct commit **reference, int num)
free_commit_list(bases);
return ret;
}
+
+struct commit_list *reduce_heads(struct commit_list *heads)
+{
+ struct commit_list *p;
+ struct commit_list *result = NULL, **tail = &result;
+ struct commit **other;
+ size_t num_head, num_other;
+
+ if (!heads)
+ return NULL;
+
+ /* Avoid unnecessary reallocations */
+ for (p = heads, num_head = 0; p; p = p->next)
+ num_head++;
+ other = xcalloc(sizeof(*other), num_head);
+
+ /* For each commit, see if it can be reached by others */
+ for (p = heads; p; p = p->next) {
+ struct commit_list *q, *base;
+
+ /* Do we already have this in the result? */
+ for (q = result; q; q = q->next)
+ if (p->item == q->item)
+ break;
+ if (q)
+ continue;
+
+ num_other = 0;
+ for (q = heads; q; q = q->next) {
+ if (p->item == q->item)
+ continue;
+ other[num_other++] = q->item;
+ }
+ if (num_other)
+ base = get_merge_bases_many(p->item, num_other, other, 1);
+ else
+ base = NULL;
+ /*
+ * If p->item does not have anything common with other
+ * commits, there won't be any merge base. If it is
+ * reachable from some of the others, p->item will be
+ * the merge base. If its history is connected with
+ * others, but p->item is not reachable by others, we
+ * will get something other than p->item back.
+ */
+ if (!base || (base->item != p->item))
+ tail = &(commit_list_insert(p->item, tail)->next);
+ free_commit_list(base);
+ }
+ free(other);
+ return result;
+}
diff --git a/commit.h b/commit.h
index fda7ad03c0..77de9621d9 100644
--- a/commit.h
+++ b/commit.h
@@ -41,6 +41,7 @@ int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size);
int parse_commit(struct commit *item);
struct commit_list * commit_list_insert(struct commit *item, struct commit_list **list_p);
+unsigned commit_list_count(const struct commit_list *l);
struct commit_list * insert_by_date(struct commit *item, struct commit_list **list);
void free_commit_list(struct commit_list *list);
@@ -120,6 +121,7 @@ int read_graft_file(const char *graft_file);
struct commit_graft *lookup_commit_graft(const unsigned char *sha1);
extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2, int cleanup);
+extern struct commit_list *get_octopus_merge_bases(struct commit_list *in);
extern int register_shallow(const unsigned char *sha1);
extern int unregister_shallow(const unsigned char *sha1);
@@ -137,4 +139,6 @@ static inline int single_parent(struct commit *commit)
return commit->parents && !commit->parents->next;
}
+struct commit_list *reduce_heads(struct commit_list *heads);
+
#endif /* COMMIT_H */
diff --git a/compat/mingw.c b/compat/mingw.c
index 3a05fe7da6..c0bc849e45 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -1017,3 +1017,25 @@ sig_handler_t mingw_signal(int sig, sig_handler_t handler)
timer_fn = handler;
return old;
}
+
+static const char *make_backslash_path(const char *path)
+{
+ static char buf[PATH_MAX + 1];
+ char *c;
+
+ if (strlcpy(buf, path, PATH_MAX) >= PATH_MAX)
+ die("Too long path: %.*s", 60, path);
+
+ for (c = buf; *c; c++) {
+ if (*c == '/')
+ *c = '\\';
+ }
+ return buf;
+}
+
+void mingw_open_html(const char *unixpath)
+{
+ const char *htmlpath = make_backslash_path(unixpath);
+ printf("Launching default browser to display HTML ...\n");
+ ShellExecute(NULL, "open", htmlpath, NULL, "\\", 0);
+}
diff --git a/compat/mingw.h b/compat/mingw.h
index 6bc049ad99..5a3bcee29b 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -202,6 +202,9 @@ sig_handler_t mingw_signal(int sig, sig_handler_t handler);
#define PATH_SEP ';'
#define PRIuMAX "I64u"
+void mingw_open_html(const char *path);
+#define open_html mingw_open_html
+
/*
* helpers
*/
diff --git a/config.c b/config.c
index 2862cc45cb..1e066c71e0 100644
--- a/config.c
+++ b/config.c
@@ -581,15 +581,8 @@ int git_config_from_file(config_fn_t fn, const char *filename, void *data)
const char *git_etc_gitconfig(void)
{
static const char *system_wide;
- if (!system_wide) {
- system_wide = ETC_GITCONFIG;
- if (!is_absolute_path(system_wide)) {
- /* interpret path relative to exec-dir */
- struct strbuf d = STRBUF_INIT;
- strbuf_addf(&d, "%s/%s", git_exec_path(), system_wide);
- system_wide = strbuf_detach(&d, NULL);
- }
- }
+ if (!system_wide)
+ system_wide = system_path(ETC_GITCONFIG);
return system_wide;
}
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 03e4e02785..29f6cd4e9e 100755
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -73,26 +73,26 @@ __git_ps1 ()
if [ -n "$g" ]; then
local r
local b
- if [ -d "$g/../.dotest" ]
+ if [ -d "$g/rebase" ]
then
- if test -f "$g/../.dotest/rebasing"
+ if test -f "$g/rebase/rebasing"
then
r="|REBASE"
- elif test -f "$g/../.dotest/applying"
+ elif test -f "$g/rebase/applying"
then
r="|AM"
else
r="|AM/REBASE"
fi
b="$(git symbolic-ref HEAD 2>/dev/null)"
- elif [ -f "$g/.dotest-merge/interactive" ]
+ elif [ -f "$g/rebase-merge/interactive" ]
then
r="|REBASE-i"
- b="$(cat "$g/.dotest-merge/head-name")"
- elif [ -d "$g/.dotest-merge" ]
+ b="$(cat "$g/rebase-merge/head-name")"
+ elif [ -d "$g/rebase-merge" ]
then
r="|REBASE-m"
- b="$(cat "$g/.dotest-merge/head-name")"
+ b="$(cat "$g/rebase-merge/head-name")"
elif [ -f "$g/MERGE_HEAD" ]
then
r="|MERGING"
@@ -487,8 +487,8 @@ __git_whitespacelist="nowarn warn error error-all strip"
_git_am ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
- if [ -d .dotest ]; then
+ local cur="${COMP_WORDS[COMP_CWORD]}" dir="$(__gitdir)"
+ if [ -d "$dir"/rebase ]; then
__gitcomp "--skip --resolved"
return
fi
@@ -915,7 +915,7 @@ _git_push ()
_git_rebase ()
{
local cur="${COMP_WORDS[COMP_CWORD]}" dir="$(__gitdir)"
- if [ -d .dotest ] || [ -d "$dir"/.dotest-merge ]; then
+ if [ -d "$dir"/rebase ] || [ -d "$dir"/rebase-merge ]; then
__gitcomp "--continue --skip --abort"
return
fi
diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el
index 4fa853fae7..43b059bbaa 100644
--- a/contrib/emacs/git.el
+++ b/contrib/emacs/git.el
@@ -1252,8 +1252,8 @@ Return the list of files that haven't been handled."
"\n")
(when subject (insert subject "\n\n"))
(cond (msg (insert msg "\n"))
- ((file-readable-p ".dotest/msg")
- (insert-file-contents ".dotest/msg"))
+ ((file-readable-p ".git/rebase/msg")
+ (insert-file-contents ".git/rebase/msg"))
((file-readable-p ".git/MERGE_MSG")
(insert-file-contents ".git/MERGE_MSG")))
; delete empty lines at end
@@ -1272,9 +1272,9 @@ Return the list of files that haven't been handled."
(coding-system (git-get-commits-coding-system))
author-name author-email subject date)
(when (eq 0 (buffer-size buffer))
- (when (file-readable-p ".dotest/info")
+ (when (file-readable-p ".git/rebase/info")
(with-temp-buffer
- (insert-file-contents ".dotest/info")
+ (insert-file-contents ".git/rebase/info")
(goto-char (point-min))
(when (re-search-forward "^Author: \\(.*\\)\nEmail: \\(.*\\)$" nil t)
(setq author-name (match-string 1))
diff --git a/git-merge.sh b/contrib/examples/git-merge.sh
index 8026ccff4a..8026ccff4a 100755
--- a/git-merge.sh
+++ b/contrib/examples/git-merge.sh
diff --git a/convert.c b/convert.c
index 352b69d4ce..78efed800d 100644
--- a/convert.c
+++ b/convert.c
@@ -61,6 +61,10 @@ static void gather_stats(const char *buf, unsigned long size, struct text_stat *
else
stats->printable++;
}
+
+ /* If file ends with EOF then don't count this EOF as non-printable. */
+ if (size >= 1 && buf[size-1] == '\032')
+ stats->nonprintable--;
}
/*
diff --git a/diff-lib.c b/diff-lib.c
index b17722d66a..e7eaff9a68 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -171,7 +171,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
if (silent_on_removed)
continue;
diff_addremove(&revs->diffopt, '-', ce->ce_mode,
- ce->sha1, ce->name, NULL);
+ ce->sha1, ce->name);
continue;
}
changed = ce_match_stat(ce, &st, ce_option);
@@ -184,7 +184,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
newmode = ce_mode_from_stat(ce, st.st_mode);
diff_change(&revs->diffopt, oldmode, newmode,
ce->sha1, (changed ? null_sha1 : ce->sha1),
- ce->name, NULL);
+ ce->name);
}
diffcore_std(&revs->diffopt);
@@ -208,7 +208,7 @@ static void diff_index_show_file(struct rev_info *revs,
const unsigned char *sha1, unsigned int mode)
{
diff_addremove(&revs->diffopt, prefix[0], mode,
- sha1, ce->name, NULL);
+ sha1, ce->name);
}
static int get_stat_data(struct cache_entry *ce,
@@ -312,7 +312,7 @@ static int show_modified(struct oneway_unpack_data *cbdata,
return 0;
diff_change(&revs->diffopt, oldmode, mode,
- old->sha1, sha1, old->name, NULL);
+ old->sha1, sha1, old->name);
return 0;
}
diff --git a/diff.c b/diff.c
index 6a39b393f3..a07812c5c7 100644
--- a/diff.c
+++ b/diff.c
@@ -3412,9 +3412,8 @@ int diff_result_code(struct diff_options *opt, int status)
void diff_addremove(struct diff_options *options,
int addremove, unsigned mode,
const unsigned char *sha1,
- const char *base, const char *path)
+ const char *concatpath)
{
- char concatpath[PATH_MAX];
struct diff_filespec *one, *two;
if (DIFF_OPT_TST(options, IGNORE_SUBMODULES) && S_ISGITLINK(mode))
@@ -3436,9 +3435,6 @@ void diff_addremove(struct diff_options *options,
addremove = (addremove == '+' ? '-' :
addremove == '-' ? '+' : addremove);
- if (!path) path = "";
- sprintf(concatpath, "%s%s", base, path);
-
if (options->prefix &&
strncmp(concatpath, options->prefix, options->prefix_length))
return;
@@ -3459,9 +3455,8 @@ void diff_change(struct diff_options *options,
unsigned old_mode, unsigned new_mode,
const unsigned char *old_sha1,
const unsigned char *new_sha1,
- const char *base, const char *path)
+ const char *concatpath)
{
- char concatpath[PATH_MAX];
struct diff_filespec *one, *two;
if (DIFF_OPT_TST(options, IGNORE_SUBMODULES) && S_ISGITLINK(old_mode)
@@ -3474,8 +3469,6 @@ void diff_change(struct diff_options *options,
tmp = old_mode; old_mode = new_mode; new_mode = tmp;
tmp_c = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_c;
}
- if (!path) path = "";
- sprintf(concatpath, "%s%s", base, path);
if (options->prefix &&
strncmp(concatpath, options->prefix, options->prefix_length))
diff --git a/diff.h b/diff.h
index 5dc0cb595b..50fb5ddb0b 100644
--- a/diff.h
+++ b/diff.h
@@ -14,12 +14,12 @@ typedef void (*change_fn_t)(struct diff_options *options,
unsigned old_mode, unsigned new_mode,
const unsigned char *old_sha1,
const unsigned char *new_sha1,
- const char *base, const char *path);
+ const char *fullpath);
typedef void (*add_remove_fn_t)(struct diff_options *options,
int addremove, unsigned mode,
const unsigned char *sha1,
- const char *base, const char *path);
+ const char *fullpath);
typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
struct diff_options *options, void *data);
@@ -164,14 +164,13 @@ extern void diff_addremove(struct diff_options *,
int addremove,
unsigned mode,
const unsigned char *sha1,
- const char *base,
- const char *path);
+ const char *fullpath);
extern void diff_change(struct diff_options *,
unsigned mode1, unsigned mode2,
const unsigned char *sha1,
const unsigned char *sha2,
- const char *base, const char *path);
+ const char *fullpath);
extern void diff_unmerge(struct diff_options *,
const char *path,
diff --git a/exec_cmd.c b/exec_cmd.c
index da04efe951..8899e31b3b 100644
--- a/exec_cmd.c
+++ b/exec_cmd.c
@@ -40,6 +40,16 @@ static const char *builtin_exec_path(void)
#endif
}
+const char *system_path(const char *path)
+{
+ if (!is_absolute_path(path)) {
+ struct strbuf d = STRBUF_INIT;
+ strbuf_addf(&d, "%s/%s", git_exec_path(), path);
+ path = strbuf_detach(&d, NULL);
+ }
+ return path;
+}
+
void git_set_argv_exec_path(const char *exec_path)
{
argv_exec_path = exec_path;
diff --git a/exec_cmd.h b/exec_cmd.h
index a892355c82..7eb94e5e11 100644
--- a/exec_cmd.h
+++ b/exec_cmd.h
@@ -6,6 +6,6 @@ extern const char* git_exec_path(void);
extern void setup_path(const char *);
extern int execv_git_cmd(const char **argv); /* NULL terminated */
extern int execl_git_cmd(const char *cmd, ...);
-
+extern const char *system_path(const char *path);
#endif /* GIT_EXEC_CMD_H */
diff --git a/git-add--interactive.perl b/git-add--interactive.perl
index a6a5c52b59..da768ee7ac 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -54,7 +54,7 @@ sub colored {
my $patch_mode;
sub run_cmd_pipe {
- if ($^O eq 'MSWin32') {
+ if ($^O eq 'MSWin32' || $^O eq 'msys') {
my @invalid = grep {m/[":*]/} @_;
die "$^O does not support: @invalid\n" if @invalid;
my @args = map { m/ /o ? "\"$_\"": $_ } @_;
diff --git a/git-am.sh b/git-am.sh
index 52b5669b43..cc8787b460 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -119,7 +119,7 @@ It does not apply to blobs recorded in its index."
}
prec=4
-dotest=".dotest"
+dotest="$GIT_DIR/rebase"
sign= utf8=t keep= skip= interactive= resolved= binary= rebasing=
resolvemsg= resume=
git_apply_opt=
@@ -195,7 +195,7 @@ then
false
;;
esac ||
- die "previous dotest directory $dotest still exists but mbox given."
+ die "previous rebase directory $dotest still exists but mbox given."
resume=yes
else
# Make sure we are not given --skip nor --resolved
@@ -325,7 +325,7 @@ do
<"$dotest"/info >/dev/null &&
go_next && continue
- test -s $dotest/patch || {
+ test -s "$dotest/patch" || {
echo "Patch is empty. Was it split wrong?"
stop_here $this
}
diff --git a/git-compat-util.h b/git-compat-util.h
index 8c7e114733..cf89cdf459 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -157,6 +157,12 @@ extern void set_warn_routine(void (*routine)(const char *warn, va_list params));
extern int prefixcmp(const char *str, const char *prefix);
extern time_t tm_to_time_t(const struct tm *tm);
+static inline const char *skip_prefix(const char *str, const char *prefix)
+{
+ size_t len = strlen(prefix);
+ return strncmp(str, prefix, len) ? NULL : str + len;
+}
+
#ifdef NO_MMAP
#ifndef PROT_READ
diff --git a/git-cvsserver.perl b/git-cvsserver.perl
index 920bbe15a3..b00d1c29cd 100755
--- a/git-cvsserver.perl
+++ b/git-cvsserver.perl
@@ -1884,7 +1884,7 @@ sub req_annotate
}
# done; get out of the tempdir
- cleanupWorkDir();
+ cleanupWorkTree();
print "ok\n";
diff --git a/git-quiltimport.sh b/git-quiltimport.sh
index 7cd8f7134e..d1efa1d741 100755
--- a/git-quiltimport.sh
+++ b/git-quiltimport.sh
@@ -53,7 +53,7 @@ if ! [ -d "$QUILT_PATCHES" ] ; then
fi
# Temporary directories
-tmp_dir=.dotest
+tmp_dir="$GIT_DIR"/rebase
tmp_msg="$tmp_dir/msg"
tmp_patch="$tmp_dir/patch"
tmp_info="$tmp_dir/info"
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index a35212d9de..e63a864c7b 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -31,7 +31,7 @@ skip skip current patch and continue rebasing process
. git-sh-setup
require_work_tree
-DOTEST="$GIT_DIR/.dotest-merge"
+DOTEST="$GIT_DIR/rebase-merge"
TODO="$DOTEST"/git-rebase-todo
DONE="$DOTEST"/done
MSG="$DOTEST"/message
@@ -174,6 +174,8 @@ pick_one_preserving_merges () {
new_parents="$new_parents $new_p"
;;
esac
+ else
+ new_parents="$new_parents $p"
fi
done
case $fast_forward in
diff --git a/git-rebase.sh b/git-rebase.sh
index 2597d777d6..6ef5754814 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -14,7 +14,7 @@ It is possible that a merge failure will prevent this process from being
completely automatic. You will have to resolve any such merge failure
and run git rebase --continue. Another option is to bypass the commit
that caused the merge failure with git rebase --skip. To restore the
-original <branch> and remove the .dotest working files, use the command
+original <branch> and remove the .git/rebase working files, use the command
git rebase --abort instead.
Note that if <branch> is not specified on the command line, the
@@ -42,7 +42,7 @@ To restore the original branch and stop rebasing run \"git rebase --abort\".
unset newbase
strategy=recursive
do_merge=
-dotest=$GIT_DIR/.dotest-merge
+dotest="$GIT_DIR"/rebase-merge
prec=4
verbose=
git_am_opt=
@@ -150,7 +150,7 @@ while test $# != 0
do
case "$1" in
--continue)
- test -d "$dotest" -o -d .dotest ||
+ test -d "$dotest" -o -d "$GIT_DIR"/rebase ||
die "No rebase in progress?"
git diff-files --quiet --ignore-submodules || {
@@ -173,15 +173,15 @@ do
finish_rb_merge
exit
fi
- head_name=$(cat .dotest/head-name) &&
- onto=$(cat .dotest/onto) &&
- orig_head=$(cat .dotest/orig-head) &&
+ head_name=$(cat "$GIT_DIR"/rebase/head-name) &&
+ onto=$(cat "$GIT_DIR"/rebase/onto) &&
+ orig_head=$(cat "$GIT_DIR"/rebase/orig-head) &&
git am --resolved --3way --resolvemsg="$RESOLVEMSG" &&
move_to_original_branch
exit
;;
--skip)
- test -d "$dotest" -o -d .dotest ||
+ test -d "$dotest" -o -d "$GIT_DIR"/rebase ||
die "No rebase in progress?"
git reset --hard HEAD || exit $?
@@ -201,15 +201,15 @@ do
finish_rb_merge
exit
fi
- head_name=$(cat .dotest/head-name) &&
- onto=$(cat .dotest/onto) &&
- orig_head=$(cat .dotest/orig-head) &&
+ head_name=$(cat "$GIT_DIR"/rebase/head-name) &&
+ onto=$(cat "$GIT_DIR"/rebase/onto) &&
+ orig_head=$(cat "$GIT_DIR"/rebase/orig-head) &&
git am -3 --skip --resolvemsg="$RESOLVEMSG" &&
move_to_original_branch
exit
;;
--abort)
- test -d "$dotest" -o -d .dotest ||
+ test -d "$dotest" -o -d "$GIT_DIR"/rebase ||
die "No rebase in progress?"
git rerere clear
@@ -217,7 +217,7 @@ do
then
move_to_original_branch
else
- dotest=.dotest
+ dotest="$GIT_DIR"/rebase
move_to_original_branch
fi
git reset --hard $(cat "$dotest/orig-head")
@@ -265,24 +265,24 @@ do
shift
done
-# Make sure we do not have .dotest
+# Make sure we do not have $GIT_DIR/rebase
if test -z "$do_merge"
then
- if mkdir .dotest
+ if mkdir "$GIT_DIR"/rebase
then
- rmdir .dotest
+ rmdir "$GIT_DIR"/rebase
else
echo >&2 '
-It seems that I cannot create a .dotest directory, and I wonder if you
+It seems that I cannot create a '"$GIT_DIR"'/rebase directory, and I wonder if you
are in the middle of patch application or another rebase. If that is not
-the case, please rm -fr .dotest and run me again. I am stopping in case
+the case, please rm -fr '"$GIT_DIR"'/rebase and run me again. I am stopping in case
you still have something valuable there.'
exit 1
fi
else
if test -d "$dotest"
then
- die "previous dotest directory $dotest still exists." \
+ die "previous rebase directory $dotest still exists." \
'try git-rebase < --continue | --abort >'
fi
fi
@@ -376,8 +376,7 @@ fi
# Detach HEAD and reset the tree
echo "First, rewinding head to replay your work on top of it..."
-git checkout "$onto^0" >/dev/null 2>&1 ||
- die "could not detach HEAD"
+git checkout -q "$onto^0" || die "could not detach HEAD"
git update-ref ORIG_HEAD $branch
# If the $onto is a proper descendant of the tip of the branch, then
@@ -396,10 +395,10 @@ then
git am $git_am_opt --rebasing --resolvemsg="$RESOLVEMSG" &&
move_to_original_branch
ret=$?
- test 0 != $ret -a -d .dotest &&
- echo $head_name > .dotest/head-name &&
- echo $onto > .dotest/onto &&
- echo $orig_head > .dotest/orig-head
+ test 0 != $ret -a -d "$GIT_DIR"/rebase &&
+ echo $head_name > "$GIT_DIR"/rebase/head-name &&
+ echo $onto > "$GIT_DIR"/rebase/onto &&
+ echo $orig_head > "$GIT_DIR"/rebase/orig-head
exit $ret
fi
diff --git a/git.c b/git.c
index 7075533aa7..74ea0e6cc5 100644
--- a/git.c
+++ b/git.c
@@ -127,59 +127,6 @@ static int handle_options(const char*** argv, int* argc, int* envchanged)
return handled;
}
-static int split_cmdline(char *cmdline, const char ***argv)
-{
- int src, dst, count = 0, size = 16;
- char quoted = 0;
-
- *argv = xmalloc(sizeof(char*) * size);
-
- /* split alias_string */
- (*argv)[count++] = cmdline;
- for (src = dst = 0; cmdline[src];) {
- char c = cmdline[src];
- if (!quoted && isspace(c)) {
- cmdline[dst++] = 0;
- while (cmdline[++src]
- && isspace(cmdline[src]))
- ; /* skip */
- if (count >= size) {
- size += 16;
- *argv = xrealloc(*argv, sizeof(char*) * size);
- }
- (*argv)[count++] = cmdline + dst;
- } else if(!quoted && (c == '\'' || c == '"')) {
- quoted = c;
- src++;
- } else if (c == quoted) {
- quoted = 0;
- src++;
- } else {
- if (c == '\\' && quoted != '\'') {
- src++;
- c = cmdline[src];
- if (!c) {
- free(*argv);
- *argv = NULL;
- return error("cmdline ends with \\");
- }
- }
- cmdline[dst++] = c;
- src++;
- }
- }
-
- cmdline[dst] = 0;
-
- if (quoted) {
- free(*argv);
- *argv = NULL;
- return error("unclosed quote");
- }
-
- return count;
-}
-
static int handle_alias(int *argcp, const char ***argv)
{
int envchanged = 0, ret = 0, saved_errno = errno;
@@ -366,6 +313,7 @@ static void handle_internal_command(int argc, const char **argv)
{ "ls-remote", cmd_ls_remote },
{ "mailinfo", cmd_mailinfo },
{ "mailsplit", cmd_mailsplit },
+ { "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE },
{ "merge-base", cmd_merge_base, RUN_SETUP },
{ "merge-file", cmd_merge_file },
{ "merge-ours", cmd_merge_ours, RUN_SETUP },
diff --git a/help.c b/help.c
index ca9632b6c5..52d39b88a3 100644
--- a/help.c
+++ b/help.c
@@ -633,15 +633,29 @@ static void show_info_page(const char *git_cmd)
static void get_html_page_path(struct strbuf *page_path, const char *page)
{
struct stat st;
+ const char *html_path = system_path(GIT_HTML_PATH);
/* Check that we have a git documentation directory. */
- if (stat(GIT_HTML_PATH "/git.html", &st) || !S_ISREG(st.st_mode))
- die("'%s': not a documentation directory.", GIT_HTML_PATH);
+ if (stat(mkpath("%s/git.html", html_path), &st)
+ || !S_ISREG(st.st_mode))
+ die("'%s': not a documentation directory.", html_path);
strbuf_init(page_path, 0);
- strbuf_addf(page_path, GIT_HTML_PATH "/%s.html", page);
+ strbuf_addf(page_path, "%s/%s.html", html_path, page);
}
+/*
+ * If open_html is not defined in a platform-specific way (see for
+ * example compat/mingw.h), we use the script web--browse to display
+ * HTML.
+ */
+#ifndef open_html
+void open_html(const char *path)
+{
+ execl_git_cmd("web--browse", "-c", "help.browser", path, NULL);
+}
+#endif
+
static void show_html_page(const char *git_cmd)
{
const char *page = cmd_to_page(git_cmd);
@@ -649,7 +663,7 @@ static void show_html_page(const char *git_cmd)
get_html_page_path(&page_path, page);
- execl_git_cmd("web--browse", "-c", "help.browser", page_path.buf, NULL);
+ open_html(page_path.buf);
}
void help_unknown_cmd(const char *cmd)
diff --git a/index-pack.c b/index-pack.c
index 25db5db24b..b4ec736174 100644
--- a/index-pack.c
+++ b/index-pack.c
@@ -26,6 +26,14 @@ union delta_base {
off_t offset;
};
+struct base_data {
+ struct base_data *base;
+ struct base_data *child;
+ struct object_entry *obj;
+ void *data;
+ unsigned long size;
+};
+
/*
* Even if sizeof(union delta_base) == 24 on 64-bit archs, we really want
* to memcmp() only the first 20 bytes.
@@ -43,6 +51,8 @@ struct delta_entry
static struct object_entry *objects;
static struct delta_entry *deltas;
+static struct base_data *base_cache;
+static size_t base_cache_used;
static int nr_objects;
static int nr_deltas;
static int nr_resolved_deltas;
@@ -211,6 +221,46 @@ static void bad_object(unsigned long offset, const char *format, ...)
die("pack has bad object at offset %lu: %s", offset, buf);
}
+static void prune_base_data(struct base_data *retain)
+{
+ struct base_data *b = base_cache;
+ for (b = base_cache;
+ base_cache_used > delta_base_cache_limit && b;
+ b = b->child) {
+ if (b->data && b != retain) {
+ free(b->data);
+ b->data = NULL;
+ base_cache_used -= b->size;
+ }
+ }
+}
+
+static void link_base_data(struct base_data *base, struct base_data *c)
+{
+ if (base)
+ base->child = c;
+ else
+ base_cache = c;
+
+ c->base = base;
+ c->child = NULL;
+ base_cache_used += c->size;
+ prune_base_data(c);
+}
+
+static void unlink_base_data(struct base_data *c)
+{
+ struct base_data *base = c->base;
+ if (base)
+ base->child = NULL;
+ else
+ base_cache = NULL;
+ if (c->data) {
+ free(c->data);
+ base_cache_used -= c->size;
+ }
+}
+
static void *unpack_entry_data(unsigned long offset, unsigned long size)
{
z_stream stream;
@@ -426,33 +476,60 @@ static void sha1_object(const void *data, unsigned long size,
}
}
-static void resolve_delta(struct object_entry *delta_obj, void *base_data,
- unsigned long base_size, enum object_type type)
+static void *get_base_data(struct base_data *c)
+{
+ if (!c->data) {
+ struct object_entry *obj = c->obj;
+
+ if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
+ void *base = get_base_data(c->base);
+ void *raw = get_data_from_pack(obj);
+ c->data = patch_delta(
+ base, c->base->size,
+ raw, obj->size,
+ &c->size);
+ free(raw);
+ if (!c->data)
+ bad_object(obj->idx.offset, "failed to apply delta");
+ } else
+ c->data = get_data_from_pack(obj);
+
+ base_cache_used += c->size;
+ prune_base_data(c);
+ }
+ return c->data;
+}
+
+static void resolve_delta(struct object_entry *delta_obj,
+ struct base_data *base_obj, enum object_type type)
{
void *delta_data;
unsigned long delta_size;
- void *result;
- unsigned long result_size;
union delta_base delta_base;
int j, first, last;
+ struct base_data result;
delta_obj->real_type = type;
delta_data = get_data_from_pack(delta_obj);
delta_size = delta_obj->size;
- result = patch_delta(base_data, base_size, delta_data, delta_size,
- &result_size);
+ result.data = patch_delta(get_base_data(base_obj), base_obj->size,
+ delta_data, delta_size,
+ &result.size);
free(delta_data);
- if (!result)
+ if (!result.data)
bad_object(delta_obj->idx.offset, "failed to apply delta");
- sha1_object(result, result_size, type, delta_obj->idx.sha1);
+ sha1_object(result.data, result.size, type, delta_obj->idx.sha1);
nr_resolved_deltas++;
+ result.obj = delta_obj;
+ link_base_data(base_obj, &result);
+
hashcpy(delta_base.sha1, delta_obj->idx.sha1);
if (!find_delta_children(&delta_base, &first, &last)) {
for (j = first; j <= last; j++) {
struct object_entry *child = objects + deltas[j].obj_no;
if (child->real_type == OBJ_REF_DELTA)
- resolve_delta(child, result, result_size, type);
+ resolve_delta(child, &result, type);
}
}
@@ -462,11 +539,11 @@ static void resolve_delta(struct object_entry *delta_obj, void *base_data,
for (j = first; j <= last; j++) {
struct object_entry *child = objects + deltas[j].obj_no;
if (child->real_type == OBJ_OFS_DELTA)
- resolve_delta(child, result, result_size, type);
+ resolve_delta(child, &result, type);
}
}
- free(result);
+ unlink_base_data(&result);
}
static int compare_delta_entry(const void *a, const void *b)
@@ -481,7 +558,6 @@ static void parse_pack_objects(unsigned char *sha1)
{
int i;
struct delta_entry *delta = deltas;
- void *data;
struct stat st;
/*
@@ -496,7 +572,7 @@ static void parse_pack_objects(unsigned char *sha1)
nr_objects);
for (i = 0; i < nr_objects; i++) {
struct object_entry *obj = &objects[i];
- data = unpack_raw_entry(obj, &delta->base);
+ void *data = unpack_raw_entry(obj, &delta->base);
obj->real_type = obj->type;
if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
nr_deltas++;
@@ -545,6 +621,7 @@ static void parse_pack_objects(unsigned char *sha1)
struct object_entry *obj = &objects[i];
union delta_base base;
int j, ref, ref_first, ref_last, ofs, ofs_first, ofs_last;
+ struct base_data base_obj;
if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA)
continue;
@@ -555,22 +632,24 @@ static void parse_pack_objects(unsigned char *sha1)
ofs = !find_delta_children(&base, &ofs_first, &ofs_last);
if (!ref && !ofs)
continue;
- data = get_data_from_pack(obj);
+ base_obj.data = get_data_from_pack(obj);
+ base_obj.size = obj->size;
+ base_obj.obj = obj;
+ link_base_data(NULL, &base_obj);
+
if (ref)
for (j = ref_first; j <= ref_last; j++) {
struct object_entry *child = objects + deltas[j].obj_no;
if (child->real_type == OBJ_REF_DELTA)
- resolve_delta(child, data,
- obj->size, obj->type);
+ resolve_delta(child, &base_obj, obj->type);
}
if (ofs)
for (j = ofs_first; j <= ofs_last; j++) {
struct object_entry *child = objects + deltas[j].obj_no;
if (child->real_type == OBJ_OFS_DELTA)
- resolve_delta(child, data,
- obj->size, obj->type);
+ resolve_delta(child, &base_obj, obj->type);
}
- free(data);
+ unlink_base_data(&base_obj);
display_progress(progress, nr_resolved_deltas);
}
}
@@ -601,7 +680,8 @@ static int write_compressed(int fd, void *in, unsigned int size, uint32_t *obj_c
return size;
}
-static void append_obj_to_pack(const unsigned char *sha1, void *buf,
+static struct object_entry *append_obj_to_pack(
+ const unsigned char *sha1, void *buf,
unsigned long size, enum object_type type)
{
struct object_entry *obj = &objects[nr_objects++];
@@ -622,6 +702,7 @@ static void append_obj_to_pack(const unsigned char *sha1, void *buf,
obj[1].idx.offset = obj[0].idx.offset + n;
obj[1].idx.offset += write_compressed(output_fd, buf, size, &obj[0].idx.crc32);
hashcpy(obj->idx.sha1, sha1);
+ return obj;
}
static int delta_pos_compare(const void *_a, const void *_b)
@@ -656,28 +737,31 @@ static void fix_unresolved_deltas(int nr_unresolved)
for (i = 0; i < n; i++) {
struct delta_entry *d = sorted_by_pos[i];
- void *data;
- unsigned long size;
enum object_type type;
int j, first, last;
+ struct base_data base_obj;
if (objects[d->obj_no].real_type != OBJ_REF_DELTA)
continue;
- data = read_sha1_file(d->base.sha1, &type, &size);
- if (!data)
+ base_obj.data = read_sha1_file(d->base.sha1, &type, &base_obj.size);
+ if (!base_obj.data)
continue;
+ if (check_sha1_signature(d->base.sha1, base_obj.data,
+ base_obj.size, typename(type)))
+ die("local object %s is corrupt", sha1_to_hex(d->base.sha1));
+ base_obj.obj = append_obj_to_pack(d->base.sha1, base_obj.data,
+ base_obj.size, type);
+ link_base_data(NULL, &base_obj);
+
find_delta_children(&d->base, &first, &last);
for (j = first; j <= last; j++) {
struct object_entry *child = objects + deltas[j].obj_no;
if (child->real_type == OBJ_REF_DELTA)
- resolve_delta(child, data, size, type);
+ resolve_delta(child, &base_obj, type);
}
- if (check_sha1_signature(d->base.sha1, data, size, typename(type)))
- die("local object %s is corrupt", sha1_to_hex(d->base.sha1));
- append_obj_to_pack(d->base.sha1, data, size, type);
- free(data);
+ unlink_base_data(&base_obj);
display_progress(progress, nr_resolved_deltas);
}
free(sorted_by_pos);
diff --git a/parse-options.c b/parse-options.c
index ae88885d4d..987b015719 100644
--- a/parse-options.c
+++ b/parse-options.c
@@ -5,12 +5,6 @@
#define OPT_SHORT 1
#define OPT_UNSET 2
-static inline const char *skip_prefix(const char *str, const char *prefix)
-{
- size_t len = strlen(prefix);
- return strncmp(str, prefix, len) ? NULL : str + len;
-}
-
static int opterror(const struct option *opt, const char *reason, int flags)
{
if (flags & OPT_SHORT)
@@ -220,7 +214,7 @@ is_abbreviated:
return -2;
}
-void check_typos(const char *arg, const struct option *options)
+static void check_typos(const char *arg, const struct option *options)
{
if (strlen(arg) < 3)
return;
diff --git a/pretty.c b/pretty.c
index 8eb39e915e..628a5201c1 100644
--- a/pretty.c
+++ b/pretty.c
@@ -3,6 +3,8 @@
#include "utf8.h"
#include "diff.h"
#include "revision.h"
+#include "path-list.h"
+#include "mailmap.h"
static char *user_format;
@@ -288,6 +290,25 @@ static char *logmsg_reencode(const struct commit *commit,
return out;
}
+static int mailmap_name(struct strbuf *sb, const char *email)
+{
+ static struct path_list *mail_map;
+ char buffer[1024];
+
+ if (!mail_map) {
+ mail_map = xcalloc(1, sizeof(*mail_map));
+ read_mailmap(mail_map, ".mailmap", NULL);
+ }
+
+ if (!mail_map->nr)
+ return -1;
+
+ if (!map_email(mail_map, email, buffer, sizeof(buffer)))
+ return -1;
+ strbuf_addstr(sb, buffer);
+ return 0;
+}
+
static size_t format_person_part(struct strbuf *sb, char part,
const char *msg, int len)
{
@@ -309,10 +330,12 @@ static size_t format_person_part(struct strbuf *sb, char part,
if (end >= len - 2)
goto skip;
- if (part == 'n') { /* name */
+ if (part == 'n' || part == 'N') { /* name */
while (end > 0 && isspace(msg[end - 1]))
end--;
- strbuf_add(sb, msg, end);
+ if (part != 'N' || !msg[end] || !msg[end + 1] ||
+ mailmap_name(sb, msg + end + 2) < 0)
+ strbuf_add(sb, msg, end);
return placeholder_len;
}
start = ++end; /* save email start position */
diff --git a/read-cache.c b/read-cache.c
index f83de8c415..d801f9d1cf 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -1410,3 +1410,34 @@ int write_index(const struct index_state *istate, int newfd)
}
return ce_flush(&c, newfd);
}
+
+/*
+ * Read the index file that is potentially unmerged into given
+ * index_state, dropping any unmerged entries. Returns true is
+ * the index is unmerged. Callers who want to refuse to work
+ * from an unmerged state can call this and check its return value,
+ * instead of calling read_cache().
+ */
+int read_index_unmerged(struct index_state *istate)
+{
+ int i;
+ struct cache_entry **dst;
+ struct cache_entry *last = NULL;
+
+ read_index(istate);
+ dst = istate->cache;
+ for (i = 0; i < istate->cache_nr; i++) {
+ struct cache_entry *ce = istate->cache[i];
+ if (ce_stage(ce)) {
+ remove_name_hash(ce);
+ if (last && !strcmp(ce->name, last->name))
+ continue;
+ cache_tree_invalidate_path(istate->cache_tree, ce->name);
+ last = ce;
+ continue;
+ }
+ *dst++ = ce;
+ }
+ istate->cache_nr = dst - istate->cache;
+ return !!last;
+}
diff --git a/revision.c b/revision.c
index bbd563e651..3897fec531 100644
--- a/revision.c
+++ b/revision.c
@@ -260,7 +260,7 @@ static int tree_difference = REV_TREE_SAME;
static void file_add_remove(struct diff_options *options,
int addremove, unsigned mode,
const unsigned char *sha1,
- const char *base, const char *path)
+ const char *fullpath)
{
int diff = REV_TREE_DIFFERENT;
@@ -286,7 +286,7 @@ static void file_change(struct diff_options *options,
unsigned old_mode, unsigned new_mode,
const unsigned char *old_sha1,
const unsigned char *new_sha1,
- const char *base, const char *path)
+ const char *fullpath)
{
tree_difference = REV_TREE_DIFFERENT;
DIFF_OPT_SET(options, HAS_CHANGES);
@@ -413,10 +413,26 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
commit->object.flags |= TREESAME;
}
-static int add_parents_to_list(struct rev_info *revs, struct commit *commit, struct commit_list **list)
+static void insert_by_date_cached(struct commit *p, struct commit_list **head,
+ struct commit_list *cached_base, struct commit_list **cache)
+{
+ struct commit_list *new_entry;
+
+ if (cached_base && p->date < cached_base->item->date)
+ new_entry = insert_by_date(p, &cached_base->next);
+ else
+ new_entry = insert_by_date(p, head);
+
+ if (cache && (!*cache || p->date < (*cache)->item->date))
+ *cache = new_entry;
+}
+
+static int add_parents_to_list(struct rev_info *revs, struct commit *commit,
+ struct commit_list **list, struct commit_list **cache_ptr)
{
struct commit_list *parent = commit->parents;
unsigned left_flag;
+ struct commit_list *cached_base = cache_ptr ? *cache_ptr : NULL;
if (commit->object.flags & ADDED)
return 0;
@@ -446,7 +462,7 @@ static int add_parents_to_list(struct rev_info *revs, struct commit *commit, str
if (p->object.flags & SEEN)
continue;
p->object.flags |= SEEN;
- insert_by_date(p, list);
+ insert_by_date_cached(p, list, cached_base, cache_ptr);
}
return 0;
}
@@ -471,7 +487,7 @@ static int add_parents_to_list(struct rev_info *revs, struct commit *commit, str
p->object.flags |= left_flag;
if (!(p->object.flags & SEEN)) {
p->object.flags |= SEEN;
- insert_by_date(p, list);
+ insert_by_date_cached(p, list, cached_base, cache_ptr);
}
if(revs->first_parent_only)
break;
@@ -612,7 +628,7 @@ static int limit_list(struct rev_info *revs)
if (revs->max_age != -1 && (commit->date < revs->max_age))
obj->flags |= UNINTERESTING;
- if (add_parents_to_list(revs, commit, &list) < 0)
+ if (add_parents_to_list(revs, commit, &list, NULL) < 0)
return -1;
if (obj->flags & UNINTERESTING) {
mark_parents_uninteresting(commit);
@@ -1415,10 +1431,12 @@ enum rewrite_result {
static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp)
{
+ struct commit_list *cache = NULL;
+
for (;;) {
struct commit *p = *pp;
if (!revs->limited)
- if (add_parents_to_list(revs, p, &revs->commits) < 0)
+ if (add_parents_to_list(revs, p, &revs->commits, &cache) < 0)
return rewrite_one_error;
if (p->parents && p->parents->next)
return rewrite_one_ok;
@@ -1542,7 +1560,7 @@ static struct commit *get_revision_1(struct rev_info *revs)
if (revs->max_age != -1 &&
(commit->date < revs->max_age))
continue;
- if (add_parents_to_list(revs, commit, &revs->commits) < 0)
+ if (add_parents_to_list(revs, commit, &revs->commits, NULL) < 0)
return NULL;
}
diff --git a/t/Makefile b/t/Makefile
index a778865ae7..0d65cedaa6 100644
--- a/t/Makefile
+++ b/t/Makefile
@@ -26,7 +26,7 @@ clean:
$(RM) -r 'trash directory' test-results
aggregate-results:
- ./aggregate-results.sh test-results/t*-*
+ '$(SHELL_PATH_SQ)' ./aggregate-results.sh test-results/t*-*
# we can test NO_OPTIMIZE_COMMITS independently of LC_ALL
full-svn-test:
diff --git a/t/aggregate-results.sh b/t/aggregate-results.sh
index 52e88e3046..d5bab75d7d 100755
--- a/t/aggregate-results.sh
+++ b/t/aggregate-results.sh
@@ -10,9 +10,9 @@ for file
do
while read type value
do
- case $type in
- '')
- continue ;;
+ case $type in
+ '')
+ continue ;;
fixed)
fixed=$(($fixed + $value)) ;;
success)
@@ -20,9 +20,9 @@ do
failed)
failed=$(($failed + $value)) ;;
broken)
- broken=$(( $broken + $value)) ;;
+ broken=$(($broken + $value)) ;;
total)
- total=$(( $total + $value)) ;;
+ total=$(($total + $value)) ;;
esac
done <"$file"
done
diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh
index 5d3bd9dda9..a841df2a9e 100644
--- a/t/lib-git-svn.sh
+++ b/t/lib-git-svn.sh
@@ -98,7 +98,7 @@ LoadModule dav_module $SVN_HTTPD_MODULE_PATH/mod_dav.so
LoadModule dav_svn_module $SVN_HTTPD_MODULE_PATH/mod_dav_svn.so
<Location /$repo_base_path>
DAV svn
- SVNPath $rawsvnrepo
+ SVNPath "$rawsvnrepo"
</Location>
EOF
"$SVN_HTTPD_PATH" -f "$GIT_DIR"/httpd.conf -k start
diff --git a/t/t3401-rebase-partial.sh b/t/t3401-rebase-partial.sh
index 4934a4e010..36d9a2ae3a 100755
--- a/t/t3401-rebase-partial.sh
+++ b/t/t3401-rebase-partial.sh
@@ -50,12 +50,12 @@ test_debug \
test_expect_success \
'rebase topic branch against new master and check git-am did not get halted' \
- 'git-rebase master && test ! -d .dotest'
+ 'git-rebase master && test ! -d .git/rebase'
test_expect_success \
'rebase --merge topic branch that was partially merged upstream' \
'git-checkout -f my-topic-branch-merge &&
git-rebase --merge master-merge &&
- test ! -d .git/.dotest-merge'
+ test ! -d .git/rebase-merge'
test_done
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index 092aa26573..ffe3dd97b7 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -159,19 +159,19 @@ test_expect_success 'stop on conflicting pick' '
git tag new-branch1 &&
test_must_fail git rebase -i master &&
test "$(git rev-parse HEAD~3)" = "$(git rev-parse master)" &&
- test_cmp expect .git/.dotest-merge/patch &&
+ test_cmp expect .git/rebase-merge/patch &&
test_cmp expect2 file1 &&
test "$(git-diff --name-status |
sed -n -e "/^U/s/^U[^a-z]*//p")" = file1 &&
- test 4 = $(grep -v "^#" < .git/.dotest-merge/done | wc -l) &&
- test 0 = $(grep -c "^[^#]" < .git/.dotest-merge/git-rebase-todo)
+ test 4 = $(grep -v "^#" < .git/rebase-merge/done | wc -l) &&
+ test 0 = $(grep -c "^[^#]" < .git/rebase-merge/git-rebase-todo)
'
test_expect_success 'abort' '
git rebase --abort &&
test $(git rev-parse new-branch1) = $(git rev-parse HEAD) &&
test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch1" &&
- ! test -d .git/.dotest-merge
+ ! test -d .git/rebase-merge
'
test_expect_success 'retain authorship' '
diff --git a/t/t3407-rebase-abort.sh b/t/t3407-rebase-abort.sh
index 1777ffe8a2..12c8804a03 100755
--- a/t/t3407-rebase-abort.sh
+++ b/t/t3407-rebase-abort.sh
@@ -74,7 +74,7 @@ testrebase() {
'
}
-testrebase "" .dotest
-testrebase " --merge" .git/.dotest-merge
+testrebase "" .git/rebase
+testrebase " --merge" .git/rebase-merge
test_done
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index bc982607d0..5cbd5ef679 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -102,7 +102,7 @@ test_expect_success 'am applies patch correctly' '
git checkout first &&
test_tick &&
git am <patch1 &&
- ! test -d .dotest &&
+ ! test -d .git/rebase &&
test -z "$(git diff second)" &&
test "$(git rev-parse second)" = "$(git rev-parse HEAD)" &&
test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
@@ -123,7 +123,7 @@ test_expect_success 'am changes committer and keeps author' '
test_tick &&
git checkout first &&
git am patch2 &&
- ! test -d .dotest &&
+ ! test -d .git/rebase &&
test "$(git rev-parse master^^)" = "$(git rev-parse HEAD^^)" &&
test -z "$(git diff master..HEAD)" &&
test -z "$(git diff master^..HEAD^)" &&
@@ -163,7 +163,7 @@ test_expect_success 'am without --keep removes Re: and [PATCH] stuff' '
test_expect_success 'am --keep really keeps the subject' '
git checkout HEAD^ &&
git am --keep patch4 &&
- ! test -d .dotest &&
+ ! test -d .git/rebase &&
git-cat-file commit HEAD |
grep -q -F "Re: Re: Re: [PATCH 1/5 v2] third"
'
@@ -176,19 +176,19 @@ test_expect_success 'am -3 falls back to 3-way merge' '
test_tick &&
git commit -m "copied stuff" &&
git am -3 lorem-move.patch &&
- ! test -d .dotest &&
+ ! test -d .git/rebase &&
test -z "$(git diff lorem)"
'
test_expect_success 'am pauses on conflict' '
git checkout lorem2^^ &&
! git am lorem-move.patch &&
- test -d .dotest
+ test -d .git/rebase
'
test_expect_success 'am --skip works' '
git am --skip &&
- ! test -d .dotest &&
+ ! test -d .git/rebase &&
test -z "$(git diff lorem2^^ -- file)" &&
test goodbye = "$(cat another)"
'
@@ -196,31 +196,31 @@ test_expect_success 'am --skip works' '
test_expect_success 'am --resolved works' '
git checkout lorem2^^ &&
! git am lorem-move.patch &&
- test -d .dotest &&
+ test -d .git/rebase &&
echo resolved >>file &&
git add file &&
git am --resolved &&
- ! test -d .dotest &&
+ ! test -d .git/rebase &&
test goodbye = "$(cat another)"
'
test_expect_success 'am takes patches from a Pine mailbox' '
git checkout first &&
cat pine patch1 | git am &&
- ! test -d .dotest &&
+ ! test -d .git/rebase &&
test -z "$(git diff master^..HEAD)"
'
test_expect_success 'am fails on mail without patch' '
! git am <failmail &&
- rm -r .dotest/
+ rm -r .git/rebase/
'
test_expect_success 'am fails on empty patch' '
echo "---" >>failmail &&
! git am <failmail &&
git am --skip &&
- ! test -d .dotest
+ ! test -d .git/rebase
'
test_expect_success 'am works from stdin in subdirectory' '
diff --git a/t/t6021-merge-criss-cross.sh b/t/t6021-merge-criss-cross.sh
index 0ab14a6e81..331b9b07d4 100755
--- a/t/t6021-merge-criss-cross.sh
+++ b/t/t6021-merge-criss-cross.sh
@@ -89,4 +89,8 @@ EOF
test_expect_success 'Criss-cross merge result' 'cmp file file-expect'
+test_expect_success 'Criss-cross merge fails (-s resolve)' \
+'git reset --hard A^ &&
+test_must_fail git merge -s resolve -m "final merge" B'
+
test_done
diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh
index d21cd290d3..9ab5bdacc4 100755
--- a/t/t7600-merge.sh
+++ b/t/t7600-merge.sh
@@ -221,37 +221,13 @@ test_expect_success 'setup' '
test_debug 'gitk --all'
-test_expect_success 'test option parsing' '
- if git merge -$ c1
- then
- echo "[OOPS] -$ accepted"
- false
- fi &&
- if git merge --no-such c1
- then
- echo "[OOPS] --no-such accepted"
- false
- fi &&
- if git merge -s foobar c1
- then
- echo "[OOPS] -s foobar accepted"
- false
- fi &&
- if git merge -s=foobar c1
- then
- echo "[OOPS] -s=foobar accepted"
- false
- fi &&
- if git merge -m
- then
- echo "[OOPS] missing commit msg accepted"
- false
- fi &&
- if git merge
- then
- echo "[OOPS] missing commit references accepted"
- false
- fi
+test_expect_failure 'test option parsing' '
+ test_must_fail git merge -$ c1 &&
+ test_must_fail git merge --no-such c1 &&
+ test_must_fail git merge -s foobar c1 &&
+ test_must_fail git merge -s=foobar c1 &&
+ test_must_fail git merge -m &&
+ test_must_fail git merge
'
test_expect_success 'merge c0 with c1' '
@@ -465,11 +441,51 @@ test_expect_success 'merge log message' '
git merge --no-log c2 &&
git show -s --pretty=format:%b HEAD >msg.act &&
verify_diff msg.nolog msg.act "[OOPS] bad merge log message" &&
+
git merge --log c3 &&
git show -s --pretty=format:%b HEAD >msg.act &&
+ verify_diff msg.log msg.act "[OOPS] bad merge log message" &&
+
+ git reset --hard HEAD^ &&
+ git config merge.log yes &&
+ git merge c3 &&
+ git show -s --pretty=format:%b HEAD >msg.act &&
verify_diff msg.log msg.act "[OOPS] bad merge log message"
'
test_debug 'gitk --all'
+test_expect_success 'merge c1 with c0, c2, c0, and c1' '
+ git reset --hard c1 &&
+ git config branch.master.mergeoptions "" &&
+ test_tick &&
+ git merge c0 c2 c0 c1 &&
+ verify_merge file result.1-5 &&
+ verify_parents $c1 $c2
+'
+
+test_debug 'gitk --all'
+
+test_expect_success 'merge c1 with c0, c2, c0, and c1' '
+ git reset --hard c1 &&
+ git config branch.master.mergeoptions "" &&
+ test_tick &&
+ git merge c0 c2 c0 c1 &&
+ verify_merge file result.1-5 &&
+ verify_parents $c1 $c2
+'
+
+test_debug 'gitk --all'
+
+test_expect_success 'merge c1 with c1 and c2' '
+ git reset --hard c1 &&
+ git config branch.master.mergeoptions "" &&
+ test_tick &&
+ git merge c1 c2 &&
+ verify_merge file result.1-5 &&
+ verify_parents $c1 $c2
+'
+
+test_debug 'gitk --all'
+
test_done
diff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh
new file mode 100755
index 0000000000..95b4d71c51
--- /dev/null
+++ b/t/t7601-merge-pull-config.sh
@@ -0,0 +1,129 @@
+#!/bin/sh
+
+test_description='git-merge
+
+Testing pull.* configuration parsing.'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ echo c0 >c0.c &&
+ git add c0.c &&
+ git commit -m c0 &&
+ git tag c0 &&
+ echo c1 >c1.c &&
+ git add c1.c &&
+ git commit -m c1 &&
+ git tag c1 &&
+ git reset --hard c0 &&
+ echo c2 >c2.c &&
+ git add c2.c &&
+ git commit -m c2 &&
+ git tag c2 &&
+ git reset --hard c0 &&
+ echo c3 >c3.c &&
+ git add c3.c &&
+ git commit -m c3 &&
+ git tag c3
+'
+
+test_expect_success 'merge c1 with c2' '
+ git reset --hard c1 &&
+ test -f c0.c &&
+ test -f c1.c &&
+ test ! -f c2.c &&
+ test ! -f c3.c &&
+ git merge c2 &&
+ test -f c1.c &&
+ test -f c2.c
+'
+
+test_expect_success 'merge c1 with c2 (ours in pull.twohead)' '
+ git reset --hard c1 &&
+ git config pull.twohead ours &&
+ git merge c2 &&
+ test -f c1.c &&
+ ! test -f c2.c
+'
+
+test_expect_success 'merge c1 with c2 and c3 (recursive in pull.octopus)' '
+ git reset --hard c1 &&
+ git config pull.octopus "recursive" &&
+ test_must_fail git merge c2 c3 &&
+ test "$(git rev-parse c1)" = "$(git rev-parse HEAD)"
+'
+
+test_expect_success 'merge c1 with c2 and c3 (recursive and octopus in pull.octopus)' '
+ git reset --hard c1 &&
+ git config pull.octopus "recursive octopus" &&
+ git merge c2 c3 &&
+ test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
+ test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
+ test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
+ test "$(git rev-parse c3)" = "$(git rev-parse HEAD^3)" &&
+ git diff --exit-code &&
+ test -f c0.c &&
+ test -f c1.c &&
+ test -f c2.c &&
+ test -f c3.c
+'
+
+conflict_count()
+{
+ {
+ git diff-files --name-only
+ git ls-files --unmerged
+ } | wc -l
+}
+
+# c4 - c5
+# \ c6
+#
+# There are two conflicts here:
+#
+# 1) Because foo.c is renamed to bar.c, recursive will handle this,
+# resolve won't.
+#
+# 2) One in conflict.c and that will always fail.
+
+test_expect_success 'setup conflicted merge' '
+ git reset --hard c0 &&
+ echo A >conflict.c &&
+ git add conflict.c &&
+ echo contents >foo.c &&
+ git add foo.c &&
+ git commit -m c4 &&
+ git tag c4 &&
+ echo B >conflict.c &&
+ git add conflict.c &&
+ git mv foo.c bar.c &&
+ git commit -m c5 &&
+ git tag c5 &&
+ git reset --hard c4 &&
+ echo C >conflict.c &&
+ git add conflict.c &&
+ echo secondline >> foo.c &&
+ git add foo.c &&
+ git commit -m c6 &&
+ git tag c6
+'
+
+# First do the merge with resolve and recursive then verify that
+# recusive is choosen.
+
+test_expect_success 'merge picks up the best result' '
+ git config pull.twohead "recursive resolve" &&
+ git reset --hard c5 &&
+ git merge -s resolve c6
+ resolve_count=$(conflict_count) &&
+ git reset --hard c5 &&
+ git merge -s recursive c6
+ recursive_count=$(conflict_count) &&
+ git reset --hard c5 &&
+ git merge c6
+ auto_count=$(conflict_count) &&
+ test $auto_count = $recursive_count &&
+ test $auto_count != $resolve_count
+'
+
+test_done
diff --git a/t/t7602-merge-octopus-many.sh b/t/t7602-merge-octopus-many.sh
new file mode 100755
index 0000000000..fcb8285746
--- /dev/null
+++ b/t/t7602-merge-octopus-many.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+
+test_description='git-merge
+
+Testing octopus merge with more than 25 refs.'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ echo c0 > c0.c &&
+ git add c0.c &&
+ git commit -m c0 &&
+ git tag c0 &&
+ i=1 &&
+ while test $i -le 30
+ do
+ git reset --hard c0 &&
+ echo c$i > c$i.c &&
+ git add c$i.c &&
+ git commit -m c$i &&
+ git tag c$i &&
+ i=`expr $i + 1` || return 1
+ done
+'
+
+test_expect_success 'merge c1 with c2, c3, c4, ... c29' '
+ git reset --hard c1 &&
+ i=2 &&
+ refs="" &&
+ while test $i -le 30
+ do
+ refs="$refs c$i"
+ i=`expr $i + 1`
+ done
+ git merge $refs &&
+ test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
+ i=1 &&
+ while test $i -le 30
+ do
+ test "$(git rev-parse c$i)" = "$(git rev-parse HEAD^$i)" &&
+ i=`expr $i + 1` || return 1
+ done &&
+ git diff --exit-code &&
+ i=1 &&
+ while test $i -le 30
+ do
+ test -f c$i.c &&
+ i=`expr $i + 1` || return 1
+ done
+'
+
+test_done
diff --git a/t/t7603-merge-reduce-heads.sh b/t/t7603-merge-reduce-heads.sh
new file mode 100755
index 0000000000..17b19dc11f
--- /dev/null
+++ b/t/t7603-merge-reduce-heads.sh
@@ -0,0 +1,63 @@
+#!/bin/sh
+
+test_description='git-merge
+
+Testing octopus merge when reducing parents to independent branches.'
+
+. ./test-lib.sh
+
+# 0 - 1
+# \ 2
+# \ 3
+# \ 4 - 5
+#
+# So 1, 2, 3 and 5 should be kept, 4 should be avoided.
+
+test_expect_success 'setup' '
+ echo c0 > c0.c &&
+ git add c0.c &&
+ git commit -m c0 &&
+ git tag c0 &&
+ echo c1 > c1.c &&
+ git add c1.c &&
+ git commit -m c1 &&
+ git tag c1 &&
+ git reset --hard c0 &&
+ echo c2 > c2.c &&
+ git add c2.c &&
+ git commit -m c2 &&
+ git tag c2 &&
+ git reset --hard c0 &&
+ echo c3 > c3.c &&
+ git add c3.c &&
+ git commit -m c3 &&
+ git tag c3 &&
+ git reset --hard c0 &&
+ echo c4 > c4.c &&
+ git add c4.c &&
+ git commit -m c4 &&
+ git tag c4 &&
+ echo c5 > c5.c &&
+ git add c5.c &&
+ git commit -m c5 &&
+ git tag c5
+'
+
+test_expect_success 'merge c1 with c2, c3, c4, c5' '
+ git reset --hard c1 &&
+ git merge c2 c3 c4 c5 &&
+ test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
+ test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
+ test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
+ test "$(git rev-parse c3)" = "$(git rev-parse HEAD^3)" &&
+ test "$(git rev-parse c5)" = "$(git rev-parse HEAD^4)" &&
+ git diff --exit-code &&
+ test -f c0.c &&
+ test -f c1.c &&
+ test -f c2.c &&
+ test -f c3.c &&
+ test -f c4.c &&
+ test -f c5.c
+'
+
+test_done
diff --git a/t/t7604-merge-custom-message.sh b/t/t7604-merge-custom-message.sh
new file mode 100755
index 0000000000..6081677d23
--- /dev/null
+++ b/t/t7604-merge-custom-message.sh
@@ -0,0 +1,37 @@
+#!/bin/sh
+
+test_description='git-merge
+
+Testing merge when using a custom message for the merge commit.'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ echo c0 > c0.c &&
+ git add c0.c &&
+ git commit -m c0 &&
+ git tag c0 &&
+ echo c1 > c1.c &&
+ git add c1.c &&
+ git commit -m c1 &&
+ git tag c1 &&
+ git reset --hard c0 &&
+ echo c2 > c2.c &&
+ git add c2.c &&
+ git commit -m c2 &&
+ git tag c2
+'
+
+cat >expected <<\EOF
+custom message
+
+Merge commit 'c2'
+EOF
+test_expect_success 'merge c2 with a custom message' '
+ git reset --hard c1 &&
+ git merge -m "custom message" c2 &&
+ git cat-file commit HEAD | sed -e "1,/^$/d" > actual &&
+ test_cmp expected actual
+'
+
+test_done
diff --git a/t/t7605-merge-resolve.sh b/t/t7605-merge-resolve.sh
new file mode 100755
index 0000000000..ee21a107fd
--- /dev/null
+++ b/t/t7605-merge-resolve.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+
+test_description='git-merge
+
+Testing the resolve strategy.'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ echo c0 > c0.c &&
+ git add c0.c &&
+ git commit -m c0 &&
+ git tag c0 &&
+ echo c1 > c1.c &&
+ git add c1.c &&
+ git commit -m c1 &&
+ git tag c1 &&
+ git reset --hard c0 &&
+ echo c2 > c2.c &&
+ git add c2.c &&
+ git commit -m c2 &&
+ git tag c2 &&
+ git reset --hard c0 &&
+ echo c3 > c2.c &&
+ git add c2.c &&
+ git commit -m c3 &&
+ git tag c3
+'
+
+test_expect_success 'merge c1 to c2' '
+ git reset --hard c1 &&
+ git merge -s resolve c2 &&
+ test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
+ test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
+ test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
+ git diff --exit-code &&
+ test -f c0.c &&
+ test -f c1.c &&
+ test -f c2.c
+'
+
+test_expect_success 'merge c2 to c3 (fails)' '
+ git reset --hard c2 &&
+ test_must_fail git merge -s resolve c3
+'
+test_done
diff --git a/t/t9106-git-svn-commit-diff-clobber.sh b/t/t9106-git-svn-commit-diff-clobber.sh
index 58a3a7b1c3..27a65e05df 100755
--- a/t/t9106-git-svn-commit-diff-clobber.sh
+++ b/t/t9106-git-svn-commit-diff-clobber.sh
@@ -87,7 +87,7 @@ test_expect_success 'multiple dcommit from git-svn will not clobber svn' "
"
-test_expect_success 'check that rebase really failed' 'test -d .dotest'
+test_expect_success 'check that rebase really failed' 'test -d .git/rebase'
test_expect_success 'resolve, continue the rebase and dcommit' "
echo clobber and I really mean it > file &&
diff --git a/tree-diff.c b/tree-diff.c
index e1e2e6c6ce..bbb126fc46 100644
--- a/tree-diff.c
+++ b/tree-diff.c
@@ -15,6 +15,15 @@ static char *malloc_base(const char *base, int baselen, const char *path, int pa
return newbase;
}
+static char *malloc_fullname(const char *base, int baselen, const char *path, int pathlen)
+{
+ char *fullname = xmalloc(baselen + pathlen + 1);
+ memcpy(fullname, base, baselen);
+ memcpy(fullname + baselen, path, pathlen);
+ fullname[baselen + pathlen] = 0;
+ return fullname;
+}
+
static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc,
const char *base, int baselen);
@@ -24,6 +33,7 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const
const char *path1, *path2;
const unsigned char *sha1, *sha2;
int cmp, pathlen1, pathlen2;
+ char *fullname;
sha1 = tree_entry_extract(t1, &path1, &mode1);
sha2 = tree_entry_extract(t2, &path2, &mode2);
@@ -55,15 +65,20 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const
if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode1)) {
int retval;
char *newbase = malloc_base(base, baselen, path1, pathlen1);
- if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE))
+ if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) {
+ newbase[baselen + pathlen1] = 0;
opt->change(opt, mode1, mode2,
- sha1, sha2, base, path1);
+ sha1, sha2, newbase);
+ newbase[baselen + pathlen1] = '/';
+ }
retval = diff_tree_sha1(sha1, sha2, newbase, opt);
free(newbase);
return retval;
}
- opt->change(opt, mode1, mode2, sha1, sha2, base, path1);
+ fullname = malloc_fullname(base, baselen, path1, pathlen1);
+ opt->change(opt, mode1, mode2, sha1, sha2, fullname);
+ free(fullname);
return 0;
}
@@ -205,10 +220,10 @@ static void show_entry(struct diff_options *opt, const char *prefix, struct tree
unsigned mode;
const char *path;
const unsigned char *sha1 = tree_entry_extract(desc, &path, &mode);
+ int pathlen = tree_entry_len(path, sha1);
if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode)) {
enum object_type type;
- int pathlen = tree_entry_len(path, sha1);
char *newbase = malloc_base(base, baselen, path, pathlen);
struct tree_desc inner;
void *tree;
@@ -224,7 +239,9 @@ static void show_entry(struct diff_options *opt, const char *prefix, struct tree
free(tree);
free(newbase);
} else {
- opt->add_remove(opt, prefix[0], mode, sha1, base, path);
+ char *fullname = malloc_fullname(base, baselen, path, pathlen);
+ opt->add_remove(opt, prefix[0], mode, sha1, fullname);
+ free(fullname);
}
}