]> git.proxmox.com Git - libgit2.git/blob - examples/blame.c
New upstream version 1.4.3+dfsg.1
[libgit2.git] / examples / blame.c
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
22 struct blame_opts {
23 char *path;
24 char *commitspec;
25 int C;
26 int M;
27 int start_line;
28 int end_line;
29 int F;
30 };
31 static void parse_opts(struct blame_opts *o, int argc, char *argv[]);
32
33 int lg2_blame(git_repository *repo, int argc, char *argv[])
34 {
35 int line, break_on_null_hunk;
36 git_object_size_t i, rawsize;
37 char spec[1024] = {0};
38 struct blame_opts o = {0};
39 const char *rawdata;
40 git_revspec revspec = {0};
41 git_blame_options blameopts = GIT_BLAME_OPTIONS_INIT;
42 git_blame *blame = NULL;
43 git_blob *blob;
44 git_object *obj;
45
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;
49 if (o.F) blameopts.flags |= GIT_BLAME_FIRST_PARENT;
50
51 /**
52 * The commit range comes in "committish" 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);
57 if (revspec.flags & GIT_REVSPEC_SINGLE) {
58 git_oid_cpy(&blameopts.newest_commit, git_object_id(revspec.from));
59 git_object_free(revspec.from);
60 } else {
61 git_oid_cpy(&blameopts.oldest_commit, git_object_id(revspec.from));
62 git_oid_cpy(&blameopts.newest_commit, git_object_id(revspec.to));
63 git_object_free(revspec.from);
64 git_object_free(revspec.to);
65 }
66 }
67
68 /** Run the blame. */
69 check_lg2(git_blame_file(&blame, repo, o.path, &blameopts), "Blame error", NULL);
70
71 /**
72 * Get the raw data inside the blob for output. We use the
73 * `committish:path/to/file.txt` format to find it.
74 */
75 if (git_oid_is_zero(&blameopts.newest_commit))
76 strcpy(spec, "HEAD");
77 else
78 git_oid_tostr(spec, sizeof(spec), &blameopts.newest_commit);
79 strcat(spec, ":");
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);
85
86 rawdata = git_blob_rawcontent(blob);
87 rawsize = git_blob_rawsize(blob);
88
89 /** Produce the output. */
90 line = 1;
91 i = 0;
92 break_on_null_hunk = 0;
93 while (i < rawsize) {
94 const char *eol = memchr(rawdata + i, '\n', (size_t)(rawsize - i));
95 char oid[10] = {0};
96 const git_blame_hunk *hunk = git_blame_get_hunk_byline(blame, line);
97
98 if (break_on_null_hunk && !hunk)
99 break;
100
101 if (hunk) {
102 char sig[128] = {0};
103 break_on_null_hunk = 1;
104
105 git_oid_tostr(oid, 10, &hunk->final_commit_id);
106 snprintf(sig, 30, "%s <%s>", hunk->final_signature->name, hunk->final_signature->email);
107
108 printf("%s ( %-30s %3d) %.*s\n",
109 oid,
110 sig,
111 line,
112 (int)(eol - rawdata - i),
113 rawdata + i);
114 }
115
116 i = (int)(eol - rawdata + 1);
117 line++;
118 }
119
120 /** Cleanup. */
121 git_blob_free(blob);
122 git_blame_free(blame);
123
124 return 0;
125 }
126
127 /** Tell the user how to make this thing work. */
128 static 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");
140 fprintf(stderr, " -F follow only the first parent commits\n");
141 fprintf(stderr, "\n");
142 exit(1);
143 }
144
145 /** Parse the arguments. */
146 static void parse_opts(struct blame_opts *o, int argc, char *argv[])
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;
169 else if (!strcasecmp(a, "-F"))
170 o->F = 1;
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 }