mirror of
https://github.com/git/git
synced 2024-08-27 11:39:22 +00:00
![Phillip Wood](/assets/img/avatar_default.png)
"zdiff3" is identical to ordinary diff3 except that it allows compaction of common lines on the two sides of history at the beginning or end of the conflict hunk. For example, the following diff3 conflict: 1 2 3 4 <<<<<< A B C D E |||||| 5 6 ====== A X C Y E >>>>>> 7 8 9 has common lines 'A', 'C', and 'E' on the two sides. With zdiff3, one would instead get the following conflict: 1 2 3 4 A <<<<<< B C D |||||| 5 6 ====== X C Y >>>>>> E 7 8 9 Note that the common lines, 'A', and 'E' were moved outside the conflict. Unlike with the two-way conflicts from the 'merge' conflictStyle, the zdiff3 conflict is NOT split into multiple conflict regions to allow the common 'C' lines to be shown outside a conflict, because zdiff3 shows the base version too and the base version cannot be reasonably split. Note also that the removing of lines common to the two sides might make the remaining text inside the conflict region match the base text inside the conflict region (for example, if the diff3 conflict had '5 6 E' on the right side of the conflict, then the common line 'E' would be moved outside and both the base and right side's remaining conflict text would be the lines '5' and '6'). This has the potential to surprise users and make them think there should not have been a conflict, but there definitely was a conflict and it should remain. Based-on-patch-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> Co-authored-by: Elijah Newren <newren@gmail.com> Signed-off-by: Phillip Wood <phillip.wood123@gmail.com> Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
121 lines
3.3 KiB
C
121 lines
3.3 KiB
C
#include "builtin.h"
|
|
#include "cache.h"
|
|
#include "config.h"
|
|
#include "xdiff/xdiff.h"
|
|
#include "xdiff-interface.h"
|
|
#include "parse-options.h"
|
|
|
|
static const char *const merge_file_usage[] = {
|
|
N_("git merge-file [<options>] [-L <name1> [-L <orig> [-L <name2>]]] <file1> <orig-file> <file2>"),
|
|
NULL
|
|
};
|
|
|
|
static int label_cb(const struct option *opt, const char *arg, int unset)
|
|
{
|
|
static int label_count = 0;
|
|
const char **names = (const char **)opt->value;
|
|
|
|
BUG_ON_OPT_NEG(unset);
|
|
|
|
if (label_count >= 3)
|
|
return error("too many labels on the command line");
|
|
names[label_count++] = arg;
|
|
return 0;
|
|
}
|
|
|
|
int cmd_merge_file(int argc, const char **argv, const char *prefix)
|
|
{
|
|
const char *names[3] = { NULL, NULL, NULL };
|
|
mmfile_t mmfs[3];
|
|
mmbuffer_t result = {NULL, 0};
|
|
xmparam_t xmp = {{0}};
|
|
int ret = 0, i = 0, to_stdout = 0;
|
|
int quiet = 0;
|
|
struct option options[] = {
|
|
OPT_BOOL('p', "stdout", &to_stdout, N_("send results to standard output")),
|
|
OPT_SET_INT(0, "diff3", &xmp.style, N_("use a diff3 based merge"), XDL_MERGE_DIFF3),
|
|
OPT_SET_INT(0, "zdiff3", &xmp.style, N_("use a zealous diff3 based merge"),
|
|
XDL_MERGE_ZEALOUS_DIFF3),
|
|
OPT_SET_INT(0, "ours", &xmp.favor, N_("for conflicts, use our version"),
|
|
XDL_MERGE_FAVOR_OURS),
|
|
OPT_SET_INT(0, "theirs", &xmp.favor, N_("for conflicts, use their version"),
|
|
XDL_MERGE_FAVOR_THEIRS),
|
|
OPT_SET_INT(0, "union", &xmp.favor, N_("for conflicts, use a union version"),
|
|
XDL_MERGE_FAVOR_UNION),
|
|
OPT_INTEGER(0, "marker-size", &xmp.marker_size,
|
|
N_("for conflicts, use this marker size")),
|
|
OPT__QUIET(&quiet, N_("do not warn about conflicts")),
|
|
OPT_CALLBACK('L', NULL, names, N_("name"),
|
|
N_("set labels for file1/orig-file/file2"), &label_cb),
|
|
OPT_END(),
|
|
};
|
|
|
|
xmp.level = XDL_MERGE_ZEALOUS_ALNUM;
|
|
xmp.style = 0;
|
|
xmp.favor = 0;
|
|
|
|
if (startup_info->have_repository) {
|
|
/* Read the configuration file */
|
|
git_config(git_xmerge_config, NULL);
|
|
if (0 <= git_xmerge_style)
|
|
xmp.style = git_xmerge_style;
|
|
}
|
|
|
|
argc = parse_options(argc, argv, prefix, options, merge_file_usage, 0);
|
|
if (argc != 3)
|
|
usage_with_options(merge_file_usage, options);
|
|
if (quiet) {
|
|
if (!freopen("/dev/null", "w", stderr))
|
|
return error_errno("failed to redirect stderr to /dev/null");
|
|
}
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
char *fname;
|
|
int ret;
|
|
|
|
if (!names[i])
|
|
names[i] = argv[i];
|
|
|
|
fname = prefix_filename(prefix, argv[i]);
|
|
ret = read_mmfile(mmfs + i, fname);
|
|
free(fname);
|
|
if (ret)
|
|
return -1;
|
|
|
|
if (mmfs[i].size > MAX_XDIFF_SIZE ||
|
|
buffer_is_binary(mmfs[i].ptr, mmfs[i].size))
|
|
return error("Cannot merge binary files: %s",
|
|
argv[i]);
|
|
}
|
|
|
|
xmp.ancestor = names[1];
|
|
xmp.file1 = names[0];
|
|
xmp.file2 = names[2];
|
|
ret = xdl_merge(mmfs + 1, mmfs + 0, mmfs + 2, &xmp, &result);
|
|
|
|
for (i = 0; i < 3; i++)
|
|
free(mmfs[i].ptr);
|
|
|
|
if (ret >= 0) {
|
|
const char *filename = argv[0];
|
|
char *fpath = prefix_filename(prefix, argv[0]);
|
|
FILE *f = to_stdout ? stdout : fopen(fpath, "wb");
|
|
|
|
if (!f)
|
|
ret = error_errno("Could not open %s for writing",
|
|
filename);
|
|
else if (result.size &&
|
|
fwrite(result.ptr, result.size, 1, f) != 1)
|
|
ret = error_errno("Could not write to %s", filename);
|
|
else if (fclose(f))
|
|
ret = error_errno("Could not close %s", filename);
|
|
free(result.ptr);
|
|
free(fpath);
|
|
}
|
|
|
|
if (ret > 127)
|
|
ret = 127;
|
|
|
|
return ret;
|
|
}
|