git
/
merge-ll.c
462 строки · 12.1 Кб
1/*
2* Low level 3-way in-core file merge.
3*
4* Copyright (c) 2007 Junio C Hamano
5*/
6
7#define USE_THE_REPOSITORY_VARIABLE8
9#include "git-compat-util.h"10#include "config.h"11#include "convert.h"12#include "attr.h"13#include "xdiff-interface.h"14#include "run-command.h"15#include "merge-ll.h"16#include "quote.h"17#include "strbuf.h"18
19struct ll_merge_driver;20
21typedef enum ll_merge_result (*ll_merge_fn)(const struct ll_merge_driver *,22mmbuffer_t *result,23const char *path,24mmfile_t *orig, const char *orig_name,25mmfile_t *src1, const char *name1,26mmfile_t *src2, const char *name2,27const struct ll_merge_options *opts,28int marker_size);29
30struct ll_merge_driver {31const char *name;32const char *description;33ll_merge_fn fn;34char *recursive;35struct ll_merge_driver *next;36char *cmdline;37};38
39static struct attr_check *merge_attributes;40static struct attr_check *load_merge_attributes(void)41{
42if (!merge_attributes)43merge_attributes = attr_check_initl("merge", "conflict-marker-size", NULL);44return merge_attributes;45}
46
47void reset_merge_attributes(void)48{
49attr_check_free(merge_attributes);50merge_attributes = NULL;51}
52
53/*
54* Built-in low-levels
55*/
56static enum ll_merge_result ll_binary_merge(const struct ll_merge_driver *drv UNUSED,57mmbuffer_t *result,58const char *path UNUSED,59mmfile_t *orig, const char *orig_name UNUSED,60mmfile_t *src1, const char *name1 UNUSED,61mmfile_t *src2, const char *name2 UNUSED,62const struct ll_merge_options *opts,63int marker_size UNUSED)64{
65enum ll_merge_result ret;66mmfile_t *stolen;67assert(opts);68
69/*70* The tentative merge result is the common ancestor for an
71* internal merge. For the final merge, it is "ours" by
72* default but -Xours/-Xtheirs can tweak the choice.
73*/
74if (opts->virtual_ancestor) {75stolen = orig;76ret = LL_MERGE_OK;77} else {78switch (opts->variant) {79default:80ret = LL_MERGE_BINARY_CONFLICT;81stolen = src1;82break;83case XDL_MERGE_FAVOR_OURS:84ret = LL_MERGE_OK;85stolen = src1;86break;87case XDL_MERGE_FAVOR_THEIRS:88ret = LL_MERGE_OK;89stolen = src2;90break;91}92}93
94result->ptr = stolen->ptr;95result->size = stolen->size;96stolen->ptr = NULL;97
98return ret;99}
100
101static enum ll_merge_result ll_xdl_merge(const struct ll_merge_driver *drv_unused,102mmbuffer_t *result,103const char *path,104mmfile_t *orig, const char *orig_name,105mmfile_t *src1, const char *name1,106mmfile_t *src2, const char *name2,107const struct ll_merge_options *opts,108int marker_size)109{
110enum ll_merge_result ret;111xmparam_t xmp;112int status;113assert(opts);114
115if (orig->size > MAX_XDIFF_SIZE ||116src1->size > MAX_XDIFF_SIZE ||117src2->size > MAX_XDIFF_SIZE ||118buffer_is_binary(orig->ptr, orig->size) ||119buffer_is_binary(src1->ptr, src1->size) ||120buffer_is_binary(src2->ptr, src2->size)) {121return ll_binary_merge(drv_unused, result,122path,123orig, orig_name,124src1, name1,125src2, name2,126opts, marker_size);127}128
129memset(&xmp, 0, sizeof(xmp));130xmp.level = XDL_MERGE_ZEALOUS;131xmp.favor = opts->variant;132xmp.xpp.flags = opts->xdl_opts;133if (opts->conflict_style >= 0)134xmp.style = opts->conflict_style;135else if (git_xmerge_style >= 0)136xmp.style = git_xmerge_style;137if (marker_size > 0)138xmp.marker_size = marker_size;139xmp.ancestor = orig_name;140xmp.file1 = name1;141xmp.file2 = name2;142status = xdl_merge(orig, src1, src2, &xmp, result);143ret = (status > 0) ? LL_MERGE_CONFLICT : status;144return ret;145}
146
147static enum ll_merge_result ll_union_merge(const struct ll_merge_driver *drv_unused,148mmbuffer_t *result,149const char *path,150mmfile_t *orig, const char *orig_name,151mmfile_t *src1, const char *name1,152mmfile_t *src2, const char *name2,153const struct ll_merge_options *opts,154int marker_size)155{
156/* Use union favor */157struct ll_merge_options o;158assert(opts);159o = *opts;160o.variant = XDL_MERGE_FAVOR_UNION;161return ll_xdl_merge(drv_unused, result, path,162orig, orig_name, src1, name1, src2, name2,163&o, marker_size);164}
165
166#define LL_BINARY_MERGE 0167#define LL_TEXT_MERGE 1168#define LL_UNION_MERGE 2169static struct ll_merge_driver ll_merge_drv[] = {170{ "binary", "built-in binary merge", ll_binary_merge },171{ "text", "built-in 3-way text merge", ll_xdl_merge },172{ "union", "built-in union merge", ll_union_merge },173};174
175static void create_temp(mmfile_t *src, char *path, size_t len)176{
177int fd;178
179xsnprintf(path, len, ".merge_file_XXXXXX");180fd = xmkstemp(path);181if (write_in_full(fd, src->ptr, src->size) < 0)182die_errno("unable to write temp-file");183close(fd);184}
185
186/*
187* User defined low-level merge driver support.
188*/
189static enum ll_merge_result ll_ext_merge(const struct ll_merge_driver *fn,190mmbuffer_t *result,191const char *path,192mmfile_t *orig, const char *orig_name,193mmfile_t *src1, const char *name1,194mmfile_t *src2, const char *name2,195const struct ll_merge_options *opts,196int marker_size)197{
198char temp[3][50];199struct strbuf cmd = STRBUF_INIT;200const char *format = fn->cmdline;201struct child_process child = CHILD_PROCESS_INIT;202int status, fd, i;203struct stat st;204enum ll_merge_result ret;205assert(opts);206
207if (!fn->cmdline)208die("custom merge driver %s lacks command line.", fn->name);209
210result->ptr = NULL;211result->size = 0;212create_temp(orig, temp[0], sizeof(temp[0]));213create_temp(src1, temp[1], sizeof(temp[1]));214create_temp(src2, temp[2], sizeof(temp[2]));215
216while (strbuf_expand_step(&cmd, &format)) {217if (skip_prefix(format, "%", &format))218strbuf_addch(&cmd, '%');219else if (skip_prefix(format, "O", &format))220strbuf_addstr(&cmd, temp[0]);221else if (skip_prefix(format, "A", &format))222strbuf_addstr(&cmd, temp[1]);223else if (skip_prefix(format, "B", &format))224strbuf_addstr(&cmd, temp[2]);225else if (skip_prefix(format, "L", &format))226strbuf_addf(&cmd, "%d", marker_size);227else if (skip_prefix(format, "P", &format))228sq_quote_buf(&cmd, path);229else if (skip_prefix(format, "S", &format))230sq_quote_buf(&cmd, orig_name ? orig_name : "");231else if (skip_prefix(format, "X", &format))232sq_quote_buf(&cmd, name1 ? name1 : "");233else if (skip_prefix(format, "Y", &format))234sq_quote_buf(&cmd, name2 ? name2 : "");235else236strbuf_addch(&cmd, '%');237}238
239child.use_shell = 1;240strvec_push(&child.args, cmd.buf);241status = run_command(&child);242fd = open(temp[1], O_RDONLY);243if (fd < 0)244goto bad;245if (fstat(fd, &st))246goto close_bad;247result->size = st.st_size;248result->ptr = xmallocz(result->size);249if (read_in_full(fd, result->ptr, result->size) != result->size) {250FREE_AND_NULL(result->ptr);251result->size = 0;252}253close_bad:254close(fd);255bad:256for (i = 0; i < 3; i++)257unlink_or_warn(temp[i]);258strbuf_release(&cmd);259if (!status)260ret = LL_MERGE_OK;261else if (status <= 128)262ret = LL_MERGE_CONFLICT;263else264/* died due to a signal: WTERMSIG(status) + 128 */265ret = LL_MERGE_ERROR;266return ret;267}
268
269/*
270* merge.default and merge.driver configuration items
271*/
272static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail;273static char *default_ll_merge;274
275static int read_merge_config(const char *var, const char *value,276const struct config_context *ctx UNUSED,277void *cb UNUSED)278{
279struct ll_merge_driver *fn;280const char *key, *name;281size_t namelen;282
283if (!strcmp(var, "merge.default"))284return git_config_string(&default_ll_merge, var, value);285
286/*287* We are not interested in anything but "merge.<name>.variable";
288* especially, we do not want to look at variables such as
289* "merge.summary", "merge.tool", and "merge.verbosity".
290*/
291if (parse_config_key(var, "merge", &name, &namelen, &key) < 0 || !name)292return 0;293
294/*295* Find existing one as we might be processing merge.<name>.var2
296* after seeing merge.<name>.var1.
297*/
298for (fn = ll_user_merge; fn; fn = fn->next)299if (!xstrncmpz(fn->name, name, namelen))300break;301if (!fn) {302CALLOC_ARRAY(fn, 1);303fn->name = xmemdupz(name, namelen);304fn->fn = ll_ext_merge;305*ll_user_merge_tail = fn;306ll_user_merge_tail = &(fn->next);307}308
309if (!strcmp("name", key)) {310/*311* The description is leaking, but that's okay as we want to
312* keep around the merge drivers anyway.
313*/
314return git_config_string((char **) &fn->description, var, value);315}316
317if (!strcmp("driver", key)) {318if (!value)319return config_error_nonbool(var);320/*321* merge.<name>.driver specifies the command line:
322*
323* command-line
324*
325* The command-line will be interpolated with the following
326* tokens and is given to the shell:
327*
328* %O - temporary file name for the merge base.
329* %A - temporary file name for our version.
330* %B - temporary file name for the other branches' version.
331* %L - conflict marker length
332* %P - the original path (safely quoted for the shell)
333* %S - the revision for the merge base
334* %X - the revision for our version
335* %Y - the revision for their version
336*
337* If the file is not named indentically in all versions, then each
338* revision is joined with the corresponding path, separated by a colon.
339* The external merge driver should write the results in the
340* file named by %A, and signal that it has done with zero exit
341* status.
342*/
343fn->cmdline = xstrdup(value);344return 0;345}346
347if (!strcmp("recursive", key))348return git_config_string(&fn->recursive, var, value);349
350return 0;351}
352
353static void initialize_ll_merge(void)354{
355if (ll_user_merge_tail)356return;357ll_user_merge_tail = &ll_user_merge;358git_config(read_merge_config, NULL);359}
360
361static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr)362{
363struct ll_merge_driver *fn;364const char *name;365int i;366
367initialize_ll_merge();368
369if (ATTR_TRUE(merge_attr))370return &ll_merge_drv[LL_TEXT_MERGE];371else if (ATTR_FALSE(merge_attr))372return &ll_merge_drv[LL_BINARY_MERGE];373else if (ATTR_UNSET(merge_attr)) {374if (!default_ll_merge)375return &ll_merge_drv[LL_TEXT_MERGE];376else377name = default_ll_merge;378}379else380name = merge_attr;381
382for (fn = ll_user_merge; fn; fn = fn->next)383if (!strcmp(fn->name, name))384return fn;385
386for (i = 0; i < ARRAY_SIZE(ll_merge_drv); i++)387if (!strcmp(ll_merge_drv[i].name, name))388return &ll_merge_drv[i];389
390/* default to the 3-way */391return &ll_merge_drv[LL_TEXT_MERGE];392}
393
394static void normalize_file(mmfile_t *mm, const char *path, struct index_state *istate)395{
396struct strbuf strbuf = STRBUF_INIT;397if (renormalize_buffer(istate, path, mm->ptr, mm->size, &strbuf)) {398free(mm->ptr);399mm->size = strbuf.len;400mm->ptr = strbuf_detach(&strbuf, NULL);401}402}
403
404enum ll_merge_result ll_merge(mmbuffer_t *result_buf,405const char *path,406mmfile_t *ancestor, const char *ancestor_label,407mmfile_t *ours, const char *our_label,408mmfile_t *theirs, const char *their_label,409struct index_state *istate,410const struct ll_merge_options *opts)411{
412struct attr_check *check = load_merge_attributes();413static const struct ll_merge_options default_opts = LL_MERGE_OPTIONS_INIT;414const char *ll_driver_name = NULL;415int marker_size = DEFAULT_CONFLICT_MARKER_SIZE;416const struct ll_merge_driver *driver;417
418if (!opts)419opts = &default_opts;420
421if (opts->renormalize) {422normalize_file(ancestor, path, istate);423normalize_file(ours, path, istate);424normalize_file(theirs, path, istate);425}426
427git_check_attr(istate, path, check);428ll_driver_name = check->items[0].value;429if (check->items[1].value) {430marker_size = atoi(check->items[1].value);431if (marker_size <= 0)432marker_size = DEFAULT_CONFLICT_MARKER_SIZE;433}434driver = find_ll_merge_driver(ll_driver_name);435
436if (opts->virtual_ancestor) {437if (driver->recursive)438driver = find_ll_merge_driver(driver->recursive);439}440if (opts->extra_marker_size) {441marker_size += opts->extra_marker_size;442}443return driver->fn(driver, result_buf, path, ancestor, ancestor_label,444ours, our_label, theirs, their_label,445opts, marker_size);446}
447
448int ll_merge_marker_size(struct index_state *istate, const char *path)449{
450static struct attr_check *check;451int marker_size = DEFAULT_CONFLICT_MARKER_SIZE;452
453if (!check)454check = attr_check_initl("conflict-marker-size", NULL);455git_check_attr(istate, path, check);456if (check->items[0].value) {457marker_size = atoi(check->items[0].value);458if (marker_size <= 0)459marker_size = DEFAULT_CONFLICT_MARKER_SIZE;460}461return marker_size;462}
463