summaryrefslogtreecommitdiff
path: root/examples/log.c
diff options
context:
space:
mode:
Diffstat (limited to 'examples/log.c')
-rw-r--r--examples/log.c481
1 files changed, 249 insertions, 232 deletions
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;
}
+