git

Форк
0
/
merge-ll.c 
462 строки · 12.1 Кб
1
/*
2
 * Low level 3-way in-core file merge.
3
 *
4
 * Copyright (c) 2007 Junio C Hamano
5
 */
6

7
#define USE_THE_REPOSITORY_VARIABLE
8

9
#include "git-compat-util.h"
10
#include "config.h"
11
#include "convert.h"
12
#include "attr.h"
13
#include "xdiff-interface.h"
14
#include "run-command.h"
15
#include "merge-ll.h"
16
#include "quote.h"
17
#include "strbuf.h"
18

19
struct ll_merge_driver;
20

21
typedef enum ll_merge_result (*ll_merge_fn)(const struct ll_merge_driver *,
22
			   mmbuffer_t *result,
23
			   const char *path,
24
			   mmfile_t *orig, const char *orig_name,
25
			   mmfile_t *src1, const char *name1,
26
			   mmfile_t *src2, const char *name2,
27
			   const struct ll_merge_options *opts,
28
			   int marker_size);
29

30
struct ll_merge_driver {
31
	const char *name;
32
	const char *description;
33
	ll_merge_fn fn;
34
	char *recursive;
35
	struct ll_merge_driver *next;
36
	char *cmdline;
37
};
38

39
static struct attr_check *merge_attributes;
40
static struct attr_check *load_merge_attributes(void)
41
{
42
	if (!merge_attributes)
43
		merge_attributes = attr_check_initl("merge", "conflict-marker-size", NULL);
44
	return merge_attributes;
45
}
46

47
void reset_merge_attributes(void)
48
{
49
	attr_check_free(merge_attributes);
50
	merge_attributes = NULL;
51
}
52

53
/*
54
 * Built-in low-levels
55
 */
56
static enum ll_merge_result ll_binary_merge(const struct ll_merge_driver *drv UNUSED,
57
			   mmbuffer_t *result,
58
			   const char *path UNUSED,
59
			   mmfile_t *orig, const char *orig_name UNUSED,
60
			   mmfile_t *src1, const char *name1 UNUSED,
61
			   mmfile_t *src2, const char *name2 UNUSED,
62
			   const struct ll_merge_options *opts,
63
			   int marker_size UNUSED)
64
{
65
	enum ll_merge_result ret;
66
	mmfile_t *stolen;
67
	assert(opts);
68

69
	/*
70
	 * The tentative merge result is the common ancestor for an
71
	 * internal merge.  For the final merge, it is "ours" by
72
	 * default but -Xours/-Xtheirs can tweak the choice.
73
	 */
74
	if (opts->virtual_ancestor) {
75
		stolen = orig;
76
		ret = LL_MERGE_OK;
77
	} else {
78
		switch (opts->variant) {
79
		default:
80
			ret = LL_MERGE_BINARY_CONFLICT;
81
			stolen = src1;
82
			break;
83
		case XDL_MERGE_FAVOR_OURS:
84
			ret = LL_MERGE_OK;
85
			stolen = src1;
86
			break;
87
		case XDL_MERGE_FAVOR_THEIRS:
88
			ret = LL_MERGE_OK;
89
			stolen = src2;
90
			break;
91
		}
92
	}
93

94
	result->ptr = stolen->ptr;
95
	result->size = stolen->size;
96
	stolen->ptr = NULL;
97

98
	return ret;
99
}
100

101
static enum ll_merge_result ll_xdl_merge(const struct ll_merge_driver *drv_unused,
102
			mmbuffer_t *result,
103
			const char *path,
104
			mmfile_t *orig, const char *orig_name,
105
			mmfile_t *src1, const char *name1,
106
			mmfile_t *src2, const char *name2,
107
			const struct ll_merge_options *opts,
108
			int marker_size)
