mirror of
https://github.com/libgit2/libgit2.git
synced 2026-01-25 02:56:17 +00:00
examples: add checkout
This commit is contained in:
235
examples/checkout.c
Normal file
235
examples/checkout.c
Normal file
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user