diff options
author | Patrick Steinhardt <ps@pks.im> | 2018-07-20 13:06:56 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-07-20 13:06:56 +0200 |
commit | ea9e2c1a4a16b572a3621d9bd7b1383365701b9e (patch) | |
tree | 8fbfc6894cb319db5606da3c314edc95c2340ae4 | |
parent | 0652abaaeaff768e78a65464e59bd1896ce2f6ce (diff) | |
parent | b24202e15462802f7c5ff4c550949453dcbd0da3 (diff) | |
download | libgit2-ea9e2c1a4a16b572a3621d9bd7b1383365701b9e.tar.gz |
Merge pull request #4692 from tiennou/examples/checkout
Add a checkout example
-rw-r--r-- | examples/checkout.c | 235 | ||||
-rw-r--r-- | examples/common.c | 44 | ||||
-rw-r--r-- | examples/common.h | 14 | ||||
-rw-r--r-- | examples/merge.c | 23 | ||||
-rw-r--r-- | include/git2/annotated_commit.h | 9 | ||||
-rw-r--r-- | src/annotated_commit.c | 7 |
6 files changed, 309 insertions, 23 deletions
diff --git a/examples/checkout.c b/examples/checkout.c new file mode 100644 index 000000000..f92ad7399 --- /dev/null +++ b/examples/checkout.c @@ -0,0 +1,235 @@ +/* + * libgit2 "checkout" example - shows how to perform checkouts + * + * Written by the libgit2 contributors + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication along + * with this software. If not, see + * <http://creativecommons.org/publicdomain/zero/1.0/>. + */ + +#include "common.h" +#include <assert.h> + +/* Define the printf format specifer to use for size_t output */ +#if defined(_MSC_VER) || defined(__MINGW32__) +# define PRIuZ "Iu" +# define PRIxZ "Ix" +# define PRIdZ "Id" +#else +# define PRIuZ "zu" +# define PRIxZ "zx" +# define PRIdZ "zd" +#endif + +/** + * The following example demonstrates how to do checkouts with libgit2. + * + * Recognized options are : + * --force: force the checkout to happen. + * --[no-]progress: show checkout progress, on by default. + * --perf: show performance data. + */ + +typedef struct { + int force : 1; + int progress : 1; + int perf : 1; +} checkout_options; + +static void print_usage(void) +{ + fprintf(stderr, "usage: checkout [options] <branch>\n" + "Options are :\n" + " --git-dir: use the following git repository.\n" + " --force: force the checkout.\n" + " --[no-]progress: show checkout progress.\n" + " --perf: show performance data.\n"); + exit(1); +} + +static void parse_options(const char **repo_path, checkout_options *opts, struct args_info *args) +{ + if (args->argc <= 1) + print_usage(); + + memset(opts, 0, sizeof(*opts)); + + /* Default values */ + opts->progress = 1; + + for (args->pos = 1; args->pos < args->argc; ++args->pos) { + const char *curr = args->argv[args->pos]; + int bool_arg; + + if (strcmp(curr, "--") == 0) { + break; + } else if (!strcmp(curr, "--force")) { + opts->force = 1; + } else if (match_bool_arg(&bool_arg, args, "--progress")) { + opts->progress = bool_arg; + } else if (match_bool_arg(&bool_arg, args, "--perf")) { + opts->perf = bool_arg; + } else if (match_str_arg(repo_path, args, "--git-dir")) { + continue; + } else { + break; + } + } +} + +/** + * This function is called to report progression, ie. it's called once with + * a NULL path and the number of total steps, then for each subsequent path, + * the current completed_step value. + */ +static void print_checkout_progress(const char *path, size_t completed_steps, size_t total_steps, void *payload) +{ + (void)payload; + if (path == NULL) { + printf("checkout started: %" PRIuZ " steps\n", total_steps); + } else { + printf("checkout: %s %" PRIuZ "/%" PRIuZ "\n", path, completed_steps, total_steps); + } +} + +/** + * This function is called when the checkout completes, and is used to report the + * number of syscalls performed. + */ +static void print_perf_data(const git_checkout_perfdata *perfdata, void *payload) +{ + (void)payload; + printf("perf: stat: %" PRIuZ " mkdir: %" PRIuZ " chmod: %" PRIuZ "\n", + perfdata->stat_calls, perfdata->mkdir_calls, perfdata->chmod_calls); +} + +/** + * This is the main "checkout <branch>" function, responsible for performing + * a branch-based checkout. + */ +static int perform_checkout_ref(git_repository *repo, git_annotated_commit *target, checkout_options *opts) +{ + git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + git_commit *target_commit = NULL; + int err; + + /** Setup our checkout options from the parsed options */ + checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; + if (opts->force) + checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + if (opts->progress) + checkout_opts.progress_cb = print_checkout_progress; + + if (opts->perf) + checkout_opts.perfdata_cb = print_perf_data; + + /** Grab the commit we're interested to move to */ + err = git_commit_lookup(&target_commit, repo, git_annotated_commit_id(target)); + if (err != 0) { + fprintf(stderr, "failed to lookup commit: %s\n", giterr_last()->message); + goto cleanup; + } + + /** + * Perform the checkout so the workdir corresponds to what target_commit + * contains. + * + * Note that it's okay to pass a git_commit here, because it will be + * peeled to a tree. + */ + err = git_checkout_tree(repo, (const git_object *)target_commit, &checkout_opts); + if (err != 0) { + fprintf(stderr, "failed to checkout tree: %s\n", giterr_last()->message); + goto cleanup; + } + + /** + * Now that the checkout has completed, we have to update HEAD. + * + * Depending on the "origin" of target (ie. it's an OID or a branch name), + * we might need to detach HEAD. + */ + if (git_annotated_commit_ref(target)) { + err = git_repository_set_head(repo, git_annotated_commit_ref(target)); + } else { + err = git_repository_set_head_detached_from_annotated(repo, target); + } + if (err != 0) { + fprintf(stderr, "failed to update HEAD reference: %s\n", giterr_last()->message); + goto cleanup; + } + +cleanup: + git_commit_free(target_commit); + + return err; +} + +/** That example's entry point */ +int main(int argc, char **argv) +{ + git_repository *repo = NULL; + struct args_info args = ARGS_INFO_INIT; + checkout_options opts; + git_repository_state_t state; + git_annotated_commit *checkout_target = NULL; + int err = 0; + const char *path = "."; + + /** Parse our command line options */ + parse_options(&path, &opts, &args); + + /** Initialize the library */ + err = git_libgit2_init(); + if (!err) + check_lg2(err, "Failed to initialize libgit2", NULL); + + /** Open the repository corresponding to the options */ + check_lg2(git_repository_open_ext(&repo, path, 0, NULL), + "Could not open repository", NULL); + + /** Make sure we're not about to checkout while something else is going on */ + state = git_repository_state(repo); + if (state != GIT_REPOSITORY_STATE_NONE) { + fprintf(stderr, "repository is in unexpected state %d\n", state); + goto cleanup; + } + + if (args.pos >= args.argc) { + fprintf(stderr, "unhandled\n"); + err = -1; + goto cleanup; + } else if (strcmp("--", args.argv[args.pos])) { + /** + * Try to checkout the given path + */ + + fprintf(stderr, "unhandled path-based checkout\n"); + err = 1; + goto cleanup; + } else { + /** + * 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) { + fprintf(stderr, "failed to resolve %s: %s\n", args.argv[args.pos], giterr_last()->message); + goto cleanup; + } + err = perform_checkout_ref(repo, checkout_target, &opts); + } + +cleanup: + git_annotated_commit_free(checkout_target); + + git_repository_free(repo); + git_libgit2_shutdown(); + + return err; +} diff --git a/examples/common.c b/examples/common.c index 118072044..8c1042471 100644 --- a/examples/common.c +++ b/examples/common.c @@ -12,6 +12,8 @@ * <http://creativecommons.org/publicdomain/zero/1.0/>. */ +#include <assert.h> + #include "common.h" void check_lg2(int error, const char *message, const char *extra) @@ -182,6 +184,25 @@ static int match_int_internal( return 1; } +int match_bool_arg(int *out, struct args_info *args, const char *opt) +{ + const char *found = args->argv[args->pos]; + + if (!strcmp(found, opt)) { + *out = 1; + return 1; + } + + if (!strncmp(found, "--no-", strlen("--no-")) && + !strcmp(found + strlen("--no-"), opt + 2)) { + *out = 0; + return 1; + } + + *out = -1; + return 0; +} + int is_integer(int *out, const char *str, int allow_negative) { return match_int_internal(out, str, allow_negative, NULL); @@ -245,3 +266,26 @@ void *xrealloc(void *oldp, size_t newsz) return p; } +int resolve_refish(git_annotated_commit **commit, git_repository *repo, const char *refish) +{ + git_reference *ref; + git_object *obj; + int err = 0; + + assert(commit != NULL); + + err = git_reference_dwim(&ref, repo, refish); + if (err == GIT_OK) { + git_annotated_commit_from_ref(commit, repo, ref); + git_reference_free(ref); + return 0; + } + + err = git_revparse_single(&obj, repo, refish); + if (err == GIT_OK) { + err = git_annotated_commit_lookup(commit, repo, git_object_id(obj)); + git_object_free(obj); + } + + return err; +} diff --git a/examples/common.h b/examples/common.h index af42324c2..1c7b2035e 100644 --- a/examples/common.h +++ b/examples/common.h @@ -91,6 +91,15 @@ extern int match_int_arg( int *out, struct args_info *args, const char *opt, int allow_negative); /** + * Check current `args` entry against a "bool" `opt` (ie. --[no-]progress). + * If `opt` matches positively, out will be set to 1, or if `opt` matches + * negatively, out will be set to 0, and in both cases 1 will be returned. + * If neither the positive or the negative form of opt matched, out will be -1, + * and 0 will be returned. + */ +extern int match_bool_arg(int *out, struct args_info *args, const char *opt); + +/** * Basic output function for plain text diff output * Pass `FILE*` such as `stdout` or `stderr` as payload (or NULL == `stdout`) */ @@ -108,3 +117,8 @@ extern void treeish_to_tree( * A realloc that exits on failure */ extern void *xrealloc(void *oldp, size_t newsz); + +/** + * Convert a refish to an annotated commit. + */ +extern int resolve_refish(git_annotated_commit **commit, git_repository *repo, const char *refish); diff --git a/examples/merge.c b/examples/merge.c index 59982cb9b..c819ad208 100644 --- a/examples/merge.c +++ b/examples/merge.c @@ -87,29 +87,6 @@ static void parse_options(const char **repo_path, merge_options *opts, int argc, } } -static int resolve_refish(git_annotated_commit **commit, git_repository *repo, const char *refish) -{ - git_reference *ref; - int err = 0; - git_oid oid; - - assert(commit != NULL); - - err = git_reference_dwim(&ref, repo, refish); - if (err == GIT_OK) { - git_annotated_commit_from_ref(commit, repo, ref); - git_reference_free(ref); - return 0; - } - - err = git_oid_fromstr(&oid, refish); - if (err == GIT_OK) { - err = git_annotated_commit_lookup(commit, repo, &oid); - } - - return err; -} - static int resolve_heads(git_repository *repo, merge_options *opts) { git_annotated_commit **annotated = calloc(opts->heads_count, sizeof(git_annotated_commit *)); diff --git a/include/git2/annotated_commit.h b/include/git2/annotated_commit.h index 7fb896a5f..fa795bfc4 100644 --- a/include/git2/annotated_commit.h +++ b/include/git2/annotated_commit.h @@ -104,6 +104,15 @@ GIT_EXTERN(const git_oid *) git_annotated_commit_id( const git_annotated_commit *commit); /** + * Get the refname that the given `git_annotated_commit` refers to. + * + * @param commit the given annotated commit + * @return ref name. + */ +GIT_EXTERN(const char *) git_annotated_commit_ref( + const git_annotated_commit *commit); + +/** * Frees a `git_annotated_commit`. * * @param commit annotated commit to free diff --git a/src/annotated_commit.c b/src/annotated_commit.c index 72ba80a22..1aa67dd52 100644 --- a/src/annotated_commit.c +++ b/src/annotated_commit.c @@ -196,6 +196,13 @@ const git_oid *git_annotated_commit_id( return git_commit_id(annotated_commit->commit); } +const char *git_annotated_commit_ref( + const git_annotated_commit *annotated_commit) +{ + assert(annotated_commit); + return annotated_commit->ref_name; +} + void git_annotated_commit_free(git_annotated_commit *annotated_commit) { if (annotated_commit == NULL) |