From bc5c5ec0446895f5c4139cd470066beb3c4ac6d5 Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Tue, 16 May 2023 06:33:57 +0000 Subject: cache.h: remove this no-longer-used header Since this header showed up in some places besides just #include statements, update/clean-up/remove those other places as well. Note that compat/fsmonitor/fsm-path-utils-darwin.c previously got away with violating the rule that all files must start with an include of git-compat-util.h (or a short-list of alternate headers that happen to include it first). This change exposed the violation and caused it to stop building correctly; fix it by having it include git-compat-util.h first, as per policy. Signed-off-by: Elijah Newren Signed-off-by: Junio C Hamano --- Documentation/CodingGuidelines | 2 +- Documentation/MyFirstObjectWalk.txt | 2 +- Documentation/user-manual.txt | 18 ++++++++---------- 3 files changed, 10 insertions(+), 12 deletions(-) (limited to 'Documentation') diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines index 003393ed16..2b472df29d 100644 --- a/Documentation/CodingGuidelines +++ b/Documentation/CodingGuidelines @@ -444,7 +444,7 @@ For C programs: - The first #include in C files, except in platform specific compat/ implementations and sha1dc/, must be either "git-compat-util.h" or one of the approved headers that includes it first for you. (The - approved headers currently include "cache.h", "builtin.h", + approved headers currently include "builtin.h", "t/helper/test-tool.h", "xdiff/xinclude.h", or "reftable/system.h"). You do not have to include more than one of these. diff --git a/Documentation/MyFirstObjectWalk.txt b/Documentation/MyFirstObjectWalk.txt index eee513e86f..200e628e30 100644 --- a/Documentation/MyFirstObjectWalk.txt +++ b/Documentation/MyFirstObjectWalk.txt @@ -124,7 +124,7 @@ parameters provided by the user over the CLI. `nr` represents the number of `rev_cmdline_entry` present in the array. -`alloc` is used by the `ALLOC_GROW` macro. Check `cache.h` - this variable is +`alloc` is used by the `ALLOC_GROW` macro. Check `alloc.h` - this variable is used to track the allocated size of the list. Per entry, we find: diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index aa385137ad..4281396093 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -4129,13 +4129,11 @@ Note that terminology has changed since that revision. For example, the README in that revision uses the word "changeset" to describe what we now call a <>. -Also, we do not call it "cache" any more, but rather "index"; however, the -file is still called `cache.h`. Remark: Not much reason to change it now, -especially since there is no good single name for it anyway, because it is -basically _the_ header file which is included by _all_ of Git's C sources. +Also, we do not call it "cache" any more, but rather "index"; however, +the file is still called `read-cache.h`. If you grasp the ideas in that initial commit, you should check out a -more recent version and skim `cache.h`, `object.h` and `commit.h`. +more recent version and skim `read-cache-ll.h`, `object.h` and `commit.h`. In the early days, Git (in the tradition of UNIX) was a bunch of programs which were extremely simple, and which you used in scripts, piping the @@ -4146,11 +4144,11 @@ many of these parts have become builtins, and some of the core has been and to avoid code duplication. By now, you know what the index is (and find the corresponding data -structures in `cache.h`), and that there are just a couple of object types -(blobs, trees, commits and tags) which inherit their common structure from -`struct object`, which is their first member (and thus, you can cast e.g. -`(struct object *)commit` to achieve the _same_ as `&commit->object`, i.e. -get at the object name and flags). +structures in `read-cache-ll.h`), and that there are just a couple of +object types (blobs, trees, commits and tags) which inherit their +common structure from `struct object`, which is their first member +(and thus, you can cast e.g. `(struct object *)commit` to achieve the +_same_ as `&commit->object`, i.e. get at the object name and flags). Now is a good point to take a break to let this information sink in. -- cgit v1.3-5-g9baa 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 (limited to 'Documentation') 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