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