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
This commit is contained in:
commit
90b7153806
@ -34,7 +34,7 @@ endif::git-diff[]
|
||||
endif::git-format-patch[]
|
||||
|
||||
ifdef::git-log[]
|
||||
--diff-merges=(off|none|on|first-parent|1|separate|m|combined|c|dense-combined|cc)::
|
||||
--diff-merges=(off|none|on|first-parent|1|separate|m|combined|c|dense-combined|cc|remerge|r)::
|
||||
--no-diff-merges::
|
||||
Specify diff format to be used for merge commits. Default is
|
||||
{diff-merges-default} unless `--first-parent` is in use, in which case
|
||||
@ -64,6 +64,18 @@ ifdef::git-log[]
|
||||
each of the parents. Separate log entry and diff is generated
|
||||
for each parent.
|
||||
+
|
||||
--diff-merges=remerge:::
|
||||
--diff-merges=r:::
|
||||
--remerge-diff:::
|
||||
With this option, two-parent merge commits are remerged to
|
||||
create a temporary tree object -- potentially containing files
|
||||
with conflict markers and such. A diff is then shown between
|
||||
that temporary tree and the actual merge commit.
|
||||
+
|
||||
The output emitted when this option is used is subject to change, and
|
||||
so is its interaction with other options (unless explicitly
|
||||
documented).
|
||||
+
|
||||
--diff-merges=combined:::
|
||||
--diff-merges=c:::
|
||||
-c:::
|
||||
|
5
apply.c
5
apply.c
@ -3494,7 +3494,7 @@ static int three_way_merge(struct apply_state *state,
|
||||
{
|
||||
mmfile_t base_file, our_file, their_file;
|
||||
mmbuffer_t result = { NULL };
|
||||
int status;
|
||||
enum ll_merge_result status;
|
||||
|
||||
/* resolve trivial cases first */
|
||||
if (oideq(base, ours))
|
||||
@ -3511,6 +3511,9 @@ static int three_way_merge(struct apply_state *state,
|
||||
&their_file, "theirs",
|
||||
state->repo->index,
|
||||
NULL);
|
||||
if (status == LL_MERGE_BINARY_CONFLICT)
|
||||
warning("Cannot merge binary files: %s (%s vs. %s)",
|
||||
path, "ours", "theirs");
|
||||
free(base_file.ptr);
|
||||
free(our_file.ptr);
|
||||
free(their_file.ptr);
|
||||
|
@ -246,6 +246,7 @@ static int checkout_merged(int pos, const struct checkout *state,
|
||||
struct cache_entry *ce = active_cache[pos];
|
||||
const char *path = ce->name;
|
||||
mmfile_t ancestor, ours, theirs;
|
||||
enum ll_merge_result merge_status;
|
||||
int status;
|
||||
struct object_id oid;
|
||||
mmbuffer_t result_buf;
|
||||
@ -276,13 +277,16 @@ static int checkout_merged(int pos, const struct checkout *state,
|
||||
memset(&ll_opts, 0, sizeof(ll_opts));
|
||||
git_config_get_bool("merge.renormalize", &renormalize);
|
||||
ll_opts.renormalize = renormalize;
|
||||
status = ll_merge(&result_buf, path, &ancestor, "base",
|
||||
&ours, "ours", &theirs, "theirs",
|
||||
state->istate, &ll_opts);
|
||||
merge_status = ll_merge(&result_buf, path, &ancestor, "base",
|
||||
&ours, "ours", &theirs, "theirs",
|
||||
state->istate, &ll_opts);
|
||||
free(ancestor.ptr);
|
||||
free(ours.ptr);
|
||||
free(theirs.ptr);
|
||||
if (status < 0 || !result_buf.ptr) {
|
||||
if (merge_status == LL_MERGE_BINARY_CONFLICT)
|
||||
warning("Cannot merge binary files: %s (%s vs. %s)",
|
||||
path, "ours", "theirs");
|
||||
if (merge_status < 0 || !result_buf.ptr) {
|
||||
free(result_buf.ptr);
|
||||
return error(_("path '%s': cannot merge"), path);
|
||||
}
|
||||
|
@ -35,6 +35,7 @@
|
||||
#include "repository.h"
|
||||
#include "commit-reach.h"
|
||||
#include "range-diff.h"
|
||||
#include "tmp-objdir.h"
|
||||
|
||||
#define MAIL_DEFAULT_WRAP 72
|
||||
#define COVER_FROM_AUTO_MAX_SUBJECT_LEN 100
|
||||
@ -422,6 +423,13 @@ static int cmd_log_walk(struct rev_info *rev)
|
||||
int saved_nrl = 0;
|
||||
int saved_dcctc = 0;
|
||||
|
||||
if (rev->remerge_diff) {
|
||||
rev->remerge_objdir = tmp_objdir_create("remerge-diff");
|
||||
if (!rev->remerge_objdir)
|
||||
die(_("unable to create temporary object directory"));
|
||||
tmp_objdir_replace_primary_odb(rev->remerge_objdir, 1);
|
||||
}
|
||||
|
||||
if (rev->early_output)
|
||||
setup_early_output();
|
||||
|
||||
@ -464,6 +472,11 @@ static int cmd_log_walk(struct rev_info *rev)
|
||||
rev->diffopt.no_free = 0;
|
||||
diff_free(&rev->diffopt);
|
||||
|
||||
if (rev->remerge_diff) {
|
||||
tmp_objdir_destroy(rev->remerge_objdir);
|
||||
rev->remerge_objdir = NULL;
|
||||
}
|
||||
|
||||
if (rev->diffopt.output_format & DIFF_FORMAT_CHECKDIFF &&
|
||||
rev->diffopt.flags.check_failed) {
|
||||
return 02;
|
||||
@ -1958,6 +1971,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
|
||||
die(_("--name-status does not make sense"));
|
||||
if (rev.diffopt.output_format & DIFF_FORMAT_CHECKDIFF)
|
||||
die(_("--check does not make sense"));
|
||||
if (rev.remerge_diff)
|
||||
die(_("--remerge-diff does not make sense"));
|
||||
|
||||
if (!use_patch_format &&
|
||||
(!rev.diffopt.output_format ||
|
||||
|
@ -17,12 +17,14 @@ static void suppress(struct rev_info *revs)
|
||||
revs->combined_all_paths = 0;
|
||||
revs->merges_imply_patch = 0;
|
||||
revs->merges_need_diff = 0;
|
||||
revs->remerge_diff = 0;
|
||||
}
|
||||
|
||||
static void set_separate(struct rev_info *revs)
|
||||
{
|
||||
suppress(revs);
|
||||
revs->separate_merges = 1;
|
||||
revs->simplify_history = 0;
|
||||
}
|
||||
|
||||
static void set_first_parent(struct rev_info *revs)
|
||||
@ -45,6 +47,13 @@ static void set_dense_combined(struct rev_info *revs)
|
||||
revs->dense_combined_merges = 1;
|
||||
}
|
||||
|
||||
static void set_remerge_diff(struct rev_info *revs)
|
||||
{
|
||||
suppress(revs);
|
||||
revs->remerge_diff = 1;
|
||||
revs->simplify_history = 0;
|
||||
}
|
||||
|
||||
static diff_merges_setup_func_t func_by_opt(const char *optarg)
|
||||
{
|
||||
if (!strcmp(optarg, "off") || !strcmp(optarg, "none"))
|
||||
@ -57,6 +66,8 @@ static diff_merges_setup_func_t func_by_opt(const char *optarg)
|
||||
return set_combined;
|
||||
else if (!strcmp(optarg, "cc") || !strcmp(optarg, "dense-combined"))
|
||||
return set_dense_combined;
|
||||
else if (!strcmp(optarg, "r") || !strcmp(optarg, "remerge"))
|
||||
return set_remerge_diff;
|
||||
else if (!strcmp(optarg, "m") || !strcmp(optarg, "on"))
|
||||
return set_to_default;
|
||||
return NULL;
|
||||
@ -110,6 +121,9 @@ int diff_merges_parse_opts(struct rev_info *revs, const char **argv)
|
||||
} else if (!strcmp(arg, "--cc")) {
|
||||
set_dense_combined(revs);
|
||||
revs->merges_imply_patch = 1;
|
||||
} else if (!strcmp(arg, "--remerge-diff")) {
|
||||
set_remerge_diff(revs);
|
||||
revs->merges_imply_patch = 1;
|
||||
} else if (!strcmp(arg, "--no-diff-merges")) {
|
||||
suppress(revs);
|
||||
} else if (!strcmp(arg, "--combined-all-paths")) {
|
||||
|
124
diff.c
124
diff.c
@ -28,6 +28,7 @@
|
||||
#include "help.h"
|
||||
#include "promisor-remote.h"
|
||||
#include "dir.h"
|
||||
#include "strmap.h"
|
||||
|
||||
#ifdef NO_FAST_WORKING_DIRECTORY
|
||||
#define FAST_WORKING_DIRECTORY 0
|
||||
@ -3353,6 +3354,31 @@ struct userdiff_driver *get_textconv(struct repository *r,
|
||||
return userdiff_get_textconv(r, one->driver);
|
||||
}
|
||||
|
||||
static struct strbuf *additional_headers(struct diff_options *o,
|
||||
const char *path)
|
||||
{
|
||||
if (!o->additional_path_headers)
|
||||
return NULL;
|
||||
return strmap_get(o->additional_path_headers, path);
|
||||
}
|
||||
|
||||
static void add_formatted_headers(struct strbuf *msg,
|
||||
struct strbuf *more_headers,
|
||||
const char *line_prefix,
|
||||
const char *meta,
|
||||
const char *reset)
|
||||
{
|
||||
char *next, *newline;
|
||||
|
||||
for (next = more_headers->buf; *next; next = newline) {
|
||||
newline = strchrnul(next, '\n');
|
||||
strbuf_addf(msg, "%s%s%.*s%s\n", line_prefix, meta,
|
||||
(int)(newline - next), next, reset);
|
||||
if (*newline)
|
||||
newline++;
|
||||
}
|
||||
}
|
||||
|
||||
static void builtin_diff(const char *name_a,
|
||||
const char *name_b,
|
||||
struct diff_filespec *one,
|
||||
@ -3411,6 +3437,17 @@ static void builtin_diff(const char *name_a,
|
||||
b_two = quote_two(b_prefix, name_b + (*name_b == '/'));
|
||||
lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
|
||||
lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
|
||||
if (!DIFF_FILE_VALID(one) && !DIFF_FILE_VALID(two)) {
|
||||
/*
|
||||
* We should only reach this point for pairs from
|
||||
* create_filepairs_for_header_only_notifications(). For
|
||||
* these, we should avoid the "/dev/null" special casing
|
||||
* above, meaning we avoid showing such pairs as either
|
||||
* "new file" or "deleted file" below.
|
||||
*/
|
||||
lbl[0] = a_one;
|
||||
lbl[1] = b_two;
|
||||
}
|
||||
strbuf_addf(&header, "%s%sdiff --git %s %s%s\n", line_prefix, meta, a_one, b_two, reset);
|
||||
if (lbl[0][0] == '/') {
|
||||
/* /dev/null */
|
||||
@ -4275,6 +4312,7 @@ static void fill_metainfo(struct strbuf *msg,
|
||||
const char *set = diff_get_color(use_color, DIFF_METAINFO);
|
||||
const char *reset = diff_get_color(use_color, DIFF_RESET);
|
||||
const char *line_prefix = diff_line_prefix(o);
|
||||
struct strbuf *more_headers = NULL;
|
||||
|
||||
*must_show_header = 1;
|
||||
strbuf_init(msg, PATH_MAX * 2 + 300);
|
||||
@ -4311,6 +4349,11 @@ static void fill_metainfo(struct strbuf *msg,
|
||||
default:
|
||||
*must_show_header = 0;
|
||||
}
|
||||
if ((more_headers = additional_headers(o, name))) {
|
||||
add_formatted_headers(msg, more_headers,
|
||||
line_prefix, set, reset);
|
||||
*must_show_header = 1;
|
||||
}
|
||||
if (one && two && !oideq(&one->oid, &two->oid)) {
|
||||
const unsigned hexsz = the_hash_algo->hexsz;
|
||||
int abbrev = o->abbrev ? o->abbrev : DEFAULT_ABBREV;
|
||||
@ -5803,12 +5846,27 @@ int diff_unmodified_pair(struct diff_filepair *p)
|
||||
|
||||
static void diff_flush_patch(struct diff_filepair *p, struct diff_options *o)
|
||||
{
|
||||
if (diff_unmodified_pair(p))
|
||||
int include_conflict_headers =
|
||||
(additional_headers(o, p->one->path) &&
|
||||
(!o->filter || filter_bit_tst(DIFF_STATUS_UNMERGED, o)));
|
||||
|
||||
/*
|
||||
* Check if we can return early without showing a diff. Note that
|
||||
* diff_filepair only stores {oid, path, mode, is_valid}
|
||||
* information for each path, and thus diff_unmodified_pair() only
|
||||
* considers those bits of info. However, we do not want pairs
|
||||
* created by create_filepairs_for_header_only_notifications()
|
||||
* (which always look like unmodified pairs) to be ignored, so
|
||||
* return early if both p is unmodified AND we don't want to
|
||||
* include_conflict_headers.
|
||||
*/
|
||||
if (diff_unmodified_pair(p) && !include_conflict_headers)
|
||||
return;
|
||||
|
||||
/* Actually, we can also return early to avoid showing tree diffs */
|
||||
if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
|
||||
(DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
|
||||
return; /* no tree diffs in patch format */
|
||||
return;
|
||||
|
||||
run_diff(p, o);
|
||||
}
|
||||
@ -5839,10 +5897,17 @@ static void diff_flush_checkdiff(struct diff_filepair *p,
|
||||
run_checkdiff(p, o);
|
||||
}
|
||||
|
||||
int diff_queue_is_empty(void)
|
||||
int diff_queue_is_empty(struct diff_options *o)
|
||||
{
|
||||
struct diff_queue_struct *q = &diff_queued_diff;
|
||||
int i;
|
||||
int include_conflict_headers =
|
||||
(o->additional_path_headers &&
|
||||
(!o->filter || filter_bit_tst(DIFF_STATUS_UNMERGED, o)));
|
||||
|
||||
if (include_conflict_headers)
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < q->nr; i++)
|
||||
if (!diff_unmodified_pair(q->queue[i]))
|
||||
return 0;
|
||||
@ -6276,6 +6341,54 @@ void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc)
|
||||
warning(_(rename_limit_advice), varname, needed);
|
||||
}
|
||||
|
||||
static void create_filepairs_for_header_only_notifications(struct diff_options *o)
|
||||
{
|
||||
struct strset present;
|
||||
struct diff_queue_struct *q = &diff_queued_diff;
|
||||
struct hashmap_iter iter;
|
||||
struct strmap_entry *e;
|
||||
int i;
|
||||
|
||||
strset_init_with_options(&present, /*pool*/ NULL, /*strdup*/ 0);
|
||||
|
||||
/*
|
||||
* Find out which paths exist in diff_queued_diff, preferring
|
||||
* one->path for any pair that has multiple paths.
|
||||
*/
|
||||
for (i = 0; i < q->nr; i++) {
|
||||
struct diff_filepair *p = q->queue[i];
|
||||
char *path = p->one->path ? p->one->path : p->two->path;
|
||||
|
||||
if (strmap_contains(o->additional_path_headers, path))
|
||||
strset_add(&present, path);
|
||||
}
|
||||
|
||||
/*
|
||||
* Loop over paths in additional_path_headers; for each NOT already
|
||||
* in diff_queued_diff, create a synthetic filepair and insert that
|
||||
* into diff_queued_diff.
|
||||
*/
|
||||
strmap_for_each_entry(o->additional_path_headers, &iter, e) {
|
||||
if (!strset_contains(&present, e->key)) {
|
||||
struct diff_filespec *one, *two;
|
||||
struct diff_filepair *p;
|
||||
|
||||
one = alloc_filespec(e->key);
|
||||
two = alloc_filespec(e->key);
|
||||
fill_filespec(one, null_oid(), 0, 0);
|
||||
fill_filespec(two, null_oid(), 0, 0);
|
||||
p = diff_queue(q, one, two);
|
||||
p->status = DIFF_STATUS_MODIFIED;
|
||||
}
|
||||
}
|
||||
|
||||
/* Re-sort the filepairs */
|
||||
diffcore_fix_diff_index();
|
||||
|
||||
/* Cleanup */
|
||||
strset_clear(&present);
|
||||
}
|
||||
|
||||
static void diff_flush_patch_all_file_pairs(struct diff_options *o)
|
||||
{
|
||||
int i;
|
||||
@ -6288,6 +6401,9 @@ static void diff_flush_patch_all_file_pairs(struct diff_options *o)
|
||||
if (o->color_moved)
|
||||
o->emitted_symbols = &esm;
|
||||
|
||||
if (o->additional_path_headers)
|
||||
create_filepairs_for_header_only_notifications(o);
|
||||
|
||||
for (i = 0; i < q->nr; i++) {
|
||||
struct diff_filepair *p = q->queue[i];
|
||||
if (check_pair_status(p))
|
||||
@ -6358,7 +6474,7 @@ void diff_flush(struct diff_options *options)
|
||||
* Order: raw, stat, summary, patch
|
||||
* or: name/name-status/checkdiff (other bits clear)
|
||||
*/
|
||||
if (!q->nr)
|
||||
if (!q->nr && !options->additional_path_headers)
|
||||
goto free_queue;
|
||||
|
||||
if (output_format & (DIFF_FORMAT_RAW |
|
||||
|
3
diff.h
3
diff.h
@ -395,6 +395,7 @@ struct diff_options {
|
||||
|
||||
struct repository *repo;
|
||||
struct option *parseopts;
|
||||
struct strmap *additional_path_headers;
|
||||
|
||||
int no_free;
|
||||
};
|
||||
@ -593,7 +594,7 @@ void diffcore_fix_diff_index(void);
|
||||
" show all files diff when -S is used and hit is found.\n" \
|
||||
" -a --text treat all files as text.\n"
|
||||
|
||||
int diff_queue_is_empty(void);
|
||||
int diff_queue_is_empty(struct diff_options *o);
|
||||
void diff_flush(struct diff_options*);
|
||||
void diff_free(struct diff_options*);
|
||||
void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc);
|
||||
|
40
ll-merge.c
40
ll-merge.c
@ -14,7 +14,7 @@
|
||||
|
||||
struct ll_merge_driver;
|
||||
|
||||
typedef int (*ll_merge_fn)(const struct ll_merge_driver *,
|
||||
typedef enum ll_merge_result (*ll_merge_fn)(const struct ll_merge_driver *,
|
||||
mmbuffer_t *result,
|
||||
const char *path,
|
||||
mmfile_t *orig, const char *orig_name,
|
||||
@ -49,7 +49,7 @@ void reset_merge_attributes(void)
|
||||
/*
|
||||
* Built-in low-levels
|
||||
*/
|
||||
static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
|
||||
static enum ll_merge_result ll_binary_merge(const struct ll_merge_driver *drv_unused,
|
||||
mmbuffer_t *result,
|
||||
const char *path,
|
||||
mmfile_t *orig, const char *orig_name,
|
||||
@ -58,6 +58,7 @@ static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
|
||||
const struct ll_merge_options *opts,
|
||||
int marker_size)
|
||||
{
|
||||
enum ll_merge_result ret;
|
||||
mmfile_t *stolen;
|
||||
assert(opts);
|
||||
|
||||
@ -68,16 +69,19 @@ static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
|
||||
*/
|
||||
if (opts->virtual_ancestor) {
|
||||
stolen = orig;
|
||||
ret = LL_MERGE_OK;
|
||||
} else {
|
||||
switch (opts->variant) {
|
||||
default:
|
||||
warning("Cannot merge binary files: %s (%s vs. %s)",
|
||||
path, name1, name2);
|
||||
/* fallthru */
|
||||
ret = LL_MERGE_BINARY_CONFLICT;
|
||||
stolen = src1;
|
||||
break;
|
||||
case XDL_MERGE_FAVOR_OURS:
|
||||
ret = LL_MERGE_OK;
|
||||
stolen = src1;
|
||||
break;
|
||||
case XDL_MERGE_FAVOR_THEIRS:
|
||||
ret = LL_MERGE_OK;
|
||||
stolen = src2;
|
||||
break;
|
||||
}
|
||||
@ -87,16 +91,10 @@ static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
|
||||
result->size = stolen->size;
|
||||
stolen->ptr = NULL;
|
||||
|
||||
/*
|
||||
* With -Xtheirs or -Xours, we have cleanly merged;
|
||||
* otherwise we got a conflict.
|
||||
*/
|
||||
return opts->variant == XDL_MERGE_FAVOR_OURS ||
|
||||
opts->variant == XDL_MERGE_FAVOR_THEIRS ?
|
||||
0 : 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
|
||||
static enum ll_merge_result ll_xdl_merge(const struct ll_merge_driver *drv_unused,
|
||||
mmbuffer_t *result,
|
||||
const char *path,
|
||||
mmfile_t *orig, const char *orig_name,
|
||||
@ -105,7 +103,9 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
|
||||
const struct ll_merge_options *opts,
|
||||
int marker_size)
|
||||
{
|
||||
enum ll_merge_result ret;
|
||||
xmparam_t xmp;
|
||||
int status;
|
||||
assert(opts);
|
||||
|
||||
if (orig->size > MAX_XDIFF_SIZE ||
|
||||
@ -133,10 +133,12 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
|
||||
xmp.ancestor = orig_name;
|
||||
xmp.file1 = name1;
|
||||
xmp.file2 = name2;
|
||||
return xdl_merge(orig, src1, src2, &xmp, result);
|
||||
status = xdl_merge(orig, src1, src2, &xmp, result);
|
||||
ret = (status > 0) ? LL_MERGE_CONFLICT : status;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ll_union_merge(const struct ll_merge_driver *drv_unused,
|
||||
static enum ll_merge_result ll_union_merge(const struct ll_merge_driver *drv_unused,
|
||||
mmbuffer_t *result,
|
||||
const char *path,
|
||||
mmfile_t *orig, const char *orig_name,
|
||||
@ -178,7 +180,7 @@ static void create_temp(mmfile_t *src, char *path, size_t len)
|
||||
/*
|
||||
* User defined low-level merge driver support.
|
||||
*/
|
||||
static int ll_ext_merge(const struct ll_merge_driver *fn,
|
||||
static enum ll_merge_result ll_ext_merge(const struct ll_merge_driver *fn,
|
||||
mmbuffer_t *result,
|
||||
const char *path,
|
||||
mmfile_t *orig, const char *orig_name,
|
||||
@ -194,6 +196,7 @@ static int ll_ext_merge(const struct ll_merge_driver *fn,
|
||||
const char *args[] = { NULL, NULL };
|
||||
int status, fd, i;
|
||||
struct stat st;
|
||||
enum ll_merge_result ret;
|
||||
assert(opts);
|
||||
|
||||
sq_quote_buf(&path_sq, path);
|
||||
@ -236,7 +239,8 @@ static int ll_ext_merge(const struct ll_merge_driver *fn,
|
||||
unlink_or_warn(temp[i]);
|
||||
strbuf_release(&cmd);
|
||||
strbuf_release(&path_sq);
|
||||
return status;
|
||||
ret = (status > 0) ? LL_MERGE_CONFLICT : status;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -362,7 +366,7 @@ static void normalize_file(mmfile_t *mm, const char *path, struct index_state *i
|
||||
}
|
||||
}
|
||||
|
||||
int ll_merge(mmbuffer_t *result_buf,
|
||||
enum ll_merge_result ll_merge(mmbuffer_t *result_buf,
|
||||
const char *path,
|
||||
mmfile_t *ancestor, const char *ancestor_label,
|
||||
mmfile_t *ours, const char *our_label,
|
||||
|
@ -82,13 +82,20 @@ struct ll_merge_options {
|
||||
long xdl_opts;
|
||||
};
|
||||
|
||||
enum ll_merge_result {
|
||||
LL_MERGE_ERROR = -1,
|
||||
LL_MERGE_OK = 0,
|
||||
LL_MERGE_CONFLICT,
|
||||
LL_MERGE_BINARY_CONFLICT,
|
||||
};
|
||||
|
||||
/**
|
||||
* Perform a three-way single-file merge in core. This is a thin wrapper
|
||||
* around `xdl_merge` that takes the path and any merge backend specified in
|
||||
* `.gitattributes` or `.git/info/attributes` into account.
|
||||
* Returns 0 for a clean merge.
|
||||
*/
|
||||
int ll_merge(mmbuffer_t *result_buf,
|
||||
enum ll_merge_result ll_merge(mmbuffer_t *result_buf,
|
||||
const char *path,
|
||||
mmfile_t *ancestor, const char *ancestor_label,
|
||||
mmfile_t *ours, const char *our_label,
|
||||
|
118
log-tree.c
118
log-tree.c
@ -1,12 +1,15 @@
|
||||
#include "cache.h"
|
||||
#include "commit-reach.h"
|
||||
#include "config.h"
|
||||
#include "diff.h"
|
||||
#include "object-store.h"
|
||||
#include "repository.h"
|
||||
#include "tmp-objdir.h"
|
||||
#include "commit.h"
|
||||
#include "tag.h"
|
||||
#include "graph.h"
|
||||
#include "log-tree.h"
|
||||
#include "merge-ort.h"
|
||||
#include "reflog-walk.h"
|
||||
#include "refs.h"
|
||||
#include "string-list.h"
|
||||
@ -16,6 +19,7 @@
|
||||
#include "line-log.h"
|
||||
#include "help.h"
|
||||
#include "range-diff.h"
|
||||
#include "strmap.h"
|
||||
|
||||
static struct decoration name_decoration = { "object names" };
|
||||
static int decoration_loaded;
|
||||
@ -849,7 +853,7 @@ int log_tree_diff_flush(struct rev_info *opt)
|
||||
opt->shown_dashes = 0;
|
||||
diffcore_std(&opt->diffopt);
|
||||
|
||||
if (diff_queue_is_empty()) {
|
||||
if (diff_queue_is_empty(&opt->diffopt)) {
|
||||
int saved_fmt = opt->diffopt.output_format;
|
||||
opt->diffopt.output_format = DIFF_FORMAT_NO_OUTPUT;
|
||||
diff_flush(&opt->diffopt);
|
||||
@ -904,6 +908,106 @@ static int do_diff_combined(struct rev_info *opt, struct commit *commit)
|
||||
return !opt->loginfo;
|
||||
}
|
||||
|
||||
static void setup_additional_headers(struct diff_options *o,
|
||||
struct strmap *all_headers)
|
||||
{
|
||||
struct hashmap_iter iter;
|
||||
struct strmap_entry *entry;
|
||||
|
||||
/*
|
||||
* Make o->additional_path_headers contain the subset of all_headers
|
||||
* that match o->pathspec. If there aren't any that match o->pathspec,
|
||||
* then make o->additional_path_headers be NULL.
|
||||
*/
|
||||
|
||||
if (!o->pathspec.nr) {
|
||||
o->additional_path_headers = all_headers;
|
||||
return;
|
||||
}
|
||||
|
||||
o->additional_path_headers = xmalloc(sizeof(struct strmap));
|
||||
strmap_init_with_options(o->additional_path_headers, NULL, 0);
|
||||
strmap_for_each_entry(all_headers, &iter, entry) {
|
||||
if (match_pathspec(the_repository->index, &o->pathspec,
|
||||
entry->key, strlen(entry->key),
|
||||
0 /* prefix */, NULL /* seen */,
|
||||
0 /* is_dir */))
|
||||
strmap_put(o->additional_path_headers,
|
||||
entry->key, entry->value);
|
||||
}
|
||||
if (!strmap_get_size(o->additional_path_headers)) {
|
||||
strmap_clear(o->additional_path_headers, 0);
|
||||
FREE_AND_NULL(o->additional_path_headers);
|
||||
}
|
||||
}
|
||||
|
||||
static void cleanup_additional_headers(struct diff_options *o)
|
||||
{
|
||||
if (!o->pathspec.nr) {
|
||||
o->additional_path_headers = NULL;
|
||||
return;
|
||||
}
|
||||
if (!o->additional_path_headers)
|
||||
return;
|
||||
|
||||
strmap_clear(o->additional_path_headers, 0);
|
||||
FREE_AND_NULL(o->additional_path_headers);
|
||||
}
|
||||
|
||||
static int do_remerge_diff(struct rev_info *opt,
|
||||
struct commit_list *parents,
|
||||
struct object_id *oid,
|
||||
struct commit *commit)
|
||||
{
|
||||
struct merge_options o;
|
||||
struct commit_list *bases;
|
||||
struct merge_result res = {0};
|
||||
struct pretty_print_context ctx = {0};
|
||||
struct commit *parent1 = parents->item;
|
||||
struct commit *parent2 = parents->next->item;
|
||||
struct strbuf parent1_desc = STRBUF_INIT;
|
||||
struct strbuf parent2_desc = STRBUF_INIT;
|
||||
|
||||
/* Setup merge options */
|
||||
init_merge_options(&o, the_repository);
|
||||
o.show_rename_progress = 0;
|
||||
o.record_conflict_msgs_as_headers = 1;
|
||||
o.msg_header_prefix = "remerge";
|
||||
|
||||
ctx.abbrev = DEFAULT_ABBREV;
|
||||
format_commit_message(parent1, "%h (%s)", &parent1_desc, &ctx);
|
||||
format_commit_message(parent2, "%h (%s)", &parent2_desc, &ctx);
|
||||
o.branch1 = parent1_desc.buf;
|
||||
o.branch2 = parent2_desc.buf;
|
||||
|
||||
/* Parse the relevant commits and get the merge bases */
|
||||
parse_commit_or_die(parent1);
|
||||
parse_commit_or_die(parent2);
|
||||
bases = get_merge_bases(parent1, parent2);
|
||||
|
||||
/* Re-merge the parents */
|
||||
merge_incore_recursive(&o, bases, parent1, parent2, &res);
|
||||
|
||||
/* Show the diff */
|
||||
setup_additional_headers(&opt->diffopt, res.path_messages);
|
||||
diff_tree_oid(&res.tree->object.oid, oid, "", &opt->diffopt);
|
||||
log_tree_diff_flush(opt);
|
||||
|
||||
/* Cleanup */
|
||||
cleanup_additional_headers(&opt->diffopt);
|
||||
strbuf_release(&parent1_desc);
|
||||
strbuf_release(&parent2_desc);
|
||||
merge_finalize(&o, &res);
|
||||
|
||||
/* Clean up the contents of the temporary object directory */
|
||||
if (opt->remerge_objdir)
|
||||
tmp_objdir_discard_objects(opt->remerge_objdir);
|
||||
else
|
||||
BUG("did a remerge diff without remerge_objdir?!?");
|
||||
|
||||
return !opt->loginfo;
|
||||
}
|
||||
|
||||
/*
|
||||
* Show the diff of a commit.
|
||||
*
|
||||
@ -938,6 +1042,18 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log
|
||||
}
|
||||
|
||||
if (is_merge) {
|
||||
int octopus = (parents->next->next != NULL);
|
||||
|
||||
if (opt->remerge_diff) {
|
||||
if (octopus) {
|
||||
show_log(opt);
|
||||
fprintf(opt->diffopt.file,
|
||||
"diff: warning: Skipping remerge-diff "
|
||||
"for octopus merges.\n");
|
||||
return 1;
|
||||
}
|
||||
return do_remerge_diff(opt, parents, oid, commit);
|
||||
}
|
||||
if (opt->combine_merges)
|
||||
return do_diff_combined(opt, commit);
|
||||
if (opt->separate_merges) {
|
||||
|
@ -36,7 +36,7 @@ static void *three_way_filemerge(struct index_state *istate,
|
||||
mmfile_t *their,
|
||||
unsigned long *size)
|
||||
{
|
||||
int merge_status;
|
||||
enum ll_merge_result merge_status;
|
||||
mmbuffer_t res;
|
||||
|
||||
/*
|
||||
@ -50,6 +50,9 @@ static void *three_way_filemerge(struct index_state *istate,
|
||||
istate, NULL);
|
||||
if (merge_status < 0)
|
||||
return NULL;
|
||||
if (merge_status == LL_MERGE_BINARY_CONFLICT)
|
||||
warning("Cannot merge binary files: %s (%s vs. %s)",
|
||||
path, ".our", ".their");
|
||||
|
||||
*size = res.size;
|
||||
return res.ptr;
|
||||
|
55
merge-ort.c
55
merge-ort.c
@ -634,17 +634,51 @@ static void path_msg(struct merge_options *opt,
|
||||
const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
struct strbuf *sb = strmap_get(&opt->priv->output, path);
|
||||
struct strbuf *sb, *dest;
|
||||
struct strbuf tmp = STRBUF_INIT;
|
||||
|
||||
if (opt->record_conflict_msgs_as_headers && omittable_hint)
|
||||
return; /* Do not record mere hints in headers */
|
||||
if (opt->record_conflict_msgs_as_headers && opt->priv->call_depth)
|
||||
return; /* Do not record inner merge issues in headers */
|
||||
sb = strmap_get(&opt->priv->output, path);
|
||||
if (!sb) {
|
||||
sb = xmalloc(sizeof(*sb));
|
||||
strbuf_init(sb, 0);
|
||||
strmap_put(&opt->priv->output, path, sb);
|
||||
}
|
||||
|
||||
dest = (opt->record_conflict_msgs_as_headers ? &tmp : sb);
|
||||
|
||||
va_start(ap, fmt);
|
||||
strbuf_vaddf(sb, fmt, ap);
|
||||
strbuf_vaddf(dest, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (opt->record_conflict_msgs_as_headers) {
|
||||
int i_sb = 0, i_tmp = 0;
|
||||
|
||||
/* Start with the specified prefix */
|
||||
if (opt->msg_header_prefix)
|
||||
strbuf_addf(sb, "%s ", opt->msg_header_prefix);
|
||||
|
||||
/* Copy tmp to sb, adding spaces after newlines */
|
||||
strbuf_grow(sb, sb->len + 2*tmp.len); /* more than sufficient */
|
||||
for (; i_tmp < tmp.len; i_tmp++, i_sb++) {
|
||||
/* Copy next character from tmp to sb */
|
||||
sb->buf[sb->len + i_sb] = tmp.buf[i_tmp];
|
||||
|
||||
/* If we copied a newline, add a space */
|
||||
if (tmp.buf[i_tmp] == '\n')
|
||||
sb->buf[++i_sb] = ' ';
|
||||
}
|
||||
/* Update length and ensure it's NUL-terminated */
|
||||
sb->len += i_sb;
|
||||
sb->buf[sb->len] = '\0';
|
||||
|
||||
strbuf_release(&tmp);
|
||||
}
|
||||
|
||||
/* Add final newline character to sb */
|
||||
strbuf_addch(sb, '\n');
|
||||
}
|
||||
|
||||
@ -1743,7 +1777,7 @@ static int merge_3way(struct merge_options *opt,
|
||||
mmfile_t orig, src1, src2;
|
||||
struct ll_merge_options ll_opts = {0};
|
||||
char *base, *name1, *name2;
|
||||
int merge_status;
|
||||
enum ll_merge_result merge_status;
|
||||
|
||||
if (!opt->priv->attr_index.initialized)
|
||||
initialize_attr_index(opt);
|
||||
@ -1787,6 +1821,10 @@ static int merge_3way(struct merge_options *opt,
|
||||
merge_status = ll_merge(result_buf, path, &orig, base,
|
||||
&src1, name1, &src2, name2,
|
||||
&opt->priv->attr_index, &ll_opts);
|
||||
if (merge_status == LL_MERGE_BINARY_CONFLICT)
|
||||
path_msg(opt, path, 0,
|
||||
"warning: Cannot merge binary files: %s (%s vs. %s)",
|
||||
path, name1, name2);
|
||||
|
||||
free(base);
|
||||
free(name1);
|
||||
@ -2416,7 +2454,7 @@ static void apply_directory_rename_modifications(struct merge_options *opt,
|
||||
*/
|
||||
ci->path_conflict = 1;
|
||||
if (pair->status == 'A')
|
||||
path_msg(opt, new_path, 0,
|
||||
path_msg(opt, new_path, 1,
|
||||
_("CONFLICT (file location): %s added in %s "
|
||||
"inside a directory that was renamed in %s, "
|
||||
"suggesting it should perhaps be moved to "
|
||||
@ -2424,7 +2462,7 @@ static void apply_directory_rename_modifications(struct merge_options *opt,
|
||||
old_path, branch_with_new_path,
|
||||
branch_with_dir_rename, new_path);
|
||||
else
|
||||
path_msg(opt, new_path, 0,
|
||||
path_msg(opt, new_path, 1,
|
||||
_("CONFLICT (file location): %s renamed to %s "
|
||||
"in %s, inside a directory that was renamed "
|
||||
"in %s, suggesting it should perhaps be "
|
||||
@ -4259,6 +4297,9 @@ void merge_switch_to_result(struct merge_options *opt,
|
||||
struct string_list olist = STRING_LIST_INIT_NODUP;
|
||||
int i;
|
||||
|
||||
if (opt->record_conflict_msgs_as_headers)
|
||||
BUG("Either display conflict messages or record them as headers, not both");
|
||||
|
||||
trace2_region_enter("merge", "display messages", opt->repo);
|
||||
|
||||
/* Hack to pre-allocate olist to the desired size */
|
||||
@ -4360,6 +4401,9 @@ static void merge_start(struct merge_options *opt, struct merge_result *result)
|
||||
assert(opt->recursive_variant >= MERGE_VARIANT_NORMAL &&
|
||||
opt->recursive_variant <= MERGE_VARIANT_THEIRS);
|
||||
|
||||
if (opt->msg_header_prefix)
|
||||
assert(opt->record_conflict_msgs_as_headers);
|
||||
|
||||
/*
|
||||
* detect_renames, verbosity, buffer_output, and obuf are ignored
|
||||
* fields that were used by "recursive" rather than "ort" -- but
|
||||
@ -4560,6 +4604,7 @@ redo:
|
||||
trace2_region_leave("merge", "process_entries", opt->repo);
|
||||
|
||||
/* Set return values */
|
||||
result->path_messages = &opt->priv->output;
|
||||
result->tree = parse_tree_indirect(&working_tree_oid);
|
||||
/* existence of conflicted entries implies unclean */
|
||||
result->clean &= strmap_empty(&opt->priv->conflicted);
|
||||
|
10
merge-ort.h
10
merge-ort.h
@ -5,6 +5,7 @@
|
||||
|
||||
struct commit;
|
||||
struct tree;
|
||||
struct strmap;
|
||||
|
||||
struct merge_result {
|
||||
/*
|
||||
@ -23,6 +24,15 @@ struct merge_result {
|
||||
*/
|
||||
struct tree *tree;
|
||||
|
||||
/*
|
||||
* Special messages and conflict notices for various paths
|
||||
*
|
||||
* This is a map of pathnames to strbufs. It contains various
|
||||
* warning/conflict/notice messages (possibly multiple per path)
|
||||
* that callers may want to use.
|
||||
*/
|
||||
struct strmap *path_messages;
|
||||
|
||||
/*
|
||||
* Additional metadata used by merge_switch_to_result() or future calls
|
||||
* to merge_incore_*(). Includes data needed to update the index (if
|
||||
|
@ -1044,7 +1044,7 @@ static int merge_3way(struct merge_options *opt,
|
||||
mmfile_t orig, src1, src2;
|
||||
struct ll_merge_options ll_opts = {0};
|
||||
char *base, *name1, *name2;
|
||||
int merge_status;
|
||||
enum ll_merge_result merge_status;
|
||||
|
||||
ll_opts.renormalize = opt->renormalize;
|
||||
ll_opts.extra_marker_size = extra_marker_size;
|
||||
@ -1090,6 +1090,9 @@ static int merge_3way(struct merge_options *opt,
|
||||
merge_status = ll_merge(result_buf, a->path, &orig, base,
|
||||
&src1, name1, &src2, name2,
|
||||
opt->repo->index, &ll_opts);
|
||||
if (merge_status == LL_MERGE_BINARY_CONFLICT)
|
||||
warning("Cannot merge binary files: %s (%s vs. %s)",
|
||||
a->path, name1, name2);
|
||||
|
||||
free(base);
|
||||
free(name1);
|
||||
@ -3711,6 +3714,10 @@ static int merge_start(struct merge_options *opt, struct tree *head)
|
||||
|
||||
assert(opt->priv == NULL);
|
||||
|
||||
/* Not supported; option specific to merge-ort */
|
||||
assert(!opt->record_conflict_msgs_as_headers);
|
||||
assert(!opt->msg_header_prefix);
|
||||
|
||||
/* Sanity check on repo state; index must match head */
|
||||
if (repo_index_has_changes(opt->repo, head, &sb)) {
|
||||
err(opt, _("Your local changes to the following files would be overwritten by merge:\n %s"),
|
||||
|
@ -46,6 +46,8 @@ struct merge_options {
|
||||
/* miscellaneous control options */
|
||||
const char *subtree_shift;
|
||||
unsigned renormalize : 1;
|
||||
unsigned record_conflict_msgs_as_headers : 1;
|
||||
const char *msg_header_prefix;
|
||||
|
||||
/* internal fields used by the implementation */
|
||||
struct merge_options_internal *priv;
|
||||
|
@ -344,7 +344,7 @@ static int ll_merge_in_worktree(struct notes_merge_options *o,
|
||||
{
|
||||
mmbuffer_t result_buf;
|
||||
mmfile_t base, local, remote;
|
||||
int status;
|
||||
enum ll_merge_result status;
|
||||
|
||||
read_mmblob(&base, &p->base);
|
||||
read_mmblob(&local, &p->local);
|
||||
@ -358,6 +358,9 @@ static int ll_merge_in_worktree(struct notes_merge_options *o,
|
||||
free(local.ptr);
|
||||
free(remote.ptr);
|
||||
|
||||
if (status == LL_MERGE_BINARY_CONFLICT)
|
||||
warning("Cannot merge binary files: %s (%s vs. %s)",
|
||||
oid_to_hex(&p->obj), o->local_ref, o->remote_ref);
|
||||
if ((status < 0) || !result_buf.ptr)
|
||||
die("Failed to execute internal merge");
|
||||
|
||||
|
9
rerere.c
9
rerere.c
@ -609,19 +609,20 @@ static int try_merge(struct index_state *istate,
|
||||
const struct rerere_id *id, const char *path,
|
||||
mmfile_t *cur, mmbuffer_t *result)
|
||||
{
|
||||
int ret;
|
||||
enum ll_merge_result ret;
|
||||
mmfile_t base = {NULL, 0}, other = {NULL, 0};
|
||||
|
||||
if (read_mmfile(&base, rerere_path(id, "preimage")) ||
|
||||
read_mmfile(&other, rerere_path(id, "postimage")))
|
||||
ret = 1;
|
||||
else
|
||||
read_mmfile(&other, rerere_path(id, "postimage"))) {
|
||||
ret = LL_MERGE_CONFLICT;
|
||||
} else {
|
||||
/*
|
||||
* A three-way merge. Note that this honors user-customizable
|
||||
* low-level merge driver settings.
|
||||
*/
|
||||
ret = ll_merge(result, path, &base, NULL, cur, "", &other, "",
|
||||
istate, NULL);
|
||||
}
|
||||
|
||||
free(base.ptr);
|
||||
free(other.ptr);
|
||||
|
@ -195,7 +195,8 @@ struct rev_info {
|
||||
combine_merges:1,
|
||||
combined_all_paths:1,
|
||||
dense_combined_merges:1,
|
||||
first_parent_merges:1;
|
||||
first_parent_merges:1,
|
||||
remerge_diff:1;
|
||||
|
||||
/* Format info */
|
||||
int show_notes;
|
||||
@ -315,6 +316,9 @@ struct rev_info {
|
||||
|
||||
/* misc. flags related to '--no-kept-objects' */
|
||||
unsigned keep_pack_cache_flags;
|
||||
|
||||
/* Location where temporary objects for remerge-diff are written. */
|
||||
struct tmp_objdir *remerge_objdir;
|
||||
};
|
||||
|
||||
int ref_excluded(struct string_list *, const char *path);
|
||||
|
291
t/t4069-remerge-diff.sh
Executable file
291
t/t4069-remerge-diff.sh
Executable file
@ -0,0 +1,291 @@
|
||||
#!/bin/sh
|
||||
|
||||
test_description='remerge-diff handling'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
# This test is ort-specific
|
||||
if test "${GIT_TEST_MERGE_ALGORITHM}" != ort
|
||||
then
|
||||
skip_all="GIT_TEST_MERGE_ALGORITHM != ort"
|
||||
test_done
|
||||
fi
|
||||
|
||||
test_expect_success 'setup basic merges' '
|
||||
test_write_lines 1 2 3 4 5 6 7 8 9 >numbers &&
|
||||
git add numbers &&
|
||||
git commit -m base &&
|
||||
|
||||
git branch feature_a &&
|
||||
git branch feature_b &&
|
||||
git branch feature_c &&
|
||||
|
||||
git branch ab_resolution &&
|
||||
git branch bc_resolution &&
|
||||
|
||||
git checkout feature_a &&
|
||||
test_write_lines 1 2 three 4 5 6 7 eight 9 >numbers &&
|
||||
git commit -a -m change_a &&
|
||||
|
||||
git checkout feature_b &&
|
||||
test_write_lines 1 2 tres 4 5 6 7 8 9 >numbers &&
|
||||
git commit -a -m change_b &&
|
||||
|
||||
git checkout feature_c &&
|
||||
test_write_lines 1 2 3 4 5 6 7 8 9 10 >numbers &&
|
||||
git commit -a -m change_c &&
|
||||
|
||||
git checkout bc_resolution &&
|
||||
git merge --ff-only feature_b &&
|
||||
# no conflict
|
||||
git merge feature_c &&
|
||||
|
||||
git checkout ab_resolution &&
|
||||
git merge --ff-only feature_a &&
|
||||
# conflicts!
|
||||
test_must_fail git merge feature_b &&
|
||||
# Resolve conflict...and make another change elsewhere
|
||||
test_write_lines 1 2 drei 4 5 6 7 acht 9 >numbers &&
|
||||
git add numbers &&
|
||||
git merge --continue
|
||||
'
|
||||
|
||||
test_expect_success 'remerge-diff on a clean merge' '
|
||||
git log -1 --oneline bc_resolution >expect &&
|
||||
git show --oneline --remerge-diff bc_resolution >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'remerge-diff with both a resolved conflict and an unrelated change' '
|
||||
git log -1 --oneline ab_resolution >tmp &&
|
||||
cat <<-EOF >>tmp &&
|
||||
diff --git a/numbers b/numbers
|
||||
remerge CONFLICT (content): Merge conflict in numbers
|
||||
index a1fb731..6875544 100644
|
||||
--- a/numbers
|
||||
+++ b/numbers
|
||||
@@ -1,13 +1,9 @@
|
||||
1
|
||||
2
|
||||
-<<<<<<< b0ed5cb (change_a)
|
||||
-three
|
||||
-=======
|
||||
-tres
|
||||
->>>>>>> 6cd3f82 (change_b)
|
||||
+drei
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
-eight
|
||||
+acht
|
||||
9
|
||||
EOF
|
||||
# Hashes above are sha1; rip them out so test works with sha256
|
||||
sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >expect &&
|
||||
|
||||
git show --oneline --remerge-diff ab_resolution >tmp &&
|
||||
sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'setup non-content conflicts' '
|
||||
git switch --orphan base &&
|
||||
|
||||
test_write_lines 1 2 3 4 5 6 7 8 9 >numbers &&
|
||||
test_write_lines a b c d e f g h i >letters &&
|
||||
test_write_lines in the way >content &&
|
||||
git add numbers letters content &&
|
||||
git commit -m base &&
|
||||
|
||||
git branch side1 &&
|
||||
git branch side2 &&
|
||||
|
||||
git checkout side1 &&
|
||||
test_write_lines 1 2 three 4 5 6 7 8 9 >numbers &&
|
||||
git mv letters letters_side1 &&
|
||||
git mv content file_or_directory &&
|
||||
git add numbers &&
|
||||
git commit -m side1 &&
|
||||
|
||||
git checkout side2 &&
|
||||
git rm numbers &&
|
||||
git mv letters letters_side2 &&
|
||||
mkdir file_or_directory &&
|
||||
echo hello >file_or_directory/world &&
|
||||
git add file_or_directory/world &&
|
||||
git commit -m side2 &&
|
||||
|
||||
git checkout -b resolution side1 &&
|
||||
test_must_fail git merge side2 &&
|
||||
test_write_lines 1 2 three 4 5 6 7 8 9 >numbers &&
|
||||
git add numbers &&
|
||||
git add letters_side1 &&
|
||||
git rm letters &&
|
||||
git rm letters_side2 &&
|
||||
git add file_or_directory~HEAD &&
|
||||
git mv file_or_directory~HEAD wanted_content &&
|
||||
git commit -m resolved
|
||||
'
|
||||
|
||||
test_expect_success 'remerge-diff with non-content conflicts' '
|
||||
git log -1 --oneline resolution >tmp &&
|
||||
cat <<-EOF >>tmp &&
|
||||
diff --git a/file_or_directory~HASH (side1) b/wanted_content
|
||||
similarity index 100%
|
||||
rename from file_or_directory~HASH (side1)
|
||||
rename to wanted_content
|
||||
remerge CONFLICT (file/directory): directory in the way of file_or_directory from HASH (side1); moving it to file_or_directory~HASH (side1) instead.
|
||||
diff --git a/letters b/letters
|
||||
remerge CONFLICT (rename/rename): letters renamed to letters_side1 in HASH (side1) and to letters_side2 in HASH (side2).
|
||||
diff --git a/letters_side2 b/letters_side2
|
||||
deleted file mode 100644
|
||||
index b236ae5..0000000
|
||||
--- a/letters_side2
|
||||
+++ /dev/null
|
||||
@@ -1,9 +0,0 @@
|
||||
-a
|
||||
-b
|
||||
-c
|
||||
-d
|
||||
-e
|
||||
-f
|
||||
-g
|
||||
-h
|
||||
-i
|
||||
diff --git a/numbers b/numbers
|
||||
remerge CONFLICT (modify/delete): numbers deleted in HASH (side2) and modified in HASH (side1). Version HASH (side1) of numbers left in tree.
|
||||
EOF
|
||||
# We still have some sha1 hashes above; rip them out so test works
|
||||
# with sha256
|
||||
sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >expect &&
|
||||
|
||||
git show --oneline --remerge-diff resolution >tmp &&
|
||||
sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'remerge-diff w/ diff-filter=U: all conflict headers, no diff content' '
|
||||
git log -1 --oneline resolution >tmp &&
|
||||
cat <<-EOF >>tmp &&
|
||||
diff --git a/file_or_directory~HASH (side1) b/file_or_directory~HASH (side1)
|
||||
remerge CONFLICT (file/directory): directory in the way of file_or_directory from HASH (side1); moving it to file_or_directory~HASH (side1) instead.
|
||||
diff --git a/letters b/letters
|
||||
remerge CONFLICT (rename/rename): letters renamed to letters_side1 in HASH (side1) and to letters_side2 in HASH (side2).
|
||||
diff --git a/numbers b/numbers
|
||||
remerge CONFLICT (modify/delete): numbers deleted in HASH (side2) and modified in HASH (side1). Version HASH (side1) of numbers left in tree.
|
||||
EOF
|
||||
# We still have some sha1 hashes above; rip them out so test works
|
||||
# with sha256
|
||||
sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >expect &&
|
||||
|
||||
git show --oneline --remerge-diff --diff-filter=U resolution >tmp &&
|
||||
sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'remerge-diff w/ diff-filter=R: relevant file + conflict header' '
|
||||
git log -1 --oneline resolution >tmp &&
|
||||
cat <<-EOF >>tmp &&
|
||||
diff --git a/file_or_directory~HASH (side1) b/wanted_content
|
||||
similarity index 100%
|
||||
rename from file_or_directory~HASH (side1)
|
||||
rename to wanted_content
|
||||
remerge CONFLICT (file/directory): directory in the way of file_or_directory from HASH (side1); moving it to file_or_directory~HASH (side1) instead.
|
||||
EOF
|
||||
# We still have some sha1 hashes above; rip them out so test works
|
||||
# with sha256
|
||||
sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >expect &&
|
||||
|
||||
git show --oneline --remerge-diff --diff-filter=R resolution >tmp &&
|
||||
sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'remerge-diff w/ pathspec: limits to relevant file including conflict header' '
|
||||
git log -1 --oneline resolution >tmp &&
|
||||
cat <<-EOF >>tmp &&
|
||||
diff --git a/letters b/letters
|
||||
remerge CONFLICT (rename/rename): letters renamed to letters_side1 in HASH (side1) and to letters_side2 in HASH (side2).
|
||||
diff --git a/letters_side2 b/letters_side2
|
||||
deleted file mode 100644
|
||||
index b236ae5..0000000
|
||||
--- a/letters_side2
|
||||
+++ /dev/null
|
||||
@@ -1,9 +0,0 @@
|
||||
-a
|
||||
-b
|
||||
-c
|
||||
-d
|
||||
-e
|
||||
-f
|
||||
-g
|
||||
-h
|
||||
-i
|
||||
EOF
|
||||
# We still have some sha1 hashes above; rip them out so test works
|
||||
# with sha256
|
||||
sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >expect &&
|
||||
|
||||
git show --oneline --remerge-diff resolution -- "letters*" >tmp &&
|
||||
sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'setup non-content conflicts' '
|
||||
git switch --orphan newbase &&
|
||||
|
||||
test_write_lines 1 2 3 4 5 6 7 8 9 >numbers &&
|
||||
git add numbers &&
|
||||
git commit -m base &&
|
||||
|
||||
git branch newside1 &&
|
||||
git branch newside2 &&
|
||||
|
||||
git checkout newside1 &&
|
||||
test_write_lines 1 2 three 4 5 6 7 8 9 >numbers &&
|
||||
git add numbers &&
|
||||
git commit -m side1 &&
|
||||
|
||||
git checkout newside2 &&
|
||||
test_write_lines 1 2 drei 4 5 6 7 8 9 >numbers &&
|
||||
git add numbers &&
|
||||
git commit -m side2 &&
|
||||
|
||||
git checkout -b newresolution newside1 &&
|
||||
test_must_fail git merge newside2 &&
|
||||
git checkout --theirs numbers &&
|
||||
git add -u numbers &&
|
||||
git commit -m resolved
|
||||
'
|
||||
|
||||
test_expect_success 'remerge-diff turns off history simplification' '
|
||||
git log -1 --oneline newresolution >tmp &&
|
||||
cat <<-EOF >>tmp &&
|
||||
diff --git a/numbers b/numbers
|
||||
remerge CONFLICT (content): Merge conflict in numbers
|
||||
index 070e9e7..5335e78 100644
|
||||
--- a/numbers
|
||||
+++ b/numbers
|
||||
@@ -1,10 +1,6 @@
|
||||
1
|
||||
2
|
||||
-<<<<<<< 96f1e45 (side1)
|
||||
-three
|
||||
-=======
|
||||
drei
|
||||
->>>>>>> 4fd522f (side2)
|
||||
4
|
||||
5
|
||||
6
|
||||
EOF
|
||||
# We still have some sha1 hashes above; rip them out so test works
|
||||
# with sha256
|
||||
sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >expect &&
|
||||
|
||||
git show --oneline --remerge-diff newresolution -- numbers >tmp &&
|
||||
sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_done
|
@ -108,8 +108,13 @@ test_expect_success 'refuse to merge binary files' '
|
||||
printf "\0\0" >binary-file &&
|
||||
git add binary-file &&
|
||||
git commit -m binary2 &&
|
||||
test_must_fail git merge F >merge.out 2>merge.err &&
|
||||
grep "Cannot merge binary files: binary-file (HEAD vs. F)" merge.err
|
||||
if test "$GIT_TEST_MERGE_ALGORITHM" = ort
|
||||
then
|
||||
test_must_fail git merge F >merge_output
|
||||
else
|
||||
test_must_fail git merge F 2>merge_output
|
||||
fi &&
|
||||
grep "Cannot merge binary files: binary-file (HEAD vs. F)" merge_output
|
||||
'
|
||||
|
||||
test_expect_success 'mark rename/delete as unmerged' '
|
||||
|
@ -221,8 +221,13 @@ test_expect_success 'binary files with union attribute' '
|
||||
printf "two\0" >bin.txt &&
|
||||
git commit -am two &&
|
||||
|
||||
test_must_fail git merge bin-main 2>stderr &&
|
||||
grep -i "warning.*cannot merge.*HEAD vs. bin-main" stderr
|
||||
if test "$GIT_TEST_MERGE_ALGORITHM" = ort
|
||||
then
|
||||
test_must_fail git merge bin-main >output
|
||||
else
|
||||
test_must_fail git merge bin-main 2>output
|
||||
fi &&
|
||||
grep -i "warning.*cannot merge.*HEAD vs. bin-main" output
|
||||
'
|
||||
|
||||
test_done
|
||||
|
@ -79,6 +79,11 @@ static void remove_tmp_objdir_on_signal(int signo)
|
||||
raise(signo);
|
||||
}
|
||||
|
||||
void tmp_objdir_discard_objects(struct tmp_objdir *t)
|
||||
{
|
||||
remove_dir_recursively(&t->path, REMOVE_DIR_KEEP_TOPLEVEL);
|
||||
}
|
||||
|
||||
/*
|
||||
* These env_* functions are for setting up the child environment; the
|
||||
* "replace" variant overrides the value of any existing variable with that
|
||||
|
@ -46,6 +46,12 @@ int tmp_objdir_migrate(struct tmp_objdir *);
|
||||
*/
|
||||
int tmp_objdir_destroy(struct tmp_objdir *);
|
||||
|
||||
/*
|
||||
* Remove all objects from the temporary object directory, while leaving it
|
||||
* around so more objects can be added.
|
||||
*/
|
||||
void tmp_objdir_discard_objects(struct tmp_objdir *);
|
||||
|
||||
/*
|
||||
* Add the temporary object directory as an alternate object store in the
|
||||
* current process.
|
||||
|
Loading…
Reference in New Issue
Block a user