]> git.proxmox.com Git - libgit2.git/blame - examples/blame.c
Update d/ch for 0.99.0+dfsg.1-1 experimental release
[libgit2.git] / examples / blame.c
CommitLineData
b7bb086b
BS
1/*
2 * libgit2 "blame" example - shows how to use the blame API
3 *
4 * Written by the libgit2 contributors
5 *
6 * To the extent possible under law, the author(s) have dedicated all copyright
7 * and related and neighboring rights to this software to the public domain
8 * worldwide. This software is distributed without any warranty.
9 *
10 * You should have received a copy of the CC0 Public Domain Dedication along
11 * with this software. If not, see
12 * <http://creativecommons.org/publicdomain/zero/1.0/>.
13 */
14
15#include "common.h"
16
17/**
18 * This example demonstrates how to invoke the libgit2 blame API to roughly
19 * simulate the output of `git blame` and a few of its command line arguments.
20 */
21
0c9c969a 22struct blame_opts {
e6b85be7
BS
23 char *path;
24 char *commitspec;
25 int C;
26 int M;
27 int start_line;
28 int end_line;
c7c83394 29 int F;
e6b85be7 30};
0c9c969a 31static void parse_opts(struct blame_opts *o, int argc, char *argv[]);
4c7fdb4d 32
0c9c969a 33int lg2_blame(git_repository *repo, int argc, char *argv[])
4c7fdb4d 34{
d202bb7d 35 int line, break_on_null_hunk;
0c9c969a 36 git_object_size_t i, rawsize;
43a07b86 37 char spec[1024] = {0};
0c9c969a 38 struct blame_opts o = {0};
e6b85be7 39 const char *rawdata;
4c7fdb4d 40 git_revspec revspec = {0};
e6b85be7 41 git_blame_options blameopts = GIT_BLAME_OPTIONS_INIT;
4c7fdb4d 42 git_blame *blame = NULL;
4c7fdb4d 43 git_blob *blob;
e6b85be7 44 git_object *obj;
4c7fdb4d 45
e6b85be7
BS
46 parse_opts(&o, argc, argv);
47 if (o.M) blameopts.flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES;
48 if (o.C) blameopts.flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES;
c7c83394 49 if (o.F) blameopts.flags |= GIT_BLAME_FIRST_PARENT;
4c7fdb4d 50
e6b85be7
BS
51 /**
52 * The commit range comes in "commitish" form. Use the rev-parse API to
53 * nail down the end points.
54 */
55 if (o.commitspec) {
56 check_lg2(git_revparse(&revspec, repo, o.commitspec), "Couldn't parse commit spec", NULL);
4c7fdb4d 57 if (revspec.flags & GIT_REVPARSE_SINGLE) {
e6b85be7 58 git_oid_cpy(&blameopts.newest_commit, git_object_id(revspec.from));
4c7fdb4d
BS
59 git_object_free(revspec.from);
60 } else {
e6b85be7
BS
61 git_oid_cpy(&blameopts.oldest_commit, git_object_id(revspec.from));
62 git_oid_cpy(&blameopts.newest_commit, git_object_id(revspec.to));
4c7fdb4d
BS
63 git_object_free(revspec.from);
64 git_object_free(revspec.to);
65 }
66 }
67
e6b85be7
BS
68 /** Run the blame. */
69 check_lg2(git_blame_file(&blame, repo, o.path, &blameopts), "Blame error", NULL);
4c7fdb4d 70
e6b85be7
BS
71 /**
72 * Get the raw data inside the blob for output. We use the
73 * `commitish:path/to/file.txt` format to find it.
74 */
0c9c969a 75 if (git_oid_is_zero(&blameopts.newest_commit))
43a07b86
BS
76 strcpy(spec, "HEAD");
77 else
e6b85be7 78 git_oid_tostr(spec, sizeof(spec), &blameopts.newest_commit);
43a07b86 79 strcat(spec, ":");
e6b85be7
BS
80 strcat(spec, o.path);
81
82 check_lg2(git_revparse_single(&obj, repo, spec), "Object lookup error", NULL);
83 check_lg2(git_blob_lookup(&blob, repo, git_object_id(obj)), "Blob lookup error", NULL);
84 git_object_free(obj);
43a07b86 85
4c7fdb4d 86 rawdata = git_blob_rawcontent(blob);
2e1e0f10 87 rawsize = git_blob_rawsize(blob);
4c7fdb4d 88
e6b85be7 89 /** Produce the output. */
2ccc84d2
BS
90 line = 1;
91 i = 0;
607fe733 92 break_on_null_hunk = 0;
2e1e0f10 93 while (i < rawsize) {
0c9c969a 94 const char *eol = memchr(rawdata + i, '\n', (size_t)(rawsize - i));
4c7fdb4d 95 char oid[10] = {0};
ebd67243 96 const git_blame_hunk *hunk = git_blame_get_hunk_byline(blame, line);
4c7fdb4d 97
2e1e0f10
VM
98 if (break_on_null_hunk && !hunk)
99 break;
607fe733 100
ebd67243 101 if (hunk) {
c1ca2b67 102 char sig[128] = {0};
300f4412 103 break_on_null_hunk = 1;
0c9c969a 104
ebd67243 105 git_oid_tostr(oid, 10, &hunk->final_commit_id);
c1ca2b67 106 snprintf(sig, 30, "%s <%s>", hunk->final_signature->name, hunk->final_signature->email);
ebd67243
BS
107
108 printf("%s ( %-30s %3d) %.*s\n",
109 oid,
c1ca2b67 110 sig,
ebd67243 111 line,
2e1e0f10
VM
112 (int)(eol - rawdata - i),
113 rawdata + i);
ebd67243 114 }
4c7fdb4d 115
fb190bbb 116 i = (int)(eol - rawdata + 1);
2ccc84d2 117 line++;
4c7fdb4d
BS
118 }
119
e6b85be7 120 /** Cleanup. */
4c7fdb4d 121 git_blob_free(blob);
4c7fdb4d 122 git_blame_free(blame);
e6b85be7
BS
123
124 return 0;
4c7fdb4d 125}
b7bb086b 126
b4794925 127/** Tell the user how to make this thing work. */
b7bb086b
BS
128static void usage(const char *msg, const char *arg)
129{
130 if (msg && arg)
131 fprintf(stderr, "%s: %s\n", msg, arg);
132 else if (msg)
133 fprintf(stderr, "%s\n", msg);
134 fprintf(stderr, "usage: blame [options] [<commit range>] <path>\n");
135 fprintf(stderr, "\n");
136 fprintf(stderr, " <commit range> example: `HEAD~10..HEAD`, or `1234abcd`\n");
137 fprintf(stderr, " -L <n,m> process only line range n-m, counting from 1\n");
138 fprintf(stderr, " -M find line moves within and across files\n");
139 fprintf(stderr, " -C find line copies within and across files\n");
899bd19a 140 fprintf(stderr, " -F follow only the first parent commits\n");
b7bb086b
BS
141 fprintf(stderr, "\n");
142 exit(1);
143}
144
e6b85be7 145/** Parse the arguments. */
0c9c969a 146static void parse_opts(struct blame_opts *o, int argc, char *argv[])
e6b85be7
BS
147{
148 int i;
149 char *bare_args[3] = {0};
150
151 if (argc < 2) usage(NULL, NULL);
152
153 for (i=1; i<argc; i++) {
154 char *a = argv[i];
155
156 if (a[0] != '-') {
157 int i=0;
158 while (bare_args[i] && i < 3) ++i;
159 if (i >= 3)
160 usage("Invalid argument set", NULL);
161 bare_args[i] = a;
162 }
163 else if (!strcmp(a, "--"))
164 continue;
165 else if (!strcasecmp(a, "-M"))
166 o->M = 1;
167 else if (!strcasecmp(a, "-C"))
168 o->C = 1;
c7c83394
JR
169 else if (!strcasecmp(a, "-F"))
170 o->F = 1;
e6b85be7
BS
171 else if (!strcasecmp(a, "-L")) {
172 i++; a = argv[i];
173 if (i >= argc) fatal("Not enough arguments to -L", NULL);
174 check_lg2(sscanf(a, "%d,%d", &o->start_line, &o->end_line)-2, "-L format error", NULL);
175 }
176 else {
177 /* commit range */
178 if (o->commitspec) fatal("Only one commit spec allowed", NULL);
179 o->commitspec = a;
180 }
181 }
182
183 /* Handle the bare arguments */
184 if (!bare_args[0]) usage("Please specify a path", NULL);
185 o->path = bare_args[0];
186 if (bare_args[1]) {
187 /* <commitspec> <path> */
188 o->path = bare_args[1];
189 o->commitspec = bare_args[0];
190 }
191 if (bare_args[2]) {
192 /* <oldcommit> <newcommit> <path> */
193 char spec[128] = {0};
194 o->path = bare_args[2];
195 sprintf(spec, "%s..%s", bare_args[0], bare_args[1]);
196 o->commitspec = spec;
197 }
198}