merge_file.c 8.1 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
#include "posix.h"
12
#include "futils.h"
13
#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
static int merge_file_input_from_index(
32 33 34
	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 40 41 42
	GIT_ASSERT_ARG(input_out);
	GIT_ASSERT_ARG(odb_object_out);
	GIT_ASSERT_ARG(odb);
	GIT_ASSERT_ARG(entry);
nulltoken committed
43

44
	if ((error = git_odb_read(odb_object_out, odb, &entry->id)) < 0)
Edward Thomson committed
45
		goto done;
nulltoken committed
46

47 48 49 50
	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
51

Edward Thomson committed
52 53 54 55
done:
	return error;
}

56 57 58
static void merge_file_normalize_opts(
	git_merge_file_options *out,
	const git_merge_file_options *given_opts)
Edward Thomson committed
59
{
60 61 62 63 64 65
	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
66 67
}

68
static int merge_file__xdiff(
Edward Thomson committed
69
	git_merge_file_result *out,
70 71 72 73
	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
74 75
{
	xmparam_t xmparam;
76
	mmfile_t ancestor_mmfile = {0}, our_mmfile = {0}, their_mmfile = {0};
Edward Thomson committed
77
	mmbuffer_t mmbuffer;
78 79
	git_merge_file_options options = GIT_MERGE_FILE_OPTIONS_INIT;
	const char *path;
Edward Thomson committed
80 81 82 83 84
	int xdl_result;
	int error = 0;

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

Russell Belfer committed
85
	merge_file_normalize_opts(&options, given_opts);
nulltoken committed
86

Edward Thomson committed
87 88
	memset(&xmparam, 0x0, sizeof(xmparam_t));

89 90 91 92 93 94
	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
95

96 97 98 99 100 101 102 103 104 105 106
	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
107
		xmparam.favor = XDL_MERGE_FAVOR_OURS;
108
	else if (options.favor == GIT_MERGE_FILE_FAVOR_THEIRS)
Edward Thomson committed
109
		xmparam.favor = XDL_MERGE_FAVOR_THEIRS;
110
	else if (options.favor == GIT_MERGE_FILE_FAVOR_UNION)
Edward Thomson committed
111
		xmparam.favor = XDL_MERGE_FAVOR_UNION;
Edward Thomson committed
112

113
	xmparam.level = (options.flags & GIT_MERGE_FILE_SIMPLIFY_ALNUM) ?
114 115
		XDL_MERGE_ZEALOUS_ALNUM : XDL_MERGE_ZEALOUS;

116
	if (options.flags & GIT_MERGE_FILE_STYLE_DIFF3)
117 118
		xmparam.style = XDL_MERGE_DIFF3;

119
	if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE)
120
		xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE;
121
	if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE)
122
		xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE_CHANGE;
123
	if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL)
124 125
		xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE_AT_EOL;

126 127 128 129 130 131
	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;

132 133
	xmparam.marker_size = options.marker_size;

134 135
	if ((xdl_result = xdl_merge(&ancestor_mmfile, &our_mmfile,
		&their_mmfile, &xmparam, &mmbuffer)) < 0) {
136
		git_error_set(GIT_ERROR_MERGE, "failed to merge files");
Edward Thomson committed
137 138 139
		error = -1;
		goto done;
	}
nulltoken committed
140

141 142
	path = git_merge_file__best_path(
		ancestor ? ancestor->path : NULL,
143 144
		ours->path,
		theirs->path);
145 146

	if (path != NULL && (out->path = git__strdup(path)) == NULL) {
147 148 149 150
		error = -1;
		goto done;
	}

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

done:
160 161 162
	if (error < 0)
		git_merge_file_result_free(out);

Edward Thomson committed
163 164
	return error;
}
165

166 167 168 169
static bool merge_file__is_binary(const git_merge_file_input *file)
{
	size_t len = file ? file->size : 0;

170
	if (len > GIT_XDIFF_MAX_SIZE)
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 220 221 222
		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);
}

223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
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} };

247 248 249
	GIT_ASSERT_ARG(out);
	GIT_ASSERT_ARG(ours);
	GIT_ASSERT_ARG(theirs);
250 251 252 253 254 255 256 257 258

	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);

259
	return merge_file__from_inputs(out, ancestor, ours, theirs, options);
260 261 262 263 264 265 266 267 268 269
}

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)
{
270 271
	git_merge_file_input *ancestor_ptr = NULL,
		ancestor_input = {0}, our_input = {0}, their_input = {0};
272 273 274 275
	git_odb *odb = NULL;
	git_odb_object *odb_object[3] = { 0 };
	int error = 0;

276 277 278 279
	GIT_ASSERT_ARG(out);
	GIT_ASSERT_ARG(repo);
	GIT_ASSERT_ARG(ours);
	GIT_ASSERT_ARG(theirs);
280 281 282 283 284 285 286

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

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

	if (ancestor) {
287
		if ((error = merge_file_input_from_index(
288
			&ancestor_input, &odb_object[0], odb, ancestor)) < 0)
289 290
			goto done;

291
		ancestor_ptr = &ancestor_input;
292 293
	}

294 295
	if ((error = merge_file_input_from_index(&our_input, &odb_object[1], odb, ours)) < 0 ||
	    (error = merge_file_input_from_index(&their_input, &odb_object[2], odb, theirs)) < 0)
296 297
		goto done;

298 299
	error = merge_file__from_inputs(out,
		ancestor_ptr, &our_input, &their_input, options);
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314

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;

315
	git__free((char *)result->path);
316
	git__free((char *)result->ptr);
317
}