From a9b5270b9a015dfbba572c9a3840432da90c7476 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 7 Oct 2019 21:18:19 +0200 Subject: examples: checkout: implement guess heuristic for remote branches --- examples/checkout.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 78 insertions(+), 5 deletions(-) diff --git a/examples/checkout.c b/examples/checkout.c index d552eece8..2aa1fbf4a 100644 --- a/examples/checkout.c +++ b/examples/checkout.c @@ -112,9 +112,10 @@ static void print_perf_data(const git_checkout_perfdata *perfdata, void *payload * This is the main "checkout " function, responsible for performing * a branch-based checkout. */ -static int perform_checkout_ref(git_repository *repo, git_annotated_commit *target, checkout_options *opts) +static int perform_checkout_ref(git_repository *repo, git_annotated_commit *target, const char *target_ref, checkout_options *opts) { git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + git_reference *ref = NULL, *branch = NULL; git_commit *target_commit = NULL; int err; @@ -156,10 +157,25 @@ static int perform_checkout_ref(git_repository *repo, git_annotated_commit *targ * we might need to detach HEAD. */ if (git_annotated_commit_ref(target)) { - err = git_repository_set_head(repo, git_annotated_commit_ref(target)); + const char *target_head; + + if ((err = git_reference_lookup(&ref, repo, git_annotated_commit_ref(target))) < 0) + goto error; + + if (git_reference_is_remote(ref)) { + if ((err = git_branch_create_from_annotated(&branch, repo, target_ref, target, 0)) < 0) + goto error; + target_head = git_reference_name(branch); + } else { + target_head = git_annotated_commit_ref(target); + } + + err = git_repository_set_head(repo, target_head); } else { err = git_repository_set_head_detached_from_annotated(repo, target); } + +error: if (err != 0) { fprintf(stderr, "failed to update HEAD reference: %s\n", git_error_last()->message); goto cleanup; @@ -167,10 +183,67 @@ static int perform_checkout_ref(git_repository *repo, git_annotated_commit *targ cleanup: git_commit_free(target_commit); + git_reference_free(branch); + git_reference_free(ref); return err; } +/** + * This corresponds to `git switch --guess`: if a given ref does + * not exist, git will by default try to guess the reference by + * seeing whether any remote has a branch called . If there + * is a single remote only that has it, then it is assumed to be + * the desired reference and a local branch is created for it. + * + * The following is a simplified implementation. It will not try + * to check whether the ref is unique across all remotes. + */ +static int guess_refish(git_annotated_commit **out, git_repository *repo, const char *ref) +{ + git_strarray remotes = { NULL, 0 }; + git_reference *remote_ref = NULL; + int error; + size_t i; + + if ((error = git_remote_list(&remotes, repo)) < 0) + goto out; + + for (i = 0; i < remotes.count; i++) { + char *refname = NULL; + size_t reflen; + + reflen = snprintf(refname, 0, "refs/remotes/%s/%s", remotes.strings[i], ref); + if ((refname = malloc(reflen + 1)) == NULL) { + error = -1; + goto next; + } + snprintf(refname, reflen + 1, "refs/remotes/%s/%s", remotes.strings[i], ref); + + if ((error = git_reference_lookup(&remote_ref, repo, refname)) < 0) + goto next; + + break; +next: + free(refname); + if (error < 0 && error != GIT_ENOTFOUND) + break; + } + + if (!remote_ref) { + error = GIT_ENOTFOUND; + goto out; + } + + if ((error = git_annotated_commit_from_ref(out, repo, remote_ref)) < 0) + goto out; + +out: + git_reference_free(remote_ref); + git_strarray_free(&remotes); + return error; +} + /** That example's entry point */ int lg2_checkout(git_repository *repo, int argc, char **argv) { @@ -207,12 +280,12 @@ int lg2_checkout(git_repository *repo, int argc, char **argv) /** * Try to resolve a "refish" argument to a target libgit2 can use */ - err = resolve_refish(&checkout_target, repo, args.argv[args.pos]); - if (err != 0) { + if ((err = resolve_refish(&checkout_target, repo, args.argv[args.pos])) < 0 && + (err = guess_refish(&checkout_target, repo, args.argv[args.pos])) < 0) { fprintf(stderr, "failed to resolve %s: %s\n", args.argv[args.pos], git_error_last()->message); goto cleanup; } - err = perform_checkout_ref(repo, checkout_target, &opts); + err = perform_checkout_ref(repo, checkout_target, args.argv[args.pos], &opts); } cleanup: -- cgit v1.2.1