]> git.proxmox.com Git - libgit2.git/blob - examples/blame.c
libgit2 v0.21.0
[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 #ifdef _MSC_VER
18 #define snprintf sprintf_s
19 #define strcasecmp strcmpi
20 #endif
21
22 /**
23 * This example demonstrates how to invoke the libgit2 blame API to roughly
24 * simulate the output of `git blame` and a few of its command line arguments.
25 */
26
27 struct opts {
28 char *path;
29 char *commitspec;
30 int C;
31 int M;
32 int start_line;
33 int end_line;
34 int F;
35 };
36 static void parse_opts(struct opts *o, int argc, char *argv[]);
37
38 int main(int argc, char *argv[])
39 {
40 int i, line, break_on_null_hunk;
41 char spec[1024] = {0};
42 struct opts o = {0};
43 const char *rawdata;
44 git_repository *repo = NULL;
45 git_revspec revspec = {0};
46 git_blame_options blameopts = GIT_BLAME_OPTIONS_INIT;
47 git_blame *blame = NULL;
48 git_blob *blob;
49 git_object *obj;
50
51 git_threads_init();
52
53 parse_opts(&o, argc, argv);
54 if (o.M) blameopts.flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES;
55 if (o.C) blameopts.flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES;
56 if (o.F) blameopts.flags |= GIT_BLAME_FIRST_PARENT;
57
58 /** Open the repository. */
59 check_lg2(git_repository_open_ext(&repo, ".", 0, NULL), "Couldn't open repository", NULL);
60
61 /**
62 * The commit range comes in "commitish" form. Use the rev-parse API to
63 * nail down the end points.
64 */
65 if (o.commitspec) {
66 check_lg2(git_revparse(&revspec, repo, o.commitspec), "Couldn't parse commit spec", NULL);
67 if (revspec.flags & GIT_REVPARSE_SINGLE) {
68 git_oid_cpy(&blameopts.newest_commit, git_object_id(revspec.from));
69 git_object_free(revspec.from);
70 } else {
71 git_oid_cpy(&blameopts.oldest_commit, git_object_id(revspec.from));
72 git_oid_cpy(&blameopts.newest_commit, git_object_id(revspec.to));
73 git_object_free(revspec.from);
74 git_object_free(revspec.to);
75 }
76 }
77
78 /** Run the blame. */
79 check_lg2(git_blame_file(&blame, repo, o.path, &blameopts), "Blame error", NULL);
80
81 /**
82 * Get the raw data inside the blob for output. We use the
83 * `commitish:path/to/file.txt` format to find it.
84 */
85 if (git_oid_iszero(&blameopts.newest_commit))
86 strcpy(spec, "HEAD");
87 else
88 git_oid_tostr(spec, sizeof(spec), &blameopts.newest_commit);
89 strcat(spec, ":");
90 strcat(spec, o.path);
91
92 check_lg2(git_revparse_single(&obj, repo, spec), "Object lookup error", NULL);
93 check_lg2(git_blob_lookup(&blob, repo, git_object_id(obj)), "Blob lookup error", NULL);
94 git_object_free(obj);
95
96 rawdata = git_blob_rawcontent(blob);
97
98 /** Produce the output. */
99 line = 1;
100 i = 0;
101 break_on_null_hunk = 0;
102 while (i < git_blob_rawsize(blob)) {
103 const char *eol = strchr(rawdata+i, '\n');
104 char oid[10] = {0};
105 const git_blame_hunk *hunk = git_blame_get_hunk_byline(blame, line);
106
107 if (break_on_null_hunk && !hunk) break;
108
109 if (hunk) {
110 char sig[128] = {0};
111 break_on_null_hunk = 1;
112
113
114 git_oid_tostr(oid, 10, &hunk->final_commit_id);
115 snprintf(sig, 30, "%s <%s>", hunk->final_signature->name, hunk->final_signature->email);
116
117 printf("%s ( %-30s %3d) %.*s\n",
118 oid,
119 sig,
120 line,
121 (int)(eol-rawdata-i),
122 rawdata+i);
123 }
124
125 i = (int)(eol - rawdata + 1);
126 line++;
127 }
128
129 /** Cleanup. */
130 git_blob_free(blob);
131 git_blame_free(blame);
132 git_repository_free(repo);
133
134 git_threads_shutdown();
135
136 return 0;
137 }
138
139 /** Tell the user how to make this thing work. */
140 static void usage(const char *msg, const char *arg)
141 {
142 if (msg && arg)
143 fprintf(stderr, "%s: %s\n", msg, arg);
144 else if (msg)
145 fprintf(stderr, "%s\n", msg);
146 fprintf(stderr, "usage: blame [options] [<commit range>] <path>\n");
147 fprintf(stderr, "\n");
148 fprintf(stderr, " <commit range> example: `HEAD~10..HEAD`, or `1234abcd`\n");
149 fprintf(stderr, " -L <n,m> process only line range n-m, counting from 1\n");
150 fprintf(stderr, " -M find line moves within and across files\n");
151 fprintf(stderr, " -C find line copies within and across files\n");
152 fprintf(stderr, " -F follow only the first parent commits\n");
153 fprintf(stderr, "\n");
154 exit(1);
155 }
156
157 /** Parse the arguments. */
158 static void parse_opts(struct opts *o, int argc, char *argv[])
159 {
160 int i;
161 char *bare_args[3] = {0};
162
163 if (argc < 2) usage(NULL, NULL);
164
165 for (i=1; i<argc; i++) {
166 char *a = argv[i];
167
168 if (a[0] != '-') {
169 int i=0;
170 while (bare_args[i] && i < 3) ++i;
171 if (i >= 3)
172 usage("Invalid argument set", NULL);
173 bare_args[i] = a;
174 }
175 else if (!strcmp(a, "--"))
176 continue;
177 else if (!strcasecmp(a, "-M"))
178 o->M = 1;
179 else if (!strcasecmp(a, "-C"))
180 o->C = 1;
181 else if (!strcasecmp(a, "-F"))
182 o->F = 1;
183 else if (!strcasecmp(a, "-L")) {
184 i++; a = argv[i];
185 if (i >= argc) fatal("Not enough arguments to -L", NULL);
186 check_lg2(sscanf(a, "%d,%d", &o->start_line, &o->end_line)-2, "-L format error", NULL);
187 }
188 else {
189 /* commit range */
190 if (o->commitspec) fatal("Only one commit spec allowed", NULL);
191 o->commitspec = a;
192 }
193 }
194
195 /* Handle the bare arguments */
196 if (!bare_args[0]) usage("Please specify a path", NULL);
197 o->path = bare_args[0];
198 if (bare_args[1]) {
199 /* <commitspec> <path> */
200 o->path = bare_args[1];
201 o->commitspec = bare_args[0];
202 }
203 if (bare_args[2]) {
204 /* <oldcommit> <newcommit> <path> */
205 char spec[128] = {0};
206 o->path = bare_args[2];
207 sprintf(spec, "%s..%s", bare_args[0], bare_args[1]);
208 o->commitspec = spec;
209 }
210 }