2 * Copyright (C) 2006, Fredrik Kuivinen <freku045@student.liu.se>
21 static const char blame_usage
[] = "[-c] [-l] [--] file [commit]\n"
22 " -c, --compability Use the same output mode as git-annotate (Default: off)\n"
23 " -l, --long Show long commit SHA1 (Default: off)\n"
24 " -h, --help This message";
26 static struct commit
**blame_lines
;
27 static int num_blame_lines
;
28 static char* blame_contents
;
33 unsigned char sha1
[20]; /* blob sha, not commit! */
41 int off1
, len1
; // ---
42 int off2
, len2
; // +++
50 static void get_blob(struct commit
*commit
);
52 /* Only used for statistics */
53 static int num_get_patch
= 0;
54 static int num_commits
= 0;
55 static int patch_time
= 0;
57 #define TEMPFILE_PATH_LEN 60
58 static struct patch
*get_patch(struct commit
*commit
, struct commit
*other
)
61 struct util_info
*info_c
= (struct util_info
*)commit
->object
.util
;
62 struct util_info
*info_o
= (struct util_info
*)other
->object
.util
;
63 char tmp_path1
[TEMPFILE_PATH_LEN
], tmp_path2
[TEMPFILE_PATH_LEN
];
64 char diff_cmd
[TEMPFILE_PATH_LEN
*2 + 20];
65 struct timeval tv_start
, tv_end
;
70 ret
= xmalloc(sizeof(struct patch
));
77 gettimeofday(&tv_start
, NULL
);
79 fd
= git_mkstemp(tmp_path1
, TEMPFILE_PATH_LEN
, "git-blame-XXXXXX");
81 die("unable to create temp-file: %s", strerror(errno
));
83 if (xwrite(fd
, info_c
->buf
, info_c
->size
) != info_c
->size
)
84 die("write failed: %s", strerror(errno
));
87 fd
= git_mkstemp(tmp_path2
, TEMPFILE_PATH_LEN
, "git-blame-XXXXXX");
89 die("unable to create temp-file: %s", strerror(errno
));
91 if (xwrite(fd
, info_o
->buf
, info_o
->size
) != info_o
->size
)
92 die("write failed: %s", strerror(errno
));
95 sprintf(diff_cmd
, "diff -U 0 %s %s", tmp_path1
, tmp_path2
);
96 fin
= popen(diff_cmd
, "r");
98 die("popen failed: %s", strerror(errno
));
100 while (fgets(buf
, sizeof(buf
), fin
)) {
104 if (buf
[0] != '@' || buf
[1] != '@')
108 printf("chunk line: %s", buf
);
110 ret
->chunks
= xrealloc(ret
->chunks
,
111 sizeof(struct chunk
) * ret
->num
);
112 chunk
= &ret
->chunks
[ret
->num
- 1];
114 assert(!strncmp(buf
, "@@ -", 4));
117 sp
= index(start
, ' ');
119 if (index(start
, ',')) {
121 sscanf(start
, "%d,%d", &chunk
->off1
, &chunk
->len1
);
124 int ret
= sscanf(start
, "%d", &chunk
->off1
);
131 sp
= index(start
, ' ');
133 if (index(start
, ',')) {
135 sscanf(start
, "%d,%d", &chunk
->off2
, &chunk
->len2
);
138 int ret
= sscanf(start
, "%d", &chunk
->off2
);
144 if (chunk
->len1
== 0)
146 if (chunk
->len2
== 0)
154 assert(chunk
->off1
>= 0);
155 assert(chunk
->off2
>= 0);
161 gettimeofday(&tv_end
, NULL
);
162 patch_time
+= 1000000 * (tv_end
.tv_sec
- tv_start
.tv_sec
) +
163 tv_end
.tv_usec
- tv_start
.tv_usec
;
169 static void free_patch(struct patch
*p
)
175 static int get_blob_sha1_internal(unsigned char *sha1
, const char *base
,
176 int baselen
, const char *pathname
,
177 unsigned mode
, int stage
);
179 static unsigned char blob_sha1
[20];
180 static int get_blob_sha1(struct tree
*t
, const char *pathname
,
184 const char *pathspec
[2];
185 pathspec
[0] = pathname
;
187 memset(blob_sha1
, 0, sizeof(blob_sha1
));
188 read_tree_recursive(t
, "", 0, 0, pathspec
, get_blob_sha1_internal
);
190 for (i
= 0; i
< 20; i
++) {
191 if (blob_sha1
[i
] != 0)
198 memcpy(sha1
, blob_sha1
, 20);
202 static int get_blob_sha1_internal(unsigned char *sha1
, const char *base
,
203 int baselen
, const char *pathname
,
204 unsigned mode
, int stage
)
207 return READ_TREE_RECURSIVE
;
209 memcpy(blob_sha1
, sha1
, 20);
213 static void get_blob(struct commit
*commit
)
215 struct util_info
*info
= commit
->object
.util
;
221 info
->buf
= read_sha1_file(info
->sha1
, type
, &info
->size
);
223 assert(!strcmp(type
, "blob"));
226 /* For debugging only */
227 static void print_patch(struct patch
*p
)
230 printf("Num chunks: %d\n", p
->num
);
231 for (i
= 0; i
< p
->num
; i
++) {
232 printf("%d,%d %d,%d\n", p
->chunks
[i
].off1
, p
->chunks
[i
].len1
,
233 p
->chunks
[i
].off2
, p
->chunks
[i
].len2
);
238 /* For debugging only */
239 static void print_map(struct commit
*cmit
, struct commit
*other
)
241 struct util_info
*util
= cmit
->object
.util
;
242 struct util_info
*util2
= other
->object
.util
;
247 util2
->num_lines
? util
->num_lines
: util2
->num_lines
;
250 for (i
= 0; i
< max
; i
++) {
254 if (i
< util
->num_lines
) {
255 num
= util
->line_map
[i
];
260 if (i
< util2
->num_lines
) {
261 int num2
= util2
->line_map
[i
];
262 printf("%d\t", num2
);
263 if (num
!= -1 && num2
!= num
)
273 // p is a patch from commit to other.
274 static void fill_line_map(struct commit
*commit
, struct commit
*other
,
277 struct util_info
*util
= commit
->object
.util
;
278 struct util_info
*util2
= other
->object
.util
;
279 int *map
= util
->line_map
;
280 int *map2
= util2
->line_map
;
288 printf("num lines 1: %d num lines 2: %d\n", util
->num_lines
,
291 for (i1
= 0, i2
= 0; i1
< util
->num_lines
; i1
++, i2
++) {
292 struct chunk
*chunk
= NULL
;
293 if (cur_chunk
< p
->num
)
294 chunk
= &p
->chunks
[cur_chunk
];
296 if (chunk
&& chunk
->off1
== i1
) {
297 if (DEBUG
&& i2
!= chunk
->off2
)
298 printf("i2: %d off2: %d\n", i2
, chunk
->off2
);
300 assert(i2
== chunk
->off2
);
312 if (i2
>= util2
->num_lines
)
315 if (map
[i1
] != map2
[i2
] && map
[i1
] != -1) {
317 printf("map: i1: %d %d %p i2: %d %d %p\n",
319 i1
!= -1 ? blame_lines
[map
[i1
]] : NULL
,
321 i2
!= -1 ? blame_lines
[map2
[i2
]] : NULL
);
322 if (map2
[i2
] != -1 &&
323 blame_lines
[map
[i1
]] &&
324 !blame_lines
[map2
[i2
]])
328 if (map
[i1
] == -1 && map2
[i2
] != -1)
333 printf("l1: %d l2: %d i1: %d i2: %d\n",
334 map
[i1
], map2
[i2
], i1
, i2
);
338 static int map_line(struct commit
*commit
, int line
)
340 struct util_info
*info
= commit
->object
.util
;
341 assert(line
>= 0 && line
< info
->num_lines
);
342 return info
->line_map
[line
];
345 static int fill_util_info(struct commit
*commit
, const char *path
)
347 struct util_info
*util
;
348 if (commit
->object
.util
)
351 util
= xmalloc(sizeof(struct util_info
));
353 if (get_blob_sha1(commit
->tree
, path
, util
->sha1
)) {
359 util
->line_map
= NULL
;
360 util
->num_lines
= -1;
361 commit
->object
.util
= util
;
366 static void alloc_line_map(struct commit
*commit
)
368 struct util_info
*util
= commit
->object
.util
;
377 for (i
= 0; i
< util
->size
; i
++) {
378 if (util
->buf
[i
] == '\n')
381 if(util
->buf
[util
->size
- 1] != '\n')
384 util
->line_map
= xmalloc(sizeof(int) * util
->num_lines
);
386 for (i
= 0; i
< util
->num_lines
; i
++)
387 util
->line_map
[i
] = -1;
390 static void init_first_commit(struct commit
* commit
, const char* filename
)
392 struct util_info
* util
;
395 if (fill_util_info(commit
, filename
))
396 die("fill_util_info failed");
398 alloc_line_map(commit
);
400 util
= commit
->object
.util
;
402 for (i
= 0; i
< util
->num_lines
; i
++)
403 util
->line_map
[i
] = i
;
407 static void process_commits(struct rev_info
*rev
, const char *path
,
408 struct commit
** initial
)
411 struct util_info
* util
;
417 struct commit
* commit
= get_revision(rev
);
419 init_first_commit(commit
, path
);
421 util
= commit
->object
.util
;
422 num_blame_lines
= util
->num_lines
;
423 blame_lines
= xmalloc(sizeof(struct commit
*) * num_blame_lines
);
424 blame_contents
= util
->buf
;
425 blame_len
= util
->size
;
427 for (i
= 0; i
< num_blame_lines
; i
++)
428 blame_lines
[i
] = NULL
;
430 lines_left
= num_blame_lines
;
431 blame_p
= xmalloc(sizeof(int) * num_blame_lines
);
432 new_lines
= xmalloc(sizeof(int) * num_blame_lines
);
434 struct commit_list
*parents
;
436 struct util_info
*util
;
439 printf("\nProcessing commit: %d %s\n", num_commits
,
440 sha1_to_hex(commit
->object
.sha1
));
446 memset(blame_p
, 0, sizeof(int) * num_blame_lines
);
449 for (parents
= commit
->parents
;
450 parents
!= NULL
; parents
= parents
->next
)
456 if(fill_util_info(commit
, path
))
459 alloc_line_map(commit
);
460 util
= commit
->object
.util
;
462 for (parents
= commit
->parents
;
463 parents
!= NULL
; parents
= parents
->next
) {
464 struct commit
*parent
= parents
->item
;
467 if (parse_commit(parent
) < 0)
468 die("parse_commit error");
471 printf("parent: %s\n",
472 sha1_to_hex(parent
->object
.sha1
));
474 if(fill_util_info(parent
, path
)) {
479 patch
= get_patch(parent
, commit
);
480 alloc_line_map(parent
);
481 fill_line_map(parent
, commit
, patch
);
483 for (i
= 0; i
< patch
->num
; i
++) {
485 for (l
= 0; l
< patch
->chunks
[i
].len2
; l
++) {
487 map_line(commit
, patch
->chunks
[i
].off2
+ l
);
488 if (mapped_line
!= -1) {
489 blame_p
[mapped_line
]++;
490 if (blame_p
[mapped_line
] == num_parents
)
491 new_lines
[new_lines_len
++] = mapped_line
;
499 printf("parents: %d\n", num_parents
);
501 for (i
= 0; i
< new_lines_len
; i
++) {
502 int mapped_line
= new_lines
[i
];
503 if (blame_lines
[mapped_line
] == NULL
) {
504 blame_lines
[mapped_line
] = commit
;
507 printf("blame: mapped: %d i: %d\n",
511 } while ((commit
= get_revision(rev
)) != NULL
);
518 unsigned long author_time
;
522 static void get_commit_info(struct commit
* commit
, struct commit_info
* ret
)
526 static char author_buf
[1024];
528 tmp
= strstr(commit
->buffer
, "\nauthor ") + 8;
529 len
= index(tmp
, '\n') - tmp
;
530 ret
->author
= author_buf
;
531 memcpy(ret
->author
, tmp
, len
);
538 ret
->author_tz
= tmp
+1;
543 ret
->author_time
= strtoul(tmp
, NULL
, 10);
548 ret
->author_mail
= tmp
+ 1;
553 static const char* format_time(unsigned long time
, const char* tz_str
)
555 static char time_buf
[128];
561 minutes
= tz
< 0 ? -tz
: tz
;
562 minutes
= (minutes
/ 100)*60 + (minutes
% 100);
563 minutes
= tz
< 0 ? -minutes
: minutes
;
564 t
= time
+ minutes
* 60;
567 strftime(time_buf
, sizeof(time_buf
), "%Y-%m-%d %H:%M:%S ", tm
);
568 strcat(time_buf
, tz_str
);
572 int main(int argc
, const char **argv
)
575 struct commit
*initial
= NULL
;
576 unsigned char sha1
[20];
578 const char *filename
= NULL
, *commit
= NULL
;
579 char filename_buf
[256];
585 const char* args
[10];
588 struct commit_info ci
;
592 const char* prefix
= setup_git_directory();
594 for(i
= 1; i
< argc
; i
++) {
596 if(!strcmp(argv
[i
], "-h") ||
597 !strcmp(argv
[i
], "--help"))
599 else if(!strcmp(argv
[i
], "-l") ||
600 !strcmp(argv
[i
], "--long")) {
603 } else if(!strcmp(argv
[i
], "-c") ||
604 !strcmp(argv
[i
], "--compability")) {
607 } else if(!strcmp(argv
[i
], "--")) {
610 } else if(argv
[i
][0] == '-')
632 sprintf(filename_buf
, "%s%s", prefix
, filename
);
634 strcpy(filename_buf
, filename
);
635 filename
= filename_buf
;
639 if (get_sha1(commit
, sha1
))
640 die("get_sha1 failed, commit '%s' not found", commit
);
641 c
= lookup_commit_reference(sha1
);
643 if (fill_util_info(c
, filename
)) {
644 printf("%s not found in %s\n", filename
, commit
);
650 args
[num_args
++] = NULL
;
651 args
[num_args
++] = "--topo-order";
652 args
[num_args
++] = "--remove-empty";
653 args
[num_args
++] = commit
;
654 args
[num_args
++] = "--";
655 args
[num_args
++] = filename
;
656 args
[num_args
] = NULL
;
658 setup_revisions(num_args
, args
, &rev
, "HEAD");
659 prepare_revision_walk(&rev
);
660 process_commits(&rev
, filename
, &initial
);
662 buf
= blame_contents
;
663 for (max_digits
= 1, i
= 10; i
<= num_blame_lines
+ 1; max_digits
++)
666 for (i
= 0; i
< num_blame_lines
; i
++) {
667 struct commit
*c
= blame_lines
[i
];
671 get_commit_info(c
, &ci
);
672 fwrite(sha1_to_hex(c
->object
.sha1
), sha1_len
, 1, stdout
);
674 printf("\t(%10s\t%10s\t%d)", ci
.author
,
675 format_time(ci
.author_time
, ci
.author_tz
), i
+1);
677 printf(" (%-15.15s %10s %*d) ", ci
.author
,
678 format_time(ci
.author_time
, ci
.author_tz
),
681 if(i
== num_blame_lines
- 1) {
682 fwrite(buf
, blame_len
- (buf
- blame_contents
),
684 if(blame_contents
[blame_len
-1] != '\n')
687 char* next_buf
= index(buf
, '\n') + 1;
688 fwrite(buf
, next_buf
- buf
, 1, stdout
);
694 printf("num get patch: %d\n", num_get_patch
);
695 printf("num commits: %d\n", num_commits
);
696 printf("patch time: %f\n", patch_time
/ 1000000.0);
697 printf("initial: %s\n", sha1_to_hex(initial
->object
.sha1
));