merge_file.c 7.97 KB
Newer Older
Edward Thomson committed
1 2 3 4 5 6 7 8
/*
 * Copyright (C) the libgit2 contributors. All rights reserved.
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */

#include "common.h"
9

Edward Thomson committed
10
#include "repository.h"
11 12 13
#include "posix.h"
#include "fileops.h"
#include "index.h"
14
#include "diff_xdiff.h"
15
#include "merge.h"
Edward Thomson committed
16 17 18 19

#include "git2/repository.h"
#include "git2/object.h"
#include "git2/index.h"
20
#include "git2/merge.h"
Edward Thomson committed
21 22 23

#include "xdiff/xdiff.h"

24 25 26 27 28
/* only examine the first 8000 bytes for binaryness.
 * https://github.com/git/git/blob/77bd3ea9f54f1584147b594abc04c26ca516d987/xdiff-interface.c#L197
 */
#define GIT_MERGE_FILE_BINARY_SIZE 8000

Edward Thomson committed
29 30
#define GIT_MERGE_FILE_SIDE_EXISTS(X)	((X)->mode != 0)

31 32 33 34
int git_merge_file__input_from_index(
	git_merge_file_input *input_out,
	git_odb_object **odb_object_out,
	git_odb *odb,
Edward Thomson committed
35 36 37
	const git_index_entry *entry)
{
	int error = 0;
nulltoken committed
38

39
	assert(input_out && odb_object_out && odb && entry);
nulltoken committed
40

41
	if ((error = git_odb_read(odb_object_out, odb, &entry->id)) < 0)
Edward Thomson committed
42
		goto done;
nulltoken committed
43

44 45 46 47
	input_out->path = entry->path;
	input_out->mode = entry->mode;
	input_out->ptr = (char *)git_odb_object_data(*odb_object_out);
	input_out->size = git_odb_object_size(*odb_object_out);
nulltoken committed
48

Edward Thomson committed
49 50 51 52
done:
	return error;
}

53 54 55
static void merge_file_normalize_opts(
	git_merge_file_options *out,
	const git_merge_file_options *given_opts)
Edward Thomson committed
56
{
57 58 59 60 61 62
	if (given_opts)
		memcpy(out, given_opts, sizeof(git_merge_file_options));
	else {
		git_merge_file_options default_opts = GIT_MERGE_FILE_OPTIONS_INIT;
		memcpy(out, &default_opts, sizeof(git_merge_file_options));
	}
Edward Thomson committed
63 64
}

65
static int merge_file__xdiff(
Edward Thomson committed
66
	git_merge_file_result *out,
67 68 69 70
	const git_merge_file_input *ancestor,
	const git_merge_file_input *ours,
	const git_merge_file_input *theirs,
	const git_merge_file_options *given_opts)
