merge_file.c 6.92 KB
Newer Older
Edward Thomson committed
1 2 3 4 5 6 7 8 9 10
/*
 * 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"
#include "merge_file.h"
11 12 13
#include "posix.h"
#include "fileops.h"
#include "index.h"
Edward Thomson committed
14 15 16 17 18 19 20 21 22 23 24 25 26 27

#include "git2/repository.h"
#include "git2/object.h"
#include "git2/index.h"

#include "xdiff/xdiff.h"

#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)
{
28 29
	if (!ancestor) {
		if (ours && theirs && strcmp(ours->path, theirs->path) == 0)
Edward Thomson committed
30 31 32 33
			return ours->path;

		return NULL;
	}
nulltoken committed
34

35 36 37 38
	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
39

Edward Thomson committed
40 41 42 43 44 45 46 47 48 49 50 51 52
	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.
	 */
53 54 55
	if (!ancestor) {
		if ((ours && ours->mode == GIT_FILEMODE_BLOB_EXECUTABLE) ||
			(theirs && theirs->mode == GIT_FILEMODE_BLOB_EXECUTABLE))
Edward Thomson committed
56
			return GIT_FILEMODE_BLOB_EXECUTABLE;
nulltoken committed
57

Edward Thomson committed
58
		return GIT_FILEMODE_BLOB;
59 60 61
	} else if (ours && theirs) {
		if (ancestor->mode == ours->mode)
			return theirs->mode;
nulltoken committed
62

Edward Thomson committed
63
		return ours->mode;
64
	}
nulltoken committed
65

Edward Thomson committed
66 67 68
	return 0;
}

69 70 71 72
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
73 74 75
	const git_index_entry *entry)
{
	int error = 0;
nulltoken committed
76

77
	assert(input_out && odb_object_out && odb && entry);
nulltoken committed
78

79
	if ((error = git_odb_read(odb_object_out, odb, &entry->id)) < 0)
Edward Thomson committed
80
		goto done;
nulltoken committed
81

82 83 84 85
	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
86

Edward Thomson committed
87 88 89 90
done:
	return error;
}

91 92 93
static void merge_file_normalize_opts(
	git_merge_file_options *out,
	const git_merge_file_options *given_opts)
Edward Thomson committed
94
{
95 96 97 98 99 100
	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
101 102
}

103
static int git_merge_file__from_inputs(
Edward Thomson committed
104
	git_merge_file_result *out,
105 106 107 108
	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
109 110
{
	xmparam_t xmparam;
111
	mmfile_t ancestor_mmfile = {0}, our_mmfile = {0}, their_mmfile = {0};
Edward Thomson committed
112
	mmbuffer_t mmbuffer;
113 114
	git_merge_file_options options = GIT_MERGE_FILE_OPTIONS_INIT;
	const char *path;
Edward Thomson committed
115 116 117 118 119
	int xdl_result;
	int error = 0;

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

Russell Belfer committed
120
	merge_file_normalize_opts(&options, given_opts);
nulltoken committed
121

Edward Thomson committed
122 123
	memset(&xmparam, 0x0, sizeof(xmparam_t));

124 125 126 127 128 129
	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
130

131 132 133 134 135 136 137 138 139 140 141
	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
142
		xmparam.favor = XDL_MERGE_FAVOR_OURS;
143
	else if (options.favor == GIT_MERGE_FILE_FAVOR_THEIRS)
Edward Thomson committed
144
		xmparam.favor = XDL_MERGE_FAVOR_THEIRS;
145
	else if (options.favor == GIT_MERGE_FILE_FAVOR_UNION)
Edward Thomson committed
146
		xmparam.favor = XDL_MERGE_FAVOR_UNION;
Edward Thomson committed
147

148
	xmparam.level = (options.flags & GIT_MERGE_FILE_SIMPLIFY_ALNUM) ?
149 150
		XDL_MERGE_ZEALOUS_ALNUM : XDL_MERGE_ZEALOUS;

151
	if (options.flags & GIT_MERGE_FILE_STYLE_DIFF3)
152 153
		xmparam.style = XDL_MERGE_DIFF3;

154 155
	if ((xdl_result = xdl_merge(&ancestor_mmfile, &our_mmfile,
		&their_mmfile, &xmparam, &mmbuffer)) < 0) {
Edward Thomson committed
156 157 158 159
		giterr_set(GITERR_MERGE, "Failed to merge files.");
		error = -1;
		goto done;
	}
nulltoken committed
160

161 162 163 164 165 166
	if ((path = merge_file_best_path(ancestor, ours, theirs)) != NULL &&
		(out->path = strdup(path)) == NULL) {
		error = -1;
		goto done;
	}

Edward Thomson committed
167
	out->automergeable = (xdl_result == 0);
Russell Belfer committed
168
	out->ptr = (const char *)mmbuffer.ptr;
Edward Thomson committed
169
	out->len = mmbuffer.size;
170
	out->mode = merge_file_best_mode(ancestor, ours, theirs);
Edward Thomson committed
171 172

done:
173 174 175
	if (error < 0)
		git_merge_file_result_free(out);

Edward Thomson committed
176 177
	return error;
}
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 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 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274

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

	return git_merge_file__from_inputs(out, ancestor, ours, theirs, options);
}

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)
{
	git_merge_file_input inputs[3] = { {0} },
		*ancestor_input = NULL, *our_input = NULL, *their_input = NULL;
	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(
			&inputs[0], &odb_object[0], odb, ancestor)) < 0)
			goto done;

		ancestor_input = &inputs[0];
	}

	if ((error = git_merge_file__input_from_index(
		&inputs[1], &odb_object[1], odb, ours)) < 0)
		goto done;

	our_input = &inputs[1];

	if ((error = git_merge_file__input_from_index(
		&inputs[2], &odb_object[2], odb, theirs)) < 0)
		goto done;

	their_input = &inputs[2];

	if ((error = git_merge_file__from_inputs(out,
		ancestor_input, our_input, their_input, options)) < 0)
		goto done;

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;

275
	git__free((char *)result->path);
276 277

	/* xdiff uses malloc() not git_malloc, so we use free(), not git_free() */
278
	free((char *)result->ptr);
279
}