summaryrefslogtreecommitdiffstats
path: root/examples/blame.c
diff options
context:
space:
mode:
Diffstat (limited to 'examples/blame.c')
-rw-r--r--examples/blame.c198
1 files changed, 198 insertions, 0 deletions
diff --git a/examples/blame.c b/examples/blame.c
new file mode 100644
index 0000000..77087a5
--- /dev/null
+++ b/examples/blame.c
@@ -0,0 +1,198 @@
+/*
+ * libgit2 "blame" example - shows how to use the blame API
+ *
+ * Written by the libgit2 contributors
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty.
+ *
+ * You should have received a copy of the CC0 Public Domain Dedication along
+ * with this software. If not, see
+ * <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+#include "common.h"
+
+/**
+ * This example demonstrates how to invoke the libgit2 blame API to roughly
+ * simulate the output of `git blame` and a few of its command line arguments.
+ */
+
+struct blame_opts {
+ char *path;
+ char *commitspec;
+ int C;
+ int M;
+ int start_line;
+ int end_line;
+ int F;
+};
+static void parse_opts(struct blame_opts *o, int argc, char *argv[]);
+
+int lg2_blame(git_repository *repo, int argc, char *argv[])
+{
+ int line, break_on_null_hunk;
+ git_object_size_t i, rawsize;
+ char spec[1024] = {0};
+ struct blame_opts o = {0};
+ const char *rawdata;
+ git_revspec revspec = {0};
+ git_blame_options blameopts = GIT_BLAME_OPTIONS_INIT;
+ git_blame *blame = NULL;
+ git_blob *blob;
+ git_object *obj;
+
+ parse_opts(&o, argc, argv);
+ if (o.M) blameopts.flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES;
+ if (o.C) blameopts.flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES;
+ if (o.F) blameopts.flags |= GIT_BLAME_FIRST_PARENT;
+
+ /**
+ * The commit range comes in "committish" form. Use the rev-parse API to
+ * nail down the end points.
+ */
+ if (o.commitspec) {
+ check_lg2(git_revparse(&revspec, repo, o.commitspec), "Couldn't parse commit spec", NULL);
+ if (revspec.flags & GIT_REVSPEC_SINGLE) {
+ git_oid_cpy(&blameopts.newest_commit, git_object_id(revspec.from));
+ git_object_free(revspec.from);
+ } else {
+ git_oid_cpy(&blameopts.oldest_commit, git_object_id(revspec.from));
+ git_oid_cpy(&blameopts.newest_commit, git_object_id(revspec.to));
+ git_object_free(revspec.from);
+ git_object_free(revspec.to);
+ }
+ }
+
+ /** Run the blame. */
+ check_lg2(git_blame_file(&blame, repo, o.path, &blameopts), "Blame error", NULL);
+
+ /**
+ * Get the raw data inside the blob for output. We use the
+ * `committish:path/to/file.txt` format to find it.
+ */
+ if (git_oid_is_zero(&blameopts.newest_commit))
+ strcpy(spec, "HEAD");
+ else
+ git_oid_tostr(spec, sizeof(spec), &blameopts.newest_commit);
+ strcat(spec, ":");
+ strcat(spec, o.path);
+
+ check_lg2(git_revparse_single(&obj, repo, spec), "Object lookup error", NULL);
+ check_lg2(git_blob_lookup(&blob, repo, git_object_id(obj)), "Blob lookup error", NULL);
+ git_object_free(obj);
+
+ rawdata = git_blob_rawcontent(blob);
+ rawsize = git_blob_rawsize(blob);
+
+ /** Produce the output. */
+ line = 1;
+ i = 0;
+ break_on_null_hunk = 0;
+ while (i < rawsize) {
+ const char *eol = memchr(rawdata + i, '\n', (size_t)(rawsize - i));
+ char oid[10] = {0};
+ const git_blame_hunk *hunk = git_blame_get_hunk_byline(blame, line);
+
+ if (break_on_null_hunk && !hunk)
+ break;
+
+ if (hunk) {
+ char sig[128] = {0};
+ break_on_null_hunk = 1;
+
+ git_oid_tostr(oid, 10, &hunk->final_commit_id);
+ snprintf(sig, 30, "%s <%s>", hunk->final_signature->name, hunk->final_signature->email);
+
+ printf("%s ( %-30s %3d) %.*s\n",
+ oid,
+ sig,
+ line,
+ (int)(eol - rawdata - i),
+ rawdata + i);
+ }
+
+ i = (int)(eol - rawdata + 1);
+ line++;
+ }
+
+ /** Cleanup. */
+ git_blob_free(blob);
+ git_blame_free(blame);
+
+ return 0;
+}
+
+/** Tell the user how to make this thing work. */
+static void usage(const char *msg, const char *arg)
+{
+ if (msg && arg)
+ fprintf(stderr, "%s: %s\n", msg, arg);
+ else if (msg)
+ fprintf(stderr, "%s\n", msg);
+ fprintf(stderr, "usage: blame [options] [<commit range>] <path>\n");
+ fprintf(stderr, "\n");
+ fprintf(stderr, " <commit range> example: `HEAD~10..HEAD`, or `1234abcd`\n");
+ fprintf(stderr, " -L <n,m> process only line range n-m, counting from 1\n");
+ fprintf(stderr, " -M find line moves within and across files\n");
+ fprintf(stderr, " -C find line copies within and across files\n");
+ fprintf(stderr, " -F follow only the first parent commits\n");
+ fprintf(stderr, "\n");
+ exit(1);
+}
+
+/** Parse the arguments. */
+static void parse_opts(struct blame_opts *o, int argc, char *argv[])
+{
+ int i;
+ char *bare_args[3] = {0};
+
+ if (argc < 2) usage(NULL, NULL);
+
+ for (i=1; i<argc; i++) {
+ char *a = argv[i];
+
+ if (a[0] != '-') {
+ int i=0;
+ while (bare_args[i] && i < 3) ++i;
+ if (i >= 3)
+ usage("Invalid argument set", NULL);
+ bare_args[i] = a;
+ }
+ else if (!strcmp(a, "--"))
+ continue;
+ else if (!strcasecmp(a, "-M"))
+ o->M = 1;
+ else if (!strcasecmp(a, "-C"))
+ o->C = 1;
+ else if (!strcasecmp(a, "-F"))
+ o->F = 1;
+ else if (!strcasecmp(a, "-L")) {
+ i++; a = argv[i];
+ if (i >= argc) fatal("Not enough arguments to -L", NULL);
+ check_lg2(sscanf(a, "%d,%d", &o->start_line, &o->end_line)-2, "-L format error", NULL);
+ }
+ else {
+ /* commit range */
+ if (o->commitspec) fatal("Only one commit spec allowed", NULL);
+ o->commitspec = a;
+ }
+ }
+
+ /* Handle the bare arguments */
+ if (!bare_args[0]) usage("Please specify a path", NULL);
+ o->path = bare_args[0];
+ if (bare_args[1]) {
+ /* <commitspec> <path> */
+ o->path = bare_args[1];
+ o->commitspec = bare_args[0];
+ }
+ if (bare_args[2]) {
+ /* <oldcommit> <newcommit> <path> */
+ char spec[128] = {0};
+ o->path = bare_args[2];
+ sprintf(spec, "%s..%s", bare_args[0], bare_args[1]);
+ o->commitspec = spec;
+ }
+}