summaryrefslogtreecommitdiff
path: root/examples/log.c
diff options
context:
space:
mode:
authorBen Straub <bs@github.com>2013-09-16 16:12:31 -0700
committerBen Straub <bs@github.com>2013-09-16 16:12:31 -0700
commit549931679a77b280eb1f36afeda8930eb31d70f7 (patch)
tree2744e3e198ad146bae72f35369bbeb4f8028c8c3 /examples/log.c
parent1a68c168a6cdbe0db6e44fb582a7026a7d536c9d (diff)
parent8821c9aa5baf31e21c21825e8c91c765e6631e7f (diff)
downloadlibgit2-549931679a77b280eb1f36afeda8930eb31d70f7.tar.gz
Merge branch 'development' into blame_rebased
Conflicts: include/git2.h
Diffstat (limited to 'examples/log.c')
-rw-r--r--examples/log.c406
1 files changed, 406 insertions, 0 deletions
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;
+}