From 6723899932eb5b6436e9bac65ffc9b6e644c7fee Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Tue, 16 May 2023 06:34:04 +0000 Subject: merge-ll: rename from ll-merge A long term (but rather minor) pet-peeve of mine was the name ll-merge.[ch]. I thought it made it harder to realize what stuff was related to merging when I was working on the merge machinery and trying to improve it. Further, back in d1cbe1e6d8a ("hash-ll.h: split out of hash.h to remove dependency on repository.h", 2023-04-22), we have split the portions of hash.h that do not depend upon repository.h into a "hash-ll.h" (due to the recommendation to use "ll" for "low-level" in its name[1], but which I used as a suffix precisely because of my distaste for "ll-merge"). When we discussed adding additional "*-ll.h" files, a request was made that we use "ll" consistently as either a prefix or a suffix. Since it is already in use as both a prefix and a suffix, the only way to do so is to rename some files. Besides my distaste for the ll-merge.[ch] name, let me also note that the files ll-fsmonitor.h, ll-hash.h, ll-merge.h, ll-object-store.h, ll-read-cache.h would have essentially nothing to do with each other and make no sense to group. But giving them the common "ll-" prefix would group them. Using "-ll" as a suffix thus seems just much more logical to me. Rename ll-merge.[ch] to merge-ll.[ch] to achieve this consistency, and to ensure we get a more logical grouping of files. [1] https://lore.kernel.org/git/kl6lsfcu1g8w.fsf@chooglen-macbookpro.roam.corp.google.com/ Signed-off-by: Elijah Newren Signed-off-by: Junio C Hamano --- Documentation/technical/api-merge.txt | 4 +- Makefile | 2 +- apply.c | 2 +- builtin/checkout.c | 2 +- convert.c | 2 +- diff.c | 2 +- ll-merge.c | 432 ---------------------------------- ll-merge.h | 109 --------- merge-blobs.c | 2 +- merge-ll.c | 432 ++++++++++++++++++++++++++++++++++ merge-ll.h | 109 +++++++++ merge-ort.c | 2 +- merge-recursive.c | 2 +- notes-merge.c | 2 +- rerere.c | 2 +- 15 files changed, 553 insertions(+), 553 deletions(-) delete mode 100644 ll-merge.c delete mode 100644 ll-merge.h create mode 100644 merge-ll.c create mode 100644 merge-ll.h diff --git a/Documentation/technical/api-merge.txt b/Documentation/technical/api-merge.txt index 487d4d83ff..c2ba01828c 100644 --- a/Documentation/technical/api-merge.txt +++ b/Documentation/technical/api-merge.txt @@ -28,9 +28,9 @@ and `diff.c` for examples. * `struct ll_merge_options` -Check ll-merge.h for details. +Check merge-ll.h for details. Low-level (single file) merge ----------------------------- -Check ll-merge.h for details. +Check merge-ll.h for details. diff --git a/Makefile b/Makefile index e62db9460d..fb541dedc9 100644 --- a/Makefile +++ b/Makefile @@ -1051,7 +1051,6 @@ LIB_OBJS += linear-assignment.o LIB_OBJS += list-objects-filter-options.o LIB_OBJS += list-objects-filter.o LIB_OBJS += list-objects.o -LIB_OBJS += ll-merge.o LIB_OBJS += lockfile.o LIB_OBJS += log-tree.o LIB_OBJS += ls-refs.o @@ -1060,6 +1059,7 @@ LIB_OBJS += mailmap.o LIB_OBJS += match-trees.o LIB_OBJS += mem-pool.o LIB_OBJS += merge-blobs.o +LIB_OBJS += merge-ll.o LIB_OBJS += merge-ort.o LIB_OBJS += merge-ort-wrappers.o LIB_OBJS += merge-recursive.o diff --git a/apply.c b/apply.c index 801f2bcc99..2f66f93fec 100644 --- a/apply.c +++ b/apply.c @@ -21,7 +21,7 @@ #include "gettext.h" #include "hex.h" #include "xdiff-interface.h" -#include "ll-merge.h" +#include "merge-ll.h" #include "lockfile.h" #include "name-hash.h" #include "object-name.h" diff --git a/builtin/checkout.c b/builtin/checkout.c index 716dcd4cae..11e2359c54 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -13,7 +13,7 @@ #include "gettext.h" #include "hex.h" #include "hook.h" -#include "ll-merge.h" +#include "merge-ll.h" #include "lockfile.h" #include "mem-pool.h" #include "merge-recursive.h" diff --git a/convert.c b/convert.c index 89aeb9e72b..572d7123a9 100644 --- a/convert.c +++ b/convert.c @@ -15,7 +15,7 @@ #include "sub-process.h" #include "trace.h" #include "utf8.h" -#include "ll-merge.h" +#include "merge-ll.h" #include "wrapper.h" /* diff --git a/diff.c b/diff.c index 7fb9abe891..d02fbf507e 100644 --- a/diff.c +++ b/diff.c @@ -26,7 +26,7 @@ #include "submodule.h" #include "hashmap.h" #include "mem-pool.h" -#include "ll-merge.h" +#include "merge-ll.h" #include "string-list.h" #include "strvec.h" #include "graph.h" diff --git a/ll-merge.c b/ll-merge.c deleted file mode 100644 index 07ec16e8e5..0000000000 --- a/ll-merge.c +++ /dev/null @@ -1,432 +0,0 @@ -/* - * Low level 3-way in-core file merge. - * - * Copyright (c) 2007 Junio C Hamano - */ - -#include "git-compat-util.h" -#include "config.h" -#include "convert.h" -#include "attr.h" -#include "xdiff-interface.h" -#include "run-command.h" -#include "ll-merge.h" -#include "quote.h" -#include "strbuf.h" -#include "wrapper.h" - -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, - mmfile_t *src1, const char *name1, - mmfile_t *src2, const char *name2, - const struct ll_merge_options *opts, - int marker_size); - -struct ll_merge_driver { - const char *name; - const char *description; - ll_merge_fn fn; - const char *recursive; - struct ll_merge_driver *next; - char *cmdline; -}; - -static struct attr_check *merge_attributes; -static struct attr_check *load_merge_attributes(void) -{ - if (!merge_attributes) - merge_attributes = attr_check_initl("merge", "conflict-marker-size", NULL); - return merge_attributes; -} - -void reset_merge_attributes(void) -{ - attr_check_free(merge_attributes); - merge_attributes = NULL; -} - -/* - * Built-in low-levels - */ -static enum ll_merge_result ll_binary_merge(const struct ll_merge_driver *drv UNUSED, - mmbuffer_t *result, - const char *path UNUSED, - mmfile_t *orig, const char *orig_name UNUSED, - mmfile_t *src1, const char *name1 UNUSED, - mmfile_t *src2, const char *name2 UNUSED, - const struct ll_merge_options *opts, - int marker_size UNUSED) -{ - enum ll_merge_result ret; - mmfile_t *stolen; - assert(opts); - - /* - * The tentative merge result is the common ancestor for an - * internal merge. For the final merge, it is "ours" by - * default but -Xours/-Xtheirs can tweak the choice. - */ - if (opts->virtual_ancestor) { - stolen = orig; - ret = LL_MERGE_OK; - } else { - switch (opts->variant) { - default: - 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; - } - } - - result->ptr = stolen->ptr; - result->size = stolen->size; - stolen->ptr = NULL; - - return ret; -} - -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, - mmfile_t *src1, const char *name1, - mmfile_t *src2, const char *name2, - 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 || - src1->size > MAX_XDIFF_SIZE || - src2->size > MAX_XDIFF_SIZE || - buffer_is_binary(orig->ptr, orig->size) || - buffer_is_binary(src1->ptr, src1->size) || - buffer_is_binary(src2->ptr, src2->size)) { - return ll_binary_merge(drv_unused, result, - path, - orig, orig_name, - src1, name1, - src2, name2, - opts, marker_size); - } - - memset(&xmp, 0, sizeof(xmp)); - xmp.level = XDL_MERGE_ZEALOUS; - xmp.favor = opts->variant; - xmp.xpp.flags = opts->xdl_opts; - if (git_xmerge_style >= 0) - xmp.style = git_xmerge_style; - if (marker_size > 0) - xmp.marker_size = marker_size; - xmp.ancestor = orig_name; - xmp.file1 = name1; - xmp.file2 = name2; - status = xdl_merge(orig, src1, src2, &xmp, result); - ret = (status > 0) ? LL_MERGE_CONFLICT : status; - return ret; -} - -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, - mmfile_t *src1, const char *name1, - mmfile_t *src2, const char *name2, - const struct ll_merge_options *opts, - int marker_size) -{ - /* Use union favor */ - struct ll_merge_options o; - assert(opts); - o = *opts; - o.variant = XDL_MERGE_FAVOR_UNION; - return ll_xdl_merge(drv_unused, result, path, - orig, orig_name, src1, name1, src2, name2, - &o, marker_size); -} - -#define LL_BINARY_MERGE 0 -#define LL_TEXT_MERGE 1 -#define LL_UNION_MERGE 2 -static struct ll_merge_driver ll_merge_drv[] = { - { "binary", "built-in binary merge", ll_binary_merge }, - { "text", "built-in 3-way text merge", ll_xdl_merge }, - { "union", "built-in union merge", ll_union_merge }, -}; - -static void create_temp(mmfile_t *src, char *path, size_t len) -{ - int fd; - - xsnprintf(path, len, ".merge_file_XXXXXX"); - fd = xmkstemp(path); - if (write_in_full(fd, src->ptr, src->size) < 0) - die_errno("unable to write temp-file"); - close(fd); -} - -/* - * User defined low-level merge driver support. - */ -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 UNUSED, - mmfile_t *src1, const char *name1 UNUSED, - mmfile_t *src2, const char *name2 UNUSED, - const struct ll_merge_options *opts, - int marker_size) -{ - char temp[4][50]; - struct strbuf cmd = STRBUF_INIT; - struct strbuf_expand_dict_entry dict[6]; - struct strbuf path_sq = STRBUF_INIT; - struct child_process child = CHILD_PROCESS_INIT; - int status, fd, i; - struct stat st; - enum ll_merge_result ret; - assert(opts); - - sq_quote_buf(&path_sq, path); - dict[0].placeholder = "O"; dict[0].value = temp[0]; - dict[1].placeholder = "A"; dict[1].value = temp[1]; - dict[2].placeholder = "B"; dict[2].value = temp[2]; - dict[3].placeholder = "L"; dict[3].value = temp[3]; - dict[4].placeholder = "P"; dict[4].value = path_sq.buf; - dict[5].placeholder = NULL; dict[5].value = NULL; - - if (!fn->cmdline) - die("custom merge driver %s lacks command line.", fn->name); - - result->ptr = NULL; - result->size = 0; - create_temp(orig, temp[0], sizeof(temp[0])); - create_temp(src1, temp[1], sizeof(temp[1])); - create_temp(src2, temp[2], sizeof(temp[2])); - xsnprintf(temp[3], sizeof(temp[3]), "%d", marker_size); - - strbuf_expand(&cmd, fn->cmdline, strbuf_expand_dict_cb, &dict); - - child.use_shell = 1; - strvec_push(&child.args, cmd.buf); - status = run_command(&child); - fd = open(temp[1], O_RDONLY); - if (fd < 0) - goto bad; - if (fstat(fd, &st)) - goto close_bad; - result->size = st.st_size; - result->ptr = xmallocz(result->size); - if (read_in_full(fd, result->ptr, result->size) != result->size) { - FREE_AND_NULL(result->ptr); - result->size = 0; - } - close_bad: - close(fd); - bad: - for (i = 0; i < 3; i++) - unlink_or_warn(temp[i]); - strbuf_release(&cmd); - strbuf_release(&path_sq); - ret = (status > 0) ? LL_MERGE_CONFLICT : status; - return ret; -} - -/* - * merge.default and merge.driver configuration items - */ -static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail; -static const char *default_ll_merge; - -static int read_merge_config(const char *var, const char *value, - void *cb UNUSED) -{ - struct ll_merge_driver *fn; - const char *key, *name; - size_t namelen; - - if (!strcmp(var, "merge.default")) - return git_config_string(&default_ll_merge, var, value); - - /* - * We are not interested in anything but "merge..variable"; - * especially, we do not want to look at variables such as - * "merge.summary", "merge.tool", and "merge.verbosity". - */ - if (parse_config_key(var, "merge", &name, &namelen, &key) < 0 || !name) - return 0; - - /* - * Find existing one as we might be processing merge..var2 - * after seeing merge..var1. - */ - for (fn = ll_user_merge; fn; fn = fn->next) - if (!strncmp(fn->name, name, namelen) && !fn->name[namelen]) - break; - if (!fn) { - CALLOC_ARRAY(fn, 1); - fn->name = xmemdupz(name, namelen); - fn->fn = ll_ext_merge; - *ll_user_merge_tail = fn; - ll_user_merge_tail = &(fn->next); - } - - if (!strcmp("name", key)) - return git_config_string(&fn->description, var, value); - - if (!strcmp("driver", key)) { - if (!value) - return error("%s: lacks value", var); - /* - * merge..driver specifies the command line: - * - * command-line - * - * The command-line will be interpolated with the following - * tokens and is given to the shell: - * - * %O - temporary file name for the merge base. - * %A - temporary file name for our version. - * %B - temporary file name for the other branches' version. - * %L - conflict marker length - * %P - the original path (safely quoted for the shell) - * - * The external merge driver should write the results in the - * file named by %A, and signal that it has done with zero exit - * status. - */ - fn->cmdline = xstrdup(value); - return 0; - } - - if (!strcmp("recursive", key)) - return git_config_string(&fn->recursive, var, value); - - return 0; -} - -static void initialize_ll_merge(void) -{ - if (ll_user_merge_tail) - return; - ll_user_merge_tail = &ll_user_merge; - git_config(read_merge_config, NULL); -} - -static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr) -{ - struct ll_merge_driver *fn; - const char *name; - int i; - - initialize_ll_merge(); - - if (ATTR_TRUE(merge_attr)) - return &ll_merge_drv[LL_TEXT_MERGE]; - else if (ATTR_FALSE(merge_attr)) - return &ll_merge_drv[LL_BINARY_MERGE]; - else if (ATTR_UNSET(merge_attr)) { - if (!default_ll_merge) - return &ll_merge_drv[LL_TEXT_MERGE]; - else - name = default_ll_merge; - } - else - name = merge_attr; - - for (fn = ll_user_merge; fn; fn = fn->next) - if (!strcmp(fn->name, name)) - return fn; - - for (i = 0; i < ARRAY_SIZE(ll_merge_drv); i++) - if (!strcmp(ll_merge_drv[i].name, name)) - return &ll_merge_drv[i]; - - /* default to the 3-way */ - return &ll_merge_drv[LL_TEXT_MERGE]; -} - -static void normalize_file(mmfile_t *mm, const char *path, struct index_state *istate) -{ - struct strbuf strbuf = STRBUF_INIT; - if (renormalize_buffer(istate, path, mm->ptr, mm->size, &strbuf)) { - free(mm->ptr); - mm->size = strbuf.len; - mm->ptr = strbuf_detach(&strbuf, NULL); - } -} - -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, - mmfile_t *theirs, const char *their_label, - struct index_state *istate, - const struct ll_merge_options *opts) -{ - struct attr_check *check = load_merge_attributes(); - static const struct ll_merge_options default_opts; - const char *ll_driver_name = NULL; - int marker_size = DEFAULT_CONFLICT_MARKER_SIZE; - const struct ll_merge_driver *driver; - - if (!opts) - opts = &default_opts; - - if (opts->renormalize) { - normalize_file(ancestor, path, istate); - normalize_file(ours, path, istate); - normalize_file(theirs, path, istate); - } - - git_check_attr(istate, path, check); - ll_driver_name = check->items[0].value; - if (check->items[1].value) { - marker_size = atoi(check->items[1].value); - if (marker_size <= 0) - marker_size = DEFAULT_CONFLICT_MARKER_SIZE; - } - driver = find_ll_merge_driver(ll_driver_name); - - if (opts->virtual_ancestor) { - if (driver->recursive) - driver = find_ll_merge_driver(driver->recursive); - } - if (opts->extra_marker_size) { - marker_size += opts->extra_marker_size; - } - return driver->fn(driver, result_buf, path, ancestor, ancestor_label, - ours, our_label, theirs, their_label, - opts, marker_size); -} - -int ll_merge_marker_size(struct index_state *istate, const char *path) -{ - static struct attr_check *check; - int marker_size = DEFAULT_CONFLICT_MARKER_SIZE; - - if (!check) - check = attr_check_initl("conflict-marker-size", NULL); - git_check_attr(istate, path, check); - if (check->items[0].value) { - marker_size = atoi(check->items[0].value); - if (marker_size <= 0) - marker_size = DEFAULT_CONFLICT_MARKER_SIZE; - } - return marker_size; -} diff --git a/ll-merge.h b/ll-merge.h deleted file mode 100644 index e4a20e81a3..0000000000 --- a/ll-merge.h +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Low level 3-way in-core file merge. - */ - -#ifndef LL_MERGE_H -#define LL_MERGE_H - -#include "xdiff/xdiff.h" - -/** - * - * Calling sequence: - * ---------------- - * - * - Prepare a `struct ll_merge_options` to record options. - * If you have no special requests, skip this and pass `NULL` - * as the `opts` parameter to use the default options. - * - * - Allocate an mmbuffer_t variable for the result. - * - * - Allocate and fill variables with the file's original content - * and two modified versions (using `read_mmfile`, for example). - * - * - Call `ll_merge()`. - * - * - Read the merged content from `result_buf.ptr` and `result_buf.size`. - * - * - Release buffers when finished. A simple - * `free(ancestor.ptr); free(ours.ptr); free(theirs.ptr); - * free(result_buf.ptr);` will do. - * - * If the modifications do not merge cleanly, `ll_merge` will return a - * nonzero value and `result_buf` will generally include a description of - * the conflict bracketed by markers such as the traditional `<<<<<<<` - * and `>>>>>>>`. - * - * The `ancestor_label`, `our_label`, and `their_label` parameters are - * used to label the different sides of a conflict if the merge driver - * supports this. - */ - - -struct index_state; - -/** - * This describes the set of options the calling program wants to affect - * the operation of a low-level (single file) merge. - */ -struct ll_merge_options { - - /** - * Behave as though this were part of a merge between common ancestors in - * a recursive merge (merges of binary files may need to be handled - * differently in such cases, for example). If a helper program is - * specified by the `[merge ""] recursive` configuration, it will - * be used. - */ - unsigned virtual_ancestor : 1; - - /** - * Resolve local conflicts automatically in favor of one side or the other - * (as in 'git merge-file' `--ours`/`--theirs`/`--union`). Can be `0`, - * `XDL_MERGE_FAVOR_OURS`, `XDL_MERGE_FAVOR_THEIRS`, - * or `XDL_MERGE_FAVOR_UNION`. - */ - unsigned variant : 2; - - /** - * Resmudge and clean the "base", "theirs" and "ours" files before merging. - * Use this when the merge is likely to have overlapped with a change in - * smudge/clean or end-of-line normalization rules. - */ - unsigned renormalize : 1; - - /** - * Increase the length of conflict markers so that nested conflicts -  * can be differentiated. - */ - unsigned extra_marker_size; - - /* Extra xpparam_t flags as defined in xdiff/xdiff.h. */ - 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. - */ -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, - mmfile_t *theirs, const char *their_label, - struct index_state *istate, - const struct ll_merge_options *opts); - -int ll_merge_marker_size(struct index_state *istate, const char *path); -void reset_merge_attributes(void); - -#endif diff --git a/merge-blobs.c b/merge-blobs.c index 5632ff6abb..40c48e3eba 100644 --- a/merge-blobs.c +++ b/merge-blobs.c @@ -1,7 +1,7 @@ #include "git-compat-util.h" #include "run-command.h" #include "xdiff-interface.h" -#include "ll-merge.h" +#include "merge-ll.h" #include "blob.h" #include "merge-blobs.h" #include "object-store.h" diff --git a/merge-ll.c b/merge-ll.c new file mode 100644 index 0000000000..740b8c6bfd --- /dev/null +++ b/merge-ll.c @@ -0,0 +1,432 @@ +/* + * Low level 3-way in-core file merge. + * + * Copyright (c) 2007 Junio C Hamano + */ + +#include "git-compat-util.h" +#include "config.h" +#include "convert.h" +#include "attr.h" +#include "xdiff-interface.h" +#include "run-command.h" +#include "merge-ll.h" +#include "quote.h" +#include "strbuf.h" +#include "wrapper.h" + +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, + mmfile_t *src1, const char *name1, + mmfile_t *src2, const char *name2, + const struct ll_merge_options *opts, + int marker_size); + +struct ll_merge_driver { + const char *name; + const char *description; + ll_merge_fn fn; + const char *recursive; + struct ll_merge_driver *next; + char *cmdline; +}; + +static struct attr_check *merge_attributes; +static struct attr_check *load_merge_attributes(void) +{ + if (!merge_attributes) + merge_attributes = attr_check_initl("merge", "conflict-marker-size", NULL); + return merge_attributes; +} + +void reset_merge_attributes(void) +{ + attr_check_free(merge_attributes); + merge_attributes = NULL; +} + +/* + * Built-in low-levels + */ +static enum ll_merge_result ll_binary_merge(const struct ll_merge_driver *drv UNUSED, + mmbuffer_t *result, + const char *path UNUSED, + mmfile_t *orig, const char *orig_name UNUSED, + mmfile_t *src1, const char *name1 UNUSED, + mmfile_t *src2, const char *name2 UNUSED, + const struct ll_merge_options *opts, + int marker_size UNUSED) +{ + enum ll_merge_result ret; + mmfile_t *stolen; + assert(opts); + + /* + * The tentative merge result is the common ancestor for an + * internal merge. For the final merge, it is "ours" by + * default but -Xours/-Xtheirs can tweak the choice. + */ + if (opts->virtual_ancestor) { + stolen = orig; + ret = LL_MERGE_OK; + } else { + switch (opts->variant) { + default: + 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; + } + } + + result->ptr = stolen->ptr; + result->size = stolen->size; + stolen->ptr = NULL; + + return ret; +} + +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, + mmfile_t *src1, const char *name1, + mmfile_t *src2, const char *name2, + 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 || + src1->size > MAX_XDIFF_SIZE || + src2->size > MAX_XDIFF_SIZE || + buffer_is_binary(orig->ptr, orig->size) || + buffer_is_binary(src1->ptr, src1->size) || + buffer_is_binary(src2->ptr, src2->size)) { + return ll_binary_merge(drv_unused, result, + path, + orig, orig_name, + src1, name1, + src2, name2, + opts, marker_size); + } + + memset(&xmp, 0, sizeof(xmp)); + xmp.level = XDL_MERGE_ZEALOUS; + xmp.favor = opts->variant; + xmp.xpp.flags = opts->xdl_opts; + if (git_xmerge_style >= 0) + xmp.style = git_xmerge_style; + if (marker_size > 0) + xmp.marker_size = marker_size; + xmp.ancestor = orig_name; + xmp.file1 = name1; + xmp.file2 = name2; + status = xdl_merge(orig, src1, src2, &xmp, result); + ret = (status > 0) ? LL_MERGE_CONFLICT : status; + return ret; +} + +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, + mmfile_t *src1, const char *name1, + mmfile_t *src2, const char *name2, + const struct ll_merge_options *opts, + int marker_size) +{ + /* Use union favor */ + struct ll_merge_options o; + assert(opts); + o = *opts; + o.variant = XDL_MERGE_FAVOR_UNION; + return ll_xdl_merge(drv_unused, result, path, + orig, orig_name, src1, name1, src2, name2, + &o, marker_size); +} + +#define LL_BINARY_MERGE 0 +#define LL_TEXT_MERGE 1 +#define LL_UNION_MERGE 2 +static struct ll_merge_driver ll_merge_drv[] = { + { "binary", "built-in binary merge", ll_binary_merge }, + { "text", "built-in 3-way text merge", ll_xdl_merge }, + { "union", "built-in union merge", ll_union_merge }, +}; + +static void create_temp(mmfile_t *src, char *path, size_t len) +{ + int fd; + + xsnprintf(path, len, ".merge_file_XXXXXX"); + fd = xmkstemp(path); + if (write_in_full(fd, src->ptr, src->size) < 0) + die_errno("unable to write temp-file"); + close(fd); +} + +/* + * User defined low-level merge driver support. + */ +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 UNUSED, + mmfile_t *src1, const char *name1 UNUSED, + mmfile_t *src2, const char *name2 UNUSED, + const struct ll_merge_options *opts, + int marker_size) +{ + char temp[4][50]; + struct strbuf cmd = STRBUF_INIT; + struct strbuf_expand_dict_entry dict[6]; + struct strbuf path_sq = STRBUF_INIT; + struct child_process child = CHILD_PROCESS_INIT; + int status, fd, i; + struct stat st; + enum ll_merge_result ret; + assert(opts); + + sq_quote_buf(&path_sq, path); + dict[0].placeholder = "O"; dict[0].value = temp[0]; + dict[1].placeholder = "A"; dict[1].value = temp[1]; + dict[2].placeholder = "B"; dict[2].value = temp[2]; + dict[3].placeholder = "L"; dict[3].value = temp[3]; + dict[4].placeholder = "P"; dict[4].value = path_sq.buf; + dict[5].placeholder = NULL; dict[5].value = NULL; + + if (!fn->cmdline) + die("custom merge driver %s lacks command line.", fn->name); + + result->ptr = NULL; + result->size = 0; + create_temp(orig, temp[0], sizeof(temp[0])); + create_temp(src1, temp[1], sizeof(temp[1])); + create_temp(src2, temp[2], sizeof(temp[2])); + xsnprintf(temp[3], sizeof(temp[3]), "%d", marker_size); + + strbuf_expand(&cmd, fn->cmdline, strbuf_expand_dict_cb, &dict); + + child.use_shell = 1; + strvec_push(&child.args, cmd.buf); + status = run_command(&child); + fd = open(temp[1], O_RDONLY); + if (fd < 0) + goto bad; + if (fstat(fd, &st)) + goto close_bad; + result->size = st.st_size; + result->ptr = xmallocz(result->size); + if (read_in_full(fd, result->ptr, result->size) != result->size) { + FREE_AND_NULL(result->ptr); + result->size = 0; + } + close_bad: + close(fd); + bad: + for (i = 0; i < 3; i++) + unlink_or_warn(temp[i]); + strbuf_release(&cmd); + strbuf_release(&path_sq); + ret = (status > 0) ? LL_MERGE_CONFLICT : status; + return ret; +} + +/* + * merge.default and merge.driver configuration items + */ +static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail; +static const char *default_ll_merge; + +static int read_merge_config(const char *var, const char *value, + void *cb UNUSED) +{ + struct ll_merge_driver *fn; + const char *key, *name; + size_t namelen; + + if (!strcmp(var, "merge.default")) + return git_config_string(&default_ll_merge, var, value); + + /* + * We are not interested in anything but "merge..variable"; + * especially, we do not want to look at variables such as + * "merge.summary", "merge.tool", and "merge.verbosity". + */ + if (parse_config_key(var, "merge", &name, &namelen, &key) < 0 || !name) + return 0; + + /* + * Find existing one as we might be processing merge..var2 + * after seeing merge..var1. + */ + for (fn = ll_user_merge; fn; fn = fn->next) + if (!strncmp(fn->name, name, namelen) && !fn->name[namelen]) + break; + if (!fn) { + CALLOC_ARRAY(fn, 1); + fn->name = xmemdupz(name, namelen); + fn->fn = ll_ext_merge; + *ll_user_merge_tail = fn; + ll_user_merge_tail = &(fn->next); + } + + if (!strcmp("name", key)) + return git_config_string(&fn->description, var, value); + + if (!strcmp("driver", key)) { + if (!value) + return error("%s: lacks value", var); + /* + * merge..driver specifies the command line: + * + * command-line + * + * The command-line will be interpolated with the following + * tokens and is given to the shell: + * + * %O - temporary file name for the merge base. + * %A - temporary file name for our version. + * %B - temporary file name for the other branches' version. + * %L - conflict marker length + * %P - the original path (safely quoted for the shell) + * + * The external merge driver should write the results in the + * file named by %A, and signal that it has done with zero exit + * status. + */ + fn->cmdline = xstrdup(value); + return 0; + } + + if (!strcmp("recursive", key)) + return git_config_string(&fn->recursive, var, value); + + return 0; +} + +static void initialize_ll_merge(void) +{ + if (ll_user_merge_tail) + return; + ll_user_merge_tail = &ll_user_merge; + git_config(read_merge_config, NULL); +} + +static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr) +{ + struct ll_merge_driver *fn; + const char *name; + int i; + + initialize_ll_merge(); + + if (ATTR_TRUE(merge_attr)) + return &ll_merge_drv[LL_TEXT_MERGE]; + else if (ATTR_FALSE(merge_attr)) + return &ll_merge_drv[LL_BINARY_MERGE]; + else if (ATTR_UNSET(merge_attr)) { + if (!default_ll_merge) + return &ll_merge_drv[LL_TEXT_MERGE]; + else + name = default_ll_merge; + } + else + name = merge_attr; + + for (fn = ll_user_merge; fn; fn = fn->next) + if (!strcmp(fn->name, name)) + return fn; + + for (i = 0; i < ARRAY_SIZE(ll_merge_drv); i++) + if (!strcmp(ll_merge_drv[i].name, name)) + return &ll_merge_drv[i]; + + /* default to the 3-way */ + return &ll_merge_drv[LL_TEXT_MERGE]; +} + +static void normalize_file(mmfile_t *mm, const char *path, struct index_state *istate) +{ + struct strbuf strbuf = STRBUF_INIT; + if (renormalize_buffer(istate, path, mm->ptr, mm->size, &strbuf)) { + free(mm->ptr); + mm->size = strbuf.len; + mm->ptr = strbuf_detach(&strbuf, NULL); + } +} + +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, + mmfile_t *theirs, const char *their_label, + struct index_state *istate, + const struct ll_merge_options *opts) +{ + struct attr_check *check = load_merge_attributes(); + static const struct ll_merge_options default_opts; + const char *ll_driver_name = NULL; + int marker_size = DEFAULT_CONFLICT_MARKER_SIZE; + const struct ll_merge_driver *driver; + + if (!opts) + opts = &default_opts; + + if (opts->renormalize) { + normalize_file(ancestor, path, istate); + normalize_file(ours, path, istate); + normalize_file(theirs, path, istate); + } + + git_check_attr(istate, path, check); + ll_driver_name = check->items[0].value; + if (check->items[1].value) { + marker_size = atoi(check->items[1].value); + if (marker_size <= 0) + marker_size = DEFAULT_CONFLICT_MARKER_SIZE; + } + driver = find_ll_merge_driver(ll_driver_name); + + if (opts->virtual_ancestor) { + if (driver->recursive) + driver = find_ll_merge_driver(driver->recursive); + } + if (opts->extra_marker_size) { + marker_size += opts->extra_marker_size; + } + return driver->fn(driver, result_buf, path, ancestor, ancestor_label, + ours, our_label, theirs, their_label, + opts, marker_size); +} + +int ll_merge_marker_size(struct index_state *istate, const char *path) +{ + static struct attr_check *check; + int marker_size = DEFAULT_CONFLICT_MARKER_SIZE; + + if (!check) + check = attr_check_initl("conflict-marker-size", NULL); + git_check_attr(istate, path, check); + if (check->items[0].value) { + marker_size = atoi(check->items[0].value); + if (marker_size <= 0) + marker_size = DEFAULT_CONFLICT_MARKER_SIZE; + } + return marker_size; +} diff --git a/merge-ll.h b/merge-ll.h new file mode 100644 index 0000000000..e4a20e81a3 --- /dev/null +++ b/merge-ll.h @@ -0,0 +1,109 @@ +/* + * Low level 3-way in-core file merge. + */ + +#ifndef LL_MERGE_H +#define LL_MERGE_H + +#include "xdiff/xdiff.h" + +/** + * + * Calling sequence: + * ---------------- + * + * - Prepare a `struct ll_merge_options` to record options. + * If you have no special requests, skip this and pass `NULL` + * as the `opts` parameter to use the default options. + * + * - Allocate an mmbuffer_t variable for the result. + * + * - Allocate and fill variables with the file's original content + * and two modified versions (using `read_mmfile`, for example). + * + * - Call `ll_merge()`. + * + * - Read the merged content from `result_buf.ptr` and `result_buf.size`. + * + * - Release buffers when finished. A simple + * `free(ancestor.ptr); free(ours.ptr); free(theirs.ptr); + * free(result_buf.ptr);` will do. + * + * If the modifications do not merge cleanly, `ll_merge` will return a + * nonzero value and `result_buf` will generally include a description of + * the conflict bracketed by markers such as the traditional `<<<<<<<` + * and `>>>>>>>`. + * + * The `ancestor_label`, `our_label`, and `their_label` parameters are + * used to label the different sides of a conflict if the merge driver + * supports this. + */ + + +struct index_state; + +/** + * This describes the set of options the calling program wants to affect + * the operation of a low-level (single file) merge. + */ +struct ll_merge_options { + + /** + * Behave as though this were part of a merge between common ancestors in + * a recursive merge (merges of binary files may need to be handled + * differently in such cases, for example). If a helper program is + * specified by the `[merge ""] recursive` configuration, it will + * be used. + */ + unsigned virtual_ancestor : 1; + + /** + * Resolve local conflicts automatically in favor of one side or the other + * (as in 'git merge-file' `--ours`/`--theirs`/`--union`). Can be `0`, + * `XDL_MERGE_FAVOR_OURS`, `XDL_MERGE_FAVOR_THEIRS`, + * or `XDL_MERGE_FAVOR_UNION`. + */ + unsigned variant : 2; + + /** + * Resmudge and clean the "base", "theirs" and "ours" files before merging. + * Use this when the merge is likely to have overlapped with a change in + * smudge/clean or end-of-line normalization rules. + */ + unsigned renormalize : 1; + + /** + * Increase the length of conflict markers so that nested conflicts +  * can be differentiated. + */ + unsigned extra_marker_size; + + /* Extra xpparam_t flags as defined in xdiff/xdiff.h. */ + 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. + */ +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, + mmfile_t *theirs, const char *their_label, + struct index_state *istate, + const struct ll_merge_options *opts); + +int ll_merge_marker_size(struct index_state *istate, const char *path); +void reset_merge_attributes(void); + +#endif diff --git a/merge-ort.c b/merge-ort.c index 587eea9801..d88178dddf 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -30,7 +30,7 @@ #include "gettext.h" #include "hex.h" #include "entry.h" -#include "ll-merge.h" +#include "merge-ll.h" #include "match-trees.h" #include "mem-pool.h" #include "object-name.h" diff --git a/merge-recursive.c b/merge-recursive.c index 527dbbd010..35e1e7e1be 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -20,7 +20,7 @@ #include "environment.h" #include "gettext.h" #include "hex.h" -#include "ll-merge.h" +#include "merge-ll.h" #include "lockfile.h" #include "match-trees.h" #include "name-hash.h" diff --git a/notes-merge.c b/notes-merge.c index 4b328d852c..4be11a0155 100644 --- a/notes-merge.c +++ b/notes-merge.c @@ -12,7 +12,7 @@ #include "diffcore.h" #include "hex.h" #include "xdiff-interface.h" -#include "ll-merge.h" +#include "merge-ll.h" #include "dir.h" #include "notes.h" #include "notes-merge.h" diff --git a/rerere.c b/rerere.c index 35b9785d57..3584fecb07 100644 --- a/rerere.c +++ b/rerere.c @@ -12,7 +12,7 @@ #include "xdiff-interface.h" #include "dir.h" #include "resolve-undo.h" -#include "ll-merge.h" +#include "merge-ll.h" #include "attr.h" #include "path.h" #include "pathspec.h" -- cgit v1.3-5-g9baa