109
{
110
	enum ll_merge_result ret;
111
	xmparam_t xmp;
112
	int status;
113
	assert(opts);
114

115
	if (orig->size > MAX_XDIFF_SIZE ||
116
	    src1->size > MAX_XDIFF_SIZE ||
117
	    src2->size > MAX_XDIFF_SIZE ||
118
	    buffer_is_binary(orig->ptr, orig->size) ||
119
	    buffer_is_binary(src1->ptr, src1->size) ||
120
	    buffer_is_binary(src2->ptr, src2->size)) {
121
		return ll_binary_merge(drv_unused, result,
122
				       path,
123
				       orig, orig_name,
124
				       src1, name1,
125
				       src2, name2,
126
				       opts, marker_size);
127
	}
128

129
	memset(&xmp, 0, sizeof(xmp));
130
	xmp.level = XDL_MERGE_ZEALOUS;
131
	xmp.favor = opts->variant;
132
	xmp.xpp.flags = opts->xdl_opts;
133
	if (opts->conflict_style >= 0)
134
		xmp.style = opts->conflict_style;
135
	else if (git_xmerge_style >= 0)
136
		xmp.style = git_xmerge_style;
137
	if (marker_size > 0)
138
		xmp.marker_size = marker_size;
139
	xmp.ancestor = orig_name;
140
	xmp.file1 = name1;
141
	xmp.file2 = name2;
142
	status = xdl_merge(orig, src1, src2, &xmp, result);
143
	ret = (status > 0) ? LL_MERGE_CONFLICT : status;
144
	return ret;
145
}
146

147
static enum ll_merge_result ll_union_merge(const struct ll_merge_driver *drv_unused,
148
			  mmbuffer_t *result,
149
			  const char *path,
150
			  mmfile_t *orig, const char *orig_name,
151
			  mmfile_t *src1, const char *name1,
152
			  mmfile_t *src2, const char *name2,
153
			  const struct ll_merge_options *opts,
154
			  int marker_size)
155
{
156
	/* Use union favor */
157
	struct ll_merge_options o;
158
	assert(opts);
159
	o = *opts;
160
	o.variant = XDL_MERGE_FAVOR_UNION;
161
	return ll_xdl_merge(drv_unused, result, path,
162
			    orig, orig_name, src1, name1, src2, name2,
163
			    &o, marker_size);
164
}
165

166
#define LL_BINARY_MERGE 0
167
#define LL_TEXT_MERGE 1
168
#define LL_UNION_MERGE 2
169
static struct ll_merge_driver ll_merge_drv[] = {
170
	{ "binary", "built-in binary merge", ll_binary_merge },
171
	{ "text", "built-in 3-way text merge", ll_xdl_merge },
172
	{ "union", "built-in union merge", ll_union_merge },
173
};
174

175
static void create_temp(mmfile_t *src, char *path, size_t len)
176
{
177
	int fd;
178

179
	xsnprintf(path, len, ".merge_file_XXXXXX");
180
	fd = xmkstemp(path);
181
	if (write_in_full(fd, src->ptr, src->size) < 0)
182
		die_errno("unable to write temp-file");
183
	close(fd);
184
}
185

186
/*
187
 * User defined low-level merge driver support.
188
 */
189
static enum ll_merge_result ll_ext_merge(const struct ll_merge_driver *fn,
190
			mmbuffer_t *result,
191
			const char *path,
192
			mmfile_t *orig, const char *orig_name,
193
			mmfile_t *src1, const char *name1,
194
			mmfile_t *src2, const char *name2,
195
			const struct ll_merge_options *opts,
196
			int marker_size)
