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