Unverified Commit ea9e2c1a by Patrick Steinhardt Committed by GitHub

Merge pull request #4692 from tiennou/examples/checkout

Add a checkout example
parents 0652abaa b24202e1
/*
* 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;
}
......@@ -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;
}
......@@ -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);
......@@ -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 *));
......
......@@ -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
......
......@@ -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)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment