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