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