1 /* 2 * libgit2 "checkout" example - shows how to perform checkouts 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 * <https://creativecommons.org/publicdomain/zero/1.0/>. 13 */ 14 /** 15 * The following example demonstrates how to do checkouts with libgit2. 16 * 17 * Recognized options are : 18 * --force: force the checkout to happen. 19 * --[no-]progress: show checkout progress, on by default. 20 * --perf: show performance data. 21 * 22 * License: $(LINK2 https://creativecommons.org/publicdomain/zero/1.0/, CC0 1.0 Universal) 23 */ 24 module libgit2.example.checkout; 25 26 27 private static import core.stdc.stdio; 28 private static import core.stdc.stdlib; 29 private static import core.stdc.string; 30 private static import libgit2.annotated_commit; 31 private static import libgit2.branch; 32 private static import libgit2.checkout; 33 private static import libgit2.commit; 34 private static import libgit2.errors; 35 private static import libgit2.example.args; 36 private static import libgit2.example.common; 37 private static import libgit2.refs; 38 private static import libgit2.remote; 39 private static import libgit2.repository; 40 private static import libgit2.strarray; 41 private static import libgit2.types; 42 private static import std.bitmanip; 43 44 /* Define the printf format specifier to use for size_t output */ 45 //#if defined(_MSC_VER) || defined(__MINGW32__) 46 version (none) { 47 enum PRIuZ = "Iu"; 48 enum PRIxZ = "Ix"; 49 enum PRIdZ = "Id"; 50 } else { 51 enum PRIuZ = "zu"; 52 enum PRIxZ = "zx"; 53 enum PRIdZ = "zd"; 54 } 55 56 /** 57 * The following example demonstrates how to do checkouts with libgit2. 58 * 59 * Recognized options are : 60 * --force: force the checkout to happen. 61 * --[no-]progress: show checkout progress, on by default. 62 * --perf: show performance data. 63 */ 64 65 extern (C) 66 public struct checkout_options 67 { 68 mixin 69 ( 70 std.bitmanip.bitfields! 71 ( 72 int, "force", 1, 73 int, "progress", 1, 74 int, "perf", 1, 75 int, "not_used", 5 76 ) 77 ); 78 } 79 80 nothrow @nogc 81 private void print_usage() 82 83 in 84 { 85 } 86 87 do 88 { 89 core.stdc.stdio.fprintf(core.stdc.stdio.stderr, 90 "usage: checkout [options] <branch>\n" 91 ~ "Options are :\n" 92 ~ " --git-dir: use the following git repository.\n" 93 ~ " --force: force the checkout.\n" 94 ~ " --[no-]progress: show checkout progress.\n" 95 ~ " --perf: show performance data.\n"); 96 97 core.stdc.stdlib.exit(1); 98 } 99 100 nothrow @nogc 101 private void parse_options(const (char)** repo_path, .checkout_options* opts, libgit2.example.args.args_info* args) 102 103 in 104 { 105 } 106 107 do 108 { 109 if (args.argc <= 1) { 110 .print_usage(); 111 } 112 113 core.stdc..string.memset(opts, 0, (*opts).sizeof); 114 115 /* Default values */ 116 opts.progress = 1; 117 118 int bool_arg; 119 120 for (args.pos = 1; args.pos < args.argc; ++args.pos) { 121 const (char)* curr = args.argv[args.pos]; 122 123 if (libgit2.example.args.match_arg_separator(args)) { 124 break; 125 } else if (!core.stdc..string.strcmp(curr, "--force")) { 126 opts.force = 1; 127 } else if (libgit2.example.args.match_bool_arg(&bool_arg, args, "--progress")) { 128 opts.progress = bool_arg; 129 } else if (libgit2.example.args.match_bool_arg(&bool_arg, args, "--perf")) { 130 opts.perf = bool_arg; 131 } else if (libgit2.example.args.match_str_arg(repo_path, args, "--git-dir")) { 132 continue; 133 } else { 134 break; 135 } 136 } 137 } 138 139 /** 140 * This function is called to report progression, ie. it's called once with 141 * a null path and the number of total steps, then for each subsequent path, 142 * the current completed_step value. 143 */ 144 extern (C) 145 nothrow @nogc 146 private void print_checkout_progress(const (char)* path, size_t completed_steps, size_t total_steps, void* payload) 147 148 in 149 { 150 } 151 152 do 153 { 154 //cast(void)(payload); 155 156 if (path == null) { 157 core.stdc.stdio.printf("checkout started: %" ~ .PRIuZ ~ " steps\n", total_steps); 158 } else { 159 core.stdc.stdio.printf("checkout: %s %" ~ .PRIuZ ~ "/%" ~ .PRIuZ ~ "\n", path, completed_steps, total_steps); 160 } 161 } 162 163 /** 164 * This function is called when the checkout completes, and is used to report the 165 * number of syscalls performed. 166 */ 167 extern (C) 168 nothrow @nogc 169 private void print_perf_data(const (libgit2.checkout.git_checkout_perfdata)* perfdata, void* payload) 170 171 in 172 { 173 } 174 175 do 176 { 177 //cast(void)(payload); 178 core.stdc.stdio.printf("perf: stat: %" ~ .PRIuZ ~ " mkdir: %" ~ .PRIuZ ~ " chmod: %" ~ .PRIuZ ~ "\n", perfdata.stat_calls, perfdata.mkdir_calls, perfdata.chmod_calls); 179 } 180 181 /** 182 * This is the main "checkout <branch>" function, responsible for performing 183 * a branch-based checkout. 184 */ 185 nothrow @nogc 186 private int perform_checkout_ref(libgit2.types.git_repository* repo, const (char)* target_ref, libgit2.types.git_annotated_commit* target, .checkout_options* opts) 187 188 in 189 { 190 } 191 192 do 193 { 194 /** Setup our checkout options from the parsed options */ 195 libgit2.checkout.git_checkout_options checkout_opts = libgit2.checkout.GIT_CHECKOUT_OPTIONS_INIT(); 196 checkout_opts.checkout_strategy = libgit2.checkout.git_checkout_strategy_t.GIT_CHECKOUT_SAFE; 197 198 if (opts.force) { 199 checkout_opts.checkout_strategy = libgit2.checkout.git_checkout_strategy_t.GIT_CHECKOUT_FORCE; 200 } 201 202 if (opts.progress) { 203 checkout_opts.progress_cb = &.print_checkout_progress; 204 } 205 206 if (opts.perf) { 207 checkout_opts.perfdata_cb = &.print_perf_data; 208 } 209 210 /** Grab the commit we're interested to move to */ 211 libgit2.types.git_reference* ref_ = null; 212 libgit2.types.git_commit* target_commit = null; 213 214 scope (exit) { 215 libgit2.commit.git_commit_free(target_commit); 216 libgit2.commit.git_commit_free(cast(libgit2.types.git_commit*)(ref_)); 217 } 218 219 int err = libgit2.commit.git_commit_lookup(&target_commit, repo, libgit2.annotated_commit.git_annotated_commit_id(target)); 220 221 if (err != 0) { 222 core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "failed to lookup commit: %s\n", libgit2.errors.git_error_last().message); 223 224 return err; 225 } 226 227 /** 228 * Perform the checkout so the workdir corresponds to what target_commit 229 * contains. 230 * 231 * Note that it's okay to pass a git_commit here, because it will be 232 * peeled to a tree. 233 */ 234 err = libgit2.checkout.git_checkout_tree(repo, cast(const (libgit2.types.git_object)*)(target_commit), &checkout_opts); 235 236 if (err != 0) { 237 core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "failed to checkout tree: %s\n", libgit2.errors.git_error_last().message); 238 239 return err; 240 } 241 242 /** 243 * Now that the checkout has completed, we have to update HEAD. 244 * 245 * Depending on the "origin" of target (ie. it's an OID or a branch name), 246 * we might need to detach HEAD. 247 */ 248 libgit2.types.git_reference* branch = null; 249 250 scope (exit) { 251 libgit2.refs.git_reference_free(branch); 252 } 253 254 if (libgit2.annotated_commit.git_annotated_commit_ref(target)) { 255 err = libgit2.refs.git_reference_lookup(&ref_, repo, libgit2.annotated_commit.git_annotated_commit_ref(target)); 256 257 if (err < 0) { 258 goto error; 259 } 260 261 const (char)* target_head; 262 263 if (libgit2.refs.git_reference_is_remote(ref_)) { 264 err = libgit2.branch.git_branch_create_from_annotated(&branch, repo, target_ref, target, 0); 265 266 if (err < 0) { 267 goto error; 268 } 269 270 target_head = libgit2.refs.git_reference_name(branch); 271 } else { 272 target_head = libgit2.annotated_commit.git_annotated_commit_ref(target); 273 } 274 275 err = libgit2.repository.git_repository_set_head(repo, target_head); 276 } else { 277 err = libgit2.repository.git_repository_set_head_detached_from_annotated(repo, target); 278 } 279 280 error: 281 if (err != 0) { 282 core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "failed to update HEAD reference: %s\n", libgit2.errors.git_error_last().message); 283 284 return err; 285 } 286 287 return err; 288 } 289 290 /** 291 * This corresponds to `git switch --guess`: if a given ref does 292 * not exist, git will by default try to guess the reference by 293 * seeing whether any remote has a branch called <ref>. If there 294 * is a single remote only that has it, then it is assumed to be 295 * the desired reference and a local branch is created for it. 296 * 297 * The following is a simplified implementation. It will not try 298 * to check whether the ref is unique across all remotes. 299 */ 300 nothrow @nogc 301 private int guess_refish(libgit2.types.git_annotated_commit** out_, libgit2.types.git_repository* repo, const (char)* ref_) 302 303 do 304 { 305 libgit2.strarray.git_strarray remotes = 306 { 307 null, 308 0, 309 }; 310 libgit2.types.git_reference* remote_ref = null; 311 312 scope (exit) { 313 libgit2.refs.git_reference_free(remote_ref); 314 libgit2.strarray.git_strarray_dispose(&remotes); 315 } 316 317 int error = libgit2.remote.git_remote_list(&remotes, repo); 318 319 if (error < 0) { 320 return error; 321 } 322 323 for (size_t i = 0; i < remotes.count; i++) { 324 char* refname = null; 325 size_t reflen = libgit2.example.common.snprintf(refname, 0, "refs/remotes/%s/%s", remotes.strings[i], ref_); 326 refname = cast(char*)(core.stdc.stdlib.malloc(reflen + 1)); 327 328 if (refname == null) { 329 error = -1; 330 331 goto next; 332 } 333 334 libgit2.example.common.snprintf(refname, reflen + 1, "refs/remotes/%s/%s", remotes.strings[i], ref_); 335 error = libgit2.refs.git_reference_lookup(&remote_ref, repo, refname); 336 337 if (error < 0) { 338 goto next; 339 } 340 341 break; 342 343 next: 344 if (refname != null) { 345 core.stdc.stdlib.free(refname); 346 } 347 348 if ((error < 0) && (error != libgit2.errors.git_error_code.GIT_ENOTFOUND)) { 349 break; 350 } 351 } 352 353 if (!remote_ref) { 354 error = libgit2.errors.git_error_code.GIT_ENOTFOUND; 355 return error; 356 } 357 358 if ((error = libgit2.annotated_commit.git_annotated_commit_from_ref(out_, repo, remote_ref)) < 0) { 359 return error; 360 } 361 362 return error; 363 } 364 365 /** 366 * That example's entry point 367 */ 368 extern (C) 369 nothrow @nogc 370 public int lg2_checkout(libgit2.types.git_repository* repo, int argc, char** argv) 371 372 in 373 { 374 } 375 376 do 377 { 378 libgit2.types.git_annotated_commit* checkout_target = null; 379 int err = 0; 380 const (char)* path = "."; 381 382 scope (exit) { 383 libgit2.annotated_commit.git_annotated_commit_free(checkout_target); 384 } 385 386 /** Parse our command line options */ 387 libgit2.example.args.args_info args = libgit2.example.args.ARGS_INFO_INIT(argc, argv); 388 .checkout_options opts; 389 .parse_options(&path, &opts, &args); 390 391 /** Make sure we're not about to checkout while something else is going on */ 392 //libgit2.repository.git_repository_state_t state 393 int state = libgit2.repository.git_repository_state(repo); 394 395 if (state != libgit2.repository.git_repository_state_t.GIT_REPOSITORY_STATE_NONE) { 396 core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "repository is in unexpected state %d\n", state); 397 398 return err; 399 } 400 401 if (libgit2.example.args.match_arg_separator(&args)) { 402 /** 403 * Try to checkout the given path 404 */ 405 406 core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "unhandled path-based checkout\n"); 407 err = 1; 408 409 return err; 410 } else { 411 /** 412 * Try to resolve a "refish" argument to a target libgit2 can use 413 */ 414 if (((err = libgit2.example.common.resolve_refish(&checkout_target, repo, args.argv[args.pos])) < 0) && ((err = .guess_refish(&checkout_target, repo, args.argv[args.pos])) < 0)) { 415 core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "failed to resolve %s: %s\n", args.argv[args.pos], libgit2.errors.git_error_last().message); 416 417 return err; 418 } 419 420 err = .perform_checkout_ref(repo, cast(const (char)*)(checkout_target), cast(libgit2.types.git_annotated_commit*)(args.argv[args.pos]), &opts); 421 } 422 423 return err; 424 }