1 /*
2  * libgit2 "merge" example - shows how to perform merges
3  *
4  * Written by the libgit2 contributors
5  *
6  * To the extent possible under law, the author(s) have dedicated all copyright
7  * and related and neighboring rights to this software to the public domain
8  * worldwide. This software is distributed without any warranty.
9  *
10  * You should have received a copy of the CC0 Public Domain Dedication along
11  * with this software. If not, see
12  * <http://creativecommons.org/publicdomain/zero/1.0/>.
13  */
14 module libgit2_d.example.merge;
15 
16 
17 private static import core.stdc.stdio;
18 private static import core.stdc.stdlib;
19 private static import core.stdc.string;
20 private static import libgit2_d.annotated_commit;
21 private static import libgit2_d.branch;
22 private static import libgit2_d.checkout;
23 private static import libgit2_d.commit;
24 private static import libgit2_d.errors;
25 private static import libgit2_d.example.args;
26 private static import libgit2_d.example.common;
27 private static import libgit2_d.index;
28 private static import libgit2_d.merge;
29 private static import libgit2_d.object;
30 private static import libgit2_d.oid;
31 private static import libgit2_d.refs;
32 private static import libgit2_d.repository;
33 private static import libgit2_d.signature;
34 private static import libgit2_d.tree;
35 private static import libgit2_d.types;
36 private static import std.bitmanip;
37 
38 package:
39 
40 /** The following example demonstrates how to do merges with libgit2.
41  *
42  * It will merge whatever commit-ish you pass in into the current branch.
43  *
44  * Recognized options are :
45  *  --no-commit: don't actually commit the merge.
46  *
47  */
48 
49 public struct merge_options
50 {
51 	const (char)** heads;
52 	size_t heads_count;
53 
54 	libgit2_d.types.git_annotated_commit** annotated;
55 	size_t annotated_count;
56 
57 	mixin
58 	(
59 		std.bitmanip.bitfields!
60 		(
61 			int, "no_commit", 1,
62 			int, "not_used", 7
63 		)
64 	);
65 }
66 
67 nothrow @nogc
68 private void print_usage()
69 
70 	in
71 	{
72 	}
73 
74 	do
75 	{
76 		core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "usage: merge [--no-commit] <commit...>\n");
77 		core.stdc.stdlib.exit(1);
78 	}
79 
80 nothrow @nogc
81 private void merge_options_init(.merge_options* opts)
82 
83 	in
84 	{
85 	}
86 
87 	do
88 	{
89 		core.stdc..string.memset(opts, 0, (*opts).sizeof);
90 
91 		opts.heads = null;
92 		opts.heads_count = 0;
93 		opts.annotated = null;
94 		opts.annotated_count = 0;
95 	}
96 
97 nothrow @nogc
98 private void opts_add_refish(.merge_options* opts, const (char)* refish)
99 
100 	in
101 	{
102 		assert(opts != null);
103 	}
104 
105 	do
106 	{
107 		size_t sz = ++opts.heads_count * opts.heads[0].sizeof;
108 		opts.heads = cast(const (char)**)(libgit2_d.example.common.xrealloc(cast(void*)(opts.heads), sz));
109 		opts.heads[opts.heads_count - 1] = refish;
110 	}
111 
112 nothrow @nogc
113 private void parse_options(const (char)** repo_path, .merge_options* opts, int argc, char** argv)
114 
115 	in
116 	{
117 	}
118 
119 	do
120 	{
121 		libgit2_d.example.args.args_info args = libgit2_d.example.args.ARGS_INFO_INIT(argc, argv);
122 
123 		if (argc <= 1) {
124 			.print_usage();
125 		}
126 
127 		for (args.pos = 1; args.pos < argc; ++args.pos) {
128 			const (char)* curr = argv[args.pos];
129 
130 			if (curr[0] != '-') {
131 				.opts_add_refish(opts, curr);
132 			} else if (!core.stdc..string.strcmp(curr, "--no-commit")) {
133 				opts.no_commit = 1;
134 			} else if (libgit2_d.example.args.match_str_arg(repo_path, &args, "--git-dir")) {
135 				continue;
136 			} else {
137 				.print_usage();
138 			}
139 		}
140 	}
141 
142 nothrow @nogc
143 private int resolve_heads(libgit2_d.types.git_repository* repo, .merge_options* opts)
144 
145 	in
146 	{
147 	}
148 
149 	do
150 	{
151 		libgit2_d.types.git_annotated_commit** annotated = cast(libgit2_d.types.git_annotated_commit**)(core.stdc.stdlib.calloc(opts.heads_count, (libgit2_d.types.git_annotated_commit*).sizeof));
152 		size_t annotated_count = 0;
153 
154 		for (size_t i = 0; i < opts.heads_count; i++) {
155 			int err = libgit2_d.example.common.resolve_refish(&annotated[annotated_count++], repo, opts.heads[i]);
156 
157 			if (err != 0) {
158 				core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "failed to resolve refish %s: %s\n", opts.heads[i], libgit2_d.errors.git_error_last().message);
159 				annotated_count--;
160 
161 				continue;
162 			}
163 		}
164 
165 		if (annotated_count != opts.heads_count) {
166 			core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "unable to parse some refish\n");
167 			core.stdc.stdlib.free(annotated);
168 
169 			return -1;
170 		}
171 
172 		opts.annotated = annotated;
173 		opts.annotated_count = annotated_count;
174 
175 		return 0;
176 	}
177 
178 nothrow @nogc
179 private int perform_fastforward(libgit2_d.types.git_repository* repo, const (libgit2_d.oid.git_oid)* target_oid, int is_unborn)
180 
181 	in
182 	{
183 	}
184 
185 	do
186 	{
187 		libgit2_d.checkout.git_checkout_options ff_checkout_options = libgit2_d.checkout.GIT_CHECKOUT_OPTIONS_INIT();
188 		libgit2_d.types.git_reference* target_ref;
189 		int err = 0;
190 
191 		if (is_unborn) {
192 			libgit2_d.types.git_reference* head_ref;
193 
194 			/* HEAD reference is unborn, lookup manually so we don't try to resolve it */
195 			err = libgit2_d.refs.git_reference_lookup(&head_ref, repo, "HEAD");
196 
197 			if (err != 0) {
198 				core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "failed to lookup HEAD ref\n");
199 
200 				return -1;
201 			}
202 
203 			/* Grab the reference HEAD should be pointing to */
204 			const (char)* symbolic_ref = libgit2_d.refs.git_reference_symbolic_target(head_ref);
205 
206 			/* Create our master reference on the target OID */
207 			err = libgit2_d.refs.git_reference_create(&target_ref, repo, symbolic_ref, target_oid, 0, null);
208 
209 			if (err != 0) {
210 				core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "failed to create master reference\n");
211 
212 				return -1;
213 			}
214 
215 			libgit2_d.refs.git_reference_free(head_ref);
216 		} else {
217 			/* HEAD exists, just lookup and resolve */
218 			err = libgit2_d.repository.git_repository_head(&target_ref, repo);
219 
220 			if (err != 0) {
221 				core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "failed to get HEAD reference\n");
222 
223 				return -1;
224 			}
225 		}
226 
227 		/* Lookup the target object */
228 		libgit2_d.types.git_object* target = null;
229 		err = libgit2_d.object.git_object_lookup(&target, repo, target_oid, libgit2_d.types.git_object_t.GIT_OBJECT_COMMIT);
230 
231 		if (err != 0) {
232 			core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "failed to lookup OID %s\n", libgit2_d.oid.git_oid_tostr_s(target_oid));
233 
234 			return -1;
235 		}
236 
237 		/* Checkout the result so the workdir is in the expected state */
238 		ff_checkout_options.checkout_strategy = libgit2_d.checkout.git_checkout_strategy_t.GIT_CHECKOUT_SAFE;
239 		err = libgit2_d.checkout.git_checkout_tree(repo, target, &ff_checkout_options);
240 
241 		if (err != 0) {
242 			core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "failed to checkout HEAD reference\n");
243 
244 			return -1;
245 		}
246 
247 		/* Move the target reference to the target OID */
248 		libgit2_d.types.git_reference* new_target_ref;
249 		err = libgit2_d.refs.git_reference_set_target(&new_target_ref, target_ref, target_oid, null);
250 
251 		if (err != 0) {
252 			core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "failed to move HEAD reference\n");
253 
254 			return -1;
255 		}
256 
257 		libgit2_d.refs.git_reference_free(target_ref);
258 		libgit2_d.refs.git_reference_free(new_target_ref);
259 		libgit2_d.object.git_object_free(target);
260 
261 		return 0;
262 	}
263 
264 nothrow @nogc
265 private void output_conflicts(libgit2_d.types.git_index* index)
266 
267 	in
268 	{
269 	}
270 
271 	do
272 	{
273 		libgit2_d.types.git_index_conflict_iterator* conflicts;
274 		libgit2_d.example.common.check_lg2(libgit2_d.index.git_index_conflict_iterator_new(&conflicts, index), "failed to create conflict iterator", null);
275 
276 		const (libgit2_d.index.git_index_entry)* ancestor;
277 		const (libgit2_d.index.git_index_entry)* our;
278 		const (libgit2_d.index.git_index_entry)* their;
279 		int err = 0;
280 
281 		while ((err = libgit2_d.index.git_index_conflict_next(&ancestor, &our, &their, conflicts)) == 0) {
282 			core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "conflict: a:%s o:%s t:%s\n", (ancestor) ? (ancestor.path) : ("null"), (our.path) ? (our.path) : ("null"), (their.path) ? (their.path) : ("null"));
283 		}
284 
285 		if (err != libgit2_d.errors.git_error_code.GIT_ITEROVER) {
286 			core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "error iterating conflicts\n");
287 		}
288 
289 		libgit2_d.index.git_index_conflict_iterator_free(conflicts);
290 	}
291 
292 nothrow @nogc
293 private int create_merge_commit(libgit2_d.types.git_repository* repo, libgit2_d.types.git_index* index, .merge_options* opts)
294 
295 	in
296 	{
297 	}
298 
299 	do
300 	{
301 		libgit2_d.types.git_commit** parents = cast(libgit2_d.types.git_commit**)(core.stdc.stdlib.calloc(opts.annotated_count + 1, (libgit2_d.types.git_commit*).sizeof));
302 
303 		scope (exit) {
304 			if (parents != null) {
305 				core.stdc.stdlib.free(parents);
306 				parents = null;
307 			}
308 		}
309 
310 		/* Grab our needed references */
311 		libgit2_d.types.git_reference* head_ref;
312 		libgit2_d.example.common.check_lg2(libgit2_d.repository.git_repository_head(&head_ref, repo), "failed to get repo HEAD", null);
313 
314 		libgit2_d.types.git_annotated_commit* merge_commit;
315 
316 		if (libgit2_d.example.common.resolve_refish(&merge_commit, repo, opts.heads[0])) {
317 			core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "failed to resolve refish %s", opts.heads[0]);
318 
319 			return -1;
320 		}
321 
322 		/* Maybe that's a ref, so DWIM it */
323 		libgit2_d.types.git_reference* merge_ref = null;
324 		int err = libgit2_d.refs.git_reference_dwim(&merge_ref, repo, opts.heads[0]);
325 		libgit2_d.example.common.check_lg2(err, "failed to DWIM reference", libgit2_d.errors.git_error_last().message);
326 
327 		/* Grab a signature */
328 		libgit2_d.types.git_signature* sign;
329 		libgit2_d.example.common.check_lg2(libgit2_d.signature.git_signature_now(&sign, "Me", "me@example.com"), "failed to create signature", null);
330 
331 		enum MERGE_COMMIT_MSG = "Merge %s '%s'";
332 
333 		const (char)* msg_target = null;
334 
335 		/* Prepare a standard merge commit message */
336 		if (merge_ref != null) {
337 			libgit2_d.example.common.check_lg2(libgit2_d.branch.git_branch_name(&msg_target, merge_ref), "failed to get branch name of merged ref", null);
338 		} else {
339 			msg_target = libgit2_d.oid.git_oid_tostr_s(libgit2_d.annotated_commit.git_annotated_commit_id(merge_commit));
340 		}
341 
342 		size_t msglen = libgit2_d.example.common.snprintf(null, 0, MERGE_COMMIT_MSG, ((merge_ref) ? (&("branch\0"[0])) : (&("commit\0"[0]))), msg_target);
343 
344 		if (msglen > 0) {
345 			msglen++;
346 		}
347 
348 		char* msg = cast(char*)(core.stdc.stdlib.malloc(msglen));
349 		err = libgit2_d.example.common.snprintf(msg, msglen, MERGE_COMMIT_MSG, ((merge_ref) ? (&("branch\0"[0])) : (&("commit\0"[0]))), msg_target);
350 
351 		/* This is only to silence the compiler */
352 		if (err < 0) {
353 			return err;
354 		}
355 
356 		/* Setup our parent commits */
357 		err = libgit2_d.refs.git_reference_peel(cast(libgit2_d.types.git_object**)(&parents[0]), head_ref, libgit2_d.types.git_object_t.GIT_OBJECT_COMMIT);
358 		libgit2_d.example.common.check_lg2(err, "failed to peel head reference", null);
359 
360 		for (size_t i = 0; i < opts.annotated_count; i++) {
361 			libgit2_d.commit.git_commit_lookup(&parents[i + 1], repo, libgit2_d.annotated_commit.git_annotated_commit_id(opts.annotated[i]));
362 		}
363 
364 		/* Prepare our commit tree */
365 		libgit2_d.oid.git_oid tree_oid;
366 		libgit2_d.example.common.check_lg2(libgit2_d.index.git_index_write_tree(&tree_oid, index), "failed to write merged tree", null);
367 		libgit2_d.types.git_tree* tree;
368 		libgit2_d.example.common.check_lg2(libgit2_d.tree.git_tree_lookup(&tree, repo, &tree_oid), "failed to lookup tree", null);
369 
370 		/* Commit time ! */
371 		libgit2_d.oid.git_oid commit_oid;
372 		err = libgit2_d.commit.git_commit_create(&commit_oid, repo, libgit2_d.refs.git_reference_name(head_ref), sign, sign, null, msg, tree, opts.annotated_count + 1, cast(const (libgit2_d.types.git_commit)**)(parents));
373 		libgit2_d.example.common.check_lg2(err, "failed to create commit", null);
374 
375 		/* We're done merging, cleanup the repository state */
376 		libgit2_d.repository.git_repository_state_cleanup(repo);
377 
378 		return err;
379 	}
380 
381 extern (C)
382 nothrow @nogc
383 public int lg2_merge(libgit2_d.types.git_repository* repo, int argc, char** argv)
384 
385 	in
386 	{
387 	}
388 
389 	do
390 	{
391 		.merge_options opts;
392 		.merge_options_init(&opts);
393 		const (char)* path = ".";
394 		.parse_options(&path, &opts, argc, argv);
395 
396 		scope (exit) {
397 			if (opts.heads != null) {
398 				core.stdc.stdlib.free(cast(char**)(opts.heads));
399 				opts.heads = null;
400 			}
401 
402 			if (opts.annotated != null) {
403 				core.stdc.stdlib.free(opts.annotated);
404 				opts.annotated = null;
405 			}
406 		}
407 
408 		//libgit2_d.repository.git_repository_state_t state
409 		int state = libgit2_d.repository.git_repository_state(repo);
410 
411 		if (state != libgit2_d.repository.git_repository_state_t.GIT_REPOSITORY_STATE_NONE) {
412 			core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "repository is in unexpected state %d\n", state);
413 
414 			return 0;
415 		}
416 
417 		int err = .resolve_heads(repo, &opts);
418 
419 		if (err != 0) {
420 			return 0;
421 		}
422 
423 		libgit2_d.merge.git_merge_analysis_t analysis;
424 		libgit2_d.merge.git_merge_preference_t preference;
425 		err = libgit2_d.merge.git_merge_analysis(&analysis, &preference, repo, cast(const (libgit2_d.types.git_annotated_commit)**)(opts.annotated), opts.annotated_count);
426 		libgit2_d.example.common.check_lg2(err, "merge analysis failed", null);
427 
428 		if (analysis & libgit2_d.merge.git_merge_analysis_t.GIT_MERGE_ANALYSIS_UP_TO_DATE) {
429 			core.stdc.stdio.printf("Already up-to-date\n");
430 
431 			return 0;
432 		} else if ((analysis & libgit2_d.merge.git_merge_analysis_t.GIT_MERGE_ANALYSIS_UNBORN) || ((analysis & libgit2_d.merge.git_merge_analysis_t.GIT_MERGE_ANALYSIS_FASTFORWARD) && (!(preference & libgit2_d.merge.git_merge_preference_t.GIT_MERGE_PREFERENCE_NO_FASTFORWARD)))) {
433 			const (libgit2_d.oid.git_oid)* target_oid;
434 
435 			if (analysis & libgit2_d.merge.git_merge_analysis_t.GIT_MERGE_ANALYSIS_UNBORN) {
436 				core.stdc.stdio.printf("Unborn\n");
437 			} else {
438 				core.stdc.stdio.printf("Fast-forward\n");
439 			}
440 
441 			/* Since this is a fast-forward, there can be only one merge head */
442 			target_oid = libgit2_d.annotated_commit.git_annotated_commit_id(opts.annotated[0]);
443 			assert(opts.annotated_count == 1);
444 
445 			return .perform_fastforward(repo, target_oid, (analysis & libgit2_d.merge.git_merge_analysis_t.GIT_MERGE_ANALYSIS_UNBORN));
446 		} else if (analysis & libgit2_d.merge.git_merge_analysis_t.GIT_MERGE_ANALYSIS_NORMAL) {
447 			libgit2_d.merge.git_merge_options merge_opts = libgit2_d.merge.GIT_MERGE_OPTIONS_INIT();
448 			libgit2_d.checkout.git_checkout_options checkout_opts = libgit2_d.checkout.GIT_CHECKOUT_OPTIONS_INIT();
449 
450 			merge_opts.flags = 0;
451 			merge_opts.file_flags = libgit2_d.merge.git_merge_file_flag_t.GIT_MERGE_FILE_STYLE_DIFF3;
452 
453 			checkout_opts.checkout_strategy = libgit2_d.checkout.git_checkout_strategy_t.GIT_CHECKOUT_FORCE | libgit2_d.checkout.git_checkout_strategy_t.GIT_CHECKOUT_ALLOW_CONFLICTS;
454 
455 			if (preference & libgit2_d.merge.git_merge_preference_t.GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY) {
456 				core.stdc.stdio.printf("Fast-forward is preferred, but only a merge is possible\n");
457 
458 				return -1;
459 			}
460 
461 			err = libgit2_d.merge.git_merge(repo, cast(const (libgit2_d.types.git_annotated_commit)**)(opts.annotated), opts.annotated_count, &merge_opts, &checkout_opts);
462 			libgit2_d.example.common.check_lg2(err, "merge failed", null);
463 		}
464 
465 		/* If we get here, we actually performed the merge above */
466 
467 		libgit2_d.types.git_index* index;
468 		libgit2_d.example.common.check_lg2(libgit2_d.repository.git_repository_index(&index, repo), "failed to get repository index", null);
469 
470 		if (libgit2_d.index.git_index_has_conflicts(index)) {
471 			/* Handle conflicts */
472 			.output_conflicts(index);
473 		} else if (!opts.no_commit) {
474 			.create_merge_commit(repo, index, &opts);
475 			core.stdc.stdio.printf("Merge made\n");
476 		}
477 
478 		return 0;
479 	}