/* * 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 * . */ #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; if (o.start_line && o.end_line) { blameopts.min_line = o.start_line; blameopts.max_line = o.end_line; } /** * 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] [] \n"); fprintf(stderr, "\n"); fprintf(stderr, " example: `HEAD~10..HEAD`, or `1234abcd`\n"); fprintf(stderr, " -L 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= 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]) { /* */ o->path = bare_args[1]; o->commitspec = bare_args[0]; } if (bare_args[2]) { /* */ char spec[128] = {0}; o->path = bare_args[2]; sprintf(spec, "%s..%s", bare_args[0], bare_args[1]); o->commitspec = spec; } }