]> git.proxmox.com Git - libgit2.git/blame - examples/checkout.c
Drop Russell Sim from uploaders' field
[libgit2.git] / examples / checkout.c
CommitLineData
ac3d33df
JK
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
15#include "common.h"
ac3d33df
JK
16
17/* Define the printf format specifer to use for size_t output */
18#if defined(_MSC_VER) || defined(__MINGW32__)
19# define PRIuZ "Iu"
20# define PRIxZ "Ix"
21# define PRIdZ "Id"
22#else
23# define PRIuZ "zu"
24# define PRIxZ "zx"
25# define PRIdZ "zd"
26#endif
27
28/**
29 * The following example demonstrates how to do checkouts with libgit2.
30 *
31 * Recognized options are :
32 * --force: force the checkout to happen.
33 * --[no-]progress: show checkout progress, on by default.
34 * --perf: show performance data.
35 */
36
37typedef struct {
38 int force : 1;
39 int progress : 1;
40 int perf : 1;
41} checkout_options;
42
43static void print_usage(void)
44{
45 fprintf(stderr, "usage: checkout [options] <branch>\n"
46 "Options are :\n"
47 " --git-dir: use the following git repository.\n"
48 " --force: force the checkout.\n"
49 " --[no-]progress: show checkout progress.\n"
50 " --perf: show performance data.\n");
51 exit(1);
52}
53
54static void parse_options(const char **repo_path, checkout_options *opts, struct args_info *args)
55{
56 if (args->argc <= 1)
57 print_usage();
58
59 memset(opts, 0, sizeof(*opts));
60
61 /* Default values */
62 opts->progress = 1;
63
64 for (args->pos = 1; args->pos < args->argc; ++args->pos) {
65 const char *curr = args->argv[args->pos];
66 int bool_arg;
67
22a2d3d5 68 if (match_arg_separator(args)) {
ac3d33df
JK
69 break;
70 } else if (!strcmp(curr, "--force")) {
71 opts->force = 1;
72 } else if (match_bool_arg(&bool_arg, args, "--progress")) {
73 opts->progress = bool_arg;
74 } else if (match_bool_arg(&bool_arg, args, "--perf")) {
75 opts->perf = bool_arg;
76 } else if (match_str_arg(repo_path, args, "--git-dir")) {
77 continue;
78 } else {
79 break;
80 }
81 }
82}
83
84/**
85 * This function is called to report progression, ie. it's called once with
86 * a NULL path and the number of total steps, then for each subsequent path,
87 * the current completed_step value.
88 */
89static void print_checkout_progress(const char *path, size_t completed_steps, size_t total_steps, void *payload)
90{
91 (void)payload;
92 if (path == NULL) {
93 printf("checkout started: %" PRIuZ " steps\n", total_steps);
94 } else {
95 printf("checkout: %s %" PRIuZ "/%" PRIuZ "\n", path, completed_steps, total_steps);
96 }
97}
98
99/**
100 * This function is called when the checkout completes, and is used to report the
101 * number of syscalls performed.
102 */
103static void print_perf_data(const git_checkout_perfdata *perfdata, void *payload)
104{
105 (void)payload;
106 printf("perf: stat: %" PRIuZ " mkdir: %" PRIuZ " chmod: %" PRIuZ "\n",
107 perfdata->stat_calls, perfdata->mkdir_calls, perfdata->chmod_calls);
108}
109
110/**
111 * This is the main "checkout <branch>" function, responsible for performing
112 * a branch-based checkout.
113 */
22a2d3d5 114static int perform_checkout_ref(git_repository *repo, git_annotated_commit *target, const char *target_ref, checkout_options *opts)
ac3d33df
JK
115{
116 git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
22a2d3d5 117 git_reference *ref = NULL, *branch = NULL;
ac3d33df
JK
118 git_commit *target_commit = NULL;
119 int err;
120
121 /** Setup our checkout options from the parsed options */
122 checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
123 if (opts->force)
124 checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE;
125
126 if (opts->progress)
127 checkout_opts.progress_cb = print_checkout_progress;
128
129 if (opts->perf)
130 checkout_opts.perfdata_cb = print_perf_data;
131
132 /** Grab the commit we're interested to move to */
133 err = git_commit_lookup(&target_commit, repo, git_annotated_commit_id(target));
134 if (err != 0) {
135 fprintf(stderr, "failed to lookup commit: %s\n", git_error_last()->message);
136 goto cleanup;
137 }
138
139 /**
140 * Perform the checkout so the workdir corresponds to what target_commit
141 * contains.
142 *
143 * Note that it's okay to pass a git_commit here, because it will be
144 * peeled to a tree.
145 */
146 err = git_checkout_tree(repo, (const git_object *)target_commit, &checkout_opts);
147 if (err != 0) {
148 fprintf(stderr, "failed to checkout tree: %s\n", git_error_last()->message);
149 goto cleanup;
150 }
151
152 /**
153 * Now that the checkout has completed, we have to update HEAD.
154 *
155 * Depending on the "origin" of target (ie. it's an OID or a branch name),
156 * we might need to detach HEAD.
157 */
158 if (git_annotated_commit_ref(target)) {
22a2d3d5
UG
159 const char *target_head;
160
161 if ((err = git_reference_lookup(&ref, repo, git_annotated_commit_ref(target))) < 0)
162 goto error;
163
164 if (git_reference_is_remote(ref)) {
165 if ((err = git_branch_create_from_annotated(&branch, repo, target_ref, target, 0)) < 0)
166 goto error;
167 target_head = git_reference_name(branch);
168 } else {
169 target_head = git_annotated_commit_ref(target);
170 }
171
172 err = git_repository_set_head(repo, target_head);
ac3d33df
JK
173 } else {
174 err = git_repository_set_head_detached_from_annotated(repo, target);
175 }
22a2d3d5
UG
176
177error:
ac3d33df
JK
178 if (err != 0) {
179 fprintf(stderr, "failed to update HEAD reference: %s\n", git_error_last()->message);
180 goto cleanup;
181 }
182
183cleanup:
184 git_commit_free(target_commit);
22a2d3d5
UG
185 git_reference_free(branch);
186 git_reference_free(ref);
ac3d33df
JK
187
188 return err;
189}
190
22a2d3d5
UG
191/**
192 * This corresponds to `git switch --guess`: if a given ref does
193 * not exist, git will by default try to guess the reference by
194 * seeing whether any remote has a branch called <ref>. If there
195 * is a single remote only that has it, then it is assumed to be
196 * the desired reference and a local branch is created for it.
197 *
198 * The following is a simplified implementation. It will not try
199 * to check whether the ref is unique across all remotes.
200 */
201static int guess_refish(git_annotated_commit **out, git_repository *repo, const char *ref)
202{
203 git_strarray remotes = { NULL, 0 };
204 git_reference *remote_ref = NULL;
205 int error;
206 size_t i;
207
208 if ((error = git_remote_list(&remotes, repo)) < 0)
209 goto out;
210
211 for (i = 0; i < remotes.count; i++) {
212 char *refname = NULL;
213 size_t reflen;
214
215 reflen = snprintf(refname, 0, "refs/remotes/%s/%s", remotes.strings[i], ref);
216 if ((refname = malloc(reflen + 1)) == NULL) {
217 error = -1;
218 goto next;
219 }
220 snprintf(refname, reflen + 1, "refs/remotes/%s/%s", remotes.strings[i], ref);
221
222 if ((error = git_reference_lookup(&remote_ref, repo, refname)) < 0)
223 goto next;
224
225 break;
226next:
227 free(refname);
228 if (error < 0 && error != GIT_ENOTFOUND)
229 break;
230 }
231
232 if (!remote_ref) {
233 error = GIT_ENOTFOUND;
234 goto out;
235 }
236
237 if ((error = git_annotated_commit_from_ref(out, repo, remote_ref)) < 0)
238 goto out;
239
240out:
241 git_reference_free(remote_ref);
242 git_strarray_dispose(&remotes);
243 return error;
244}
245
ac3d33df 246/** That example's entry point */
22a2d3d5 247int lg2_checkout(git_repository *repo, int argc, char **argv)
ac3d33df 248{
ac3d33df
JK
249 struct args_info args = ARGS_INFO_INIT;
250 checkout_options opts;
251 git_repository_state_t state;
252 git_annotated_commit *checkout_target = NULL;
253 int err = 0;
254 const char *path = ".";
255
256 /** Parse our command line options */
257 parse_options(&path, &opts, &args);
258
ac3d33df
JK
259 /** Make sure we're not about to checkout while something else is going on */
260 state = git_repository_state(repo);
261 if (state != GIT_REPOSITORY_STATE_NONE) {
262 fprintf(stderr, "repository is in unexpected state %d\n", state);
263 goto cleanup;
264 }
265
22a2d3d5 266 if (match_arg_separator(&args)) {
ac3d33df
JK
267 /**
268 * Try to checkout the given path
269 */
270
271 fprintf(stderr, "unhandled path-based checkout\n");
272 err = 1;
273 goto cleanup;
274 } else {
275 /**
276 * Try to resolve a "refish" argument to a target libgit2 can use
277 */
22a2d3d5
UG
278 if ((err = resolve_refish(&checkout_target, repo, args.argv[args.pos])) < 0 &&
279 (err = guess_refish(&checkout_target, repo, args.argv[args.pos])) < 0) {
ac3d33df
JK
280 fprintf(stderr, "failed to resolve %s: %s\n", args.argv[args.pos], git_error_last()->message);
281 goto cleanup;
282 }
22a2d3d5 283 err = perform_checkout_ref(repo, checkout_target, args.argv[args.pos], &opts);
ac3d33df
JK
284 }
285
286cleanup:
287 git_annotated_commit_free(checkout_target);
288
ac3d33df
JK
289 return err;
290}