* [PATCH 1/9] merge-recursive: remove dead conditional in update_stages()
2014-02-04 22:17 [PATCH 0/9] remerge diff proof of concept/RFC Thomas Rast
@ 2014-02-04 22:17 ` Thomas Rast
2014-02-04 22:17 ` [PATCH 2/9] merge-recursive: internal flag to avoid touching the worktree Thomas Rast
` (8 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Thomas Rast @ 2014-02-04 22:17 UTC (permalink / raw)
To: git; +Cc: Thomas Rast, Junio C Hamano
From: Thomas Rast <trast@inf.ethz.ch>
650467c (merge-recursive: Consolidate different update_stages
functions, 2011-08-11) changed the former argument 'clear' to always
be true. Remove the useless conditional.
Signed-off-by: Thomas Rast <trast@inf.ethz.ch>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
merge-recursive.c | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/merge-recursive.c b/merge-recursive.c
index 8400a8e..c36dc79 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -545,11 +545,9 @@ static int update_stages(const char *path, const struct diff_filespec *o,
* would_lose_untracked). Instead, reverse the order of the calls
* (executing update_file first and then update_stages).
*/
- int clear = 1;
int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_SKIP_DFCHECK;
- if (clear)
- if (remove_file_from_cache(path))
- return -1;
+ if (remove_file_from_cache(path))
+ return -1;
if (o)
if (add_cacheinfo(o->mode, o->sha1, path, 1, 0, options))
return -1;
--
1.9.rc2.232.gdd31389
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 2/9] merge-recursive: internal flag to avoid touching the worktree
2014-02-04 22:17 [PATCH 0/9] remerge diff proof of concept/RFC Thomas Rast
2014-02-04 22:17 ` [PATCH 1/9] merge-recursive: remove dead conditional in update_stages() Thomas Rast
@ 2014-02-04 22:17 ` Thomas Rast
2014-02-04 22:17 ` [PATCH 3/9] merge-recursive: -Xindex-only to leave worktree unchanged Thomas Rast
` (7 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Thomas Rast @ 2014-02-04 22:17 UTC (permalink / raw)
To: git; +Cc: Thomas Rast, Junio C Hamano
From: Thomas Rast <trast@inf.ethz.ch>
o->call_depth has a double function: a nonzero call_depth means we
want to construct virtual merge bases, but it also means we want to
avoid touching the worktree. Introduce a new flag o->no_worktree to
trigger only the latter.
Signed-off-by: Thomas Rast <trast@inf.ethz.ch>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
merge-recursive.c | 37 +++++++++++++++++++++----------------
merge-recursive.h | 1 +
2 files changed, 22 insertions(+), 16 deletions(-)
diff --git a/merge-recursive.c b/merge-recursive.c
index c36dc79..35be144 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -408,10 +408,10 @@ static void record_df_conflict_files(struct merge_options *o,
int i;
/*
- * If we're merging merge-bases, we don't want to bother with
- * any working directory changes.
+ * If we're working in-core only (e.g., merging merge-bases),
+ * we don't want to bother with any working directory changes.
*/
- if (o->call_depth)
+ if (o->call_depth || o->no_worktree)
return;
/* Ensure D/F conflicts are adjacent in the entries list. */
@@ -724,7 +724,7 @@ static void update_file_flags(struct merge_options *o,
int update_cache,
int update_wd)
{
- if (o->call_depth)
+ if (o->call_depth || o->no_worktree)
update_wd = 0;
if (update_wd) {
@@ -931,7 +931,8 @@ static struct merge_file_info merge_file_1(struct merge_options *o,
result.clean = merge_submodule(result.sha,
one->path, one->sha1,
a->sha1, b->sha1,
- !o->call_depth);
+ !(o->call_depth ||
+ o->no_worktree));
} else if (S_ISLNK(a->mode)) {
hashcpy(result.sha, a->sha1);
@@ -1003,7 +1004,7 @@ static void handle_change_delete(struct merge_options *o,
const char *change, const char *change_past)
{
char *renamed = NULL;
- if (dir_in_way(path, !o->call_depth)) {
+ if (dir_in_way(path, !(o->call_depth || o->no_worktree))) {
renamed = unique_path(o, path, a_sha ? o->branch1 : o->branch2);
}
@@ -1128,10 +1129,10 @@ static void handle_file(struct merge_options *o,
char *add_name = unique_path(o, rename->path, other_branch);
update_file(o, 0, add->sha1, add->mode, add_name);
- remove_file(o, 0, rename->path, 0);
+ remove_file(o, 0, rename->path, o->call_depth || o->no_worktree);
dst_name = unique_path(o, rename->path, cur_branch);
} else {
- if (dir_in_way(rename->path, !o->call_depth)) {
+ if (dir_in_way(rename->path, !(o->call_depth || o->no_worktree))) {
dst_name = unique_path(o, rename->path, cur_branch);
output(o, 1, _("%s is a directory in %s adding as %s instead"),
rename->path, other_branch, dst_name);
@@ -1238,7 +1239,7 @@ static void conflict_rename_rename_2to1(struct merge_options *o,
* merge base just undo the renames; they can be detected
* again later for the non-recursive merge.
*/
- remove_file(o, 0, path, 0);
+ remove_file(o, 0, path, o->call_depth || o->no_worktree);
update_file(o, 0, mfi_c1.sha, mfi_c1.mode, a->path);
update_file(o, 0, mfi_c2.sha, mfi_c2.mode, b->path);
} else {
@@ -1246,7 +1247,7 @@ static void conflict_rename_rename_2to1(struct merge_options *o,
char *new_path2 = unique_path(o, path, ci->branch2);
output(o, 1, _("Renaming %s to %s and %s to %s instead"),
a->path, new_path1, b->path, new_path2);
- remove_file(o, 0, path, 0);
+ remove_file(o, 0, path, o->call_depth || o->no_worktree);
update_file(o, 0, mfi_c1.sha, mfi_c1.mode, new_path1);
update_file(o, 0, mfi_c2.sha, mfi_c2.mode, new_path2);
free(new_path2);
@@ -1405,6 +1406,7 @@ static int process_renames(struct merge_options *o,
* add-source case).
*/
remove_file(o, 1, ren1_src,
+ o->call_depth || o->no_worktree ||
renamed_stage == 2 || !was_tracked(ren1_src));
hashcpy(src_other.sha1, ren1->src_entry->stages[other_stage].sha);
@@ -1601,7 +1603,7 @@ static int merge_content(struct merge_options *o,
o->branch2 == rename_conflict_info->branch1) ?
pair1->two->path : pair1->one->path;
- if (dir_in_way(path, !o->call_depth))
+ if (dir_in_way(path, !(o->call_depth || o->no_worktree)))
df_conflict_remains = 1;
}
mfi = merge_file_special_markers(o, &one, &a, &b,
@@ -1621,7 +1623,7 @@ static int merge_content(struct merge_options *o,
path_renamed_outside_HEAD = !path2 || !strcmp(path, path2);
if (!path_renamed_outside_HEAD) {
add_cacheinfo(mfi.mode, mfi.sha, path,
- 0, (!o->call_depth), 0);
+ 0, !(o->call_depth || o->no_worktree), 0);
return mfi.clean;
}
} else
@@ -1722,7 +1724,8 @@ static int process_entry(struct merge_options *o,
if (a_sha)
output(o, 2, _("Removing %s"), path);
/* do not touch working file if it did not exist */
- remove_file(o, 1, path, !a_sha);
+ remove_file(o, 1, path,
+ o->call_depth || o->no_worktree || !a_sha);
} else {
/* Modify/delete; deleted side may have put a directory in the way */
clean_merge = 0;
@@ -1753,7 +1756,7 @@ static int process_entry(struct merge_options *o,
sha = b_sha;
conf = _("directory/file");
}
- if (dir_in_way(path, !o->call_depth)) {
+ if (dir_in_way(path, !(o->call_depth || o->no_worktree))) {
char *new_path = unique_path(o, path, add_branch);
clean_merge = 0;
output(o, 1, _("CONFLICT (%s): There is a directory with name %s in %s. "
@@ -1781,7 +1784,8 @@ static int process_entry(struct merge_options *o,
* this entry was deleted altogether. a_mode == 0 means
* we had that path and want to actively remove it.
*/
- remove_file(o, 1, path, !a_mode);
+ remove_file(o, 1, path,
+ o->call_depth || o->no_worktree || !a_mode);
} else
die(_("Fatal merge failure, shouldn't happen."));
@@ -1807,7 +1811,8 @@ int merge_trees(struct merge_options *o,
return 1;
}
- code = git_merge_trees(o->call_depth, common, head, merge);
+ code = git_merge_trees(o->call_depth || o->no_worktree,
+ common, head, merge);
if (code != 0) {
if (show(o, 4) || o->call_depth)
diff --git a/merge-recursive.h b/merge-recursive.h
index 9e090a3..d8dd7a1 100644
--- a/merge-recursive.h
+++ b/merge-recursive.h
@@ -15,6 +15,7 @@ struct merge_options {
const char *subtree_shift;
unsigned buffer_output : 1;
unsigned renormalize : 1;
+ unsigned no_worktree : 1; /* do not touch worktree */
long xdl_opts;
int verbosity;
int diff_rename_limit;
--
1.9.rc2.232.gdd31389
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 3/9] merge-recursive: -Xindex-only to leave worktree unchanged
2014-02-04 22:17 [PATCH 0/9] remerge diff proof of concept/RFC Thomas Rast
2014-02-04 22:17 ` [PATCH 1/9] merge-recursive: remove dead conditional in update_stages() Thomas Rast
2014-02-04 22:17 ` [PATCH 2/9] merge-recursive: internal flag to avoid touching the worktree Thomas Rast
@ 2014-02-04 22:17 ` Thomas Rast
2014-02-04 22:17 ` [POC PATCH 4/9] pretty: refactor add_merge_info() into parts Thomas Rast
` (6 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Thomas Rast @ 2014-02-04 22:17 UTC (permalink / raw)
To: git; +Cc: Thomas Rast, Junio C Hamano
From: Thomas Rast <trast@inf.ethz.ch>
Using the new no_worktree flag from the previous commit, we can teach
merge-recursive to leave the worktree untouched. Expose this with a
new strategy option so that scripts can use it.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
Documentation/merge-strategies.txt | 4 ++++
merge-recursive.c | 2 ++
t/t3030-merge-recursive.sh | 13 +++++++++++++
3 files changed, 19 insertions(+)
diff --git a/Documentation/merge-strategies.txt b/Documentation/merge-strategies.txt
index fb6e593..2934e99 100644
--- a/Documentation/merge-strategies.txt
+++ b/Documentation/merge-strategies.txt
@@ -92,6 +92,10 @@ subtree[=<path>];;
is prefixed (or stripped from the beginning) to make the shape of
two trees to match.
+index-only;;
+ Write the merge result only to the index; do not touch the
+ worktree.
+
octopus::
This resolves cases with more than two heads, but refuses to do
a complex merge that needs manual resolution. It is
diff --git a/merge-recursive.c b/merge-recursive.c
index 35be144..f59c1d3 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -2096,6 +2096,8 @@ int parse_merge_opt(struct merge_options *o, const char *s)
if ((o->rename_score = parse_rename_score(&score)) == -1 || *score != 0)
return -1;
}
+ else if (!strcmp(s, "index-only"))
+ o->no_worktree = 1;
else
return -1;
return 0;
diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh
index 2f96100..2f3a16c 100755
--- a/t/t3030-merge-recursive.sh
+++ b/t/t3030-merge-recursive.sh
@@ -296,6 +296,19 @@ test_expect_success 'merge-recursive result' '
'
+test_expect_success 'merge-recursive --index-only' '
+
+ rm -fr [abcd] &&
+ git checkout -f "$c2" &&
+ test_expect_code 1 git merge-recursive --index-only "$c0" -- "$c2" "$c1" &&
+ git ls-files -s >actual &&
+ # reuses "expected" from previous test!
+ test_cmp expected actual &&
+ git diff HEAD >actual-diff &&
+ : >expected-diff &&
+ test_cmp expected-diff actual-diff
+'
+
test_expect_success 'fail if the index has unresolved entries' '
rm -fr [abcd] &&
--
1.9.rc2.232.gdd31389
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [POC PATCH 4/9] pretty: refactor add_merge_info() into parts
2014-02-04 22:17 [PATCH 0/9] remerge diff proof of concept/RFC Thomas Rast
` (2 preceding siblings ...)
2014-02-04 22:17 ` [PATCH 3/9] merge-recursive: -Xindex-only to leave worktree unchanged Thomas Rast
@ 2014-02-04 22:17 ` Thomas Rast
2014-02-04 22:17 ` [POC PATCH 5/9] log: add a merge base inspection option Thomas Rast
` (5 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Thomas Rast @ 2014-02-04 22:17 UTC (permalink / raw)
To: git
pp_commit_list() will be reused later.
Signed-off-by: Thomas Rast <tr@thomasrast.ch>
---
Necessary only for the next patch, which may be of dubious value.
commit.h | 1 +
pretty.c | 40 ++++++++++++++++++++++++++--------------
2 files changed, 27 insertions(+), 14 deletions(-)
diff --git a/commit.h b/commit.h
index 16d9c43..b51817b 100644
--- a/commit.h
+++ b/commit.h
@@ -101,6 +101,7 @@ struct pretty_print_context {
struct string_list *mailmap;
int color;
struct ident_split *from_ident;
+ struct commit_list *merge_bases;
/*
* Fields below here are manipulated internally by pp_* functions and
diff --git a/pretty.c b/pretty.c
index 87db08b..5e44cf8 100644
--- a/pretty.c
+++ b/pretty.c
@@ -517,6 +517,31 @@ static const char *skip_empty_lines(const char *msg)
return msg;
}
+static const char *pp_sha1_to_hex(const struct pretty_print_context *pp,
+ const unsigned char *sha1)
+{
+ const char *hex = NULL;
+ if (pp->abbrev)
+ hex = find_unique_abbrev(sha1, pp->abbrev);
+ if (!hex)
+ hex = sha1_to_hex(sha1);
+ return hex;
+}
+
+static void pp_commit_list(const struct pretty_print_context *pp,
+ struct strbuf *sb,
+ const char *prefix,
+ const struct commit_list *list)
+{
+ strbuf_addstr(sb, prefix);
+ while (list) {
+ struct commit *commit = list->item;
+ strbuf_addf(sb, " %s", pp_sha1_to_hex(pp, commit->object.sha1));
+ list = list->next;
+ }
+ strbuf_addch(sb, '\n');
+}
+
static void add_merge_info(const struct pretty_print_context *pp,
struct strbuf *sb, const struct commit *commit)
{
@@ -526,20 +551,7 @@ static void add_merge_info(const struct pretty_print_context *pp,
!parent || !parent->next)
return;
- strbuf_addstr(sb, "Merge:");
-
- while (parent) {
- struct commit *p = parent->item;
- const char *hex = NULL;
- if (pp->abbrev)
- hex = find_unique_abbrev(p->object.sha1, pp->abbrev);
- if (!hex)
- hex = sha1_to_hex(p->object.sha1);
- parent = parent->next;
-
- strbuf_addf(sb, " %s", hex);
- }
- strbuf_addch(sb, '\n');
+ pp_commit_list(pp, sb, "Merge:", parent);
}
static char *get_header(const struct commit *commit, const char *msg,
--
1.9.rc2.232.gdd31389
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [POC PATCH 5/9] log: add a merge base inspection option
2014-02-04 22:17 [PATCH 0/9] remerge diff proof of concept/RFC Thomas Rast
` (3 preceding siblings ...)
2014-02-04 22:17 ` [POC PATCH 4/9] pretty: refactor add_merge_info() into parts Thomas Rast
@ 2014-02-04 22:17 ` Thomas Rast
2014-02-04 22:17 ` [PATCH 6/9] combine-diff: do not pass revs->dense_combined_merges redundantly Thomas Rast
` (4 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Thomas Rast @ 2014-02-04 22:17 UTC (permalink / raw)
To: git
With the new --bases, print merge commits' parents' merge bases. This
is mostly a proof of viability, in particular wrt. revision walk
decoupling and speed.
We can do "inline" get_merge_bases() (via get_octopus_merge_bases)
because the walks in get_merge_bases() only use flag bits 16-19, and
we reset them after use. The get_revision()/log display walk OTOH
uses only flag bits 0-15 (actually only 0-10 as of this commit).
Speed-wise it turns out to be better than attempting to compute merge
bases in one go, mostly because the latter approach would require
extensive data structures to track flags. This commit does not have
to: the commit graph will be loaded anyway, and the room for flags is
already there. As a big plus, this approach also works in a streaming
fashion, showing the first few commits very quickly.
Signed-off-by: Thomas Rast <tr@thomasrast.ch>
---
As indicated in the cover letter, this is cute, but I'm not married to
it. It's probably just a useless instance of feature creep.
Documentation/rev-list-options.txt | 7 +++++++
log-tree.c | 3 +++
pretty.c | 3 +++
revision.c | 2 ++
revision.h | 2 ++
t/t4202-log.sh | 31 +++++++++++++++++++++++++++++++
6 files changed, 48 insertions(+)
diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt
index 03533af..d023290 100644
--- a/Documentation/rev-list-options.txt
+++ b/Documentation/rev-list-options.txt
@@ -705,6 +705,13 @@ endif::git-rev-list[]
Print also the children of the commit (in the form "commit child...").
Also enables parent rewriting, see 'History Simplification' below.
+ifndef::git-rev-list[]
+--bases::
+ For merge commits, print the merge bases of the commit's
+ parents. (These are the bases that were used in the creation
+ of the merge itself.)
+endif::git-rev-list[]
+
ifdef::git-rev-list[]
--timestamp::
Print the raw commit timestamp.
diff --git a/log-tree.c b/log-tree.c
index 08970bf..080f412 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -622,6 +622,8 @@ void show_log(struct rev_info *opt)
ctx.output_encoding = get_log_output_encoding();
if (opt->from_ident.mail_begin && opt->from_ident.name_begin)
ctx.from_ident = &opt->from_ident;
+ if (opt->show_merge_bases && commit->parents && commit->parents->next)
+ ctx.merge_bases = get_octopus_merge_bases(commit->parents);
pretty_print_commit(&ctx, commit, &msgbuf);
if (opt->add_signoff)
@@ -662,6 +664,7 @@ void show_log(struct rev_info *opt)
strbuf_release(&msgbuf);
free(ctx.notes_message);
+ free(ctx.merge_bases);
}
int log_tree_diff_flush(struct rev_info *opt)
diff --git a/pretty.c b/pretty.c
index 5e44cf8..8b28664 100644
--- a/pretty.c
+++ b/pretty.c
@@ -552,6 +552,9 @@ static void add_merge_info(const struct pretty_print_context *pp,
return;
pp_commit_list(pp, sb, "Merge:", parent);
+
+ if (pp->merge_bases)
+ pp_commit_list(pp, sb, "Bases:", pp->merge_bases);
}
static char *get_header(const struct commit *commit, const char *msg,
diff --git a/revision.c b/revision.c
index a0df72f..72255fb 100644
--- a/revision.c
+++ b/revision.c
@@ -1729,6 +1729,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
} else if (!strcmp(arg, "--parents")) {
revs->rewrite_parents = 1;
revs->print_parents = 1;
+ } else if (!strcmp(arg, "--merge-bases")) {
+ revs->show_merge_bases = 1;
} else if (!strcmp(arg, "--dense")) {
revs->dense = 1;
} else if (!strcmp(arg, "--sparse")) {
diff --git a/revision.h b/revision.h
index 88967d6..3111228 100644
--- a/revision.h
+++ b/revision.h
@@ -19,6 +19,7 @@
#define PATCHSAME (1u<<9)
#define BOTTOM (1u<<10)
#define ALL_REV_FLAGS ((1u<<11)-1)
+/* merge-base.c uses bits 16-19. --merge-bases will break if they overlap! */
#define DECORATE_SHORT_REFS 1
#define DECORATE_FULL_REFS 2
@@ -137,6 +138,7 @@ struct rev_info {
preserve_subject:1;
unsigned int disable_stdin:1;
unsigned int leak_pending:1;
+ unsigned int show_merge_bases:1;
enum date_mode date_mode;
diff --git a/t/t4202-log.sh b/t/t4202-log.sh
index cb03d28..64f34a6 100755
--- a/t/t4202-log.sh
+++ b/t/t4202-log.sh
@@ -841,4 +841,35 @@ test_expect_success 'dotdot is a parent directory' '
test_cmp expect actual
'
+shorten () {
+ for arg; do
+ git rev-parse --short "$arg"
+ done
+}
+
+fill_in_merge_bases () {
+ while IFS= read line; do
+ case "$line" in
+ Merge:*)
+ printf "%s\n" "$line"
+ printf "%s" "Bases:"
+ printf " %s" $(shorten \
+ $(git merge-base --all --octopus \
+ ${line##Merge:}))
+ printf "\n"
+ ;;
+ *)
+ printf "%s\n" "$line"
+ ;;
+ esac
+ done
+}
+
+test_expect_success '--merge-bases' '
+ git log |
+ fill_in_merge_bases >expect
+ git log --merge-bases >actual &&
+ test_cmp expect actual
+'
+
test_done
--
1.9.rc2.232.gdd31389
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 6/9] combine-diff: do not pass revs->dense_combined_merges redundantly
2014-02-04 22:17 [PATCH 0/9] remerge diff proof of concept/RFC Thomas Rast
` (4 preceding siblings ...)
2014-02-04 22:17 ` [POC PATCH 5/9] log: add a merge base inspection option Thomas Rast
@ 2014-02-04 22:17 ` Thomas Rast
2014-02-04 22:17 ` [PATCH 7/9] Fold all merge diff variants into an enum Thomas Rast
` (3 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Thomas Rast @ 2014-02-04 22:17 UTC (permalink / raw)
To: git
The existing code passed revs->dense_combined_merges along revs itself
into the combine-diff functions, which is rather redundant. Remove
the 'dense' argument until much further down the callchain to simplify
callers.
Note that while the caller in submodule.c needs to do extra work now,
the next commit will simplify this to a single setting again.
Signed-off-by: Thomas Rast <tr@thomasrast.ch>
---
builtin/diff.c | 3 +--
combine-diff.c | 13 ++++++-------
diff-lib.c | 6 ++----
diff.h | 6 +++---
log-tree.c | 2 +-
submodule.c | 5 ++++-
6 files changed, 17 insertions(+), 18 deletions(-)
diff --git a/builtin/diff.c b/builtin/diff.c
index 0f247d2..47f663b 100644
--- a/builtin/diff.c
+++ b/builtin/diff.c
@@ -196,8 +196,7 @@ static int builtin_diff_combined(struct rev_info *revs,
revs->dense_combined_merges = revs->combine_merges = 1;
for (i = 1; i < ents; i++)
sha1_array_append(&parents, ent[i].item->sha1);
- diff_tree_combined(ent[0].item->sha1, &parents,
- revs->dense_combined_merges, revs);
+ diff_tree_combined(ent[0].item->sha1, &parents, revs);
sha1_array_clear(&parents);
return 0;
}
diff --git a/combine-diff.c b/combine-diff.c
index 3b92c448..6e80a73 100644
--- a/combine-diff.c
+++ b/combine-diff.c
@@ -952,7 +952,7 @@ static void show_combined_header(struct combine_diff_path *elem,
}
static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
- int dense, int working_tree_file,
+ int working_tree_file,
struct rev_info *rev)
{
struct diff_options *opt = &rev->diffopt;
@@ -967,6 +967,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
struct userdiff_driver *textconv = NULL;
int is_binary;
const char *line_prefix = diff_line_prefix(opt);
+ int dense = rev->dense_combined_merges;
context = opt->context;
userdiff = userdiff_find_by_path(elem->path);
@@ -1214,7 +1215,6 @@ static void show_raw_diff(struct combine_diff_path *p, int num_parent, struct re
*/
void show_combined_diff(struct combine_diff_path *p,
int num_parent,
- int dense,
struct rev_info *rev)
{
struct diff_options *opt = &rev->diffopt;
@@ -1226,7 +1226,7 @@ void show_combined_diff(struct combine_diff_path *p,
DIFF_FORMAT_NAME_STATUS))
show_raw_diff(p, num_parent, rev);
else if (opt->output_format & DIFF_FORMAT_PATCH)
- show_patch_diff(p, num_parent, dense, 1, rev);
+ show_patch_diff(p, num_parent, 1, rev);
}
static void free_combined_pair(struct diff_filepair *pair)
@@ -1297,7 +1297,6 @@ static void handle_combined_callback(struct diff_options *opt,
void diff_tree_combined(const unsigned char *sha1,
const struct sha1_array *parents,
- int dense,
struct rev_info *rev)
{
struct diff_options *opt = &rev->diffopt;
@@ -1365,7 +1364,7 @@ void diff_tree_combined(const unsigned char *sha1,
opt->line_termination);
for (p = paths; p; p = p->next) {
if (p->len)
- show_patch_diff(p, num_parent, dense,
+ show_patch_diff(p, num_parent,
0, rev);
}
}
@@ -1381,7 +1380,7 @@ void diff_tree_combined(const unsigned char *sha1,
free_pathspec(&diffopts.pathspec);
}
-void diff_tree_combined_merge(const struct commit *commit, int dense,
+void diff_tree_combined_merge(const struct commit *commit,
struct rev_info *rev)
{
struct commit_list *parent = get_saved_parents(rev, commit);
@@ -1391,6 +1390,6 @@ void diff_tree_combined_merge(const struct commit *commit, int dense,
sha1_array_append(&parents, parent->item->object.sha1);
parent = parent->next;
}
- diff_tree_combined(commit->object.sha1, &parents, dense, rev);
+ diff_tree_combined(commit->object.sha1, &parents, rev);
sha1_array_clear(&parents);
}
diff --git a/diff-lib.c b/diff-lib.c
index 346cac6..8d0f572 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -174,9 +174,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
i--;
if (revs->combine_merges && num_compare_stages == 2) {
- show_combined_diff(dpath, 2,
- revs->dense_combined_merges,
- revs);
+ show_combined_diff(dpath, 2, revs);
free(dpath);
continue;
}
@@ -338,7 +336,7 @@ static int show_modified(struct rev_info *revs,
p->parent[1].status = DIFF_STATUS_MODIFIED;
p->parent[1].mode = old->ce_mode;
hashcpy(p->parent[1].sha1, old->sha1);
- show_combined_diff(p, 2, revs->dense_combined_merges, revs);
+ show_combined_diff(p, 2, revs);
free(p);
return 0;
}
diff --git a/diff.h b/diff.h
index ce123fa..ff77802 100644
--- a/diff.h
+++ b/diff.h
@@ -213,11 +213,11 @@ struct combine_diff_path {
sizeof(struct combine_diff_parent) * (n) + (l) + 1)
extern void show_combined_diff(struct combine_diff_path *elem, int num_parent,
- int dense, struct rev_info *);
+ struct rev_info *);
-extern void diff_tree_combined(const unsigned char *sha1, const struct sha1_array *parents, int dense, struct rev_info *rev);
+extern void diff_tree_combined(const unsigned char *sha1, const struct sha1_array *parents, struct rev_info *rev);
-extern void diff_tree_combined_merge(const struct commit *commit, int dense, struct rev_info *rev);
+extern void diff_tree_combined_merge(const struct commit *commit, struct rev_info *rev);
void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const char *b);
diff --git a/log-tree.c b/log-tree.c
index 080f412..2fcca45 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -721,7 +721,7 @@ int log_tree_diff_flush(struct rev_info *opt)
static int do_diff_combined(struct rev_info *opt, struct commit *commit)
{
- diff_tree_combined_merge(commit, opt->dense_combined_merges, opt);
+ diff_tree_combined_merge(commit, opt);
return !opt->loginfo;
}
diff --git a/submodule.c b/submodule.c
index 613857e..83b80fb 100644
--- a/submodule.c
+++ b/submodule.c
@@ -505,10 +505,13 @@ static void find_unpushed_submodule_commits(struct commit *commit,
struct rev_info rev;
init_revisions(&rev, NULL);
+ rev.ignore_merges = 0;
+ rev.combined_merges = 1;
+ rev.dense_combined_merges = 1;
rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
rev.diffopt.format_callback = collect_submodules_from_diff;
rev.diffopt.format_callback_data = needs_pushing;
- diff_tree_combined_merge(commit, 1, &rev);
+ diff_tree_combined_merge(commit, &rev);
}
int find_unpushed_submodules(unsigned char new_sha1[20],
--
1.9.rc2.232.gdd31389
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 7/9] Fold all merge diff variants into an enum
2014-02-04 22:17 [PATCH 0/9] remerge diff proof of concept/RFC Thomas Rast
` (5 preceding siblings ...)
2014-02-04 22:17 ` [PATCH 6/9] combine-diff: do not pass revs->dense_combined_merges redundantly Thomas Rast
@ 2014-02-04 22:17 ` Thomas Rast
2014-02-04 22:17 ` [PATCH 8/9] merge-recursive: allow storing conflict hunks in index Thomas Rast
` (2 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Thomas Rast @ 2014-02-04 22:17 UTC (permalink / raw)
To: git
The four ways of displaying merge diffs,
* none: no diff
* -m: against each parent
* -c: combined
* --cc: combined-condensed
were encoded in three flag bits in struct rev_info. Fold them all
into a single enum field that captures the variants.
This makes it easier to add new merge diff variants without yet more
special casing. It should also be slightly easier to read because one
does not have to ensure that the flag bits are set in an expected
combination.
Signed-off-by: Thomas Rast <tr@thomasrast.ch>
---
builtin/diff-files.c | 5 +++--
builtin/diff-tree.c | 2 +-
builtin/diff.c | 9 +++++----
builtin/fmt-merge-msg.c | 2 +-
builtin/log.c | 9 ++++-----
builtin/merge.c | 1 -
combine-diff.c | 2 +-
diff-lib.c | 7 ++++---
log-tree.c | 4 ++--
revision.c | 13 +++----------
revision.h | 22 +++++++++++++++++++---
submodule.c | 4 +---
12 files changed, 44 insertions(+), 36 deletions(-)
diff --git a/builtin/diff-files.c b/builtin/diff-files.c
index 9200069..172b50d 100644
--- a/builtin/diff-files.c
+++ b/builtin/diff-files.c
@@ -57,9 +57,10 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix)
* was not asked to. "diff-files -c -p" should not densify
* (the user should ask with "diff-files --cc" explicitly).
*/
- if (rev.max_count == -1 && !rev.combine_merges &&
+ if (rev.max_count == -1 &&
+ !merge_diff_mode_is_any_combined(&rev) &&
(rev.diffopt.output_format & DIFF_FORMAT_PATCH))
- rev.combine_merges = rev.dense_combined_merges = 1;
+ rev.merge_diff_mode = MERGE_DIFF_COMBINED_CONDENSED;
if (read_cache_preload(&rev.diffopt.pathspec) < 0) {
perror("read_cache_preload");
diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c
index be6417d..2950f80 100644
--- a/builtin/diff-tree.c
+++ b/builtin/diff-tree.c
@@ -96,7 +96,7 @@ static int diff_tree_stdin(char *line)
static void diff_tree_tweak_rev(struct rev_info *rev, struct setup_revision_opt *opt)
{
if (!rev->diffopt.output_format) {
- if (rev->dense_combined_merges)
+ if (rev->merge_diff_mode == MERGE_DIFF_COMBINED_CONDENSED)
rev->diffopt.output_format = DIFF_FORMAT_PATCH;
else
rev->diffopt.output_format = DIFF_FORMAT_RAW;
diff --git a/builtin/diff.c b/builtin/diff.c
index 47f663b..fd4c75f 100644
--- a/builtin/diff.c
+++ b/builtin/diff.c
@@ -192,8 +192,8 @@ static int builtin_diff_combined(struct rev_info *revs,
if (argc > 1)
usage(builtin_diff_usage);
- if (!revs->dense_combined_merges && !revs->combine_merges)
- revs->dense_combined_merges = revs->combine_merges = 1;
+ if (!merge_diff_mode_is_any_combined(revs))
+ revs->merge_diff_mode = MERGE_DIFF_COMBINED_CONDENSED;
for (i = 1; i < ents; i++)
sha1_array_append(&parents, ent[i].item->sha1);
diff_tree_combined(ent[0].item->sha1, &parents, revs);
@@ -242,9 +242,10 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv
* dense one, --cc can be explicitly asked for, or just rely
* on the default).
*/
- if (revs->max_count == -1 && !revs->combine_merges &&
+ if (revs->max_count == -1 &&
+ !merge_diff_mode_is_any_combined(revs) &&
(revs->diffopt.output_format & DIFF_FORMAT_PATCH))
- revs->combine_merges = revs->dense_combined_merges = 1;
+ revs->merge_diff_mode = MERGE_DIFF_COMBINED_CONDENSED;
setup_work_tree();
if (read_cache_preload(&revs->diffopt.pathspec) < 0) {
diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c
index 3906eda..2deeacd 100644
--- a/builtin/fmt-merge-msg.c
+++ b/builtin/fmt-merge-msg.c
@@ -637,7 +637,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
head = lookup_commit_or_die(head_sha1, "HEAD");
init_revisions(&rev, NULL);
rev.commit_format = CMIT_FMT_ONELINE;
- rev.ignore_merges = 1;
+ rev.merge_diff_mode = MERGE_DIFF_IGNORE;
rev.limited = 1;
strbuf_complete_line(out);
diff --git a/builtin/log.c b/builtin/log.c
index b97373d..cebebea 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -499,13 +499,12 @@ static int show_tree_object(const unsigned char *sha1,
static void show_rev_tweak_rev(struct rev_info *rev, struct setup_revision_opt *opt)
{
- if (rev->ignore_merges) {
+ if (!rev->merge_diff_mode) {
/* There was no "-m" on the command line */
- rev->ignore_merges = 0;
- if (!rev->first_parent_only && !rev->combine_merges) {
+ rev->merge_diff_mode = MERGE_DIFF_EACH;
+ if (!rev->first_parent_only) {
/* No "--first-parent", "-c", nor "--cc" */
- rev->combine_merges = 1;
- rev->dense_combined_merges = 1;
+ rev->merge_diff_mode = MERGE_DIFF_COMBINED_CONDENSED;
}
}
if (!rev->diffopt.output_format)
diff --git a/builtin/merge.c b/builtin/merge.c
index e576a7f..6977af7 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -343,7 +343,6 @@ static void squash_message(struct commit *commit, struct commit_list *remotehead
die_errno(_("Could not write to '%s'"), filename);
init_revisions(&rev, NULL);
- rev.ignore_merges = 1;
rev.commit_format = CMIT_FMT_MEDIUM;
commit->object.flags |= UNINTERESTING;
diff --git a/combine-diff.c b/combine-diff.c
index 6e80a73..3fae2dd 100644
--- a/combine-diff.c
+++ b/combine-diff.c
@@ -967,7 +967,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
struct userdiff_driver *textconv = NULL;
int is_binary;
const char *line_prefix = diff_line_prefix(opt);
- int dense = rev->dense_combined_merges;
+ int dense = (rev->merge_diff_mode == MERGE_DIFF_COMBINED_CONDENSED);
context = opt->context;
userdiff = userdiff_find_by_path(elem->path);
diff --git a/diff-lib.c b/diff-lib.c
index 8d0f572..e2700eb 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -173,7 +173,8 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
*/
i--;
- if (revs->combine_merges && num_compare_stages == 2) {
+ if (merge_diff_mode_is_any_combined(revs) &&
+ num_compare_stages == 2) {
show_combined_diff(dpath, 2, revs);
free(dpath);
continue;
@@ -316,7 +317,7 @@ static int show_modified(struct rev_info *revs,
return -1;
}
- if (revs->combine_merges && !cached &&
+ if (merge_diff_mode_is_any_combined(revs) && !cached &&
(hashcmp(sha1, old->sha1) || hashcmp(old->sha1, new->sha1))) {
struct combine_diff_path *p;
int pathlen = ce_namelen(new);
@@ -375,7 +376,7 @@ static void do_oneway_diff(struct unpack_trees_options *o,
* But with the revision flag parsing, that's found in
* "!revs->ignore_merges".
*/
- match_missing = !revs->ignore_merges;
+ match_missing = (revs->merge_diff_mode == MERGE_DIFF_EACH);
if (cached && idx && ce_stage(idx)) {
struct diff_filepair *pair;
diff --git a/log-tree.c b/log-tree.c
index 2fcca45..4ab3ffe 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -754,9 +754,9 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log
/* More than one parent? */
if (parents && parents->next) {
- if (opt->ignore_merges)
+ if (opt->merge_diff_mode == MERGE_DIFF_IGNORE)
return 0;
- else if (opt->combine_merges)
+ else if (merge_diff_mode_is_any_combined(opt))
return do_diff_combined(opt, commit);
else if (opt->first_parent_only) {
/*
diff --git a/revision.c b/revision.c
index 72255fb..3a1a810 100644
--- a/revision.c
+++ b/revision.c
@@ -1329,7 +1329,6 @@ void init_revisions(struct rev_info *revs, const char *prefix)
memset(revs, 0, sizeof(*revs));
revs->abbrev = DEFAULT_ABBREV;
- revs->ignore_merges = 1;
revs->simplify_history = 1;
DIFF_OPT_SET(&revs->pruning, RECURSIVE);
DIFF_OPT_SET(&revs->pruning, QUICK);
@@ -1809,15 +1808,11 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
DIFF_OPT_SET(&revs->diffopt, RECURSIVE);
DIFF_OPT_SET(&revs->diffopt, TREE_IN_RECURSIVE);
} else if (!strcmp(arg, "-m")) {
- revs->ignore_merges = 0;
+ revs->merge_diff_mode = MERGE_DIFF_EACH;
} else if (!strcmp(arg, "-c")) {
- revs->diff = 1;
- revs->dense_combined_merges = 0;
- revs->combine_merges = 1;
+ revs->merge_diff_mode = MERGE_DIFF_COMBINED;
} else if (!strcmp(arg, "--cc")) {
- revs->diff = 1;
- revs->dense_combined_merges = 1;
- revs->combine_merges = 1;
+ revs->merge_diff_mode = MERGE_DIFF_COMBINED_CONDENSED;
} else if (!strcmp(arg, "-v")) {
revs->verbose_header = 1;
} else if (!strcmp(arg, "--pretty")) {
@@ -2230,8 +2225,6 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
copy_pathspec(&revs->diffopt.pathspec,
&revs->prune_data);
}
- if (revs->combine_merges)
- revs->ignore_merges = 0;
revs->diffopt.abbrev = revs->abbrev;
if (revs->line_level_traverse) {
diff --git a/revision.h b/revision.h
index 3111228..2ec596f 100644
--- a/revision.h
+++ b/revision.h
@@ -51,6 +51,17 @@ struct rev_cmdline_info {
#define REVISION_WALK_NO_WALK_SORTED 1
#define REVISION_WALK_NO_WALK_UNSORTED 2
+enum merge_diff_mode {
+ /* default: do not show diffs for merge */
+ MERGE_DIFF_IGNORE = 0,
+ /* diff against each side (-m) */
+ MERGE_DIFF_EACH,
+ /* combined format (-c) */
+ MERGE_DIFF_COMBINED,
+ /* combined-condensed format (-cc) */
+ MERGE_DIFF_COMBINED_CONDENSED
+};
+
struct rev_info {
/* Starting list */
struct commit_list *commits;
@@ -117,11 +128,10 @@ struct rev_info {
show_root_diff:1,
no_commit_id:1,
verbose_header:1,
- ignore_merges:1,
- combine_merges:1,
- dense_combined_merges:1,
always_show_header:1;
+ enum merge_diff_mode merge_diff_mode;
+
/* Format info */
unsigned int shown_one:1,
shown_dashes:1,
@@ -199,6 +209,12 @@ struct rev_info {
struct saved_parents *saved_parents_slab;
};
+static inline int merge_diff_mode_is_any_combined(struct rev_info *revs)
+{
+ return (revs->merge_diff_mode == MERGE_DIFF_COMBINED ||
+ revs->merge_diff_mode == MERGE_DIFF_COMBINED_CONDENSED);
+}
+
extern int ref_excluded(struct string_list *, const char *path);
void clear_ref_exclusion(struct string_list **);
void add_ref_exclusion(struct string_list **, const char *exclude);
diff --git a/submodule.c b/submodule.c
index 83b80fb..38973f2 100644
--- a/submodule.c
+++ b/submodule.c
@@ -505,9 +505,7 @@ static void find_unpushed_submodule_commits(struct commit *commit,
struct rev_info rev;
init_revisions(&rev, NULL);
- rev.ignore_merges = 0;
- rev.combined_merges = 1;
- rev.dense_combined_merges = 1;
+ rev.merge_diff_mode = MERGE_DIFF_COMBINED_CONDENSED;
rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
rev.diffopt.format_callback = collect_submodules_from_diff;
rev.diffopt.format_callback_data = needs_pushing;
--
1.9.rc2.232.gdd31389
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 8/9] merge-recursive: allow storing conflict hunks in index
2014-02-04 22:17 [PATCH 0/9] remerge diff proof of concept/RFC Thomas Rast
` (6 preceding siblings ...)
2014-02-04 22:17 ` [PATCH 7/9] Fold all merge diff variants into an enum Thomas Rast
@ 2014-02-04 22:17 ` Thomas Rast
2014-02-04 22:17 ` [RFC PATCH 9/9] log --remerge-diff: show what the conflict resolution changed Thomas Rast
2014-02-04 23:04 ` [PATCH 0/9] remerge diff proof of concept/RFC Junio C Hamano
9 siblings, 0 replies; 11+ messages in thread
From: Thomas Rast @ 2014-02-04 22:17 UTC (permalink / raw)
To: git
Add a --conflicts-in-index option to merge-recursive, which instructs
it to always store the 3-way merged result in the index. (Normally it
only does so in recursive invocations, but not for the final result.)
This serves as a building block for the "remerge diff" feature coming
up in a subsequent patch. The external option lets us easily use it
from tests, where we'd otherwise need a new test-* helper to access
the feature.
Furthermore, it might occasionally be useful for scripts that want to
look at the result of invoking git-merge without tampering with the
worktree. They could already get the _conflicts_ with --index-only,
but not (conveniently) the conflict-hunk formatted files that would
normally be written to the worktree.
Signed-off-by: Thomas Rast <tr@thomasrast.ch>
---
Documentation/merge-strategies.txt | 5 +++++
merge-recursive.c | 4 ++++
merge-recursive.h | 1 +
t/t3030-merge-recursive.sh | 20 ++++++++++++++++++++
4 files changed, 30 insertions(+)
diff --git a/Documentation/merge-strategies.txt b/Documentation/merge-strategies.txt
index 2934e99..3468d99 100644
--- a/Documentation/merge-strategies.txt
+++ b/Documentation/merge-strategies.txt
@@ -96,6 +96,11 @@ index-only;;
Write the merge result only to the index; do not touch the
worktree.
+conflicts-in-index;;
+ For conflicted files, write 3-way merged contents with
+ conflict hunks to the index, instead of leaving their entries
+ unresolved.
+
octopus::
This resolves cases with more than two heads, but refuses to do
a complex merge that needs manual resolution. It is
diff --git a/merge-recursive.c b/merge-recursive.c
index f59c1d3..b682812 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -724,6 +724,8 @@ static void update_file_flags(struct merge_options *o,
int update_cache,
int update_wd)
{
+ if (o->conflicts_in_index)
+ update_cache = 1;
if (o->call_depth || o->no_worktree)
update_wd = 0;
@@ -2098,6 +2100,8 @@ int parse_merge_opt(struct merge_options *o, const char *s)
}
else if (!strcmp(s, "index-only"))
o->no_worktree = 1;
+ else if (!strcmp(s, "conflicts-in-index"))
+ o->conflicts_in_index = 1;
else
return -1;
return 0;
diff --git a/merge-recursive.h b/merge-recursive.h
index d8dd7a1..9b8e20b 100644
--- a/merge-recursive.h
+++ b/merge-recursive.h
@@ -16,6 +16,7 @@ struct merge_options {
unsigned buffer_output : 1;
unsigned renormalize : 1;
unsigned no_worktree : 1; /* do not touch worktree */
+ unsigned conflicts_in_index : 1; /* index will contain conflict hunks */
long xdl_opts;
int verbosity;
int diff_rename_limit;
diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh
index 2f3a16c..4192fd3 100755
--- a/t/t3030-merge-recursive.sh
+++ b/t/t3030-merge-recursive.sh
@@ -309,6 +309,26 @@ test_expect_success 'merge-recursive --index-only' '
test_cmp expected-diff actual-diff
'
+test_expect_success 'merge-recursive --index-only --conflicts-in-index' '
+ # first pass: do a merge as usual to obtain "expected"
+ rm -fr [abcd] &&
+ git checkout -f "$c2" &&
+ test_expect_code 1 git merge-recursive "$c0" -- "$c2" "$c1" &&
+ git add [abcd] &&
+ git ls-files -s >expected &&
+ # second pass: actual test
+ rm -fr [abcd] &&
+ git checkout -f "$c2" &&
+ test_expect_code 1 \
+ git merge-recursive --index-only --conflicts-in-index \
+ "$c0" -- "$c2" "$c1" &&
+ git ls-files -s >actual &&
+ test_cmp expected actual &&
+ git diff HEAD >actual-diff &&
+ : >expected-diff &&
+ test_cmp expected-diff actual-diff
+'
+
test_expect_success 'fail if the index has unresolved entries' '
rm -fr [abcd] &&
--
1.9.rc2.232.gdd31389
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [RFC PATCH 9/9] log --remerge-diff: show what the conflict resolution changed
2014-02-04 22:17 [PATCH 0/9] remerge diff proof of concept/RFC Thomas Rast
` (7 preceding siblings ...)
2014-02-04 22:17 ` [PATCH 8/9] merge-recursive: allow storing conflict hunks in index Thomas Rast
@ 2014-02-04 22:17 ` Thomas Rast
2014-02-04 23:04 ` [PATCH 0/9] remerge diff proof of concept/RFC Junio C Hamano
9 siblings, 0 replies; 11+ messages in thread
From: Thomas Rast @ 2014-02-04 22:17 UTC (permalink / raw)
To: git
Git has --cc as a very fast inspection tool that shows a brief summary
of what a conflicted merge "looks like", and -c/-m as "give me the
full information" data dumps.
But --cc actually loses information: if the merge lost(!) some changes
from one side, that hunk would fully agree with the other side, and
therefore be elided. So --cc cannot be used to investigate mismerges.
Indeed it is rather hard to find a merge that has lost changes, unless
one knows where to look.
The new option --remerge-diff is an attempt at filling this gap,
admittedly at the cost of a lot of CPU cycles. For each merge commit,
it diffs the merge result against a recursive merge of the merge's
parents.
For files that can be auto-merged cleanly, it will typically show
nothing. However, it will make it obvious when the merge introduces
extra changes.
For files that result in merge conflicts, we diff against the
representation with conflict hunks (what the user would usually see in
the worktree). So the diff will show what was changed in the conflict
hunks to resolve the conflict.
It still takes a bit of staring to tell an evil from a regular merge.
But at least the information is there, unlike with --cc; and the
output is usually much shorter than with -c.
Signed-off-by: Thomas Rast <tr@thomasrast.ch>
---
Documentation/rev-list-options.txt | 7 ++
log-tree.c | 60 +++++++++++++++
merge-recursive.c | 3 +-
merge-recursive.h | 1 +
revision.c | 2 +
revision.h | 4 +-
t/t4202-log.sh | 3 +
t/t4213-log-remerge-diff.sh | 145 +++++++++++++++++++++++++++++++++++++
8 files changed, 223 insertions(+), 2 deletions(-)
create mode 100755 t/t4213-log-remerge-diff.sh
diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt
index d023290..6611557 100644
--- a/Documentation/rev-list-options.txt
+++ b/Documentation/rev-list-options.txt
@@ -797,6 +797,13 @@ options may be given. See linkgit:git-diff-files[1] for more options.
in that case, the output represents the changes the merge
brought _into_ the then-current branch.
+--remerge-diff::
+ Diff merge commits against a recursive merge of their parents,
+ with conflict hunks. Intuitively speaking, this shows what
+ the author of the merge changed to resolve the merge. It
+ assumes that all (or most) merges are recursive merges; other
+ strategies are not supported.
+
-r::
Show recursive diffs.
diff --git a/log-tree.c b/log-tree.c
index 4ab3ffe..fa22737 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -11,6 +11,8 @@
#include "gpg-interface.h"
#include "sequencer.h"
#include "line-log.h"
+#include "cache-tree.h"
+#include "merge-recursive.h"
struct decoration name_decoration = { "object names" };
@@ -725,6 +727,62 @@ static int do_diff_combined(struct rev_info *opt, struct commit *commit)
return !opt->loginfo;
}
+static int do_diff_remerge(struct rev_info *opt, struct commit *commit)
+{
+ struct commit_list *merge_bases;
+ struct commit *result, *parent1, *parent2;
+ struct merge_options o;
+ char *branch1, *branch2;
+
+ if (commit->parents->next->next) {
+ printf("--remerge-diff not supported for octopus merges.\n");
+ return 0;
+ }
+
+ parent1 = commit->parents->item;
+ parent2 = commit->parents->next->item;
+ parse_commit(parent1);
+ parse_commit(parent2);
+ branch1 = xstrdup(sha1_to_hex(parent1->object.sha1));
+ branch2 = xstrdup(sha1_to_hex(parent2->object.sha1));
+
+ merge_bases = get_octopus_merge_bases(commit->parents);
+ init_merge_options(&o);
+ o.verbosity = -1;
+ o.no_worktree = 1;
+ o.conflicts_in_index = 1;
+ o.use_ondisk_index = 0;
+ o.branch1 = branch1;
+ o.branch2 = branch2;
+ merge_recursive(&o, parent1, parent2, merge_bases, &result);
+ free(branch1);
+ free(branch2);
+
+ active_cache_tree = cache_tree();
+ if (cache_tree_update(active_cache_tree,
+ (const struct cache_entry * const *)active_cache,
+ active_nr, WRITE_TREE_SILENT) < 0) {
+ printf("BUG: merge conflicts not fully folded, cannot diff.\n");
+ return 0;
+ }
+
+ if (opt->loginfo && !opt->no_commit_id) {
+ show_log(opt);
+
+ if (opt->verbose_header && opt->diffopt.output_format)
+ printf("%s%c", diff_line_prefix(&opt->diffopt),
+ opt->diffopt.line_termination);
+ }
+
+ diff_tree_sha1(active_cache_tree->sha1, commit->tree->object.sha1,
+ "", &opt->diffopt);
+ log_tree_diff_flush(opt);
+
+ cache_tree_free(&active_cache_tree);
+
+ return !opt->loginfo;
+}
+
/*
* Show the diff of a commit.
*
@@ -758,6 +816,8 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log
return 0;
else if (merge_diff_mode_is_any_combined(opt))
return do_diff_combined(opt, commit);
+ else if (opt->merge_diff_mode == MERGE_DIFF_REMERGE)
+ return do_diff_remerge(opt, commit);
else if (opt->first_parent_only) {
/*
* Generate merge log entry only for the first
diff --git a/merge-recursive.c b/merge-recursive.c
index b682812..1507a7a 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -1947,7 +1947,7 @@ int merge_recursive(struct merge_options *o,
}
discard_cache();
- if (!o->call_depth)
+ if (!o->call_depth && o->use_ondisk_index)
read_cache();
o->ancestor = "merged common ancestors";
@@ -2043,6 +2043,7 @@ void init_merge_options(struct merge_options *o)
o->diff_rename_limit = -1;
o->merge_rename_limit = -1;
o->renormalize = 0;
+ o->use_ondisk_index = 1;
git_config(merge_recursive_config, o);
if (getenv("GIT_MERGE_VERBOSITY"))
o->verbosity =
diff --git a/merge-recursive.h b/merge-recursive.h
index 9b8e20b..d7466c7 100644
--- a/merge-recursive.h
+++ b/merge-recursive.h
@@ -17,6 +17,7 @@ struct merge_options {
unsigned renormalize : 1;
unsigned no_worktree : 1; /* do not touch worktree */
unsigned conflicts_in_index : 1; /* index will contain conflict hunks */
+ unsigned use_ondisk_index : 1; /* tree-level merge loads .git/index */
long xdl_opts;
int verbosity;
int diff_rename_limit;
diff --git a/revision.c b/revision.c
index 3a1a810..bfdf91d 100644
--- a/revision.c
+++ b/revision.c
@@ -1813,6 +1813,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
revs->merge_diff_mode = MERGE_DIFF_COMBINED;
} else if (!strcmp(arg, "--cc")) {
revs->merge_diff_mode = MERGE_DIFF_COMBINED_CONDENSED;
+ } else if (!strcmp(arg, "--remerge-diff")) {
+ revs->merge_diff_mode = MERGE_DIFF_REMERGE;
} else if (!strcmp(arg, "-v")) {
revs->verbose_header = 1;
} else if (!strcmp(arg, "--pretty")) {
diff --git a/revision.h b/revision.h
index 2ec596f..419bbd9 100644
--- a/revision.h
+++ b/revision.h
@@ -59,7 +59,9 @@ enum merge_diff_mode {
/* combined format (-c) */
MERGE_DIFF_COMBINED,
/* combined-condensed format (-cc) */
- MERGE_DIFF_COMBINED_CONDENSED
+ MERGE_DIFF_COMBINED_CONDENSED,
+ /* --remerge-diff */
+ MERGE_DIFF_REMERGE
};
struct rev_info {
diff --git a/t/t4202-log.sh b/t/t4202-log.sh
index 64f34a6..bd3f536 100755
--- a/t/t4202-log.sh
+++ b/t/t4202-log.sh
@@ -872,4 +872,7 @@ test_expect_success '--merge-bases' '
test_cmp expect actual
'
+cat >expect <<\EOF
+EOF
+
test_done
diff --git a/t/t4213-log-remerge-diff.sh b/t/t4213-log-remerge-diff.sh
new file mode 100755
index 0000000..ca6bbde
--- /dev/null
+++ b/t/t4213-log-remerge-diff.sh
@@ -0,0 +1,145 @@
+#!/bin/sh
+
+test_description='test log --remerge-diff'
+. ./test-lib.sh
+
+# A ----------
+# | \ \ \
+# | C \ \
+# B |\ \ |
+# | | | D unrelated_file
+# |\ | |__|__ |
+# | X |_ | \ |
+# |/ \/ \| \ |
+# M1 M2 M3 M4
+# ^ ^ ^ ^
+# | | dm unrelated
+# | evil
+# benign
+#
+#
+# M1 has a "benign" conflict
+# M2 has an "evil" conflict: it ignores the changes in D
+# M3 has a delete/modify conflict, resolved in favor of a modification
+# M4 is a merge of an unrelated change, without conflicts
+
+test_expect_success 'setup' '
+ test_commit A file original &&
+ test_commit B file change &&
+ git checkout -b side A &&
+ test_commit C file side &&
+ git checkout -b delete A &&
+ git rm file &&
+ test_commit D &&
+ git checkout -b benign master &&
+ test_must_fail git merge C &&
+ test_commit M1 file merged &&
+ git checkout -b evil B &&
+ test_must_fail git merge C &&
+ test_commit M2 file change &&
+ git checkout -b dm C &&
+ test_must_fail git merge D &&
+ test_commit M3 file resolved &&
+ git checkout -b unrelated A &&
+ test_commit unrelated_file &&
+ git merge C &&
+ test_tick &&
+ git tag M4 &&
+ git branch -D master side
+'
+
+test_expect_success 'unrelated merge: without conflicts' '
+ git log -p --cc unrelated >expected &&
+ git log -p --remerge-diff unrelated >actual &&
+ test_cmp expected actual
+'
+
+clean_output () {
+ git name-rev --name-only --stdin |
+ # strip away bits that aren't treated by the above
+ sed -e 's/^\(index\|Merge:\|Date:\).*/\1/'
+}
+
+cat >expected <<EOF
+commit benign
+Merge:
+Author: A U Thor <author@example.com>
+Date:
+
+ M1
+
+diff --git a/file b/file
+index
+--- a/file
++++ b/file
+@@ -1,5 +1 @@
+-<<<<<<< tags/B
+-change
+-=======
+-side
+->>>>>>> tags/C
++merged
+EOF
+
+test_expect_success 'benign merge: conflicts resolved' '
+ git log -1 -p --remerge-diff benign >output &&
+ clean_output <output >actual &&
+ test_cmp expected actual
+'
+
+cat >expected <<EOF
+commit evil
+Merge:
+Author: A U Thor <author@example.com>
+Date:
+
+ M2
+
+diff --git a/file b/file
+index
+--- a/file
++++ b/file
+@@ -1,5 +1 @@
+-<<<<<<< tags/B
+ change
+-=======
+-side
+->>>>>>> tags/C
+EOF
+
+test_expect_success 'evil merge: changes ignored' '
+ git log -1 --remerge-diff -p evil >output &&
+ clean_output <output >actual &&
+ test_cmp expected actual
+'
+
+cat >expected <<EOF
+commit dm
+Merge:
+Author: A U Thor <author@example.com>
+Date:
+
+ M3
+
+diff --git a/file b/file
+index
+--- a/file
++++ b/file
+@@ -1,5 +1 @@
+-<<<<<<< tags/B
+ change
+-=======
+->>>>>>> tags/D
++resolved
+EOF
+
+# The above just one idea what the output might be. It's not clear
+# yet what the best solution is.
+
+test_expect_failure 'delete/modify conflict' '
+ git log -1 --remerge-diff -p dm >output &&
+ clean_output <output >actual &&
+ test_cmp expected actual
+'
+
+test_done
--
1.9.rc2.232.gdd31389
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH 0/9] remerge diff proof of concept/RFC
2014-02-04 22:17 [PATCH 0/9] remerge diff proof of concept/RFC Thomas Rast
` (8 preceding siblings ...)
2014-02-04 22:17 ` [RFC PATCH 9/9] log --remerge-diff: show what the conflict resolution changed Thomas Rast
@ 2014-02-04 23:04 ` Junio C Hamano
9 siblings, 0 replies; 11+ messages in thread
From: Junio C Hamano @ 2014-02-04 23:04 UTC (permalink / raw)
To: Thomas Rast; +Cc: git
Thomas Rast <tr@thomasrast.ch> writes:
> log --remerge-diff: show what the conflict resolution changed
Yay ;-)
> This series explores another angle, which I call "remerge diff". It
> works by re-doing the merge in core, using features from patches 1-3
> and 7. Likely that will result in conflicts, which are formatted in
> the usual <<<<<<< way. Then it diffs this "remerge" against the
> merge's tree that is recorded in history.
Yup, that is the obvious way to recreate what would have been shown
to the person who recorded the merge result. I like that approach.
> So I would welcome comments, and/or better ideas, on the following
> proposed resolution:
>
> * Implement what I described last, to take care of delete/modify
> conflicts.
Naively, I would have thought that
- If the recorded result of the merge does not have the path, then
show the deletion of the contents relative to the side that
modified it.
- If the recorded result of the merge has the path, then show the
change between the side that modified it and the recorded result.
might be more useful. Then we will know what we lost in full (if
the recorded result is to "delete" it), but it is very likely that
we won't see anything if the recorded result blindly took what the
modifying side left. Comparing with the synthetic stages 2+3 will
at least show us there was something funky going on, so your
approach would be reasonable in this case.
> * Punt on more complex conflicts, by removing those files from the
> index, and emitting a warning about those files instead.
Sensible.
^ permalink raw reply [flat|nested] 11+ messages in thread