Skip to content

Commit d2d01f7

Browse files
authored
Merge pull request libgit2#5283 from pks-t/pks/example-checkout-remote-branch
examples: checkout: implement guess heuristic for remote branches
2 parents 3e6a904 + a9b5270 commit d2d01f7

File tree

1 file changed

+78
-5
lines changed

1 file changed

+78
-5
lines changed

examples/checkout.c

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,10 @@ static void print_perf_data(const git_checkout_perfdata *perfdata, void *payload
111111
* This is the main "checkout <branch>" function, responsible for performing
112112
* a branch-based checkout.
113113
*/
114-
static int perform_checkout_ref(git_repository *repo, git_annotated_commit *target, checkout_options *opts)
114+
static int perform_checkout_ref(git_repository *repo, git_annotated_commit *target, const char *target_ref, checkout_options *opts)
115115
{
116116
git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
117+
git_reference *ref = NULL, *branch = NULL;
117118
git_commit *target_commit = NULL;
118119
int err;
119120

@@ -155,21 +156,93 @@ static int perform_checkout_ref(git_repository *repo, git_annotated_commit *targ
155156
* we might need to detach HEAD.
156157
*/
157158
if (git_annotated_commit_ref(target)) {
158-
err = git_repository_set_head(repo, git_annotated_commit_ref(target));
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);
159173
} else {
160174
err = git_repository_set_head_detached_from_annotated(repo, target);
161175
}
176+
177+
error:
162178
if (err != 0) {
163179
fprintf(stderr, "failed to update HEAD reference: %s\n", git_error_last()->message);
164180
goto cleanup;
165181
}
166182

167183
cleanup:
168184
git_commit_free(target_commit);
185+
git_reference_free(branch);
186+
git_reference_free(ref);
169187

170188
return err;
171189
}
172190

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_free(&remotes);
243+
return error;
244+
}
245+
173246
/** That example's entry point */
174247
int lg2_checkout(git_repository *repo, int argc, char **argv)
175248
{
@@ -202,12 +275,12 @@ int lg2_checkout(git_repository *repo, int argc, char **argv)
202275
/**
203276
* Try to resolve a "refish" argument to a target libgit2 can use
204277
*/
205-
err = resolve_refish(&checkout_target, repo, args.argv[args.pos]);
206-
if (err != 0) {
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) {
207280
fprintf(stderr, "failed to resolve %s: %s\n", args.argv[args.pos], git_error_last()->message);
208281
goto cleanup;
209282
}
210-
err = perform_checkout_ref(repo, checkout_target, &opts);
283+
err = perform_checkout_ref(repo, checkout_target, args.argv[args.pos], &opts);
211284
}
212285

213286
cleanup:

0 commit comments

Comments
 (0)