Add blame example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
diff --git a/examples/.gitignore b/examples/.gitignore
index e8e0820..2b693f1 100644
--- a/examples/.gitignore
+++ b/examples/.gitignore
@@ -2,4 +2,5 @@ general
showindex
diff
rev-list
+blame
*.dSYM
diff --git a/examples/Makefile b/examples/Makefile
index d53ed82..79c57d7 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 status log rev-parse init
+APPS = general blame showindex diff rev-list cat-file status log rev-parse
all: $(APPS)
diff --git a/examples/blame.c b/examples/blame.c
new file mode 100644
index 0000000..1b08ec4
--- /dev/null
+++ b/examples/blame.c
@@ -0,0 +1,131 @@
+#include <stdio.h>
+#include <git2.h>
+#include <stdlib.h>
+#include <string.h>
+
+static void check(int error, const char *msg)
+{
+ if (error) {
+ fprintf(stderr, "%s (%d)\n", msg, error);
+ exit(error);
+ }
+}
+
+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 <path> [options] [<commit range>]\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 fine line moves within and across files\n");
+ fprintf(stderr, " -C find line copies within and across files\n");
+ fprintf(stderr, "\n");
+ exit(1);
+}
+
+int main(int argc, char *argv[])
+{
+ int i;
+ char *path = NULL, *a;
+ const char *rawdata, *commitspec=NULL;
+ git_repository *repo = NULL;
+ git_revspec revspec = {0};
+ git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
+ git_blame *blame = NULL;
+ git_commit *commit;
+ git_tree *tree;
+ git_tree_entry *entry;
+ git_blob *blob;
+
+ if (argc < 2) usage(NULL, NULL);
+ path = argv[1];
+
+ for (i=2; i<argc; i++) {
+ a = argv[i];
+
+ if (!strcmp(a, "-M"))
+ opts.flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES;
+ else if (!strcmp(a, "-C"))
+ opts.flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES;
+ else if (!strncmp(a, "-L", 3)) {
+ i++; a = argv[i];
+ if (i >= argc) check(-1, "Not enough arguments to -L");
+ check(sscanf(a, "%d,%d", &opts.min_line, &opts.max_line)-2, "-L format error");
+ }
+ else {
+ /* commit range */
+ if (commitspec) check(-1, "Only one commit spec allowed");
+ commitspec = a;
+ }
+ }
+
+ /* Open the repo */
+ check(git_repository_open_ext(&repo, ".", 0, NULL), "Couldn't open repository");
+
+ /* Parse the end points */
+ if (commitspec) {
+ check(git_revparse(&revspec, repo, commitspec), "Couldn't parse commit spec");
+ if (revspec.flags & GIT_REVPARSE_SINGLE) {
+ git_oid_cpy(&opts.newest_commit, git_object_id(revspec.from));
+ git_object_free(revspec.from);
+ } else {
+ git_oid_cpy(&opts.oldest_commit, git_object_id(revspec.from));
+ git_oid_cpy(&opts.newest_commit, git_object_id(revspec.to));
+ git_object_free(revspec.from);
+ git_object_free(revspec.to);
+ }
+ }
+
+ /* Run the blame */
+ check(git_blame_file(&blame, repo, path, &opts), "Blame error");
+
+ /* Get the raw data for output */
+ if (git_oid_iszero(&opts.newest_commit)) {
+ git_object *obj;
+ check(git_revparse_single(&obj, repo, "HEAD"), "Can't find HEAD");
+ git_oid_cpy(&opts.newest_commit, git_object_id(obj));
+ git_object_free(obj);
+ }
+ check(git_commit_lookup(&commit, repo, &opts.newest_commit), "Commit lookup error");
+ check(git_commit_tree(&tree, commit), "Commit tree lookup error");
+ check(git_tree_entry_bypath(&entry, tree, path), "Tree entry lookup error");
+ check(git_blob_lookup(&blob, repo, git_tree_entry_id(entry)), "Blob lookup error");
+ rawdata = git_blob_rawcontent(blob);
+
+ /* Produce the output */
+ i = 1;
+ while (rawdata[0]) {
+ const char *eol = strchr(rawdata, '\n');
+ char oid[10] = {0};
+ const git_blame_hunk *hunk = git_blame_get_hunk_byline(blame, i);
+ git_commit *hunkcommit;
+ const git_signature *sig;
+
+ git_oid_tostr(oid, 10, &hunk->final_commit_id);
+ check(git_commit_lookup(&hunkcommit, repo, &hunk->final_commit_id), "Commit lookup error");
+ sig = git_commit_author(hunkcommit);
+
+ printf("%s ( %-30s %3d) %.*s\n",
+ oid,
+ sig->name,
+ i,
+ (int)(eol-rawdata),
+ rawdata);
+
+ git_commit_free(hunkcommit);
+ rawdata = eol+1;
+ i++;
+ }
+
+ /* Cleanup */
+ git_blob_free(blob);
+ git_tree_entry_free(entry);
+ git_tree_free(tree);
+ git_commit_free(commit);
+ git_blame_free(blame);
+ git_repository_free(repo);
+}