Edward Thomson committed
71 72
{
	xmparam_t xmparam;
73
	mmfile_t ancestor_mmfile = {0}, our_mmfile = {0}, their_mmfile = {0};
Edward Thomson committed
74
	mmbuffer_t mmbuffer;
75 76
	git_merge_file_options options = GIT_MERGE_FILE_OPTIONS_INIT;
	const char *path;
Edward Thomson committed
77 78 79 80 81
	int xdl_result;
	int error = 0;

	memset(out, 0x0, sizeof(git_merge_file_result));

Russell Belfer committed
82
	merge_file_normalize_opts(&options, given_opts);
nulltoken committed
83

Edward Thomson committed
84 85
	memset(&xmparam, 0x0, sizeof(xmparam_t));

86 87 88 89 90 91
	if (ancestor) {
		xmparam.ancestor = (options.ancestor_label) ?
			options.ancestor_label : ancestor->path;
		ancestor_mmfile.ptr = (char *)ancestor->ptr;
		ancestor_mmfile.size = ancestor->size;
	}
Edward Thomson committed
92

93 94 95 96 97 98 99 100 101 102 103
	xmparam.file1 = (options.our_label) ?
		options.our_label : ours->path;
	our_mmfile.ptr = (char *)ours->ptr;
	our_mmfile.size = ours->size;

	xmparam.file2 = (options.their_label) ?
		options.their_label : theirs->path;
	their_mmfile.ptr = (char *)theirs->ptr;
	their_mmfile.size = theirs->size;

	if (options.favor == GIT_MERGE_FILE_FAVOR_OURS)
Edward Thomson committed
104
		xmparam.favor = XDL_MERGE_FAVOR_OURS;
105
	else if (options.favor == GIT_MERGE_FILE_FAVOR_THEIRS)
Edward Thomson committed
106
		xmparam.favor = XDL_MERGE_FAVOR_THEIRS;
107
	else if (options.favor == GIT_MERGE_FILE_FAVOR_UNION)
Edward Thomson committed
108
		xmparam.favor = XDL_MERGE_FAVOR_UNION;
Edward Thomson committed
109

110
	xmparam.level = (options.flags & GIT_MERGE_FILE_SIMPLIFY_ALNUM) ?
111 112
		XDL_MERGE_ZEALOUS_ALNUM : XDL_MERGE_ZEALOUS;

113
	if (options.flags & GIT_MERGE_FILE_STYLE_DIFF3)
114 115
		xmparam.style = XDL_MERGE_DIFF3;

116
	if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE)
117
		xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE;
118
	if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE)
119
		xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE_CHANGE;
120
	if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL)
121 122
		xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE_AT_EOL;

123 124 125 126 127 128
	if (options.flags & GIT_MERGE_FILE_DIFF_PATIENCE)
		xmparam.xpp.flags |= XDF_PATIENCE_DIFF;

	if (options.flags & GIT_MERGE_FILE_DIFF_MINIMAL)
		xmparam.xpp.flags |= XDF_NEED_MINIMAL;

129 130
	xmparam.marker_size = options.marker_size;

131 132
	if ((xdl_result = xdl_merge(&ancestor_mmfile, &our_mmfile,
		&their_mmfile, &xmparam, &mmbuffer)) < 0) {
133
		giterr_set(GITERR_MERGE, "failed to merge files");
Edward Thomson committed
134 135 136
		error = -1;
		goto done;
	}
nulltoken committed
137

138 139
	path = git_merge_file__best_path(
		ancestor ? ancestor->path : NULL,
140 141
		ours->path,
		theirs->path);
142 143

	if (path != NULL && (out->path = git__strdup(path)) == NULL) {
144 145 146 147
		error = -1;
		goto done;
	}

Edward Thomson committed
148
	out->automergeable = (xdl_result == 0);
Russell Belfer committed
149
	out->ptr = (const char *)mmbuffer.ptr;
Edward Thomson committed
150
	out->len = mmbuffer.size;
151 152
	out->mode = git_merge_file__best_mode(
		ancestor ? ancestor->mode : 0,
153 154
		ours->mode,
		theirs->mode);
Edward Thomson committed
155 156

done:
157 158 159
	if (error < 0)
		git_merge_file_result_free(out);

Edward Thomson committed
160 161
	return error;
}
162

163 164 165 166
static bool merge_file__is_binary(const git_merge_file_input *file)
{
	size_t len = file ? file->size : 0;

167
	if (len > GIT_XDIFF_MAX_SIZE)
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
		return true;
	if (len > GIT_MERGE_FILE_BINARY_SIZE)
		len = GIT_MERGE_FILE_BINARY_SIZE;

	return len ? (memchr(file->ptr, 0, len) != NULL) : false;
}

