]>
Commit | Line | Data |
---|---|---|
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 | ||
37 | typedef struct { | |
38 | int force : 1; | |
39 | int progress : 1; | |
40 | int perf : 1; | |
41 | } checkout_options; | |
42 | ||
43 | static 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 | ||
54 | static 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 | */ | |
89 | static 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 | */ | |
103 | static 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 | 114 | static 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 | |
177 | error: | |
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 | ||
183 | cleanup: | |
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 | */ | |
201 | static 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; | |
226 | next: | |
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 | ||
240 | out: | |
241 | git_reference_free(remote_ref); | |
242 | git_strarray_dispose(&remotes); | |
243 | return error; | |
244 | } | |
245 | ||
ac3d33df | 246 | /** That example's entry point */ |
22a2d3d5 | 247 | int 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 | ||
286 | cleanup: | |
287 | git_annotated_commit_free(checkout_target); | |
288 | ||
ac3d33df JK |
289 | return err; |
290 | } |