diff options
author | Vicent Martà <vicent@github.com> | 2013-11-01 09:38:34 -0700 |
---|---|---|
committer | Vicent Martà <vicent@github.com> | 2013-11-01 09:38:34 -0700 |
commit | 567649f2ada60e5c3009cc985af238b452b14a81 (patch) | |
tree | 12a19dd97c05818557228ed2b3fcc36e183959b2 | |
parent | 948f00b4e79bb60c68e03342500013246e642ae6 (diff) | |
parent | 4f62d55968d4a22a6ea03194aa34ab1b6ca8fdea (diff) | |
download | libgit2-567649f2ada60e5c3009cc985af238b452b14a81.tar.gz |
Merge pull request #1916 from libgit2/simplify-examples
Fix examples to make the important stuff more obvious
-rw-r--r-- | examples/CMakeLists.txt | 6 | ||||
-rw-r--r-- | examples/Makefile | 2 | ||||
-rw-r--r-- | examples/README.md | 20 | ||||
-rw-r--r-- | examples/add.c | 129 | ||||
-rw-r--r-- | examples/cat-file.c | 162 | ||||
-rw-r--r-- | examples/common.c | 184 | ||||
-rw-r--r-- | examples/common.h | 80 | ||||
-rw-r--r-- | examples/diff.c | 378 | ||||
-rw-r--r-- | examples/init.c | 289 | ||||
-rw-r--r-- | examples/log.c | 481 | ||||
-rw-r--r-- | examples/network/fetch.c | 11 | ||||
-rw-r--r-- | examples/network/ls-remote.c | 9 | ||||
-rw-r--r-- | examples/rev-list.c | 62 | ||||
-rw-r--r-- | examples/rev-parse.c | 108 | ||||
-rw-r--r-- | examples/showindex.c | 38 | ||||
-rw-r--r-- | examples/status.c | 246 |
16 files changed, 1255 insertions, 950 deletions
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index c20a6df3b..596be45ed 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -9,6 +9,8 @@ ENDIF() FILE(GLOB SRC_EXAMPLE_APPS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.c) FOREACH(src_app ${SRC_EXAMPLE_APPS}) STRING(REPLACE ".c" "" app_name ${src_app}) - ADD_EXECUTABLE(${app_name} ${src_app}) - TARGET_LINK_LIBRARIES(${app_name} git2) + IF(NOT ${app_name} STREQUAL "common") + ADD_EXECUTABLE(${app_name} ${src_app} "common.c") + TARGET_LINK_LIBRARIES(${app_name} git2) + ENDIF() ENDFOREACH() diff --git a/examples/Makefile b/examples/Makefile index d53ed8241..f4d55570a 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -8,7 +8,7 @@ APPS = general showindex diff rev-list cat-file status log rev-parse init all: $(APPS) % : %.c - $(CC) -o $@ $(CFLAGS) $< $(LFLAGS) + $(CC) -o $@ common.c $(CFLAGS) $< $(LFLAGS) clean: $(RM) $(APPS) diff --git a/examples/README.md b/examples/README.md index f2b6d7d23..494d3291a 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,11 +1,19 @@ libgit2 examples ================ -These examples are meant as thin, easy-to-read snippets for Docurium -(https://github.com/github/docurium) rather than full-blown -implementations of Git commands. They are not vetted as carefully -for bugs, error handling, or cross-platform compatibility as the -rest of the code in libgit2, so copy with some caution. +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. -For HTML versions, check "Examples" at http://libgit2.github.com/libgit2 +That being said, you are welcome to copy code from these examples as +desired when using libgit2. +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 index a0edf4376..999a41e64 100644 --- a/examples/add.c +++ b/examples/add.c @@ -1,6 +1,11 @@ -#include <git2.h> -#include <stdio.h> -#include <string.h> +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" #include <assert.h> enum print_options { @@ -14,19 +19,49 @@ struct print_payload { git_repository *repo; }; -void init_array(git_strarray *array, int argc, char **argv) +/* Forward declarations for helpers */ +static void parse_opts(int *options, int *count, int argc, char *argv[]); +void init_array(git_strarray *array, int argc, char **argv); +int print_matched_cb(const char *path, const char *matched_pathspec, void *payload); + +int main (int argc, char** argv) { - unsigned int i; + git_index_matched_path_cb matched_cb = NULL; + git_repository *repo = NULL; + git_index *index; + git_strarray array = {0}; + int options = 0, count = 0; + struct print_payload payload = {0}; - array->count = argc; - array->strings = malloc(sizeof(char*) * array->count); - assert(array->strings!=NULL); + git_threads_init(); - for(i=0; i<array->count; i++) { - array->strings[i]=argv[i]; + parse_opts(&options, &count, argc, argv); + + init_array(&array, argc-count, argv+count); + + check_lg2(git_repository_open(&repo, "."), "No git repository", NULL); + check_lg2(git_repository_index(&index, repo), "Could not open repository index", NULL); + + if (options&VERBOSE || options&SKIP) { + matched_cb = &print_matched_cb; } - return; + payload.options = options; + payload.repo = repo; + + if (options&UPDATE) { + git_index_update_all(index, &array, matched_cb, &payload); + } else { + git_index_add_all(index, &array, 0, matched_cb, &payload); + } + + git_index_write(index); + git_index_free(index); + git_repository_free(repo); + + git_threads_shutdown(); + + return 0; } int print_matched_cb(const char *path, const char *matched_pathspec, void *payload) @@ -55,36 +90,46 @@ int print_matched_cb(const char *path, const char *matched_pathspec, void *paylo return ret; } +void init_array(git_strarray *array, int argc, char **argv) +{ + unsigned int i; + + array->count = argc; + array->strings = malloc(sizeof(char*) * array->count); + assert(array->strings!=NULL); + + for(i=0; i<array->count; i++) { + array->strings[i]=argv[i]; + } + + return; +} + 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); } - -int main (int argc, char** argv) +static void parse_opts(int *options, int *count, int argc, char *argv[]) { - git_index_matched_path_cb matched_cb = NULL; - git_repository *repo = NULL; - git_index *index; - git_strarray array = {0}; - int i, options = 0; - struct print_payload payload = {0}; + int i; for (i = 1; i < argc; ++i) { if (argv[i][0] != '-') { break; } else if(!strcmp(argv[i], "--verbose") || !strcmp(argv[i], "-v")) { - options |= VERBOSE; + *options |= VERBOSE; } else if(!strcmp(argv[i], "--dry-run") || !strcmp(argv[i], "-n")) { - options |= SKIP; + *options |= SKIP; } else if(!strcmp(argv[i], "--update") || !strcmp(argv[i], "-u")) { - options |= UPDATE; + *options |= UPDATE; } else if(!strcmp(argv[i], "-h")) { print_usage(); @@ -97,47 +142,11 @@ int main (int argc, char** argv) else { fprintf(stderr, "Unsupported option %s.\n", argv[i]); print_usage(); - return 1; } } - if (argc<=i) { + if (argc<=i) print_usage(); - return 1; - } - git_threads_init(); - - init_array(&array, argc-i, argv+i); - - if (git_repository_open(&repo, ".") < 0) { - fprintf(stderr, "No git repository\n"); - return 1; - } - - if (git_repository_index(&index, repo) < 0) { - fprintf(stderr, "Could not open repository index\n"); - return 1; - } - - if (options&VERBOSE || options&SKIP) { - matched_cb = &print_matched_cb; - } - - payload.options = options; - payload.repo = repo; - - if (options&UPDATE) { - git_index_update_all(index, &array, matched_cb, &payload); - } else { - git_index_add_all(index, &array, 0, matched_cb, &payload); - } - - git_index_write(index); - git_index_free(index); - git_repository_free(repo); - - git_threads_shutdown(); - - return 0; + *count = i; } diff --git a/examples/cat-file.c b/examples/cat-file.c index ebb6cb0ca..5e547628a 100644 --- a/examples/cat-file.c +++ b/examples/cat-file.c @@ -1,37 +1,11 @@ -#include <stdio.h> -#include <git2.h> -#include <stdlib.h> -#include <string.h> +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ -static git_repository *g_repo; - -static void check(int error, const char *message) -{ - if (error) { - fprintf(stderr, "%s (%d)\n", message, error); - exit(1); - } -} - -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) [<options>] <object>\n"); - exit(1); -} - -static int check_str_param( - const char *arg, const char *pattern, const char **val) -{ - size_t len = strlen(pattern); - if (strncmp(arg, pattern, len)) - return 0; - *val = (const char *)(arg + len); - return 1; -} +#include "common.h" static void print_signature(const char *header, const git_signature *sig) { @@ -57,12 +31,14 @@ static void print_signature(const char *header, const git_signature *sig) 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), 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); @@ -81,6 +57,9 @@ static void show_tree(const git_tree *tree) } } +/** + * Commits and tags have a few interesting fields in their header. + */ static void show_commit(const git_commit *commit) { unsigned int i, max_i; @@ -123,53 +102,34 @@ enum { SHOW_PRETTY = 4 }; +/* Forward declarations for option-parsing helper */ +struct opts { + const char *dir; + const char *rev; + int action; + int verbose; +}; +static void parse_opts(struct opts *o, int argc, char *argv[]); + + +/** Entry point for this command */ int main(int argc, char *argv[]) { - const char *dir = ".", *rev = NULL; - int i, action = 0, verbose = 0; + git_repository *repo; + struct opts o = { ".", NULL, 0, 0 }; git_object *obj = NULL; char oidstr[GIT_OID_HEXSZ + 1]; git_threads_init(); - for (i = 1; i < argc; ++i) { - char *a = argv[i]; - - if (a[0] != '-') { - if (rev != NULL) - usage("Only one rev should be provided", NULL); - else - rev = a; - } - else if (!strcmp(a, "-t")) - action = SHOW_TYPE; - else if (!strcmp(a, "-s")) - action = SHOW_SIZE; - else if (!strcmp(a, "-e")) - action = SHOW_NONE; - else if (!strcmp(a, "-p")) - action = SHOW_PRETTY; - else if (!strcmp(a, "-q")) - verbose = 0; - else if (!strcmp(a, "-v")) - verbose = 1; - else if (!strcmp(a, "--help") || !strcmp(a, "-h")) - usage(NULL, NULL); - else if (!check_str_param(a, "--git-dir=", &dir)) - usage("Unknown option", a); - } - - if (!action || !rev) - usage(NULL, NULL); + parse_opts(&o, argc, argv); - check(git_repository_open_ext(&g_repo, dir, 0, NULL), - "Could not open repository"); + check_lg2(git_repository_open_ext(&repo, o.dir, 0, NULL), + "Could not open repository", NULL); + check_lg2(git_revparse_single(&obj, repo, o.rev), + "Could not resolve", o.rev); - if (git_revparse_single(&obj, g_repo, rev) < 0) { - fprintf(stderr, "Could not resolve '%s'\n", rev); - exit(1); - } - if (verbose) { + if (o.verbose) { char oidstr[GIT_OID_HEXSZ + 1]; git_oid_tostr(oidstr, sizeof(oidstr), git_object_id(obj)); @@ -177,7 +137,7 @@ int main(int argc, char *argv[]) git_object_type2string(git_object_type(obj)), oidstr); } - switch (action) { + switch (o.action) { case SHOW_TYPE: printf("%s\n", git_object_type2string(git_object_type(obj))); break; @@ -185,9 +145,9 @@ int main(int argc, char *argv[]) git_odb *odb; git_odb_object *odbobj; - check(git_repository_odb(&odb, g_repo), "Could not open ODB"); - check(git_odb_read(&odbobj, odb, git_object_id(obj)), - "Could not find obj"); + 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)); @@ -221,9 +181,59 @@ int main(int argc, char *argv[]) } git_object_free(obj); - git_repository_free(g_repo); + git_repository_free(repo); git_threads_shutdown(); 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 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->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/common.c b/examples/common.c new file mode 100644 index 000000000..5972bc5c7 --- /dev/null +++ b/examples/common.c @@ -0,0 +1,184 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.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 = giterr_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); +} + +size_t is_prefixed(const char *str, const char *pfx) +{ + size_t len = strlen(pfx); + return strncmp(str, pfx, len) ? 0 : len; +} + +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; +} + +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 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 diff_output( + const git_diff_delta *d, + const git_diff_hunk *h, + const git_diff_line *l, + void *p) +{ + FILE *fp = 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_OBJ_TREE), + "resolving object to tree", treeish); + + git_object_free(obj); +} + diff --git a/examples/common.h b/examples/common.h new file mode 100644 index 000000000..5ffc9c8eb --- /dev/null +++ b/examples/common.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <git2.h> + +/** + * 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); + +/** + * Exit the program, printing error to stderr + */ +extern void fatal(const char *message, const char *extra); + +/** + * 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); + +struct args_info { + int argc; + char **argv; + int pos; +}; +#define ARGS_INFO_INIT { argc, argv, 0 } + +/** + * 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 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); + +/** + * 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); diff --git a/examples/diff.c b/examples/diff.c index 694621f1e..b1415648a 100644 --- a/examples/diff.c +++ b/examples/diff.c @@ -1,49 +1,149 @@ -#include <stdio.h> -#include <git2.h> -#include <stdlib.h> -#include <string.h> +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#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 */ +}; -static void check(int error, const char *message) -{ - if (error) { - fprintf(stderr, "%s (%d)\n", message, error); - exit(1); - } -} +/** The 'opts' struct captures all the various parsed command line options. */ +struct opts { + git_diff_options diffopts; + git_diff_find_options findopts; + int color; + int cached; + git_diff_format_t format; + const char *treeish1; + const char *treeish2; + const char *dir; +}; + +/** These functions are implemented at the end */ +static void parse_opts(struct opts *o, int argc, char *argv[]); +static int color_printer( + const git_diff_delta*, const git_diff_hunk*, const git_diff_line*, void*); -static int resolve_to_tree( - git_repository *repo, const char *identifier, git_tree **tree) +int main(int argc, char *argv[]) { - int err = 0; - git_object *obj = NULL; - - if ((err = git_revparse_single(&obj, repo, identifier)) < 0) - return err; - - switch (git_object_type(obj)) { - case GIT_OBJ_TREE: - *tree = (git_tree *)obj; - break; - case GIT_OBJ_COMMIT: - err = git_commit_tree(tree, (git_commit *)obj); - git_object_free(obj); - break; - default: - err = GIT_ENOTFOUND; + git_repository *repo = NULL; + git_tree *t1 = NULL, *t2 = NULL; + git_diff *diff; + struct opts o = { + GIT_DIFF_OPTIONS_INIT, GIT_DIFF_FIND_OPTIONS_INIT, + -1, 0, GIT_DIFF_FORMAT_PATCH, NULL, NULL, "." + }; + + git_threads_init(); + + parse_opts(&o, argc, argv); + + check_lg2(git_repository_open_ext(&repo, o.dir, 0, NULL), + "Could not open repository", o.dir); + + /** + * Possible argument patterns: + * + * * <sha1> <sha2> + * * <sha1> --cached + * * <sha1> + * * --cached + * * nothing + * + * Currently ranged arguments like <sha1>..<sha2> and <sha1>...<sha2> + * are not supported in this example + */ + + 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 (t1 && o.cached) + 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 if (o.cached) { + treeish_to_tree(&t1, repo, "HEAD"); + check_lg2( + git_diff_tree_to_index(&diff, repo, t1, NULL, &o.diffopts), + "diff tree to index", 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.color >= 0) + fputs(colors[0], stdout); + + check_lg2( + git_diff_print(diff, o.format, color_printer, &o.color), + "displaying diff", NULL); - return err; + if (o.color >= 0) + fputs(colors[0], stdout); + + /** Cleanup before exiting. */ + + git_diff_free(diff); + git_tree_free(t1); + git_tree_free(t2); + git_repository_free(repo); + + git_threads_shutdown(); + + return 0; } -char *colors[] = { - "\033[m", /* reset */ - "\033[1m", /* bold */ - "\033[31m", /* red */ - "\033[32m", /* green */ - "\033[36m" /* cyan */ -}; +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); +} -static int printer( +/** 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, @@ -63,6 +163,7 @@ static int printer( 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); @@ -71,186 +172,79 @@ static int printer( } } - if (line->origin == GIT_DIFF_LINE_CONTEXT || - line->origin == GIT_DIFF_LINE_ADDITION || - line->origin == GIT_DIFF_LINE_DELETION) - fputc(line->origin, stdout); - - fwrite(line->content, 1, line->content_len, stdout); - - return 0; -} - -static int check_uint16_param(const char *arg, const char *pattern, uint16_t *val) -{ - size_t len = strlen(pattern); - uint16_t strval; - char *endptr = NULL; - if (strncmp(arg, pattern, len)) - return 0; - if (arg[len] == '\0' && pattern[len - 1] != '=') - return 1; - if (arg[len] == '=') - len++; - strval = strtoul(arg + len, &endptr, 0); - if (endptr == arg) - return 0; - *val = strval; - return 1; -} - -static int check_str_param(const char *arg, const char *pattern, const char **val) -{ - size_t len = strlen(pattern); - if (strncmp(arg, pattern, len)) - return 0; - *val = (const char *)(arg + len); - return 1; -} - -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); + return diff_output(delta, hunk, line, stdout); } -int main(int argc, char *argv[]) +/** Parse arguments as copied from git-diff. */ +static void parse_opts(struct opts *o, int argc, char *argv[]) { - git_repository *repo = NULL; - git_tree *t1 = NULL, *t2 = NULL; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; - git_diff *diff; - int i, color = -1, cached = 0; - git_diff_format_t format = GIT_DIFF_FORMAT_PATCH; - char *a, *treeish1 = NULL, *treeish2 = NULL; - const char *dir = "."; - - git_threads_init(); + struct args_info args = ARGS_INFO_INIT; - /* parse arguments as copied from git-diff */ - for (i = 1; i < argc; ++i) { - a = argv[i]; + for (args.pos = 1; args.pos < argc; ++args.pos) { + const char *a = argv[args.pos]; if (a[0] != '-') { - if (treeish1 == NULL) - treeish1 = a; - else if (treeish2 == NULL) - treeish2 = a; + 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")) - format = GIT_DIFF_FORMAT_PATCH; + o->format = GIT_DIFF_FORMAT_PATCH; else if (!strcmp(a, "--cached")) - cached = 1; + o->cached = 1; else if (!strcmp(a, "--name-only")) - format = GIT_DIFF_FORMAT_NAME_ONLY; + o->format = GIT_DIFF_FORMAT_NAME_ONLY; else if (!strcmp(a, "--name-status")) - format = GIT_DIFF_FORMAT_NAME_STATUS; + o->format = GIT_DIFF_FORMAT_NAME_STATUS; else if (!strcmp(a, "--raw")) - format = GIT_DIFF_FORMAT_RAW; + o->format = GIT_DIFF_FORMAT_RAW; else if (!strcmp(a, "--color")) - color = 0; + o->color = 0; else if (!strcmp(a, "--no-color")) - color = -1; + o->color = -1; else if (!strcmp(a, "-R")) - opts.flags |= GIT_DIFF_REVERSE; + o->diffopts.flags |= GIT_DIFF_REVERSE; else if (!strcmp(a, "-a") || !strcmp(a, "--text")) - opts.flags |= GIT_DIFF_FORCE_TEXT; + o->diffopts.flags |= GIT_DIFF_FORCE_TEXT; else if (!strcmp(a, "--ignore-space-at-eol")) - opts.flags |= GIT_DIFF_IGNORE_WHITESPACE_EOL; + o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE_EOL; else if (!strcmp(a, "-b") || !strcmp(a, "--ignore-space-change")) - opts.flags |= GIT_DIFF_IGNORE_WHITESPACE_CHANGE; + o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE_CHANGE; else if (!strcmp(a, "-w") || !strcmp(a, "--ignore-all-space")) - opts.flags |= GIT_DIFF_IGNORE_WHITESPACE; + o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE; else if (!strcmp(a, "--ignored")) - opts.flags |= GIT_DIFF_INCLUDE_IGNORED; + o->diffopts.flags |= GIT_DIFF_INCLUDE_IGNORED; else if (!strcmp(a, "--untracked")) - opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; - else if (check_uint16_param(a, "-M", &findopts.rename_threshold) || - check_uint16_param(a, "--find-renames", - &findopts.rename_threshold)) - findopts.flags |= GIT_DIFF_FIND_RENAMES; - else if (check_uint16_param(a, "-C", &findopts.copy_threshold) || - check_uint16_param(a, "--find-copies", - &findopts.copy_threshold)) - findopts.flags |= GIT_DIFF_FIND_COPIES; + o->diffopts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; + 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")) - findopts.flags |= GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED; - else if (!strncmp(a, "-B", 2) || !strncmp(a, "--break-rewrites", 16)) { + o->findopts.flags |= GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED; + else if (is_prefixed(a, "-B") || is_prefixed(a, "--break-rewrites")) /* TODO: parse thresholds */ - findopts.flags |= GIT_DIFF_FIND_REWRITES; - } - else if (!check_uint16_param(a, "-U", &opts.context_lines) && - !check_uint16_param(a, "--unified=", &opts.context_lines) && - !check_uint16_param(a, "--inter-hunk-context=", - &opts.interhunk_lines) && - !check_str_param(a, "--src-prefix=", &opts.old_prefix) && - !check_str_param(a, "--dst-prefix=", &opts.new_prefix) && - !check_str_param(a, "--git-dir=", &dir)) - usage("Unknown arg", a); + o->findopts.flags |= GIT_DIFF_FIND_REWRITES; + else if (!match_uint16_arg( + &o->diffopts.context_lines, &args, "-U") && + !match_uint16_arg( + &o->diffopts.context_lines, &args, "--unified") && + !match_uint16_arg( + &o->diffopts.interhunk_lines, &args, "--inter-hunk-context") && + !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); } - - /* open repo */ - - check(git_repository_open_ext(&repo, dir, 0, NULL), - "Could not open repository"); - - if (treeish1) - check(resolve_to_tree(repo, treeish1, &t1), "Looking up first tree"); - if (treeish2) - check(resolve_to_tree(repo, treeish2, &t2), "Looking up second tree"); - - /* <sha1> <sha2> */ - /* <sha1> --cached */ - /* <sha1> */ - /* --cached */ - /* nothing */ - - if (t1 && t2) - check(git_diff_tree_to_tree(&diff, repo, t1, t2, &opts), "Diff"); - else if (t1 && cached) - check(git_diff_tree_to_index(&diff, repo, t1, NULL, &opts), "Diff"); - else if (t1) { - git_diff *diff2; - check(git_diff_tree_to_index(&diff, repo, t1, NULL, &opts), "Diff"); - check(git_diff_index_to_workdir(&diff2, repo, NULL, &opts), "Diff"); - check(git_diff_merge(diff, diff2), "Merge diffs"); - git_diff_free(diff2); - } - else if (cached) { - check(resolve_to_tree(repo, "HEAD", &t1), "looking up HEAD"); - check(git_diff_tree_to_index(&diff, repo, t1, NULL, &opts), "Diff"); - } - else - check(git_diff_index_to_workdir(&diff, repo, NULL, &opts), "Diff"); - - if ((findopts.flags & GIT_DIFF_FIND_ALL) != 0) - check(git_diff_find_similar(diff, &findopts), - "finding renames and copies "); - - if (color >= 0) - fputs(colors[0], stdout); - - check(git_diff_print(diff, format, printer, &color), "Displaying diff"); - - if (color >= 0) - fputs(colors[0], stdout); - - git_diff_free(diff); - git_tree_free(t1); - git_tree_free(t2); - git_repository_free(repo); - - git_threads_shutdown(); - - return 0; } - diff --git a/examples/init.c b/examples/init.c index 4a379c6e3..1c371252e 100644 --- a/examples/init.c +++ b/examples/init.c @@ -1,4 +1,13 @@ /* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#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. @@ -8,179 +17,93 @@ * 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. - * - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. */ -#include <stdio.h> -#include <git2.h> -#include <stdlib.h> -#include <string.h> -#include <time.h> - -/* not actually good error handling */ -static void fail(const char *msg, const char *arg) -{ - if (arg) - fprintf(stderr, "%s %s\n", msg, arg); - else - fprintf(stderr, "%s\n", msg); - exit(1); -} - -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>] [--shared[=perms]] <directory>\n"); - exit(1); -} - -/* simple string prefix test used in argument parsing */ -static size_t is_prefixed(const char *arg, const char *pfx) -{ - size_t len = strlen(pfx); - return !strncmp(arg, pfx, len) ? len : 0; -} - -/* 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; -} - -/* forward declaration of helper to make an empty parent-less commit */ +/** Forward declarations of helpers */ +struct 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 opts *o, int argc, char *argv[]); int main(int argc, char *argv[]) { git_repository *repo = NULL; - int no_options = 1, quiet = 0, bare = 0, initial_commit = 0, i; - uint32_t shared = GIT_REPOSITORY_INIT_SHARED_UMASK; - const char *template = NULL, *gitdir = NULL, *dir = NULL; - size_t pfxlen; + struct opts o = { 1, 0, 0, 0, GIT_REPOSITORY_INIT_SHARED_UMASK, 0, 0, 0 }; git_threads_init(); - /* Process arguments */ + parse_opts(&o, argc, argv); - for (i = 1; i < argc; ++i) { - char *a = argv[i]; + /* Initialize repository. */ - if (a[0] == '-') - no_options = 0; - - if (a[0] != '-') { - if (dir != NULL) - usage("extra argument", a); - dir = a; - } - else if (!strcmp(a, "-q") || !strcmp(a, "--quiet")) - quiet = 1; - else if (!strcmp(a, "--bare")) - bare = 1; - else if ((pfxlen = is_prefixed(a, "--template=")) > 0) - template = a + pfxlen; - else if (!strcmp(a, "--separate-git-dir")) - gitdir = argv[++i]; - else if ((pfxlen = is_prefixed(a, "--separate-git-dir=")) > 0) - gitdir = a + pfxlen; - else if (!strcmp(a, "--shared")) - shared = GIT_REPOSITORY_INIT_SHARED_GROUP; - else if ((pfxlen = is_prefixed(a, "--shared=")) > 0) - shared = parse_shared(a + pfxlen); - else if (!strcmp(a, "--initial-commit")) - initial_commit = 1; - else - usage("unknown option", a); - } - - if (!dir) - usage("must specify directory to init", NULL); - - /* Initialize repository */ - - if (no_options) { - /* No options were specified, so let's demonstrate the default + if (o.no_options) { + /** + * No options were specified, so let's demonstrate the default * simple case of git_repository_init() API usage... */ - - if (git_repository_init(&repo, dir, 0) < 0) - fail("Could not initialize repository", dir); + 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 + /** + * Some command line options were specified, so we'll use the * extended init API to handle them */ - git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + git_repository_init_options initopts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + initopts.flags = GIT_REPOSITORY_INIT_MKPATH; - if (bare) - opts.flags |= GIT_REPOSITORY_INIT_BARE; + if (o.bare) + initopts.flags |= GIT_REPOSITORY_INIT_BARE; - if (template) { - opts.flags |= GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; - opts.template_path = template; + if (o.template) { + initopts.flags |= GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; + initopts.template_path = o.template; } - if (gitdir) { - /* if you specified a separate git directory, then initialize + 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) */ - opts.workdir_path = dir; - dir = gitdir; + initopts.workdir_path = o.dir; + o.dir = o.gitdir; } - if (shared != 0) - opts.mode = shared; + if (o.shared != 0) + initopts.mode = o.shared; - if (git_repository_init_ext(&repo, dir, &opts) < 0) - fail("Could not initialize repository", dir); + check_lg2(git_repository_init_ext(&repo, o.dir, &initopts), + "Could not initialize repository", NULL); } - /* Print a message to stdout like "git init" does */ + /** Print a message to stdout like "git init" does. */ - if (!quiet) { - if (bare || gitdir) - dir = git_repository_path(repo); + if (!o.quiet) { + if (o.bare || o.gitdir) + o.dir = git_repository_path(repo); else - dir = git_repository_workdir(repo); + o.dir = git_repository_workdir(repo); - printf("Initialized empty Git repository in %s\n", dir); + printf("Initialized empty Git repository in %s\n", o.dir); } - /* As an extension to the basic "git init" command, this example + /** + * 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 (initial_commit) { + if (o.initial_commit) { create_initial_commit(repo); printf("Created empty initial commit\n"); } @@ -191,7 +114,8 @@ int main(int argc, char *argv[]) return 0; } -/* Unlike regular "git init", this example shows how to create an initial +/** + * 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. */ @@ -202,31 +126,33 @@ static void create_initial_commit(git_repository *repo) git_oid tree_id, commit_id; git_tree *tree; - /* First use the config to initialize a commit signature for the user */ + /** First use the config to initialize a commit signature for the user. */ if (git_signature_default(&sig, repo) < 0) - fail("Unable to create a commit signature.", - "Perhaps 'user.name' and 'user.email' are not set"); + 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) - fail("Could not open repository index", NULL); + fatal("Could not open repository index", NULL); - /* Outside of this example, you could call git_index_add_bypath() + /** + * 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) - fail("Unable to write initial tree from index", NULL); + fatal("Unable to write initial tree from index", NULL); git_index_free(index); if (git_tree_lookup(&tree, repo, &tree_id) < 0) - fail("Could not look up initial tree", NULL); + fatal("Could not look up initial tree", NULL); - /* Ready to create the initial commit + /** + * 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, @@ -236,10 +162,85 @@ static void create_initial_commit(git_repository *repo) if (git_commit_create_v( &commit_id, repo, "HEAD", sig, sig, NULL, "Initial commit", tree, 0) < 0) - fail("Could not create the initial commit", NULL); + fatal("Could not create the initial commit", NULL); - /* Clean up so we don't leak memory */ + /** 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 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", NULL); +} diff --git a/examples/log.c b/examples/log.c index 81b056cc5..a36d4c95e 100644 --- a/examples/log.c +++ b/examples/log.c @@ -1,88 +1,205 @@ -#include <stdio.h> -#include <git2.h> -#include <stdlib.h> -#include <string.h> - -static void check(int error, const char *message, const char *arg) -{ - if (!error) - return; - if (arg) - fprintf(stderr, "%s '%s' (%d)\n", message, arg, error); - else - fprintf(stderr, "%s (%d)\n", message, error); - exit(1); -} - -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); -} - +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#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; }; -static void set_sorting(struct log_state *s, unsigned int sort_mode) +/** 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 skip, limit; + int min_parents, max_parents; + git_time_t before; + git_time_t after; + char *author; + char *committer; +}; + +/** 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); +static int match_with_parent(git_commit *commit, int i, git_diff_options *); + + +int main(int argc, char *argv[]) { - if (!s->repo) { - if (!s->repodir) s->repodir = "."; - check(git_repository_open_ext(&s->repo, s->repodir, 0, NULL), - "Could not open repository", s->repodir); - } + 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; - if (!s->walker) - check(git_revwalk_new(&s->walker, s->repo), - "Could not create revision walker", NULL); + git_threads_init(); - if (sort_mode == GIT_SORT_REVERSE) - s->sorting = s->sorting ^ GIT_SORT_REVERSE; - else - s->sorting = sort_mode | (s->sorting & GIT_SORT_REVERSE); + /** Parse arguments and set up revwalker. */ - git_revwalk_sorting(s->walker, s->sorting); + last_arg = parse_options(&s, &opt, argc, argv); + + 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 (count++ < opt.skip) + continue; + if (opt.limit != -1 && printed++ >= opt.limit) { + git_commit_free(commit); + break; + } + + print_commit(commit); + + 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); + git_repository_free(s.repo); + git_threads_shutdown(); + + 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(git_revwalk_new(&s->walker, s->repo), + 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(git_revwalk_push_head(s->walker), + check_lg2(git_revwalk_push_head(s->walker), "Could not find repository HEAD", NULL); else if (hide) - check(git_revwalk_hide(s->walker, git_object_id(obj)), + check_lg2(git_revwalk_hide(s->walker, git_object_id(obj)), "Reference does not refer to a commit", NULL); else - check(git_revwalk_push(s->walker, git_object_id(obj)), + 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; + /** Open repo on demand if it isn't already open. */ if (!s->repo) { if (!s->repodir) s->repodir = "."; - check(git_repository_open_ext(&s->repo, s->repodir, 0, NULL), + check_lg2(git_repository_open_ext(&s->repo, s->repodir, 0, NULL), "Could not open repository", s->repodir); } @@ -107,10 +224,11 @@ static int add_revision(struct log_state *s, const char *revstr) if ((revs.flags & GIT_REVPARSE_MERGE_BASE) != 0) { git_oid base; - check(git_merge_base(&base, s->repo, + 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(git_object_lookup(&revs.to, s->repo, &base, GIT_OBJ_COMMIT), + check_lg2( + git_object_lookup(&revs.to, s->repo, &base, GIT_OBJ_COMMIT), "Could not find merge base commit", NULL); push_rev(s, revs.to, hide); @@ -122,6 +240,30 @@ static int add_revision(struct log_state *s, const char *revstr) 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]; @@ -148,6 +290,7 @@ static void print_time(const git_time *intime, const char *prefix) 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) { char buf[GIT_OID_HEXSZ + 1]; @@ -182,54 +325,21 @@ static void print_commit(git_commit *commit) printf("\n"); } -static int print_diff( - const git_diff_delta *delta, - const git_diff_hunk *hunk, - const git_diff_line *line, - void *data) -{ - (void)delta; (void)hunk; (void)data; - - if (line->origin == GIT_DIFF_LINE_CONTEXT || - line->origin == GIT_DIFF_LINE_ADDITION || - line->origin == GIT_DIFF_LINE_DELETION) - fputc(line->origin, stdout); - - fwrite(line->content, 1, line->content_len, stdout); - return 0; -} - -static int match_int(int *value, const char *arg, int allow_negative) -{ - char *found; - *value = (int)strtol(arg, &found, 10); - return (found && *found == '\0' && (allow_negative || *value >= 0)); -} - -static int match_int_arg( - int *value, const char *arg, const char *pfx, int allow_negative) -{ - size_t pfxlen = strlen(pfx); - if (strncmp(arg, pfx, pfxlen) != 0) - return 0; - if (!match_int(value, arg + pfxlen, allow_negative)) - usage("Invalid value after argument", arg); - return 1; -} - -static int match_with_parent( - git_commit *commit, int i, git_diff_options *opts) +/** 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(git_commit_parent(&parent, commit, (size_t)i), "Get parent", NULL); - check(git_commit_tree(&a, parent), "Tree for parent", NULL); - check(git_commit_tree(&b, commit), "Tree for commit", NULL); - check(git_diff_tree_to_tree(&diff, git_commit_owner(commit), a, b, opts), - "Checking diff between parent and commit", NULL); + 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); @@ -241,170 +351,77 @@ static int match_with_parent( return ndeltas > 0; } -struct log_options { - int show_diff; - int skip, limit; - int min_parents, max_parents; - git_time_t before; - git_time_t after; - char *author; - char *committer; -}; - -int main(int argc, char *argv[]) +/** Print a usage message for the program. */ +static void usage(const char *message, const char *arg) { - int i, count = 0, printed = 0, parents; - char *a; - 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; + 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); +} - git_threads_init(); +/** 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(s, 0, sizeof(*s)); + s->sorting = GIT_SORT_TIME; - memset(&opt, 0, sizeof(opt)); - opt.max_parents = -1; - opt.limit = -1; + memset(opt, 0, sizeof(*opt)); + opt->max_parents = -1; + opt->limit = -1; - for (i = 1; i < argc; ++i) { - a = argv[i]; + for (args.pos = 1; args.pos < argc; ++args.pos) { + const char *a = argv[args.pos]; if (a[0] != '-') { - if (!add_revision(&s, a)) - ++count; - else /* try failed revision parse as filename */ + if (!add_revision(s, a)) + s->revisions++; + else + /** Try failed revision parse as filename. */ break; } else if (!strcmp(a, "--")) { - ++i; + ++args.pos; break; } else if (!strcmp(a, "--date-order")) - set_sorting(&s, GIT_SORT_TIME); + set_sorting(s, GIT_SORT_TIME); else if (!strcmp(a, "--topo-order")) - set_sorting(&s, GIT_SORT_TOPOLOGICAL); + set_sorting(s, GIT_SORT_TOPOLOGICAL); else if (!strcmp(a, "--reverse")) - set_sorting(&s, GIT_SORT_REVERSE); - else if (!strncmp(a, "--git-dir=", strlen("--git-dir="))) - s.repodir = a + strlen("--git-dir="); - else if (match_int_arg(&opt.skip, a, "--skip=", 0)) - /* found valid --skip */; - else if (match_int_arg(&opt.limit, a, "--max-count=", 0)) - /* found valid --max-count */; - else if (a[1] >= '0' && a[1] <= '9') { - if (!match_int(&opt.limit, a + 1, 0)) - usage("Invalid limit on number of commits", a); - } else if (!strcmp(a, "-n")) { - if (i + 1 == argc || !match_int(&opt.limit, argv[i + 1], 0)) - usage("Argument -n not followed by valid count", argv[i + 1]); - else - ++i; - } + set_sorting(s, GIT_SORT_REVERSE); + 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; + opt->min_parents = 2; else if (!strcmp(a, "--no-merges")) - opt.max_parents = 1; + opt->max_parents = 1; else if (!strcmp(a, "--no-min-parents")) - opt.min_parents = 0; + opt->min_parents = 0; else if (!strcmp(a, "--no-max-parents")) - opt.max_parents = -1; - else if (match_int_arg(&opt.max_parents, a, "--max-parents=", 1)) - /* found valid --max-parents */; - else if (match_int_arg(&opt.min_parents, a, "--min-parents=", 0)) - /* found valid --min_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; + opt->show_diff = 1; else usage("Unsupported argument", a); } - if (!count) - add_revision(&s, NULL); - - diffopts.pathspec.strings = &argv[i]; - diffopts.pathspec.count = argc - i; - if (diffopts.pathspec.count > 0) - check(git_pathspec_new(&ps, &diffopts.pathspec), - "Building pathspec", NULL); - - printed = count = 0; - - for (; !git_revwalk_next(&oid, s.walker); git_commit_free(commit)) { - check(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(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 (count++ < opt.skip) - continue; - if (opt.limit != -1 && printed++ >= opt.limit) { - git_commit_free(commit); - break; - } - - print_commit(commit); - - if (opt.show_diff) { - git_tree *a = NULL, *b = NULL; - git_diff *diff = NULL; - - if (parents > 1) - continue; - check(git_commit_tree(&b, commit), "Get tree", NULL); - if (parents == 1) { - git_commit *parent; - check(git_commit_parent(&parent, commit, 0), "Get parent", NULL); - check(git_commit_tree(&a, parent), "Tree for parent", NULL); - git_commit_free(parent); - } - - check(git_diff_tree_to_tree( - &diff, git_commit_owner(commit), a, b, &diffopts), - "Diff commit with parent", NULL); - check(git_diff_print(diff, GIT_DIFF_FORMAT_PATCH, print_diff, 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); - git_repository_free(s.repo); - git_threads_shutdown(); - - return 0; + return args.pos; } + diff --git a/examples/network/fetch.c b/examples/network/fetch.c index 4167ef3ca..ad01001d7 100644 --- a/examples/network/fetch.c +++ b/examples/network/fetch.c @@ -47,6 +47,11 @@ exit: return &data->ret; } +/** + * 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_HEXSZ+1], b_str[GIT_OID_HEXSZ+1]; @@ -66,6 +71,7 @@ static int update_cb(const char *refname, const git_oid *a, const git_oid *b, vo return 0; } +/** Entry point for this command */ int fetch(git_repository *repo, int argc, char **argv) { git_remote *remote = NULL; @@ -130,6 +136,11 @@ int fetch(git_repository *repo, int argc, char **argv) pthread_join(worker, NULL); #endif + /** + * 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. + */ if (stats->local_objects > 0) { printf("\rReceived %d/%d objects in %zu bytes (used %d local objects)\n", stats->indexed_objects, stats->total_objects, stats->received_bytes, stats->local_objects); diff --git a/examples/network/ls-remote.c b/examples/network/ls-remote.c index b65759ed3..18cd02367 100644 --- a/examples/network/ls-remote.c +++ b/examples/network/ls-remote.c @@ -4,6 +4,7 @@ #include <string.h> #include "common.h" +/** Callback to show each item */ static int show_ref__cb(git_remote_head *head, void *payload) { char oid[GIT_OID_HEXSZ + 1] = {0}; @@ -28,6 +29,10 @@ static int use_remote(git_repository *repo, char *name) goto cleanup; } + /** + * Connect to the remote and call the printing function for + * each of the remote references. + */ callbacks.credentials = cred_acquire_cb; git_remote_set_callbacks(remote, &callbacks); @@ -42,9 +47,7 @@ cleanup: return error; } -// This gets called to do the work. The remote can be given either as -// the name of a configured remote or an URL. - +/** Entry point for this command */ int ls_remote(git_repository *repo, int argc, char **argv) { int error; diff --git a/examples/rev-list.c b/examples/rev-list.c index 1fb7ebf9f..9330c644c 100644 --- a/examples/rev-list.c +++ b/examples/rev-list.c @@ -1,17 +1,35 @@ -#include <stdio.h> -#include <string.h> +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ -#include <git2.h> +#include "common.h" -static void check_error(int error_code, const char *action) +static int revwalk_parseopts(git_repository *repo, git_revwalk *walk, int nopts, char **opts); + +int main (int argc, char **argv) { - if (!error_code) - return; + git_repository *repo; + git_revwalk *walk; + git_oid oid; + char buf[41]; + + git_threads_init(); - const git_error *error = giterr_last(); - fprintf(stderr, "Error %d %s: %s\n", -error_code, action, - (error && error->message) ? error->message : "???"); - exit(1); + check_lg2(git_repository_open_ext(&repo, ".", 0, NULL), "opening repository", NULL); + check_lg2(git_revwalk_new(&walk, repo), "allocating revwalk", NULL); + check_lg2(revwalk_parseopts(repo, walk, argc-1, argv+1), "parsing options", NULL); + + while (!git_revwalk_next(&oid, walk)) { + git_oid_fmt(buf, &oid); + buf[40] = '\0'; + printf("%s\n", buf); + } + + git_threads_shutdown(); + return 0; } static int push_commit(git_revwalk *walk, const git_oid *oid, int hide) @@ -93,27 +111,3 @@ static int revwalk_parseopts(git_repository *repo, git_revwalk *walk, int nopts, return 0; } -int main (int argc, char **argv) -{ - int error; - git_repository *repo; - git_revwalk *walk; - git_oid oid; - char buf[41]; - - error = git_repository_open_ext(&repo, ".", 0, NULL); - check_error(error, "opening repository"); - - error = git_revwalk_new(&walk, repo); - check_error(error, "allocating revwalk"); - error = revwalk_parseopts(repo, walk, argc-1, argv+1); - check_error(error, "parsing options"); - - while (!git_revwalk_next(&oid, walk)) { - git_oid_fmt(buf, &oid); - buf[40] = '\0'; - printf("%s\n", buf); - } - - return 0; -} diff --git a/examples/rev-parse.c b/examples/rev-parse.c index cdbb61e46..64a02fe6e 100644 --- a/examples/rev-parse.c +++ b/examples/rev-parse.c @@ -1,17 +1,36 @@ -#include <stdio.h> -#include <git2.h> -#include <stdlib.h> -#include <string.h> +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ -static void check(int error, const char *message, const char *arg) +#include "common.h" + +/** Forward declarations for helpers. */ +struct parse_state { + git_repository *repo; + const char *repodir; + const char *spec; + int not; +}; +static void parse_opts(struct parse_state *ps, int argc, char *argv[]); +static int parse_revision(struct parse_state *ps); + + +int main(int argc, char *argv[]) { - if (!error) - return; - if (arg) - fprintf(stderr, "%s %s (%d)\n", message, arg, error); - else - fprintf(stderr, "%s(%d)\n", message, error); - exit(1); + struct parse_state ps = {0}; + + git_threads_init(); + parse_opts(&ps, argc, argv); + + check_lg2(parse_revision(&ps), "Parsing", NULL); + + git_repository_free(ps.repo); + git_threads_shutdown(); + + return 0; } static void usage(const char *message, const char *arg) @@ -24,13 +43,25 @@ static void usage(const char *message, const char *arg) exit(1); } -struct parse_state { - git_repository *repo; - const char *repodir; - int not; -}; +static void parse_opts(struct parse_state *ps, int argc, char *argv[]) +{ + struct args_info args = ARGS_INFO_INIT; -static int parse_revision(struct parse_state *ps, const char *revstr) + 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(struct parse_state *ps) { git_revspec rs; char str[GIT_OID_HEXSZ + 1]; @@ -38,11 +69,11 @@ static int parse_revision(struct parse_state *ps, const char *revstr) if (!ps->repo) { if (!ps->repodir) ps->repodir = "."; - check(git_repository_open_ext(&ps->repo, ps->repodir, 0, NULL), + check_lg2(git_repository_open_ext(&ps->repo, ps->repodir, 0, NULL), "Could not open repository from", ps->repodir); } - check(git_revparse(&rs, ps->repo, revstr), "Could not parse", revstr); + check_lg2(git_revparse(&rs, ps->repo, ps->spec), "Could not parse", ps->spec); if ((rs.flags & GIT_REVPARSE_SINGLE) != 0) { git_oid_tostr(str, sizeof(str), git_object_id(rs.from)); @@ -56,9 +87,9 @@ static int parse_revision(struct parse_state *ps, const char *revstr) if ((rs.flags & GIT_REVPARSE_MERGE_BASE) != 0) { git_oid base; - check(git_merge_base(&base, ps->repo, - git_object_id(rs.from), git_object_id(rs.to)), - "Could not find merge base", revstr); + check_lg2(git_merge_base(&base, ps->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); @@ -69,38 +100,9 @@ static int parse_revision(struct parse_state *ps, const char *revstr) git_object_free(rs.from); } else { - check(0, "Invalid results from git_revparse", revstr); + fatal("Invalid results from git_revparse", ps->spec); } return 0; } -int main(int argc, char *argv[]) -{ - int i; - char *a; - struct parse_state ps; - - git_threads_init(); - - memset(&ps, 0, sizeof(ps)); - - for (i = 1; i < argc; ++i) { - a = argv[i]; - - if (a[0] != '-') { - if (parse_revision(&ps, a) != 0) - break; - } else if (!strcmp(a, "--not")) - ps.not = !ps.not; - else if (!strncmp(a, "--git-dir=", strlen("--git-dir="))) - ps.repodir = a + strlen("--git-dir="); - else - usage("Cannot handle argument", a); - } - - git_repository_free(ps.repo); - git_threads_shutdown(); - - return 0; -} diff --git a/examples/showindex.c b/examples/showindex.c index 93718c89b..bf852384c 100644 --- a/examples/showindex.c +++ b/examples/showindex.c @@ -1,10 +1,14 @@ -#include <git2.h> -#include <stdio.h> -#include <string.h> +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" int main (int argc, char** argv) { - git_repository *repo = NULL; git_index *index; unsigned int i, ecount; char *dir = "."; @@ -14,28 +18,19 @@ int main (int argc, char** argv) git_threads_init(); + if (argc > 2) + fatal("usage: showindex [<repo-dir>]", NULL); if (argc > 1) dir = argv[1]; - if (!dir || argc > 2) { - fprintf(stderr, "usage: showindex [<repo-dir>]\n"); - return 1; - } dirlen = strlen(dir); if (dirlen > 5 && strcmp(dir + dirlen - 5, "index") == 0) { - if (git_index_open(&index, dir) < 0) { - fprintf(stderr, "could not open index: %s\n", dir); - return 1; - } + check_lg2(git_index_open(&index, dir), "could not open index", dir); } else { - if (git_repository_open_ext(&repo, dir, 0, NULL) < 0) { - fprintf(stderr, "could not open repository: %s\n", dir); - return 1; - } - if (git_repository_index(&index, repo) < 0) { - fprintf(stderr, "could not open repository index\n"); - return 1; - } + git_repository *repo; + 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); @@ -62,10 +57,7 @@ int main (int argc, char** argv) } git_index_free(index); - git_repository_free(repo); - git_threads_shutdown(); return 0; } - diff --git a/examples/status.c b/examples/status.c index 0d9f55f13..459e6fafb 100644 --- a/examples/status.c +++ b/examples/status.c @@ -4,30 +4,22 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ -#include <git2.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -enum { - FORMAT_DEFAULT = 0, - FORMAT_LONG = 1, - FORMAT_SHORT = 2, - FORMAT_PORCELAIN = 3, -}; -#define MAX_PATHSPEC 8 +#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 @@ -35,32 +27,83 @@ enum { * - A sample status formatter that matches the "short" format */ -static void check(int error, const char *message, const char *extra) +enum { + FORMAT_DEFAULT = 0, + FORMAT_LONG = 1, + FORMAT_SHORT = 2, + FORMAT_PORCELAIN = 3, +}; + +#define MAX_PATHSPEC 8 + +struct opts { + git_status_options statusopt; + char *repodir; + char *pathspec[MAX_PATHSPEC]; + int npaths; + int format; + int zterm; + int showbranch; +}; + +static void parse_opts(struct opts *o, int argc, char *argv[]); +static void show_branch(git_repository *repo, int format); +static void print_long(git_repository *repo, git_status_list *status); +static void print_short(git_repository *repo, git_status_list *status); + +int main(int argc, char *argv[]) { - const git_error *lg2err; - const char *lg2msg = "", *lg2spacer = ""; + git_repository *repo = NULL; + git_status_list *status; + struct opts o = { GIT_STATUS_OPTIONS_INIT, "." }; - if (!error) - return; + git_threads_init(); - if ((lg2err = giterr_last()) != NULL && lg2err->message != NULL) { - lg2msg = lg2err->message; - lg2spacer = " - "; - } + 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); + + /** + * Try to open the repository at the given path (or at the current + * directory if none was given). + */ + check_lg2(git_repository_open_ext(&repo, o.repodir, 0, NULL), + "Could not open repository", o.repodir); + + if (git_repository_is_bare(repo)) + fatal("Cannot report status on bare repository", + git_repository_path(repo)); - if (extra) - fprintf(stderr, "%s '%s' [%d]%s%s\n", - message, extra, error, lg2spacer, lg2msg); + /** + * Run status on the repository + * + * Because we want to simluate a full "git status" run and want to + * support some command line options, we use `git_status_foreach_ext()` + * instead of just the plain status call. This allows (a) iterating + * over the index and then the workdir and (b) extra flags that control + * which files are included. If you just want simple status (e.g. to + * enumerate files that are modified) then you probably don't need the + * extended API. + */ + 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.format == FORMAT_LONG) + print_long(repo, status); else - fprintf(stderr, "%s [%d]%s%s\n", - message, error, lg2spacer, lg2msg); + print_short(repo, status); - exit(1); -} + git_status_list_free(status); + git_repository_free(repo); + git_threads_shutdown(); -static void fail(const char *message) -{ - check(-1, message, NULL); + return 0; } static void show_branch(git_repository *repo, int format) @@ -78,7 +121,7 @@ static void show_branch(git_repository *repo, int format) if (!strncmp(branch, "refs/heads/", strlen("refs/heads/"))) branch += strlen("refs/heads/"); } else - check(error, "failed to get current branch", NULL); + check_lg2(error, "failed to get current branch", NULL); if (format == FORMAT_LONG) printf("# On branch %s\n", @@ -99,7 +142,7 @@ static void print_long(git_repository *repo, git_status_list *status) (void)repo; - /* print index changes */ + /** Print index changes. */ for (i = 0; i < maxi; ++i) { char *istatus = NULL; @@ -148,7 +191,7 @@ static void print_long(git_repository *repo, git_status_list *status) } header = 0; - /* print workdir changes to tracked files */ + /** Print workdir changes to tracked files. */ for (i = 0; i < maxi; ++i) { char *wstatus = NULL; @@ -193,7 +236,7 @@ static void print_long(git_repository *repo, git_status_list *status) } header = 0; - /* print untracked files */ + /** Print untracked files. */ header = 0; @@ -215,7 +258,7 @@ static void print_long(git_repository *repo, git_status_list *status) header = 0; - /* print ignored files */ + /** Print ignored files. */ for (i = 0; i < maxi; ++i) { s = git_status_byindex(status, i); @@ -341,103 +384,58 @@ static void print_short(git_repository *repo, git_status_list *status) } } -int main(int argc, char *argv[]) +static void parse_opts(struct opts *o, int argc, char *argv[]) { - git_repository *repo = NULL; - int i, npaths = 0, format = FORMAT_DEFAULT, zterm = 0, showbranch = 0; - git_status_options opt = GIT_STATUS_OPTIONS_INIT; - git_status_list *status; - char *repodir = ".", *pathspec[MAX_PATHSPEC]; + struct args_info args = ARGS_INFO_INIT; - opt.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; - opt.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | - GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX | - GIT_STATUS_OPT_SORT_CASE_SENSITIVELY; + for (args.pos = 1; args.pos < argc; ++args.pos) { + char *a = argv[args.pos]; - for (i = 1; i < argc; ++i) { - if (argv[i][0] != '-') { - if (npaths < MAX_PATHSPEC) - pathspec[npaths++] = argv[i]; + if (a[0] != '-') { + if (o->npaths < MAX_PATHSPEC) + o->pathspec[o->npaths++] = a; else - fail("Example only supports a limited pathspec"); + fatal("Example only supports a limited pathspec", NULL); } - else if (!strcmp(argv[i], "-s") || !strcmp(argv[i], "--short")) - format = FORMAT_SHORT; - else if (!strcmp(argv[i], "--long")) - format = FORMAT_LONG; - else if (!strcmp(argv[i], "--porcelain")) - format = FORMAT_PORCELAIN; - else if (!strcmp(argv[i], "-b") || !strcmp(argv[i], "--branch")) - showbranch = 1; - else if (!strcmp(argv[i], "-z")) { - zterm = 1; - if (format == FORMAT_DEFAULT) - format = FORMAT_PORCELAIN; + 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(argv[i], "--ignored")) - opt.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED; - else if (!strcmp(argv[i], "-uno") || - !strcmp(argv[i], "--untracked-files=no")) - opt.flags &= ~GIT_STATUS_OPT_INCLUDE_UNTRACKED; - else if (!strcmp(argv[i], "-unormal") || - !strcmp(argv[i], "--untracked-files=normal")) - opt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; - else if (!strcmp(argv[i], "-uall") || - !strcmp(argv[i], "--untracked-files=all")) - opt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED | + 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(argv[i], "--ignore-submodules=all")) - opt.flags |= GIT_STATUS_OPT_EXCLUDE_SUBMODULES; - else if (!strncmp(argv[i], "--git-dir=", strlen("--git-dir="))) - repodir = argv[i] + strlen("--git-dir="); + 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 - check(-1, "Unsupported option", argv[i]); + check_lg2(-1, "Unsupported option", a); } - if (format == FORMAT_DEFAULT) - format = FORMAT_LONG; - if (format == FORMAT_LONG) - showbranch = 1; - if (npaths > 0) { - opt.pathspec.strings = pathspec; - opt.pathspec.count = npaths; + 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; } - - /* - * Try to open the repository at the given path (or at the current - * directory if none was given). - */ - check(git_repository_open_ext(&repo, repodir, 0, NULL), - "Could not open repository", repodir); - - if (git_repository_is_bare(repo)) - fail("Cannot report status on bare repository"); - - /* - * Run status on the repository - * - * Because we want to simluate a full "git status" run and want to - * support some command line options, we use `git_status_foreach_ext()` - * instead of just the plain status call. This allows (a) iterating - * over the index and then the workdir and (b) extra flags that control - * which files are included. If you just want simple status (e.g. to - * enumerate files that are modified) then you probably don't need the - * extended API. - */ - check(git_status_list_new(&status, repo, &opt), - "Could not get status", NULL); - - if (showbranch) - show_branch(repo, format); - - if (format == FORMAT_LONG) - print_long(repo, status); - else - print_short(repo, status); - - git_status_list_free(status); - git_repository_free(repo); - - return 0; } - |