197
{
198
	char temp[3][50];
199
	struct strbuf cmd = STRBUF_INIT;
200
	const char *format = fn->cmdline;
201
	struct child_process child = CHILD_PROCESS_INIT;
202
	int status, fd, i;
203
	struct stat st;
204
	enum ll_merge_result ret;
205
	assert(opts);
206

207
	if (!fn->cmdline)
208
		die("custom merge driver %s lacks command line.", fn->name);
209

210
	result->ptr = NULL;
211
	result->size = 0;
212
	create_temp(orig, temp[0], sizeof(temp[0]));
213
	create_temp(src1, temp[1], sizeof(temp[1]));
214
	create_temp(src2, temp[2], sizeof(temp[2]));
215

216
	while (strbuf_expand_step(&cmd, &format)) {
217
		if (skip_prefix(format, "%", &format))
218
			strbuf_addch(&cmd, '%');
219
		else if (skip_prefix(format, "O", &format))
220
			strbuf_addstr(&cmd, temp[0]);
221
		else if (skip_prefix(format, "A", &format))
222
			strbuf_addstr(&cmd, temp[1]);
223
		else if (skip_prefix(format, "B", &format))
224
			strbuf_addstr(&cmd, temp[2]);
225
		else if (skip_prefix(format, "L", &format))
226
			strbuf_addf(&cmd, "%d", marker_size);
227
		else if (skip_prefix(format, "P", &format))
228
			sq_quote_buf(&cmd, path);
229
		else if (skip_prefix(format, "S", &format))
230
			sq_quote_buf(&cmd, orig_name ? orig_name : "");
231
		else if (skip_prefix(format, "X", &format))
232
			sq_quote_buf(&cmd, name1 ? name1 : "");
233
		else if (skip_prefix(format, "Y", &format))
234
			sq_quote_buf(&cmd, name2 ? name2 : "");
235
		else
236
			strbuf_addch(&cmd, '%');
237
	}
238

239
	child.use_shell = 1;
240
	strvec_push(&child.args, cmd.buf);
241
	status = run_command(&child);
242
	fd = open(temp[1], O_RDONLY);
243
	if (fd < 0)
244
		goto bad;
245
	if (fstat(fd, &st))
246
		goto close_bad;
247
	result->size = st.st_size;
248
	result->ptr = xmallocz(result->size);
249
	if (read_in_full(fd, result->ptr, result->size) != result->size) {
250
		FREE_AND_NULL(result->ptr);
251
		result->size = 0;
252
	}
253
 close_bad:
254
	close(fd);
255
 bad:
256
	for (i = 0; i < 3; i++)
257
		unlink_or_warn(temp[i]);
258
	strbuf_release(&cmd);
259
	if (!status)
260
		ret = LL_MERGE_OK;
261
	else if (status <= 128)
262
		ret = LL_MERGE_CONFLICT;
263
	else
264
		/* died due to a signal: WTERMSIG(status) + 128 */
265
		ret = LL_MERGE_ERROR;
266
	return ret;
267
}
268

269
/*
270
 * merge.default and merge.driver configuration items
271
 */
272
static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail;
273
static char *default_ll_merge;
274

275
static int read_merge_config(const char *var, const char *value,
276
			     const struct config_context *ctx UNUSED,
277
			     void *cb UNUSED)
278
{
279
	struct ll_merge_driver *fn;
280
	const char *key, *name;
281
	size_t namelen;
282

283
	if (!strcmp(var, "merge.default"))
284
		return git_config_string(&default_ll_merge, var, value);
285

286
	/*
287
	 * We are not interested in anything but "merge.<name>.variable";
288
	 * especially, we do not want to look at variables such as
289
	 * "merge.summary", "merge.tool", and "merge.verbosity".
290
	 */
291
	if (parse_config_key(var, "merge", &name, &namelen, &key) < 0 || !name)
292
		return 0;
293

294
	/*
295
	 * Find existing one as we might be processing merge.<name>.var2
296
	 * after seeing merge.<name>.var1.
297
	 */
298
	for (fn = ll_user_merge; fn; fn = fn->next)
299
		if (!xstrncmpz(fn->name, name, namelen))
300
			break;
301
	if (!fn) {
302
		CALLOC_ARRAY(fn, 1);
303
		fn->name = xmemdupz(name, namelen);
304
		fn->fn = ll_ext_merge;
305
		*ll_user_merge_tail = fn;
306
		ll_user_merge_tail = &(fn->next);
307
	}
308

309
	if (!strcmp("name", key)) {
310
		/*
311
		 * The description is leaking, but that's okay as we want to
312
		 * keep around the merge drivers anyway.
313
		 */
314
		return git_config_string((char **) &fn->description, var, value);
315
	}
316

317
	if (!strcmp("driver", key)) {
318
		if (!value)
319
			return config_error_nonbool(var);
320
		/*
321
		 * merge.<name>.driver specifies the command line:
322
		 *
323
		 *	command-line
324
		 *
325
		 * The command-line will be interpolated with the following
326
		 * tokens and is given to the shell:
327
		 *
328
		 *    %O - temporary file name for the merge base.
329
		 *    %A - temporary file name for our version.
330
		 *    %B - temporary file name for the other branches' version.
331
		 *    %L - conflict marker length
332
		 *    %P - the original path (safely quoted for the shell)
333
		 *    %S - the revision for the merge base
334
		 *    %X - the revision for our version
335
		 *    %Y - the revision for their version
336
		 *
337
		 * If the file is not named indentically in all versions, then each
338
		 * revision is joined with the corresponding path, separated by a colon.
339
		 * The external merge driver should write the results in the
340
		 * file named by %A, and signal that it has done with zero exit
341
		 * status.
342
		 */
343
		fn->cmdline = xstrdup(value);
344
		return 0;
345
	}
346

347
	if (!strcmp("recursive", key))
348
		return git_config_string(&fn->recursive, var, value);
349

350
	return 0;
351
}
352

