]>
Commit | Line | Data |
---|---|---|
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 | 22 | struct 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 | 31 | static void parse_opts(struct blame_opts *o, int argc, char *argv[]); |
4c7fdb4d | 32 | |
0c9c969a | 33 | int 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 |
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"); | |
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 | 146 | static 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 | } |