]>
Commit | Line | Data |
---|---|---|
fc675b8c FK |
1 | /* |
2 | * Copyright (C) 2006, Fredrik Kuivinen <freku045@student.liu.se> | |
3 | */ | |
4 | ||
cbfb73d7 | 5 | #include <assert.h> |
fc675b8c FK |
6 | #include <time.h> |
7 | #include <sys/time.h> | |
ea4c7f9b | 8 | #include <math.h> |
cbfb73d7 FK |
9 | |
10 | #include "cache.h" | |
11 | #include "refs.h" | |
12 | #include "tag.h" | |
13 | #include "commit.h" | |
14 | #include "tree.h" | |
15 | #include "blob.h" | |
cbfb73d7 | 16 | #include "diff.h" |
27e73045 | 17 | #include "diffcore.h" |
fc675b8c | 18 | #include "revision.h" |
f2f880f5 | 19 | #include "xdiff-interface.h" |
cbfb73d7 | 20 | |
4903161f | 21 | #ifndef DEBUG |
cbfb73d7 | 22 | #define DEBUG 0 |
4903161f | 23 | #endif |
cbfb73d7 | 24 | |
15e593e4 | 25 | static const char blame_usage[] = "git-blame [-c] [-l] [-t] [-S <revs-file>] [--] file [commit]\n" |
82e5a82f PR |
26 | " -c, --compatibility Use the same output mode as git-annotate (Default: off)\n" |
27 | " -l, --long Show long commit SHA1 (Default: off)\n" | |
28 | " -t, --time Show raw timestamp (Default: off)\n" | |
29 | " -S, --revs-file Use revisions from revs-file instead of calling git-rev-list\n" | |
30 | " -h, --help This message"; | |
ea4c7f9b FK |
31 | |
32 | static struct commit **blame_lines; | |
33 | static int num_blame_lines; | |
34 | static char* blame_contents; | |
35 | static int blame_len; | |
cbfb73d7 | 36 | |
fc675b8c FK |
37 | struct util_info { |
38 | int *line_map; | |
39 | unsigned char sha1[20]; /* blob sha, not commit! */ | |
40 | char *buf; | |
41 | unsigned long size; | |
42 | int num_lines; | |
27e73045 FK |
43 | const char* pathname; |
44 | ||
45 | void* topo_data; | |
cbfb73d7 FK |
46 | }; |
47 | ||
fc675b8c | 48 | struct chunk { |
a9486b02 PR |
49 | int off1, len1; /* --- */ |
50 | int off2, len2; /* +++ */ | |
cbfb73d7 FK |
51 | }; |
52 | ||
fc675b8c FK |
53 | struct patch { |
54 | struct chunk *chunks; | |
55 | int num; | |
cbfb73d7 FK |
56 | }; |
57 | ||
fc675b8c | 58 | static void get_blob(struct commit *commit); |
cbfb73d7 | 59 | |
fc675b8c | 60 | /* Only used for statistics */ |
96f1e58f DR |
61 | static int num_get_patch; |
62 | static int num_commits; | |
63 | static int patch_time; | |
cbfb73d7 | 64 | |
f2f880f5 JH |
65 | struct blame_diff_state { |
66 | struct xdiff_emit_state xm; | |
fc675b8c | 67 | struct patch *ret; |
f2f880f5 | 68 | }; |
fc675b8c | 69 | |
f2f880f5 JH |
70 | static void process_u0_diff(void *state_, char *line, unsigned long len) |
71 | { | |
72 | struct blame_diff_state *state = state_; | |
73 | struct chunk *chunk; | |
fc675b8c | 74 | |
f2f880f5 JH |
75 | if (len < 4 || line[0] != '@' || line[1] != '@') |
76 | return; | |
fc675b8c | 77 | |
f2f880f5 JH |
78 | if (DEBUG) |
79 | printf("chunk line: %.*s", (int)len, line); | |
80 | state->ret->num++; | |
81 | state->ret->chunks = xrealloc(state->ret->chunks, | |
82 | sizeof(struct chunk) * state->ret->num); | |
83 | chunk = &state->ret->chunks[state->ret->num - 1]; | |
84 | ||
85 | assert(!strncmp(line, "@@ -", 4)); | |
86 | ||
87 | if (parse_hunk_header(line, len, | |
88 | &chunk->off1, &chunk->len1, | |
89 | &chunk->off2, &chunk->len2)) { | |
90 | state->ret->num--; | |
91 | return; | |
92 | } | |
fc675b8c | 93 | |
f2f880f5 JH |
94 | if (chunk->len1 == 0) |
95 | chunk->off1++; | |
96 | if (chunk->len2 == 0) | |
97 | chunk->off2++; | |
fc675b8c | 98 | |
f2f880f5 JH |
99 | if (chunk->off1 > 0) |
100 | chunk->off1--; | |
101 | if (chunk->off2 > 0) | |
102 | chunk->off2--; | |
fc675b8c | 103 | |
f2f880f5 JH |
104 | assert(chunk->off1 >= 0); |
105 | assert(chunk->off2 >= 0); | |
106 | } | |
fc675b8c | 107 | |
f2f880f5 JH |
108 | static struct patch *get_patch(struct commit *commit, struct commit *other) |
109 | { | |
110 | struct blame_diff_state state; | |
111 | xpparam_t xpp; | |
112 | xdemitconf_t xecfg; | |
113 | mmfile_t file_c, file_o; | |
114 | xdemitcb_t ecb; | |
d3ff6f55 LT |
115 | struct util_info *info_c = (struct util_info *)commit->util; |
116 | struct util_info *info_o = (struct util_info *)other->util; | |
f2f880f5 | 117 | struct timeval tv_start, tv_end; |
fc675b8c | 118 | |
f2f880f5 JH |
119 | get_blob(commit); |
120 | file_c.ptr = info_c->buf; | |
121 | file_c.size = info_c->size; | |
fc675b8c | 122 | |
f2f880f5 JH |
123 | get_blob(other); |
124 | file_o.ptr = info_o->buf; | |
125 | file_o.size = info_o->size; | |
fc675b8c | 126 | |
f2f880f5 | 127 | gettimeofday(&tv_start, NULL); |
fc675b8c | 128 | |
f2f880f5 JH |
129 | xpp.flags = XDF_NEED_MINIMAL; |
130 | xecfg.ctxlen = 0; | |
131 | xecfg.flags = 0; | |
132 | ecb.outf = xdiff_outf; | |
133 | ecb.priv = &state; | |
134 | memset(&state, 0, sizeof(state)); | |
135 | state.xm.consume = process_u0_diff; | |
136 | state.ret = xmalloc(sizeof(struct patch)); | |
137 | state.ret->chunks = NULL; | |
138 | state.ret->num = 0; | |
fc675b8c | 139 | |
f2f880f5 | 140 | xdl_diff(&file_c, &file_o, &xpp, &xecfg, &ecb); |
fc675b8c FK |
141 | |
142 | gettimeofday(&tv_end, NULL); | |
143 | patch_time += 1000000 * (tv_end.tv_sec - tv_start.tv_sec) + | |
144 | tv_end.tv_usec - tv_start.tv_usec; | |
145 | ||
146 | num_get_patch++; | |
f2f880f5 | 147 | return state.ret; |
cbfb73d7 FK |
148 | } |
149 | ||
fc675b8c | 150 | static void free_patch(struct patch *p) |
cbfb73d7 | 151 | { |
fc675b8c FK |
152 | free(p->chunks); |
153 | free(p); | |
cbfb73d7 FK |
154 | } |
155 | ||
3a7c352b | 156 | static int get_blob_sha1_internal(const unsigned char *sha1, const char *base, |
fc675b8c FK |
157 | int baselen, const char *pathname, |
158 | unsigned mode, int stage); | |
cbfb73d7 FK |
159 | |
160 | static unsigned char blob_sha1[20]; | |
53dc4636 | 161 | static const char* blame_file; |
fc675b8c FK |
162 | static int get_blob_sha1(struct tree *t, const char *pathname, |
163 | unsigned char *sha1) | |
cbfb73d7 | 164 | { |
fc675b8c FK |
165 | int i; |
166 | const char *pathspec[2]; | |
53dc4636 | 167 | blame_file = pathname; |
fc675b8c FK |
168 | pathspec[0] = pathname; |
169 | pathspec[1] = NULL; | |
a8e0d16d | 170 | hashclr(blob_sha1); |
fc675b8c FK |
171 | read_tree_recursive(t, "", 0, 0, pathspec, get_blob_sha1_internal); |
172 | ||
173 | for (i = 0; i < 20; i++) { | |
174 | if (blob_sha1[i] != 0) | |
175 | break; | |
176 | } | |
177 | ||
178 | if (i == 20) | |
179 | return -1; | |
180 | ||
e702496e | 181 | hashcpy(sha1, blob_sha1); |
fc675b8c | 182 | return 0; |
cbfb73d7 FK |
183 | } |
184 | ||
3a7c352b | 185 | static int get_blob_sha1_internal(const unsigned char *sha1, const char *base, |
fc675b8c FK |
186 | int baselen, const char *pathname, |
187 | unsigned mode, int stage) | |
cbfb73d7 | 188 | { |
fc675b8c FK |
189 | if (S_ISDIR(mode)) |
190 | return READ_TREE_RECURSIVE; | |
cbfb73d7 | 191 | |
53dc4636 FK |
192 | if (strncmp(blame_file, base, baselen) || |
193 | strcmp(blame_file + baselen, pathname)) | |
194 | return -1; | |
195 | ||
e702496e | 196 | hashcpy(blob_sha1, sha1); |
fc675b8c | 197 | return -1; |
cbfb73d7 FK |
198 | } |
199 | ||
fc675b8c | 200 | static void get_blob(struct commit *commit) |
cbfb73d7 | 201 | { |
d3ff6f55 | 202 | struct util_info *info = commit->util; |
fc675b8c | 203 | char type[20]; |
cbfb73d7 | 204 | |
fc675b8c FK |
205 | if (info->buf) |
206 | return; | |
cbfb73d7 | 207 | |
fc675b8c FK |
208 | info->buf = read_sha1_file(info->sha1, type, &info->size); |
209 | ||
8e440259 | 210 | assert(!strcmp(type, blob_type)); |
cbfb73d7 FK |
211 | } |
212 | ||
fc675b8c FK |
213 | /* For debugging only */ |
214 | static void print_patch(struct patch *p) | |
cbfb73d7 | 215 | { |
fc675b8c FK |
216 | int i; |
217 | printf("Num chunks: %d\n", p->num); | |
218 | for (i = 0; i < p->num; i++) { | |
219 | printf("%d,%d %d,%d\n", p->chunks[i].off1, p->chunks[i].len1, | |
220 | p->chunks[i].off2, p->chunks[i].len2); | |
221 | } | |
cbfb73d7 FK |
222 | } |
223 | ||
690e307f | 224 | #if DEBUG |
fc675b8c FK |
225 | /* For debugging only */ |
226 | static void print_map(struct commit *cmit, struct commit *other) | |
227 | { | |
d3ff6f55 LT |
228 | struct util_info *util = cmit->util; |
229 | struct util_info *util2 = other->util; | |
fc675b8c FK |
230 | |
231 | int i; | |
232 | int max = | |
233 | util->num_lines > | |
234 | util2->num_lines ? util->num_lines : util2->num_lines; | |
235 | int num; | |
236 | ||
4903161f JH |
237 | if (print_map == NULL) |
238 | ; /* to avoid "unused function" warning */ | |
239 | ||
fc675b8c FK |
240 | for (i = 0; i < max; i++) { |
241 | printf("i: %d ", i); | |
242 | num = -1; | |
243 | ||
244 | if (i < util->num_lines) { | |
245 | num = util->line_map[i]; | |
246 | printf("%d\t", num); | |
247 | } else | |
248 | printf("\t"); | |
249 | ||
250 | if (i < util2->num_lines) { | |
251 | int num2 = util2->line_map[i]; | |
252 | printf("%d\t", num2); | |
253 | if (num != -1 && num2 != num) | |
254 | printf("---"); | |
255 | } else | |
256 | printf("\t"); | |
257 | ||
258 | printf("\n"); | |
259 | } | |
260 | } | |
9201c707 | 261 | #endif |
cbfb73d7 | 262 | |
a9486b02 | 263 | /* p is a patch from commit to other. */ |
fc675b8c FK |
264 | static void fill_line_map(struct commit *commit, struct commit *other, |
265 | struct patch *p) | |
cbfb73d7 | 266 | { |
d3ff6f55 LT |
267 | struct util_info *util = commit->util; |
268 | struct util_info *util2 = other->util; | |
fc675b8c FK |
269 | int *map = util->line_map; |
270 | int *map2 = util2->line_map; | |
271 | int cur_chunk = 0; | |
272 | int i1, i2; | |
273 | ||
274 | if (p->num && DEBUG) | |
275 | print_patch(p); | |
276 | ||
277 | if (DEBUG) | |
278 | printf("num lines 1: %d num lines 2: %d\n", util->num_lines, | |
279 | util2->num_lines); | |
280 | ||
281 | for (i1 = 0, i2 = 0; i1 < util->num_lines; i1++, i2++) { | |
282 | struct chunk *chunk = NULL; | |
283 | if (cur_chunk < p->num) | |
284 | chunk = &p->chunks[cur_chunk]; | |
285 | ||
286 | if (chunk && chunk->off1 == i1) { | |
287 | if (DEBUG && i2 != chunk->off2) | |
288 | printf("i2: %d off2: %d\n", i2, chunk->off2); | |
289 | ||
290 | assert(i2 == chunk->off2); | |
291 | ||
292 | i1--; | |
293 | i2--; | |
294 | if (chunk->len1 > 0) | |
295 | i1 += chunk->len1; | |
296 | ||
297 | if (chunk->len2 > 0) | |
298 | i2 += chunk->len2; | |
299 | ||
300 | cur_chunk++; | |
301 | } else { | |
302 | if (i2 >= util2->num_lines) | |
303 | break; | |
304 | ||
305 | if (map[i1] != map2[i2] && map[i1] != -1) { | |
306 | if (DEBUG) | |
307 | printf("map: i1: %d %d %p i2: %d %d %p\n", | |
308 | i1, map[i1], | |
04f08607 | 309 | (void *) (i1 != -1 ? blame_lines[map[i1]] : NULL), |
fc675b8c | 310 | i2, map2[i2], |
04f08607 | 311 | (void *) (i2 != -1 ? blame_lines[map2[i2]] : NULL)); |
fc675b8c FK |
312 | if (map2[i2] != -1 && |
313 | blame_lines[map[i1]] && | |
314 | !blame_lines[map2[i2]]) | |
315 | map[i1] = map2[i2]; | |
316 | } | |
317 | ||
318 | if (map[i1] == -1 && map2[i2] != -1) | |
319 | map[i1] = map2[i2]; | |
320 | } | |
321 | ||
322 | if (DEBUG > 1) | |
323 | printf("l1: %d l2: %d i1: %d i2: %d\n", | |
324 | map[i1], map2[i2], i1, i2); | |
325 | } | |
cbfb73d7 FK |
326 | } |
327 | ||
fc675b8c | 328 | static int map_line(struct commit *commit, int line) |
cbfb73d7 | 329 | { |
d3ff6f55 | 330 | struct util_info *info = commit->util; |
fc675b8c FK |
331 | assert(line >= 0 && line < info->num_lines); |
332 | return info->line_map[line]; | |
cbfb73d7 FK |
333 | } |
334 | ||
27e73045 | 335 | static struct util_info* get_util(struct commit *commit) |
cbfb73d7 | 336 | { |
d3ff6f55 | 337 | struct util_info *util = commit->util; |
27e73045 FK |
338 | |
339 | if (util) | |
340 | return util; | |
fc675b8c FK |
341 | |
342 | util = xmalloc(sizeof(struct util_info)); | |
27e73045 FK |
343 | util->buf = NULL; |
344 | util->size = 0; | |
345 | util->line_map = NULL; | |
346 | util->num_lines = -1; | |
347 | util->pathname = NULL; | |
d3ff6f55 | 348 | commit->util = util; |
27e73045 FK |
349 | return util; |
350 | } | |
351 | ||
352 | static int fill_util_info(struct commit *commit) | |
353 | { | |
d3ff6f55 | 354 | struct util_info *util = commit->util; |
27e73045 FK |
355 | |
356 | assert(util); | |
357 | assert(util->pathname); | |
fc675b8c | 358 | |
b4e27599 | 359 | return !!get_blob_sha1(commit->tree, util->pathname, util->sha1); |
cbfb73d7 FK |
360 | } |
361 | ||
fc675b8c | 362 | static void alloc_line_map(struct commit *commit) |
cbfb73d7 | 363 | { |
d3ff6f55 | 364 | struct util_info *util = commit->util; |
fc675b8c | 365 | int i; |
cbfb73d7 | 366 | |
fc675b8c FK |
367 | if (util->line_map) |
368 | return; | |
cbfb73d7 | 369 | |
fc675b8c | 370 | get_blob(commit); |
cbfb73d7 | 371 | |
fc675b8c FK |
372 | util->num_lines = 0; |
373 | for (i = 0; i < util->size; i++) { | |
374 | if (util->buf[i] == '\n') | |
375 | util->num_lines++; | |
376 | } | |
377 | if(util->buf[util->size - 1] != '\n') | |
378 | util->num_lines++; | |
cbfb73d7 | 379 | |
fc675b8c | 380 | util->line_map = xmalloc(sizeof(int) * util->num_lines); |
cbfb73d7 | 381 | |
fc675b8c FK |
382 | for (i = 0; i < util->num_lines; i++) |
383 | util->line_map[i] = -1; | |
cbfb73d7 FK |
384 | } |
385 | ||
fc675b8c | 386 | static void init_first_commit(struct commit* commit, const char* filename) |
cbfb73d7 | 387 | { |
d3ff6f55 | 388 | struct util_info* util = commit->util; |
fc675b8c | 389 | int i; |
cbfb73d7 | 390 | |
27e73045 FK |
391 | util->pathname = filename; |
392 | if (fill_util_info(commit)) | |
fc675b8c | 393 | die("fill_util_info failed"); |
cbfb73d7 | 394 | |
fc675b8c | 395 | alloc_line_map(commit); |
cbfb73d7 | 396 | |
d3ff6f55 | 397 | util = commit->util; |
cbfb73d7 | 398 | |
ea4c7f9b | 399 | for (i = 0; i < util->num_lines; i++) |
fc675b8c FK |
400 | util->line_map[i] = i; |
401 | } | |
cbfb73d7 | 402 | |
cbfb73d7 | 403 | |
fc675b8c FK |
404 | static void process_commits(struct rev_info *rev, const char *path, |
405 | struct commit** initial) | |
406 | { | |
407 | int i; | |
408 | struct util_info* util; | |
409 | int lines_left; | |
410 | int *blame_p; | |
411 | int *new_lines; | |
412 | int new_lines_len; | |
413 | ||
414 | struct commit* commit = get_revision(rev); | |
415 | assert(commit); | |
416 | init_first_commit(commit, path); | |
417 | ||
d3ff6f55 | 418 | util = commit->util; |
fc675b8c FK |
419 | num_blame_lines = util->num_lines; |
420 | blame_lines = xmalloc(sizeof(struct commit *) * num_blame_lines); | |
ea4c7f9b FK |
421 | blame_contents = util->buf; |
422 | blame_len = util->size; | |
423 | ||
fc675b8c FK |
424 | for (i = 0; i < num_blame_lines; i++) |
425 | blame_lines[i] = NULL; | |
426 | ||
427 | lines_left = num_blame_lines; | |
428 | blame_p = xmalloc(sizeof(int) * num_blame_lines); | |
429 | new_lines = xmalloc(sizeof(int) * num_blame_lines); | |
430 | do { | |
431 | struct commit_list *parents; | |
432 | int num_parents; | |
433 | struct util_info *util; | |
434 | ||
435 | if (DEBUG) | |
436 | printf("\nProcessing commit: %d %s\n", num_commits, | |
437 | sha1_to_hex(commit->object.sha1)); | |
438 | ||
439 | if (lines_left == 0) | |
440 | return; | |
441 | ||
442 | num_commits++; | |
443 | memset(blame_p, 0, sizeof(int) * num_blame_lines); | |
444 | new_lines_len = 0; | |
445 | num_parents = 0; | |
446 | for (parents = commit->parents; | |
447 | parents != NULL; parents = parents->next) | |
448 | num_parents++; | |
449 | ||
450 | if(num_parents == 0) | |
451 | *initial = commit; | |
452 | ||
27e73045 | 453 | if (fill_util_info(commit)) |
fc675b8c FK |
454 | continue; |
455 | ||
456 | alloc_line_map(commit); | |
d3ff6f55 | 457 | util = commit->util; |
fc675b8c FK |
458 | |
459 | for (parents = commit->parents; | |
460 | parents != NULL; parents = parents->next) { | |
461 | struct commit *parent = parents->item; | |
462 | struct patch *patch; | |
463 | ||
464 | if (parse_commit(parent) < 0) | |
465 | die("parse_commit error"); | |
466 | ||
467 | if (DEBUG) | |
468 | printf("parent: %s\n", | |
469 | sha1_to_hex(parent->object.sha1)); | |
470 | ||
27e73045 | 471 | if (fill_util_info(parent)) { |
fc675b8c FK |
472 | num_parents--; |
473 | continue; | |
474 | } | |
475 | ||
476 | patch = get_patch(parent, commit); | |
477 | alloc_line_map(parent); | |
478 | fill_line_map(parent, commit, patch); | |
479 | ||
480 | for (i = 0; i < patch->num; i++) { | |
481 | int l; | |
482 | for (l = 0; l < patch->chunks[i].len2; l++) { | |
483 | int mapped_line = | |
484 | map_line(commit, patch->chunks[i].off2 + l); | |
485 | if (mapped_line != -1) { | |
486 | blame_p[mapped_line]++; | |
487 | if (blame_p[mapped_line] == num_parents) | |
488 | new_lines[new_lines_len++] = mapped_line; | |
489 | } | |
490 | } | |
491 | } | |
492 | free_patch(patch); | |
493 | } | |
494 | ||
495 | if (DEBUG) | |
496 | printf("parents: %d\n", num_parents); | |
497 | ||
498 | for (i = 0; i < new_lines_len; i++) { | |
499 | int mapped_line = new_lines[i]; | |
500 | if (blame_lines[mapped_line] == NULL) { | |
501 | blame_lines[mapped_line] = commit; | |
502 | lines_left--; | |
503 | if (DEBUG) | |
504 | printf("blame: mapped: %d i: %d\n", | |
505 | mapped_line, i); | |
506 | } | |
507 | } | |
508 | } while ((commit = get_revision(rev)) != NULL); | |
cbfb73d7 FK |
509 | } |
510 | ||
27e73045 FK |
511 | |
512 | static int compare_tree_path(struct rev_info* revs, | |
513 | struct commit* c1, struct commit* c2) | |
514 | { | |
c4e05b1a | 515 | int ret; |
27e73045 | 516 | const char* paths[2]; |
d3ff6f55 | 517 | struct util_info* util = c2->util; |
27e73045 FK |
518 | paths[0] = util->pathname; |
519 | paths[1] = NULL; | |
520 | ||
c4e05b1a | 521 | diff_tree_setup_paths(get_pathspec(revs->prefix, paths), |
899707da | 522 | &revs->pruning); |
c4e05b1a | 523 | ret = rev_compare_tree(revs, c1->tree, c2->tree); |
899707da | 524 | diff_tree_release_paths(&revs->pruning); |
c4e05b1a | 525 | return ret; |
27e73045 FK |
526 | } |
527 | ||
528 | ||
529 | static int same_tree_as_empty_path(struct rev_info *revs, struct tree* t1, | |
530 | const char* path) | |
531 | { | |
c4e05b1a | 532 | int ret; |
27e73045 FK |
533 | const char* paths[2]; |
534 | paths[0] = path; | |
535 | paths[1] = NULL; | |
536 | ||
c4e05b1a | 537 | diff_tree_setup_paths(get_pathspec(revs->prefix, paths), |
899707da | 538 | &revs->pruning); |
c4e05b1a | 539 | ret = rev_same_tree_as_empty(revs, t1); |
899707da | 540 | diff_tree_release_paths(&revs->pruning); |
c4e05b1a | 541 | return ret; |
27e73045 FK |
542 | } |
543 | ||
544 | static const char* find_rename(struct commit* commit, struct commit* parent) | |
545 | { | |
d3ff6f55 | 546 | struct util_info* cutil = commit->util; |
27e73045 FK |
547 | struct diff_options diff_opts; |
548 | const char *paths[1]; | |
549 | int i; | |
550 | ||
551 | if (DEBUG) { | |
552 | printf("find_rename commit: %s ", | |
553 | sha1_to_hex(commit->object.sha1)); | |
554 | puts(sha1_to_hex(parent->object.sha1)); | |
555 | } | |
556 | ||
557 | diff_setup(&diff_opts); | |
558 | diff_opts.recursive = 1; | |
559 | diff_opts.detect_rename = DIFF_DETECT_RENAME; | |
560 | paths[0] = NULL; | |
c4e05b1a | 561 | diff_tree_setup_paths(paths, &diff_opts); |
27e73045 FK |
562 | if (diff_setup_done(&diff_opts) < 0) |
563 | die("diff_setup_done failed"); | |
564 | ||
565 | diff_tree_sha1(commit->tree->object.sha1, parent->tree->object.sha1, | |
566 | "", &diff_opts); | |
567 | diffcore_std(&diff_opts); | |
568 | ||
569 | for (i = 0; i < diff_queued_diff.nr; i++) { | |
570 | struct diff_filepair *p = diff_queued_diff.queue[i]; | |
571 | ||
572 | if (p->status == 'R' && !strcmp(p->one->path, cutil->pathname)) { | |
573 | if (DEBUG) | |
574 | printf("rename %s -> %s\n", p->one->path, p->two->path); | |
575 | return p->two->path; | |
576 | } | |
577 | } | |
578 | ||
579 | return 0; | |
580 | } | |
581 | ||
582 | static void simplify_commit(struct rev_info *revs, struct commit *commit) | |
583 | { | |
584 | struct commit_list **pp, *parent; | |
585 | ||
586 | if (!commit->tree) | |
587 | return; | |
588 | ||
589 | if (!commit->parents) { | |
d3ff6f55 | 590 | struct util_info* util = commit->util; |
27e73045 FK |
591 | if (!same_tree_as_empty_path(revs, commit->tree, |
592 | util->pathname)) | |
593 | commit->object.flags |= TREECHANGE; | |
594 | return; | |
595 | } | |
596 | ||
597 | pp = &commit->parents; | |
598 | while ((parent = *pp) != NULL) { | |
599 | struct commit *p = parent->item; | |
600 | ||
601 | if (p->object.flags & UNINTERESTING) { | |
602 | pp = &parent->next; | |
603 | continue; | |
604 | } | |
605 | ||
606 | parse_commit(p); | |
607 | switch (compare_tree_path(revs, p, commit)) { | |
608 | case REV_TREE_SAME: | |
609 | parent->next = NULL; | |
610 | commit->parents = parent; | |
611 | get_util(p)->pathname = get_util(commit)->pathname; | |
612 | return; | |
613 | ||
614 | case REV_TREE_NEW: | |
615 | { | |
616 | ||
d3ff6f55 | 617 | struct util_info* util = commit->util; |
27e73045 FK |
618 | if (revs->remove_empty_trees && |
619 | same_tree_as_empty_path(revs, p->tree, | |
620 | util->pathname)) { | |
621 | const char* new_name = find_rename(commit, p); | |
622 | if (new_name) { | |
623 | struct util_info* putil = get_util(p); | |
624 | if (!putil->pathname) | |
9befac47 | 625 | putil->pathname = xstrdup(new_name); |
27e73045 FK |
626 | } else { |
627 | *pp = parent->next; | |
628 | continue; | |
629 | } | |
630 | } | |
631 | } | |
632 | ||
633 | /* fallthrough */ | |
634 | case REV_TREE_DIFFERENT: | |
635 | pp = &parent->next; | |
636 | if (!get_util(p)->pathname) | |
637 | get_util(p)->pathname = | |
638 | get_util(commit)->pathname; | |
639 | continue; | |
640 | } | |
641 | die("bad tree compare for commit %s", | |
642 | sha1_to_hex(commit->object.sha1)); | |
643 | } | |
644 | commit->object.flags |= TREECHANGE; | |
645 | } | |
646 | ||
647 | ||
ea4c7f9b FK |
648 | struct commit_info |
649 | { | |
650 | char* author; | |
651 | char* author_mail; | |
652 | unsigned long author_time; | |
653 | char* author_tz; | |
654 | }; | |
655 | ||
656 | static void get_commit_info(struct commit* commit, struct commit_info* ret) | |
657 | { | |
658 | int len; | |
659 | char* tmp; | |
660 | static char author_buf[1024]; | |
661 | ||
662 | tmp = strstr(commit->buffer, "\nauthor ") + 8; | |
f2f880f5 | 663 | len = strchr(tmp, '\n') - tmp; |
ea4c7f9b FK |
664 | ret->author = author_buf; |
665 | memcpy(ret->author, tmp, len); | |
666 | ||
667 | tmp = ret->author; | |
668 | tmp += len; | |
669 | *tmp = 0; | |
670 | while(*tmp != ' ') | |
671 | tmp--; | |
672 | ret->author_tz = tmp+1; | |
673 | ||
674 | *tmp = 0; | |
675 | while(*tmp != ' ') | |
676 | tmp--; | |
677 | ret->author_time = strtoul(tmp, NULL, 10); | |
678 | ||
679 | *tmp = 0; | |
680 | while(*tmp != ' ') | |
681 | tmp--; | |
682 | ret->author_mail = tmp + 1; | |
683 | ||
684 | *tmp = 0; | |
685 | } | |
686 | ||
b19ee24b FK |
687 | static const char* format_time(unsigned long time, const char* tz_str, |
688 | int show_raw_time) | |
ea4c7f9b FK |
689 | { |
690 | static char time_buf[128]; | |
691 | time_t t = time; | |
cfea8e07 JH |
692 | int minutes, tz; |
693 | struct tm *tm; | |
694 | ||
b19ee24b FK |
695 | if (show_raw_time) { |
696 | sprintf(time_buf, "%lu %s", time, tz_str); | |
697 | return time_buf; | |
698 | } | |
699 | ||
cfea8e07 JH |
700 | tz = atoi(tz_str); |
701 | minutes = tz < 0 ? -tz : tz; | |
702 | minutes = (minutes / 100)*60 + (minutes % 100); | |
703 | minutes = tz < 0 ? -minutes : minutes; | |
704 | t = time + minutes * 60; | |
705 | tm = gmtime(&t); | |
706 | ||
707 | strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S ", tm); | |
708 | strcat(time_buf, tz_str); | |
ea4c7f9b FK |
709 | return time_buf; |
710 | } | |
711 | ||
27e73045 FK |
712 | static void topo_setter(struct commit* c, void* data) |
713 | { | |
d3ff6f55 | 714 | struct util_info* util = c->util; |
27e73045 FK |
715 | util->topo_data = data; |
716 | } | |
717 | ||
718 | static void* topo_getter(struct commit* c) | |
719 | { | |
d3ff6f55 | 720 | struct util_info* util = c->util; |
27e73045 FK |
721 | return util->topo_data; |
722 | } | |
723 | ||
5040f17e JH |
724 | static int read_ancestry(const char *graft_file, |
725 | unsigned char **start_sha1) | |
726 | { | |
727 | FILE *fp = fopen(graft_file, "r"); | |
728 | char buf[1024]; | |
729 | if (!fp) | |
730 | return -1; | |
731 | while (fgets(buf, sizeof(buf), fp)) { | |
732 | /* The format is just "Commit Parent1 Parent2 ...\n" */ | |
733 | int len = strlen(buf); | |
734 | struct commit_graft *graft = read_graft_line(buf, len); | |
735 | register_commit_graft(graft, 0); | |
736 | if (!*start_sha1) | |
737 | *start_sha1 = graft->sha1; | |
738 | } | |
739 | fclose(fp); | |
740 | return 0; | |
741 | } | |
742 | ||
cbfb73d7 FK |
743 | int main(int argc, const char **argv) |
744 | { | |
fc675b8c FK |
745 | int i; |
746 | struct commit *initial = NULL; | |
5040f17e | 747 | unsigned char sha1[20], *sha1_p = NULL; |
ea4c7f9b FK |
748 | |
749 | const char *filename = NULL, *commit = NULL; | |
750 | char filename_buf[256]; | |
751 | int sha1_len = 8; | |
82e5a82f | 752 | int compatibility = 0; |
b19ee24b | 753 | int show_raw_time = 0; |
ea4c7f9b | 754 | int options = 1; |
27e73045 | 755 | struct commit* start_commit; |
ea4c7f9b | 756 | |
fc675b8c FK |
757 | const char* args[10]; |
758 | struct rev_info rev; | |
759 | ||
ea4c7f9b FK |
760 | struct commit_info ci; |
761 | const char *buf; | |
762 | int max_digits; | |
29283907 | 763 | int longest_file, longest_author; |
88a8b795 | 764 | int found_rename; |
fc675b8c | 765 | |
ea4c7f9b | 766 | const char* prefix = setup_git_directory(); |
84a9b58c | 767 | git_config(git_default_config); |
fc675b8c | 768 | |
ea4c7f9b FK |
769 | for(i = 1; i < argc; i++) { |
770 | if(options) { | |
771 | if(!strcmp(argv[i], "-h") || | |
772 | !strcmp(argv[i], "--help")) | |
773 | usage(blame_usage); | |
774 | else if(!strcmp(argv[i], "-l") || | |
775 | !strcmp(argv[i], "--long")) { | |
a0fb95e3 | 776 | sha1_len = 40; |
ea4c7f9b FK |
777 | continue; |
778 | } else if(!strcmp(argv[i], "-c") || | |
82e5a82f PR |
779 | !strcmp(argv[i], "--compatibility")) { |
780 | compatibility = 1; | |
ea4c7f9b | 781 | continue; |
b19ee24b FK |
782 | } else if(!strcmp(argv[i], "-t") || |
783 | !strcmp(argv[i], "--time")) { | |
784 | show_raw_time = 1; | |
785 | continue; | |
5040f17e JH |
786 | } else if(!strcmp(argv[i], "-S")) { |
787 | if (i + 1 < argc && | |
788 | !read_ancestry(argv[i + 1], &sha1_p)) { | |
82e5a82f | 789 | compatibility = 1; |
5040f17e JH |
790 | i++; |
791 | continue; | |
792 | } | |
793 | usage(blame_usage); | |
ea4c7f9b FK |
794 | } else if(!strcmp(argv[i], "--")) { |
795 | options = 0; | |
796 | continue; | |
797 | } else if(argv[i][0] == '-') | |
798 | usage(blame_usage); | |
799 | else | |
800 | options = 0; | |
801 | } | |
fc675b8c | 802 | |
ea4c7f9b FK |
803 | if(!options) { |
804 | if(!filename) | |
805 | filename = argv[i]; | |
806 | else if(!commit) | |
807 | commit = argv[i]; | |
808 | else | |
809 | usage(blame_usage); | |
810 | } | |
811 | } | |
812 | ||
813 | if(!filename) | |
814 | usage(blame_usage); | |
5040f17e JH |
815 | if (commit && sha1_p) |
816 | usage(blame_usage); | |
817 | else if(!commit) | |
ea4c7f9b FK |
818 | commit = "HEAD"; |
819 | ||
820 | if(prefix) | |
821 | sprintf(filename_buf, "%s%s", prefix, filename); | |
822 | else | |
823 | strcpy(filename_buf, filename); | |
824 | filename = filename_buf; | |
fc675b8c | 825 | |
5040f17e JH |
826 | if (!sha1_p) { |
827 | if (get_sha1(commit, sha1)) | |
828 | die("get_sha1 failed, commit '%s' not found", commit); | |
829 | sha1_p = sha1; | |
830 | } | |
831 | start_commit = lookup_commit_reference(sha1_p); | |
27e73045 FK |
832 | get_util(start_commit)->pathname = filename; |
833 | if (fill_util_info(start_commit)) { | |
834 | printf("%s not found in %s\n", filename, commit); | |
835 | return 1; | |
fc675b8c FK |
836 | } |
837 | ||
fc675b8c | 838 | |
db6296a5 | 839 | init_revisions(&rev, setup_git_directory()); |
27e73045 FK |
840 | rev.remove_empty_trees = 1; |
841 | rev.topo_order = 1; | |
842 | rev.prune_fn = simplify_commit; | |
843 | rev.topo_setter = topo_setter; | |
844 | rev.topo_getter = topo_getter; | |
ba3c9374 | 845 | rev.parents = 1; |
27e73045 FK |
846 | rev.limited = 1; |
847 | ||
848 | commit_list_insert(start_commit, &rev.commits); | |
849 | ||
850 | args[0] = filename; | |
851 | args[1] = NULL; | |
899707da | 852 | diff_tree_setup_paths(args, &rev.pruning); |
fc675b8c FK |
853 | prepare_revision_walk(&rev); |
854 | process_commits(&rev, filename, &initial); | |
855 | ||
ea4c7f9b | 856 | buf = blame_contents; |
a0fb95e3 JH |
857 | for (max_digits = 1, i = 10; i <= num_blame_lines + 1; max_digits++) |
858 | i *= 10; | |
859 | ||
88a8b795 FK |
860 | longest_file = 0; |
861 | longest_author = 0; | |
862 | found_rename = 0; | |
863 | for (i = 0; i < num_blame_lines; i++) { | |
864 | struct commit *c = blame_lines[i]; | |
865 | struct util_info* u; | |
866 | if (!c) | |
867 | c = initial; | |
d3ff6f55 | 868 | u = c->util; |
88a8b795 FK |
869 | |
870 | if (!found_rename && strcmp(filename, u->pathname)) | |
871 | found_rename = 1; | |
872 | if (longest_file < strlen(u->pathname)) | |
873 | longest_file = strlen(u->pathname); | |
874 | get_commit_info(c, &ci); | |
875 | if (longest_author < strlen(ci.author)) | |
876 | longest_author = strlen(ci.author); | |
877 | } | |
878 | ||
fc675b8c FK |
879 | for (i = 0; i < num_blame_lines; i++) { |
880 | struct commit *c = blame_lines[i]; | |
27e73045 FK |
881 | struct util_info* u; |
882 | ||
fc675b8c FK |
883 | if (!c) |
884 | c = initial; | |
885 | ||
d3ff6f55 | 886 | u = c->util; |
ea4c7f9b FK |
887 | get_commit_info(c, &ci); |
888 | fwrite(sha1_to_hex(c->object.sha1), sha1_len, 1, stdout); | |
82e5a82f | 889 | if(compatibility) { |
ea4c7f9b | 890 | printf("\t(%10s\t%10s\t%d)", ci.author, |
b19ee24b FK |
891 | format_time(ci.author_time, ci.author_tz, |
892 | show_raw_time), | |
893 | i+1); | |
88a8b795 FK |
894 | } else { |
895 | if (found_rename) | |
896 | printf(" %-*.*s", longest_file, longest_file, | |
897 | u->pathname); | |
898 | printf(" (%-*.*s %10s %*d) ", | |
899 | longest_author, longest_author, ci.author, | |
b19ee24b FK |
900 | format_time(ci.author_time, ci.author_tz, |
901 | show_raw_time), | |
ea4c7f9b | 902 | max_digits, i+1); |
88a8b795 | 903 | } |
ea4c7f9b FK |
904 | |
905 | if(i == num_blame_lines - 1) { | |
906 | fwrite(buf, blame_len - (buf - blame_contents), | |
907 | 1, stdout); | |
908 | if(blame_contents[blame_len-1] != '\n') | |
909 | putc('\n', stdout); | |
910 | } else { | |
f2f880f5 | 911 | char* next_buf = strchr(buf, '\n') + 1; |
ea4c7f9b FK |
912 | fwrite(buf, next_buf - buf, 1, stdout); |
913 | buf = next_buf; | |
914 | } | |
fc675b8c FK |
915 | } |
916 | ||
917 | if (DEBUG) { | |
918 | printf("num get patch: %d\n", num_get_patch); | |
919 | printf("num commits: %d\n", num_commits); | |
920 | printf("patch time: %f\n", patch_time / 1000000.0); | |
921 | printf("initial: %s\n", sha1_to_hex(initial->object.sha1)); | |
922 | } | |
923 | ||
924 | return 0; | |
cbfb73d7 | 925 | } |