diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:47:08 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:47:08 +0000 |
commit | 29b5ab554790bb57337a3b6ab9dcd963cf69d22e (patch) | |
tree | be1456d2bc6c1fb078695fad7bc8f6b212062d3c /examples | |
parent | Initial commit. (diff) | |
download | libgit2-29b5ab554790bb57337a3b6ab9dcd963cf69d22e.tar.xz libgit2-29b5ab554790bb57337a3b6ab9dcd963cf69d22e.zip |
Adding upstream version 1.7.2+ds.upstream/1.7.2+ds
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
34 files changed, 6569 insertions, 0 deletions
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..8e38c7d --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,18 @@ +# examples: code usage examples of libgit2 + +file(GLOB SRC_EXAMPLES *.c *.h) + +add_executable(lg2 ${SRC_EXAMPLES}) +set_target_properties(lg2 PROPERTIES C_STANDARD 90) + +# Ensure that we do not use deprecated functions internally +add_definitions(-DGIT_DEPRECATE_HARD) + +target_include_directories(lg2 PRIVATE ${LIBGIT2_INCLUDES} ${LIBGIT2_DEPENDENCY_INCLUDES}) +target_include_directories(lg2 SYSTEM PRIVATE ${LIBGIT2_SYSTEM_INCLUDES}) + +if(WIN32 OR ANDROID) + target_link_libraries(lg2 libgit2package) +else() + target_link_libraries(lg2 libgit2package pthread) +endif() diff --git a/examples/COPYING b/examples/COPYING new file mode 100644 index 0000000..0e259d4 --- /dev/null +++ b/examples/COPYING @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..769c4b2 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,22 @@ +libgit2 examples +================ + +These examples are a mixture of basic emulation of core Git command line +functions and simple snippets demonstrating libgit2 API usage (for use +with Docurium). As a whole, they are not vetted carefully for bugs, error +handling, and cross-platform compatibility in the same manner as the rest +of the code in libgit2, so copy with caution. + +That being said, you are welcome to copy code from these examples as +desired when using libgit2. They have been [released to the public domain][cc0], +so there are no restrictions on their use. + +[cc0]: COPYING + +For annotated HTML versions, see the "Examples" section of: + + http://libgit2.github.com/libgit2 + +such as: + + http://libgit2.github.com/libgit2/ex/HEAD/general.html diff --git a/examples/add.c b/examples/add.c new file mode 100644 index 0000000..1c93b11 --- /dev/null +++ b/examples/add.c @@ -0,0 +1,157 @@ +/* + * libgit2 "add" example - shows how to modify the index + * + * 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" + +/** + * The following example demonstrates how to add files with libgit2. + * + * It will use the repository in the current working directory, and act + * on files passed as its parameters. + * + * Recognized options are: + * -v/--verbose: show the file's status after acting on it. + * -n/--dry-run: do not actually change the index. + * -u/--update: update the index instead of adding to it. + */ + +enum index_mode { + INDEX_NONE, + INDEX_ADD +}; + +struct index_options { + int dry_run; + int verbose; + git_repository *repo; + enum index_mode mode; + int add_update; +}; + +/* Forward declarations for helpers */ +static void parse_opts(const char **repo_path, struct index_options *opts, struct args_info *args); +int print_matched_cb(const char *path, const char *matched_pathspec, void *payload); + +int lg2_add(git_repository *repo, int argc, char **argv) +{ + git_index_matched_path_cb matched_cb = NULL; + git_index *index; + git_strarray array = {0}; + struct index_options options = {0}; + struct args_info args = ARGS_INFO_INIT; + + options.mode = INDEX_ADD; + + /* Parse the options & arguments. */ + parse_opts(NULL, &options, &args); + strarray_from_args(&array, &args); + + /* Grab the repository's index. */ + check_lg2(git_repository_index(&index, repo), "Could not open repository index", NULL); + + /* Setup a callback if the requested options need it */ + if (options.verbose || options.dry_run) { + matched_cb = &print_matched_cb; + } + + options.repo = repo; + + /* Perform the requested action with the index and files */ + if (options.add_update) { + git_index_update_all(index, &array, matched_cb, &options); + } else { + git_index_add_all(index, &array, 0, matched_cb, &options); + } + + /* Cleanup memory */ + git_index_write(index); + git_index_free(index); + + return 0; +} + +/* + * This callback is called for each file under consideration by + * git_index_(update|add)_all above. + * It makes uses of the callback's ability to abort the action. + */ +int print_matched_cb(const char *path, const char *matched_pathspec, void *payload) +{ + struct index_options *opts = (struct index_options *)(payload); + int ret; + unsigned status; + (void)matched_pathspec; + + /* Get the file status */ + if (git_status_file(&status, opts->repo, path) < 0) + return -1; + + if ((status & GIT_STATUS_WT_MODIFIED) || (status & GIT_STATUS_WT_NEW)) { + printf("add '%s'\n", path); + ret = 0; + } else { + ret = 1; + } + + if (opts->dry_run) + ret = 1; + + return ret; +} + +static void print_usage(void) +{ + fprintf(stderr, "usage: add [options] [--] file-spec [file-spec] [...]\n\n"); + fprintf(stderr, "\t-n, --dry-run dry run\n"); + fprintf(stderr, "\t-v, --verbose be verbose\n"); + fprintf(stderr, "\t-u, --update update tracked files\n"); + exit(1); +} + +static void parse_opts(const char **repo_path, struct index_options *opts, struct args_info *args) +{ + if (args->argc <= 1) + print_usage(); + + for (args->pos = 1; args->pos < args->argc; ++args->pos) { + const char *curr = args->argv[args->pos]; + + if (curr[0] != '-') { + if (!strcmp("add", curr)) { + opts->mode = INDEX_ADD; + continue; + } else if (opts->mode == INDEX_NONE) { + fprintf(stderr, "missing command: %s", curr); + print_usage(); + break; + } else { + /* We might be looking at a filename */ + break; + } + } else if (match_bool_arg(&opts->verbose, args, "--verbose") || + match_bool_arg(&opts->dry_run, args, "--dry-run") || + match_str_arg(repo_path, args, "--git-dir") || + (opts->mode == INDEX_ADD && match_bool_arg(&opts->add_update, args, "--update"))) { + continue; + } else if (match_bool_arg(NULL, args, "--help")) { + print_usage(); + break; + } else if (match_arg_separator(args)) { + break; + } else { + fprintf(stderr, "Unsupported option %s.\n", curr); + print_usage(); + } + } +} diff --git a/examples/args.c b/examples/args.c new file mode 100644 index 0000000..533e157 --- /dev/null +++ b/examples/args.c @@ -0,0 +1,197 @@ +#include "common.h" +#include "args.h" + +size_t is_prefixed(const char *str, const char *pfx) +{ + size_t len = strlen(pfx); + return strncmp(str, pfx, len) ? 0 : len; +} + +int optional_str_arg( + const char **out, struct args_info *args, const char *opt, const char *def) +{ + const char *found = args->argv[args->pos]; + size_t len = is_prefixed(found, opt); + + if (!len) + return 0; + + if (!found[len]) { + if (args->pos + 1 == args->argc) { + *out = def; + return 1; + } + args->pos += 1; + *out = args->argv[args->pos]; + return 1; + } + + if (found[len] == '=') { + *out = found + len + 1; + return 1; + } + + return 0; +} + +int match_str_arg( + const char **out, struct args_info *args, const char *opt) +{ + const char *found = args->argv[args->pos]; + size_t len = is_prefixed(found, opt); + + if (!len) + return 0; + + if (!found[len]) { + if (args->pos + 1 == args->argc) + fatal("expected value following argument", opt); + args->pos += 1; + *out = args->argv[args->pos]; + return 1; + } + + if (found[len] == '=') { + *out = found + len + 1; + return 1; + } + + return 0; +} + +static const char *match_numeric_arg(struct args_info *args, const char *opt) +{ + const char *found = args->argv[args->pos]; + size_t len = is_prefixed(found, opt); + + if (!len) + return NULL; + + if (!found[len]) { + if (args->pos + 1 == args->argc) + fatal("expected numeric value following argument", opt); + args->pos += 1; + found = args->argv[args->pos]; + } else { + found = found + len; + if (*found == '=') + found++; + } + + return found; +} + +int match_uint16_arg( + uint16_t *out, struct args_info *args, const char *opt) +{ + const char *found = match_numeric_arg(args, opt); + uint16_t val; + char *endptr = NULL; + + if (!found) + return 0; + + val = (uint16_t)strtoul(found, &endptr, 0); + if (!endptr || *endptr != '\0') + fatal("expected number after argument", opt); + + if (out) + *out = val; + return 1; +} + +int match_uint32_arg( + uint32_t *out, struct args_info *args, const char *opt) +{ + const char *found = match_numeric_arg(args, opt); + uint16_t val; + char *endptr = NULL; + + if (!found) + return 0; + + val = (uint32_t)strtoul(found, &endptr, 0); + if (!endptr || *endptr != '\0') + fatal("expected number after argument", opt); + + if (out) + *out = val; + return 1; +} + +static int match_int_internal( + int *out, const char *str, int allow_negative, const char *opt) +{ + char *endptr = NULL; + int val = (int)strtol(str, &endptr, 10); + + if (!endptr || *endptr != '\0') + fatal("expected number", opt); + else if (val < 0 && !allow_negative) + fatal("negative values are not allowed", opt); + + if (out) + *out = val; + + 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); +} + +int match_int_arg( + int *out, struct args_info *args, const char *opt, int allow_negative) +{ + const char *found = match_numeric_arg(args, opt); + if (!found) + return 0; + return match_int_internal(out, found, allow_negative, opt); +} + +int match_arg_separator(struct args_info *args) +{ + if (args->opts_done) + return 1; + + if (strcmp(args->argv[args->pos], "--") != 0) + return 0; + + args->opts_done = 1; + args->pos++; + return 1; +} + +void strarray_from_args(git_strarray *array, struct args_info *args) +{ + size_t i; + + array->count = args->argc - args->pos; + array->strings = calloc(array->count, sizeof(char *)); + assert(array->strings != NULL); + + for (i = 0; args->pos < args->argc; ++args->pos) { + array->strings[i++] = args->argv[args->pos]; + } + args->pos = args->argc; +} diff --git a/examples/args.h b/examples/args.h new file mode 100644 index 0000000..d626f98 --- /dev/null +++ b/examples/args.h @@ -0,0 +1,90 @@ +#ifndef INCLUDE_examples_args_h__ +#define INCLUDE_examples_args_h__ + +/** + * Argument-processing helper structure + */ +struct args_info { + int argc; + char **argv; + int pos; + int opts_done : 1; /**< Did we see a -- separator */ +}; +#define ARGS_INFO_INIT { argc, argv, 0, 0 } +#define ARGS_CURRENT(args) args->argv[args->pos] + +/** + * Check if a string has the given prefix. Returns 0 if not prefixed + * or the length of the prefix if it is. + */ +extern size_t is_prefixed(const char *str, const char *pfx); + +/** + * Match an integer string, returning 1 if matched, 0 if not. + */ +extern int is_integer(int *out, const char *str, int allow_negative); + +/** + * Check current `args` entry against `opt` string. If it matches + * exactly, take the next arg as a string; if it matches as a prefix with + * an equal sign, take the remainder as a string; if value not supplied, + * default value `def` will be given. otherwise return 0. + */ +extern int optional_str_arg( + const char **out, struct args_info *args, const char *opt, const char *def); + +/** + * Check current `args` entry against `opt` string. If it matches + * exactly, take the next arg as a string; if it matches as a prefix with + * an equal sign, take the remainder as a string; otherwise return 0. + */ +extern int match_str_arg( + const char **out, struct args_info *args, const char *opt); + +/** + * Check current `args` entry against `opt` string parsing as uint16. If + * `opt` matches exactly, take the next arg as a uint16_t value; if `opt` + * is a prefix (equal sign optional), take the remainder of the arg as a + * uint16_t value; otherwise return 0. + */ +extern int match_uint16_arg( + uint16_t *out, struct args_info *args, const char *opt); + +/** + * Check current `args` entry against `opt` string parsing as uint32. If + * `opt` matches exactly, take the next arg as a uint16_t value; if `opt` + * is a prefix (equal sign optional), take the remainder of the arg as a + * uint32_t value; otherwise return 0. + */ +extern int match_uint32_arg( + uint32_t *out, struct args_info *args, const char *opt); + +/** + * Check current `args` entry against `opt` string parsing as int. If + * `opt` matches exactly, take the next arg as an int value; if it matches + * as a prefix (equal sign optional), take the remainder of the arg as a + * int value; otherwise return 0. + */ +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); + +/** + * Check if we're processing past the single -- separator + */ +extern int match_arg_separator(struct args_info *args); + +/** + * Consume all remaining arguments in a git_strarray + */ +extern void strarray_from_args(git_strarray *array, struct args_info *args); + +#endif diff --git a/examples/blame.c b/examples/blame.c new file mode 100644 index 0000000..77087a5 --- /dev/null +++ b/examples/blame.c @@ -0,0 +1,198 @@ +/* + * libgit2 "blame" example - shows how to use the blame API + * + * 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" + +/** + * This example demonstrates how to invoke the libgit2 blame API to roughly + * simulate the output of `git blame` and a few of its command line arguments. + */ + +struct blame_opts { + char *path; + char *commitspec; + int C; + int M; + int start_line; + int end_line; + int F; +}; +static void parse_opts(struct blame_opts *o, int argc, char *argv[]); + +int lg2_blame(git_repository *repo, int argc, char *argv[]) +{ + int line, break_on_null_hunk; + git_object_size_t i, rawsize; + char spec[1024] = {0}; + struct blame_opts o = {0}; + const char *rawdata; + git_revspec revspec = {0}; + git_blame_options blameopts = GIT_BLAME_OPTIONS_INIT; + git_blame *blame = NULL; + git_blob *blob; + git_object *obj; + + parse_opts(&o, argc, argv); + if (o.M) blameopts.flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES; + if (o.C) blameopts.flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES; + if (o.F) blameopts.flags |= GIT_BLAME_FIRST_PARENT; + + /** + * The commit range comes in "committish" form. Use the rev-parse API to + * nail down the end points. + */ + if (o.commitspec) { + check_lg2(git_revparse(&revspec, repo, o.commitspec), "Couldn't parse commit spec", NULL); + if (revspec.flags & GIT_REVSPEC_SINGLE) { + git_oid_cpy(&blameopts.newest_commit, git_object_id(revspec.from)); + git_object_free(revspec.from); + } else { + git_oid_cpy(&blameopts.oldest_commit, git_object_id(revspec.from)); + git_oid_cpy(&blameopts.newest_commit, git_object_id(revspec.to)); + git_object_free(revspec.from); + git_object_free(revspec.to); + } + } + + /** Run the blame. */ + check_lg2(git_blame_file(&blame, repo, o.path, &blameopts), "Blame error", NULL); + + /** + * Get the raw data inside the blob for output. We use the + * `committish:path/to/file.txt` format to find it. + */ + if (git_oid_is_zero(&blameopts.newest_commit)) + strcpy(spec, "HEAD"); + else + git_oid_tostr(spec, sizeof(spec), &blameopts.newest_commit); + strcat(spec, ":"); + strcat(spec, o.path); + + check_lg2(git_revparse_single(&obj, repo, spec), "Object lookup error", NULL); + check_lg2(git_blob_lookup(&blob, repo, git_object_id(obj)), "Blob lookup error", NULL); + git_object_free(obj); + + rawdata = git_blob_rawcontent(blob); + rawsize = git_blob_rawsize(blob); + + /** Produce the output. */ + line = 1; + i = 0; + break_on_null_hunk = 0; + while (i < rawsize) { + const char *eol = memchr(rawdata + i, '\n', (size_t)(rawsize - i)); + char oid[10] = {0}; + const git_blame_hunk *hunk = git_blame_get_hunk_byline(blame, line); + + if (break_on_null_hunk && !hunk) + break; + + if (hunk) { + char sig[128] = {0}; + break_on_null_hunk = 1; + + git_oid_tostr(oid, 10, &hunk->final_commit_id); + snprintf(sig, 30, "%s <%s>", hunk->final_signature->name, hunk->final_signature->email); + + printf("%s ( %-30s %3d) %.*s\n", + oid, + sig, + line, + (int)(eol - rawdata - i), + rawdata + i); + } + + i = (int)(eol - rawdata + 1); + line++; + } + + /** Cleanup. */ + git_blob_free(blob); + git_blame_free(blame); + + return 0; +} + +/** Tell the user how to make this thing work. */ +static void usage(const char *msg, const char *arg) +{ + if (msg && arg) + fprintf(stderr, "%s: %s\n", msg, arg); + else if (msg) + fprintf(stderr, "%s\n", msg); + fprintf(stderr, "usage: blame [options] [<commit range>] <path>\n"); + fprintf(stderr, "\n"); + fprintf(stderr, " <commit range> example: `HEAD~10..HEAD`, or `1234abcd`\n"); + fprintf(stderr, " -L <n,m> process only line range n-m, counting from 1\n"); + fprintf(stderr, " -M find line moves within and across files\n"); + fprintf(stderr, " -C find line copies within and across files\n"); + fprintf(stderr, " -F follow only the first parent commits\n"); + fprintf(stderr, "\n"); + exit(1); +} + +/** Parse the arguments. */ +static void parse_opts(struct blame_opts *o, int argc, char *argv[]) +{ + int i; + char *bare_args[3] = {0}; + + if (argc < 2) usage(NULL, NULL); + + for (i=1; i<argc; i++) { + char *a = argv[i]; + + if (a[0] != '-') { + int i=0; + while (bare_args[i] && i < 3) ++i; + if (i >= 3) + usage("Invalid argument set", NULL); + bare_args[i] = a; + } + else if (!strcmp(a, "--")) + continue; + else if (!strcasecmp(a, "-M")) + o->M = 1; + else if (!strcasecmp(a, "-C")) + o->C = 1; + else if (!strcasecmp(a, "-F")) + o->F = 1; + else if (!strcasecmp(a, "-L")) { + i++; a = argv[i]; + if (i >= argc) fatal("Not enough arguments to -L", NULL); + check_lg2(sscanf(a, "%d,%d", &o->start_line, &o->end_line)-2, "-L format error", NULL); + } + else { + /* commit range */ + if (o->commitspec) fatal("Only one commit spec allowed", NULL); + o->commitspec = a; + } + } + + /* Handle the bare arguments */ + if (!bare_args[0]) usage("Please specify a path", NULL); + o->path = bare_args[0]; + if (bare_args[1]) { + /* <commitspec> <path> */ + o->path = bare_args[1]; + o->commitspec = bare_args[0]; + } + if (bare_args[2]) { + /* <oldcommit> <newcommit> <path> */ + char spec[128] = {0}; + o->path = bare_args[2]; + sprintf(spec, "%s..%s", bare_args[0], bare_args[1]); + o->commitspec = spec; + } +} diff --git a/examples/cat-file.c b/examples/cat-file.c new file mode 100644 index 0000000..741edb4 --- /dev/null +++ b/examples/cat-file.c @@ -0,0 +1,239 @@ +/* + * libgit2 "cat-file" example - shows how to print data from the ODB + * + * 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" + +static void print_signature(const char *header, const git_signature *sig) +{ + char sign; + int offset, hours, minutes; + + if (!sig) + return; + + offset = sig->when.offset; + if (offset < 0) { + sign = '-'; + offset = -offset; + } else { + sign = '+'; + } + + hours = offset / 60; + minutes = offset % 60; + + printf("%s %s <%s> %ld %c%02d%02d\n", + header, sig->name, sig->email, (long)sig->when.time, + sign, hours, minutes); +} + +/** Printing out a blob is simple, get the contents and print */ +static void show_blob(const git_blob *blob) +{ + /* ? Does this need crlf filtering? */ + fwrite(git_blob_rawcontent(blob), (size_t)git_blob_rawsize(blob), 1, stdout); +} + +/** Show each entry with its type, id and attributes */ +static void show_tree(const git_tree *tree) +{ + size_t i, max_i = (int)git_tree_entrycount(tree); + char oidstr[GIT_OID_SHA1_HEXSIZE + 1]; + const git_tree_entry *te; + + for (i = 0; i < max_i; ++i) { + te = git_tree_entry_byindex(tree, i); + + git_oid_tostr(oidstr, sizeof(oidstr), git_tree_entry_id(te)); + + printf("%06o %s %s\t%s\n", + git_tree_entry_filemode(te), + git_object_type2string(git_tree_entry_type(te)), + oidstr, git_tree_entry_name(te)); + } +} + +/** + * Commits and tags have a few interesting fields in their header. + */ +static void show_commit(const git_commit *commit) +{ + unsigned int i, max_i; + char oidstr[GIT_OID_SHA1_HEXSIZE + 1]; + + git_oid_tostr(oidstr, sizeof(oidstr), git_commit_tree_id(commit)); + printf("tree %s\n", oidstr); + + max_i = (unsigned int)git_commit_parentcount(commit); + for (i = 0; i < max_i; ++i) { + git_oid_tostr(oidstr, sizeof(oidstr), git_commit_parent_id(commit, i)); + printf("parent %s\n", oidstr); + } + + print_signature("author", git_commit_author(commit)); + print_signature("committer", git_commit_committer(commit)); + + if (git_commit_message(commit)) + printf("\n%s\n", git_commit_message(commit)); +} + +static void show_tag(const git_tag *tag) +{ + char oidstr[GIT_OID_SHA1_HEXSIZE + 1]; + + git_oid_tostr(oidstr, sizeof(oidstr), git_tag_target_id(tag));; + printf("object %s\n", oidstr); + printf("type %s\n", git_object_type2string(git_tag_target_type(tag))); + printf("tag %s\n", git_tag_name(tag)); + print_signature("tagger", git_tag_tagger(tag)); + + if (git_tag_message(tag)) + printf("\n%s\n", git_tag_message(tag)); +} + +typedef enum { + SHOW_TYPE = 1, + SHOW_SIZE = 2, + SHOW_NONE = 3, + SHOW_PRETTY = 4 +} catfile_mode; + +/* Forward declarations for option-parsing helper */ +struct catfile_options { + const char *dir; + const char *rev; + catfile_mode action; + int verbose; +}; + +static void parse_opts(struct catfile_options *o, int argc, char *argv[]); + + +/** Entry point for this command */ +int lg2_cat_file(git_repository *repo, int argc, char *argv[]) +{ + struct catfile_options o = { ".", NULL, 0, 0 }; + git_object *obj = NULL; + char oidstr[GIT_OID_SHA1_HEXSIZE + 1]; + + parse_opts(&o, argc, argv); + + check_lg2(git_revparse_single(&obj, repo, o.rev), + "Could not resolve", o.rev); + + if (o.verbose) { + char oidstr[GIT_OID_SHA1_HEXSIZE + 1]; + git_oid_tostr(oidstr, sizeof(oidstr), git_object_id(obj)); + + printf("%s %s\n--\n", + git_object_type2string(git_object_type(obj)), oidstr); + } + + switch (o.action) { + case SHOW_TYPE: + printf("%s\n", git_object_type2string(git_object_type(obj))); + break; + case SHOW_SIZE: { + git_odb *odb; + git_odb_object *odbobj; + + check_lg2(git_repository_odb(&odb, repo), "Could not open ODB", NULL); + check_lg2(git_odb_read(&odbobj, odb, git_object_id(obj)), + "Could not find obj", NULL); + + printf("%ld\n", (long)git_odb_object_size(odbobj)); + + git_odb_object_free(odbobj); + git_odb_free(odb); + } + break; + case SHOW_NONE: + /* just want return result */ + break; + case SHOW_PRETTY: + + switch (git_object_type(obj)) { + case GIT_OBJECT_BLOB: + show_blob((const git_blob *)obj); + break; + case GIT_OBJECT_COMMIT: + show_commit((const git_commit *)obj); + break; + case GIT_OBJECT_TREE: + show_tree((const git_tree *)obj); + break; + case GIT_OBJECT_TAG: + show_tag((const git_tag *)obj); + break; + default: + printf("unknown %s\n", oidstr); + break; + } + break; + } + + git_object_free(obj); + + return 0; +} + +/** Print out usage information */ +static void usage(const char *message, const char *arg) +{ + if (message && arg) + fprintf(stderr, "%s: %s\n", message, arg); + else if (message) + fprintf(stderr, "%s\n", message); + fprintf(stderr, + "usage: cat-file (-t | -s | -e | -p) [-v] [-q] " + "[-h|--help] [--git-dir=<dir>] <object>\n"); + exit(1); +} + +/** Parse the command-line options taken from git */ +static void parse_opts(struct catfile_options *o, int argc, char *argv[]) +{ + struct args_info args = ARGS_INFO_INIT; + + for (args.pos = 1; args.pos < argc; ++args.pos) { + char *a = argv[args.pos]; + + if (a[0] != '-') { + if (o->rev != NULL) + usage("Only one rev should be provided", NULL); + else + o->rev = a; + } + else if (!strcmp(a, "-t")) + o->action = SHOW_TYPE; + else if (!strcmp(a, "-s")) + o->action = SHOW_SIZE; + else if (!strcmp(a, "-e")) + o->action = SHOW_NONE; + else if (!strcmp(a, "-p")) + o->action = SHOW_PRETTY; + else if (!strcmp(a, "-q")) + o->verbose = 0; + else if (!strcmp(a, "-v")) + o->verbose = 1; + else if (!strcmp(a, "--help") || !strcmp(a, "-h")) + usage(NULL, NULL); + else if (!match_str_arg(&o->dir, &args, "--git-dir")) + usage("Unknown option", a); + } + + if (!o->action || !o->rev) + usage(NULL, NULL); + +} diff --git a/examples/checkout.c b/examples/checkout.c new file mode 100644 index 0000000..ac7b742 --- /dev/null +++ b/examples/checkout.c @@ -0,0 +1,290 @@ +/* + * 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" + +/* Define the printf format specifier 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 (match_arg_separator(args)) { + 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, 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; + + /** 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", git_error_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", git_error_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)) { + 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; + } + +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 <ref>. 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_dispose(&remotes); + return error; +} + +/** That example's entry point */ +int lg2_checkout(git_repository *repo, int argc, char **argv) +{ + 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); + + /** 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 (match_arg_separator(&args)) { + /** + * 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 + */ + 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, args.argv[args.pos], &opts); + } + +cleanup: + git_annotated_commit_free(checkout_target); + + return err; +} diff --git a/examples/clone.c b/examples/clone.c new file mode 100644 index 0000000..22d9d9b --- /dev/null +++ b/examples/clone.c @@ -0,0 +1,104 @@ +#include "common.h" + +typedef struct progress_data { + git_indexer_progress fetch_progress; + size_t completed_steps; + size_t total_steps; + const char *path; +} progress_data; + +static void print_progress(const progress_data *pd) +{ + int network_percent = pd->fetch_progress.total_objects > 0 ? + (100*pd->fetch_progress.received_objects) / pd->fetch_progress.total_objects : + 0; + int index_percent = pd->fetch_progress.total_objects > 0 ? + (100*pd->fetch_progress.indexed_objects) / pd->fetch_progress.total_objects : + 0; + + int checkout_percent = pd->total_steps > 0 + ? (int)((100 * pd->completed_steps) / pd->total_steps) + : 0; + size_t kbytes = pd->fetch_progress.received_bytes / 1024; + + if (pd->fetch_progress.total_objects && + pd->fetch_progress.received_objects == pd->fetch_progress.total_objects) { + printf("Resolving deltas %u/%u\r", + pd->fetch_progress.indexed_deltas, + pd->fetch_progress.total_deltas); + } else { + printf("net %3d%% (%4" PRIuZ " kb, %5u/%5u) / idx %3d%% (%5u/%5u) / chk %3d%% (%4" PRIuZ "/%4" PRIuZ")%s\n", + network_percent, kbytes, + pd->fetch_progress.received_objects, pd->fetch_progress.total_objects, + index_percent, pd->fetch_progress.indexed_objects, pd->fetch_progress.total_objects, + checkout_percent, + pd->completed_steps, pd->total_steps, + pd->path); + } +} + +static int sideband_progress(const char *str, int len, void *payload) +{ + (void)payload; /* unused */ + + printf("remote: %.*s", len, str); + fflush(stdout); + return 0; +} + +static int fetch_progress(const git_indexer_progress *stats, void *payload) +{ + progress_data *pd = (progress_data*)payload; + pd->fetch_progress = *stats; + print_progress(pd); + return 0; +} +static void checkout_progress(const char *path, size_t cur, size_t tot, void *payload) +{ + progress_data *pd = (progress_data*)payload; + pd->completed_steps = cur; + pd->total_steps = tot; + pd->path = path; + print_progress(pd); +} + + +int lg2_clone(git_repository *repo, int argc, char **argv) +{ + progress_data pd = {{0}}; + git_repository *cloned_repo = NULL; + git_clone_options clone_opts = GIT_CLONE_OPTIONS_INIT; + git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + const char *url = argv[1]; + const char *path = argv[2]; + int error; + + (void)repo; /* unused */ + + /* Validate args */ + if (argc < 3) { + printf ("USAGE: %s <url> <path>\n", argv[0]); + return -1; + } + + /* Set up options */ + checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; + checkout_opts.progress_cb = checkout_progress; + checkout_opts.progress_payload = &pd; + clone_opts.checkout_opts = checkout_opts; + clone_opts.fetch_opts.callbacks.sideband_progress = sideband_progress; + clone_opts.fetch_opts.callbacks.transfer_progress = &fetch_progress; + clone_opts.fetch_opts.callbacks.credentials = cred_acquire_cb; + clone_opts.fetch_opts.callbacks.payload = &pd; + + /* Do the clone */ + error = git_clone(&cloned_repo, url, path, &clone_opts); + printf("\n"); + if (error != 0) { + const git_error *err = git_error_last(); + if (err) printf("ERROR %d: %s\n", err->klass, err->message); + else printf("ERROR %d: no detailed info\n", error); + } + else if (cloned_repo) git_repository_free(cloned_repo); + return error; +} diff --git a/examples/commit.c b/examples/commit.c new file mode 100644 index 0000000..aedc1af --- /dev/null +++ b/examples/commit.c @@ -0,0 +1,86 @@ +/* + * libgit2 "commit" example - shows how to create a git commit + * + * 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" + +/** + * This example demonstrates the libgit2 commit APIs to roughly + * simulate `git commit` with the commit message argument. + * + * This does not have: + * + * - Robust error handling + * - Most of the `git commit` options + * + * This does have: + * + * - Example of performing a git commit with a comment + * + */ +int lg2_commit(git_repository *repo, int argc, char **argv) +{ + const char *opt = argv[1]; + const char *comment = argv[2]; + int error; + + git_oid commit_oid,tree_oid; + git_tree *tree; + git_index *index; + git_object *parent = NULL; + git_reference *ref = NULL; + git_signature *signature; + + /* Validate args */ + if (argc < 3 || strcmp(opt, "-m") != 0) { + printf ("USAGE: %s -m <comment>\n", argv[0]); + return -1; + } + + error = git_revparse_ext(&parent, &ref, repo, "HEAD"); + if (error == GIT_ENOTFOUND) { + printf("HEAD not found. Creating first commit\n"); + error = 0; + } else if (error != 0) { + const git_error *err = git_error_last(); + if (err) printf("ERROR %d: %s\n", err->klass, err->message); + else printf("ERROR %d: no detailed info\n", error); + } + + check_lg2(git_repository_index(&index, repo), "Could not open repository index", NULL); + check_lg2(git_index_write_tree(&tree_oid, index), "Could not write tree", NULL);; + check_lg2(git_index_write(index), "Could not write index", NULL);; + + check_lg2(git_tree_lookup(&tree, repo, &tree_oid), "Error looking up tree", NULL); + + check_lg2(git_signature_default(&signature, repo), "Error creating signature", NULL); + + check_lg2(git_commit_create_v( + &commit_oid, + repo, + "HEAD", + signature, + signature, + NULL, + comment, + tree, + parent ? 1 : 0, parent), "Error creating commit", NULL); + + git_index_free(index); + git_signature_free(signature); + git_tree_free(tree); + git_object_free(parent); + git_reference_free(ref); + + return error; +} diff --git a/examples/common.c b/examples/common.c new file mode 100644 index 0000000..b068b84 --- /dev/null +++ b/examples/common.c @@ -0,0 +1,260 @@ +/* + * Utilities library for libgit2 examples + * + * 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" + +#ifndef _WIN32 +# include <unistd.h> +#endif +#include <errno.h> + +void check_lg2(int error, const char *message, const char *extra) +{ + const git_error *lg2err; + const char *lg2msg = "", *lg2spacer = ""; + + if (!error) + return; + + if ((lg2err = git_error_last()) != NULL && lg2err->message != NULL) { + lg2msg = lg2err->message; + lg2spacer = " - "; + } + + if (extra) + fprintf(stderr, "%s '%s' [%d]%s%s\n", + message, extra, error, lg2spacer, lg2msg); + else + fprintf(stderr, "%s [%d]%s%s\n", + message, error, lg2spacer, lg2msg); + + exit(1); +} + +void fatal(const char *message, const char *extra) +{ + if (extra) + fprintf(stderr, "%s %s\n", message, extra); + else + fprintf(stderr, "%s\n", message); + + exit(1); +} + +int diff_output( + const git_diff_delta *d, + const git_diff_hunk *h, + const git_diff_line *l, + void *p) +{ + FILE *fp = (FILE*)p; + + (void)d; (void)h; + + if (!fp) + fp = stdout; + + if (l->origin == GIT_DIFF_LINE_CONTEXT || + l->origin == GIT_DIFF_LINE_ADDITION || + l->origin == GIT_DIFF_LINE_DELETION) + fputc(l->origin, fp); + + fwrite(l->content, 1, l->content_len, fp); + + return 0; +} + +void treeish_to_tree( + git_tree **out, git_repository *repo, const char *treeish) +{ + git_object *obj = NULL; + + check_lg2( + git_revparse_single(&obj, repo, treeish), + "looking up object", treeish); + + check_lg2( + git_object_peel((git_object **)out, obj, GIT_OBJECT_TREE), + "resolving object to tree", treeish); + + git_object_free(obj); +} + +void *xrealloc(void *oldp, size_t newsz) +{ + void *p = realloc(oldp, newsz); + if (p == NULL) { + fprintf(stderr, "Cannot allocate memory, exiting.\n"); + exit(1); + } + 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; +} + +static int readline(char **out) +{ + int c, error = 0, length = 0, allocated = 0; + char *line = NULL; + + errno = 0; + + while ((c = getchar()) != EOF) { + if (length == allocated) { + allocated += 16; + + if ((line = realloc(line, allocated)) == NULL) { + error = -1; + goto error; + } + } + + if (c == '\n') + break; + + line[length++] = c; + } + + if (errno != 0) { + error = -1; + goto error; + } + + line[length] = '\0'; + *out = line; + line = NULL; + error = length; +error: + free(line); + return error; +} + +static int ask(char **out, const char *prompt, char optional) +{ + printf("%s ", prompt); + fflush(stdout); + + if (!readline(out) && !optional) { + fprintf(stderr, "Could not read response: %s", strerror(errno)); + return -1; + } + + return 0; +} + +int cred_acquire_cb(git_credential **out, + const char *url, + const char *username_from_url, + unsigned int allowed_types, + void *payload) +{ + char *username = NULL, *password = NULL, *privkey = NULL, *pubkey = NULL; + int error = 1; + + UNUSED(url); + UNUSED(payload); + + if (username_from_url) { + if ((username = strdup(username_from_url)) == NULL) + goto out; + } else if ((error = ask(&username, "Username:", 0)) < 0) { + goto out; + } + + if (allowed_types & GIT_CREDENTIAL_SSH_KEY) { + int n; + + if ((error = ask(&privkey, "SSH Key:", 0)) < 0 || + (error = ask(&password, "Password:", 1)) < 0) + goto out; + + if ((n = snprintf(NULL, 0, "%s.pub", privkey)) < 0 || + (pubkey = malloc(n + 1)) == NULL || + (n = snprintf(pubkey, n + 1, "%s.pub", privkey)) < 0) + goto out; + + error = git_credential_ssh_key_new(out, username, pubkey, privkey, password); + } else if (allowed_types & GIT_CREDENTIAL_USERPASS_PLAINTEXT) { + if ((error = ask(&password, "Password:", 1)) < 0) + goto out; + + error = git_credential_userpass_plaintext_new(out, username, password); + } else if (allowed_types & GIT_CREDENTIAL_USERNAME) { + error = git_credential_username_new(out, username); + } + +out: + free(username); + free(password); + free(privkey); + free(pubkey); + return error; +} + +char *read_file(const char *path) +{ + ssize_t total = 0; + char *buf = NULL; + struct stat st; + int fd = -1; + + if ((fd = open(path, O_RDONLY)) < 0 || fstat(fd, &st) < 0) + goto out; + + if ((buf = malloc(st.st_size + 1)) == NULL) + goto out; + + while (total < st.st_size) { + ssize_t bytes = read(fd, buf + total, st.st_size - total); + if (bytes <= 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + free(buf); + buf = NULL; + goto out; + } + total += bytes; + } + + buf[total] = '\0'; + +out: + if (fd >= 0) + close(fd); + return buf; +} + diff --git a/examples/common.h b/examples/common.h new file mode 100644 index 0000000..901c041 --- /dev/null +++ b/examples/common.h @@ -0,0 +1,136 @@ +/* + * Utilities library for libgit2 examples + * + * 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/>. + */ +#ifndef INCLUDE_examples_common_h__ +#define INCLUDE_examples_common_h__ + +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <git2.h> +#include <fcntl.h> + +#ifdef _WIN32 +# include <io.h> +# include <Windows.h> +# define open _open +# define read _read +# define close _close +# define ssize_t int +# define sleep(a) Sleep(a * 1000) +#else +# include <unistd.h> +#endif + +#ifndef PRIuZ +/* Define the printf format specifier to use for size_t output */ +#if defined(_MSC_VER) || defined(__MINGW32__) +# define PRIuZ "Iu" +#else +# define PRIuZ "zu" +#endif +#endif + +#ifdef _MSC_VER +#define snprintf _snprintf +#define strcasecmp strcmpi +#endif + +#define ARRAY_SIZE(x) (sizeof(x)/sizeof(*x)) +#define UNUSED(x) (void)(x) + +#include "args.h" + +extern int lg2_add(git_repository *repo, int argc, char **argv); +extern int lg2_blame(git_repository *repo, int argc, char **argv); +extern int lg2_cat_file(git_repository *repo, int argc, char **argv); +extern int lg2_checkout(git_repository *repo, int argc, char **argv); +extern int lg2_clone(git_repository *repo, int argc, char **argv); +extern int lg2_commit(git_repository *repo, int argc, char **argv); +extern int lg2_config(git_repository *repo, int argc, char **argv); +extern int lg2_describe(git_repository *repo, int argc, char **argv); +extern int lg2_diff(git_repository *repo, int argc, char **argv); +extern int lg2_fetch(git_repository *repo, int argc, char **argv); +extern int lg2_for_each_ref(git_repository *repo, int argc, char **argv); +extern int lg2_general(git_repository *repo, int argc, char **argv); +extern int lg2_index_pack(git_repository *repo, int argc, char **argv); +extern int lg2_init(git_repository *repo, int argc, char **argv); +extern int lg2_log(git_repository *repo, int argc, char **argv); +extern int lg2_ls_files(git_repository *repo, int argc, char **argv); +extern int lg2_ls_remote(git_repository *repo, int argc, char **argv); +extern int lg2_merge(git_repository *repo, int argc, char **argv); +extern int lg2_push(git_repository *repo, int argc, char **argv); +extern int lg2_remote(git_repository *repo, int argc, char **argv); +extern int lg2_rev_list(git_repository *repo, int argc, char **argv); +extern int lg2_rev_parse(git_repository *repo, int argc, char **argv); +extern int lg2_show_index(git_repository *repo, int argc, char **argv); +extern int lg2_stash(git_repository *repo, int argc, char **argv); +extern int lg2_status(git_repository *repo, int argc, char **argv); +extern int lg2_tag(git_repository *repo, int argc, char **argv); + +/** + * Check libgit2 error code, printing error to stderr on failure and + * exiting the program. + */ +extern void check_lg2(int error, const char *message, const char *extra); + +/** + * Read a file into a buffer + * + * @param path The path to the file that shall be read + * @return NUL-terminated buffer if the file was successfully read, NULL-pointer otherwise + */ +extern char *read_file(const char *path); + +/** + * Exit the program, printing error to stderr + */ +extern void fatal(const char *message, const char *extra); + +/** + * Basic output function for plain text diff output + * Pass `FILE*` such as `stdout` or `stderr` as payload (or NULL == `stdout`) + */ +extern int diff_output( + const git_diff_delta*, const git_diff_hunk*, const git_diff_line*, void*); + +/** + * Convert a treeish argument to an actual tree; this will call check_lg2 + * and exit the program if `treeish` cannot be resolved to a tree + */ +extern void treeish_to_tree( + git_tree **out, git_repository *repo, const char *treeish); + +/** + * 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); + +/** + * Acquire credentials via command line + */ +extern int cred_acquire_cb(git_credential **out, + const char *url, + const char *username_from_url, + unsigned int allowed_types, + void *payload); + +#endif diff --git a/examples/config.c b/examples/config.c new file mode 100644 index 0000000..6e14ce8 --- /dev/null +++ b/examples/config.c @@ -0,0 +1,71 @@ +/* + * libgit2 "config" example - shows how to use the config API + * + * 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" + +static int config_get(git_config *cfg, const char *key) +{ + git_config_entry *entry; + int error; + + if ((error = git_config_get_entry(&entry, cfg, key)) < 0) { + if (error != GIT_ENOTFOUND) + printf("Unable to get configuration: %s\n", git_error_last()->message); + return 1; + } + + puts(entry->value); + + /* Free the git_config_entry after use with `git_config_entry_free()`. */ + git_config_entry_free(entry); + + return 0; +} + +static int config_set(git_config *cfg, const char *key, const char *value) +{ + if (git_config_set_string(cfg, key, value) < 0) { + printf("Unable to set configuration: %s\n", git_error_last()->message); + return 1; + } + return 0; +} + +int lg2_config(git_repository *repo, int argc, char **argv) +{ + git_config *cfg; + int error; + + if ((error = git_repository_config(&cfg, repo)) < 0) { + printf("Unable to obtain repository config: %s\n", git_error_last()->message); + goto out; + } + + if (argc == 2) { + error = config_get(cfg, argv[1]); + } else if (argc == 3) { + error = config_set(cfg, argv[1], argv[2]); + } else { + printf("USAGE: %s config <KEY> [<VALUE>]\n", argv[0]); + error = 1; + } + + /** + * The configuration file must be freed once it's no longer + * being used by the user. + */ + git_config_free(cfg); +out: + return error; +} diff --git a/examples/describe.c b/examples/describe.c new file mode 100644 index 0000000..1236272 --- /dev/null +++ b/examples/describe.c @@ -0,0 +1,162 @@ +/* + * libgit2 "describe" example - shows how to describe commits + * + * 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" + +/** + * The following example partially reimplements the `git describe` command + * and some of its options. + * + * These commands should work: + + * - Describe HEAD with default options (`describe`) + * - Describe specified revision (`describe master~2`) + * - Describe specified revisions (`describe master~2 HEAD~3`) + * - Describe HEAD with dirty state suffix (`describe --dirty=*`) + * - Describe consider all refs (`describe --all master`) + * - Describe consider lightweight tags (`describe --tags temp-tag`) + * - Describe show non-default abbreviated size (`describe --abbrev=10`) + * - Describe always output the long format if matches a tag (`describe --long v1.0`) + * - Describe consider only tags of specified pattern (`describe --match v*-release`) + * - Describe show the fallback result (`describe --always`) + * - Describe follow only the first parent commit (`describe --first-parent`) + * + * The command line parsing logic is simplified and doesn't handle + * all of the use cases. + */ + +/** describe_options represents the parsed command line options */ +struct describe_options { + const char **commits; + size_t commit_count; + git_describe_options describe_options; + git_describe_format_options format_options; +}; + +static void opts_add_commit(struct describe_options *opts, const char *commit) +{ + size_t sz; + + assert(opts != NULL); + + sz = ++opts->commit_count * sizeof(opts->commits[0]); + opts->commits = xrealloc((void *) opts->commits, sz); + opts->commits[opts->commit_count - 1] = commit; +} + +static void do_describe_single(git_repository *repo, struct describe_options *opts, const char *rev) +{ + git_object *commit; + git_describe_result *describe_result; + git_buf buf = { 0 }; + + if (rev) { + check_lg2(git_revparse_single(&commit, repo, rev), + "Failed to lookup rev", rev); + + check_lg2(git_describe_commit(&describe_result, commit, &opts->describe_options), + "Failed to describe rev", rev); + } + else + check_lg2(git_describe_workdir(&describe_result, repo, &opts->describe_options), + "Failed to describe workdir", NULL); + + check_lg2(git_describe_format(&buf, describe_result, &opts->format_options), + "Failed to format describe rev", rev); + + printf("%s\n", buf.ptr); +} + +static void do_describe(git_repository *repo, struct describe_options *opts) +{ + if (opts->commit_count == 0) + do_describe_single(repo, opts, NULL); + else + { + size_t i; + for (i = 0; i < opts->commit_count; i++) + do_describe_single(repo, opts, opts->commits[i]); + } +} + +static void print_usage(void) +{ + fprintf(stderr, "usage: see `git help describe`\n"); + exit(1); +} + +/** Parse command line arguments */ +static void parse_options(struct describe_options *opts, int argc, char **argv) +{ + struct args_info args = ARGS_INFO_INIT; + + for (args.pos = 1; args.pos < argc; ++args.pos) { + const char *curr = argv[args.pos]; + + if (curr[0] != '-') { + opts_add_commit(opts, curr); + } else if (!strcmp(curr, "--all")) { + opts->describe_options.describe_strategy = GIT_DESCRIBE_ALL; + } else if (!strcmp(curr, "--tags")) { + opts->describe_options.describe_strategy = GIT_DESCRIBE_TAGS; + } else if (!strcmp(curr, "--exact-match")) { + opts->describe_options.max_candidates_tags = 0; + } else if (!strcmp(curr, "--long")) { + opts->format_options.always_use_long_format = 1; + } else if (!strcmp(curr, "--always")) { + opts->describe_options.show_commit_oid_as_fallback = 1; + } else if (!strcmp(curr, "--first-parent")) { + opts->describe_options.only_follow_first_parent = 1; + } else if (optional_str_arg(&opts->format_options.dirty_suffix, &args, "--dirty", "-dirty")) { + } else if (match_int_arg((int *)&opts->format_options.abbreviated_size, &args, "--abbrev", 0)) { + } else if (match_int_arg((int *)&opts->describe_options.max_candidates_tags, &args, "--candidates", 0)) { + } else if (match_str_arg(&opts->describe_options.pattern, &args, "--match")) { + } else { + print_usage(); + } + } + + if (opts->commit_count > 0) { + if (opts->format_options.dirty_suffix) + fatal("--dirty is incompatible with commit-ishes", NULL); + } + else { + if (!opts->format_options.dirty_suffix || !opts->format_options.dirty_suffix[0]) { + opts_add_commit(opts, "HEAD"); + } + } +} + +/** Initialize describe_options struct */ +static void describe_options_init(struct describe_options *opts) +{ + memset(opts, 0, sizeof(*opts)); + + opts->commits = NULL; + opts->commit_count = 0; + git_describe_options_init(&opts->describe_options, GIT_DESCRIBE_OPTIONS_VERSION); + git_describe_format_options_init(&opts->format_options, GIT_DESCRIBE_FORMAT_OPTIONS_VERSION); +} + +int lg2_describe(git_repository *repo, int argc, char **argv) +{ + struct describe_options opts; + + describe_options_init(&opts); + parse_options(&opts, argc, argv); + + do_describe(repo, &opts); + + return 0; +} diff --git a/examples/diff.c b/examples/diff.c new file mode 100644 index 0000000..80c5200 --- /dev/null +++ b/examples/diff.c @@ -0,0 +1,377 @@ +/* + * libgit2 "diff" example - shows how to use the diff API + * + * 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" + +/** + * This example demonstrates the use of the libgit2 diff APIs to + * create `git_diff` objects and display them, emulating a number of + * core Git `diff` command line options. + * + * This covers on a portion of the core Git diff options and doesn't + * have particularly good error handling, but it should show most of + * the core libgit2 diff APIs, including various types of diffs and + * how to do renaming detection and patch formatting. + */ + +static const char *colors[] = { + "\033[m", /* reset */ + "\033[1m", /* bold */ + "\033[31m", /* red */ + "\033[32m", /* green */ + "\033[36m" /* cyan */ +}; + +enum { + OUTPUT_DIFF = (1 << 0), + OUTPUT_STAT = (1 << 1), + OUTPUT_SHORTSTAT = (1 << 2), + OUTPUT_NUMSTAT = (1 << 3), + OUTPUT_SUMMARY = (1 << 4) +}; + +enum { + CACHE_NORMAL = 0, + CACHE_ONLY = 1, + CACHE_NONE = 2 +}; + +/** The 'diff_options' struct captures all the various parsed command line options. */ +struct diff_options { + git_diff_options diffopts; + git_diff_find_options findopts; + int color; + int no_index; + int cache; + int output; + git_diff_format_t format; + const char *treeish1; + const char *treeish2; + const char *dir; +}; + +/** These functions are implemented at the end */ +static void usage(const char *message, const char *arg); +static void parse_opts(struct diff_options *o, int argc, char *argv[]); +static int color_printer( + const git_diff_delta*, const git_diff_hunk*, const git_diff_line*, void*); +static void diff_print_stats(git_diff *diff, struct diff_options *o); +static void compute_diff_no_index(git_diff **diff, struct diff_options *o); + +int lg2_diff(git_repository *repo, int argc, char *argv[]) +{ + git_tree *t1 = NULL, *t2 = NULL; + git_diff *diff; + struct diff_options o = { + GIT_DIFF_OPTIONS_INIT, GIT_DIFF_FIND_OPTIONS_INIT, + -1, -1, 0, 0, GIT_DIFF_FORMAT_PATCH, NULL, NULL, "." + }; + + parse_opts(&o, argc, argv); + + /** + * Possible argument patterns: + * + * * <sha1> <sha2> + * * <sha1> --cached + * * <sha1> + * * --cached + * * --nocache (don't use index data in diff at all) + * * --no-index <file1> <file2> + * * nothing + * + * Currently ranged arguments like <sha1>..<sha2> and <sha1>...<sha2> + * are not supported in this example + */ + + if (o.no_index >= 0) { + compute_diff_no_index(&diff, &o); + } else { + if (o.treeish1) + treeish_to_tree(&t1, repo, o.treeish1); + if (o.treeish2) + treeish_to_tree(&t2, repo, o.treeish2); + + if (t1 && t2) + check_lg2( + git_diff_tree_to_tree(&diff, repo, t1, t2, &o.diffopts), + "diff trees", NULL); + else if (o.cache != CACHE_NORMAL) { + if (!t1) + treeish_to_tree(&t1, repo, "HEAD"); + + if (o.cache == CACHE_NONE) + check_lg2( + git_diff_tree_to_workdir(&diff, repo, t1, &o.diffopts), + "diff tree to working directory", NULL); + else + check_lg2( + git_diff_tree_to_index(&diff, repo, t1, NULL, &o.diffopts), + "diff tree to index", NULL); + } + else if (t1) + check_lg2( + git_diff_tree_to_workdir_with_index(&diff, repo, t1, &o.diffopts), + "diff tree to working directory", NULL); + else + check_lg2( + git_diff_index_to_workdir(&diff, repo, NULL, &o.diffopts), + "diff index to working directory", NULL); + + /** Apply rename and copy detection if requested. */ + + if ((o.findopts.flags & GIT_DIFF_FIND_ALL) != 0) + check_lg2( + git_diff_find_similar(diff, &o.findopts), + "finding renames and copies", NULL); + } + + /** Generate simple output using libgit2 display helper. */ + + if (!o.output) + o.output = OUTPUT_DIFF; + + if (o.output != OUTPUT_DIFF) + diff_print_stats(diff, &o); + + if ((o.output & OUTPUT_DIFF) != 0) { + if (o.color >= 0) + fputs(colors[0], stdout); + + check_lg2( + git_diff_print(diff, o.format, color_printer, &o.color), + "displaying diff", NULL); + + if (o.color >= 0) + fputs(colors[0], stdout); + } + + /** Cleanup before exiting. */ + git_diff_free(diff); + git_tree_free(t1); + git_tree_free(t2); + + return 0; +} + +static void compute_diff_no_index(git_diff **diff, struct diff_options *o) { + git_patch *patch = NULL; + char *file1_str = NULL; + char *file2_str = NULL; + git_buf buf = {0}; + + if (!o->treeish1 || !o->treeish2) { + usage("two files should be provided as arguments", NULL); + } + file1_str = read_file(o->treeish1); + if (file1_str == NULL) { + usage("file cannot be read", o->treeish1); + } + file2_str = read_file(o->treeish2); + if (file2_str == NULL) { + usage("file cannot be read", o->treeish2); + } + check_lg2( + git_patch_from_buffers(&patch, file1_str, strlen(file1_str), o->treeish1, file2_str, strlen(file2_str), o->treeish2, &o->diffopts), + "patch buffers", NULL); + check_lg2( + git_patch_to_buf(&buf, patch), + "patch to buf", NULL); + +#ifdef GIT_EXPERIMENTAL_SHA256 + check_lg2( + git_diff_from_buffer(diff, buf.ptr, buf.size, NULL), + "diff from patch", NULL); +#else + check_lg2( + git_diff_from_buffer(diff, buf.ptr, buf.size), + "diff from patch", NULL); +#endif + + git_patch_free(patch); + git_buf_dispose(&buf); + free(file1_str); + free(file2_str); +} + +static void usage(const char *message, const char *arg) +{ + if (message && arg) + fprintf(stderr, "%s: %s\n", message, arg); + else if (message) + fprintf(stderr, "%s\n", message); + fprintf(stderr, "usage: diff [<tree-oid> [<tree-oid>]]\n"); + exit(1); +} + +/** This implements very rudimentary colorized output. */ +static int color_printer( + const git_diff_delta *delta, + const git_diff_hunk *hunk, + const git_diff_line *line, + void *data) +{ + int *last_color = data, color = 0; + + (void)delta; (void)hunk; + + if (*last_color >= 0) { + switch (line->origin) { + case GIT_DIFF_LINE_ADDITION: color = 3; break; + case GIT_DIFF_LINE_DELETION: color = 2; break; + case GIT_DIFF_LINE_ADD_EOFNL: color = 3; break; + case GIT_DIFF_LINE_DEL_EOFNL: color = 2; break; + case GIT_DIFF_LINE_FILE_HDR: color = 1; break; + case GIT_DIFF_LINE_HUNK_HDR: color = 4; break; + default: break; + } + + if (color != *last_color) { + if (*last_color == 1 || color == 1) + fputs(colors[0], stdout); + fputs(colors[color], stdout); + *last_color = color; + } + } + + return diff_output(delta, hunk, line, stdout); +} + +/** Parse arguments as copied from git-diff. */ +static void parse_opts(struct diff_options *o, int argc, char *argv[]) +{ + struct args_info args = ARGS_INFO_INIT; + + for (args.pos = 1; args.pos < argc; ++args.pos) { + const char *a = argv[args.pos]; + + if (a[0] != '-') { + if (o->treeish1 == NULL) + o->treeish1 = a; + else if (o->treeish2 == NULL) + o->treeish2 = a; + else + usage("Only one or two tree identifiers can be provided", NULL); + } + else if (!strcmp(a, "-p") || !strcmp(a, "-u") || + !strcmp(a, "--patch")) { + o->output |= OUTPUT_DIFF; + o->format = GIT_DIFF_FORMAT_PATCH; + } + else if (!strcmp(a, "--cached")) { + o->cache = CACHE_ONLY; + if (o->no_index >= 0) usage("--cached and --no-index are incompatible", NULL); + } else if (!strcmp(a, "--nocache")) + o->cache = CACHE_NONE; + else if (!strcmp(a, "--name-only") || !strcmp(a, "--format=name")) + o->format = GIT_DIFF_FORMAT_NAME_ONLY; + else if (!strcmp(a, "--name-status") || + !strcmp(a, "--format=name-status")) + o->format = GIT_DIFF_FORMAT_NAME_STATUS; + else if (!strcmp(a, "--raw") || !strcmp(a, "--format=raw")) + o->format = GIT_DIFF_FORMAT_RAW; + else if (!strcmp(a, "--format=diff-index")) { + o->format = GIT_DIFF_FORMAT_RAW; + o->diffopts.id_abbrev = 40; + } + else if (!strcmp(a, "--no-index")) { + o->no_index = 0; + if (o->cache == CACHE_ONLY) usage("--cached and --no-index are incompatible", NULL); + } else if (!strcmp(a, "--color")) + o->color = 0; + else if (!strcmp(a, "--no-color")) + o->color = -1; + else if (!strcmp(a, "-R")) + o->diffopts.flags |= GIT_DIFF_REVERSE; + else if (!strcmp(a, "-a") || !strcmp(a, "--text")) + o->diffopts.flags |= GIT_DIFF_FORCE_TEXT; + else if (!strcmp(a, "--ignore-space-at-eol")) + o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE_EOL; + else if (!strcmp(a, "-b") || !strcmp(a, "--ignore-space-change")) + o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE_CHANGE; + else if (!strcmp(a, "-w") || !strcmp(a, "--ignore-all-space")) + o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE; + else if (!strcmp(a, "--ignored")) + o->diffopts.flags |= GIT_DIFF_INCLUDE_IGNORED; + else if (!strcmp(a, "--untracked")) + o->diffopts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; + else if (!strcmp(a, "--patience")) + o->diffopts.flags |= GIT_DIFF_PATIENCE; + else if (!strcmp(a, "--minimal")) + o->diffopts.flags |= GIT_DIFF_MINIMAL; + else if (!strcmp(a, "--stat")) + o->output |= OUTPUT_STAT; + else if (!strcmp(a, "--numstat")) + o->output |= OUTPUT_NUMSTAT; + else if (!strcmp(a, "--shortstat")) + o->output |= OUTPUT_SHORTSTAT; + else if (!strcmp(a, "--summary")) + o->output |= OUTPUT_SUMMARY; + else if (match_uint16_arg( + &o->findopts.rename_threshold, &args, "-M") || + match_uint16_arg( + &o->findopts.rename_threshold, &args, "--find-renames")) + o->findopts.flags |= GIT_DIFF_FIND_RENAMES; + else if (match_uint16_arg( + &o->findopts.copy_threshold, &args, "-C") || + match_uint16_arg( + &o->findopts.copy_threshold, &args, "--find-copies")) + o->findopts.flags |= GIT_DIFF_FIND_COPIES; + else if (!strcmp(a, "--find-copies-harder")) + o->findopts.flags |= GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED; + else if (is_prefixed(a, "-B") || is_prefixed(a, "--break-rewrites")) + /* TODO: parse thresholds */ + o->findopts.flags |= GIT_DIFF_FIND_REWRITES; + else if (!match_uint32_arg( + &o->diffopts.context_lines, &args, "-U") && + !match_uint32_arg( + &o->diffopts.context_lines, &args, "--unified") && + !match_uint32_arg( + &o->diffopts.interhunk_lines, &args, "--inter-hunk-context") && + !match_uint16_arg( + &o->diffopts.id_abbrev, &args, "--abbrev") && + !match_str_arg(&o->diffopts.old_prefix, &args, "--src-prefix") && + !match_str_arg(&o->diffopts.new_prefix, &args, "--dst-prefix") && + !match_str_arg(&o->dir, &args, "--git-dir")) + usage("Unknown command line argument", a); + } +} + +/** Display diff output with "--stat", "--numstat", or "--shortstat" */ +static void diff_print_stats(git_diff *diff, struct diff_options *o) +{ + git_diff_stats *stats; + git_buf b = GIT_BUF_INIT; + git_diff_stats_format_t format = 0; + + check_lg2( + git_diff_get_stats(&stats, diff), "generating stats for diff", NULL); + + if (o->output & OUTPUT_STAT) + format |= GIT_DIFF_STATS_FULL; + if (o->output & OUTPUT_SHORTSTAT) + format |= GIT_DIFF_STATS_SHORT; + if (o->output & OUTPUT_NUMSTAT) + format |= GIT_DIFF_STATS_NUMBER; + if (o->output & OUTPUT_SUMMARY) + format |= GIT_DIFF_STATS_INCLUDE_SUMMARY; + + check_lg2( + git_diff_stats_to_buf(&b, stats, format, 80), "formatting stats", NULL); + + fputs(b.ptr, stdout); + + git_buf_dispose(&b); + git_diff_stats_free(stats); +} diff --git a/examples/fetch.c b/examples/fetch.c new file mode 100644 index 0000000..bbd882c --- /dev/null +++ b/examples/fetch.c @@ -0,0 +1,109 @@ +#include "common.h" + +static int progress_cb(const char *str, int len, void *data) +{ + (void)data; + printf("remote: %.*s", len, str); + fflush(stdout); /* We don't have the \n to force the flush */ + return 0; +} + +/** + * This function gets called for each remote-tracking branch that gets + * updated. The message we output depends on whether it's a new one or + * an update. + */ +static int update_cb(const char *refname, const git_oid *a, const git_oid *b, void *data) +{ + char a_str[GIT_OID_SHA1_HEXSIZE+1], b_str[GIT_OID_SHA1_HEXSIZE+1]; + (void)data; + + git_oid_fmt(b_str, b); + b_str[GIT_OID_SHA1_HEXSIZE] = '\0'; + + if (git_oid_is_zero(a)) { + printf("[new] %.20s %s\n", b_str, refname); + } else { + git_oid_fmt(a_str, a); + a_str[GIT_OID_SHA1_HEXSIZE] = '\0'; + printf("[updated] %.10s..%.10s %s\n", a_str, b_str, refname); + } + + return 0; +} + +/** + * This gets called during the download and indexing. Here we show + * processed and total objects in the pack and the amount of received + * data. Most frontends will probably want to show a percentage and + * the download rate. + */ +static int transfer_progress_cb(const git_indexer_progress *stats, void *payload) +{ + (void)payload; + + if (stats->received_objects == stats->total_objects) { + printf("Resolving deltas %u/%u\r", + stats->indexed_deltas, stats->total_deltas); + } else if (stats->total_objects > 0) { + printf("Received %u/%u objects (%u) in %" PRIuZ " bytes\r", + stats->received_objects, stats->total_objects, + stats->indexed_objects, stats->received_bytes); + } + return 0; +} + +/** Entry point for this command */ +int lg2_fetch(git_repository *repo, int argc, char **argv) +{ + git_remote *remote = NULL; + const git_indexer_progress *stats; + git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT; + + if (argc < 2) { + fprintf(stderr, "usage: %s fetch <repo>\n", argv[-1]); + return EXIT_FAILURE; + } + + /* Figure out whether it's a named remote or a URL */ + printf("Fetching %s for repo %p\n", argv[1], repo); + if (git_remote_lookup(&remote, repo, argv[1]) < 0) + if (git_remote_create_anonymous(&remote, repo, argv[1]) < 0) + goto on_error; + + /* Set up the callbacks (only update_tips for now) */ + fetch_opts.callbacks.update_tips = &update_cb; + fetch_opts.callbacks.sideband_progress = &progress_cb; + fetch_opts.callbacks.transfer_progress = transfer_progress_cb; + fetch_opts.callbacks.credentials = cred_acquire_cb; + + /** + * Perform the fetch with the configured refspecs from the + * config. Update the reflog for the updated references with + * "fetch". + */ + if (git_remote_fetch(remote, NULL, &fetch_opts, "fetch") < 0) + goto on_error; + + /** + * If there are local objects (we got a thin pack), then tell + * the user how many objects we saved from having to cross the + * network. + */ + stats = git_remote_stats(remote); + if (stats->local_objects > 0) { + printf("\rReceived %u/%u objects in %" PRIuZ " bytes (used %u local objects)\n", + stats->indexed_objects, stats->total_objects, stats->received_bytes, stats->local_objects); + } else{ + printf("\rReceived %u/%u objects in %" PRIuZ "bytes\n", + stats->indexed_objects, stats->total_objects, stats->received_bytes); + } + + git_remote_free(remote); + + return 0; + + on_error: + git_remote_free(remote); + return -1; +} diff --git a/examples/for-each-ref.c b/examples/for-each-ref.c new file mode 100644 index 0000000..a070674 --- /dev/null +++ b/examples/for-each-ref.c @@ -0,0 +1,44 @@ +#include <git2.h> +#include "common.h" + +static int show_ref(git_reference *ref, void *data) +{ + git_repository *repo = data; + git_reference *resolved = NULL; + char hex[GIT_OID_SHA1_HEXSIZE+1]; + const git_oid *oid; + git_object *obj; + + if (git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) + check_lg2(git_reference_resolve(&resolved, ref), + "Unable to resolve symbolic reference", + git_reference_name(ref)); + + oid = git_reference_target(resolved ? resolved : ref); + git_oid_fmt(hex, oid); + hex[GIT_OID_SHA1_HEXSIZE] = 0; + check_lg2(git_object_lookup(&obj, repo, oid, GIT_OBJECT_ANY), + "Unable to lookup object", hex); + + printf("%s %-6s\t%s\n", + hex, + git_object_type2string(git_object_type(obj)), + git_reference_name(ref)); + + if (resolved) + git_reference_free(resolved); + return 0; +} + +int lg2_for_each_ref(git_repository *repo, int argc, char **argv) +{ + UNUSED(argv); + + if (argc != 1) + fatal("Sorry, no for-each-ref options supported yet", NULL); + + check_lg2(git_reference_foreach(repo, show_ref, repo), + "Could not iterate over references", NULL); + + return 0; +} diff --git a/examples/general.c b/examples/general.c new file mode 100644 index 0000000..7f44cd7 --- /dev/null +++ b/examples/general.c @@ -0,0 +1,798 @@ +/* + * libgit2 "general" example - shows basic libgit2 concepts + * + * 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/>. + */ + +/** + * [**libgit2**][lg] is a portable, pure C implementation of the Git core + * methods provided as a re-entrant linkable library with a solid API, + * allowing you to write native speed custom Git applications in any + * language which supports C bindings. + * + * This file is an example of using that API in a real, compilable C file. + * As the API is updated, this file will be updated to demonstrate the new + * functionality. + * + * If you're trying to write something in C using [libgit2][lg], you should + * also check out the generated [API documentation][ap]. We try to link to + * the relevant sections of the API docs in each section in this file. + * + * **libgit2** (for the most part) only implements the core plumbing + * functions, not really the higher level porcelain stuff. For a primer on + * Git Internals that you will need to know to work with Git at this level, + * check out [Chapter 10][pg] of the Pro Git book. + * + * [lg]: http://libgit2.github.com + * [ap]: http://libgit2.github.com/libgit2 + * [pg]: https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Porcelain + */ + +#include "common.h" + +/** + * ### Includes + * + * Including the `git2.h` header will include all the other libgit2 headers + * that you need. It should be the only thing you need to include in order + * to compile properly and get all the libgit2 API. + */ +#include "git2.h" + +static void oid_parsing(git_oid *out); +static void object_database(git_repository *repo, git_oid *oid); +static void commit_writing(git_repository *repo); +static void commit_parsing(git_repository *repo); +static void tag_parsing(git_repository *repo); +static void tree_parsing(git_repository *repo); +static void blob_parsing(git_repository *repo); +static void revwalking(git_repository *repo); +static void index_walking(git_repository *repo); +static void reference_listing(git_repository *repo); +static void config_files(const char *repo_path, git_repository *repo); + +/** + * Almost all libgit2 functions return 0 on success or negative on error. + * This is not production quality error checking, but should be sufficient + * as an example. + */ +static void check_error(int error_code, const char *action) +{ + const git_error *error = git_error_last(); + if (!error_code) + return; + + printf("Error %d %s - %s\n", error_code, action, + (error && error->message) ? error->message : "???"); + + exit(1); +} + +int lg2_general(git_repository *repo, int argc, char** argv) +{ + int error; + git_oid oid; + char *repo_path; + + /** + * Initialize the library, this will set up any global state which libgit2 needs + * including threading and crypto + */ + git_libgit2_init(); + + /** + * ### Opening the Repository + * + * There are a couple of methods for opening a repository, this being the + * simplest. There are also [methods][me] for specifying the index file + * and work tree locations, here we assume they are in the normal places. + * + * (Try running this program against tests/resources/testrepo.git.) + * + * [me]: http://libgit2.github.com/libgit2/#HEAD/group/repository + */ + repo_path = (argc > 1) ? argv[1] : "/opt/libgit2-test/.git"; + + error = git_repository_open(&repo, repo_path); + check_error(error, "opening repository"); + + oid_parsing(&oid); + object_database(repo, &oid); + commit_writing(repo); + commit_parsing(repo); + tag_parsing(repo); + tree_parsing(repo); + blob_parsing(repo); + revwalking(repo); + index_walking(repo); + reference_listing(repo); + config_files(repo_path, repo); + + /** + * Finally, when you're done with the repository, you can free it as well. + */ + git_repository_free(repo); + + return 0; +} + +/** + * ### SHA-1 Value Conversions + */ +static void oid_parsing(git_oid *oid) +{ + char out[GIT_OID_SHA1_HEXSIZE+1]; + char hex[] = "4a202b346bb0fb0db7eff3cffeb3c70babbd2045"; + + printf("*Hex to Raw*\n"); + + /** + * For our first example, we will convert a 40 character hex value to the + * 20 byte raw SHA1 value. + * + * The `git_oid` is the structure that keeps the SHA value. We will use + * this throughout the example for storing the value of the current SHA + * key we're working with. + */ +#ifdef GIT_EXPERIMENTAL_SHA256 + git_oid_fromstr(oid, hex, GIT_OID_SHA1); +#else + git_oid_fromstr(oid, hex); +#endif + + /* + * Once we've converted the string into the oid value, we can get the raw + * value of the SHA by accessing `oid.id` + * + * Next we will convert the 20 byte raw SHA1 value to a human readable 40 + * char hex value. + */ + printf("\n*Raw to Hex*\n"); + out[GIT_OID_SHA1_HEXSIZE] = '\0'; + + /** + * If you have a oid, you can easily get the hex value of the SHA as well. + */ + git_oid_fmt(out, oid); + printf("SHA hex string: %s\n", out); +} + +/** + * ### Working with the Object Database + * + * **libgit2** provides [direct access][odb] to the object database. The + * object database is where the actual objects are stored in Git. For + * working with raw objects, we'll need to get this structure from the + * repository. + * + * [odb]: http://libgit2.github.com/libgit2/#HEAD/group/odb + */ +static void object_database(git_repository *repo, git_oid *oid) +{ + char oid_hex[GIT_OID_SHA1_HEXSIZE+1] = { 0 }; + const unsigned char *data; + const char *str_type; + int error; + git_odb_object *obj; + git_odb *odb; + git_object_t otype; + + git_repository_odb(&odb, repo); + + /** + * #### Raw Object Reading + */ + + printf("\n*Raw Object Read*\n"); + + /** + * We can read raw objects directly from the object database if we have + * the oid (SHA) of the object. This allows us to access objects without + * knowing their type and inspect the raw bytes unparsed. + */ + error = git_odb_read(&obj, odb, oid); + check_error(error, "finding object in repository"); + + /** + * A raw object only has three properties - the type (commit, blob, tree + * or tag), the size of the raw data and the raw, unparsed data itself. + * For a commit or tag, that raw data is human readable plain ASCII + * text. For a blob it is just file contents, so it could be text or + * binary data. For a tree it is a special binary format, so it's unlikely + * to be hugely helpful as a raw object. + */ + data = (const unsigned char *)git_odb_object_data(obj); + otype = git_odb_object_type(obj); + + /** + * We provide methods to convert from the object type which is an enum, to + * a string representation of that value (and vice-versa). + */ + str_type = git_object_type2string(otype); + printf("object length and type: %d, %s\nobject data: %s\n", + (int)git_odb_object_size(obj), + str_type, data); + + /** + * For proper memory management, close the object when you are done with + * it or it will leak memory. + */ + git_odb_object_free(obj); + + /** + * #### Raw Object Writing + */ + + printf("\n*Raw Object Write*\n"); + + /** + * You can also write raw object data to Git. This is pretty cool because + * it gives you direct access to the key/value properties of Git. Here + * we'll write a new blob object that just contains a simple string. + * Notice that we have to specify the object type as the `git_otype` enum. + */ + git_odb_write(oid, odb, "test data", sizeof("test data") - 1, GIT_OBJECT_BLOB); + + /** + * Now that we've written the object, we can check out what SHA1 was + * generated when the object was written to our database. + */ + git_oid_fmt(oid_hex, oid); + printf("Written Object: %s\n", oid_hex); + + /** + * Free the object database after usage. + */ + git_odb_free(odb); +} + +/** + * #### Writing Commits + * + * libgit2 provides a couple of methods to create commit objects easily as + * well. There are four different create signatures, we'll just show one + * of them here. You can read about the other ones in the [commit API + * docs][cd]. + * + * [cd]: http://libgit2.github.com/libgit2/#HEAD/group/commit + */ +static void commit_writing(git_repository *repo) +{ + git_oid tree_id, parent_id, commit_id; + git_tree *tree; + git_commit *parent; + git_signature *author, *committer; + char oid_hex[GIT_OID_SHA1_HEXSIZE+1] = { 0 }; + + printf("\n*Commit Writing*\n"); + + /** + * Creating signatures for an authoring identity and time is simple. You + * will need to do this to specify who created a commit and when. Default + * values for the name and email should be found in the `user.name` and + * `user.email` configuration options. See the `config` section of this + * example file to see how to access config values. + */ + git_signature_new(&author, + "Scott Chacon", "schacon@gmail.com", 123456789, 60); + git_signature_new(&committer, + "Scott A Chacon", "scott@github.com", 987654321, 90); + + /** + * Commit objects need a tree to point to and optionally one or more + * parents. Here we're creating oid objects to create the commit with, + * but you can also use + */ +#ifdef GIT_EXPERIMENTAL_SHA256 + git_oid_fromstr(&tree_id, "f60079018b664e4e79329a7ef9559c8d9e0378d1", GIT_OID_SHA1); + git_oid_fromstr(&parent_id, "5b5b025afb0b4c913b4c338a42934a3863bf3644", GIT_OID_SHA1); +#else + git_oid_fromstr(&tree_id, "f60079018b664e4e79329a7ef9559c8d9e0378d1"); + git_oid_fromstr(&parent_id, "5b5b025afb0b4c913b4c338a42934a3863bf3644"); +#endif + git_tree_lookup(&tree, repo, &tree_id); + git_commit_lookup(&parent, repo, &parent_id); + + /** + * Here we actually create the commit object with a single call with all + * the values we need to create the commit. The SHA key is written to the + * `commit_id` variable here. + */ + git_commit_create_v( + &commit_id, /* out id */ + repo, + NULL, /* do not update the HEAD */ + author, + committer, + NULL, /* use default message encoding */ + "example commit", + tree, + 1, parent); + + /** + * Now we can take a look at the commit SHA we've generated. + */ + git_oid_fmt(oid_hex, &commit_id); + printf("New Commit: %s\n", oid_hex); + + /** + * Free all objects used in the meanwhile. + */ + git_tree_free(tree); + git_commit_free(parent); + git_signature_free(author); + git_signature_free(committer); +} + +/** + * ### Object Parsing + * + * libgit2 has methods to parse every object type in Git so you don't have + * to work directly with the raw data. This is much faster and simpler + * than trying to deal with the raw data yourself. + */ + +/** + * #### Commit Parsing + * + * [Parsing commit objects][pco] is simple and gives you access to all the + * data in the commit - the author (name, email, datetime), committer + * (same), tree, message, encoding and parent(s). + * + * [pco]: http://libgit2.github.com/libgit2/#HEAD/group/commit + */ +static void commit_parsing(git_repository *repo) +{ + const git_signature *author, *cmtter; + git_commit *commit, *parent; + git_oid oid; + char oid_hex[GIT_OID_SHA1_HEXSIZE+1]; + const char *message; + unsigned int parents, p; + int error; + time_t time; + + printf("\n*Commit Parsing*\n"); + +#ifdef GIT_EXPERIMENTAL_SHA256 + git_oid_fromstr(&oid, "8496071c1b46c854b31185ea97743be6a8774479", GIT_OID_SHA1); +#else + git_oid_fromstr(&oid, "8496071c1b46c854b31185ea97743be6a8774479"); +#endif + + error = git_commit_lookup(&commit, repo, &oid); + check_error(error, "looking up commit"); + + /** + * Each of the properties of the commit object are accessible via methods, + * including commonly needed variations, such as `git_commit_time` which + * returns the author time and `git_commit_message` which gives you the + * commit message (as a NUL-terminated string). + */ + message = git_commit_message(commit); + author = git_commit_author(commit); + cmtter = git_commit_committer(commit); + time = git_commit_time(commit); + + /** + * The author and committer methods return [git_signature] structures, + * which give you name, email and `when`, which is a `git_time` structure, + * giving you a timestamp and timezone offset. + */ + printf("Author: %s (%s)\nCommitter: %s (%s)\nDate: %s\nMessage: %s\n", + author->name, author->email, + cmtter->name, cmtter->email, + ctime(&time), message); + + /** + * Commits can have zero or more parents. The first (root) commit will + * have no parents, most commits will have one (i.e. the commit it was + * based on) and merge commits will have two or more. Commits can + * technically have any number, though it's rare to have more than two. + */ + parents = git_commit_parentcount(commit); + for (p = 0;p < parents;p++) { + memset(oid_hex, 0, sizeof(oid_hex)); + + git_commit_parent(&parent, commit, p); + git_oid_fmt(oid_hex, git_commit_id(parent)); + printf("Parent: %s\n", oid_hex); + git_commit_free(parent); + } + + git_commit_free(commit); +} + +/** + * #### Tag Parsing + * + * You can parse and create tags with the [tag management API][tm], which + * functions very similarly to the commit lookup, parsing and creation + * methods, since the objects themselves are very similar. + * + * [tm]: http://libgit2.github.com/libgit2/#HEAD/group/tag + */ +static void tag_parsing(git_repository *repo) +{ + git_commit *commit; + git_object_t type; + git_tag *tag; + git_oid oid; + const char *name, *message; + int error; + + printf("\n*Tag Parsing*\n"); + + /** + * We create an oid for the tag object if we know the SHA and look it up + * the same way that we would a commit (or any other object). + */ +#ifdef GIT_EXPERIMENTAL_SHA256 + git_oid_fromstr(&oid, "b25fa35b38051e4ae45d4222e795f9df2e43f1d1", GIT_OID_SHA1); +#else + git_oid_fromstr(&oid, "b25fa35b38051e4ae45d4222e795f9df2e43f1d1"); +#endif + + error = git_tag_lookup(&tag, repo, &oid); + check_error(error, "looking up tag"); + + /** + * Now that we have the tag object, we can extract the information it + * generally contains: the target (usually a commit object), the type of + * the target object (usually 'commit'), the name ('v1.0'), the tagger (a + * git_signature - name, email, timestamp), and the tag message. + */ + git_tag_target((git_object **)&commit, tag); + name = git_tag_name(tag); /* "test" */ + type = git_tag_target_type(tag); /* GIT_OBJECT_COMMIT (object_t enum) */ + message = git_tag_message(tag); /* "tag message\n" */ + printf("Tag Name: %s\nTag Type: %s\nTag Message: %s\n", + name, git_object_type2string(type), message); + + /** + * Free both the commit and tag after usage. + */ + git_commit_free(commit); + git_tag_free(tag); +} + +/** + * #### Tree Parsing + * + * [Tree parsing][tp] is a bit different than the other objects, in that + * we have a subtype which is the tree entry. This is not an actual + * object type in Git, but a useful structure for parsing and traversing + * tree entries. + * + * [tp]: http://libgit2.github.com/libgit2/#HEAD/group/tree + */ +static void tree_parsing(git_repository *repo) +{ + const git_tree_entry *entry; + size_t cnt; + git_object *obj; + git_tree *tree; + git_oid oid; + + printf("\n*Tree Parsing*\n"); + + /** + * Create the oid and lookup the tree object just like the other objects. + */ +#ifdef GIT_EXPERIMENTAL_SHA256 + git_oid_fromstr(&oid, "f60079018b664e4e79329a7ef9559c8d9e0378d1", GIT_OID_SHA1); +#else + git_oid_fromstr(&oid, "f60079018b664e4e79329a7ef9559c8d9e0378d1"); +#endif + git_tree_lookup(&tree, repo, &oid); + + /** + * Getting the count of entries in the tree so you can iterate over them + * if you want to. + */ + cnt = git_tree_entrycount(tree); /* 2 */ + printf("tree entries: %d\n", (int) cnt); + + entry = git_tree_entry_byindex(tree, 0); + printf("Entry name: %s\n", git_tree_entry_name(entry)); /* "README" */ + + /** + * You can also access tree entries by name if you know the name of the + * entry you're looking for. + */ + entry = git_tree_entry_byname(tree, "README"); + git_tree_entry_name(entry); /* "README" */ + + /** + * Once you have the entry object, you can access the content or subtree + * (or commit, in the case of submodules) that it points to. You can also + * get the mode if you want. + */ + git_tree_entry_to_object(&obj, repo, entry); /* blob */ + + /** + * Remember to close the looked-up object and tree once you are done using it + */ + git_object_free(obj); + git_tree_free(tree); +} + +/** + * #### Blob Parsing + * + * The last object type is the simplest and requires the least parsing + * help. Blobs are just file contents and can contain anything, there is + * no structure to it. The main advantage to using the [simple blob + * api][ba] is that when you're creating blobs you don't have to calculate + * the size of the content. There is also a helper for reading a file + * from disk and writing it to the db and getting the oid back so you + * don't have to do all those steps yourself. + * + * [ba]: http://libgit2.github.com/libgit2/#HEAD/group/blob + */ +static void blob_parsing(git_repository *repo) +{ + git_blob *blob; + git_oid oid; + + printf("\n*Blob Parsing*\n"); + +#ifdef GIT_EXPERIMENTAL_SHA256 + git_oid_fromstr(&oid, "1385f264afb75a56a5bec74243be9b367ba4ca08", GIT_OID_SHA1); +#else + git_oid_fromstr(&oid, "1385f264afb75a56a5bec74243be9b367ba4ca08"); +#endif + git_blob_lookup(&blob, repo, &oid); + + /** + * You can access a buffer with the raw contents of the blob directly. + * Note that this buffer may not be contain ASCII data for certain blobs + * (e.g. binary files): do not consider the buffer a NULL-terminated + * string, and use the `git_blob_rawsize` attribute to find out its exact + * size in bytes + * */ + printf("Blob Size: %ld\n", (long)git_blob_rawsize(blob)); /* 8 */ + git_blob_rawcontent(blob); /* "content" */ + + /** + * Free the blob after usage. + */ + git_blob_free(blob); +} + +/** + * ### Revwalking + * + * The libgit2 [revision walking api][rw] provides methods to traverse the + * directed graph created by the parent pointers of the commit objects. + * Since all commits point back to the commit that came directly before + * them, you can walk this parentage as a graph and find all the commits + * that were ancestors of (reachable from) a given starting point. This + * can allow you to create `git log` type functionality. + * + * [rw]: http://libgit2.github.com/libgit2/#HEAD/group/revwalk + */ +static void revwalking(git_repository *repo) +{ + const git_signature *cauth; + const char *cmsg; + int error; + git_revwalk *walk; + git_commit *wcommit; + git_oid oid; + + printf("\n*Revwalking*\n"); + +#ifdef GIT_EXPERIMENTAL_SHA256 + git_oid_fromstr(&oid, "5b5b025afb0b4c913b4c338a42934a3863bf3644", GIT_OID_SHA1); +#else + git_oid_fromstr(&oid, "5b5b025afb0b4c913b4c338a42934a3863bf3644"); +#endif + + /** + * To use the revwalker, create a new walker, tell it how you want to sort + * the output and then push one or more starting points onto the walker. + * If you want to emulate the output of `git log` you would push the SHA + * of the commit that HEAD points to into the walker and then start + * traversing them. You can also 'hide' commits that you want to stop at + * or not see any of their ancestors. So if you want to emulate `git log + * branch1..branch2`, you would push the oid of `branch2` and hide the oid + * of `branch1`. + */ + git_revwalk_new(&walk, repo); + git_revwalk_sorting(walk, GIT_SORT_TOPOLOGICAL | GIT_SORT_REVERSE); + git_revwalk_push(walk, &oid); + + /** + * Now that we have the starting point pushed onto the walker, we start + * asking for ancestors. It will return them in the sorting order we asked + * for as commit oids. We can then lookup and parse the committed pointed + * at by the returned OID; note that this operation is specially fast + * since the raw contents of the commit object will be cached in memory + */ + while ((git_revwalk_next(&oid, walk)) == 0) { + error = git_commit_lookup(&wcommit, repo, &oid); + check_error(error, "looking up commit during revwalk"); + + cmsg = git_commit_message(wcommit); + cauth = git_commit_author(wcommit); + printf("%s (%s)\n", cmsg, cauth->email); + + git_commit_free(wcommit); + } + + /** + * Like the other objects, be sure to free the revwalker when you're done + * to prevent memory leaks. Also, make sure that the repository being + * walked it not deallocated while the walk is in progress, or it will + * result in undefined behavior + */ + git_revwalk_free(walk); +} + +/** + * ### Index File Manipulation * + * The [index file API][gi] allows you to read, traverse, update and write + * the Git index file (sometimes thought of as the staging area). + * + * [gi]: http://libgit2.github.com/libgit2/#HEAD/group/index + */ +static void index_walking(git_repository *repo) +{ + git_index *index; + size_t i, ecount; + + printf("\n*Index Walking*\n"); + + /** + * You can either open the index from the standard location in an open + * repository, as we're doing here, or you can open and manipulate any + * index file with `git_index_open_bare()`. The index for the repository + * will be located and loaded from disk. + */ + git_repository_index(&index, repo); + + /** + * For each entry in the index, you can get a bunch of information + * including the SHA (oid), path and mode which map to the tree objects + * that are written out. It also has filesystem properties to help + * determine what to inspect for changes (ctime, mtime, dev, ino, uid, + * gid, file_size and flags) All these properties are exported publicly in + * the `git_index_entry` struct + */ + ecount = git_index_entrycount(index); + for (i = 0; i < ecount; ++i) { + const git_index_entry *e = git_index_get_byindex(index, i); + + printf("path: %s\n", e->path); + printf("mtime: %d\n", (int)e->mtime.seconds); + printf("fs: %d\n", (int)e->file_size); + } + + git_index_free(index); +} + +/** + * ### References + * + * The [reference API][ref] allows you to list, resolve, create and update + * references such as branches, tags and remote references (everything in + * the .git/refs directory). + * + * [ref]: http://libgit2.github.com/libgit2/#HEAD/group/reference + */ +static void reference_listing(git_repository *repo) +{ + git_strarray ref_list; + unsigned i; + + printf("\n*Reference Listing*\n"); + + /** + * Here we will implement something like `git for-each-ref` simply listing + * out all available references and the object SHA they resolve to. + * + * Now that we have the list of reference names, we can lookup each ref + * one at a time and resolve them to the SHA, then print both values out. + */ + + git_reference_list(&ref_list, repo); + + for (i = 0; i < ref_list.count; ++i) { + git_reference *ref; + char oid_hex[GIT_OID_SHA1_HEXSIZE+1] = GIT_OID_SHA1_HEXZERO; + const char *refname; + + refname = ref_list.strings[i]; + git_reference_lookup(&ref, repo, refname); + + switch (git_reference_type(ref)) { + case GIT_REFERENCE_DIRECT: + git_oid_fmt(oid_hex, git_reference_target(ref)); + printf("%s [%s]\n", refname, oid_hex); + break; + + case GIT_REFERENCE_SYMBOLIC: + printf("%s => %s\n", refname, git_reference_symbolic_target(ref)); + break; + default: + fprintf(stderr, "Unexpected reference type\n"); + exit(1); + } + + git_reference_free(ref); + } + + git_strarray_dispose(&ref_list); +} + +/** + * ### Config Files + * + * The [config API][config] allows you to list and update config values + * in any of the accessible config file locations (system, global, local). + * + * [config]: http://libgit2.github.com/libgit2/#HEAD/group/config + */ +static void config_files(const char *repo_path, git_repository* repo) +{ + const char *email; + char config_path[256]; + int32_t autocorrect; + git_config *cfg; + git_config *snap_cfg; + int error_code; + + printf("\n*Config Listing*\n"); + + /** + * Open a config object so we can read global values from it. + */ + sprintf(config_path, "%s/config", repo_path); + check_error(git_config_open_ondisk(&cfg, config_path), "opening config"); + + if (git_config_get_int32(&autocorrect, cfg, "help.autocorrect") == 0) + printf("Autocorrect: %d\n", autocorrect); + + check_error(git_repository_config_snapshot(&snap_cfg, repo), "config snapshot"); + git_config_get_string(&email, snap_cfg, "user.email"); + printf("Email: %s\n", email); + + error_code = git_config_get_int32(&autocorrect, cfg, "help.autocorrect"); + switch (error_code) + { + case 0: + printf("Autocorrect: %d\n", autocorrect); + break; + case GIT_ENOTFOUND: + printf("Autocorrect: Undefined\n"); + break; + default: + check_error(error_code, "get_int32 failed"); + } + git_config_free(cfg); + + check_error(git_repository_config_snapshot(&snap_cfg, repo), "config snapshot"); + error_code = git_config_get_string(&email, snap_cfg, "user.email"); + switch (error_code) + { + case 0: + printf("Email: %s\n", email); + break; + case GIT_ENOTFOUND: + printf("Email: Undefined\n"); + break; + default: + check_error(error_code, "get_string failed"); + } + + git_config_free(snap_cfg); +} diff --git a/examples/index-pack.c b/examples/index-pack.c new file mode 100644 index 0000000..0f8234c --- /dev/null +++ b/examples/index-pack.c @@ -0,0 +1,76 @@ +#include "common.h" + +/* + * This could be run in the main loop whilst the application waits for + * the indexing to finish in a worker thread + */ +static int index_cb(const git_indexer_progress *stats, void *data) +{ + (void)data; + printf("\rProcessing %u of %u", stats->indexed_objects, stats->total_objects); + + return 0; +} + +int lg2_index_pack(git_repository *repo, int argc, char **argv) +{ + git_indexer *idx; + git_indexer_progress stats = {0, 0}; + int error; + int fd; + ssize_t read_bytes; + char buf[512]; + + (void)repo; + + if (argc < 2) { + fprintf(stderr, "usage: %s index-pack <packfile>\n", argv[-1]); + return EXIT_FAILURE; + } + +#ifdef GIT_EXPERIMENTAL_SHA256 + error = git_indexer_new(&idx, ".", git_repository_oid_type(repo), NULL); +#else + error = git_indexer_new(&idx, ".", 0, NULL, NULL); +#endif + + if (error < 0) { + puts("bad idx"); + return -1; + } + + + if ((fd = open(argv[1], 0)) < 0) { + perror("open"); + return -1; + } + + do { + read_bytes = read(fd, buf, sizeof(buf)); + if (read_bytes < 0) + break; + + if ((error = git_indexer_append(idx, buf, read_bytes, &stats)) < 0) + goto cleanup; + + index_cb(&stats, NULL); + } while (read_bytes > 0); + + if (read_bytes < 0) { + error = -1; + perror("failed reading"); + goto cleanup; + } + + if ((error = git_indexer_commit(idx, &stats)) < 0) + goto cleanup; + + printf("\rIndexing %u of %u\n", stats.indexed_objects, stats.total_objects); + + puts(git_indexer_name(idx)); + + cleanup: + close(fd); + git_indexer_free(idx); + return error; +} diff --git a/examples/init.c b/examples/init.c new file mode 100644 index 0000000..2f681c5 --- /dev/null +++ b/examples/init.c @@ -0,0 +1,248 @@ +/* + * libgit2 "init" example - shows how to initialize a new repo + * + * 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" + +/** + * This is a sample program that is similar to "git init". See the + * documentation for that (try "git help init") to understand what this + * program is emulating. + * + * This demonstrates using the libgit2 APIs to initialize a new repository. + * + * This also contains a special additional option that regular "git init" + * does not support which is "--initial-commit" to make a first empty commit. + * That is demonstrated in the "create_initial_commit" helper function. + */ + +/** Forward declarations of helpers */ +struct init_opts { + int no_options; + int quiet; + int bare; + int initial_commit; + uint32_t shared; + const char *template; + const char *gitdir; + const char *dir; +}; +static void create_initial_commit(git_repository *repo); +static void parse_opts(struct init_opts *o, int argc, char *argv[]); + +int lg2_init(git_repository *repo, int argc, char *argv[]) +{ + struct init_opts o = { 1, 0, 0, 0, GIT_REPOSITORY_INIT_SHARED_UMASK, 0, 0, 0 }; + + parse_opts(&o, argc, argv); + + /* Initialize repository. */ + + if (o.no_options) { + /** + * No options were specified, so let's demonstrate the default + * simple case of git_repository_init() API usage... + */ + check_lg2(git_repository_init(&repo, o.dir, 0), + "Could not initialize repository", NULL); + } + else { + /** + * Some command line options were specified, so we'll use the + * extended init API to handle them + */ + git_repository_init_options initopts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + initopts.flags = GIT_REPOSITORY_INIT_MKPATH; + + if (o.bare) + initopts.flags |= GIT_REPOSITORY_INIT_BARE; + + if (o.template) { + initopts.flags |= GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; + initopts.template_path = o.template; + } + + if (o.gitdir) { + /** + * If you specified a separate git directory, then initialize + * the repository at that path and use the second path as the + * working directory of the repository (with a git-link file) + */ + initopts.workdir_path = o.dir; + o.dir = o.gitdir; + } + + if (o.shared != 0) + initopts.mode = o.shared; + + check_lg2(git_repository_init_ext(&repo, o.dir, &initopts), + "Could not initialize repository", NULL); + } + + /** Print a message to stdout like "git init" does. */ + + if (!o.quiet) { + if (o.bare || o.gitdir) + o.dir = git_repository_path(repo); + else + o.dir = git_repository_workdir(repo); + + printf("Initialized empty Git repository in %s\n", o.dir); + } + + /** + * As an extension to the basic "git init" command, this example + * gives the option to create an empty initial commit. This is + * mostly to demonstrate what it takes to do that, but also some + * people like to have that empty base commit in their repo. + */ + if (o.initial_commit) { + create_initial_commit(repo); + printf("Created empty initial commit\n"); + } + + git_repository_free(repo); + + return 0; +} + +/** + * Unlike regular "git init", this example shows how to create an initial + * empty commit in the repository. This is the helper function that does + * that. + */ +static void create_initial_commit(git_repository *repo) +{ + git_signature *sig; + git_index *index; + git_oid tree_id, commit_id; + git_tree *tree; + + /** First use the config to initialize a commit signature for the user. */ + + if (git_signature_default(&sig, repo) < 0) + fatal("Unable to create a commit signature.", + "Perhaps 'user.name' and 'user.email' are not set"); + + /* Now let's create an empty tree for this commit */ + + if (git_repository_index(&index, repo) < 0) + fatal("Could not open repository index", NULL); + + /** + * Outside of this example, you could call git_index_add_bypath() + * here to put actual files into the index. For our purposes, we'll + * leave it empty for now. + */ + + if (git_index_write_tree(&tree_id, index) < 0) + fatal("Unable to write initial tree from index", NULL); + + git_index_free(index); + + if (git_tree_lookup(&tree, repo, &tree_id) < 0) + fatal("Could not look up initial tree", NULL); + + /** + * Ready to create the initial commit. + * + * Normally creating a commit would involve looking up the current + * HEAD commit and making that be the parent of the initial commit, + * but here this is the first commit so there will be no parent. + */ + + if (git_commit_create_v( + &commit_id, repo, "HEAD", sig, sig, + NULL, "Initial commit", tree, 0) < 0) + fatal("Could not create the initial commit", NULL); + + /** Clean up so we don't leak memory. */ + + git_tree_free(tree); + git_signature_free(sig); +} + +static void usage(const char *error, const char *arg) +{ + fprintf(stderr, "error: %s '%s'\n", error, arg); + fprintf(stderr, + "usage: init [-q | --quiet] [--bare] [--template=<dir>]\n" + " [--shared[=perms]] [--initial-commit]\n" + " [--separate-git-dir] <directory>\n"); + exit(1); +} + +/** Parse the tail of the --shared= argument. */ +static uint32_t parse_shared(const char *shared) +{ + if (!strcmp(shared, "false") || !strcmp(shared, "umask")) + return GIT_REPOSITORY_INIT_SHARED_UMASK; + + else if (!strcmp(shared, "true") || !strcmp(shared, "group")) + return GIT_REPOSITORY_INIT_SHARED_GROUP; + + else if (!strcmp(shared, "all") || !strcmp(shared, "world") || + !strcmp(shared, "everybody")) + return GIT_REPOSITORY_INIT_SHARED_ALL; + + else if (shared[0] == '0') { + long val; + char *end = NULL; + val = strtol(shared + 1, &end, 8); + if (end == shared + 1 || *end != 0) + usage("invalid octal value for --shared", shared); + return (uint32_t)val; + } + + else + usage("unknown value for --shared", shared); + + return 0; +} + +static void parse_opts(struct init_opts *o, int argc, char *argv[]) +{ + struct args_info args = ARGS_INFO_INIT; + const char *sharedarg; + + /** Process arguments. */ + + for (args.pos = 1; args.pos < argc; ++args.pos) { + char *a = argv[args.pos]; + + if (a[0] == '-') + o->no_options = 0; + + if (a[0] != '-') { + if (o->dir != NULL) + usage("extra argument", a); + o->dir = a; + } + else if (!strcmp(a, "-q") || !strcmp(a, "--quiet")) + o->quiet = 1; + else if (!strcmp(a, "--bare")) + o->bare = 1; + else if (!strcmp(a, "--shared")) + o->shared = GIT_REPOSITORY_INIT_SHARED_GROUP; + else if (!strcmp(a, "--initial-commit")) + o->initial_commit = 1; + else if (match_str_arg(&sharedarg, &args, "--shared")) + o->shared = parse_shared(sharedarg); + else if (!match_str_arg(&o->template, &args, "--template") || + !match_str_arg(&o->gitdir, &args, "--separate-git-dir")) + usage("unknown option", a); + } + + if (!o->dir) + usage("must specify directory to init", ""); +} diff --git a/examples/lg2.c b/examples/lg2.c new file mode 100644 index 0000000..7946bc2 --- /dev/null +++ b/examples/lg2.c @@ -0,0 +1,124 @@ +#include "common.h" + +/* This part is not strictly libgit2-dependent, but you can use this + * as a starting point for a git-like tool */ + +typedef int (*git_command_fn)(git_repository *, int , char **); + +struct { + char *name; + git_command_fn fn; + char requires_repo; +} commands[] = { + { "add", lg2_add, 1 }, + { "blame", lg2_blame, 1 }, + { "cat-file", lg2_cat_file, 1 }, + { "checkout", lg2_checkout, 1 }, + { "clone", lg2_clone, 0 }, + { "commit", lg2_commit, 1 }, + { "config", lg2_config, 1 }, + { "describe", lg2_describe, 1 }, + { "diff", lg2_diff, 1 }, + { "fetch", lg2_fetch, 1 }, + { "for-each-ref", lg2_for_each_ref, 1 }, + { "general", lg2_general, 0 }, + { "index-pack", lg2_index_pack, 1 }, + { "init", lg2_init, 0 }, + { "log", lg2_log, 1 }, + { "ls-files", lg2_ls_files, 1 }, + { "ls-remote", lg2_ls_remote, 1 }, + { "merge", lg2_merge, 1 }, + { "push", lg2_push, 1 }, + { "remote", lg2_remote, 1 }, + { "rev-list", lg2_rev_list, 1 }, + { "rev-parse", lg2_rev_parse, 1 }, + { "show-index", lg2_show_index, 0 }, + { "stash", lg2_stash, 1 }, + { "status", lg2_status, 1 }, + { "tag", lg2_tag, 1 }, +}; + +static int run_command(git_command_fn fn, git_repository *repo, struct args_info args) +{ + int error; + + /* Run the command. If something goes wrong, print the error message to stderr */ + error = fn(repo, args.argc - args.pos, &args.argv[args.pos]); + if (error < 0) { + if (git_error_last() == NULL) + fprintf(stderr, "Error without message"); + else + fprintf(stderr, "Bad news:\n %s\n", git_error_last()->message); + } + + return !!error; +} + +static int usage(const char *prog) +{ + size_t i; + + fprintf(stderr, "usage: %s <cmd>...\n\nAvailable commands:\n\n", prog); + for (i = 0; i < ARRAY_SIZE(commands); i++) + fprintf(stderr, "\t%s\n", commands[i].name); + + exit(EXIT_FAILURE); +} + +int main(int argc, char **argv) +{ + struct args_info args = ARGS_INFO_INIT; + git_repository *repo = NULL; + const char *git_dir = NULL; + int return_code = 1; + size_t i; + + if (argc < 2) + usage(argv[0]); + + git_libgit2_init(); + + for (args.pos = 1; args.pos < args.argc; ++args.pos) { + char *a = args.argv[args.pos]; + + if (a[0] != '-') { + /* non-arg */ + break; + } else if (optional_str_arg(&git_dir, &args, "--git-dir", ".git")) { + continue; + } else if (match_arg_separator(&args)) { + break; + } + } + + if (args.pos == args.argc) + usage(argv[0]); + + if (!git_dir) + git_dir = "."; + + for (i = 0; i < ARRAY_SIZE(commands); ++i) { + if (strcmp(args.argv[args.pos], commands[i].name)) + continue; + + /* + * Before running the actual command, create an instance + * of the local repository and pass it to the function. + * */ + if (commands[i].requires_repo) { + check_lg2(git_repository_open_ext(&repo, git_dir, 0, NULL), + "Unable to open repository '%s'", git_dir); + } + + return_code = run_command(commands[i].fn, repo, args); + goto shutdown; + } + + fprintf(stderr, "Command not found: %s\n", argv[1]); + +shutdown: + git_repository_free(repo); + git_libgit2_shutdown(); + + return return_code; +} diff --git a/examples/log.c b/examples/log.c new file mode 100644 index 0000000..4b0a95d --- /dev/null +++ b/examples/log.c @@ -0,0 +1,483 @@ +/* + * libgit2 "log" example - shows how to walk history and get commit info + * + * 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" + +/** + * This example demonstrates the libgit2 rev walker APIs to roughly + * simulate the output of `git log` and a few of command line arguments. + * `git log` has many many options and this only shows a few of them. + * + * This does not have: + * + * - Robust error handling + * - Colorized or paginated output formatting + * - Most of the `git log` options + * + * This does have: + * + * - Examples of translating command line arguments to equivalent libgit2 + * revwalker configuration calls + * - Simplified options to apply pathspec limits and to show basic diffs + */ + +/** log_state represents walker being configured while handling options */ +struct log_state { + git_repository *repo; + const char *repodir; + git_revwalk *walker; + int hide; + int sorting; + int revisions; +}; + +/** utility functions that are called to configure the walker */ +static void set_sorting(struct log_state *s, unsigned int sort_mode); +static void push_rev(struct log_state *s, git_object *obj, int hide); +static int add_revision(struct log_state *s, const char *revstr); + +/** log_options holds other command line options that affect log output */ +struct log_options { + int show_diff; + int show_log_size; + int skip, limit; + int min_parents, max_parents; + git_time_t before; + git_time_t after; + const char *author; + const char *committer; + const char *grep; +}; + +/** utility functions that parse options and help with log output */ +static int parse_options( + struct log_state *s, struct log_options *opt, int argc, char **argv); +static void print_time(const git_time *intime, const char *prefix); +static void print_commit(git_commit *commit, struct log_options *opts); +static int match_with_parent(git_commit *commit, int i, git_diff_options *); + +/** utility functions for filtering */ +static int signature_matches(const git_signature *sig, const char *filter); +static int log_message_matches(const git_commit *commit, const char *filter); + +int lg2_log(git_repository *repo, int argc, char *argv[]) +{ + int i, count = 0, printed = 0, parents, last_arg; + struct log_state s; + struct log_options opt; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_oid oid; + git_commit *commit = NULL; + git_pathspec *ps = NULL; + + /** Parse arguments and set up revwalker. */ + last_arg = parse_options(&s, &opt, argc, argv); + s.repo = repo; + + diffopts.pathspec.strings = &argv[last_arg]; + diffopts.pathspec.count = argc - last_arg; + if (diffopts.pathspec.count > 0) + check_lg2(git_pathspec_new(&ps, &diffopts.pathspec), + "Building pathspec", NULL); + + if (!s.revisions) + add_revision(&s, NULL); + + /** Use the revwalker to traverse the history. */ + + printed = count = 0; + + for (; !git_revwalk_next(&oid, s.walker); git_commit_free(commit)) { + check_lg2(git_commit_lookup(&commit, s.repo, &oid), + "Failed to look up commit", NULL); + + parents = (int)git_commit_parentcount(commit); + if (parents < opt.min_parents) + continue; + if (opt.max_parents > 0 && parents > opt.max_parents) + continue; + + if (diffopts.pathspec.count > 0) { + int unmatched = parents; + + if (parents == 0) { + git_tree *tree; + check_lg2(git_commit_tree(&tree, commit), "Get tree", NULL); + if (git_pathspec_match_tree( + NULL, tree, GIT_PATHSPEC_NO_MATCH_ERROR, ps) != 0) + unmatched = 1; + git_tree_free(tree); + } else if (parents == 1) { + unmatched = match_with_parent(commit, 0, &diffopts) ? 0 : 1; + } else { + for (i = 0; i < parents; ++i) { + if (match_with_parent(commit, i, &diffopts)) + unmatched--; + } + } + + if (unmatched > 0) + continue; + } + + if (!signature_matches(git_commit_author(commit), opt.author)) + continue; + + if (!signature_matches(git_commit_committer(commit), opt.committer)) + continue; + + if (!log_message_matches(commit, opt.grep)) + continue; + + if (count++ < opt.skip) + continue; + if (opt.limit != -1 && printed++ >= opt.limit) { + git_commit_free(commit); + break; + } + + print_commit(commit, &opt); + + if (opt.show_diff) { + git_tree *a = NULL, *b = NULL; + git_diff *diff = NULL; + + if (parents > 1) + continue; + check_lg2(git_commit_tree(&b, commit), "Get tree", NULL); + if (parents == 1) { + git_commit *parent; + check_lg2(git_commit_parent(&parent, commit, 0), "Get parent", NULL); + check_lg2(git_commit_tree(&a, parent), "Tree for parent", NULL); + git_commit_free(parent); + } + + check_lg2(git_diff_tree_to_tree( + &diff, git_commit_owner(commit), a, b, &diffopts), + "Diff commit with parent", NULL); + check_lg2( + git_diff_print(diff, GIT_DIFF_FORMAT_PATCH, diff_output, NULL), + "Displaying diff", NULL); + + git_diff_free(diff); + git_tree_free(a); + git_tree_free(b); + } + } + + git_pathspec_free(ps); + git_revwalk_free(s.walker); + + return 0; +} + +/** Determine if the given git_signature does not contain the filter text. */ +static int signature_matches(const git_signature *sig, const char *filter) { + if (filter == NULL) + return 1; + + if (sig != NULL && + (strstr(sig->name, filter) != NULL || + strstr(sig->email, filter) != NULL)) + return 1; + + return 0; +} + +static int log_message_matches(const git_commit *commit, const char *filter) { + const char *message = NULL; + + if (filter == NULL) + return 1; + + if ((message = git_commit_message(commit)) != NULL && + strstr(message, filter) != NULL) + return 1; + + return 0; +} + +/** Push object (for hide or show) onto revwalker. */ +static void push_rev(struct log_state *s, git_object *obj, int hide) +{ + hide = s->hide ^ hide; + + /** Create revwalker on demand if it doesn't already exist. */ + if (!s->walker) { + check_lg2(git_revwalk_new(&s->walker, s->repo), + "Could not create revision walker", NULL); + git_revwalk_sorting(s->walker, s->sorting); + } + + if (!obj) + check_lg2(git_revwalk_push_head(s->walker), + "Could not find repository HEAD", NULL); + else if (hide) + check_lg2(git_revwalk_hide(s->walker, git_object_id(obj)), + "Reference does not refer to a commit", NULL); + else + check_lg2(git_revwalk_push(s->walker, git_object_id(obj)), + "Reference does not refer to a commit", NULL); + + git_object_free(obj); +} + +/** Parse revision string and add revs to walker. */ +static int add_revision(struct log_state *s, const char *revstr) +{ + git_revspec revs; + int hide = 0; + + if (!revstr) { + push_rev(s, NULL, hide); + return 0; + } + + if (*revstr == '^') { + revs.flags = GIT_REVSPEC_SINGLE; + hide = !hide; + + if (git_revparse_single(&revs.from, s->repo, revstr + 1) < 0) + return -1; + } else if (git_revparse(&revs, s->repo, revstr) < 0) + return -1; + + if ((revs.flags & GIT_REVSPEC_SINGLE) != 0) + push_rev(s, revs.from, hide); + else { + push_rev(s, revs.to, hide); + + if ((revs.flags & GIT_REVSPEC_MERGE_BASE) != 0) { + git_oid base; + check_lg2(git_merge_base(&base, s->repo, + git_object_id(revs.from), git_object_id(revs.to)), + "Could not find merge base", revstr); + check_lg2( + git_object_lookup(&revs.to, s->repo, &base, GIT_OBJECT_COMMIT), + "Could not find merge base commit", NULL); + + push_rev(s, revs.to, hide); + } + + push_rev(s, revs.from, !hide); + } + + return 0; +} + +/** Update revwalker with sorting mode. */ +static void set_sorting(struct log_state *s, unsigned int sort_mode) +{ + /** Open repo on demand if it isn't already open. */ + if (!s->repo) { + if (!s->repodir) s->repodir = "."; + check_lg2(git_repository_open_ext(&s->repo, s->repodir, 0, NULL), + "Could not open repository", s->repodir); + } + + /** Create revwalker on demand if it doesn't already exist. */ + if (!s->walker) + check_lg2(git_revwalk_new(&s->walker, s->repo), + "Could not create revision walker", NULL); + + if (sort_mode == GIT_SORT_REVERSE) + s->sorting = s->sorting ^ GIT_SORT_REVERSE; + else + s->sorting = sort_mode | (s->sorting & GIT_SORT_REVERSE); + + git_revwalk_sorting(s->walker, s->sorting); +} + +/** Helper to format a git_time value like Git. */ +static void print_time(const git_time *intime, const char *prefix) +{ + char sign, out[32]; + struct tm *intm; + int offset, hours, minutes; + time_t t; + + offset = intime->offset; + if (offset < 0) { + sign = '-'; + offset = -offset; + } else { + sign = '+'; + } + + hours = offset / 60; + minutes = offset % 60; + + t = (time_t)intime->time + (intime->offset * 60); + + intm = gmtime(&t); + strftime(out, sizeof(out), "%a %b %e %T %Y", intm); + + printf("%s%s %c%02d%02d\n", prefix, out, sign, hours, minutes); +} + +/** Helper to print a commit object. */ +static void print_commit(git_commit *commit, struct log_options *opts) +{ + char buf[GIT_OID_SHA1_HEXSIZE + 1]; + int i, count; + const git_signature *sig; + const char *scan, *eol; + + git_oid_tostr(buf, sizeof(buf), git_commit_id(commit)); + printf("commit %s\n", buf); + + if (opts->show_log_size) { + printf("log size %d\n", (int)strlen(git_commit_message(commit))); + } + + if ((count = (int)git_commit_parentcount(commit)) > 1) { + printf("Merge:"); + for (i = 0; i < count; ++i) { + git_oid_tostr(buf, 8, git_commit_parent_id(commit, i)); + printf(" %s", buf); + } + printf("\n"); + } + + if ((sig = git_commit_author(commit)) != NULL) { + printf("Author: %s <%s>\n", sig->name, sig->email); + print_time(&sig->when, "Date: "); + } + printf("\n"); + + for (scan = git_commit_message(commit); scan && *scan; ) { + for (eol = scan; *eol && *eol != '\n'; ++eol) /* find eol */; + + printf(" %.*s\n", (int)(eol - scan), scan); + scan = *eol ? eol + 1 : NULL; + } + printf("\n"); +} + +/** Helper to find how many files in a commit changed from its nth parent. */ +static int match_with_parent(git_commit *commit, int i, git_diff_options *opts) +{ + git_commit *parent; + git_tree *a, *b; + git_diff *diff; + int ndeltas; + + check_lg2( + git_commit_parent(&parent, commit, (size_t)i), "Get parent", NULL); + check_lg2(git_commit_tree(&a, parent), "Tree for parent", NULL); + check_lg2(git_commit_tree(&b, commit), "Tree for commit", NULL); + check_lg2( + git_diff_tree_to_tree(&diff, git_commit_owner(commit), a, b, opts), + "Checking diff between parent and commit", NULL); + + ndeltas = (int)git_diff_num_deltas(diff); + + git_diff_free(diff); + git_tree_free(a); + git_tree_free(b); + git_commit_free(parent); + + return ndeltas > 0; +} + +/** Print a usage message for the program. */ +static void usage(const char *message, const char *arg) +{ + if (message && arg) + fprintf(stderr, "%s: %s\n", message, arg); + else if (message) + fprintf(stderr, "%s\n", message); + fprintf(stderr, "usage: log [<options>]\n"); + exit(1); +} + +/** Parse some log command line options. */ +static int parse_options( + struct log_state *s, struct log_options *opt, int argc, char **argv) +{ + struct args_info args = ARGS_INFO_INIT; + + memset(s, 0, sizeof(*s)); + s->sorting = GIT_SORT_TIME; + + memset(opt, 0, sizeof(*opt)); + opt->max_parents = -1; + opt->limit = -1; + + for (args.pos = 1; args.pos < argc; ++args.pos) { + const char *a = argv[args.pos]; + + if (a[0] != '-') { + if (!add_revision(s, a)) + s->revisions++; + else + /** Try failed revision parse as filename. */ + break; + } else if (!match_arg_separator(&args)) { + break; + } + else if (!strcmp(a, "--date-order")) + set_sorting(s, GIT_SORT_TIME); + else if (!strcmp(a, "--topo-order")) + set_sorting(s, GIT_SORT_TOPOLOGICAL); + else if (!strcmp(a, "--reverse")) + set_sorting(s, GIT_SORT_REVERSE); + else if (match_str_arg(&opt->author, &args, "--author")) + /** Found valid --author */ + ; + else if (match_str_arg(&opt->committer, &args, "--committer")) + /** Found valid --committer */ + ; + else if (match_str_arg(&opt->grep, &args, "--grep")) + /** Found valid --grep */ + ; + else if (match_str_arg(&s->repodir, &args, "--git-dir")) + /** Found git-dir. */ + ; + else if (match_int_arg(&opt->skip, &args, "--skip", 0)) + /** Found valid --skip. */ + ; + else if (match_int_arg(&opt->limit, &args, "--max-count", 0)) + /** Found valid --max-count. */ + ; + else if (a[1] >= '0' && a[1] <= '9') + is_integer(&opt->limit, a + 1, 0); + else if (match_int_arg(&opt->limit, &args, "-n", 0)) + /** Found valid -n. */ + ; + else if (!strcmp(a, "--merges")) + opt->min_parents = 2; + else if (!strcmp(a, "--no-merges")) + opt->max_parents = 1; + else if (!strcmp(a, "--no-min-parents")) + opt->min_parents = 0; + else if (!strcmp(a, "--no-max-parents")) + opt->max_parents = -1; + else if (match_int_arg(&opt->max_parents, &args, "--max-parents=", 1)) + /** Found valid --max-parents. */ + ; + else if (match_int_arg(&opt->min_parents, &args, "--min-parents=", 0)) + /** Found valid --min_parents. */ + ; + else if (!strcmp(a, "-p") || !strcmp(a, "-u") || !strcmp(a, "--patch")) + opt->show_diff = 1; + else if (!strcmp(a, "--log-size")) + opt->show_log_size = 1; + else + usage("Unsupported argument", a); + } + + return args.pos; +} + diff --git a/examples/ls-files.c b/examples/ls-files.c new file mode 100644 index 0000000..a235069 --- /dev/null +++ b/examples/ls-files.c @@ -0,0 +1,131 @@ +/* + * libgit2 "ls-files" example - shows how to view all files currently in the index + * + * 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" + +/** + * This example demonstrates the libgit2 index APIs to roughly + * simulate the output of `git ls-files`. + * `git ls-files` has many options and this currently does not show them. + * + * `git ls-files` base command shows all paths in the index at that time. + * This includes staged and committed files, but unstaged files will not display. + * + * This currently supports the default behavior and the `--error-unmatch` option. + */ + +struct ls_options { + int error_unmatch; + char *files[1024]; + size_t file_count; +}; + +static void usage(const char *message, const char *arg) +{ + if (message && arg) + fprintf(stderr, "%s: %s\n", message, arg); + else if (message) + fprintf(stderr, "%s\n", message); + fprintf(stderr, "usage: ls-files [--error-unmatch] [--] [<file>...]\n"); + exit(1); +} + +static int parse_options(struct ls_options *opts, int argc, char *argv[]) +{ + int parsing_files = 0; + int i; + + memset(opts, 0, sizeof(struct ls_options)); + + if (argc < 2) + return 0; + + for (i = 1; i < argc; ++i) { + char *a = argv[i]; + + /* if it doesn't start with a '-' or is after the '--' then it is a file */ + if (a[0] != '-' || parsing_files) { + parsing_files = 1; + + /* watch for overflows (just in case) */ + if (opts->file_count == 1024) { + fprintf(stderr, "ls-files can only support 1024 files at this time.\n"); + return -1; + } + + opts->files[opts->file_count++] = a; + } else if (!strcmp(a, "--")) { + parsing_files = 1; + } else if (!strcmp(a, "--error-unmatch")) { + opts->error_unmatch = 1; + } else { + usage("Unsupported argument", a); + return -1; + } + } + + return 0; +} + +static int print_paths(struct ls_options *opts, git_index *index) +{ + size_t i; + const git_index_entry *entry; + + /* if there are no files explicitly listed by the user print all entries in the index */ + if (opts->file_count == 0) { + size_t entry_count = git_index_entrycount(index); + + for (i = 0; i < entry_count; i++) { + entry = git_index_get_byindex(index, i); + puts(entry->path); + } + return 0; + } + + /* loop through the files found in the args and print them if they exist */ + for (i = 0; i < opts->file_count; ++i) { + const char *path = opts->files[i]; + + if ((entry = git_index_get_bypath(index, path, GIT_INDEX_STAGE_NORMAL)) != NULL) { + puts(path); + } else if (opts->error_unmatch) { + fprintf(stderr, "error: pathspec '%s' did not match any file(s) known to git.\n", path); + fprintf(stderr, "Did you forget to 'git add'?\n"); + return -1; + } + } + + return 0; +} + +int lg2_ls_files(git_repository *repo, int argc, char *argv[]) +{ + git_index *index = NULL; + struct ls_options opts; + int error; + + if ((error = parse_options(&opts, argc, argv)) < 0) + return error; + + if ((error = git_repository_index(&index, repo)) < 0) + goto cleanup; + + error = print_paths(&opts, index); + +cleanup: + git_index_free(index); + + return error; +} diff --git a/examples/ls-remote.c b/examples/ls-remote.c new file mode 100644 index 0000000..24caae7 --- /dev/null +++ b/examples/ls-remote.c @@ -0,0 +1,60 @@ +#include "common.h" + +static int use_remote(git_repository *repo, char *name) +{ + git_remote *remote = NULL; + int error; + const git_remote_head **refs; + size_t refs_len, i; + git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; + + /* Find the remote by name */ + error = git_remote_lookup(&remote, repo, name); + if (error < 0) { + error = git_remote_create_anonymous(&remote, repo, name); + if (error < 0) + goto cleanup; + } + + /** + * Connect to the remote and call the printing function for + * each of the remote references. + */ + callbacks.credentials = cred_acquire_cb; + + error = git_remote_connect(remote, GIT_DIRECTION_FETCH, &callbacks, NULL, NULL); + if (error < 0) + goto cleanup; + + /** + * Get the list of references on the remote and print out + * their name next to what they point to. + */ + if (git_remote_ls(&refs, &refs_len, remote) < 0) + goto cleanup; + + for (i = 0; i < refs_len; i++) { + char oid[GIT_OID_SHA1_HEXSIZE + 1] = {0}; + git_oid_fmt(oid, &refs[i]->oid); + printf("%s\t%s\n", oid, refs[i]->name); + } + +cleanup: + git_remote_free(remote); + return error; +} + +/** Entry point for this command */ +int lg2_ls_remote(git_repository *repo, int argc, char **argv) +{ + int error; + + if (argc < 2) { + fprintf(stderr, "usage: %s ls-remote <remote>\n", argv[-1]); + return EXIT_FAILURE; + } + + error = use_remote(repo, argv[1]); + + return error; +} diff --git a/examples/merge.c b/examples/merge.c new file mode 100644 index 0000000..460c06a --- /dev/null +++ b/examples/merge.c @@ -0,0 +1,361 @@ +/* + * libgit2 "merge" example - shows how to perform merges + * + * 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" + +/** The following example demonstrates how to do merges with libgit2. + * + * It will merge whatever commit-ish you pass in into the current branch. + * + * Recognized options are : + * --no-commit: don't actually commit the merge. + * + */ + +struct merge_options { + const char **heads; + size_t heads_count; + + git_annotated_commit **annotated; + size_t annotated_count; + + int no_commit : 1; +}; + +static void print_usage(void) +{ + fprintf(stderr, "usage: merge [--no-commit] <commit...>\n"); + exit(1); +} + +static void merge_options_init(struct merge_options *opts) +{ + memset(opts, 0, sizeof(*opts)); + + opts->heads = NULL; + opts->heads_count = 0; + opts->annotated = NULL; + opts->annotated_count = 0; +} + +static void opts_add_refish(struct merge_options *opts, const char *refish) +{ + size_t sz; + + assert(opts != NULL); + + sz = ++opts->heads_count * sizeof(opts->heads[0]); + opts->heads = xrealloc((void *) opts->heads, sz); + opts->heads[opts->heads_count - 1] = refish; +} + +static void parse_options(const char **repo_path, struct merge_options *opts, int argc, char **argv) +{ + struct args_info args = ARGS_INFO_INIT; + + if (argc <= 1) + print_usage(); + + for (args.pos = 1; args.pos < argc; ++args.pos) { + const char *curr = argv[args.pos]; + + if (curr[0] != '-') { + opts_add_refish(opts, curr); + } else if (!strcmp(curr, "--no-commit")) { + opts->no_commit = 1; + } else if (match_str_arg(repo_path, &args, "--git-dir")) { + continue; + } else { + print_usage(); + } + } +} + +static int resolve_heads(git_repository *repo, struct merge_options *opts) +{ + git_annotated_commit **annotated = calloc(opts->heads_count, sizeof(git_annotated_commit *)); + size_t annotated_count = 0, i; + int err = 0; + + for (i = 0; i < opts->heads_count; i++) { + err = resolve_refish(&annotated[annotated_count++], repo, opts->heads[i]); + if (err != 0) { + fprintf(stderr, "failed to resolve refish %s: %s\n", opts->heads[i], git_error_last()->message); + annotated_count--; + continue; + } + } + + if (annotated_count != opts->heads_count) { + fprintf(stderr, "unable to parse some refish\n"); + free(annotated); + return -1; + } + + opts->annotated = annotated; + opts->annotated_count = annotated_count; + return 0; +} + +static int perform_fastforward(git_repository *repo, const git_oid *target_oid, int is_unborn) +{ + git_checkout_options ff_checkout_options = GIT_CHECKOUT_OPTIONS_INIT; + git_reference *target_ref; + git_reference *new_target_ref; + git_object *target = NULL; + int err = 0; + + if (is_unborn) { + const char *symbolic_ref; + git_reference *head_ref; + + /* HEAD reference is unborn, lookup manually so we don't try to resolve it */ + err = git_reference_lookup(&head_ref, repo, "HEAD"); + if (err != 0) { + fprintf(stderr, "failed to lookup HEAD ref\n"); + return -1; + } + + /* Grab the reference HEAD should be pointing to */ + symbolic_ref = git_reference_symbolic_target(head_ref); + + /* Create our master reference on the target OID */ + err = git_reference_create(&target_ref, repo, symbolic_ref, target_oid, 0, NULL); + if (err != 0) { + fprintf(stderr, "failed to create master reference\n"); + return -1; + } + + git_reference_free(head_ref); + } else { + /* HEAD exists, just lookup and resolve */ + err = git_repository_head(&target_ref, repo); + if (err != 0) { + fprintf(stderr, "failed to get HEAD reference\n"); + return -1; + } + } + + /* Lookup the target object */ + err = git_object_lookup(&target, repo, target_oid, GIT_OBJECT_COMMIT); + if (err != 0) { + fprintf(stderr, "failed to lookup OID %s\n", git_oid_tostr_s(target_oid)); + return -1; + } + + /* Checkout the result so the workdir is in the expected state */ + ff_checkout_options.checkout_strategy = GIT_CHECKOUT_SAFE; + err = git_checkout_tree(repo, target, &ff_checkout_options); + if (err != 0) { + fprintf(stderr, "failed to checkout HEAD reference\n"); + return -1; + } + + /* Move the target reference to the target OID */ + err = git_reference_set_target(&new_target_ref, target_ref, target_oid, NULL); + if (err != 0) { + fprintf(stderr, "failed to move HEAD reference\n"); + return -1; + } + + git_reference_free(target_ref); + git_reference_free(new_target_ref); + git_object_free(target); + + return 0; +} + +static void output_conflicts(git_index *index) +{ + git_index_conflict_iterator *conflicts; + const git_index_entry *ancestor; + const git_index_entry *our; + const git_index_entry *their; + int err = 0; + + check_lg2(git_index_conflict_iterator_new(&conflicts, index), "failed to create conflict iterator", NULL); + + while ((err = git_index_conflict_next(&ancestor, &our, &their, conflicts)) == 0) { + fprintf(stderr, "conflict: a:%s o:%s t:%s\n", + ancestor ? ancestor->path : "NULL", + our->path ? our->path : "NULL", + their->path ? their->path : "NULL"); + } + + if (err != GIT_ITEROVER) { + fprintf(stderr, "error iterating conflicts\n"); + } + + git_index_conflict_iterator_free(conflicts); +} + +static int create_merge_commit(git_repository *repo, git_index *index, struct merge_options *opts) +{ + git_oid tree_oid, commit_oid; + git_tree *tree; + git_signature *sign; + git_reference *merge_ref = NULL; + git_annotated_commit *merge_commit; + git_reference *head_ref; + git_commit **parents = calloc(opts->annotated_count + 1, sizeof(git_commit *)); + const char *msg_target = NULL; + size_t msglen = 0; + char *msg; + size_t i; + int err; + + /* Grab our needed references */ + check_lg2(git_repository_head(&head_ref, repo), "failed to get repo HEAD", NULL); + if (resolve_refish(&merge_commit, repo, opts->heads[0])) { + fprintf(stderr, "failed to resolve refish %s", opts->heads[0]); + free(parents); + return -1; + } + + /* Maybe that's a ref, so DWIM it */ + err = git_reference_dwim(&merge_ref, repo, opts->heads[0]); + check_lg2(err, "failed to DWIM reference", git_error_last()->message); + + /* Grab a signature */ + check_lg2(git_signature_now(&sign, "Me", "me@example.com"), "failed to create signature", NULL); + +#define MERGE_COMMIT_MSG "Merge %s '%s'" + /* Prepare a standard merge commit message */ + if (merge_ref != NULL) { + check_lg2(git_branch_name(&msg_target, merge_ref), "failed to get branch name of merged ref", NULL); + } else { + msg_target = git_oid_tostr_s(git_annotated_commit_id(merge_commit)); + } + + msglen = snprintf(NULL, 0, MERGE_COMMIT_MSG, (merge_ref ? "branch" : "commit"), msg_target); + if (msglen > 0) msglen++; + msg = malloc(msglen); + err = snprintf(msg, msglen, MERGE_COMMIT_MSG, (merge_ref ? "branch" : "commit"), msg_target); + + /* This is only to silence the compiler */ + if (err < 0) goto cleanup; + + /* Setup our parent commits */ + err = git_reference_peel((git_object **)&parents[0], head_ref, GIT_OBJECT_COMMIT); + check_lg2(err, "failed to peel head reference", NULL); + for (i = 0; i < opts->annotated_count; i++) { + git_commit_lookup(&parents[i + 1], repo, git_annotated_commit_id(opts->annotated[i])); + } + + /* Prepare our commit tree */ + check_lg2(git_index_write_tree(&tree_oid, index), "failed to write merged tree", NULL); + check_lg2(git_tree_lookup(&tree, repo, &tree_oid), "failed to lookup tree", NULL); + + /* Commit time ! */ + err = git_commit_create(&commit_oid, + repo, git_reference_name(head_ref), + sign, sign, + NULL, msg, + tree, + opts->annotated_count + 1, (const git_commit **)parents); + check_lg2(err, "failed to create commit", NULL); + + /* We're done merging, cleanup the repository state */ + git_repository_state_cleanup(repo); + +cleanup: + free(parents); + return err; +} + +int lg2_merge(git_repository *repo, int argc, char **argv) +{ + struct merge_options opts; + git_index *index; + git_repository_state_t state; + git_merge_analysis_t analysis; + git_merge_preference_t preference; + const char *path = "."; + int err = 0; + + merge_options_init(&opts); + parse_options(&path, &opts, argc, argv); + + state = git_repository_state(repo); + if (state != GIT_REPOSITORY_STATE_NONE) { + fprintf(stderr, "repository is in unexpected state %d\n", state); + goto cleanup; + } + + err = resolve_heads(repo, &opts); + if (err != 0) + goto cleanup; + + err = git_merge_analysis(&analysis, &preference, + repo, + (const git_annotated_commit **)opts.annotated, + opts.annotated_count); + check_lg2(err, "merge analysis failed", NULL); + + if (analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE) { + printf("Already up-to-date\n"); + return 0; + } else if (analysis & GIT_MERGE_ANALYSIS_UNBORN || + (analysis & GIT_MERGE_ANALYSIS_FASTFORWARD && + !(preference & GIT_MERGE_PREFERENCE_NO_FASTFORWARD))) { + const git_oid *target_oid; + if (analysis & GIT_MERGE_ANALYSIS_UNBORN) { + printf("Unborn\n"); + } else { + printf("Fast-forward\n"); + } + + /* Since this is a fast-forward, there can be only one merge head */ + target_oid = git_annotated_commit_id(opts.annotated[0]); + assert(opts.annotated_count == 1); + + return perform_fastforward(repo, target_oid, (analysis & GIT_MERGE_ANALYSIS_UNBORN)); + } else if (analysis & GIT_MERGE_ANALYSIS_NORMAL) { + git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT; + git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + + merge_opts.flags = 0; + merge_opts.file_flags = GIT_MERGE_FILE_STYLE_DIFF3; + + checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE|GIT_CHECKOUT_ALLOW_CONFLICTS; + + if (preference & GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY) { + printf("Fast-forward is preferred, but only a merge is possible\n"); + return -1; + } + + err = git_merge(repo, + (const git_annotated_commit **)opts.annotated, opts.annotated_count, + &merge_opts, &checkout_opts); + check_lg2(err, "merge failed", NULL); + } + + /* If we get here, we actually performed the merge above */ + + check_lg2(git_repository_index(&index, repo), "failed to get repository index", NULL); + + if (git_index_has_conflicts(index)) { + /* Handle conflicts */ + output_conflicts(index); + } else if (!opts.no_commit) { + create_merge_commit(repo, index, &opts); + printf("Merge made\n"); + } + +cleanup: + free((char **)opts.heads); + free(opts.annotated); + + return 0; +} diff --git a/examples/push.c b/examples/push.c new file mode 100644 index 0000000..bcf3076 --- /dev/null +++ b/examples/push.c @@ -0,0 +1,56 @@ +/* + * libgit2 "push" example - shows how to push to remote + * + * 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" + +/** + * This example demonstrates the libgit2 push API to roughly + * simulate `git push`. + * + * This does not have: + * + * - Robust error handling + * - Any of the `git push` options + * + * This does have: + * + * - Example of push to origin/master + * + */ + +/** Entry point for this command */ +int lg2_push(git_repository *repo, int argc, char **argv) { + git_push_options options; + git_remote* remote = NULL; + char *refspec = "refs/heads/master"; + const git_strarray refspecs = { + &refspec, + 1 + }; + + /* Validate args */ + if (argc > 1) { + printf ("USAGE: %s\n\nsorry, no arguments supported yet\n", argv[0]); + return -1; + } + + check_lg2(git_remote_lookup(&remote, repo, "origin" ), "Unable to lookup remote", NULL); + + check_lg2(git_push_options_init(&options, GIT_PUSH_OPTIONS_VERSION ), "Error initializing push", NULL); + + check_lg2(git_remote_push(remote, &refspecs, &options), "Error pushing", NULL); + + printf("pushed\n"); + return 0; +} diff --git a/examples/remote.c b/examples/remote.c new file mode 100644 index 0000000..14fac03 --- /dev/null +++ b/examples/remote.c @@ -0,0 +1,256 @@ +/* + * libgit2 "remote" example - shows how to modify remotes for a repo + * + * 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" + +/** + * This is a sample program that is similar to "git remote". See the + * documentation for that (try "git help remote") to understand what this + * program is emulating. + * + * This demonstrates using the libgit2 APIs to modify remotes of a repository. + */ + +enum subcmd { + subcmd_add, + subcmd_remove, + subcmd_rename, + subcmd_seturl, + subcmd_show +}; + +struct remote_opts { + enum subcmd cmd; + + /* for command-specific args */ + int argc; + char **argv; +}; + +static int cmd_add(git_repository *repo, struct remote_opts *o); +static int cmd_remove(git_repository *repo, struct remote_opts *o); +static int cmd_rename(git_repository *repo, struct remote_opts *o); +static int cmd_seturl(git_repository *repo, struct remote_opts *o); +static int cmd_show(git_repository *repo, struct remote_opts *o); + +static void parse_subcmd( + struct remote_opts *opt, int argc, char **argv); +static void usage(const char *msg, const char *arg); + +int lg2_remote(git_repository *repo, int argc, char *argv[]) +{ + int retval = 0; + struct remote_opts opt = {0}; + + parse_subcmd(&opt, argc, argv); + + switch (opt.cmd) + { + case subcmd_add: + retval = cmd_add(repo, &opt); + break; + case subcmd_remove: + retval = cmd_remove(repo, &opt); + break; + case subcmd_rename: + retval = cmd_rename(repo, &opt); + break; + case subcmd_seturl: + retval = cmd_seturl(repo, &opt); + break; + case subcmd_show: + retval = cmd_show(repo, &opt); + break; + } + + return retval; +} + +static int cmd_add(git_repository *repo, struct remote_opts *o) +{ + char *name, *url; + git_remote *remote = {0}; + + if (o->argc != 2) + usage("you need to specify a name and URL", NULL); + + name = o->argv[0]; + url = o->argv[1]; + + check_lg2(git_remote_create(&remote, repo, name, url), + "could not create remote", NULL); + + return 0; +} + +static int cmd_remove(git_repository *repo, struct remote_opts *o) +{ + char *name; + + if (o->argc != 1) + usage("you need to specify a name", NULL); + + name = o->argv[0]; + + check_lg2(git_remote_delete(repo, name), + "could not delete remote", name); + + return 0; +} + +static int cmd_rename(git_repository *repo, struct remote_opts *o) +{ + int i, retval; + char *old, *new; + git_strarray problems = {0}; + + if (o->argc != 2) + usage("you need to specify old and new remote name", NULL); + + old = o->argv[0]; + new = o->argv[1]; + + retval = git_remote_rename(&problems, repo, old, new); + if (!retval) + return 0; + + for (i = 0; i < (int) problems.count; i++) { + puts(problems.strings[0]); + } + + git_strarray_dispose(&problems); + + return retval; +} + +static int cmd_seturl(git_repository *repo, struct remote_opts *o) +{ + int i, retval, push = 0; + char *name = NULL, *url = NULL; + + for (i = 0; i < o->argc; i++) { + char *arg = o->argv[i]; + + if (!strcmp(arg, "--push")) { + push = 1; + } else if (arg[0] != '-' && name == NULL) { + name = arg; + } else if (arg[0] != '-' && url == NULL) { + url = arg; + } else { + usage("invalid argument to set-url", arg); + } + } + + if (name == NULL || url == NULL) + usage("you need to specify remote and the new URL", NULL); + + if (push) + retval = git_remote_set_pushurl(repo, name, url); + else + retval = git_remote_set_url(repo, name, url); + + check_lg2(retval, "could not set URL", url); + + return 0; +} + +static int cmd_show(git_repository *repo, struct remote_opts *o) +{ + int i; + const char *arg, *name, *fetch, *push; + int verbose = 0; + git_strarray remotes = {0}; + git_remote *remote = {0}; + + for (i = 0; i < o->argc; i++) { + arg = o->argv[i]; + + if (!strcmp(arg, "-v") || !strcmp(arg, "--verbose")) { + verbose = 1; + } + } + + check_lg2(git_remote_list(&remotes, repo), + "could not retrieve remotes", NULL); + + for (i = 0; i < (int) remotes.count; i++) { + name = remotes.strings[i]; + if (!verbose) { + puts(name); + continue; + } + + check_lg2(git_remote_lookup(&remote, repo, name), + "could not look up remote", name); + + fetch = git_remote_url(remote); + if (fetch) + printf("%s\t%s (fetch)\n", name, fetch); + push = git_remote_pushurl(remote); + /* use fetch URL if no distinct push URL has been set */ + push = push ? push : fetch; + if (push) + printf("%s\t%s (push)\n", name, push); + + git_remote_free(remote); + } + + git_strarray_dispose(&remotes); + + return 0; +} + +static void parse_subcmd( + struct remote_opts *opt, int argc, char **argv) +{ + char *arg = argv[1]; + enum subcmd cmd = 0; + + if (argc < 2) + usage("no command specified", NULL); + + if (!strcmp(arg, "add")) { + cmd = subcmd_add; + } else if (!strcmp(arg, "remove")) { + cmd = subcmd_remove; + } else if (!strcmp(arg, "rename")) { + cmd = subcmd_rename; + } else if (!strcmp(arg, "set-url")) { + cmd = subcmd_seturl; + } else if (!strcmp(arg, "show")) { + cmd = subcmd_show; + } else { + usage("command is not valid", arg); + } + opt->cmd = cmd; + + opt->argc = argc - 2; /* executable and subcommand are removed */ + opt->argv = argv + 2; +} + +static void usage(const char *msg, const char *arg) +{ + fputs("usage: remote add <name> <url>\n", stderr); + fputs(" remote remove <name>\n", stderr); + fputs(" remote rename <old> <new>\n", stderr); + fputs(" remote set-url [--push] <name> <newurl>\n", stderr); + fputs(" remote show [-v|--verbose]\n", stderr); + + if (msg && !arg) + fprintf(stderr, "\n%s\n", msg); + else if (msg && arg) + fprintf(stderr, "\n%s: %s\n", msg, arg); + exit(1); +} diff --git a/examples/rev-list.c b/examples/rev-list.c new file mode 100644 index 0000000..cf8ac30 --- /dev/null +++ b/examples/rev-list.c @@ -0,0 +1,158 @@ +/* + * libgit2 "rev-list" example - shows how to transform a rev-spec into a list + * of commit ids + * + * 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> + +static int revwalk_parse_options(git_sort_t *sort, struct args_info *args); +static int revwalk_parse_revs(git_repository *repo, git_revwalk *walk, struct args_info *args); + +int lg2_rev_list(git_repository *repo, int argc, char **argv) +{ + struct args_info args = ARGS_INFO_INIT; + git_revwalk *walk; + git_oid oid; + git_sort_t sort; + char buf[GIT_OID_SHA1_HEXSIZE+1]; + + check_lg2(revwalk_parse_options(&sort, &args), "parsing options", NULL); + + check_lg2(git_revwalk_new(&walk, repo), "allocating revwalk", NULL); + git_revwalk_sorting(walk, sort); + check_lg2(revwalk_parse_revs(repo, walk, &args), "parsing revs", NULL); + + while (!git_revwalk_next(&oid, walk)) { + git_oid_fmt(buf, &oid); + buf[GIT_OID_SHA1_HEXSIZE] = '\0'; + printf("%s\n", buf); + } + + git_revwalk_free(walk); + return 0; +} + +static int push_commit(git_revwalk *walk, const git_oid *oid, int hide) +{ + if (hide) + return git_revwalk_hide(walk, oid); + else + return git_revwalk_push(walk, oid); +} + +static int push_spec(git_repository *repo, git_revwalk *walk, const char *spec, int hide) +{ + int error; + git_object *obj; + + if ((error = git_revparse_single(&obj, repo, spec)) < 0) + return error; + + error = push_commit(walk, git_object_id(obj), hide); + git_object_free(obj); + return error; +} + +static int push_range(git_repository *repo, git_revwalk *walk, const char *range, int hide) +{ + git_revspec revspec; + int error = 0; + + if ((error = git_revparse(&revspec, repo, range))) + return error; + + if (revspec.flags & GIT_REVSPEC_MERGE_BASE) { + /* TODO: support "<commit>...<commit>" */ + return GIT_EINVALIDSPEC; + } + + if ((error = push_commit(walk, git_object_id(revspec.from), !hide))) + goto out; + + error = push_commit(walk, git_object_id(revspec.to), hide); + +out: + git_object_free(revspec.from); + git_object_free(revspec.to); + return error; +} + +static void print_usage(void) +{ + fprintf(stderr, "rev-list [--git-dir=dir] [--topo-order|--date-order] [--reverse] <revspec>\n"); + exit(-1); +} + +static int revwalk_parse_options(git_sort_t *sort, struct args_info *args) +{ + assert(sort && args); + *sort = GIT_SORT_NONE; + + if (args->argc < 1) + print_usage(); + + for (args->pos = 1; args->pos < args->argc; ++args->pos) { + const char *curr = args->argv[args->pos]; + + if (!strcmp(curr, "--topo-order")) { + *sort |= GIT_SORT_TOPOLOGICAL; + } else if (!strcmp(curr, "--date-order")) { + *sort |= GIT_SORT_TIME; + } else if (!strcmp(curr, "--reverse")) { + *sort |= (*sort & ~GIT_SORT_REVERSE) ^ GIT_SORT_REVERSE; + } else { + break; + } + } + return 0; +} + +static int revwalk_parse_revs(git_repository *repo, git_revwalk *walk, struct args_info *args) +{ + int hide, error; + git_oid oid; + + hide = 0; + for (; args->pos < args->argc; ++args->pos) { + const char *curr = args->argv[args->pos]; + + if (!strcmp(curr, "--not")) { + hide = !hide; + } else if (curr[0] == '^') { + if ((error = push_spec(repo, walk, curr + 1, !hide))) + return error; + } else if (strstr(curr, "..")) { + if ((error = push_range(repo, walk, curr, hide))) + return error; + } else { + if (push_spec(repo, walk, curr, hide) == 0) + continue; + +#ifdef GIT_EXPERIMENTAL_SHA256 + if ((error = git_oid_fromstr(&oid, curr, GIT_OID_SHA1))) + return error; +#else + if ((error = git_oid_fromstr(&oid, curr))) + return error; +#endif + + if ((error = push_commit(walk, &oid, hide))) + return error; + } + } + + return 0; +} + diff --git a/examples/rev-parse.c b/examples/rev-parse.c new file mode 100644 index 0000000..3f68d79 --- /dev/null +++ b/examples/rev-parse.c @@ -0,0 +1,102 @@ +/* + * libgit2 "rev-parse" example - shows how to parse revspecs + * + * 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" + +/** Forward declarations for helpers. */ +struct parse_state { + const char *repodir; + const char *spec; + int not; +}; +static void parse_opts(struct parse_state *ps, int argc, char *argv[]); +static int parse_revision(git_repository *repo, struct parse_state *ps); + +int lg2_rev_parse(git_repository *repo, int argc, char *argv[]) +{ + struct parse_state ps = {0}; + + parse_opts(&ps, argc, argv); + + check_lg2(parse_revision(repo, &ps), "Parsing", NULL); + + return 0; +} + +static void usage(const char *message, const char *arg) +{ + if (message && arg) + fprintf(stderr, "%s: %s\n", message, arg); + else if (message) + fprintf(stderr, "%s\n", message); + fprintf(stderr, "usage: rev-parse [ --option ] <args>...\n"); + exit(1); +} + +static void parse_opts(struct parse_state *ps, int argc, char *argv[]) +{ + struct args_info args = ARGS_INFO_INIT; + + for (args.pos=1; args.pos < argc; ++args.pos) { + const char *a = argv[args.pos]; + + if (a[0] != '-') { + if (ps->spec) + usage("Too many specs", a); + ps->spec = a; + } else if (!strcmp(a, "--not")) + ps->not = !ps->not; + else if (!match_str_arg(&ps->repodir, &args, "--git-dir")) + usage("Cannot handle argument", a); + } +} + +static int parse_revision(git_repository *repo, struct parse_state *ps) +{ + git_revspec rs; + char str[GIT_OID_SHA1_HEXSIZE + 1]; + + check_lg2(git_revparse(&rs, repo, ps->spec), "Could not parse", ps->spec); + + if ((rs.flags & GIT_REVSPEC_SINGLE) != 0) { + git_oid_tostr(str, sizeof(str), git_object_id(rs.from)); + printf("%s\n", str); + git_object_free(rs.from); + } + else if ((rs.flags & GIT_REVSPEC_RANGE) != 0) { + git_oid_tostr(str, sizeof(str), git_object_id(rs.to)); + printf("%s\n", str); + git_object_free(rs.to); + + if ((rs.flags & GIT_REVSPEC_MERGE_BASE) != 0) { + git_oid base; + check_lg2(git_merge_base(&base, repo, + git_object_id(rs.from), git_object_id(rs.to)), + "Could not find merge base", ps->spec); + + git_oid_tostr(str, sizeof(str), &base); + printf("%s\n", str); + } + + git_oid_tostr(str, sizeof(str), git_object_id(rs.from)); + printf("^%s\n", str); + git_object_free(rs.from); + } + else { + fatal("Invalid results from git_revparse", ps->spec); + } + + return 0; +} + diff --git a/examples/show-index.c b/examples/show-index.c new file mode 100644 index 0000000..0a5e7d1 --- /dev/null +++ b/examples/show-index.c @@ -0,0 +1,70 @@ +/* + * libgit2 "showindex" example - shows how to extract data from the index + * + * 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" + +int lg2_show_index(git_repository *repo, int argc, char **argv) +{ + git_index *index; + size_t i, ecount; + char *dir = "."; + size_t dirlen; + char out[GIT_OID_SHA1_HEXSIZE+1]; + out[GIT_OID_SHA1_HEXSIZE] = '\0'; + + if (argc > 2) + fatal("usage: showindex [<repo-dir>]", NULL); + if (argc > 1) + dir = argv[1]; + + dirlen = strlen(dir); + if (dirlen > 5 && strcmp(dir + dirlen - 5, "index") == 0) { +#ifdef GIT_EXPERIMENTAL_SHA256 + check_lg2(git_index_open(&index, dir, GIT_OID_SHA1), "could not open index", dir); +#else + check_lg2(git_index_open(&index, dir), "could not open index", dir); +#endif + } else { + check_lg2(git_repository_open_ext(&repo, dir, 0, NULL), "could not open repository", dir); + check_lg2(git_repository_index(&index, repo), "could not open repository index", NULL); + git_repository_free(repo); + } + + git_index_read(index, 0); + + ecount = git_index_entrycount(index); + if (!ecount) + printf("Empty index\n"); + + for (i = 0; i < ecount; ++i) { + const git_index_entry *e = git_index_get_byindex(index, i); + + git_oid_fmt(out, &e->id); + + printf("File Path: %s\n", e->path); + printf(" Stage: %d\n", git_index_entry_stage(e)); + printf(" Blob SHA: %s\n", out); + printf("File Mode: %07o\n", e->mode); + printf("File Size: %d bytes\n", (int)e->file_size); + printf("Dev/Inode: %d/%d\n", (int)e->dev, (int)e->ino); + printf(" UID/GID: %d/%d\n", (int)e->uid, (int)e->gid); + printf(" ctime: %d\n", (int)e->ctime.seconds); + printf(" mtime: %d\n", (int)e->mtime.seconds); + printf("\n"); + } + + git_index_free(index); + + return 0; +} diff --git a/examples/stash.c b/examples/stash.c new file mode 100644 index 0000000..8142439 --- /dev/null +++ b/examples/stash.c @@ -0,0 +1,157 @@ +/* + * libgit2 "stash" example - shows how to use the stash API + * + * 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 <stdarg.h> + +#include "common.h" + +enum subcmd { + SUBCMD_APPLY, + SUBCMD_LIST, + SUBCMD_POP, + SUBCMD_PUSH +}; + +struct opts { + enum subcmd cmd; + int argc; + char **argv; +}; + +static void usage(const char *fmt, ...) +{ + va_list ap; + + fputs("usage: git stash list\n", stderr); + fputs(" or: git stash ( pop | apply )\n", stderr); + fputs(" or: git stash [push]\n", stderr); + fputs("\n", stderr); + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + exit(1); +} + +static void parse_subcommand(struct opts *opts, int argc, char *argv[]) +{ + char *arg = (argc < 2) ? "push" : argv[1]; + enum subcmd cmd; + + if (!strcmp(arg, "apply")) { + cmd = SUBCMD_APPLY; + } else if (!strcmp(arg, "list")) { + cmd = SUBCMD_LIST; + } else if (!strcmp(arg, "pop")) { + cmd = SUBCMD_POP; + } else if (!strcmp(arg, "push")) { + cmd = SUBCMD_PUSH; + } else { + usage("invalid command %s", arg); + return; + } + + opts->cmd = cmd; + opts->argc = (argc < 2) ? argc - 1 : argc - 2; + opts->argv = argv; +} + +static int cmd_apply(git_repository *repo, struct opts *opts) +{ + if (opts->argc) + usage("apply does not accept any parameters"); + + check_lg2(git_stash_apply(repo, 0, NULL), + "Unable to apply stash", NULL); + + return 0; +} + +static int list_stash_cb(size_t index, const char *message, + const git_oid *stash_id, void *payload) +{ + UNUSED(stash_id); + UNUSED(payload); + printf("stash@{%"PRIuZ"}: %s\n", index, message); + return 0; +} + +static int cmd_list(git_repository *repo, struct opts *opts) +{ + if (opts->argc) + usage("list does not accept any parameters"); + + check_lg2(git_stash_foreach(repo, list_stash_cb, NULL), + "Unable to list stashes", NULL); + + return 0; +} + +static int cmd_push(git_repository *repo, struct opts *opts) +{ + git_signature *signature; + git_commit *stash; + git_oid stashid; + + if (opts->argc) + usage("push does not accept any parameters"); + + check_lg2(git_signature_default(&signature, repo), + "Unable to get signature", NULL); + check_lg2(git_stash_save(&stashid, repo, signature, NULL, GIT_STASH_DEFAULT), + "Unable to save stash", NULL); + check_lg2(git_commit_lookup(&stash, repo, &stashid), + "Unable to lookup stash commit", NULL); + + printf("Saved working directory %s\n", git_commit_summary(stash)); + + git_signature_free(signature); + git_commit_free(stash); + + return 0; +} + +static int cmd_pop(git_repository *repo, struct opts *opts) +{ + if (opts->argc) + usage("pop does not accept any parameters"); + + check_lg2(git_stash_pop(repo, 0, NULL), + "Unable to pop stash", NULL); + + printf("Dropped refs/stash@{0}\n"); + + return 0; +} + +int lg2_stash(git_repository *repo, int argc, char *argv[]) +{ + struct opts opts = { 0 }; + + parse_subcommand(&opts, argc, argv); + + switch (opts.cmd) { + case SUBCMD_APPLY: + return cmd_apply(repo, &opts); + case SUBCMD_LIST: + return cmd_list(repo, &opts); + case SUBCMD_PUSH: + return cmd_push(repo, &opts); + case SUBCMD_POP: + return cmd_pop(repo, &opts); + } + + return -1; +} diff --git a/examples/status.c b/examples/status.c new file mode 100644 index 0000000..e659efb --- /dev/null +++ b/examples/status.c @@ -0,0 +1,498 @@ +/* + * libgit2 "status" example - shows how to use the status APIs + * + * 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" + +/** + * This example demonstrates the use of the libgit2 status APIs, + * particularly the `git_status_list` object, to roughly simulate the + * output of running `git status`. It serves as a simple example of + * using those APIs to get basic status information. + * + * This does not have: + * + * - Robust error handling + * - Colorized or paginated output formatting + * + * This does have: + * + * - Examples of translating command line arguments to the status + * options settings to mimic `git status` results. + * - A sample status formatter that matches the default "long" format + * from `git status` + * - A sample status formatter that matches the "short" format + */ + +enum { + FORMAT_DEFAULT = 0, + FORMAT_LONG = 1, + FORMAT_SHORT = 2, + FORMAT_PORCELAIN = 3 +}; + +#define MAX_PATHSPEC 8 + +struct status_opts { + git_status_options statusopt; + char *repodir; + char *pathspec[MAX_PATHSPEC]; + int npaths; + int format; + int zterm; + int showbranch; + int showsubmod; + int repeat; +}; + +static void parse_opts(struct status_opts *o, int argc, char *argv[]); +static void show_branch(git_repository *repo, int format); +static void print_long(git_status_list *status); +static void print_short(git_repository *repo, git_status_list *status); +static int print_submod(git_submodule *sm, const char *name, void *payload); + +int lg2_status(git_repository *repo, int argc, char *argv[]) +{ + git_status_list *status; + struct status_opts o = { GIT_STATUS_OPTIONS_INIT, "." }; + + o.statusopt.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + o.statusopt.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX | + GIT_STATUS_OPT_SORT_CASE_SENSITIVELY; + + parse_opts(&o, argc, argv); + + if (git_repository_is_bare(repo)) + fatal("Cannot report status on bare repository", + git_repository_path(repo)); + +show_status: + if (o.repeat) + printf("\033[H\033[2J"); + + /** + * Run status on the repository + * + * We use `git_status_list_new()` to generate a list of status + * information which lets us iterate over it at our + * convenience and extract the data we want to show out of + * each entry. + * + * You can use `git_status_foreach()` or + * `git_status_foreach_ext()` if you'd prefer to execute a + * callback for each entry. The latter gives you more control + * about what results are presented. + */ + check_lg2(git_status_list_new(&status, repo, &o.statusopt), + "Could not get status", NULL); + + if (o.showbranch) + show_branch(repo, o.format); + + if (o.showsubmod) { + int submod_count = 0; + check_lg2(git_submodule_foreach(repo, print_submod, &submod_count), + "Cannot iterate submodules", o.repodir); + } + + if (o.format == FORMAT_LONG) + print_long(status); + else + print_short(repo, status); + + git_status_list_free(status); + + if (o.repeat) { + sleep(o.repeat); + goto show_status; + } + + return 0; +} + +/** + * If the user asked for the branch, let's show the short name of the + * branch. + */ +static void show_branch(git_repository *repo, int format) +{ + int error = 0; + const char *branch = NULL; + git_reference *head = NULL; + + error = git_repository_head(&head, repo); + + if (error == GIT_EUNBORNBRANCH || error == GIT_ENOTFOUND) + branch = NULL; + else if (!error) { + branch = git_reference_shorthand(head); + } else + check_lg2(error, "failed to get current branch", NULL); + + if (format == FORMAT_LONG) + printf("# On branch %s\n", + branch ? branch : "Not currently on any branch."); + else + printf("## %s\n", branch ? branch : "HEAD (no branch)"); + + git_reference_free(head); +} + +/** + * This function print out an output similar to git's status command + * in long form, including the command-line hints. + */ +static void print_long(git_status_list *status) +{ + size_t i, maxi = git_status_list_entrycount(status); + const git_status_entry *s; + int header = 0, changes_in_index = 0; + int changed_in_workdir = 0, rm_in_workdir = 0; + const char *old_path, *new_path; + + /** Print index changes. */ + + for (i = 0; i < maxi; ++i) { + char *istatus = NULL; + + s = git_status_byindex(status, i); + + if (s->status == GIT_STATUS_CURRENT) + continue; + + if (s->status & GIT_STATUS_WT_DELETED) + rm_in_workdir = 1; + + if (s->status & GIT_STATUS_INDEX_NEW) + istatus = "new file: "; + if (s->status & GIT_STATUS_INDEX_MODIFIED) + istatus = "modified: "; + if (s->status & GIT_STATUS_INDEX_DELETED) + istatus = "deleted: "; + if (s->status & GIT_STATUS_INDEX_RENAMED) + istatus = "renamed: "; + if (s->status & GIT_STATUS_INDEX_TYPECHANGE) + istatus = "typechange:"; + + if (istatus == NULL) + continue; + + if (!header) { + printf("# Changes to be committed:\n"); + printf("# (use \"git reset HEAD <file>...\" to unstage)\n"); + printf("#\n"); + header = 1; + } + + old_path = s->head_to_index->old_file.path; + new_path = s->head_to_index->new_file.path; + + if (old_path && new_path && strcmp(old_path, new_path)) + printf("#\t%s %s -> %s\n", istatus, old_path, new_path); + else + printf("#\t%s %s\n", istatus, old_path ? old_path : new_path); + } + + if (header) { + changes_in_index = 1; + printf("#\n"); + } + header = 0; + + /** Print workdir changes to tracked files. */ + + for (i = 0; i < maxi; ++i) { + char *wstatus = NULL; + + s = git_status_byindex(status, i); + + /** + * With `GIT_STATUS_OPT_INCLUDE_UNMODIFIED` (not used in this example) + * `index_to_workdir` may not be `NULL` even if there are + * no differences, in which case it will be a `GIT_DELTA_UNMODIFIED`. + */ + if (s->status == GIT_STATUS_CURRENT || s->index_to_workdir == NULL) + continue; + + /** Print out the output since we know the file has some changes */ + if (s->status & GIT_STATUS_WT_MODIFIED) + wstatus = "modified: "; + if (s->status & GIT_STATUS_WT_DELETED) + wstatus = "deleted: "; + if (s->status & GIT_STATUS_WT_RENAMED) + wstatus = "renamed: "; + if (s->status & GIT_STATUS_WT_TYPECHANGE) + wstatus = "typechange:"; + + if (wstatus == NULL) + continue; + + if (!header) { + printf("# Changes not staged for commit:\n"); + printf("# (use \"git add%s <file>...\" to update what will be committed)\n", rm_in_workdir ? "/rm" : ""); + printf("# (use \"git checkout -- <file>...\" to discard changes in working directory)\n"); + printf("#\n"); + header = 1; + } + + old_path = s->index_to_workdir->old_file.path; + new_path = s->index_to_workdir->new_file.path; + + if (old_path && new_path && strcmp(old_path, new_path)) + printf("#\t%s %s -> %s\n", wstatus, old_path, new_path); + else + printf("#\t%s %s\n", wstatus, old_path ? old_path : new_path); + } + + if (header) { + changed_in_workdir = 1; + printf("#\n"); + } + + /** Print untracked files. */ + + header = 0; + + for (i = 0; i < maxi; ++i) { + s = git_status_byindex(status, i); + + if (s->status == GIT_STATUS_WT_NEW) { + + if (!header) { + printf("# Untracked files:\n"); + printf("# (use \"git add <file>...\" to include in what will be committed)\n"); + printf("#\n"); + header = 1; + } + + printf("#\t%s\n", s->index_to_workdir->old_file.path); + } + } + + header = 0; + + /** Print ignored files. */ + + for (i = 0; i < maxi; ++i) { + s = git_status_byindex(status, i); + + if (s->status == GIT_STATUS_IGNORED) { + + if (!header) { + printf("# Ignored files:\n"); + printf("# (use \"git add -f <file>...\" to include in what will be committed)\n"); + printf("#\n"); + header = 1; + } + + printf("#\t%s\n", s->index_to_workdir->old_file.path); + } + } + + if (!changes_in_index && changed_in_workdir) + printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n"); +} + +/** + * This version of the output prefixes each path with two status + * columns and shows submodule status information. + */ +static void print_short(git_repository *repo, git_status_list *status) +{ + size_t i, maxi = git_status_list_entrycount(status); + const git_status_entry *s; + char istatus, wstatus; + const char *extra, *a, *b, *c; + + for (i = 0; i < maxi; ++i) { + s = git_status_byindex(status, i); + + if (s->status == GIT_STATUS_CURRENT) + continue; + + a = b = c = NULL; + istatus = wstatus = ' '; + extra = ""; + + if (s->status & GIT_STATUS_INDEX_NEW) + istatus = 'A'; + if (s->status & GIT_STATUS_INDEX_MODIFIED) + istatus = 'M'; + if (s->status & GIT_STATUS_INDEX_DELETED) + istatus = 'D'; + if (s->status & GIT_STATUS_INDEX_RENAMED) + istatus = 'R'; + if (s->status & GIT_STATUS_INDEX_TYPECHANGE) + istatus = 'T'; + + if (s->status & GIT_STATUS_WT_NEW) { + if (istatus == ' ') + istatus = '?'; + wstatus = '?'; + } + if (s->status & GIT_STATUS_WT_MODIFIED) + wstatus = 'M'; + if (s->status & GIT_STATUS_WT_DELETED) + wstatus = 'D'; + if (s->status & GIT_STATUS_WT_RENAMED) + wstatus = 'R'; + if (s->status & GIT_STATUS_WT_TYPECHANGE) + wstatus = 'T'; + + if (s->status & GIT_STATUS_IGNORED) { + istatus = '!'; + wstatus = '!'; + } + + if (istatus == '?' && wstatus == '?') + continue; + + /** + * A commit in a tree is how submodules are stored, so + * let's go take a look at its status. + */ + if (s->index_to_workdir && + s->index_to_workdir->new_file.mode == GIT_FILEMODE_COMMIT) + { + unsigned int smstatus = 0; + + if (!git_submodule_status(&smstatus, repo, s->index_to_workdir->new_file.path, + GIT_SUBMODULE_IGNORE_UNSPECIFIED)) { + if (smstatus & GIT_SUBMODULE_STATUS_WD_MODIFIED) + extra = " (new commits)"; + else if (smstatus & GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED) + extra = " (modified content)"; + else if (smstatus & GIT_SUBMODULE_STATUS_WD_WD_MODIFIED) + extra = " (modified content)"; + else if (smstatus & GIT_SUBMODULE_STATUS_WD_UNTRACKED) + extra = " (untracked content)"; + } + } + + /** + * Now that we have all the information, format the output. + */ + + if (s->head_to_index) { + a = s->head_to_index->old_file.path; + b = s->head_to_index->new_file.path; + } + if (s->index_to_workdir) { + if (!a) + a = s->index_to_workdir->old_file.path; + if (!b) + b = s->index_to_workdir->old_file.path; + c = s->index_to_workdir->new_file.path; + } + + if (istatus == 'R') { + if (wstatus == 'R') + printf("%c%c %s %s %s%s\n", istatus, wstatus, a, b, c, extra); + else + printf("%c%c %s %s%s\n", istatus, wstatus, a, b, extra); + } else { + if (wstatus == 'R') + printf("%c%c %s %s%s\n", istatus, wstatus, a, c, extra); + else + printf("%c%c %s%s\n", istatus, wstatus, a, extra); + } + } + + for (i = 0; i < maxi; ++i) { + s = git_status_byindex(status, i); + + if (s->status == GIT_STATUS_WT_NEW) + printf("?? %s\n", s->index_to_workdir->old_file.path); + } +} + +static int print_submod(git_submodule *sm, const char *name, void *payload) +{ + int *count = payload; + (void)name; + + if (*count == 0) + printf("# Submodules\n"); + (*count)++; + + printf("# - submodule '%s' at %s\n", + git_submodule_name(sm), git_submodule_path(sm)); + + return 0; +} + +/** + * Parse options that git's status command supports. + */ +static void parse_opts(struct status_opts *o, int argc, char *argv[]) +{ + struct args_info args = ARGS_INFO_INIT; + + for (args.pos = 1; args.pos < argc; ++args.pos) { + char *a = argv[args.pos]; + + if (a[0] != '-') { + if (o->npaths < MAX_PATHSPEC) + o->pathspec[o->npaths++] = a; + else + fatal("Example only supports a limited pathspec", NULL); + } + else if (!strcmp(a, "-s") || !strcmp(a, "--short")) + o->format = FORMAT_SHORT; + else if (!strcmp(a, "--long")) + o->format = FORMAT_LONG; + else if (!strcmp(a, "--porcelain")) + o->format = FORMAT_PORCELAIN; + else if (!strcmp(a, "-b") || !strcmp(a, "--branch")) + o->showbranch = 1; + else if (!strcmp(a, "-z")) { + o->zterm = 1; + if (o->format == FORMAT_DEFAULT) + o->format = FORMAT_PORCELAIN; + } + else if (!strcmp(a, "--ignored")) + o->statusopt.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED; + else if (!strcmp(a, "-uno") || + !strcmp(a, "--untracked-files=no")) + o->statusopt.flags &= ~GIT_STATUS_OPT_INCLUDE_UNTRACKED; + else if (!strcmp(a, "-unormal") || + !strcmp(a, "--untracked-files=normal")) + o->statusopt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; + else if (!strcmp(a, "-uall") || + !strcmp(a, "--untracked-files=all")) + o->statusopt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + else if (!strcmp(a, "--ignore-submodules=all")) + o->statusopt.flags |= GIT_STATUS_OPT_EXCLUDE_SUBMODULES; + else if (!strncmp(a, "--git-dir=", strlen("--git-dir="))) + o->repodir = a + strlen("--git-dir="); + else if (!strcmp(a, "--repeat")) + o->repeat = 10; + else if (match_int_arg(&o->repeat, &args, "--repeat", 0)) + /* okay */; + else if (!strcmp(a, "--list-submodules")) + o->showsubmod = 1; + else + check_lg2(-1, "Unsupported option", a); + } + + if (o->format == FORMAT_DEFAULT) + o->format = FORMAT_LONG; + if (o->format == FORMAT_LONG) + o->showbranch = 1; + if (o->npaths > 0) { + o->statusopt.pathspec.strings = o->pathspec; + o->statusopt.pathspec.count = o->npaths; + } +} diff --git a/examples/tag.c b/examples/tag.c new file mode 100644 index 0000000..e4f71ae --- /dev/null +++ b/examples/tag.c @@ -0,0 +1,310 @@ +/* + * libgit2 "tag" example - shows how to list, create and delete tags + * + * 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" + +/** + * The following example partially reimplements the `git tag` command + * and some of its options. + * + * These commands should work: + + * - Tag name listing (`tag`) + * - Filtered tag listing with messages (`tag -n3 -l "v0.1*"`) + * - Lightweight tag creation (`tag test v0.18.0`) + * - Tag creation (`tag -a -m "Test message" test v0.18.0`) + * - Tag deletion (`tag -d test`) + * + * The command line parsing logic is simplified and doesn't handle + * all of the use cases. + */ + +/** tag_options represents the parsed command line options */ +struct tag_options { + const char *message; + const char *pattern; + const char *tag_name; + const char *target; + int num_lines; + int force; +}; + +/** tag_state represents the current program state for dragging around */ +typedef struct { + git_repository *repo; + struct tag_options *opts; +} tag_state; + +/** An action to execute based on the command line arguments */ +typedef void (*tag_action)(tag_state *state); +typedef struct args_info args_info; + +static void check(int result, const char *message) +{ + if (result) fatal(message, NULL); +} + +/** Tag listing: Print individual message lines */ +static void print_list_lines(const char *message, const tag_state *state) +{ + const char *msg = message; + int num = state->opts->num_lines - 1; + + if (!msg) return; + + /** first line - headline */ + while(*msg && *msg != '\n') printf("%c", *msg++); + + /** skip over new lines */ + while(*msg && *msg == '\n') msg++; + + printf("\n"); + + /** print just headline? */ + if (num == 0) return; + if (*msg && msg[1]) printf("\n"); + + /** print individual commit/tag lines */ + while (*msg && num-- >= 2) { + printf(" "); + + while (*msg && *msg != '\n') printf("%c", *msg++); + + /** handle consecutive new lines */ + if (*msg && *msg == '\n' && msg[1] == '\n') { + num--; + printf("\n"); + } + while(*msg && *msg == '\n') msg++; + + printf("\n"); + } +} + +/** Tag listing: Print an actual tag object */ +static void print_tag(git_tag *tag, const tag_state *state) +{ + printf("%-16s", git_tag_name(tag)); + + if (state->opts->num_lines) { + const char *msg = git_tag_message(tag); + print_list_lines(msg, state); + } else { + printf("\n"); + } +} + +/** Tag listing: Print a commit (target of a lightweight tag) */ +static void print_commit(git_commit *commit, const char *name, + const tag_state *state) +{ + printf("%-16s", name); + + if (state->opts->num_lines) { + const char *msg = git_commit_message(commit); + print_list_lines(msg, state); + } else { + printf("\n"); + } +} + +/** Tag listing: Fallback, should not happen */ +static void print_name(const char *name) +{ + printf("%s\n", name); +} + +/** Tag listing: Lookup tags based on ref name and dispatch to print */ +static int each_tag(const char *name, tag_state *state) +{ + git_repository *repo = state->repo; + git_object *obj; + + check_lg2(git_revparse_single(&obj, repo, name), + "Failed to lookup rev", name); + + switch (git_object_type(obj)) { + case GIT_OBJECT_TAG: + print_tag((git_tag *) obj, state); + break; + case GIT_OBJECT_COMMIT: + print_commit((git_commit *) obj, name, state); + break; + default: + print_name(name); + } + + git_object_free(obj); + return 0; +} + +static void action_list_tags(tag_state *state) +{ + const char *pattern = state->opts->pattern; + git_strarray tag_names = {0}; + size_t i; + + check_lg2(git_tag_list_match(&tag_names, pattern ? pattern : "*", state->repo), + "Unable to get list of tags", NULL); + + for(i = 0; i < tag_names.count; i++) { + each_tag(tag_names.strings[i], state); + } + + git_strarray_dispose(&tag_names); +} + +static void action_delete_tag(tag_state *state) +{ + struct tag_options *opts = state->opts; + git_object *obj; + git_buf abbrev_oid = {0}; + + check(!opts->tag_name, "Name required"); + + check_lg2(git_revparse_single(&obj, state->repo, opts->tag_name), + "Failed to lookup rev", opts->tag_name); + + check_lg2(git_object_short_id(&abbrev_oid, obj), + "Unable to get abbreviated OID", opts->tag_name); + + check_lg2(git_tag_delete(state->repo, opts->tag_name), + "Unable to delete tag", opts->tag_name); + + printf("Deleted tag '%s' (was %s)\n", opts->tag_name, abbrev_oid.ptr); + + git_buf_dispose(&abbrev_oid); + git_object_free(obj); +} + +static void action_create_lightweight_tag(tag_state *state) +{ + git_repository *repo = state->repo; + struct tag_options *opts = state->opts; + git_oid oid; + git_object *target; + + check(!opts->tag_name, "Name required"); + + if (!opts->target) opts->target = "HEAD"; + + check(!opts->target, "Target required"); + + check_lg2(git_revparse_single(&target, repo, opts->target), + "Unable to resolve spec", opts->target); + + check_lg2(git_tag_create_lightweight(&oid, repo, opts->tag_name, + target, opts->force), "Unable to create tag", NULL); + + git_object_free(target); +} + +static void action_create_tag(tag_state *state) +{ + git_repository *repo = state->repo; + struct tag_options *opts = state->opts; + git_signature *tagger; + git_oid oid; + git_object *target; + + check(!opts->tag_name, "Name required"); + check(!opts->message, "Message required"); + + if (!opts->target) opts->target = "HEAD"; + + check_lg2(git_revparse_single(&target, repo, opts->target), + "Unable to resolve spec", opts->target); + + check_lg2(git_signature_default(&tagger, repo), + "Unable to create signature", NULL); + + check_lg2(git_tag_create(&oid, repo, opts->tag_name, + target, tagger, opts->message, opts->force), "Unable to create tag", NULL); + + git_object_free(target); + git_signature_free(tagger); +} + +static void print_usage(void) +{ + fprintf(stderr, "usage: see `git help tag`\n"); + exit(1); +} + +/** Parse command line arguments and choose action to run when done */ +static void parse_options(tag_action *action, struct tag_options *opts, int argc, char **argv) +{ + args_info args = ARGS_INFO_INIT; + *action = &action_list_tags; + + for (args.pos = 1; args.pos < argc; ++args.pos) { + const char *curr = argv[args.pos]; + + if (curr[0] != '-') { + if (!opts->tag_name) + opts->tag_name = curr; + else if (!opts->target) + opts->target = curr; + else + print_usage(); + + if (*action != &action_create_tag) + *action = &action_create_lightweight_tag; + } else if (!strcmp(curr, "-n")) { + opts->num_lines = 1; + *action = &action_list_tags; + } else if (!strcmp(curr, "-a")) { + *action = &action_create_tag; + } else if (!strcmp(curr, "-f")) { + opts->force = 1; + } else if (match_int_arg(&opts->num_lines, &args, "-n", 0)) { + *action = &action_list_tags; + } else if (match_str_arg(&opts->pattern, &args, "-l")) { + *action = &action_list_tags; + } else if (match_str_arg(&opts->tag_name, &args, "-d")) { + *action = &action_delete_tag; + } else if (match_str_arg(&opts->message, &args, "-m")) { + *action = &action_create_tag; + } + } +} + +/** Initialize tag_options struct */ +static void tag_options_init(struct tag_options *opts) +{ + memset(opts, 0, sizeof(*opts)); + + opts->message = NULL; + opts->pattern = NULL; + opts->tag_name = NULL; + opts->target = NULL; + opts->num_lines = 0; + opts->force = 0; +} + +int lg2_tag(git_repository *repo, int argc, char **argv) +{ + struct tag_options opts; + tag_action action; + tag_state state; + + tag_options_init(&opts); + parse_options(&action, &opts, argc, argv); + + state.repo = repo; + state.opts = &opts; + action(&state); + + return 0; +} |