2 * libgit2 "checkout" example - shows how to perform checkouts
4 * Written by the libgit2 contributors
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.
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/>.
17 /* Define the printf format specifer to use for size_t output */
18 #if defined(_MSC_VER) || defined(__MINGW32__)
29 * The following example demonstrates how to do checkouts with libgit2.
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.
43 static void print_usage(void)
45 fprintf(stderr
, "usage: checkout [options] <branch>\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");
54 static void parse_options(const char **repo_path
, checkout_options
*opts
, struct args_info
*args
)
59 memset(opts
, 0, sizeof(*opts
));
64 for (args
->pos
= 1; args
->pos
< args
->argc
; ++args
->pos
) {
65 const char *curr
= args
->argv
[args
->pos
];
68 if (match_arg_separator(args
)) {
70 } else if (!strcmp(curr
, "--force")) {
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")) {
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.
89 static void print_checkout_progress(const char *path
, size_t completed_steps
, size_t total_steps
, void *payload
)
93 printf("checkout started: %" PRIuZ
" steps\n", total_steps
);
95 printf("checkout: %s %" PRIuZ
"/%" PRIuZ
"\n", path
, completed_steps
, total_steps
);
100 * This function is called when the checkout completes, and is used to report the
101 * number of syscalls performed.
103 static void print_perf_data(const git_checkout_perfdata
*perfdata
, void *payload
)
106 printf("perf: stat: %" PRIuZ
" mkdir: %" PRIuZ
" chmod: %" PRIuZ
"\n",
107 perfdata
->stat_calls
, perfdata
->mkdir_calls
, perfdata
->chmod_calls
);
111 * This is the main "checkout <branch>" function, responsible for performing
112 * a branch-based checkout.
114 static int perform_checkout_ref(git_repository
*repo
, git_annotated_commit
*target
, const char *target_ref
, checkout_options
*opts
)
116 git_checkout_options checkout_opts
= GIT_CHECKOUT_OPTIONS_INIT
;
117 git_reference
*ref
= NULL
, *branch
= NULL
;
118 git_commit
*target_commit
= NULL
;
121 /** Setup our checkout options from the parsed options */
122 checkout_opts
.checkout_strategy
= GIT_CHECKOUT_SAFE
;
124 checkout_opts
.checkout_strategy
= GIT_CHECKOUT_FORCE
;
127 checkout_opts
.progress_cb
= print_checkout_progress
;
130 checkout_opts
.perfdata_cb
= print_perf_data
;
132 /** Grab the commit we're interested to move to */
133 err
= git_commit_lookup(&target_commit
, repo
, git_annotated_commit_id(target
));
135 fprintf(stderr
, "failed to lookup commit: %s\n", git_error_last()->message
);
140 * Perform the checkout so the workdir corresponds to what target_commit
143 * Note that it's okay to pass a git_commit here, because it will be
146 err
= git_checkout_tree(repo
, (const git_object
*)target_commit
, &checkout_opts
);
148 fprintf(stderr
, "failed to checkout tree: %s\n", git_error_last()->message
);
153 * Now that the checkout has completed, we have to update HEAD.
155 * Depending on the "origin" of target (ie. it's an OID or a branch name),
156 * we might need to detach HEAD.
158 if (git_annotated_commit_ref(target
)) {
159 const char *target_head
;
161 if ((err
= git_reference_lookup(&ref
, repo
, git_annotated_commit_ref(target
))) < 0)
164 if (git_reference_is_remote(ref
)) {
165 if ((err
= git_branch_create_from_annotated(&branch
, repo
, target_ref
, target
, 0)) < 0)
167 target_head
= git_reference_name(branch
);
169 target_head
= git_annotated_commit_ref(target
);
172 err
= git_repository_set_head(repo
, target_head
);
174 err
= git_repository_set_head_detached_from_annotated(repo
, target
);
179 fprintf(stderr
, "failed to update HEAD reference: %s\n", git_error_last()->message
);
184 git_commit_free(target_commit
);
185 git_reference_free(branch
);
186 git_reference_free(ref
);
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.
198 * The following is a simplified implementation. It will not try
199 * to check whether the ref is unique across all remotes.
201 static int guess_refish(git_annotated_commit
**out
, git_repository
*repo
, const char *ref
)
203 git_strarray remotes
= { NULL
, 0 };
204 git_reference
*remote_ref
= NULL
;
208 if ((error
= git_remote_list(&remotes
, repo
)) < 0)
211 for (i
= 0; i
< remotes
.count
; i
++) {
212 char *refname
= NULL
;
215 reflen
= snprintf(refname
, 0, "refs/remotes/%s/%s", remotes
.strings
[i
], ref
);
216 if ((refname
= malloc(reflen
+ 1)) == NULL
) {
220 snprintf(refname
, reflen
+ 1, "refs/remotes/%s/%s", remotes
.strings
[i
], ref
);
222 if ((error
= git_reference_lookup(&remote_ref
, repo
, refname
)) < 0)
228 if (error
< 0 && error
!= GIT_ENOTFOUND
)
233 error
= GIT_ENOTFOUND
;
237 if ((error
= git_annotated_commit_from_ref(out
, repo
, remote_ref
)) < 0)
241 git_reference_free(remote_ref
);
242 git_strarray_free(&remotes
);
246 /** That example's entry point */
247 int lg2_checkout(git_repository
*repo
, int argc
, char **argv
)
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
;
254 const char *path
= ".";
256 /** Parse our command line options */
257 parse_options(&path
, &opts
, &args
);
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
);
266 if (match_arg_separator(&args
)) {
268 * Try to checkout the given path
271 fprintf(stderr
, "unhandled path-based checkout\n");
276 * Try to resolve a "refish" argument to a target libgit2 can use
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) {
280 fprintf(stderr
, "failed to resolve %s: %s\n", args
.argv
[args
.pos
], git_error_last()->message
);
283 err
= perform_checkout_ref(repo
, checkout_target
, args
.argv
[args
.pos
], &opts
);
287 git_annotated_commit_free(checkout_target
);