merge_file.c 8.94 KB
Newer Older
Edward Thomson committed
1 2 3 4 5 6 7 8 9
/*
 * 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"
#include "repository.h"
10 11 12
#include "posix.h"
#include "fileops.h"
#include "index.h"
13
#include "diff_xdiff.h"
Edward Thomson committed
14 15 16 17

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

#include "xdiff/xdiff.h"

22 23 24 25 26
/* 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
27 28 29 30 31 32 33
#define GIT_MERGE_FILE_SIDE_EXISTS(X)	((X)->mode != 0)

GIT_INLINE(const char *) merge_file_best_path(
	const git_merge_file_input *ancestor,
	const git_merge_file_input *ours,
	const git_merge_file_input *theirs)
{
34 35
	if (!ancestor) {
		if (ours && theirs && strcmp(ours->path, theirs->path) == 0)
Edward Thomson committed
36 37 38 39
			return ours->path;

		return NULL;
	}
nulltoken committed
40

41 42 43 44
	if (ours && strcmp(ancestor->path, ours->path) == 0)
		return theirs ? theirs->path : NULL;
	else if(theirs && strcmp(ancestor->path, theirs->path) == 0)
		return ours ? ours->path : NULL;
nulltoken committed
45

Edward Thomson committed
46 47 48 49 50 51 52 53 54 55 56 57 58
	return NULL;
}

GIT_INLINE(int) merge_file_best_mode(
	const git_merge_file_input *ancestor,
	const git_merge_file_input *ours,
	const git_merge_file_input *theirs)
{
	/*
	 * If ancestor didn't exist and either ours or theirs is executable,
	 * assume executable.  Otherwise, if any mode changed from the ancestor,
	 * use that one.
	 */
59 60 61
	if (!ancestor) {
		if ((ours && ours->mode == GIT_FILEMODE_BLOB_EXECUTABLE) ||
			(theirs && theirs->mode == GIT_FILEMODE_BLOB_EXECUTABLE))
Edward Thomson committed
62
			return GIT_FILEMODE_BLOB_EXECUTABLE;
nulltoken committed
63

Edward Thomson committed
64
		return GIT_FILEMODE_BLOB;
65 66 67
	} else if (ours && theirs) {
		if (ancestor->mode == ours->mode)
			return theirs->mode;
nulltoken committed
68

Edward Thomson committed
69
		return ours->mode;
70
	}
nulltoken committed
71

Edward Thomson committed
72 73 74
	return 0;
}

75 76 77 78
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
79 80 81
	const git_index_entry *entry)
{
	int error = 0;
nulltoken committed
82

83
	assert(input_out && odb_object_out && odb && entry);
nulltoken committed
84

85
	if ((error = git_odb_read(odb_object_out, odb, &entry->id)) < 0)
Edward Thomson committed
86
		goto done;
nulltoken committed
87

88 89 90 91
	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
92

Edward Thomson committed
93 94 95 96
done:
	return error;
}

97 98 99
static void merge_file_normalize_opts(
	git_merge_file_options *out,
	const git_merge_file_options *given_opts)
Edward Thomson committed
100
{
101 102 103 104 105 106
	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
107 108
}

109
static int merge_file__xdiff(
Edward Thomson committed
110
	git_merge_file_result *out,
111 112 113 114
	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
115 116
{
	xmparam_t xmparam;
117
	mmfile_t ancestor_mmfile = {0}, our_mmfile = {0}, their_mmfile = {0};
Edward Thomson committed
118
	mmbuffer_t mmbuffer;
119 120
	git_merge_file_options options = GIT_MERGE_FILE_OPTIONS_INIT;
	const char *path;
Edward Thomson committed
121 122 123 124 125
	int xdl_result;
	int error = 0;

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

Russell Belfer committed
126
	merge_file_normalize_opts(&options, given_opts);
nulltoken committed
127

Edward Thomson committed
128 129
	memset(&xmparam, 0x0, sizeof(xmparam_t));

130 131 132 133 134 135
	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
136

137 138 139 140 141 142 143 144 145 146 147
	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
148
		xmparam.favor = XDL_MERGE_FAVOR_OURS;
149
	else if (options.favor == GIT_MERGE_FILE_FAVOR_THEIRS)
Edward Thomson committed
150
		xmparam.favor = XDL_MERGE_FAVOR_THEIRS;
151
	else if (options.favor == GIT_MERGE_FILE_FAVOR_UNION)
Edward Thomson committed
152
		xmparam.favor = XDL_MERGE_FAVOR_UNION;
Edward Thomson committed
153

154
	xmparam.level = (options.flags & GIT_MERGE_FILE_SIMPLIFY_ALNUM) ?
155 156
		XDL_MERGE_ZEALOUS_ALNUM : XDL_MERGE_ZEALOUS;

157
	if (options.flags & GIT_MERGE_FILE_STYLE_DIFF3)
158 159
		xmparam.style = XDL_MERGE_DIFF3;

160
	if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE)
161
		xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE;
162
	if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE)
163
		xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE_CHANGE;
164
	if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL)
165 166
		xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE_AT_EOL;

167 168 169 170 171 172
	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;

173 174
	if ((xdl_result = xdl_merge(&ancestor_mmfile, &our_mmfile,
		&their_mmfile, &xmparam, &mmbuffer)) < 0) {
Edward Thomson committed
175 176 177 178
		giterr_set(GITERR_MERGE, "Failed to merge files.");
		error = -1;
		goto done;
	}
nulltoken committed
179

180 181 182 183 184 185
	if ((path = merge_file_best_path(ancestor, ours, theirs)) != NULL &&
		(out->path = strdup(path)) == NULL) {
		error = -1;
		goto done;
	}

Edward Thomson committed
186
	out->automergeable = (xdl_result == 0);
Russell Belfer committed
187
	out->ptr = (const char *)mmbuffer.ptr;
Edward Thomson committed
188
	out->len = mmbuffer.size;
189
	out->mode = merge_file_best_mode(ancestor, ours, theirs);
Edward Thomson committed
190 191

done:
192 193 194
	if (error < 0)
		git_merge_file_result_free(out);

Edward Thomson committed
195 196
	return error;
}
197

198 199 200 201
static bool merge_file__is_binary(const git_merge_file_input *file)
{
	size_t len = file ? file->size : 0;

202
	if (len > GIT_XDIFF_MAX_SIZE)
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 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 254
		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);
}

255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
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);

289
	return merge_file__from_inputs(out, ancestor, ours, theirs, options);
290 291 292 293 294 295 296 297 298 299
}

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)
{
300 301
	git_merge_file_input *ancestor_ptr = NULL,
		ancestor_input = {0}, our_input = {0}, their_input = {0};
302 303 304 305 306 307 308 309 310 311 312 313 314
	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(
315
			&ancestor_input, &odb_object[0], odb, ancestor)) < 0)
316 317
			goto done;

318
		ancestor_ptr = &ancestor_input;
319 320 321
	}

	if ((error = git_merge_file__input_from_index(
322 323 324
			&our_input, &odb_object[1], odb, ours)) < 0 ||
		(error = git_merge_file__input_from_index(
			&their_input, &odb_object[2], odb, theirs)) < 0)
325 326
		goto done;

327 328
	error = merge_file__from_inputs(out,
		ancestor_ptr, &our_input, &their_input, options);
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343

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;

344
	git__free((char *)result->path);
345
	git__free((char *)result->ptr);
346
}