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 	}