diff options
| author | Ben Straub <bs@github.com> | 2013-09-16 16:12:31 -0700 |
|---|---|---|
| committer | Ben Straub <bs@github.com> | 2013-09-16 16:12:31 -0700 |
| commit | 549931679a77b280eb1f36afeda8930eb31d70f7 (patch) | |
| tree | 2744e3e198ad146bae72f35369bbeb4f8028c8c3 /examples | |
| parent | 1a68c168a6cdbe0db6e44fb582a7026a7d536c9d (diff) | |
| parent | 8821c9aa5baf31e21c21825e8c91c765e6631e7f (diff) | |
| download | libgit2-549931679a77b280eb1f36afeda8930eb31d70f7.tar.gz | |
Merge branch 'development' into blame_rebased
Conflicts:
include/git2.h
Diffstat (limited to 'examples')
| -rw-r--r-- | examples/CMakeLists.txt | 14 | ||||
| -rw-r--r-- | examples/Makefile | 2 | ||||
| -rw-r--r-- | examples/add.c | 143 | ||||
| -rw-r--r-- | examples/init.c | 245 | ||||
| -rw-r--r-- | examples/log.c | 406 | ||||
| -rw-r--r-- | examples/network/Makefile | 3 | ||||
| -rw-r--r-- | examples/network/clone.c | 33 | ||||
| -rw-r--r-- | examples/network/common.c | 34 | ||||
| -rw-r--r-- | examples/network/common.h | 6 | ||||
| -rw-r--r-- | examples/network/fetch.c | 1 | ||||
| -rw-r--r-- | examples/network/ls-remote.c | 41 | ||||
| -rw-r--r-- | examples/rev-parse.c | 106 | ||||
| -rw-r--r-- | examples/status.c | 443 |
13 files changed, 1410 insertions, 67 deletions
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 000000000..c20a6df3b --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,14 @@ +FILE(GLOB_RECURSE SRC_EXAMPLE_GIT2 network/*.c network/*.h) +ADD_EXECUTABLE(cgit2 ${SRC_EXAMPLE_GIT2}) +IF(WIN32 OR ANDROID) + TARGET_LINK_LIBRARIES(cgit2 git2) +ELSE() + TARGET_LINK_LIBRARIES(cgit2 git2 pthread) +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) +ENDFOREACH() diff --git a/examples/Makefile b/examples/Makefile index c5d555566..d53ed8241 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -3,7 +3,7 @@ CC = gcc CFLAGS = -g -I../include -I../src -Wall -Wextra -Wmissing-prototypes -Wno-missing-field-initializers LFLAGS = -L../build -lgit2 -lz -APPS = general showindex diff rev-list cat-file +APPS = general showindex diff rev-list cat-file status log rev-parse init all: $(APPS) diff --git a/examples/add.c b/examples/add.c new file mode 100644 index 000000000..a0edf4376 --- /dev/null +++ b/examples/add.c @@ -0,0 +1,143 @@ +#include <git2.h> +#include <stdio.h> +#include <string.h> +#include <assert.h> + +enum print_options { + SKIP = 1, + VERBOSE = 2, + UPDATE = 4, +}; + +struct print_payload { + enum print_options options; + git_repository *repo; +}; + +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; +} + +int print_matched_cb(const char *path, const char *matched_pathspec, void *payload) +{ + struct print_payload p = *(struct print_payload*)(payload); + int ret; + git_status_t status; + (void)matched_pathspec; + + if (git_status_file(&status, p.repo, path)) { + return -1; //abort + } + + if (status & GIT_STATUS_WT_MODIFIED || + status & GIT_STATUS_WT_NEW) { + printf("add '%s'\n", path); + ret = 0; + } else { + ret = 1; + } + + if(p.options & SKIP) { + ret = 1; + } + + return ret; +} + +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"); +} + + +int main (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}; + + for (i = 1; i < argc; ++i) { + if (argv[i][0] != '-') { + break; + } + else if(!strcmp(argv[i], "--verbose") || !strcmp(argv[i], "-v")) { + options |= VERBOSE; + } + else if(!strcmp(argv[i], "--dry-run") || !strcmp(argv[i], "-n")) { + options |= SKIP; + } + else if(!strcmp(argv[i], "--update") || !strcmp(argv[i], "-u")) { + options |= UPDATE; + } + else if(!strcmp(argv[i], "-h")) { + print_usage(); + break; + } + else if(!strcmp(argv[i], "--")) { + i++; + break; + } + else { + fprintf(stderr, "Unsupported option %s.\n", argv[i]); + print_usage(); + return 1; + } + } + + 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; +} diff --git a/examples/init.c b/examples/init.c new file mode 100644 index 000000000..4a379c6e3 --- /dev/null +++ b/examples/init.c @@ -0,0 +1,245 @@ +/* + * 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. + * + * 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 */ +static void create_initial_commit(git_repository *repo); + + +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; + + git_threads_init(); + + /* Process arguments */ + + for (i = 1; i < argc; ++i) { + char *a = argv[i]; + + 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 + * simple case of git_repository_init() API usage... + */ + + if (git_repository_init(&repo, dir, 0) < 0) + fail("Could not initialize repository", dir); + } + else { + /* 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; + + if (bare) + opts.flags |= GIT_REPOSITORY_INIT_BARE; + + if (template) { + opts.flags |= GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; + opts.template_path = template; + } + + if (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; + } + + if (shared != 0) + opts.mode = shared; + + if (git_repository_init_ext(&repo, dir, &opts) < 0) + fail("Could not initialize repository", dir); + } + + /* Print a message to stdout like "git init" does */ + + if (!quiet) { + if (bare || gitdir) + dir = git_repository_path(repo); + else + dir = git_repository_workdir(repo); + + printf("Initialized empty Git repository in %s\n", 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 (initial_commit) { + create_initial_commit(repo); + printf("Created empty initial commit\n"); + } + + git_repository_free(repo); + git_threads_shutdown(); + + 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) + fail("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); + + /* 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); + + git_index_free(index); + + if (git_tree_lookup(&tree, repo, &tree_id) < 0) + fail("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) + fail("Could not create the initial commit", NULL); + + /* Clean up so we don't leak memory */ + + git_tree_free(tree); + git_signature_free(sig); +} diff --git a/examples/log.c b/examples/log.c new file mode 100644 index 000000000..413c211e4 --- /dev/null +++ b/examples/log.c @@ -0,0 +1,406 @@ +#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); +} + +struct log_state { + git_repository *repo; + const char *repodir; + git_revwalk *walker; + int hide; + int sorting; +}; + +static void set_sorting(struct log_state *s, unsigned int sort_mode) +{ + 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); + } + + if (!s->walker) + check(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); +} + +static void push_rev(struct log_state *s, git_object *obj, int hide) +{ + hide = s->hide ^ hide; + + if (!s->walker) { + check(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), + "Could not find repository HEAD", NULL); + else if (hide) + check(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)), + "Reference does not refer to a commit", NULL); + + git_object_free(obj); +} + +static int add_revision(struct log_state *s, const char *revstr) +{ + git_revspec revs; + int hide = 0; + + 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); + } + + if (!revstr) { + push_rev(s, NULL, hide); + return 0; + } + + if (*revstr == '^') { + revs.flags = GIT_REVPARSE_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_REVPARSE_SINGLE) != 0) + push_rev(s, revs.from, hide); + else { + push_rev(s, revs.to, hide); + + if ((revs.flags & GIT_REVPARSE_MERGE_BASE) != 0) { + git_oid base; + check(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), + "Could not find merge base commit", NULL); + + push_rev(s, revs.to, hide); + } + + push_rev(s, revs.from, !hide); + } + + return 0; +} + +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); + + gmtime_r(&t, &intm); + strftime(out, sizeof(out), "%a %b %e %T %Y", &intm); + + printf("%s%s %c%02d%02d\n", prefix, out, sign, hours, minutes); +} + +static void print_commit(git_commit *commit) +{ + char buf[GIT_OID_HEXSZ + 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 ((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"); +} + +static int print_diff( + const git_diff_delta *delta, + const git_diff_range *range, + char usage, + const char *line, + size_t line_len, + void *data) +{ + (void)delta; (void)range; (void)usage; (void)line_len; (void)data; + fputs(line, 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) +{ + git_commit *parent; + git_tree *a, *b; + git_diff_list *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); + + ndeltas = (int)git_diff_num_deltas(diff); + + git_diff_list_free(diff); + git_tree_free(a); + git_tree_free(b); + git_commit_free(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[]) +{ + 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; + + git_threads_init(); + + memset(&s, 0, sizeof(s)); + s.sorting = GIT_SORT_TIME; + + memset(&opt, 0, sizeof(opt)); + opt.max_parents = -1; + opt.limit = -1; + + for (i = 1; i < argc; ++i) { + a = argv[i]; + + if (a[0] != '-') { + if (!add_revision(&s, a)) + ++count; + else /* try failed revision parse as filename */ + break; + } else if (!strcmp(a, "--")) { + ++i; + 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 (!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; + } + 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, a, "--max-parents=", 1)) + /* found valid --max-parents */; + else if (match_int_arg(&opt.min_parents, a, "--min-parents=", 0)) + /* found valid --min_parents */; + else if (!strcmp(a, "-p") || !strcmp(a, "-u") || !strcmp(a, "--patch")) + 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_list *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_patch(diff, print_diff, NULL), + "Displaying diff", NULL); + + git_diff_list_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; +} diff --git a/examples/network/Makefile b/examples/network/Makefile index 810eb705b..f65c6cb26 100644 --- a/examples/network/Makefile +++ b/examples/network/Makefile @@ -11,7 +11,8 @@ OBJECTS = \ ls-remote.o \ fetch.o \ clone.o \ - index-pack.o + index-pack.o \ + common.o all: $(OBJECTS) $(CC) $(CFLAGS) $(LDFLAGS) -o git2 $(OBJECTS) $(LIBRARIES) diff --git a/examples/network/clone.c b/examples/network/clone.c index 00c25c1ae..a09a94728 100644 --- a/examples/network/clone.c +++ b/examples/network/clone.c @@ -9,19 +9,6 @@ # include <unistd.h> #endif -/* Shamelessly borrowed from http://stackoverflow.com/questions/3417837/ - * with permission of the original author, Martin Pool. - * http://sourcefrog.net/weblog/software/languages/C/unused.html - */ -#ifdef UNUSED -#elif defined(__GNUC__) -# define UNUSED(x) UNUSED_ ## x __attribute__((unused)) -#elif defined(__LCLINT__) -# define UNUSED(x) /*@unused@*/ x -#else -# define UNUSED(x) x -#endif - typedef struct progress_data { git_transfer_progress fetch_progress; size_t completed_steps; @@ -63,24 +50,6 @@ static void checkout_progress(const char *path, size_t cur, size_t tot, void *pa print_progress(pd); } -static int cred_acquire(git_cred **out, - const char * UNUSED(url), - const char * UNUSED(username_from_url), - unsigned int UNUSED(allowed_types), - void * UNUSED(payload)) -{ - char username[128] = {0}; - char password[128] = {0}; - - printf("Username: "); - scanf("%s", username); - - /* Yup. Right there on your terminal. Careful where you copy/paste output. */ - printf("Password: "); - scanf("%s", password); - - return git_cred_userpass_plaintext_new(out, username, password); -} int do_clone(git_repository *repo, int argc, char **argv) { @@ -107,7 +76,7 @@ int do_clone(git_repository *repo, int argc, char **argv) clone_opts.checkout_opts = checkout_opts; clone_opts.fetch_progress_cb = &fetch_progress; clone_opts.fetch_progress_payload = &pd; - clone_opts.cred_acquire_cb = cred_acquire; + clone_opts.cred_acquire_cb = cred_acquire_cb; // Do the clone error = git_clone(&cloned_repo, url, path, &clone_opts); diff --git a/examples/network/common.c b/examples/network/common.c new file mode 100644 index 000000000..d123eedbd --- /dev/null +++ b/examples/network/common.c @@ -0,0 +1,34 @@ +#include "common.h" +#include <stdio.h> + +/* Shamelessly borrowed from http://stackoverflow.com/questions/3417837/ + * with permission of the original author, Martin Pool. + * http://sourcefrog.net/weblog/software/languages/C/unused.html + */ +#ifdef UNUSED +#elif defined(__GNUC__) +# define UNUSED(x) UNUSED_ ## x __attribute__((unused)) +#elif defined(__LCLINT__) +# define UNUSED(x) /*@unused@*/ x +#else +# define UNUSED(x) x +#endif + +int cred_acquire_cb(git_cred **out, + const char * UNUSED(url), + const char * UNUSED(username_from_url), + unsigned int UNUSED(allowed_types), + void * UNUSED(payload)) +{ + char username[128] = {0}; + char password[128] = {0}; + + printf("Username: "); + scanf("%s", username); + + /* Yup. Right there on your terminal. Careful where you copy/paste output. */ + printf("Password: "); + scanf("%s", password); + + return git_cred_userpass_plaintext_new(out, username, password); +} diff --git a/examples/network/common.h b/examples/network/common.h index a4cfa1a7e..1b09caad4 100644 --- a/examples/network/common.h +++ b/examples/network/common.h @@ -12,6 +12,12 @@ int fetch(git_repository *repo, int argc, char **argv); int index_pack(git_repository *repo, int argc, char **argv); int do_clone(git_repository *repo, int argc, char **argv); +int cred_acquire_cb(git_cred **out, + const char * url, + const char * username_from_url, + unsigned int allowed_types, + void *payload); + #ifndef PRIuZ /* Define the printf format specifer to use for size_t output */ #if defined(_MSC_VER) || defined(__MINGW32__) diff --git a/examples/network/fetch.c b/examples/network/fetch.c index 6020ec6ec..ce016ce0b 100644 --- a/examples/network/fetch.c +++ b/examples/network/fetch.c @@ -92,6 +92,7 @@ int fetch(git_repository *repo, int argc, char **argv) callbacks.update_tips = &update_cb; callbacks.progress = &progress_cb; git_remote_set_callbacks(remote, &callbacks); + git_remote_set_cred_acquire_cb(remote, &cred_acquire_cb, NULL); // Set up the information for the background worker thread data.remote = remote; diff --git a/examples/network/ls-remote.c b/examples/network/ls-remote.c index 252011828..b22ac47a0 100644 --- a/examples/network/ls-remote.c +++ b/examples/network/ls-remote.c @@ -14,31 +14,6 @@ static int show_ref__cb(git_remote_head *head, void *payload) return 0; } -static int use_unnamed(git_repository *repo, const char *url) -{ - git_remote *remote = NULL; - int error; - - // Create an instance of a remote from the URL. The transport to use - // is detected from the URL - error = git_remote_create_inmemory(&remote, repo, NULL, url); - if (error < 0) - goto cleanup; - - // When connecting, the underlying code needs to know wether we - // want to push or fetch - error = git_remote_connect(remote, GIT_DIRECTION_FETCH); - if (error < 0) - goto cleanup; - - // With git_remote_ls we can retrieve the advertised heads - error = git_remote_ls(remote, &show_ref__cb, NULL); - -cleanup: - git_remote_free(remote); - return error; -} - static int use_remote(git_repository *repo, char *name) { git_remote *remote = NULL; @@ -46,8 +21,13 @@ static int use_remote(git_repository *repo, char *name) // Find the remote by name error = git_remote_load(&remote, repo, name); - if (error < 0) - goto cleanup; + if (error < 0) { + error = git_remote_create_inmemory(&remote, repo, NULL, name); + if (error < 0) + goto cleanup; + } + + git_remote_set_cred_acquire_cb(remote, &cred_acquire_cb, NULL); error = git_remote_connect(remote, GIT_DIRECTION_FETCH); if (error < 0) @@ -72,12 +52,7 @@ int ls_remote(git_repository *repo, int argc, char **argv) return EXIT_FAILURE; } - /* If there's a ':' in the name, assume it's an URL */ - if (strchr(argv[1], ':') != NULL) { - error = use_unnamed(repo, argv[1]); - } else { - error = use_remote(repo, argv[1]); - } + error = use_remote(repo, argv[1]); return error; } diff --git a/examples/rev-parse.c b/examples/rev-parse.c new file mode 100644 index 000000000..cdbb61e46 --- /dev/null +++ b/examples/rev-parse.c @@ -0,0 +1,106 @@ +#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: rev-parse [ --option ] <args>...\n"); + exit(1); +} + +struct parse_state { + git_repository *repo; + const char *repodir; + int not; +}; + +static int parse_revision(struct parse_state *ps, const char *revstr) +{ + git_revspec rs; + char str[GIT_OID_HEXSZ + 1]; + + if (!ps->repo) { + if (!ps->repodir) + ps->repodir = "."; + check(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); + + if ((rs.flags & GIT_REVPARSE_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_REVPARSE_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_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); + + 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 { + check(0, "Invalid results from git_revparse", revstr); + } + + 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/status.c b/examples/status.c new file mode 100644 index 000000000..689098415 --- /dev/null +++ b/examples/status.c @@ -0,0 +1,443 @@ +/* + * 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 <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 + +/* + * 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 + */ + +static void check(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); +} + +static void fail(const char *message) +{ + check(-1, message, NULL); +} + +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_EORPHANEDHEAD || error == GIT_ENOTFOUND) + branch = NULL; + else if (!error) { + branch = git_reference_name(head); + if (!strncmp(branch, "refs/heads/", strlen("refs/heads/"))) + branch += strlen("refs/heads/"); + } else + check(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); +} + +static void print_long(git_repository *repo, 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; + + (void)repo; + + /* 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); + + if (s->status == GIT_STATUS_CURRENT || s->index_to_workdir == NULL) + continue; + + 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"); + } + header = 0; + + /* 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"); +} + +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; + + if (s->index_to_workdir && + s->index_to_workdir->new_file.mode == GIT_FILEMODE_COMMIT) + { + git_submodule *sm = NULL; + unsigned int smstatus = 0; + + if (!git_submodule_lookup( + &sm, repo, s->index_to_workdir->new_file.path) && + !git_submodule_status(&smstatus, sm)) + { + 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)"; + } + } + + 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); + } +} + +int main(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]; + + 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 (i = 1; i < argc; ++i) { + if (argv[i][0] != '-') { + if (npaths < MAX_PATHSPEC) + pathspec[npaths++] = argv[i]; + else + fail("Example only supports a limited pathspec"); + } + 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(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 | + 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 + check(-1, "Unsupported option", argv[i]); + } + + if (format == FORMAT_DEFAULT) + format = FORMAT_LONG; + if (format == FORMAT_LONG) + showbranch = 1; + if (npaths > 0) { + opt.pathspec.strings = pathspec; + opt.pathspec.count = 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; +} + |