353
static void initialize_ll_merge(void)
354
{
355
	if (ll_user_merge_tail)
356
		return;
357
	ll_user_merge_tail = &ll_user_merge;
358
	git_config(read_merge_config, NULL);
359
}
360

361
static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr)
362
{
363
	struct ll_merge_driver *fn;
364
	const char *name;
365
	int i;
366

367
	initialize_ll_merge();
368

369
	if (ATTR_TRUE(merge_attr))
370
		return &ll_merge_drv[LL_TEXT_MERGE];
371
	else if (ATTR_FALSE(merge_attr))
372
		return &ll_merge_drv[LL_BINARY_MERGE];
373
	else if (ATTR_UNSET(merge_attr)) {
374
		if (!default_ll_merge)
375
			return &ll_merge_drv[LL_TEXT_MERGE];
376
		else
377
			name = default_ll_merge;
378
	}
379
	else
380
		name = merge_attr;
381

382
	for (fn = ll_user_merge; fn; fn = fn->next)
383
		if (!strcmp(fn->name, name))
384
			return fn;
385

386
	for (i = 0; i < ARRAY_SIZE(ll_merge_drv); i++)
387
		if (!strcmp(ll_merge_drv[i].name, name))
388
			return &ll_merge_drv[i];
389

390
	/* default to the 3-way */
391
	return &ll_merge_drv[LL_TEXT_MERGE];
392
}
393

394
static void normalize_file(mmfile_t *mm, const char *path, struct index_state *istate)
395
{
396
	struct strbuf strbuf = STRBUF_INIT;
397
	if (renormalize_buffer(istate, path, mm->ptr, mm->size, &strbuf)) {
398
		free(mm->ptr);
399
		mm->size = strbuf.len;
400
		mm->ptr = strbuf_detach(&strbuf, NULL);
401
	}
402
}
403

404
enum ll_merge_result ll_merge(mmbuffer_t *result_buf,
405
	     const char *path,
406
	     mmfile_t *ancestor, const char *ancestor_label,
407
	     mmfile_t *ours, const char *our_label,
408
	     mmfile_t *theirs, const char *their_label,
409
	     struct index_state *istate,
410
	     const struct ll_merge_options *opts)
411
{
412
	struct attr_check *check = load_merge_attributes();
413
	static const struct ll_merge_options default_opts = LL_MERGE_OPTIONS_INIT;
414
	const char *ll_driver_name = NULL;
415
	int marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
416
	const struct ll_merge_driver *driver;
417

418
	if (!opts)
419
		opts = &default_opts;
420

421
	if (opts->renormalize) {
422
		normalize_file(ancestor, path, istate);
423
		normalize_file(ours, path, istate);
424
		normalize_file(theirs, path, istate);
425
	}
426

427
	git_check_attr(istate, path, check);
428
	ll_driver_name = check->items[0].value;
429
	if (check->items[1].value) {
430
		marker_size = atoi(check->items[1].value);
431
		if (marker_size <= 0)
432
			marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
433
	}
434
	driver = find_ll_merge_driver(ll_driver_name);
435

436
	if (opts->virtual_ancestor) {
437
		if (driver->recursive)
438
			driver = find_ll_merge_driver(driver->recursive);
439
	}
440
	if (opts->extra_marker_size) {
441
		marker_size += opts->extra_marker_size;
442
	}
443
	return driver->fn(driver, result_buf, path, ancestor, ancestor_label,
444
			  ours, our_label, theirs, their_label,
445
			  opts, marker_size);
446
}
447

448
int ll_merge_marker_size(struct index_state *istate, const char *path)
449
{
450
	static struct attr_check *check;
451
	int marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
452

453
	if (!check)
454
		check = attr_check_initl("conflict-marker-size", NULL);
455
	git_check_attr(istate, path, check);
456
	if (check->items[0].value) {
457
		marker_size = atoi(check->items[0].value);
458
		if (marker_size <= 0)
459
			marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
460
	}
461
	return marker_size;
462
}
463

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.