diff --git a/builtin/checkout.c b/builtin/checkout.c index acefaaf41a4e26e45225f6d228734c93c8f2b23c..88b1f43e05e64f0e8dcd6dd0461bbebd6fab1e25 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -149,7 +149,7 @@ static int checkout_merged(int pos, struct checkout *state) read_mmblob(&ours, active_cache[pos+1]->sha1); read_mmblob(&theirs, active_cache[pos+2]->sha1); - status = ll_merge(&result_buf, path, &ancestor, + status = ll_merge(&result_buf, path, &ancestor, "base", &ours, "ours", &theirs, "theirs", 0); free(ancestor.ptr); free(ours.ptr); @@ -439,6 +439,7 @@ static int merge_working_tree(struct checkout_opts *opts, ret = reset_tree(new->commit->tree, opts, 1); if (ret) return ret; + o.ancestor = old->name; o.branch1 = new->name; o.branch2 = "local"; merge_trees(&o, new->commit->tree, work, diff --git a/builtin/merge-file.c b/builtin/merge-file.c index 69cc683332a04ddc6959fcef3d2853ccfb2860b8..610849a6533c6fd2d3d2e8d3f8d233757174aabd 100644 --- a/builtin/merge-file.c +++ b/builtin/merge-file.c @@ -77,8 +77,10 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) argv[i]); } - ret = xdl_merge(mmfs + 1, mmfs + 0, names[0], mmfs + 2, names[2], - &xmp, &result); + xmp.ancestor = names[1]; + xmp.file1 = names[0]; + xmp.file2 = names[2]; + ret = xdl_merge(mmfs + 1, mmfs + 0, mmfs + 2, &xmp, &result); for (i = 0; i < 3; i++) free(mmfs[i].ptr); diff --git a/builtin/revert.c b/builtin/revert.c index 9a3c14c329713f16d14147b81a4b8031028a4f82..778a56eb512edde3aac6c2a4d668cc73ab3d8460 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -46,6 +46,8 @@ static const char *me; #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" +static char *get_encoding(const char *message); + static void parse_args(int argc, const char **argv) { const char * const * usage_str = @@ -85,33 +87,64 @@ static void parse_args(int argc, const char **argv) exit(1); } -static char *get_oneline(const char *message) +struct commit_message { + char *parent_label; + const char *label; + const char *subject; + char *reencoded_message; + const char *message; +}; + +static int get_message(const char *raw_message, struct commit_message *out) { - char *result; - const char *p = message, *abbrev, *eol; + const char *encoding; + const char *p, *abbrev, *eol; + char *q; int abbrev_len, oneline_len; - if (!p) - die ("Could not read commit message of %s", - sha1_to_hex(commit->object.sha1)); + if (!raw_message) + return -1; + encoding = get_encoding(raw_message); + if (!encoding) + encoding = "UTF-8"; + if (!git_commit_encoding) + git_commit_encoding = "UTF-8"; + if ((out->reencoded_message = reencode_string(raw_message, + git_commit_encoding, encoding))) + out->message = out->reencoded_message; + + abbrev = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV); + abbrev_len = strlen(abbrev); + + /* Find beginning and end of commit subject. */ + p = out->message; while (*p && (*p != '\n' || p[1] != '\n')) p++; - if (*p) { p += 2; for (eol = p + 1; *eol && *eol != '\n'; eol++) ; /* do nothing */ } else eol = p; - abbrev = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV); - abbrev_len = strlen(abbrev); oneline_len = eol - p; - result = xmalloc(abbrev_len + 5 + oneline_len); - memcpy(result, abbrev, abbrev_len); - memcpy(result + abbrev_len, "... ", 4); - memcpy(result + abbrev_len + 4, p, oneline_len); - result[abbrev_len + 4 + oneline_len] = '\0'; - return result; + + out->parent_label = xmalloc(strlen("parent of ") + abbrev_len + + strlen("... ") + oneline_len + 1); + q = out->parent_label; + q = mempcpy(q, "parent of ", strlen("parent of ")); + out->label = q; + q = mempcpy(q, abbrev, abbrev_len); + q = mempcpy(q, "... ", strlen("... ")); + out->subject = q; + q = mempcpy(q, p, oneline_len); + *q = '\0'; + return 0; +} + +static void free_message(struct commit_message *msg) +{ + free(msg->parent_label); + free(msg->reencoded_message); } static char *get_encoding(const char *message) @@ -271,9 +304,9 @@ static int revert_or_cherry_pick(int argc, const char **argv) { unsigned char head[20]; struct commit *base, *next, *parent; + const char *base_label, *next_label; int i, index_fd, clean; - char *oneline, *reencoded_message = NULL; - const char *message, *encoding; + struct commit_message msg = { NULL, NULL, NULL, NULL, NULL }; char *defmsg = NULL; struct merge_options o; struct tree *result, *next_tree, *base_tree, *head_tree; @@ -349,14 +382,14 @@ static int revert_or_cherry_pick(int argc, const char **argv) if (allow_ff && !hashcmp(parent->object.sha1, head)) return fast_forward_to(commit->object.sha1, head); - if (!(message = commit->buffer)) - die ("Cannot get commit message for %s", - sha1_to_hex(commit->object.sha1)); - if (parent && parse_commit(parent) < 0) die("%s: cannot parse parent commit %s", me, sha1_to_hex(parent->object.sha1)); + if (get_message(commit->buffer, &msg) != 0) + die("Cannot get commit message for %s", + sha1_to_hex(commit->object.sha1)); + /* * "commit" is an existing commit. We would want to apply * the difference it introduces since its first parent "prev" @@ -368,26 +401,15 @@ static int revert_or_cherry_pick(int argc, const char **argv) msg_fd = hold_lock_file_for_update(&msg_file, defmsg, LOCK_DIE_ON_ERROR); - encoding = get_encoding(message); - if (!encoding) - encoding = "UTF-8"; - if (!git_commit_encoding) - git_commit_encoding = "UTF-8"; - if ((reencoded_message = reencode_string(message, - git_commit_encoding, encoding))) - message = reencoded_message; - - oneline = get_oneline(message); - index_fd = hold_locked_index(&index_lock, 1); if (action == REVERT) { - char *oneline_body = strchr(oneline, ' '); - base = commit; + base_label = msg.label; next = parent; + next_label = msg.parent_label; add_to_msg("Revert \""); - add_to_msg(oneline_body + 1); + add_to_msg(msg.subject); add_to_msg("\"\n\nThis reverts commit "); add_to_msg(sha1_to_hex(commit->object.sha1)); @@ -398,9 +420,11 @@ static int revert_or_cherry_pick(int argc, const char **argv) add_to_msg(".\n"); } else { base = parent; + base_label = msg.parent_label; next = commit; - set_author_ident_env(message); - add_message_to_msg(message); + next_label = msg.label; + set_author_ident_env(msg.message); + add_message_to_msg(msg.message); if (no_replay) { add_to_msg("(cherry picked from commit "); add_to_msg(sha1_to_hex(commit->object.sha1)); @@ -410,8 +434,9 @@ static int revert_or_cherry_pick(int argc, const char **argv) read_cache(); init_merge_options(&o); + o.ancestor = base ? base_label : "(empty tree)"; o.branch1 = "HEAD"; - o.branch2 = oneline; + o.branch2 = next ? next_label : "(empty tree)"; head_tree = parse_tree_indirect(head); next_tree = next ? next->tree : empty_tree(); @@ -475,7 +500,7 @@ static int revert_or_cherry_pick(int argc, const char **argv) args[i] = NULL; return execv_git_cmd(args); } - free(reencoded_message); + free_message(&msg); free(defmsg); return 0; diff --git a/git-compat-util.h b/git-compat-util.h index e292926fec2d6910ed8a44bb0501fb6e219af3f9..7e62b552700a580646dbb6a8921c40761d6b57a2 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -332,6 +332,7 @@ extern int git_vsnprintf(char *str, size_t maxsize, #ifdef __GLIBC_PREREQ #if __GLIBC_PREREQ(2, 1) #define HAVE_STRCHRNUL +#define HAVE_MEMPCPY #endif #endif @@ -345,6 +346,14 @@ static inline char *gitstrchrnul(const char *s, int c) } #endif +#ifndef HAVE_MEMPCPY +#define mempcpy gitmempcpy +static inline void *gitmempcpy(void *dest, const void *src, size_t n) +{ + return (char *)memcpy(dest, src, n) + n; +} +#endif + extern void release_pack_memory(size_t, int); extern char *xstrdup(const char *str); diff --git a/ll-merge.c b/ll-merge.c index 82c7742e41f4645e7d6086bb277437a84ff4f03b..f9b3d854a921ab6fa3eed6f4dbf01af1a8657602 100644 --- a/ll-merge.c +++ b/ll-merge.c @@ -15,7 +15,7 @@ struct ll_merge_driver; typedef int (*ll_merge_fn)(const struct ll_merge_driver *, mmbuffer_t *result, const char *path, - mmfile_t *orig, + mmfile_t *orig, const char *orig_name, mmfile_t *src1, const char *name1, mmfile_t *src2, const char *name2, int flag, @@ -36,7 +36,7 @@ struct ll_merge_driver { static int ll_binary_merge(const struct ll_merge_driver *drv_unused, mmbuffer_t *result, const char *path_unused, - mmfile_t *orig, + mmfile_t *orig, const char *orig_name, mmfile_t *src1, const char *name1, mmfile_t *src2, const char *name2, int flag, int marker_size) @@ -57,7 +57,7 @@ static int ll_binary_merge(const struct ll_merge_driver *drv_unused, static int ll_xdl_merge(const struct ll_merge_driver *drv_unused, mmbuffer_t *result, const char *path, - mmfile_t *orig, + mmfile_t *orig, const char *orig_name, mmfile_t *src1, const char *name1, mmfile_t *src2, const char *name2, int flag, int marker_size) @@ -71,7 +71,8 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused, path, name1, name2); return ll_binary_merge(drv_unused, result, path, - orig, src1, name1, + orig, orig_name, + src1, name1, src2, name2, flag, marker_size); } @@ -83,13 +84,16 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused, xmp.style = git_xmerge_style; if (marker_size > 0) xmp.marker_size = marker_size; - return xdl_merge(orig, src1, name1, src2, name2, &xmp, result); + xmp.ancestor = orig_name; + xmp.file1 = name1; + xmp.file2 = name2; + return xdl_merge(orig, src1, src2, &xmp, result); } static int ll_union_merge(const struct ll_merge_driver *drv_unused, mmbuffer_t *result, const char *path_unused, - mmfile_t *orig, + mmfile_t *orig, const char *orig_name, mmfile_t *src1, const char *name1, mmfile_t *src2, const char *name2, int flag, int marker_size) @@ -97,7 +101,7 @@ static int ll_union_merge(const struct ll_merge_driver *drv_unused, /* Use union favor */ flag = (flag & 1) | (XDL_MERGE_FAVOR_UNION << 1); return ll_xdl_merge(drv_unused, result, path_unused, - orig, src1, NULL, src2, NULL, + orig, NULL, src1, NULL, src2, NULL, flag, marker_size); return 0; } @@ -128,7 +132,7 @@ static void create_temp(mmfile_t *src, char *path) static int ll_ext_merge(const struct ll_merge_driver *fn, mmbuffer_t *result, const char *path, - mmfile_t *orig, + mmfile_t *orig, const char *orig_name, mmfile_t *src1, const char *name1, mmfile_t *src2, const char *name2, int flag, int marker_size) @@ -319,7 +323,7 @@ static int git_path_check_merge(const char *path, struct git_attr_check check[2] int ll_merge(mmbuffer_t *result_buf, const char *path, - mmfile_t *ancestor, + mmfile_t *ancestor, const char *ancestor_label, mmfile_t *ours, const char *our_label, mmfile_t *theirs, const char *their_label, int flag) @@ -341,7 +345,7 @@ int ll_merge(mmbuffer_t *result_buf, driver = find_ll_merge_driver(ll_driver_name); if (virtual_ancestor && driver->recursive) driver = find_ll_merge_driver(driver->recursive); - return driver->fn(driver, result_buf, path, ancestor, + return driver->fn(driver, result_buf, path, ancestor, ancestor_label, ours, our_label, theirs, their_label, flag, marker_size); } diff --git a/ll-merge.h b/ll-merge.h index 57889227b1782d3792be2046fbb54bca67b779de..57754cc8ca7b378a86b168a4fd6299fa3dfba045 100644 --- a/ll-merge.h +++ b/ll-merge.h @@ -7,7 +7,7 @@ int ll_merge(mmbuffer_t *result_buf, const char *path, - mmfile_t *ancestor, + mmfile_t *ancestor, const char *ancestor_label, mmfile_t *ours, const char *our_label, mmfile_t *theirs, const char *their_label, int flag); diff --git a/merge-file.c b/merge-file.c index fd34d76e1516b2c944778a11a5670d382f245873..c336c93c01c0bad76d6189065f0e6630d0b7f5af 100644 --- a/merge-file.c +++ b/merge-file.c @@ -30,7 +30,13 @@ static void *three_way_filemerge(const char *path, mmfile_t *base, mmfile_t *our int merge_status; mmbuffer_t res; - merge_status = ll_merge(&res, path, base, + /* + * This function is only used by cmd_merge_tree, which + * does not respect the merge.conflictstyle option. + * There is no need to worry about a label for the + * common ancestor. + */ + merge_status = ll_merge(&res, path, base, NULL, our, ".our", their, ".their", 0); if (merge_status < 0) return NULL; diff --git a/merge-recursive.c b/merge-recursive.c index 195ebf974435b0b08cd2be0f54c137218034008b..917397ca7a0c3a702681e1c3e93eb385bb6f28fb 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -608,7 +608,7 @@ static int merge_3way(struct merge_options *o, const char *branch2) { mmfile_t orig, src1, src2; - char *name1, *name2; + char *base_name, *name1, *name2; int merge_status; int favor; @@ -628,10 +628,15 @@ static int merge_3way(struct merge_options *o, } } - if (strcmp(a->path, b->path)) { + if (strcmp(a->path, b->path) || + (o->ancestor != NULL && strcmp(a->path, one->path) != 0)) { + base_name = o->ancestor == NULL ? NULL : + xstrdup(mkpath("%s:%s", o->ancestor, one->path)); name1 = xstrdup(mkpath("%s:%s", branch1, a->path)); name2 = xstrdup(mkpath("%s:%s", branch2, b->path)); } else { + base_name = o->ancestor == NULL ? NULL : + xstrdup(mkpath("%s", o->ancestor)); name1 = xstrdup(mkpath("%s", branch1)); name2 = xstrdup(mkpath("%s", branch2)); } @@ -640,7 +645,7 @@ static int merge_3way(struct merge_options *o, read_mmblob(&src1, a->sha1); read_mmblob(&src2, b->sha1); - merge_status = ll_merge(result_buf, a->path, &orig, + merge_status = ll_merge(result_buf, a->path, &orig, base_name, &src1, name1, &src2, name2, (!!o->call_depth) | (favor << 1)); @@ -1342,6 +1347,7 @@ int merge_recursive(struct merge_options *o, if (!o->call_depth) read_cache(); + o->ancestor = "merged common ancestors"; clean = merge_trees(o, h1->tree, h2->tree, merged_common_ancestors->tree, &mrtree); diff --git a/merge-recursive.h b/merge-recursive.h index be8410ad1803bc10e5dbf74f39eecdfed53469b1..d1192f56d797a1664f5bfb5028d52ad679928e23 100644 --- a/merge-recursive.h +++ b/merge-recursive.h @@ -4,6 +4,7 @@ #include "string-list.h" struct merge_options { + const char *ancestor; const char *branch1; const char *branch2; enum { diff --git a/rerere.c b/rerere.c index a59f74f76c293efa783103eaf3d167c97b3768ea..f221bed1e97a0f1b41d1845edac270a109e3a4dd 100644 --- a/rerere.c +++ b/rerere.c @@ -319,7 +319,7 @@ static int handle_cache(const char *path, unsigned char *sha1, const char *outpu if (!mmfile[i].ptr && !mmfile[i].size) mmfile[i].ptr = xstrdup(""); } - ll_merge(&result, path, &mmfile[0], + ll_merge(&result, path, &mmfile[0], NULL, &mmfile[1], "ours", &mmfile[2], "theirs", 0); for (i = 0; i < 3; i++) @@ -376,7 +376,7 @@ static int merge(const char *name, const char *path) ret = 1; goto out; } - ret = ll_merge(&result, path, &base, &cur, "", &other, "", 0); + ret = ll_merge(&result, path, &base, NULL, &cur, "", &other, "", 0); if (!ret) { FILE *f = fopen(path, "w"); if (!f) diff --git a/t/t3507-cherry-pick-conflict.sh b/t/t3507-cherry-pick-conflict.sh new file mode 100644 index 0000000000000000000000000000000000000000..e25cf8039a310e325c23e85ce5fc845a0f681200 --- /dev/null +++ b/t/t3507-cherry-pick-conflict.sh @@ -0,0 +1,198 @@ +#!/bin/sh + +test_description='test cherry-pick and revert with conflicts + + - + + picked: rewrites foo to c + + base: rewrites foo to b + + initial: writes foo as a, unrelated as unrelated + +' + +. ./test-lib.sh + +test_expect_success setup ' + + echo unrelated >unrelated && + git add unrelated && + test_commit initial foo a && + test_commit base foo b && + test_commit picked foo c && + git config advice.detachedhead false + +' + +test_expect_success 'failed cherry-pick does not advance HEAD' ' + + git checkout -f initial^0 && + git read-tree -u --reset HEAD && + git clean -d -f -f -q -x && + + git update-index --refresh && + git diff-index --exit-code HEAD && + + head=$(git rev-parse HEAD) && + test_must_fail git cherry-pick picked && + newhead=$(git rev-parse HEAD) && + + test "$head" = "$newhead" +' + +test_expect_success 'failed cherry-pick produces dirty index' ' + + git checkout -f initial^0 && + git read-tree -u --reset HEAD && + git clean -d -f -f -q -x && + + git update-index --refresh && + git diff-index --exit-code HEAD && + + test_must_fail git cherry-pick picked && + + test_must_fail git update-index --refresh -q && + test_must_fail git diff-index --exit-code HEAD +' + +test_expect_success 'failed cherry-pick registers participants in index' ' + + git read-tree -u --reset HEAD && + git clean -d -f -f -q -x && + { + git checkout base -- foo && + git ls-files --stage foo && + git checkout initial -- foo && + git ls-files --stage foo && + git checkout picked -- foo && + git ls-files --stage foo + } > stages && + sed " + 1 s/ 0 / 1 / + 2 s/ 0 / 2 / + 3 s/ 0 / 3 / + " < stages > expected && + git checkout -f initial^0 && + + git update-index --refresh && + git diff-index --exit-code HEAD && + + test_must_fail git cherry-pick picked && + git ls-files --stage --unmerged > actual && + + test_cmp expected actual +' + +test_expect_success 'failed cherry-pick describes conflict in work tree' ' + + git checkout -f initial^0 && + git read-tree -u --reset HEAD && + git clean -d -f -f -q -x && + cat <<-EOF > expected && + <<<<<<< HEAD + a + ======= + c + >>>>>>> objid picked + EOF + + git update-index --refresh && + git diff-index --exit-code HEAD && + + test_must_fail git cherry-pick picked && + + sed "s/[a-f0-9]*\.\.\./objid/" foo > actual && + test_cmp expected actual +' + +test_expect_success 'diff3 -m style' ' + + git config merge.conflictstyle diff3 && + git checkout -f initial^0 && + git read-tree -u --reset HEAD && + git clean -d -f -f -q -x && + cat <<-EOF > expected && + <<<<<<< HEAD + a + ||||||| parent of objid picked + b + ======= + c + >>>>>>> objid picked + EOF + + git update-index --refresh && + git diff-index --exit-code HEAD && + + test_must_fail git cherry-pick picked && + + sed "s/[a-f0-9]*\.\.\./objid/" foo > actual && + test_cmp expected actual +' + +test_expect_success 'revert also handles conflicts sanely' ' + + git config --unset merge.conflictstyle && + git read-tree -u --reset HEAD && + git clean -d -f -f -q -x && + cat <<-EOF > expected && + <<<<<<< HEAD + a + ======= + b + >>>>>>> parent of objid picked + EOF + { + git checkout picked -- foo && + git ls-files --stage foo && + git checkout initial -- foo && + git ls-files --stage foo && + git checkout base -- foo && + git ls-files --stage foo + } > stages && + sed " + 1 s/ 0 / 1 / + 2 s/ 0 / 2 / + 3 s/ 0 / 3 / + " < stages > expected-stages && + git checkout -f initial^0 && + + git update-index --refresh && + git diff-index --exit-code HEAD && + + head=$(git rev-parse HEAD) && + test_must_fail git revert picked && + newhead=$(git rev-parse HEAD) && + git ls-files --stage --unmerged > actual-stages && + + test "$head" = "$newhead" && + test_must_fail git update-index --refresh -q && + test_must_fail git diff-index --exit-code HEAD && + test_cmp expected-stages actual-stages && + sed "s/[a-f0-9]*\.\.\./objid/" foo > actual && + test_cmp expected actual +' + +test_expect_success 'revert conflict, diff3 -m style' ' + git config merge.conflictstyle diff3 && + git checkout -f initial^0 && + git read-tree -u --reset HEAD && + git clean -d -f -f -q -x && + cat <<-EOF > expected && + <<<<<<< HEAD + a + ||||||| objid picked + c + ======= + b + >>>>>>> parent of objid picked + EOF + + git update-index --refresh && + git diff-index --exit-code HEAD && + + test_must_fail git revert picked && + + sed "s/[a-f0-9]*\.\.\./objid/" foo > actual && + test_cmp expected actual +' + +test_done diff --git a/t/t6023-merge-file.sh b/t/t6023-merge-file.sh index 5034dd1352c275beca4d5dae805cad6573a89aca..d486d73994cf563063b578ec5e212331e87bf67f 100755 --- a/t/t6023-merge-file.sh +++ b/t/t6023-merge-file.sh @@ -181,7 +181,7 @@ et nihil mihi deerit; In loco pascuae ibi me collocavit; super aquam refectionis educavit me. -||||||| +||||||| new5.txt et nihil mihi deerit. In loco pascuae ibi me collocavit, super aquam refectionis educavit me; @@ -225,7 +225,7 @@ et nihil mihi deerit; In loco pascuae ibi me collocavit; super aquam refectionis educavit me. -|||||||||| +|||||||||| new5.txt et nihil mihi deerit. In loco pascuae ibi me collocavit, super aquam refectionis educavit me; diff --git a/t/t7201-co.sh b/t/t7201-co.sh index d20ed61b481539b25c054c49bd82aa6fb9b3a981..1337fa5a2209d489c43f0a34c95a89053d3fd8bf 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -11,10 +11,12 @@ Test switching across them. ! [master] Initial A one, A two * [renamer] Renamer R one->uno, M two ! [side] Side M one, D two, A three - --- - + [side] Side M one, D two, A three - * [renamer] Renamer R one->uno, M two - +*+ [master] Initial A one, A two + ! [simple] Simple D one, M two + ---- + + [simple] Simple D one, M two + + [side] Side M one, D two, A three + * [renamer] Renamer R one->uno, M two + +*++ [master] Initial A one, A two ' @@ -52,6 +54,11 @@ test_expect_success setup ' git update-index --add --remove one two three && git commit -m "Side M one, D two, A three" && + git checkout -b simple master && + rm -f one && + fill a c e > two && + git commit -a -m "Simple D one, M two" && + git checkout master ' @@ -166,6 +173,56 @@ test_expect_success 'checkout -m with merge conflict' ' ! test -s current ' +test_expect_success 'format of merge conflict from checkout -m' ' + + git checkout -f master && git clean -f && + + fill b d > two && + git checkout -m simple && + + git ls-files >current && + fill same two two two >expect && + test_cmp current expect && + + cat <<-EOF >expect && + <<<<<<< simple + a + c + e + ======= + b + d + >>>>>>> local + EOF + test_cmp two expect +' + +test_expect_success 'checkout --merge --conflict=diff3 ' ' + + git checkout -f master && git reset --hard && git clean -f && + + fill b d > two && + git checkout --merge --conflict=diff3 simple && + + cat <<-EOF >expect && + <<<<<<< simple + a + c + e + ||||||| master + a + b + c + d + e + ======= + b + d + >>>>>>> local + EOF + test_cmp two expect +' + test_expect_success 'checkout to detach HEAD (with advice declined)' ' git config advice.detachedHead false && @@ -481,7 +538,7 @@ test_expect_success 'checkout with --merge, in diff3 -m style' ' ( echo "<<<<<<< ours" echo ourside - echo "|||||||" + echo "||||||| base" echo original echo "=======" echo theirside @@ -525,7 +582,7 @@ test_expect_success 'checkout --conflict=diff3' ' ( echo "<<<<<<< ours" echo ourside - echo "|||||||" + echo "||||||| base" echo original echo "=======" echo theirside diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h index a71763ade835fd93ef291106298647408ada08e9..711048ea36dda1d814b395efa8bc69ab142425ca 100644 --- a/xdiff/xdiff.h +++ b/xdiff/xdiff.h @@ -117,12 +117,14 @@ typedef struct s_xmparam { int level; int favor; int style; + const char *ancestor; /* label for orig */ + const char *file1; /* label for mf1 */ + const char *file2; /* label for mf2 */ } xmparam_t; #define DEFAULT_CONFLICT_MARKER_SIZE 7 -int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1, - mmfile_t *mf2, const char *name2, +int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2, xmparam_t const *xmp, mmbuffer_t *result); #ifdef __cplusplus diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c index 87cafa7021468d00a11c72dfdc1df05f1ede0fea..16dd9acd37b431f1e439d9ab051fe2436c54b45c 100644 --- a/xdiff/xmerge.c +++ b/xdiff/xmerge.c @@ -145,11 +145,13 @@ static int xdl_orig_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest) static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, xdfenv_t *xe2, const char *name2, + const char *name3, int size, int i, int style, xdmerge_t *m, char *dest, int marker_size) { int marker1_size = (name1 ? strlen(name1) + 1 : 0); int marker2_size = (name2 ? strlen(name2) + 1 : 0); + int marker3_size = (name3 ? strlen(name3) + 1 : 0); int j; if (marker_size <= 0) @@ -179,10 +181,15 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, if (style == XDL_MERGE_DIFF3) { /* Shared preimage */ if (!dest) { - size += marker_size + 1; + size += marker_size + 1 + marker3_size; } else { for (j = 0; j < marker_size; j++) dest[size++] = '|'; + if (marker3_size) { + dest[size] = ' '; + memcpy(dest + size + 1, name3, marker3_size - 1); + size += marker3_size; + } dest[size++] = '\n'; } size += xdl_orig_copy(xe1, m->i0, m->chg0, 1, @@ -217,6 +224,7 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1, xdfenv_t *xe2, const char *name2, + const char *ancestor_name, int favor, xdmerge_t *m, char *dest, int style, int marker_size) @@ -229,6 +237,7 @@ static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1, if (m->mode == 0) size = fill_conflict_hunk(xe1, name1, xe2, name2, + ancestor_name, size, i, style, m, dest, marker_size); else if (m->mode & 3) { @@ -398,11 +407,15 @@ static int xdl_simplify_non_conflicts(xdfenv_t *xe1, xdmerge_t *m, * * returns < 0 on error, == 0 for no conflicts, else number of conflicts */ -static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, - xdfenv_t *xe2, xdchange_t *xscr2, const char *name2, - xmparam_t const *xmp, mmbuffer_t *result) { +static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, + xdfenv_t *xe2, xdchange_t *xscr2, + xmparam_t const *xmp, mmbuffer_t *result) +{ xdmerge_t *changes, *c; xpparam_t const *xpp = &xmp->xpp; + const char *const ancestor_name = xmp->ancestor; + const char *const name1 = xmp->file1; + const char *const name2 = xmp->file2; int i0, i1, i2, chg0, chg1, chg2; int level = xmp->level; int style = xmp->style; @@ -540,6 +553,7 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, if (result) { int marker_size = xmp->marker_size; int size = xdl_fill_merge_buffer(xe1, name1, xe2, name2, + ancestor_name, favor, changes, NULL, style, marker_size); result->ptr = xdl_malloc(size); @@ -548,15 +562,16 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, return -1; } result->size = size; - xdl_fill_merge_buffer(xe1, name1, xe2, name2, favor, changes, + xdl_fill_merge_buffer(xe1, name1, xe2, name2, + ancestor_name, favor, changes, result->ptr, style, marker_size); } return xdl_cleanup_merge(changes); } -int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1, - mmfile_t *mf2, const char *name2, - xmparam_t const *xmp, mmbuffer_t *result) { +int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2, + xmparam_t const *xmp, mmbuffer_t *result) +{ xdchange_t *xscr1, *xscr2; xdfenv_t xe1, xe2; int status; @@ -591,8 +606,8 @@ int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1, memcpy(result->ptr, mf1->ptr, mf1->size); result->size = mf1->size; } else { - status = xdl_do_merge(&xe1, xscr1, name1, - &xe2, xscr2, name2, + status = xdl_do_merge(&xe1, xscr1, + &xe2, xscr2, xmp, result); } xdl_free_script(xscr1);