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
#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 [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, "\n");
exit(1);
}
int main(int argc, char *argv[])
{
int i, line, break_on_null_hunk;
const char *path = NULL, *a;
const char *rawdata, *commitspec=NULL, *bare_args[3] = {0};
char spec[1024] = {0};
git_repository *repo = NULL;
git_revspec revspec = {0};
git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
git_blame *blame = NULL;
git_blob *blob;
git_threads_init();
if (argc < 2) usage(NULL, NULL);
for (i=1; i<argc; i++) {
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"))
opts.flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES;
else if (!strcasecmp(a, "-C"))
opts.flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES;
else if (!strcasecmp(a, "-L")) {
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;
}
}
/* Handle the bare arguments */
if (!bare_args[0]) usage("Please specify a path", NULL);
path = bare_args[0];
if (bare_args[1]) {
/* <commitspec> <path> */
path = bare_args[1];
commitspec = bare_args[0];
}
if (bare_args[2]) {
/* <oldcommit> <newcommit> <path> */
path = bare_args[2];
sprintf(spec, "%s..%s", bare_args[0], bare_args[1]);
commitspec = spec;
}
/* 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))
strcpy(spec, "HEAD");
else
git_oid_tostr(spec, sizeof(spec), &opts.newest_commit);
strcat(spec, ":");
strcat(spec, path);
{
git_object *obj;
check(git_revparse_single(&obj, repo, spec), "Object lookup error");
check(git_blob_lookup(&blob, repo, git_object_id(obj)), "Blob lookup error");
git_object_free(obj);
}
rawdata = git_blob_rawcontent(blob);
/* Produce the output */
line = 1;
i = 0;
break_on_null_hunk = 0;
while (i < git_blob_rawsize(blob)) {
const char *eol = strchr(rawdata+i, '\n');
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) {
break_on_null_hunk = 1;
char sig[128] = {0};
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 = eol - rawdata + 1;
line++;
}
/* Cleanup */
git_blob_free(blob);
git_blame_free(blame);
git_repository_free(repo);
git_threads_shutdown();
}