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 }