2007-12-24 08:36:00 +00:00
|
|
|
/*
|
|
|
|
* Low level 3-way in-core file merge.
|
|
|
|
*
|
|
|
|
* Copyright (c) 2007 Junio C Hamano
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "cache.h"
|
2017-06-14 18:07:36 +00:00
|
|
|
#include "config.h"
|
2007-12-24 08:36:00 +00:00
|
|
|
#include "attr.h"
|
|
|
|
#include "xdiff-interface.h"
|
|
|
|
#include "run-command.h"
|
|
|
|
#include "ll-merge.h"
|
2015-06-04 22:10:29 +00:00
|
|
|
#include "quote.h"
|
2007-12-24 08:36:00 +00:00
|
|
|
|
|
|
|
struct ll_merge_driver;
|
|
|
|
|
2022-02-02 02:37:30 +00:00
|
|
|
typedef enum ll_merge_result (*ll_merge_fn)(const struct ll_merge_driver *,
|
2007-12-24 08:36:00 +00:00
|
|
|
mmbuffer_t *result,
|
|
|
|
const char *path,
|
2010-03-21 00:38:58 +00:00
|
|
|
mmfile_t *orig, const char *orig_name,
|
2007-12-24 08:36:00 +00:00
|
|
|
mmfile_t *src1, const char *name1,
|
|
|
|
mmfile_t *src2, const char *name2,
|
2010-08-26 05:49:53 +00:00
|
|
|
const struct ll_merge_options *opts,
|
2010-01-16 06:37:32 +00:00
|
|
|
int marker_size);
|
2007-12-24 08:36:00 +00:00
|
|
|
|
|
|
|
struct ll_merge_driver {
|
|
|
|
const char *name;
|
|
|
|
const char *description;
|
|
|
|
ll_merge_fn fn;
|
|
|
|
const char *recursive;
|
|
|
|
struct ll_merge_driver *next;
|
|
|
|
char *cmdline;
|
|
|
|
};
|
|
|
|
|
2019-09-02 22:39:44 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2007-12-24 08:36:00 +00:00
|
|
|
/*
|
|
|
|
* Built-in low-levels
|
|
|
|
*/
|
2022-10-18 01:10:24 +00:00
|
|
|
static enum ll_merge_result ll_binary_merge(const struct ll_merge_driver *drv UNUSED,
|
2007-12-24 08:36:00 +00:00
|
|
|
mmbuffer_t *result,
|
2022-10-18 01:10:24 +00:00
|
|
|
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,
|
2010-08-26 05:49:53 +00:00
|
|
|
const struct ll_merge_options *opts,
|
2022-10-18 01:10:24 +00:00
|
|
|
int marker_size UNUSED)
|
2007-12-24 08:36:00 +00:00
|
|
|
{
|
2022-02-02 02:37:30 +00:00
|
|
|
enum ll_merge_result ret;
|
2010-08-26 05:49:53 +00:00
|
|
|
mmfile_t *stolen;
|
|
|
|
assert(opts);
|
|
|
|
|
2007-12-24 08:36:00 +00:00
|
|
|
/*
|
2016-04-14 22:12:15 +00:00
|
|
|
* 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.
|
2007-12-24 08:36:00 +00:00
|
|
|
*/
|
2012-09-09 04:27:19 +00:00
|
|
|
if (opts->virtual_ancestor) {
|
|
|
|
stolen = orig;
|
2022-02-02 02:37:30 +00:00
|
|
|
ret = LL_MERGE_OK;
|
2012-09-09 04:27:19 +00:00
|
|
|
} else {
|
|
|
|
switch (opts->variant) {
|
|
|
|
default:
|
2022-02-02 02:37:30 +00:00
|
|
|
ret = LL_MERGE_BINARY_CONFLICT;
|
|
|
|
stolen = src1;
|
|
|
|
break;
|
2012-09-09 04:27:19 +00:00
|
|
|
case XDL_MERGE_FAVOR_OURS:
|
2022-02-02 02:37:30 +00:00
|
|
|
ret = LL_MERGE_OK;
|
2012-09-09 04:27:19 +00:00
|
|
|
stolen = src1;
|
|
|
|
break;
|
|
|
|
case XDL_MERGE_FAVOR_THEIRS:
|
2022-02-02 02:37:30 +00:00
|
|
|
ret = LL_MERGE_OK;
|
2012-09-09 04:27:19 +00:00
|
|
|
stolen = src2;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2007-12-24 08:36:00 +00:00
|
|
|
|
|
|
|
result->ptr = stolen->ptr;
|
|
|
|
result->size = stolen->size;
|
|
|
|
stolen->ptr = NULL;
|
2012-09-09 04:27:19 +00:00
|
|
|
|
2022-02-02 02:37:30 +00:00
|
|
|
return ret;
|
2007-12-24 08:36:00 +00:00
|
|
|
}
|
|
|
|
|
2022-02-02 02:37:30 +00:00
|
|
|
static enum ll_merge_result ll_xdl_merge(const struct ll_merge_driver *drv_unused,
|
2007-12-24 08:36:00 +00:00
|
|
|
mmbuffer_t *result,
|
2009-07-01 20:18:04 +00:00
|
|
|
const char *path,
|
2010-03-21 00:38:58 +00:00
|
|
|
mmfile_t *orig, const char *orig_name,
|
2007-12-24 08:36:00 +00:00
|
|
|
mmfile_t *src1, const char *name1,
|
|
|
|
mmfile_t *src2, const char *name2,
|
2010-08-26 05:49:53 +00:00
|
|
|
const struct ll_merge_options *opts,
|
|
|
|
int marker_size)
|
2007-12-24 08:36:00 +00:00
|
|
|
{
|
2022-02-02 02:37:30 +00:00
|
|
|
enum ll_merge_result ret;
|
2010-01-17 05:01:28 +00:00
|
|
|
xmparam_t xmp;
|
2022-02-02 02:37:30 +00:00
|
|
|
int status;
|
2010-08-26 05:49:53 +00:00
|
|
|
assert(opts);
|
2007-12-24 08:36:00 +00:00
|
|
|
|
xdiff: reject files larger than ~1GB
The xdiff code is not prepared to handle extremely large
files. It uses "int" in many places, which can overflow if
we have a very large number of lines or even bytes in our
input files. This can cause us to produce incorrect diffs,
with no indication that the output is wrong. Or worse, we
may even underallocate a buffer whose size is the result of
an overflowing addition.
We're much better off to tell the user that we cannot diff
or merge such a large file. This patch covers both cases,
but in slightly different ways:
1. For merging, we notice the large file and cleanly fall
back to a binary merge (which is effectively "we cannot
merge this").
2. For diffing, we make the binary/text distinction much
earlier, and in many different places. For this case,
we'll use the xdi_diff as our choke point, and reject
any diff there before it hits the xdiff code.
This means in most cases we'll die() immediately after.
That's not ideal, but in practice we shouldn't
generally hit this code path unless the user is trying
to do something tricky. We already consider files
larger than core.bigfilethreshold to be binary, so this
code would only kick in when that is circumvented
(either by bumping that value, or by using a
.gitattribute to mark a file as diffable).
In other words, we can avoid being "nice" here, because
there is already nice code that tries to do the right
thing. We are adding the suspenders to the nice code's
belt, so notice when it has been worked around (both to
protect the user from malicious inputs, and because it
is better to die() than generate bogus output).
The maximum size was chosen after experimenting with feeding
large files to the xdiff code. It's just under a gigabyte,
which leaves room for two obvious cases:
- a diff3 merge conflict result on files of maximum size X
could be 3*X plus the size of the markers, which would
still be only about 3G, which fits in a 32-bit int.
- some of the diff code allocates arrays of one int per
record. Even if each file consists only of blank lines,
then a file smaller than 1G will have fewer than 1G
records, and therefore the int array will fit in 4G.
Since the limit is arbitrary anyway, I chose to go under a
gigabyte, to leave a safety margin (e.g., we would not want
to overflow by allocating "(records + 1) * sizeof(int)" or
similar.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-09-24 23:12:45 +00:00
|
|
|
if (orig->size > MAX_XDIFF_SIZE ||
|
|
|
|
src1->size > MAX_XDIFF_SIZE ||
|
|
|
|
src2->size > MAX_XDIFF_SIZE ||
|
|
|
|
buffer_is_binary(orig->ptr, orig->size) ||
|
2007-12-24 08:36:00 +00:00
|
|
|
buffer_is_binary(src1->ptr, src1->size) ||
|
|
|
|
buffer_is_binary(src2->ptr, src2->size)) {
|
|
|
|
return ll_binary_merge(drv_unused, result,
|
2009-07-01 20:18:04 +00:00
|
|
|
path,
|
2010-03-21 00:38:58 +00:00
|
|
|
orig, orig_name,
|
|
|
|
src1, name1,
|
2007-12-24 08:36:00 +00:00
|
|
|
src2, name2,
|
2010-08-26 05:49:53 +00:00
|
|
|
opts, marker_size);
|
2007-12-24 08:36:00 +00:00
|
|
|
}
|
|
|
|
|
2010-01-17 05:01:28 +00:00
|
|
|
memset(&xmp, 0, sizeof(xmp));
|
2010-03-01 21:46:26 +00:00
|
|
|
xmp.level = XDL_MERGE_ZEALOUS;
|
2010-08-26 05:49:53 +00:00
|
|
|
xmp.favor = opts->variant;
|
2010-08-26 05:50:45 +00:00
|
|
|
xmp.xpp.flags = opts->xdl_opts;
|
2008-08-29 17:59:16 +00:00
|
|
|
if (git_xmerge_style >= 0)
|
2010-03-01 21:46:26 +00:00
|
|
|
xmp.style = git_xmerge_style;
|
2010-01-16 06:37:32 +00:00
|
|
|
if (marker_size > 0)
|
|
|
|
xmp.marker_size = marker_size;
|
2010-03-21 00:38:58 +00:00
|
|
|
xmp.ancestor = orig_name;
|
2010-03-21 00:35:18 +00:00
|
|
|
xmp.file1 = name1;
|
|
|
|
xmp.file2 = name2;
|
2022-02-02 02:37:30 +00:00
|
|
|
status = xdl_merge(orig, src1, src2, &xmp, result);
|
|
|
|
ret = (status > 0) ? LL_MERGE_CONFLICT : status;
|
|
|
|
return ret;
|
2007-12-24 08:36:00 +00:00
|
|
|
}
|
|
|
|
|
2022-02-02 02:37:30 +00:00
|
|
|
static enum ll_merge_result ll_union_merge(const struct ll_merge_driver *drv_unused,
|
2007-12-24 08:36:00 +00:00
|
|
|
mmbuffer_t *result,
|
2021-06-10 16:14:12 +00:00
|
|
|
const char *path,
|
2010-03-21 00:38:58 +00:00
|
|
|
mmfile_t *orig, const char *orig_name,
|
2007-12-24 08:36:00 +00:00
|
|
|
mmfile_t *src1, const char *name1,
|
|
|
|
mmfile_t *src2, const char *name2,
|
2010-08-26 05:49:53 +00:00
|
|
|
const struct ll_merge_options *opts,
|
|
|
|
int marker_size)
|
2007-12-24 08:36:00 +00:00
|
|
|
{
|
2010-03-01 21:46:25 +00:00
|
|
|
/* Use union favor */
|
2010-08-26 05:49:53 +00:00
|
|
|
struct ll_merge_options o;
|
|
|
|
assert(opts);
|
|
|
|
o = *opts;
|
|
|
|
o.variant = XDL_MERGE_FAVOR_UNION;
|
2021-06-10 16:14:12 +00:00
|
|
|
return ll_xdl_merge(drv_unused, result, path,
|
ll_union_merge(): pass name labels to ll_xdl_merge()
Since cd1d61c44f (make union merge an xdl merge favor, 2010-03-01), we
pass NULL to ll_xdl_merge() for the "name" labels of the ancestor, ours
and theirs buffers. We usually use these for annotating conflict markers
left in a file. For a union merge, these shouldn't matter; the point of
it is that we'd never leave conflict markers in the first place.
But there is one code path where we may dereference them: if the file
contents appear to be binary, ll_binary_merge() will give up and pass
them to warning() to generate a message for the user (that was true even
when cd1d61c44f was written, though the warning was in ll_xdl_merge()
back then).
That can result in a segfault, though on many systems (including glibc),
the printf routines will helpfully just say "(null)" instead. We can
extend our binary-union test in t6406 to check stderr, which catches the
problem on all systems.
This also fixes a warning from "gcc -O3". Unlike lower optimization
levels, it inlines enough to see that the NULL can make it to warning()
and complains:
In function ‘ll_binary_merge’,
inlined from ‘ll_xdl_merge’ at ll-merge.c:115:10,
inlined from ‘ll_union_merge’ at ll-merge.c:151:9:
ll-merge.c:74:4: warning: ‘%s’ directive argument is null [-Wformat-overflow=]
74 | warning("Cannot merge binary files: %s (%s vs. %s)",
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
75 | path, name1, name2);
| ~~~~~~~~~~~~~~~~~~~
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-06-10 12:58:43 +00:00
|
|
|
orig, orig_name, src1, name1, src2, name2,
|
2010-08-26 05:49:53 +00:00
|
|
|
&o, marker_size);
|
2007-12-24 08:36:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#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 },
|
|
|
|
};
|
|
|
|
|
2015-09-24 21:06:08 +00:00
|
|
|
static void create_temp(mmfile_t *src, char *path, size_t len)
|
2007-12-24 08:36:00 +00:00
|
|
|
{
|
|
|
|
int fd;
|
|
|
|
|
2015-09-24 21:06:08 +00:00
|
|
|
xsnprintf(path, len, ".merge_file_XXXXXX");
|
2007-12-24 08:36:00 +00:00
|
|
|
fd = xmkstemp(path);
|
avoid "write_in_full(fd, buf, len) != len" pattern
The return value of write_in_full() is either "-1", or the
requested number of bytes[1]. If we make a partial write
before seeing an error, we still return -1, not a partial
value. This goes back to f6aa66cb95 (write_in_full: really
write in full or return error on disk full., 2007-01-11).
So checking anything except "was the return value negative"
is pointless. And there are a couple of reasons not to do
so:
1. It can do a funny signed/unsigned comparison. If your
"len" is signed (e.g., a size_t) then the compiler will
promote the "-1" to its unsigned variant.
This works out for "!= len" (unless you really were
trying to write the maximum size_t bytes), but is a
bug if you check "< len" (an example of which was fixed
recently in config.c).
We should avoid promoting the mental model that you
need to check the length at all, so that new sites are
not tempted to copy us.
2. Checking for a negative value is shorter to type,
especially when the length is an expression.
3. Linus says so. In d34cf19b89 (Clean up write_in_full()
users, 2007-01-11), right after the write_in_full()
semantics were changed, he wrote:
I really wish every "write_in_full()" user would just
check against "<0" now, but this fixes the nasty and
stupid ones.
Appeals to authority aside, this makes it clear that
writing it this way does not have an intentional
benefit. It's a historical curiosity that we never
bothered to clean up (and which was undoubtedly
cargo-culted into new sites).
So let's convert these obviously-correct cases (this
includes write_str_in_full(), which is just a wrapper for
write_in_full()).
[1] A careful reader may notice there is one way that
write_in_full() can return a different value. If we ask
write() to write N bytes and get a return value that is
_larger_ than N, we could return a larger total. But
besides the fact that this would imply a totally broken
version of write(), it would already invoke undefined
behavior. Our internal remaining counter is an unsigned
size_t, which means that subtracting too many byte will
wrap it around to a very large number. So we'll instantly
begin reading off the end of the buffer, trying to write
gigabytes (or petabytes) of data.
Signed-off-by: Jeff King <peff@peff.net>
Reviewed-by: Jonathan Nieder <jrnieder@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-09-13 17:16:03 +00:00
|
|
|
if (write_in_full(fd, src->ptr, src->size) < 0)
|
2009-06-27 15:58:47 +00:00
|
|
|
die_errno("unable to write temp-file");
|
2007-12-24 08:36:00 +00:00
|
|
|
close(fd);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* User defined low-level merge driver support.
|
|
|
|
*/
|
2022-02-02 02:37:30 +00:00
|
|
|
static enum ll_merge_result ll_ext_merge(const struct ll_merge_driver *fn,
|
2007-12-24 08:36:00 +00:00
|
|
|
mmbuffer_t *result,
|
|
|
|
const char *path,
|
2022-10-18 01:10:24 +00:00
|
|
|
mmfile_t *orig, const char *orig_name UNUSED,
|
|
|
|
mmfile_t *src1, const char *name1 UNUSED,
|
|
|
|
mmfile_t *src2, const char *name2 UNUSED,
|
2010-08-26 05:49:53 +00:00
|
|
|
const struct ll_merge_options *opts,
|
|
|
|
int marker_size)
|
2007-12-24 08:36:00 +00:00
|
|
|
{
|
2010-01-16 06:37:32 +00:00
|
|
|
char temp[4][50];
|
2008-11-22 23:13:00 +00:00
|
|
|
struct strbuf cmd = STRBUF_INIT;
|
2015-06-04 22:10:29 +00:00
|
|
|
struct strbuf_expand_dict_entry dict[6];
|
|
|
|
struct strbuf path_sq = STRBUF_INIT;
|
2022-10-30 11:50:27 +00:00
|
|
|
struct child_process child = CHILD_PROCESS_INIT;
|
2007-12-24 08:36:00 +00:00
|
|
|
int status, fd, i;
|
|
|
|
struct stat st;
|
2022-02-02 02:37:30 +00:00
|
|
|
enum ll_merge_result ret;
|
2010-08-26 05:49:53 +00:00
|
|
|
assert(opts);
|
2007-12-24 08:36:00 +00:00
|
|
|
|
2015-06-04 22:10:29 +00:00
|
|
|
sq_quote_buf(&path_sq, path);
|
2010-05-14 09:31:33 +00:00
|
|
|
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];
|
2015-06-04 22:10:29 +00:00
|
|
|
dict[4].placeholder = "P"; dict[4].value = path_sq.buf;
|
|
|
|
dict[5].placeholder = NULL; dict[5].value = NULL;
|
2010-05-14 09:31:33 +00:00
|
|
|
|
2022-05-02 16:50:37 +00:00
|
|
|
if (!fn->cmdline)
|
2007-12-24 08:36:00 +00:00
|
|
|
die("custom merge driver %s lacks command line.", fn->name);
|
|
|
|
|
|
|
|
result->ptr = NULL;
|
|
|
|
result->size = 0;
|
2015-09-24 21:06:08 +00:00
|
|
|
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);
|
2007-12-24 08:36:00 +00:00
|
|
|
|
2008-11-22 23:13:00 +00:00
|
|
|
strbuf_expand(&cmd, fn->cmdline, strbuf_expand_dict_cb, &dict);
|
2007-12-24 08:36:00 +00:00
|
|
|
|
2022-10-30 11:50:27 +00:00
|
|
|
child.use_shell = 1;
|
|
|
|
strvec_push(&child.args, cmd.buf);
|
|
|
|
status = run_command(&child);
|
2007-12-24 08:36:00 +00:00
|
|
|
fd = open(temp[1], O_RDONLY);
|
|
|
|
if (fd < 0)
|
|
|
|
goto bad;
|
|
|
|
if (fstat(fd, &st))
|
|
|
|
goto close_bad;
|
|
|
|
result->size = st.st_size;
|
2016-02-22 22:44:28 +00:00
|
|
|
result->ptr = xmallocz(result->size);
|
2007-12-24 08:36:00 +00:00
|
|
|
if (read_in_full(fd, result->ptr, result->size) != result->size) {
|
2017-06-15 23:15:48 +00:00
|
|
|
FREE_AND_NULL(result->ptr);
|
2007-12-24 08:36:00 +00:00
|
|
|
result->size = 0;
|
|
|
|
}
|
|
|
|
close_bad:
|
|
|
|
close(fd);
|
|
|
|
bad:
|
|
|
|
for (i = 0; i < 3; i++)
|
2009-04-29 21:22:56 +00:00
|
|
|
unlink_or_warn(temp[i]);
|
2008-11-22 23:13:00 +00:00
|
|
|
strbuf_release(&cmd);
|
2015-06-04 22:10:29 +00:00
|
|
|
strbuf_release(&path_sq);
|
2022-02-02 02:37:30 +00:00
|
|
|
ret = (status > 0) ? LL_MERGE_CONFLICT : status;
|
|
|
|
return ret;
|
2007-12-24 08:36:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
|
2022-08-19 10:08:44 +00:00
|
|
|
static int read_merge_config(const char *var, const char *value,
|
2022-08-25 17:09:48 +00:00
|
|
|
void *cb UNUSED)
|
2007-12-24 08:36:00 +00:00
|
|
|
{
|
|
|
|
struct ll_merge_driver *fn;
|
2013-01-23 06:24:23 +00:00
|
|
|
const char *key, *name;
|
2020-04-10 19:44:28 +00:00
|
|
|
size_t namelen;
|
2007-12-24 08:36:00 +00:00
|
|
|
|
ll-merge.c: refactor `read_merge_config()` to use `git_config_string()`
There is one slight behavior change, previously "merge.default"
silently ignored a NULL value and didn't raise any error. But,
in the same function, all other values raise an error on a NULL
value. So to conform with other call sites in Git, a NULL value
for "merge.default" raises an error.
The the new config-set API is not very useful here, because much of
the function is dedicated to processing "merge.<name>.variable",
which the new API does not handle well. If it were for variables
like, "merge.summary", "merge.tool", and "merge.verbosity", we could
use the new API.
Signed-off-by: Tanay Abhra <tanayabh@gmail.com>
Reviewed-by: Matthieu Moy <Matthieu.Moy@imag.fr>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-08-13 12:43:04 +00:00
|
|
|
if (!strcmp(var, "merge.default"))
|
|
|
|
return git_config_string(&default_ll_merge, var, value);
|
2007-12-24 08:36:00 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* We are not interested in anything but "merge.<name>.variable";
|
|
|
|
* especially, we do not want to look at variables such as
|
|
|
|
* "merge.summary", "merge.tool", and "merge.verbosity".
|
|
|
|
*/
|
2013-01-23 06:24:23 +00:00
|
|
|
if (parse_config_key(var, "merge", &name, &namelen, &key) < 0 || !name)
|
2007-12-24 08:36:00 +00:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Find existing one as we might be processing merge.<name>.var2
|
|
|
|
* after seeing merge.<name>.var1.
|
|
|
|
*/
|
|
|
|
for (fn = ll_user_merge; fn; fn = fn->next)
|
|
|
|
if (!strncmp(fn->name, name, namelen) && !fn->name[namelen])
|
|
|
|
break;
|
|
|
|
if (!fn) {
|
2021-03-13 16:17:22 +00:00
|
|
|
CALLOC_ARRAY(fn, 1);
|
2007-12-24 08:36:00 +00:00
|
|
|
fn->name = xmemdupz(name, namelen);
|
|
|
|
fn->fn = ll_ext_merge;
|
|
|
|
*ll_user_merge_tail = fn;
|
|
|
|
ll_user_merge_tail = &(fn->next);
|
|
|
|
}
|
|
|
|
|
ll-merge.c: refactor `read_merge_config()` to use `git_config_string()`
There is one slight behavior change, previously "merge.default"
silently ignored a NULL value and didn't raise any error. But,
in the same function, all other values raise an error on a NULL
value. So to conform with other call sites in Git, a NULL value
for "merge.default" raises an error.
The the new config-set API is not very useful here, because much of
the function is dedicated to processing "merge.<name>.variable",
which the new API does not handle well. If it were for variables
like, "merge.summary", "merge.tool", and "merge.verbosity", we could
use the new API.
Signed-off-by: Tanay Abhra <tanayabh@gmail.com>
Reviewed-by: Matthieu Moy <Matthieu.Moy@imag.fr>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-08-13 12:43:04 +00:00
|
|
|
if (!strcmp("name", key))
|
|
|
|
return git_config_string(&fn->description, var, value);
|
2007-12-24 08:36:00 +00:00
|
|
|
|
2013-01-23 06:24:23 +00:00
|
|
|
if (!strcmp("driver", key)) {
|
2007-12-24 08:36:00 +00:00
|
|
|
if (!value)
|
|
|
|
return error("%s: lacks value", var);
|
|
|
|
/*
|
|
|
|
* merge.<name>.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.
|
2010-01-16 06:37:32 +00:00
|
|
|
* %L - conflict marker length
|
2015-06-04 22:10:29 +00:00
|
|
|
* %P - the original path (safely quoted for the shell)
|
2007-12-24 08:36:00 +00:00
|
|
|
*
|
|
|
|
* The external merge driver should write the results in the
|
|
|
|
* file named by %A, and signal that it has done with zero exit
|
|
|
|
* status.
|
|
|
|
*/
|
2009-06-14 19:47:54 +00:00
|
|
|
fn->cmdline = xstrdup(value);
|
2007-12-24 08:36:00 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
ll-merge.c: refactor `read_merge_config()` to use `git_config_string()`
There is one slight behavior change, previously "merge.default"
silently ignored a NULL value and didn't raise any error. But,
in the same function, all other values raise an error on a NULL
value. So to conform with other call sites in Git, a NULL value
for "merge.default" raises an error.
The the new config-set API is not very useful here, because much of
the function is dedicated to processing "merge.<name>.variable",
which the new API does not handle well. If it were for variables
like, "merge.summary", "merge.tool", and "merge.verbosity", we could
use the new API.
Signed-off-by: Tanay Abhra <tanayabh@gmail.com>
Reviewed-by: Matthieu Moy <Matthieu.Moy@imag.fr>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-08-13 12:43:04 +00:00
|
|
|
if (!strcmp("recursive", key))
|
|
|
|
return git_config_string(&fn->recursive, var, value);
|
2007-12-24 08:36:00 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void initialize_ll_merge(void)
|
|
|
|
{
|
|
|
|
if (ll_user_merge_tail)
|
|
|
|
return;
|
|
|
|
ll_user_merge_tail = &ll_user_merge;
|
2008-05-14 17:46:53 +00:00
|
|
|
git_config(read_merge_config, NULL);
|
2007-12-24 08:36:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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];
|
|
|
|
}
|
|
|
|
|
2018-09-21 15:57:27 +00:00
|
|
|
static void normalize_file(mmfile_t *mm, const char *path, struct index_state *istate)
|
2010-07-02 19:20:47 +00:00
|
|
|
{
|
|
|
|
struct strbuf strbuf = STRBUF_INIT;
|
2018-09-21 15:57:27 +00:00
|
|
|
if (renormalize_buffer(istate, path, mm->ptr, mm->size, &strbuf)) {
|
2010-07-02 19:20:47 +00:00
|
|
|
free(mm->ptr);
|
|
|
|
mm->size = strbuf.len;
|
|
|
|
mm->ptr = strbuf_detach(&strbuf, NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-02 02:37:30 +00:00
|
|
|
enum ll_merge_result ll_merge(mmbuffer_t *result_buf,
|
2007-12-24 08:36:00 +00:00
|
|
|
const char *path,
|
2010-03-21 00:38:58 +00:00
|
|
|
mmfile_t *ancestor, const char *ancestor_label,
|
2007-12-24 08:36:00 +00:00
|
|
|
mmfile_t *ours, const char *our_label,
|
|
|
|
mmfile_t *theirs, const char *their_label,
|
2018-09-21 15:57:27 +00:00
|
|
|
struct index_state *istate,
|
2010-08-26 05:49:53 +00:00
|
|
|
const struct ll_merge_options *opts)
|
2007-12-24 08:36:00 +00:00
|
|
|
{
|
2019-09-02 22:39:44 +00:00
|
|
|
struct attr_check *check = load_merge_attributes();
|
2011-01-16 01:08:42 +00:00
|
|
|
static const struct ll_merge_options default_opts;
|
2010-01-16 06:37:32 +00:00
|
|
|
const char *ll_driver_name = NULL;
|
|
|
|
int marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
|
2007-12-24 08:36:00 +00:00
|
|
|
const struct ll_merge_driver *driver;
|
|
|
|
|
2011-01-16 01:08:42 +00:00
|
|
|
if (!opts)
|
|
|
|
opts = &default_opts;
|
2010-08-26 05:49:53 +00:00
|
|
|
|
|
|
|
if (opts->renormalize) {
|
2018-09-21 15:57:27 +00:00
|
|
|
normalize_file(ancestor, path, istate);
|
|
|
|
normalize_file(ours, path, istate);
|
|
|
|
normalize_file(theirs, path, istate);
|
2010-07-02 19:20:47 +00:00
|
|
|
}
|
2017-01-28 02:01:57 +00:00
|
|
|
|
2018-10-19 04:34:02 +00:00
|
|
|
git_check_attr(istate, path, check);
|
2018-09-12 19:32:02 +00:00
|
|
|
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;
|
2010-01-16 06:37:32 +00:00
|
|
|
}
|
2007-12-24 08:36:00 +00:00
|
|
|
driver = find_ll_merge_driver(ll_driver_name);
|
2016-04-14 22:35:09 +00:00
|
|
|
|
|
|
|
if (opts->virtual_ancestor) {
|
|
|
|
if (driver->recursive)
|
|
|
|
driver = find_ll_merge_driver(driver->recursive);
|
2018-11-08 04:40:24 +00:00
|
|
|
}
|
|
|
|
if (opts->extra_marker_size) {
|
|
|
|
marker_size += opts->extra_marker_size;
|
2016-04-14 22:35:09 +00:00
|
|
|
}
|
2010-03-21 00:38:58 +00:00
|
|
|
return driver->fn(driver, result_buf, path, ancestor, ancestor_label,
|
2010-01-16 06:37:32 +00:00
|
|
|
ours, our_label, theirs, their_label,
|
2010-08-26 05:49:53 +00:00
|
|
|
opts, marker_size);
|
2007-12-24 08:36:00 +00:00
|
|
|
}
|
2010-01-17 07:28:46 +00:00
|
|
|
|
2018-09-21 15:57:27 +00:00
|
|
|
int ll_merge_marker_size(struct index_state *istate, const char *path)
|
2010-01-17 07:28:46 +00:00
|
|
|
{
|
2017-01-28 02:01:57 +00:00
|
|
|
static struct attr_check *check;
|
2010-01-17 07:28:46 +00:00
|
|
|
int marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
|
|
|
|
|
2017-01-28 02:01:57 +00:00
|
|
|
if (!check)
|
|
|
|
check = attr_check_initl("conflict-marker-size", NULL);
|
2018-10-19 04:34:02 +00:00
|
|
|
git_check_attr(istate, path, check);
|
2018-09-12 19:32:02 +00:00
|
|
|
if (check->items[0].value) {
|
2017-01-28 02:01:57 +00:00
|
|
|
marker_size = atoi(check->items[0].value);
|
2010-01-17 07:28:46 +00:00
|
|
|
if (marker_size <= 0)
|
|
|
|
marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
|
|
|
|
}
|
|
|
|
return marker_size;
|
2007-12-24 08:36:00 +00:00
|
|
|
}
|