static int merge_file__binary(
	git_merge_file_result *out,
	const git_merge_file_input *ours,
	const git_merge_file_input *theirs,
	const git_merge_file_options *given_opts)
{
	const git_merge_file_input *favored = NULL;

	memset(out, 0x0, sizeof(git_merge_file_result));

	if (given_opts && given_opts->favor == GIT_MERGE_FILE_FAVOR_OURS)
		favored = ours;
	else if (given_opts && given_opts->favor == GIT_MERGE_FILE_FAVOR_THEIRS)
		favored = theirs;
	else
		goto done;

	if ((out->path = git__strdup(favored->path)) == NULL ||
		(out->ptr = git__malloc(favored->size)) == NULL)
		goto done;

	memcpy((char *)out->ptr, favored->ptr, favored->size);
	out->len = favored->size;
	out->mode = favored->mode;
	out->automergeable = 1;

done:
	return 0;
}

static int merge_file__from_inputs(
	git_merge_file_result *out,
	const git_merge_file_input *ancestor,
	const git_merge_file_input *ours,
	const git_merge_file_input *theirs,
	const git_merge_file_options *given_opts)
{
	if (merge_file__is_binary(ancestor) ||
		merge_file__is_binary(ours) ||
		merge_file__is_binary(theirs))
		return merge_file__binary(out, ours, theirs, given_opts);

	return merge_file__xdiff(out, ancestor, ours, theirs, given_opts);
}

220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
static git_merge_file_input *git_merge_file__normalize_inputs(
	git_merge_file_input *out,
	const git_merge_file_input *given)
{
	memcpy(out, given, sizeof(git_merge_file_input));

	if (!out->path)
		out->path = "file.txt";

	if (!out->mode)
		out->mode = 0100644;

	return out;
}

int git_merge_file(
	git_merge_file_result *out,
	const git_merge_file_input *ancestor,
	const git_merge_file_input *ours,
	const git_merge_file_input *theirs,
	const git_merge_file_options *options)
{
	git_merge_file_input inputs[3] = { {0} };

	assert(out && ours && theirs);

	memset(out, 0x0, sizeof(git_merge_file_result));

	if (ancestor)
		ancestor = git_merge_file__normalize_inputs(&inputs[0], ancestor);

	ours = git_merge_file__normalize_inputs(&inputs[1], ours);
	theirs = git_merge_file__normalize_inputs(&inputs[2], theirs);

254
	return merge_file__from_inputs(out, ancestor, ours, theirs, options);
255 256 257 258 259 260 261 262 263 264
}

int git_merge_file_from_index(
	git_merge_file_result *out,
	git_repository *repo,
	const git_index_entry *ancestor,
	const git_index_entry *ours,
	const git_index_entry *theirs,
	const git_merge_file_options *options)
{
265 266
	git_merge_file_input *ancestor_ptr = NULL,
		ancestor_input = {0}, our_input = {0}, their_input = {0};
267 268 269 270 271 272 273 274 275 276 277 278 279
	git_odb *odb = NULL;
	git_odb_object *odb_object[3] = { 0 };
	int error = 0;

	assert(out && repo && ours && theirs);

	memset(out, 0x0, sizeof(git_merge_file_result));

	if ((error = git_repository_odb(&odb, repo)) < 0)
		goto done;

	if (ancestor) {
		if ((error = git_merge_file__input_from_index(
280
			&ancestor_input, &odb_object[0], odb, ancestor)) < 0)
281 282
			goto done;

283
		ancestor_ptr = &ancestor_input;
284 285 286
	}

	if ((error = git_merge_file__input_from_index(
287 288 289
			&our_input, &odb_object[1], odb, ours)) < 0 ||
		(error = git_merge_file__input_from_index(
			&their_input, &odb_object[2], odb, theirs)) < 0)
290 291
		goto done;

292 293
	error = merge_file__from_inputs(out,
		ancestor_ptr, &our_input, &their_input, options);
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308

done:
	git_odb_object_free(odb_object[0]);
	git_odb_object_free(odb_object[1]);
	git_odb_object_free(odb_object[2]);
	git_odb_free(odb);

	return error;
}

void git_merge_file_result_free(git_merge_file_result *result)
{
	if (result == NULL)
		return;

309
	git__free((char *)result->path);
310
	git__free((char *)result->ptr);
311
}