From cf54a029ff82ce9b565e075bfa5d97ec82841283 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 5 Oct 2006 14:06:26 -0700 Subject: [PATCH 1/4] git-blame: --show-number (and -n) The new option makes the command's native output format show the original line number in the blamed revision. Note: the current implementation of find_orig_linenum involves linear search through the line_map array every time. It should probably build a reverse map upfront and do a simple look-up to speed things up, but I'll leave it to more clever and beautiful people ;-). Signed-off-by: Junio C Hamano --- blame.c | 42 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/blame.c b/blame.c index d830b293fd..bf4a1a1ff0 100644 --- a/blame.c +++ b/blame.c @@ -731,6 +731,25 @@ static int read_ancestry(const char *graft_file, return 0; } +static int lineno_width(int lines) +{ + int i, width; + + for (width = 1, i = 10; i <= lines + 1; width++) + i *= 10; + return width; +} + +static int find_orig_linenum(struct util_info *u, int lineno) +{ + int i; + + for (i = 0; i < u->num_lines; i++) + if (lineno == u->line_map[i]) + return i + 1; + return 0; +} + int main(int argc, const char **argv) { int i; @@ -750,9 +769,10 @@ int main(int argc, const char **argv) struct commit_info ci; const char *buf; - int max_digits; - int longest_file, longest_author; + int max_digits, max_orig_digits; + int longest_file, longest_author, longest_file_lines; int show_name = 0; + int show_number = 0; const char *prefix = setup_git_directory(); git_config(git_default_config); @@ -791,6 +811,11 @@ int main(int argc, const char **argv) show_name = 1; continue; } + if (!strcmp(argv[i], "-n") || + !strcmp(argv[i], "--show-number")) { + show_number = 1; + continue; + } if (!strcmp(argv[i], "--")) { options = 0; continue; @@ -853,11 +878,11 @@ int main(int argc, const char **argv) process_commits(&rev, filename, &initial); buf = blame_contents; - for (max_digits = 1, i = 10; i <= num_blame_lines + 1; max_digits++) - i *= 10; + max_digits = lineno_width(num_blame_lines); longest_file = 0; longest_author = 0; + longest_file_lines = 0; for (i = 0; i < num_blame_lines; i++) { struct commit *c = blame_lines[i]; struct util_info *u; @@ -869,17 +894,23 @@ int main(int argc, const char **argv) show_name = 1; if (longest_file < strlen(u->pathname)) longest_file = strlen(u->pathname); + if (longest_file_lines < u->num_lines) + longest_file_lines = u->num_lines; get_commit_info(c, &ci); if (longest_author < strlen(ci.author)) longest_author = strlen(ci.author); } + max_orig_digits = lineno_width(longest_file_lines); + for (i = 0; i < num_blame_lines; i++) { struct commit *c = blame_lines[i]; struct util_info *u; + int lineno; if (!c) c = initial; u = c->util; + lineno = find_orig_linenum(u, i); get_commit_info(c, &ci); fwrite(sha1_to_hex(c->object.sha1), sha1_len, 1, stdout); @@ -893,6 +924,9 @@ int main(int argc, const char **argv) if (show_name) printf(" %-*.*s", longest_file, longest_file, u->pathname); + if (show_number) + printf(" %*d", max_orig_digits, + lineno); printf(" (%-*.*s %10s %*d) ", longest_author, longest_author, ci.author, format_time(ci.author_time, ci.author_tz, From c137f40f8a9dfe05ee002cf5f23bf562c1f13100 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 5 Oct 2006 14:06:42 -0700 Subject: [PATCH 2/4] blame.c: move code to output metainfo into a separate function. This does not change any behaviour, but just separates out the code to emit the initial part of the output of each line into a separate function, since I'll be mucking with it further. Signed-off-by: Junio C Hamano --- blame.c | 76 ++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 45 insertions(+), 31 deletions(-) diff --git a/blame.c b/blame.c index bf4a1a1ff0..47471e8889 100644 --- a/blame.c +++ b/blame.c @@ -750,6 +750,42 @@ static int find_orig_linenum(struct util_info *u, int lineno) return 0; } +static void emit_meta(struct commit *c, int lno, + int sha1_len, int compatibility, + int show_name, int show_number, int show_raw_time, + int longest_file, int longest_author, + int max_digits, int max_orig_digits) +{ + struct util_info *u; + int lineno; + struct commit_info ci; + + u = c->util; + lineno = find_orig_linenum(u, lno); + + get_commit_info(c, &ci); + fwrite(sha1_to_hex(c->object.sha1), sha1_len, 1, stdout); + if (compatibility) { + printf("\t(%10s\t%10s\t%d)", ci.author, + format_time(ci.author_time, ci.author_tz, + show_raw_time), + lno + 1); + } + else { + if (show_name) + printf(" %-*.*s", longest_file, longest_file, + u->pathname); + if (show_number) + printf(" %*d", max_orig_digits, + lineno); + printf(" (%-*.*s %10s %*d) ", + longest_author, longest_author, ci.author, + format_time(ci.author_time, ci.author_tz, + show_raw_time), + max_digits, lno + 1); + } +} + int main(int argc, const char **argv) { int i; @@ -877,6 +913,10 @@ int main(int argc, const char **argv) prepare_revision_walk(&rev); process_commits(&rev, filename, &initial); + for (i = 0; i < num_blame_lines; i++) + if (!blame_lines[i]) + blame_lines[i] = initial; + buf = blame_contents; max_digits = lineno_width(num_blame_lines); @@ -886,8 +926,6 @@ int main(int argc, const char **argv) for (i = 0; i < num_blame_lines; i++) { struct commit *c = blame_lines[i]; struct util_info *u; - if (!c) - c = initial; u = c->util; if (!show_name && strcmp(filename, u->pathname)) @@ -904,35 +942,11 @@ int main(int argc, const char **argv) max_orig_digits = lineno_width(longest_file_lines); for (i = 0; i < num_blame_lines; i++) { - struct commit *c = blame_lines[i]; - struct util_info *u; - int lineno; - if (!c) - c = initial; - u = c->util; - lineno = find_orig_linenum(u, i); - - get_commit_info(c, &ci); - fwrite(sha1_to_hex(c->object.sha1), sha1_len, 1, stdout); - if (compatibility) { - printf("\t(%10s\t%10s\t%d)", ci.author, - format_time(ci.author_time, ci.author_tz, - show_raw_time), - i+1); - } - else { - if (show_name) - printf(" %-*.*s", longest_file, longest_file, - u->pathname); - if (show_number) - printf(" %*d", max_orig_digits, - lineno); - printf(" (%-*.*s %10s %*d) ", - longest_author, longest_author, ci.author, - format_time(ci.author_time, ci.author_tz, - show_raw_time), - max_digits, i+1); - } + emit_meta(blame_lines[i], i, + sha1_len, compatibility, + show_name, show_number, show_raw_time, + longest_file, longest_author, + max_digits, max_orig_digits); if (i == num_blame_lines - 1) { fwrite(buf, blame_len - (buf - blame_contents), From b5c698d947c236559e338e45c6234ece7c819338 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 5 Oct 2006 14:07:42 -0700 Subject: [PATCH 3/4] git-blame --porcelain The new option makes the command's native output format to emit output that is easier to handle by Porcelain. Each line is output after a header. The header at the minimum has the first line which has: - 40-byte SHA-1 of the commit the line is attributed to; - the line number of the line in the original file; - the line number of the line in the final file; - on a line that starts a group of line from a different commit than the previous one, the number of lines in this group. On subsequent lines this field is absent. This header line is followed by the following information once for each commit: - author name ("author"), email ("author-mail"), time ("author-time"), and timezone ("author-tz"); similarly for committer. - filename in the commit the line is attributed to. - the first line of the commit log message ("summary"). The contents of the actual line is output after the above header, prefixed by a TAB. This is to allow adding more header elements later. Signed-off-by: Junio C Hamano --- blame.c | 147 ++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 126 insertions(+), 21 deletions(-) diff --git a/blame.c b/blame.c index 47471e8889..314f3e2f0d 100644 --- a/blame.c +++ b/blame.c @@ -17,6 +17,7 @@ #include "diffcore.h" #include "revision.h" #include "xdiff-interface.h" +#include "quote.h" #define DEBUG 0 @@ -40,6 +41,7 @@ struct util_info { unsigned long size; int num_lines; const char *pathname; + unsigned meta_given:1; void *topo_data; }; @@ -332,12 +334,8 @@ static struct util_info *get_util(struct commit *commit) if (util) return util; - util = xmalloc(sizeof(struct util_info)); - util->buf = NULL; - util->size = 0; - util->line_map = NULL; + util = xcalloc(1, sizeof(struct util_info)); util->num_lines = -1; - util->pathname = NULL; commit->util = util; return util; } @@ -642,39 +640,99 @@ struct commit_info char *author_mail; unsigned long author_time; char *author_tz; + + /* filled only when asked for details */ + char *committer; + char *committer_mail; + unsigned long committer_time; + char *committer_tz; + + char *summary; }; -static void get_commit_info(struct commit *commit, struct commit_info *ret) +static void get_ac_line(const char *inbuf, const char *what, + int bufsz, char *person, char **mail, + unsigned long *time, char **tz) { int len; - char *tmp; - static char author_buf[1024]; + char *tmp, *endp; - tmp = strstr(commit->buffer, "\nauthor ") + 8; - len = strchr(tmp, '\n') - tmp; - ret->author = author_buf; - memcpy(ret->author, tmp, len); + tmp = strstr(inbuf, what); + if (!tmp) + goto error_out; + tmp += strlen(what); + endp = strchr(tmp, '\n'); + if (!endp) + len = strlen(tmp); + else + len = endp - tmp; + if (bufsz <= len) { + error_out: + /* Ugh */ + person = *mail = *tz = "(unknown)"; + *time = 0; + return; + } + memcpy(person, tmp, len); - tmp = ret->author; + tmp = person; tmp += len; *tmp = 0; while (*tmp != ' ') tmp--; - ret->author_tz = tmp+1; + *tz = tmp+1; *tmp = 0; while (*tmp != ' ') tmp--; - ret->author_time = strtoul(tmp, NULL, 10); + *time = strtoul(tmp, NULL, 10); *tmp = 0; while (*tmp != ' ') tmp--; - ret->author_mail = tmp + 1; - + *mail = tmp + 1; *tmp = 0; } +static void get_commit_info(struct commit *commit, struct commit_info *ret, int detailed) +{ + int len; + char *tmp, *endp; + static char author_buf[1024]; + static char committer_buf[1024]; + static char summary_buf[1024]; + + ret->author = author_buf; + get_ac_line(commit->buffer, "\nauthor ", + sizeof(author_buf), author_buf, &ret->author_mail, + &ret->author_time, &ret->author_tz); + + if (!detailed) + return; + + ret->committer = committer_buf; + get_ac_line(commit->buffer, "\ncommitter ", + sizeof(committer_buf), committer_buf, &ret->committer_mail, + &ret->committer_time, &ret->committer_tz); + + ret->summary = summary_buf; + tmp = strstr(commit->buffer, "\n\n"); + if (!tmp) { + error_out: + sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1)); + return; + } + tmp += 2; + endp = strchr(tmp, '\n'); + if (!endp) + goto error_out; + len = endp - tmp; + if (len >= sizeof(summary_buf)) + goto error_out; + memcpy(summary_buf, tmp, len); + summary_buf[len] = 0; +} + static const char *format_time(unsigned long time, const char *tz_str, int show_raw_time) { @@ -751,7 +809,7 @@ static int find_orig_linenum(struct util_info *u, int lineno) } static void emit_meta(struct commit *c, int lno, - int sha1_len, int compatibility, + int sha1_len, int compatibility, int porcelain, int show_name, int show_number, int show_raw_time, int longest_file, int longest_author, int max_digits, int max_orig_digits) @@ -763,7 +821,47 @@ static void emit_meta(struct commit *c, int lno, u = c->util; lineno = find_orig_linenum(u, lno); - get_commit_info(c, &ci); + if (porcelain) { + int group_size = -1; + struct commit *cc = (lno == 0) ? NULL : blame_lines[lno-1]; + if (cc != c) { + /* This is the beginning of this group */ + int i; + for (i = lno + 1; i < num_blame_lines; i++) + if (blame_lines[i] != c) + break; + group_size = i - lno; + } + if (0 < group_size) + printf("%s %d %d %d\n", sha1_to_hex(c->object.sha1), + lineno, lno + 1, group_size); + else + printf("%s %d %d\n", sha1_to_hex(c->object.sha1), + lineno, lno + 1); + if (!u->meta_given) { + get_commit_info(c, &ci, 1); + printf("author %s\n", ci.author); + printf("author-mail %s\n", ci.author_mail); + printf("author-time %lu\n", ci.author_time); + printf("author-tz %s\n", ci.author_tz); + printf("committer %s\n", ci.committer); + printf("committer-mail %s\n", ci.committer_mail); + printf("committer-time %lu\n", ci.committer_time); + printf("committer-tz %s\n", ci.committer_tz); + printf("filename "); + if (quote_c_style(u->pathname, NULL, NULL, 0)) + quote_c_style(u->pathname, NULL, stdout, 0); + else + fputs(u->pathname, stdout); + printf("\nsummary %s\n", ci.summary); + + u->meta_given = 1; + } + putchar('\t'); + return; + } + + get_commit_info(c, &ci, 0); fwrite(sha1_to_hex(c->object.sha1), sha1_len, 1, stdout); if (compatibility) { printf("\t(%10s\t%10s\t%d)", ci.author, @@ -809,6 +907,7 @@ int main(int argc, const char **argv) int longest_file, longest_author, longest_file_lines; int show_name = 0; int show_number = 0; + int porcelain = 0; const char *prefix = setup_git_directory(); git_config(git_default_config); @@ -852,6 +951,12 @@ int main(int argc, const char **argv) show_number = 1; continue; } + if (!strcmp(argv[i], "--porcelain")) { + porcelain = 1; + sha1_len = 40; + show_raw_time = 1; + continue; + } if (!strcmp(argv[i], "--")) { options = 0; continue; @@ -934,7 +1039,7 @@ int main(int argc, const char **argv) longest_file = strlen(u->pathname); if (longest_file_lines < u->num_lines) longest_file_lines = u->num_lines; - get_commit_info(c, &ci); + get_commit_info(c, &ci, 0); if (longest_author < strlen(ci.author)) longest_author = strlen(ci.author); } @@ -943,7 +1048,7 @@ int main(int argc, const char **argv) for (i = 0; i < num_blame_lines; i++) { emit_meta(blame_lines[i], i, - sha1_len, compatibility, + sha1_len, compatibility, porcelain, show_name, show_number, show_raw_time, longest_file, longest_author, max_digits, max_orig_digits); From eeef88cd20f2965325cde66e97a616bce4a757f0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 5 Oct 2006 13:55:58 -0700 Subject: [PATCH 4/4] gitweb: use blame --porcelain This makes gitweb (git_blame2) use "blame --porcelain", which lets the caller to figure out which line in the original version each line comes from. Using this information, change the behaviour of clicking the line number to go to the line of the blame output for the original commit. Before, clicking the line number meant "scoll up to show this line at the beginning of the page", which was not all that useful. The new behaviour lets you click on the line you are interested in to view the line in the context it was introduced, and keep digging deeper as you examine it. Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 65 +++++++++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 21 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 0ac05cc718..68347ac975 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -974,6 +974,9 @@ sub parse_date { $date{'hour_local'} = $hour; $date{'minute_local'} = $min; $date{'tz_local'} = $tz; + $date{'iso-tz'} = sprintf ("%04d-%02d-%02d %02d:%02d:%02d %s", + 1900+$year, $mon+1, $mday, + $hour, $min, $sec, $tz); return %date; } @@ -2501,7 +2504,8 @@ sub git_blame2 { if ($ftype !~ "blob") { die_error("400 Bad Request", "Object is not a blob"); } - open ($fd, "-|", git_cmd(), "blame", '-l', '--', $file_name, $hash_base) + open ($fd, "-|", git_cmd(), "blame", '--porcelain', '--', + $file_name, $hash_base) or die_error(undef, "Open git-blame failed"); git_header_html(); my $formats_nav = @@ -2525,33 +2529,52 @@ sub git_blame2 { HTML - while (<$fd>) { - my ($full_rev, $author, $date, $lineno, $data) = - /^([0-9a-f]{40}).*?\s\((.*?)\s+([-\d]+ [:\d]+ [-+\d]+)\s+(\d+)\)\s(.*)/; + my %metainfo = (); + while (1) { + $_ = <$fd>; + last unless defined $_; + my ($full_rev, $lineno, $orig_lineno, $group_size) = + /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/; + if (!exists $metainfo{$full_rev}) { + $metainfo{$full_rev} = {}; + } + my $meta = $metainfo{$full_rev}; + while (<$fd>) { + last if (s/^\t//); + if (/^(\S+) (.*)$/) { + $meta->{$1} = $2; + } + } + my $data = $_; my $rev = substr($full_rev, 0, 8); - my $print_c8 = 0; - - if (!defined $last_rev) { - $last_rev = $full_rev; - $print_c8 = 1; - } elsif ($last_rev ne $full_rev) { - $last_rev = $full_rev; + my $author = $meta->{'author'}; + my %date = parse_date($meta->{'author-time'}, + $meta->{'author-tz'}); + my $date = $date{'iso-tz'}; + if ($group_size) { $current_color = ++$current_color % $num_colors; - $print_c8 = 1; } print "\n"; - print "\n"; } - print "\n"; - print "\n"; + my $blamed = href(action => 'blame', + file_name => $meta->{'filename'}, + hash_base => $full_rev); + print ""; print "\n"; print "\n"; }
CommitLineData
"; - if ($print_c8 == 1) { - print $cgi->a({-href => href(action=>"commit", hash=>$full_rev, file_name=>$file_name)}, + print " rowspan=\"$group_size\"" if ($group_size > 1); + print ">"; + print $cgi->a({-href => href(action=>"commit", + hash=>$full_rev, + file_name=>$file_name)}, esc_html($rev)); + print "" . - esc_html($lineno) . ""; + print $cgi->a({ -href => "$blamed#l$orig_lineno", + -id => "l$lineno", + -class => "linenr" }, + esc_html($lineno)); + print "" . esc_html($data) . "