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 	}