]>
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 | ||
b19ee24b | 23 | static const char blame_usage[] = "[-c] [-l] [-t] [-S <revs-file>] [--] file [commit]\n" |
ea4c7f9b FK |
24 | " -c, --compability Use the same output mode as git-annotate (Default: off)\n" |
25 | " -l, --long Show long commit SHA1 (Default: off)\n" | |
b19ee24b FK |
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" | |
ea4c7f9b FK |
28 | " -h, --help This message"; |
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 FK |
46 | struct chunk { |
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 FK |
58 | /* Only used for statistics */ |
59 | static int num_get_patch = 0; | |
60 | static int num_commits = 0; | |
61 | static int patch_time = 0; | |
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; | |
113 | struct util_info *info_c = (struct util_info *)commit->object.util; | |
114 | struct util_info *info_o = (struct util_info *)other->object.util; | |
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; | |
168 | memset(blob_sha1, 0, sizeof(blob_sha1)); | |
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 | ||
179 | memcpy(sha1, blob_sha1, 20); | |
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 | ||
fc675b8c FK |
194 | memcpy(blob_sha1, sha1, 20); |
195 | return -1; | |
cbfb73d7 FK |
196 | } |
197 | ||
fc675b8c | 198 | static void get_blob(struct commit *commit) |
cbfb73d7 | 199 | { |
fc675b8c FK |
200 | struct util_info *info = commit->object.util; |
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 | { | |
226 | struct util_info *util = cmit->object.util; | |
227 | struct util_info *util2 = other->object.util; | |
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 FK |
257 | |
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 | { |
fc675b8c FK |
262 | struct util_info *util = commit->object.util; |
263 | struct util_info *util2 = other->object.util; | |
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], | |
304 | i1 != -1 ? blame_lines[map[i1]] : NULL, | |
305 | i2, map2[i2], | |
306 | i2 != -1 ? blame_lines[map2[i2]] : NULL); | |
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 | { |
fc675b8c FK |
325 | struct util_info *info = commit->object.util; |
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 | { |
27e73045 FK |
332 | struct util_info *util = commit->object.util; |
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; | |
343 | commit->object.util = util; | |
344 | return util; | |
345 | } | |
346 | ||
347 | static int fill_util_info(struct commit *commit) | |
348 | { | |
349 | struct util_info *util = commit->object.util; | |
350 | ||
351 | assert(util); | |
352 | assert(util->pathname); | |
fc675b8c | 353 | |
27e73045 | 354 | if (get_blob_sha1(commit->tree, util->pathname, util->sha1)) |
fc675b8c | 355 | return 1; |
27e73045 | 356 | else |
fc675b8c | 357 | return 0; |
cbfb73d7 FK |
358 | } |
359 | ||
fc675b8c | 360 | static void alloc_line_map(struct commit *commit) |
cbfb73d7 | 361 | { |
fc675b8c FK |
362 | struct util_info *util = commit->object.util; |
363 | int i; | |
cbfb73d7 | 364 | |
fc675b8c FK |
365 | if (util->line_map) |
366 | return; | |
cbfb73d7 | 367 | |
fc675b8c | 368 | get_blob(commit); |
cbfb73d7 | 369 | |
fc675b8c FK |
370 | util->num_lines = 0; |
371 | for (i = 0; i < util->size; i++) { | |
372 | if (util->buf[i] == '\n') | |
373 | util->num_lines++; | |
374 | } | |
375 | if(util->buf[util->size - 1] != '\n') | |
376 | util->num_lines++; | |
cbfb73d7 | 377 | |
fc675b8c | 378 | util->line_map = xmalloc(sizeof(int) * util->num_lines); |
cbfb73d7 | 379 | |
fc675b8c FK |
380 | for (i = 0; i < util->num_lines; i++) |
381 | util->line_map[i] = -1; | |
cbfb73d7 FK |
382 | } |
383 | ||
fc675b8c | 384 | static void init_first_commit(struct commit* commit, const char* filename) |
cbfb73d7 | 385 | { |
27e73045 | 386 | struct util_info* util = commit->object.util; |
fc675b8c | 387 | int i; |
cbfb73d7 | 388 | |
27e73045 FK |
389 | util->pathname = filename; |
390 | if (fill_util_info(commit)) | |
fc675b8c | 391 | die("fill_util_info failed"); |
cbfb73d7 | 392 | |
fc675b8c | 393 | alloc_line_map(commit); |
cbfb73d7 | 394 | |
fc675b8c | 395 | util = commit->object.util; |
cbfb73d7 | 396 | |
ea4c7f9b | 397 | for (i = 0; i < util->num_lines; i++) |
fc675b8c FK |
398 | util->line_map[i] = i; |
399 | } | |
cbfb73d7 | 400 | |
cbfb73d7 | 401 | |
fc675b8c FK |
402 | static void process_commits(struct rev_info *rev, const char *path, |
403 | struct commit** initial) | |
404 | { | |
405 | int i; | |
406 | struct util_info* util; | |
407 | int lines_left; | |
408 | int *blame_p; | |
409 | int *new_lines; | |
410 | int new_lines_len; | |
411 | ||
412 | struct commit* commit = get_revision(rev); | |
413 | assert(commit); | |
414 | init_first_commit(commit, path); | |
415 | ||
416 | util = commit->object.util; | |
417 | num_blame_lines = util->num_lines; | |
418 | blame_lines = xmalloc(sizeof(struct commit *) * num_blame_lines); | |
ea4c7f9b FK |
419 | blame_contents = util->buf; |
420 | blame_len = util->size; | |
421 | ||
fc675b8c FK |
422 | for (i = 0; i < num_blame_lines; i++) |
423 | blame_lines[i] = NULL; | |
424 | ||
425 | lines_left = num_blame_lines; | |
426 | blame_p = xmalloc(sizeof(int) * num_blame_lines); | |
427 | new_lines = xmalloc(sizeof(int) * num_blame_lines); | |
428 | do { | |
429 | struct commit_list *parents; | |
430 | int num_parents; | |
431 | struct util_info *util; | |
432 | ||
433 | if (DEBUG) | |
434 | printf("\nProcessing commit: %d %s\n", num_commits, | |
435 | sha1_to_hex(commit->object.sha1)); | |
436 | ||
437 | if (lines_left == 0) | |
438 | return; | |
439 | ||
440 | num_commits++; | |
441 | memset(blame_p, 0, sizeof(int) * num_blame_lines); | |
442 | new_lines_len = 0; | |
443 | num_parents = 0; | |
444 | for (parents = commit->parents; | |
445 | parents != NULL; parents = parents->next) | |
446 | num_parents++; | |
447 | ||
448 | if(num_parents == 0) | |
449 | *initial = commit; | |
450 | ||
27e73045 | 451 | if (fill_util_info(commit)) |
fc675b8c FK |
452 | continue; |
453 | ||
454 | alloc_line_map(commit); | |
455 | util = commit->object.util; | |
456 | ||
457 | for (parents = commit->parents; | |
458 | parents != NULL; parents = parents->next) { | |
459 | struct commit *parent = parents->item; | |
460 | struct patch *patch; | |
461 | ||
462 | if (parse_commit(parent) < 0) | |
463 | die("parse_commit error"); | |
464 | ||
465 | if (DEBUG) | |
466 | printf("parent: %s\n", | |
467 | sha1_to_hex(parent->object.sha1)); | |
468 | ||
27e73045 | 469 | if (fill_util_info(parent)) { |
fc675b8c FK |
470 | num_parents--; |
471 | continue; | |
472 | } | |
473 | ||
474 | patch = get_patch(parent, commit); | |
475 | alloc_line_map(parent); | |
476 | fill_line_map(parent, commit, patch); | |
477 | ||
478 | for (i = 0; i < patch->num; i++) { | |
479 | int l; | |
480 | for (l = 0; l < patch->chunks[i].len2; l++) { | |
481 | int mapped_line = | |
482 | map_line(commit, patch->chunks[i].off2 + l); | |
483 | if (mapped_line != -1) { | |
484 | blame_p[mapped_line]++; | |
485 | if (blame_p[mapped_line] == num_parents) | |
486 | new_lines[new_lines_len++] = mapped_line; | |
487 | } | |
488 | } | |
489 | } | |
490 | free_patch(patch); | |
491 | } | |
492 | ||
493 | if (DEBUG) | |
494 | printf("parents: %d\n", num_parents); | |
495 | ||
496 | for (i = 0; i < new_lines_len; i++) { | |
497 | int mapped_line = new_lines[i]; | |
498 | if (blame_lines[mapped_line] == NULL) { | |
499 | blame_lines[mapped_line] = commit; | |
500 | lines_left--; | |
501 | if (DEBUG) | |
502 | printf("blame: mapped: %d i: %d\n", | |
503 | mapped_line, i); | |
504 | } | |
505 | } | |
506 | } while ((commit = get_revision(rev)) != NULL); | |
cbfb73d7 FK |
507 | } |
508 | ||
27e73045 FK |
509 | |
510 | static int compare_tree_path(struct rev_info* revs, | |
511 | struct commit* c1, struct commit* c2) | |
512 | { | |
c4e05b1a | 513 | int ret; |
27e73045 FK |
514 | const char* paths[2]; |
515 | struct util_info* util = c2->object.util; | |
516 | paths[0] = util->pathname; | |
517 | paths[1] = NULL; | |
518 | ||
c4e05b1a | 519 | diff_tree_setup_paths(get_pathspec(revs->prefix, paths), |
899707da | 520 | &revs->pruning); |
c4e05b1a | 521 | ret = rev_compare_tree(revs, c1->tree, c2->tree); |
899707da | 522 | diff_tree_release_paths(&revs->pruning); |
c4e05b1a | 523 | return ret; |
27e73045 FK |
524 | } |
525 | ||
526 | ||
527 | static int same_tree_as_empty_path(struct rev_info *revs, struct tree* t1, | |
528 | const char* path) | |
529 | { | |
c4e05b1a | 530 | int ret; |
27e73045 FK |
531 | const char* paths[2]; |
532 | paths[0] = path; | |
533 | paths[1] = NULL; | |
534 | ||
c4e05b1a | 535 | diff_tree_setup_paths(get_pathspec(revs->prefix, paths), |
899707da | 536 | &revs->pruning); |
c4e05b1a | 537 | ret = rev_same_tree_as_empty(revs, t1); |
899707da | 538 | diff_tree_release_paths(&revs->pruning); |
c4e05b1a | 539 | return ret; |
27e73045 FK |
540 | } |
541 | ||
542 | static const char* find_rename(struct commit* commit, struct commit* parent) | |
543 | { | |
544 | struct util_info* cutil = commit->object.util; | |
545 | struct diff_options diff_opts; | |
546 | const char *paths[1]; | |
547 | int i; | |
548 | ||
549 | if (DEBUG) { | |
550 | printf("find_rename commit: %s ", | |
551 | sha1_to_hex(commit->object.sha1)); | |
552 | puts(sha1_to_hex(parent->object.sha1)); | |
553 | } | |
554 | ||
555 | diff_setup(&diff_opts); | |
556 | diff_opts.recursive = 1; | |
557 | diff_opts.detect_rename = DIFF_DETECT_RENAME; | |
558 | paths[0] = NULL; | |
c4e05b1a | 559 | diff_tree_setup_paths(paths, &diff_opts); |
27e73045 FK |
560 | if (diff_setup_done(&diff_opts) < 0) |
561 | die("diff_setup_done failed"); | |
562 | ||
563 | diff_tree_sha1(commit->tree->object.sha1, parent->tree->object.sha1, | |
564 | "", &diff_opts); | |
565 | diffcore_std(&diff_opts); | |
566 | ||
567 | for (i = 0; i < diff_queued_diff.nr; i++) { | |
568 | struct diff_filepair *p = diff_queued_diff.queue[i]; | |
569 | ||
570 | if (p->status == 'R' && !strcmp(p->one->path, cutil->pathname)) { | |
571 | if (DEBUG) | |
572 | printf("rename %s -> %s\n", p->one->path, p->two->path); | |
573 | return p->two->path; | |
574 | } | |
575 | } | |
576 | ||
577 | return 0; | |
578 | } | |
579 | ||
580 | static void simplify_commit(struct rev_info *revs, struct commit *commit) | |
581 | { | |
582 | struct commit_list **pp, *parent; | |
583 | ||
584 | if (!commit->tree) | |
585 | return; | |
586 | ||
587 | if (!commit->parents) { | |
588 | struct util_info* util = commit->object.util; | |
589 | if (!same_tree_as_empty_path(revs, commit->tree, | |
590 | util->pathname)) | |
591 | commit->object.flags |= TREECHANGE; | |
592 | return; | |
593 | } | |
594 | ||
595 | pp = &commit->parents; | |
596 | while ((parent = *pp) != NULL) { | |
597 | struct commit *p = parent->item; | |
598 | ||
599 | if (p->object.flags & UNINTERESTING) { | |
600 | pp = &parent->next; | |
601 | continue; | |
602 | } | |
603 | ||
604 | parse_commit(p); | |
605 | switch (compare_tree_path(revs, p, commit)) { | |
606 | case REV_TREE_SAME: | |
607 | parent->next = NULL; | |
608 | commit->parents = parent; | |
609 | get_util(p)->pathname = get_util(commit)->pathname; | |
610 | return; | |
611 | ||
612 | case REV_TREE_NEW: | |
613 | { | |
614 | ||
615 | struct util_info* util = commit->object.util; | |
616 | if (revs->remove_empty_trees && | |
617 | same_tree_as_empty_path(revs, p->tree, | |
618 | util->pathname)) { | |
619 | const char* new_name = find_rename(commit, p); | |
620 | if (new_name) { | |
621 | struct util_info* putil = get_util(p); | |
622 | if (!putil->pathname) | |
623 | putil->pathname = strdup(new_name); | |
624 | } else { | |
625 | *pp = parent->next; | |
626 | continue; | |
627 | } | |
628 | } | |
629 | } | |
630 | ||
631 | /* fallthrough */ | |
632 | case REV_TREE_DIFFERENT: | |
633 | pp = &parent->next; | |
634 | if (!get_util(p)->pathname) | |
635 | get_util(p)->pathname = | |
636 | get_util(commit)->pathname; | |
637 | continue; | |
638 | } | |
639 | die("bad tree compare for commit %s", | |
640 | sha1_to_hex(commit->object.sha1)); | |
641 | } | |
642 | commit->object.flags |= TREECHANGE; | |
643 | } | |
644 | ||
645 | ||
ea4c7f9b FK |
646 | struct commit_info |
647 | { | |
648 | char* author; | |
649 | char* author_mail; | |
650 | unsigned long author_time; | |
651 | char* author_tz; | |
652 | }; | |
653 | ||
654 | static void get_commit_info(struct commit* commit, struct commit_info* ret) | |
655 | { | |
656 | int len; | |
657 | char* tmp; | |
658 | static char author_buf[1024]; | |
659 | ||
660 | tmp = strstr(commit->buffer, "\nauthor ") + 8; | |
f2f880f5 | 661 | len = strchr(tmp, '\n') - tmp; |
ea4c7f9b FK |
662 | ret->author = author_buf; |
663 | memcpy(ret->author, tmp, len); | |
664 | ||
665 | tmp = ret->author; | |
666 | tmp += len; | |
667 | *tmp = 0; | |
668 | while(*tmp != ' ') | |
669 | tmp--; | |
670 | ret->author_tz = tmp+1; | |
671 | ||
672 | *tmp = 0; | |
673 | while(*tmp != ' ') | |
674 | tmp--; | |
675 | ret->author_time = strtoul(tmp, NULL, 10); | |
676 | ||
677 | *tmp = 0; | |
678 | while(*tmp != ' ') | |
679 | tmp--; | |
680 | ret->author_mail = tmp + 1; | |
681 | ||
682 | *tmp = 0; | |
683 | } | |
684 | ||
b19ee24b FK |
685 | static const char* format_time(unsigned long time, const char* tz_str, |
686 | int show_raw_time) | |
ea4c7f9b FK |
687 | { |
688 | static char time_buf[128]; | |
689 | time_t t = time; | |
cfea8e07 JH |
690 | int minutes, tz; |
691 | struct tm *tm; | |
692 | ||
b19ee24b FK |
693 | if (show_raw_time) { |
694 | sprintf(time_buf, "%lu %s", time, tz_str); | |
695 | return time_buf; | |
696 | } | |
697 | ||
cfea8e07 JH |
698 | tz = atoi(tz_str); |
699 | minutes = tz < 0 ? -tz : tz; | |
700 | minutes = (minutes / 100)*60 + (minutes % 100); | |
701 | minutes = tz < 0 ? -minutes : minutes; | |
702 | t = time + minutes * 60; | |
703 | tm = gmtime(&t); | |
704 | ||
705 | strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S ", tm); | |
706 | strcat(time_buf, tz_str); | |
ea4c7f9b FK |
707 | return time_buf; |
708 | } | |
709 | ||
27e73045 FK |
710 | static void topo_setter(struct commit* c, void* data) |
711 | { | |
712 | struct util_info* util = c->object.util; | |
713 | util->topo_data = data; | |
714 | } | |
715 | ||
716 | static void* topo_getter(struct commit* c) | |
717 | { | |
718 | struct util_info* util = c->object.util; | |
719 | return util->topo_data; | |
720 | } | |
721 | ||
5040f17e JH |
722 | static int read_ancestry(const char *graft_file, |
723 | unsigned char **start_sha1) | |
724 | { | |
725 | FILE *fp = fopen(graft_file, "r"); | |
726 | char buf[1024]; | |
727 | if (!fp) | |
728 | return -1; | |
729 | while (fgets(buf, sizeof(buf), fp)) { | |
730 | /* The format is just "Commit Parent1 Parent2 ...\n" */ | |
731 | int len = strlen(buf); | |
732 | struct commit_graft *graft = read_graft_line(buf, len); | |
733 | register_commit_graft(graft, 0); | |
734 | if (!*start_sha1) | |
735 | *start_sha1 = graft->sha1; | |
736 | } | |
737 | fclose(fp); | |
738 | return 0; | |
739 | } | |
740 | ||
cbfb73d7 FK |
741 | int main(int argc, const char **argv) |
742 | { | |
fc675b8c FK |
743 | int i; |
744 | struct commit *initial = NULL; | |
5040f17e | 745 | unsigned char sha1[20], *sha1_p = NULL; |
ea4c7f9b FK |
746 | |
747 | const char *filename = NULL, *commit = NULL; | |
748 | char filename_buf[256]; | |
749 | int sha1_len = 8; | |
750 | int compability = 0; | |
b19ee24b | 751 | int show_raw_time = 0; |
ea4c7f9b | 752 | int options = 1; |
27e73045 | 753 | struct commit* start_commit; |
ea4c7f9b | 754 | |
fc675b8c FK |
755 | const char* args[10]; |
756 | struct rev_info rev; | |
757 | ||
ea4c7f9b FK |
758 | struct commit_info ci; |
759 | const char *buf; | |
760 | int max_digits; | |
29283907 | 761 | int longest_file, longest_author; |
88a8b795 | 762 | int found_rename; |
fc675b8c | 763 | |
ea4c7f9b | 764 | const char* prefix = setup_git_directory(); |
84a9b58c | 765 | git_config(git_default_config); |
fc675b8c | 766 | |
ea4c7f9b FK |
767 | for(i = 1; i < argc; i++) { |
768 | if(options) { | |
769 | if(!strcmp(argv[i], "-h") || | |
770 | !strcmp(argv[i], "--help")) | |
771 | usage(blame_usage); | |
772 | else if(!strcmp(argv[i], "-l") || | |
773 | !strcmp(argv[i], "--long")) { | |
a0fb95e3 | 774 | sha1_len = 40; |
ea4c7f9b FK |
775 | continue; |
776 | } else if(!strcmp(argv[i], "-c") || | |
777 | !strcmp(argv[i], "--compability")) { | |
778 | compability = 1; | |
779 | continue; | |
b19ee24b FK |
780 | } else if(!strcmp(argv[i], "-t") || |
781 | !strcmp(argv[i], "--time")) { | |
782 | show_raw_time = 1; | |
783 | continue; | |
5040f17e JH |
784 | } else if(!strcmp(argv[i], "-S")) { |
785 | if (i + 1 < argc && | |
786 | !read_ancestry(argv[i + 1], &sha1_p)) { | |
787 | compability = 1; | |
788 | i++; | |
789 | continue; | |
790 | } | |
791 | usage(blame_usage); | |
ea4c7f9b FK |
792 | } else if(!strcmp(argv[i], "--")) { |
793 | options = 0; | |
794 | continue; | |
795 | } else if(argv[i][0] == '-') | |
796 | usage(blame_usage); | |
797 | else | |
798 | options = 0; | |
799 | } | |
fc675b8c | 800 | |
ea4c7f9b FK |
801 | if(!options) { |
802 | if(!filename) | |
803 | filename = argv[i]; | |
804 | else if(!commit) | |
805 | commit = argv[i]; | |
806 | else | |
807 | usage(blame_usage); | |
808 | } | |
809 | } | |
810 | ||
811 | if(!filename) | |
812 | usage(blame_usage); | |
5040f17e JH |
813 | if (commit && sha1_p) |
814 | usage(blame_usage); | |
815 | else if(!commit) | |
ea4c7f9b FK |
816 | commit = "HEAD"; |
817 | ||
818 | if(prefix) | |
819 | sprintf(filename_buf, "%s%s", prefix, filename); | |
820 | else | |
821 | strcpy(filename_buf, filename); | |
822 | filename = filename_buf; | |
fc675b8c | 823 | |
5040f17e JH |
824 | if (!sha1_p) { |
825 | if (get_sha1(commit, sha1)) | |
826 | die("get_sha1 failed, commit '%s' not found", commit); | |
827 | sha1_p = sha1; | |
828 | } | |
829 | start_commit = lookup_commit_reference(sha1_p); | |
27e73045 FK |
830 | get_util(start_commit)->pathname = filename; |
831 | if (fill_util_info(start_commit)) { | |
832 | printf("%s not found in %s\n", filename, commit); | |
833 | return 1; | |
fc675b8c FK |
834 | } |
835 | ||
fc675b8c | 836 | |
27e73045 FK |
837 | init_revisions(&rev); |
838 | rev.remove_empty_trees = 1; | |
839 | rev.topo_order = 1; | |
840 | rev.prune_fn = simplify_commit; | |
841 | rev.topo_setter = topo_setter; | |
842 | rev.topo_getter = topo_getter; | |
ba3c9374 | 843 | rev.parents = 1; |
27e73045 FK |
844 | rev.limited = 1; |
845 | ||
846 | commit_list_insert(start_commit, &rev.commits); | |
847 | ||
848 | args[0] = filename; | |
849 | args[1] = NULL; | |
899707da | 850 | diff_tree_setup_paths(args, &rev.pruning); |
fc675b8c FK |
851 | prepare_revision_walk(&rev); |
852 | process_commits(&rev, filename, &initial); | |
853 | ||
ea4c7f9b | 854 | buf = blame_contents; |
a0fb95e3 JH |
855 | for (max_digits = 1, i = 10; i <= num_blame_lines + 1; max_digits++) |
856 | i *= 10; | |
857 | ||
88a8b795 FK |
858 | longest_file = 0; |
859 | longest_author = 0; | |
860 | found_rename = 0; | |
861 | for (i = 0; i < num_blame_lines; i++) { | |
862 | struct commit *c = blame_lines[i]; | |
863 | struct util_info* u; | |
864 | if (!c) | |
865 | c = initial; | |
866 | u = c->object.util; | |
867 | ||
868 | if (!found_rename && strcmp(filename, u->pathname)) | |
869 | found_rename = 1; | |
870 | if (longest_file < strlen(u->pathname)) | |
871 | longest_file = strlen(u->pathname); | |
872 | get_commit_info(c, &ci); | |
873 | if (longest_author < strlen(ci.author)) | |
874 | longest_author = strlen(ci.author); | |
875 | } | |
876 | ||
fc675b8c FK |
877 | for (i = 0; i < num_blame_lines; i++) { |
878 | struct commit *c = blame_lines[i]; | |
27e73045 FK |
879 | struct util_info* u; |
880 | ||
fc675b8c FK |
881 | if (!c) |
882 | c = initial; | |
883 | ||
27e73045 | 884 | u = c->object.util; |
ea4c7f9b FK |
885 | get_commit_info(c, &ci); |
886 | fwrite(sha1_to_hex(c->object.sha1), sha1_len, 1, stdout); | |
88a8b795 | 887 | if(compability) { |
ea4c7f9b | 888 | printf("\t(%10s\t%10s\t%d)", ci.author, |
b19ee24b FK |
889 | format_time(ci.author_time, ci.author_tz, |
890 | show_raw_time), | |
891 | i+1); | |
88a8b795 FK |
892 | } else { |
893 | if (found_rename) | |
894 | printf(" %-*.*s", longest_file, longest_file, | |
895 | u->pathname); | |
896 | printf(" (%-*.*s %10s %*d) ", | |
897 | longest_author, longest_author, ci.author, | |
b19ee24b FK |
898 | format_time(ci.author_time, ci.author_tz, |
899 | show_raw_time), | |
ea4c7f9b | 900 | max_digits, i+1); |
88a8b795 | 901 | } |
ea4c7f9b FK |
902 | |
903 | if(i == num_blame_lines - 1) { | |
904 | fwrite(buf, blame_len - (buf - blame_contents), | |
905 | 1, stdout); | |
906 | if(blame_contents[blame_len-1] != '\n') | |
907 | putc('\n', stdout); | |
908 | } else { | |
f2f880f5 | 909 | char* next_buf = strchr(buf, '\n') + 1; |
ea4c7f9b FK |
910 | fwrite(buf, next_buf - buf, 1, stdout); |
911 | buf = next_buf; | |
912 | } | |
fc675b8c FK |
913 | } |
914 | ||
915 | if (DEBUG) { | |
916 | printf("num get patch: %d\n", num_get_patch); | |
917 | printf("num commits: %d\n", num_commits); | |
918 | printf("patch time: %f\n", patch_time / 1000000.0); | |
919 | printf("initial: %s\n", sha1_to_hex(initial->object.sha1)); | |
920 | } | |
921 | ||
922 | return 0; | |
cbfb73d7 | 923 | } |