Skip to content

Commit 90b7153

Browse files
committed
Merge branch 'en/remerge-diff'
"git log --remerge-diff" shows the difference from mechanical merge result and the result that is actually recorded in a merge commit. * en/remerge-diff: diff-merges: avoid history simplifications when diffing merges merge-ort: mark conflict/warning messages from inner merges as omittable show, log: include conflict/warning messages in --remerge-diff headers diff: add ability to insert additional headers for paths merge-ort: format messages slightly different for use in headers merge-ort: mark a few more conflict messages as omittable merge-ort: capture and print ll-merge warnings in our preferred fashion ll-merge: make callers responsible for showing warnings log: clean unneeded objects during `log --remerge-diff` show, log: provide a --remerge-diff capability
2 parents 3423051 + 0dec322 commit 90b7153

23 files changed

+727
-48
lines changed

Documentation/diff-options.txt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ endif::git-diff[]
3434
endif::git-format-patch[]
3535

3636
ifdef::git-log[]
37-
--diff-merges=(off|none|on|first-parent|1|separate|m|combined|c|dense-combined|cc)::
37+
--diff-merges=(off|none|on|first-parent|1|separate|m|combined|c|dense-combined|cc|remerge|r)::
3838
--no-diff-merges::
3939
Specify diff format to be used for merge commits. Default is
4040
{diff-merges-default} unless `--first-parent` is in use, in which case
@@ -64,6 +64,18 @@ ifdef::git-log[]
6464
each of the parents. Separate log entry and diff is generated
6565
for each parent.
6666
+
67+
--diff-merges=remerge:::
68+
--diff-merges=r:::
69+
--remerge-diff:::
70+
With this option, two-parent merge commits are remerged to
71+
create a temporary tree object -- potentially containing files
72+
with conflict markers and such. A diff is then shown between
73+
that temporary tree and the actual merge commit.
74+
+
75+
The output emitted when this option is used is subject to change, and
76+
so is its interaction with other options (unless explicitly
77+
documented).
78+
+
6779
--diff-merges=combined:::
6880
--diff-merges=c:::
6981
-c:::

apply.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3494,7 +3494,7 @@ static int three_way_merge(struct apply_state *state,
34943494
{
34953495
mmfile_t base_file, our_file, their_file;
34963496
mmbuffer_t result = { NULL };
3497-
int status;
3497+
enum ll_merge_result status;
34983498

34993499
/* resolve trivial cases first */
35003500
if (oideq(base, ours))
@@ -3511,6 +3511,9 @@ static int three_way_merge(struct apply_state *state,
35113511
&their_file, "theirs",
35123512
state->repo->index,
35133513
NULL);
3514+
if (status == LL_MERGE_BINARY_CONFLICT)
3515+
warning("Cannot merge binary files: %s (%s vs. %s)",
3516+
path, "ours", "theirs");
35143517
free(base_file.ptr);
35153518
free(our_file.ptr);
35163519
free(their_file.ptr);

builtin/checkout.c

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ static int checkout_merged(int pos, const struct checkout *state,
246246
struct cache_entry *ce = active_cache[pos];
247247
const char *path = ce->name;
248248
mmfile_t ancestor, ours, theirs;
249+
enum ll_merge_result merge_status;
249250
int status;
250251
struct object_id oid;
251252
mmbuffer_t result_buf;
@@ -276,13 +277,16 @@ static int checkout_merged(int pos, const struct checkout *state,
276277
memset(&ll_opts, 0, sizeof(ll_opts));
277278
git_config_get_bool("merge.renormalize", &renormalize);
278279
ll_opts.renormalize = renormalize;
279-
status = ll_merge(&result_buf, path, &ancestor, "base",
280-
&ours, "ours", &theirs, "theirs",
281-
state->istate, &ll_opts);
280+
merge_status = ll_merge(&result_buf, path, &ancestor, "base",
281+
&ours, "ours", &theirs, "theirs",
282+
state->istate, &ll_opts);
282283
free(ancestor.ptr);
283284
free(ours.ptr);
284285
free(theirs.ptr);
285-
if (status < 0 || !result_buf.ptr) {
286+
if (merge_status == LL_MERGE_BINARY_CONFLICT)
287+
warning("Cannot merge binary files: %s (%s vs. %s)",
288+
path, "ours", "theirs");
289+
if (merge_status < 0 || !result_buf.ptr) {
286290
free(result_buf.ptr);
287291
return error(_("path '%s': cannot merge"), path);
288292
}

builtin/log.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include "repository.h"
3636
#include "commit-reach.h"
3737
#include "range-diff.h"
38+
#include "tmp-objdir.h"
3839

3940
#define MAIL_DEFAULT_WRAP 72
4041
#define COVER_FROM_AUTO_MAX_SUBJECT_LEN 100
@@ -422,6 +423,13 @@ static int cmd_log_walk(struct rev_info *rev)
422423
int saved_nrl = 0;
423424
int saved_dcctc = 0;
424425

426+
if (rev->remerge_diff) {
427+
rev->remerge_objdir = tmp_objdir_create("remerge-diff");
428+
if (!rev->remerge_objdir)
429+
die(_("unable to create temporary object directory"));
430+
tmp_objdir_replace_primary_odb(rev->remerge_objdir, 1);
431+
}
432+
425433
if (rev->early_output)
426434
setup_early_output();
427435

@@ -464,6 +472,11 @@ static int cmd_log_walk(struct rev_info *rev)
464472
rev->diffopt.no_free = 0;
465473
diff_free(&rev->diffopt);
466474

475+
if (rev->remerge_diff) {
476+
tmp_objdir_destroy(rev->remerge_objdir);
477+
rev->remerge_objdir = NULL;
478+
}
479+
467480
if (rev->diffopt.output_format & DIFF_FORMAT_CHECKDIFF &&
468481
rev->diffopt.flags.check_failed) {
469482
return 02;
@@ -1958,6 +1971,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
19581971
die(_("--name-status does not make sense"));
19591972
if (rev.diffopt.output_format & DIFF_FORMAT_CHECKDIFF)
19601973
die(_("--check does not make sense"));
1974+
if (rev.remerge_diff)
1975+
die(_("--remerge-diff does not make sense"));
19611976

19621977
if (!use_patch_format &&
19631978
(!rev.diffopt.output_format ||

diff-merges.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@ static void suppress(struct rev_info *revs)
1717
revs->combined_all_paths = 0;
1818
revs->merges_imply_patch = 0;
1919
revs->merges_need_diff = 0;
20+
revs->remerge_diff = 0;
2021
}
2122

2223
static void set_separate(struct rev_info *revs)
2324
{
2425
suppress(revs);
2526
revs->separate_merges = 1;
27+
revs->simplify_history = 0;
2628
}
2729

2830
static void set_first_parent(struct rev_info *revs)
@@ -45,6 +47,13 @@ static void set_dense_combined(struct rev_info *revs)
4547
revs->dense_combined_merges = 1;
4648
}
4749

50+
static void set_remerge_diff(struct rev_info *revs)
51+
{
52+
suppress(revs);
53+
revs->remerge_diff = 1;
54+
revs->simplify_history = 0;
55+
}
56+
4857
static diff_merges_setup_func_t func_by_opt(const char *optarg)
4958
{
5059
if (!strcmp(optarg, "off") || !strcmp(optarg, "none"))
@@ -57,6 +66,8 @@ static diff_merges_setup_func_t func_by_opt(const char *optarg)
5766
return set_combined;
5867
else if (!strcmp(optarg, "cc") || !strcmp(optarg, "dense-combined"))
5968
return set_dense_combined;
69+
else if (!strcmp(optarg, "r") || !strcmp(optarg, "remerge"))
70+
return set_remerge_diff;
6071
else if (!strcmp(optarg, "m") || !strcmp(optarg, "on"))
6172
return set_to_default;
6273
return NULL;
@@ -110,6 +121,9 @@ int diff_merges_parse_opts(struct rev_info *revs, const char **argv)
110121
} else if (!strcmp(arg, "--cc")) {
111122
set_dense_combined(revs);
112123
revs->merges_imply_patch = 1;
124+
} else if (!strcmp(arg, "--remerge-diff")) {
125+
set_remerge_diff(revs);
126+
revs->merges_imply_patch = 1;
113127
} else if (!strcmp(arg, "--no-diff-merges")) {
114128
suppress(revs);
115129
} else if (!strcmp(arg, "--combined-all-paths")) {

diff.c

Lines changed: 120 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "help.h"
2929
#include "promisor-remote.h"
3030
#include "dir.h"
31+
#include "strmap.h"
3132

3233
#ifdef NO_FAST_WORKING_DIRECTORY
3334
#define FAST_WORKING_DIRECTORY 0
@@ -3353,6 +3354,31 @@ struct userdiff_driver *get_textconv(struct repository *r,
33533354
return userdiff_get_textconv(r, one->driver);
33543355
}
33553356

3357+
static struct strbuf *additional_headers(struct diff_options *o,
3358+
const char *path)
3359+
{
3360+
if (!o->additional_path_headers)
3361+
return NULL;
3362+
return strmap_get(o->additional_path_headers, path);
3363+
}
3364+
3365+
static void add_formatted_headers(struct strbuf *msg,
3366+
struct strbuf *more_headers,
3367+
const char *line_prefix,
3368+
const char *meta,
3369+
const char *reset)
3370+
{
3371+
char *next, *newline;
3372+
3373+
for (next = more_headers->buf; *next; next = newline) {
3374+
newline = strchrnul(next, '\n');
3375+
strbuf_addf(msg, "%s%s%.*s%s\n", line_prefix, meta,
3376+
(int)(newline - next), next, reset);
3377+
if (*newline)
3378+
newline++;
3379+
}
3380+
}
3381+
33563382
static void builtin_diff(const char *name_a,
33573383
const char *name_b,
33583384
struct diff_filespec *one,
@@ -3411,6 +3437,17 @@ static void builtin_diff(const char *name_a,
34113437
b_two = quote_two(b_prefix, name_b + (*name_b == '/'));
34123438
lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
34133439
lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
3440+
if (!DIFF_FILE_VALID(one) && !DIFF_FILE_VALID(two)) {
3441+
/*
3442+
* We should only reach this point for pairs from
3443+
* create_filepairs_for_header_only_notifications(). For
3444+
* these, we should avoid the "/dev/null" special casing
3445+
* above, meaning we avoid showing such pairs as either
3446+
* "new file" or "deleted file" below.
3447+
*/
3448+
lbl[0] = a_one;
3449+
lbl[1] = b_two;
3450+
}
34143451
strbuf_addf(&header, "%s%sdiff --git %s %s%s\n", line_prefix, meta, a_one, b_two, reset);
34153452
if (lbl[0][0] == '/') {
34163453
/* /dev/null */
@@ -4275,6 +4312,7 @@ static void fill_metainfo(struct strbuf *msg,
42754312
const char *set = diff_get_color(use_color, DIFF_METAINFO);
42764313
const char *reset = diff_get_color(use_color, DIFF_RESET);
42774314
const char *line_prefix = diff_line_prefix(o);
4315+
struct strbuf *more_headers = NULL;
42784316

42794317
*must_show_header = 1;
42804318
strbuf_init(msg, PATH_MAX * 2 + 300);
@@ -4311,6 +4349,11 @@ static void fill_metainfo(struct strbuf *msg,
43114349
default:
43124350
*must_show_header = 0;
43134351
}
4352+
if ((more_headers = additional_headers(o, name))) {
4353+
add_formatted_headers(msg, more_headers,
4354+
line_prefix, set, reset);
4355+
*must_show_header = 1;
4356+
}
43144357
if (one && two && !oideq(&one->oid, &two->oid)) {
43154358
const unsigned hexsz = the_hash_algo->hexsz;
43164359
int abbrev = o->abbrev ? o->abbrev : DEFAULT_ABBREV;
@@ -5803,12 +5846,27 @@ int diff_unmodified_pair(struct diff_filepair *p)
58035846

58045847
static void diff_flush_patch(struct diff_filepair *p, struct diff_options *o)
58055848
{
5806-
if (diff_unmodified_pair(p))
5849+
int include_conflict_headers =
5850+
(additional_headers(o, p->one->path) &&
5851+
(!o->filter || filter_bit_tst(DIFF_STATUS_UNMERGED, o)));
5852+
5853+
/*
5854+
* Check if we can return early without showing a diff. Note that
5855+
* diff_filepair only stores {oid, path, mode, is_valid}
5856+
* information for each path, and thus diff_unmodified_pair() only
5857+
* considers those bits of info. However, we do not want pairs
5858+
* created by create_filepairs_for_header_only_notifications()
5859+
* (which always look like unmodified pairs) to be ignored, so
5860+
* return early if both p is unmodified AND we don't want to
5861+
* include_conflict_headers.
5862+
*/
5863+
if (diff_unmodified_pair(p) && !include_conflict_headers)
58075864
return;
58085865

5866+
/* Actually, we can also return early to avoid showing tree diffs */
58095867
if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
58105868
(DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
5811-
return; /* no tree diffs in patch format */
5869+
return;
58125870

58135871
run_diff(p, o);
58145872
}
@@ -5839,10 +5897,17 @@ static void diff_flush_checkdiff(struct diff_filepair *p,
58395897
run_checkdiff(p, o);
58405898
}
58415899

5842-
int diff_queue_is_empty(void)
5900+
int diff_queue_is_empty(struct diff_options *o)
58435901
{
58445902
struct diff_queue_struct *q = &diff_queued_diff;
58455903
int i;
5904+
int include_conflict_headers =
5905+
(o->additional_path_headers &&
5906+
(!o->filter || filter_bit_tst(DIFF_STATUS_UNMERGED, o)));
5907+
5908+
if (include_conflict_headers)
5909+
return 0;
5910+
58465911
for (i = 0; i < q->nr; i++)
58475912
if (!diff_unmodified_pair(q->queue[i]))
58485913
return 0;
@@ -6276,6 +6341,54 @@ void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc)
62766341
warning(_(rename_limit_advice), varname, needed);
62776342
}
62786343

6344+
static void create_filepairs_for_header_only_notifications(struct diff_options *o)
6345+
{
6346+
struct strset present;
6347+
struct diff_queue_struct *q = &diff_queued_diff;
6348+
struct hashmap_iter iter;
6349+
struct strmap_entry *e;
6350+
int i;
6351+
6352+
strset_init_with_options(&present, /*pool*/ NULL, /*strdup*/ 0);
6353+
6354+
/*
6355+
* Find out which paths exist in diff_queued_diff, preferring
6356+
* one->path for any pair that has multiple paths.
6357+
*/
6358+
for (i = 0; i < q->nr; i++) {
6359+
struct diff_filepair *p = q->queue[i];
6360+
char *path = p->one->path ? p->one->path : p->two->path;
6361+
6362+
if (strmap_contains(o->additional_path_headers, path))
6363+
strset_add(&present, path);
6364+
}
6365+
6366+
/*
6367+
* Loop over paths in additional_path_headers; for each NOT already
6368+
* in diff_queued_diff, create a synthetic filepair and insert that
6369+
* into diff_queued_diff.
6370+
*/
6371+
strmap_for_each_entry(o->additional_path_headers, &iter, e) {
6372+
if (!strset_contains(&present, e->key)) {
6373+
struct diff_filespec *one, *two;
6374+
struct diff_filepair *p;
6375+
6376+
one = alloc_filespec(e->key);
6377+
two = alloc_filespec(e->key);
6378+
fill_filespec(one, null_oid(), 0, 0);
6379+
fill_filespec(two, null_oid(), 0, 0);
6380+
p = diff_queue(q, one, two);
6381+
p->status = DIFF_STATUS_MODIFIED;
6382+
}
6383+
}
6384+
6385+
/* Re-sort the filepairs */
6386+
diffcore_fix_diff_index();
6387+
6388+
/* Cleanup */
6389+
strset_clear(&present);
6390+
}
6391+
62796392
static void diff_flush_patch_all_file_pairs(struct diff_options *o)
62806393
{
62816394
int i;
@@ -6288,6 +6401,9 @@ static void diff_flush_patch_all_file_pairs(struct diff_options *o)
62886401
if (o->color_moved)
62896402
o->emitted_symbols = &esm;
62906403

6404+
if (o->additional_path_headers)
6405+
create_filepairs_for_header_only_notifications(o);
6406+
62916407
for (i = 0; i < q->nr; i++) {
62926408
struct diff_filepair *p = q->queue[i];
62936409
if (check_pair_status(p))
@@ -6358,7 +6474,7 @@ void diff_flush(struct diff_options *options)
63586474
* Order: raw, stat, summary, patch
63596475
* or: name/name-status/checkdiff (other bits clear)
63606476
*/
6361-
if (!q->nr)
6477+
if (!q->nr && !options->additional_path_headers)
63626478
goto free_queue;
63636479

63646480
if (output_format & (DIFF_FORMAT_RAW |

diff.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,7 @@ struct diff_options {
395395

396396
struct repository *repo;
397397
struct option *parseopts;
398+
struct strmap *additional_path_headers;
398399

399400
int no_free;
400401
};
@@ -593,7 +594,7 @@ void diffcore_fix_diff_index(void);
593594
" show all files diff when -S is used and hit is found.\n" \
594595
" -a --text treat all files as text.\n"
595596

596-
int diff_queue_is_empty(void);
597+
int diff_queue_is_empty(struct diff_options *o);
597598
void diff_flush(struct diff_options*);
598599
void diff_free(struct diff_options*);
599600
void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc);

0 commit comments

Comments
 (0)