From 954a6183756a073723a7c9fd8d2feb13132876b0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 1 Oct 2006 02:16:11 -0700 Subject: gitweb: make leftmost column of blame less cluttered. Instead of labelling each and every line with clickable commit object name, this makes the blame output to show them only on the first line of each group of lines from the same revision. Placing too many lines in one group would make the commit object name to appear too widely separated and also makes it consume more memory, the number of lines in one group is capped to 20 lines or so. Also it makes mouse-over to show the minimum authorship and authordate information for extra cuteness ;-). Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl old mode 100755 new mode 100644 index 3e9d4a0..55d1b2c --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2430,9 +2430,64 @@ sub git_tag { git_footer_html(); } +sub git_blame_flush_chunk { + my ($name, $revdata, $color, $rev, @line) = @_; + my $label = substr($rev, 0, 8); + my $line = scalar(@line); + my $cnt = 0; + my $pop = ''; + + if ($revdata->{$rev} ne '') { + $pop = ' title="' . esc_html($revdata->{$rev}) . '"'; + } + + for (@line) { + my ($lineno, $data) = @$_; + $cnt++; + print "\n"; + if ($cnt == 1) { + print " 1) { + print " rowspan=\"$line\""; + } + print ">"; + print $cgi->a({-href => href(action=>"commit", + hash=>$rev, + file_name=>$name)}, + $label); + print "\n"; + } + print "". + "" . + esc_html($lineno) . "\n"; + print "" . esc_html($data) . "\n"; + print "\n"; + } +} + +# We can have up to N*2 lines. If it is more than N lines, split it +# into two to avoid orphans. +sub git_blame_flush_chunk_1 { + my ($chunk_cap, $name, $revdata, $color, $rev, @chunk) = @_; + if ($chunk_cap < @chunk) { + my @first = splice(@chunk, 0, @chunk/2); + git_blame_flush_chunk($name, + $revdata, + $color, + $rev, + @first); + } + git_blame_flush_chunk($name, + $revdata, + $color, + $rev, + @chunk); +} + sub git_blame2 { my $fd; my $ftype; + my $chunk_cap = 20; my ($have_blame) = gitweb_check_feature('blame'); if (!$have_blame) { @@ -2475,27 +2530,45 @@ sub git_blame2 { HTML + my @chunk = (); + my %revdata = (); while (<$fd>) { /^([0-9a-fA-F]{40}).*?(\d+)\)\s{1}(\s*.*)/; - my $full_rev = $1; - my $rev = substr($full_rev, 0, 8); - my $lineno = $2; - my $data = $3; - + my ($full_rev, $author, $date, $lineno, $data) = + /^([0-9a-f]{40}).*?\s\((.*?)\s+([-\d]+ [:\d]+ [-+\d]+)\s+(\d+)\)\s(.*)/; + if (!exists $revdata{$full_rev}) { + $revdata{$full_rev} = "$author, $date"; + } if (!defined $last_rev) { $last_rev = $full_rev; } elsif ($last_rev ne $full_rev) { + git_blame_flush_chunk_1($chunk_cap, + $file_name, + \%revdata, + $rev_color[$current_color], + $last_rev, @chunk); + @chunk = (); $last_rev = $full_rev; $current_color = ++$current_color % $num_colors; } - print "\n"; - print "\n"; - print "\n"; - print "\n"; - print "\n"; + elsif ($chunk_cap * 2 < @chunk) { + # We have more than N*2 lines from the same + # revision. Flush N lines and leave N lines + # in @chunk to avoid orphaned lines. + my @first = splice(@chunk, 0, $chunk_cap); + git_blame_flush_chunk($file_name, + \%revdata, + $rev_color[$current_color], + $last_rev, @first); + } + push @chunk, [$lineno, $data]; + } + if (@chunk) { + git_blame_flush_chunk_1($chunk_cap, + $file_name, + \%revdata, + $rev_color[$current_color], + $last_rev, @chunk); } print "
CommitLineData
" . - $cgi->a({-href => href(action=>"commit", hash=>$full_rev, file_name=>$file_name)}, - esc_html($rev)) . "" . - esc_html($lineno) . "" . esc_html($data) . "
\n"; print ""; -- cgit v0.10.2-6-g49f6 From 2172ce4b01c862e66e3d581282dc92223cbd28fa Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 3 Oct 2006 02:30:47 -0700 Subject: gitweb: prepare for repositories with packed refs. When a repository is initialized long time ago with symbolic HEAD, and "git-pack-refs --prune" is run, HEAD will be a dangling symlink to refs/heads/ somewhere. Running -e "$dir/HEAD" to guess if $dir is a git repository does not give us the right answer anymore in such a case. Also factor out two places that checked if the repository can be exported with similar code into a call to a new function, check_export_ok. Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 55d1b2c..671a4e6 100644 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -180,6 +180,22 @@ sub feature_pickaxe { return ($_[0]); } +# checking HEAD file with -e is fragile if the repository was +# initialized long time ago (i.e. symlink HEAD) and was pack-ref'ed +# and then pruned. +sub check_head_link { + my ($dir) = @_; + my $headfile = "$dir/HEAD"; + return ((-e $headfile) || + (-l $headfile && readlink($headfile) =~ /^refs\/heads\//)); +} + +sub check_export_ok { + my ($dir) = @_; + return (check_head_link($dir) && + (!$export_ok || -e "$dir/$export_ok")); +} + # rename detection options for git-diff and git-diff-tree # - default is '-M', with the cost proportional to # (number of removed files) * (number of new files). @@ -212,7 +228,7 @@ our $project = $cgi->param('p'); if (defined $project) { if (!validate_pathname($project) || !(-d "$projectroot/$project") || - !(-e "$projectroot/$project/HEAD") || + !check_head_link("$projectroot/$project") || ($export_ok && !(-e "$projectroot/$project/$export_ok")) || ($strict_export && !project_in_list($project))) { undef $project; @@ -289,7 +305,7 @@ sub evaluate_path_info { # find which part of PATH_INFO is project $project = $path_info; $project =~ s,/+$,,; - while ($project && !-e "$projectroot/$project/HEAD") { + while ($project && !check_head_link("$projectroot/$project")) { $project =~ s,/*[^/]*$,,; } # validate project @@ -816,8 +832,7 @@ sub git_get_projects_list { my $subdir = substr($File::Find::name, $pfxlen + 1); # we check related file in $projectroot - if (-e "$projectroot/$subdir/HEAD" && (!$export_ok || - -e "$projectroot/$subdir/$export_ok")) { + if (check_export_ok("$projectroot/$subdir")) { push @list, { path => $subdir }; $File::Find::prune = 1; } @@ -838,8 +853,7 @@ sub git_get_projects_list { if (!defined $path) { next; } - if (-e "$projectroot/$path/HEAD" && (!$export_ok || - -e "$projectroot/$path/$export_ok")) { + if (check_export_ok("$projectroot/$path")) { my $pr = { path => $path, owner => decode("utf8", $owner, Encode::FB_DEFAULT), -- cgit v0.10.2-6-g49f6 From 9074484a2ba28cd41a78e0b10074a8882653bf3a Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 4 Oct 2006 14:54:32 -0700 Subject: Revert 954a6183756a073723a7c9fd8d2feb13132876b0 Luben makes a good argument against it, and I agree with him in general. The clickable handle that appear at seemingly random places makes them look as if they are separating groups when it is not. This also restores the executable bit I lost by mistake. Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl old mode 100644 new mode 100755 index 671a4e6..32bd7b6 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2444,64 +2444,9 @@ sub git_tag { git_footer_html(); } -sub git_blame_flush_chunk { - my ($name, $revdata, $color, $rev, @line) = @_; - my $label = substr($rev, 0, 8); - my $line = scalar(@line); - my $cnt = 0; - my $pop = ''; - - if ($revdata->{$rev} ne '') { - $pop = ' title="' . esc_html($revdata->{$rev}) . '"'; - } - - for (@line) { - my ($lineno, $data) = @$_; - $cnt++; - print "\n"; - if ($cnt == 1) { - print " 1) { - print " rowspan=\"$line\""; - } - print ">"; - print $cgi->a({-href => href(action=>"commit", - hash=>$rev, - file_name=>$name)}, - $label); - print "\n"; - } - print "". - "" . - esc_html($lineno) . "\n"; - print "" . esc_html($data) . "\n"; - print "\n"; - } -} - -# We can have up to N*2 lines. If it is more than N lines, split it -# into two to avoid orphans. -sub git_blame_flush_chunk_1 { - my ($chunk_cap, $name, $revdata, $color, $rev, @chunk) = @_; - if ($chunk_cap < @chunk) { - my @first = splice(@chunk, 0, @chunk/2); - git_blame_flush_chunk($name, - $revdata, - $color, - $rev, - @first); - } - git_blame_flush_chunk($name, - $revdata, - $color, - $rev, - @chunk); -} - sub git_blame2 { my $fd; my $ftype; - my $chunk_cap = 20; my ($have_blame) = gitweb_check_feature('blame'); if (!$have_blame) { @@ -2544,45 +2489,27 @@ sub git_blame2 { HTML - my @chunk = (); - my %revdata = (); while (<$fd>) { /^([0-9a-fA-F]{40}).*?(\d+)\)\s{1}(\s*.*)/; - my ($full_rev, $author, $date, $lineno, $data) = - /^([0-9a-f]{40}).*?\s\((.*?)\s+([-\d]+ [:\d]+ [-+\d]+)\s+(\d+)\)\s(.*)/; - if (!exists $revdata{$full_rev}) { - $revdata{$full_rev} = "$author, $date"; - } + my $full_rev = $1; + my $rev = substr($full_rev, 0, 8); + my $lineno = $2; + my $data = $3; + if (!defined $last_rev) { $last_rev = $full_rev; } elsif ($last_rev ne $full_rev) { - git_blame_flush_chunk_1($chunk_cap, - $file_name, - \%revdata, - $rev_color[$current_color], - $last_rev, @chunk); - @chunk = (); $last_rev = $full_rev; $current_color = ++$current_color % $num_colors; } - elsif ($chunk_cap * 2 < @chunk) { - # We have more than N*2 lines from the same - # revision. Flush N lines and leave N lines - # in @chunk to avoid orphaned lines. - my @first = splice(@chunk, 0, $chunk_cap); - git_blame_flush_chunk($file_name, - \%revdata, - $rev_color[$current_color], - $last_rev, @first); - } - push @chunk, [$lineno, $data]; - } - if (@chunk) { - git_blame_flush_chunk_1($chunk_cap, - $file_name, - \%revdata, - $rev_color[$current_color], - $last_rev, @chunk); + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; } print "
CommitLineData
" . + $cgi->a({-href => href(action=>"commit", hash=>$full_rev, file_name=>$file_name)}, + esc_html($rev)) . "" . + esc_html($lineno) . "" . esc_html($data) . "
\n"; print ""; -- cgit v0.10.2-6-g49f6 From 9dc5f8c9c2a10f77ecfa448c93da6ceec759df73 Mon Sep 17 00:00:00 2001 From: Luben Tuikov Date: Wed, 4 Oct 2006 00:12:17 -0700 Subject: gitweb: blame: print commit-8 on the leading row of a commit-block Print commit-8 only on the first, leading row of a commit block, to complement the per-commit block coloring. Signed-off-by: Luben Tuikov Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 32bd7b6..dc21cd6 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2495,17 +2495,23 @@ HTML my $rev = substr($full_rev, 0, 8); my $lineno = $2; my $data = $3; + 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; $current_color = ++$current_color % $num_colors; + $print_c8 = 1; } print "\n"; - print "" . - $cgi->a({-href => href(action=>"commit", hash=>$full_rev, file_name=>$file_name)}, - esc_html($rev)) . "\n"; + print ""; + if ($print_c8 == 1) { + print $cgi->a({-href => href(action=>"commit", hash=>$full_rev, file_name=>$file_name)}, + esc_html($rev)); + } + print "\n"; print "" . esc_html($lineno) . "\n"; print "" . esc_html($data) . "\n"; -- cgit v0.10.2-6-g49f6 From c8aeaaf7fc7dd93a284218e6a2b6e96b90e3100c Mon Sep 17 00:00:00 2001 From: Luben Tuikov Date: Wed, 4 Oct 2006 00:13:38 -0700 Subject: gitweb: blame: Mouse-over commit-8 shows author and date Signed-off-by: Luben Tuikov Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index dc21cd6..a99fabf 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2490,11 +2490,9 @@ sub git_blame2 { CommitLineData HTML while (<$fd>) { - /^([0-9a-fA-F]{40}).*?(\d+)\)\s{1}(\s*.*)/; - my $full_rev = $1; + my ($full_rev, $author, $date, $lineno, $data) = + /^([0-9a-f]{40}).*?\s\((.*?)\s+([-\d]+ [:\d]+ [-+\d]+)\s+(\d+)\)\s(.*)/; my $rev = substr($full_rev, 0, 8); - my $lineno = $2; - my $data = $3; my $print_c8 = 0; if (!defined $last_rev) { @@ -2506,7 +2504,11 @@ HTML $print_c8 = 1; } print "\n"; - print ""; + print ""; if ($print_c8 == 1) { print $cgi->a({-href => href(action=>"commit", hash=>$full_rev, file_name=>$file_name)}, esc_html($rev)); -- cgit v0.10.2-6-g49f6 From b2d3476e15cefdbd94366d4cf46fd05c1623034f Mon Sep 17 00:00:00 2001 From: Alan Chandler Date: Tue, 3 Oct 2006 13:49:03 +0100 Subject: Gitweb - provide site headers and footers This allows web sites with a header and footer standard for each page to add them to the pages produced by gitweb. Two new variables $site_header and $site_footer are defined (default to null) each of which can specify a file containing the header and footer html. In addition, if the $stylesheet variable is undefined, a new array @stylesheets (which defaults to a single element of gitweb.css) can be used to specify more than one style sheet. This allows the clasical gitweb.css styles to be retained, but a site wide style sheet used within the header and footer areas. Signed-off-by: Alan Chandler Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 401b893..09f60bb 100644 --- a/Makefile +++ b/Makefile @@ -132,6 +132,8 @@ GITWEB_HOMETEXT = indextext.html GITWEB_CSS = gitweb.css GITWEB_LOGO = git-logo.png GITWEB_FAVICON = git-favicon.png +GITWEB_SITE_HEADER = +GITWEB_SITE_FOOTER = export prefix bindir gitexecdir template_dir GIT_PYTHON_DIR @@ -675,6 +677,8 @@ gitweb/gitweb.cgi: gitweb/gitweb.perl -e 's|++GITWEB_CSS++|$(GITWEB_CSS)|g' \ -e 's|++GITWEB_LOGO++|$(GITWEB_LOGO)|g' \ -e 's|++GITWEB_FAVICON++|$(GITWEB_FAVICON)|g' \ + -e 's|++GITWEB_SITE_HEADER++|$(GITWEB_SITE_HEADER)|g' \ + -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' \ $< >$@+ chmod +x $@+ mv $@+ $@ diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index a99fabf..e4ebce6 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -41,11 +41,19 @@ our $home_link_str = "++GITWEB_HOME_LINK_STR++"; # replace this with something more descriptive for clearer bookmarks our $site_name = "++GITWEB_SITENAME++" || $ENV{'SERVER_NAME'} || "Untitled"; +# filename of html text to include at top of each page +our $site_header = "++GITWEB_SITE_HEADER++"; # html text to include at home page our $home_text = "++GITWEB_HOMETEXT++"; +# filename of html text to include at bottom of each page +our $site_footer = "++GITWEB_SITE_FOOTER++"; + +# URI of stylesheets +our @stylesheets = ("++GITWEB_CSS++"); +our $stylesheet; +# default is not to define style sheet, but it can be overwritten later +undef $stylesheet; -# URI of default stylesheet -our $stylesheet = "++GITWEB_CSS++"; # URI of GIT logo our $logo = "++GITWEB_LOGO++"; # URI of GIT favicon, assumed to be image/png type @@ -1366,8 +1374,17 @@ sub git_header_html { $title - EOF +# print out each stylesheet that exist + if (defined $stylesheet) { +#provides backwards capability for those people who define style sheet in a config file + print ''."\n"; + } else { + foreach my $stylesheet (@stylesheets) { + next unless $stylesheet; + print ''."\n"; + } + } if (defined $project) { printf(''."\n", @@ -1385,8 +1402,15 @@ EOF } print "\n" . - "\n" . - "
\n" . + "\n"; + + if (-f $site_header) { + open (my $fd, $site_header); + print <$fd>; + close $fd; + } + + print "
\n" . "" . "\"git\"" . "\n"; @@ -1437,8 +1461,15 @@ sub git_footer_html { print $cgi->a({-href => href(project=>undef, action=>"project_index"), -class => "rss_logo"}, "TXT") . "\n"; } - print "
\n" . - "\n" . + print "
\n" ; + + if (-f $site_footer) { + open (my $fd, $site_footer); + print <$fd>; + close $fd; + } + + print "\n" . ""; } -- cgit v0.10.2-6-g49f6 From 506e49ff9ff2b5be34b2bdf15c88038b00d3ef66 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 5 Oct 2006 14:00:55 -0700 Subject: blame.c: whitespace and formatting clean-up. Signed-off-by: Junio C Hamano diff --git a/blame.c b/blame.c index 8cfd5d9..394b5c3 100644 --- a/blame.c +++ b/blame.c @@ -20,16 +20,17 @@ #define DEBUG 0 -static const char blame_usage[] = "git-blame [-c] [-l] [-t] [-S ] [--] file [commit]\n" - " -c, --compatibility Use the same output mode as git-annotate (Default: off)\n" - " -l, --long Show long commit SHA1 (Default: off)\n" - " -t, --time Show raw timestamp (Default: off)\n" - " -S, --revs-file Use revisions from revs-file instead of calling git-rev-list\n" - " -h, --help This message"; +static const char blame_usage[] = +"git-blame [-c] [-l] [-t] [-S ] [--] file [commit]\n" +" -c, --compatibility Use the same output mode as git-annotate (Default: off)\n" +" -l, --long Show long commit SHA1 (Default: off)\n" +" -t, --time Show raw timestamp (Default: off)\n" +" -S, --revs-file Use revisions from revs-file instead of calling git-rev-list\n" +" -h, --help This message"; static struct commit **blame_lines; static int num_blame_lines; -static char* blame_contents; +static char *blame_contents; static int blame_len; struct util_info { @@ -38,9 +39,9 @@ struct util_info { char *buf; unsigned long size; int num_lines; - const char* pathname; + const char *pathname; - void* topo_data; + void *topo_data; }; struct chunk { @@ -156,11 +157,10 @@ static int get_blob_sha1_internal(const unsigned char *sha1, const char *base, unsigned mode, int stage); static unsigned char blob_sha1[20]; -static const char* blame_file; +static const char *blame_file; static int get_blob_sha1(struct tree *t, const char *pathname, unsigned char *sha1) { - int i; const char *pathspec[2]; blame_file = pathname; pathspec[0] = pathname; @@ -168,12 +168,7 @@ static int get_blob_sha1(struct tree *t, const char *pathname, hashclr(blob_sha1); read_tree_recursive(t, "", 0, 0, pathspec, get_blob_sha1_internal); - for (i = 0; i < 20; i++) { - if (blob_sha1[i] != 0) - break; - } - - if (i == 20) + if (is_null_sha1(blob_sha1)) return -1; hashcpy(sha1, blob_sha1); @@ -239,7 +234,8 @@ static void print_map(struct commit *cmit, struct commit *other) if (i < util->num_lines) { num = util->line_map[i]; printf("%d\t", num); - } else + } + else printf("\t"); if (i < util2->num_lines) { @@ -247,7 +243,8 @@ static void print_map(struct commit *cmit, struct commit *other) printf("%d\t", num2); if (num != -1 && num2 != num) printf("---"); - } else + } + else printf("\t"); printf("\n"); @@ -266,12 +263,12 @@ static void fill_line_map(struct commit *commit, struct commit *other, int cur_chunk = 0; int i1, i2; - if (p->num && DEBUG) - print_patch(p); - - if (DEBUG) + if (DEBUG) { + if (p->num) + print_patch(p); printf("num lines 1: %d num lines 2: %d\n", util->num_lines, util2->num_lines); + } for (i1 = 0, i2 = 0; i1 < util->num_lines; i1++, i2++) { struct chunk *chunk = NULL; @@ -293,7 +290,8 @@ static void fill_line_map(struct commit *commit, struct commit *other, i2 += chunk->len2; cur_chunk++; - } else { + } + else { if (i2 >= util2->num_lines) break; @@ -327,7 +325,7 @@ static int map_line(struct commit *commit, int line) return info->line_map[line]; } -static struct util_info* get_util(struct commit *commit) +static struct util_info *get_util(struct commit *commit) { struct util_info *util = commit->util; @@ -369,7 +367,7 @@ static void alloc_line_map(struct commit *commit) if (util->buf[i] == '\n') util->num_lines++; } - if(util->buf[util->size - 1] != '\n') + if (util->buf[util->size - 1] != '\n') util->num_lines++; util->line_map = xmalloc(sizeof(int) * util->num_lines); @@ -378,9 +376,9 @@ static void alloc_line_map(struct commit *commit) util->line_map[i] = -1; } -static void init_first_commit(struct commit* commit, const char* filename) +static void init_first_commit(struct commit *commit, const char *filename) { - struct util_info* util = commit->util; + struct util_info *util = commit->util; int i; util->pathname = filename; @@ -395,18 +393,17 @@ static void init_first_commit(struct commit* commit, const char* filename) util->line_map[i] = i; } - static void process_commits(struct rev_info *rev, const char *path, - struct commit** initial) + struct commit **initial) { int i; - struct util_info* util; + struct util_info *util; int lines_left; int *blame_p; int *new_lines; int new_lines_len; - struct commit* commit = get_revision(rev); + struct commit *commit = get_revision(rev); assert(commit); init_first_commit(commit, path); @@ -442,7 +439,7 @@ static void process_commits(struct rev_info *rev, const char *path, parents != NULL; parents = parents->next) num_parents++; - if(num_parents == 0) + if (num_parents == 0) *initial = commit; if (fill_util_info(commit)) @@ -503,13 +500,12 @@ static void process_commits(struct rev_info *rev, const char *path, } while ((commit = get_revision(rev)) != NULL); } - -static int compare_tree_path(struct rev_info* revs, - struct commit* c1, struct commit* c2) +static int compare_tree_path(struct rev_info *revs, + struct commit *c1, struct commit *c2) { int ret; - const char* paths[2]; - struct util_info* util = c2->util; + const char *paths[2]; + struct util_info *util = c2->util; paths[0] = util->pathname; paths[1] = NULL; @@ -520,12 +516,11 @@ static int compare_tree_path(struct rev_info* revs, return ret; } - -static int same_tree_as_empty_path(struct rev_info *revs, struct tree* t1, - const char* path) +static int same_tree_as_empty_path(struct rev_info *revs, struct tree *t1, + const char *path) { int ret; - const char* paths[2]; + const char *paths[2]; paths[0] = path; paths[1] = NULL; @@ -536,9 +531,9 @@ static int same_tree_as_empty_path(struct rev_info *revs, struct tree* t1, return ret; } -static const char* find_rename(struct commit* commit, struct commit* parent) +static const char *find_rename(struct commit *commit, struct commit *parent) { - struct util_info* cutil = commit->util; + struct util_info *cutil = commit->util; struct diff_options diff_opts; const char *paths[1]; int i; @@ -564,9 +559,11 @@ static const char* find_rename(struct commit* commit, struct commit* parent) for (i = 0; i < diff_queued_diff.nr; i++) { struct diff_filepair *p = diff_queued_diff.queue[i]; - if (p->status == 'R' && !strcmp(p->one->path, cutil->pathname)) { + if (p->status == 'R' && + !strcmp(p->one->path, cutil->pathname)) { if (DEBUG) - printf("rename %s -> %s\n", p->one->path, p->two->path); + printf("rename %s -> %s\n", + p->one->path, p->two->path); return p->two->path; } } @@ -582,7 +579,7 @@ static void simplify_commit(struct rev_info *revs, struct commit *commit) return; if (!commit->parents) { - struct util_info* util = commit->util; + struct util_info *util = commit->util; if (!same_tree_as_empty_path(revs, commit->tree, util->pathname)) commit->object.flags |= TREECHANGE; @@ -608,17 +605,17 @@ static void simplify_commit(struct rev_info *revs, struct commit *commit) case REV_TREE_NEW: { - - struct util_info* util = commit->util; + struct util_info *util = commit->util; if (revs->remove_empty_trees && same_tree_as_empty_path(revs, p->tree, util->pathname)) { - const char* new_name = find_rename(commit, p); + const char *new_name = find_rename(commit, p); if (new_name) { - struct util_info* putil = get_util(p); + struct util_info *putil = get_util(p); if (!putil->pathname) putil->pathname = xstrdup(new_name); - } else { + } + else { *pp = parent->next; continue; } @@ -639,19 +636,18 @@ static void simplify_commit(struct rev_info *revs, struct commit *commit) commit->object.flags |= TREECHANGE; } - struct commit_info { - char* author; - char* author_mail; + char *author; + char *author_mail; unsigned long author_time; - char* author_tz; + char *author_tz; }; -static void get_commit_info(struct commit* commit, struct commit_info* ret) +static void get_commit_info(struct commit *commit, struct commit_info *ret) { int len; - char* tmp; + char *tmp; static char author_buf[1024]; tmp = strstr(commit->buffer, "\nauthor ") + 8; @@ -662,24 +658,24 @@ static void get_commit_info(struct commit* commit, struct commit_info* ret) tmp = ret->author; tmp += len; *tmp = 0; - while(*tmp != ' ') + while (*tmp != ' ') tmp--; ret->author_tz = tmp+1; *tmp = 0; - while(*tmp != ' ') + while (*tmp != ' ') tmp--; ret->author_time = strtoul(tmp, NULL, 10); *tmp = 0; - while(*tmp != ' ') + while (*tmp != ' ') tmp--; ret->author_mail = tmp + 1; *tmp = 0; } -static const char* format_time(unsigned long time, const char* tz_str, +static const char *format_time(unsigned long time, const char *tz_str, int show_raw_time) { static char time_buf[128]; @@ -704,15 +700,15 @@ static const char* format_time(unsigned long time, const char* tz_str, return time_buf; } -static void topo_setter(struct commit* c, void* data) +static void topo_setter(struct commit *c, void *data) { - struct util_info* util = c->util; + struct util_info *util = c->util; util->topo_data = data; } -static void* topo_getter(struct commit* c) +static void *topo_getter(struct commit *c) { - struct util_info* util = c->util; + struct util_info *util = c->util; return util->topo_data; } @@ -747,9 +743,9 @@ int main(int argc, const char **argv) int compatibility = 0; int show_raw_time = 0; int options = 1; - struct commit* start_commit; + struct commit *start_commit; - const char* args[10]; + const char *args[10]; struct rev_info rev; struct commit_info ci; @@ -758,27 +754,30 @@ int main(int argc, const char **argv) int longest_file, longest_author; int found_rename; - const char* prefix = setup_git_directory(); + const char *prefix = setup_git_directory(); git_config(git_default_config); - for(i = 1; i < argc; i++) { - if(options) { - if(!strcmp(argv[i], "-h") || + for (i = 1; i < argc; i++) { + if (options) { + if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) usage(blame_usage); - else if(!strcmp(argv[i], "-l") || - !strcmp(argv[i], "--long")) { + if (!strcmp(argv[i], "-l") || + !strcmp(argv[i], "--long")) { sha1_len = 40; continue; - } else if(!strcmp(argv[i], "-c") || - !strcmp(argv[i], "--compatibility")) { + } + if (!strcmp(argv[i], "-c") || + !strcmp(argv[i], "--compatibility")) { compatibility = 1; continue; - } else if(!strcmp(argv[i], "-t") || - !strcmp(argv[i], "--time")) { + } + if (!strcmp(argv[i], "-t") || + !strcmp(argv[i], "--time")) { show_raw_time = 1; continue; - } else if(!strcmp(argv[i], "-S")) { + } + if (!strcmp(argv[i], "-S")) { if (i + 1 < argc && !read_ancestry(argv[i + 1], &sha1_p)) { compatibility = 1; @@ -786,33 +785,34 @@ int main(int argc, const char **argv) continue; } usage(blame_usage); - } else if(!strcmp(argv[i], "--")) { + } + if (!strcmp(argv[i], "--")) { options = 0; continue; - } else if(argv[i][0] == '-') + } + if (argv[i][0] == '-') usage(blame_usage); - else - options = 0; + options = 0; } - if(!options) { - if(!filename) + if (!options) { + if (!filename) filename = argv[i]; - else if(!commit) + else if (!commit) commit = argv[i]; else usage(blame_usage); } } - if(!filename) + if (!filename) usage(blame_usage); if (commit && sha1_p) usage(blame_usage); - else if(!commit) + else if (!commit) commit = "HEAD"; - if(prefix) + if (prefix) sprintf(filename_buf, "%s%s", prefix, filename); else strcpy(filename_buf, filename); @@ -830,7 +830,6 @@ int main(int argc, const char **argv) return 1; } - init_revisions(&rev, setup_git_directory()); rev.remove_empty_trees = 1; rev.topo_order = 1; @@ -857,7 +856,7 @@ int main(int argc, const char **argv) found_rename = 0; for (i = 0; i < num_blame_lines; i++) { struct commit *c = blame_lines[i]; - struct util_info* u; + struct util_info *u; if (!c) c = initial; u = c->util; @@ -873,20 +872,20 @@ 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; - + struct util_info *u; if (!c) c = initial; - u = c->util; + get_commit_info(c, &ci); fwrite(sha1_to_hex(c->object.sha1), sha1_len, 1, stdout); - if(compatibility) { + 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 { + } + else { if (found_rename) printf(" %-*.*s", longest_file, longest_file, u->pathname); @@ -897,13 +896,14 @@ int main(int argc, const char **argv) max_digits, i+1); } - if(i == num_blame_lines - 1) { + if (i == num_blame_lines - 1) { fwrite(buf, blame_len - (buf - blame_contents), 1, stdout); - if(blame_contents[blame_len-1] != '\n') + if (blame_contents[blame_len-1] != '\n') putc('\n', stdout); - } else { - char* next_buf = strchr(buf, '\n') + 1; + } + else { + char *next_buf = strchr(buf, '\n') + 1; fwrite(buf, next_buf - buf, 1, stdout); buf = next_buf; } -- cgit v0.10.2-6-g49f6 From eb93b7240665e35ecc0ed72098e1a5b3352bdc17 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 5 Oct 2006 14:06:22 -0700 Subject: git-blame: --show-name (and -f) The new option makes the command's native output format show the filename even when there were no renames in its history, to make it simpler for Porcelains to parse its output. Signed-off-by: Junio C Hamano diff --git a/blame.c b/blame.c index 394b5c3..d830b29 100644 --- a/blame.c +++ b/blame.c @@ -752,7 +752,7 @@ int main(int argc, const char **argv) const char *buf; int max_digits; int longest_file, longest_author; - int found_rename; + int show_name = 0; const char *prefix = setup_git_directory(); git_config(git_default_config); @@ -786,6 +786,11 @@ int main(int argc, const char **argv) } usage(blame_usage); } + if (!strcmp(argv[i], "-f") || + !strcmp(argv[i], "--show-name")) { + show_name = 1; + continue; + } if (!strcmp(argv[i], "--")) { options = 0; continue; @@ -853,7 +858,6 @@ int main(int argc, const char **argv) longest_file = 0; longest_author = 0; - found_rename = 0; for (i = 0; i < num_blame_lines; i++) { struct commit *c = blame_lines[i]; struct util_info *u; @@ -861,8 +865,8 @@ int main(int argc, const char **argv) c = initial; u = c->util; - if (!found_rename && strcmp(filename, u->pathname)) - found_rename = 1; + if (!show_name && strcmp(filename, u->pathname)) + show_name = 1; if (longest_file < strlen(u->pathname)) longest_file = strlen(u->pathname); get_commit_info(c, &ci); @@ -886,7 +890,7 @@ int main(int argc, const char **argv) i+1); } else { - if (found_rename) + if (show_name) printf(" %-*.*s", longest_file, longest_file, u->pathname); printf(" (%-*.*s %10s %*d) ", -- cgit v0.10.2-6-g49f6 From cf54a029ff82ce9b565e075bfa5d97ec82841283 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 5 Oct 2006 14:06:26 -0700 Subject: 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 diff --git a/blame.c b/blame.c index d830b29..bf4a1a1 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, -- cgit v0.10.2-6-g49f6 From c137f40f8a9dfe05ee002cf5f23bf562c1f13100 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 5 Oct 2006 14:06:42 -0700 Subject: 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 diff --git a/blame.c b/blame.c index bf4a1a1..47471e8 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), -- cgit v0.10.2-6-g49f6 From b5c698d947c236559e338e45c6234ece7c819338 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 5 Oct 2006 14:07:42 -0700 Subject: 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 diff --git a/blame.c b/blame.c index 47471e8..314f3e2 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]; - - tmp = strstr(commit->buffer, "\nauthor ") + 8; - len = strchr(tmp, '\n') - tmp; - ret->author = author_buf; - memcpy(ret->author, tmp, len); + char *tmp, *endp; + + 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); -- cgit v0.10.2-6-g49f6 From eeef88cd20f2965325cde66e97a616bce4a757f0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 5 Oct 2006 13:55:58 -0700 Subject: 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 diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 0ac05cc..68347ac 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"; } -- cgit v0.10.2-6-g49f6 From f789e347465dded7fcec3a605473fa3f549792d8 Mon Sep 17 00:00:00 2001 From: Ryan Anderson Date: Mon, 9 Oct 2006 03:32:05 -0700 Subject: Remove git-annotate.perl and create a builtin-alias for git-blame Signed-off-by: Ryan Anderson Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 2c7c338..7e62e76 100644 --- a/Makefile +++ b/Makefile @@ -173,7 +173,7 @@ SCRIPT_SH = \ SCRIPT_PERL = \ git-archimport.perl git-cvsimport.perl git-relink.perl \ git-shortlog.perl git-rerere.perl \ - git-annotate.perl git-cvsserver.perl \ + git-cvsserver.perl \ git-svnimport.perl git-cvsexportcommit.perl \ git-send-email.perl git-svn.perl @@ -265,6 +265,7 @@ LIB_OBJS = \ BUILTIN_OBJS = \ builtin-add.o \ + builtin-annotate.o \ builtin-apply.o \ builtin-archive.o \ builtin-cat-file.o \ diff --git a/builtin-annotate.c b/builtin-annotate.c new file mode 100644 index 0000000..25ad473 --- /dev/null +++ b/builtin-annotate.c @@ -0,0 +1,25 @@ +/* + * "git annotate" builtin alias + * + * Copyright (C) 2006 Ryan Anderson + */ +#include "git-compat-util.h" +#include "exec_cmd.h" + +int cmd_annotate(int argc, const char **argv, const char *prefix) +{ + const char **nargv; + int i; + nargv = xmalloc(sizeof(char *) * (argc + 2)); + + nargv[0] = "blame"; + nargv[1] = "-c"; + + for (i = 1; i < argc; i++) { + nargv[i+1] = argv[i]; + } + nargv[argc + 1] = NULL; + + return execv_git_cmd(nargv); +} + diff --git a/builtin.h b/builtin.h index f9fa9ff..2c5d900 100644 --- a/builtin.h +++ b/builtin.h @@ -13,6 +13,7 @@ extern void stripspace(FILE *in, FILE *out); extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix); extern int cmd_add(int argc, const char **argv, const char *prefix); +extern int cmd_annotate(int argc, const char **argv, const char *prefix); extern int cmd_apply(int argc, const char **argv, const char *prefix); extern int cmd_archive(int argc, const char **argv, const char *prefix); extern int cmd_cat_file(int argc, const char **argv, const char *prefix); diff --git a/git-annotate.perl b/git-annotate.perl deleted file mode 100755 index 215ed26..0000000 --- a/git-annotate.perl +++ /dev/null @@ -1,708 +0,0 @@ -#!/usr/bin/perl -# Copyright 2006, Ryan Anderson -# -# GPL v2 (See COPYING) -# -# This file is licensed under the GPL v2, or a later version -# at the discretion of Linus Torvalds. - -use warnings; -use strict; -use Getopt::Long; -use POSIX qw(strftime gmtime); -use File::Basename qw(basename dirname); - -sub usage() { - print STDERR "Usage: ${\basename $0} [-s] [-S revs-file] file [ revision ] - -l, --long - Show long rev (Defaults off) - -t, --time - Show raw timestamp (Defaults off) - -r, --rename - Follow renames (Defaults on). - -S, --rev-file revs-file - Use revs from revs-file instead of calling git-rev-list - -h, --help - This message. -"; - - exit(1); -} - -our ($help, $longrev, $rename, $rawtime, $starting_rev, $rev_file) = (0, 0, 1); - -my $rc = GetOptions( "long|l" => \$longrev, - "time|t" => \$rawtime, - "help|h" => \$help, - "rename|r" => \$rename, - "rev-file|S=s" => \$rev_file); -if (!$rc or $help or !@ARGV) { - usage(); -} - -my $filename = shift @ARGV; -if (@ARGV) { - $starting_rev = shift @ARGV; -} - -my @stack = ( - { - 'rev' => defined $starting_rev ? $starting_rev : "HEAD", - 'filename' => $filename, - }, -); - -our @filelines = (); - -if (defined $starting_rev) { - @filelines = git_cat_file($starting_rev, $filename); -} else { - open(F,"<",$filename) - or die "Failed to open filename: $!"; - - while() { - chomp; - push @filelines, $_; - } - close(F); - -} - -our %revs; -our @revqueue; -our $head; - -my $revsprocessed = 0; -while (my $bound = pop @stack) { - my @revisions = git_rev_list($bound->{'rev'}, $bound->{'filename'}); - foreach my $revinst (@revisions) { - my ($rev, @parents) = @$revinst; - $head ||= $rev; - - if (!defined($rev)) { - $rev = ""; - } - $revs{$rev}{'filename'} = $bound->{'filename'}; - if (scalar @parents > 0) { - $revs{$rev}{'parents'} = \@parents; - next; - } - - if (!$rename) { - next; - } - - my $newbound = find_parent_renames($rev, $bound->{'filename'}); - if ( exists $newbound->{'filename'} && $newbound->{'filename'} ne $bound->{'filename'}) { - push @stack, $newbound; - $revs{$rev}{'parents'} = [$newbound->{'rev'}]; - } - } -} -push @revqueue, $head; -init_claim( defined $starting_rev ? $head : 'dirty'); -unless (defined $starting_rev) { - my $diff = open_pipe("git","diff","HEAD", "--",$filename) - or die "Failed to call git diff to check for dirty state: $!"; - - _git_diff_parse($diff, [$head], "dirty", ( - 'author' => gitvar_name("GIT_AUTHOR_IDENT"), - 'author_date' => sprintf("%s +0000",time()), - ) - ); - close($diff); -} -handle_rev(); - - -my $i = 0; -foreach my $l (@filelines) { - my ($output, $rev, $committer, $date); - if (ref $l eq 'ARRAY') { - ($output, $rev, $committer, $date) = @$l; - if (!$longrev && length($rev) > 8) { - $rev = substr($rev,0,8); - } - } else { - $output = $l; - ($rev, $committer, $date) = ('unknown', 'unknown', 'unknown'); - } - - printf("%s\t(%10s\t%10s\t%d)%s\n", $rev, $committer, - format_date($date), ++$i, $output); -} - -sub init_claim { - my ($rev) = @_; - for (my $i = 0; $i < @filelines; $i++) { - $filelines[$i] = [ $filelines[$i], '', '', '', 1]; - # line, - # rev, - # author, - # date, - # 1 <-- belongs to the original file. - } - $revs{$rev}{'lines'} = \@filelines; -} - - -sub handle_rev { - my $revseen = 0; - my %seen; - while (my $rev = shift @revqueue) { - next if $seen{$rev}++; - - my %revinfo = git_commit_info($rev); - - if (exists $revs{$rev}{parents} && - scalar @{$revs{$rev}{parents}} != 0) { - - git_diff_parse($revs{$rev}{'parents'}, $rev, %revinfo); - push @revqueue, @{$revs{$rev}{'parents'}}; - - } else { - # We must be at the initial rev here, so claim everything that is left. - for (my $i = 0; $i < @{$revs{$rev}{lines}}; $i++) { - if (ref ${$revs{$rev}{lines}}[$i] eq '' || ${$revs{$rev}{lines}}[$i][1] eq '') { - claim_line($i, $rev, $revs{$rev}{lines}, %revinfo); - } - } - } - } -} - - -sub git_rev_list { - my ($rev, $file) = @_; - - my $revlist; - if ($rev_file) { - open($revlist, '<' . $rev_file) - or die "Failed to open $rev_file : $!"; - } else { - $revlist = open_pipe("git-rev-list","--parents","--remove-empty",$rev,"--",$file) - or die "Failed to exec git-rev-list: $!"; - } - - my @revs; - while(my $line = <$revlist>) { - chomp $line; - my ($rev, @parents) = split /\s+/, $line; - push @revs, [ $rev, @parents ]; - } - close($revlist); - - printf("0 revs found for rev %s (%s)\n", $rev, $file) if (@revs == 0); - return @revs; -} - -sub find_parent_renames { - my ($rev, $file) = @_; - - my $patch = open_pipe("git-diff-tree", "-M50", "-r","--name-status", "-z","$rev") - or die "Failed to exec git-diff: $!"; - - local $/ = "\0"; - my %bound; - my $junk = <$patch>; - while (my $change = <$patch>) { - chomp $change; - my $filename = <$patch>; - if (!defined $filename) { - next; - } - chomp $filename; - - if ($change =~ m/^[AMD]$/ ) { - next; - } elsif ($change =~ m/^R/ ) { - my $oldfilename = $filename; - $filename = <$patch>; - chomp $filename; - if ( $file eq $filename ) { - my $parent = git_find_parent($rev, $oldfilename); - @bound{'rev','filename'} = ($parent, $oldfilename); - last; - } - } - } - close($patch); - - return \%bound; -} - - -sub git_find_parent { - my ($rev, $filename) = @_; - - my $revparent = open_pipe("git-rev-list","--remove-empty", "--parents","--max-count=1","$rev","--",$filename) - or die "Failed to open git-rev-list to find a single parent: $!"; - - my $parentline = <$revparent>; - chomp $parentline; - my ($revfound,$parent) = split m/\s+/, $parentline; - - close($revparent); - - return $parent; -} - -sub git_find_all_parents { - my ($rev) = @_; - - my $revparent = open_pipe("git-rev-list","--remove-empty", "--parents","--max-count=1","$rev") - or die "Failed to open git-rev-list to find a single parent: $!"; - - my $parentline = <$revparent>; - chomp $parentline; - my ($origrev, @parents) = split m/\s+/, $parentline; - - close($revparent); - - return @parents; -} - -sub git_merge_base { - my ($rev1, $rev2) = @_; - - my $mb = open_pipe("git-merge-base", $rev1, $rev2) - or die "Failed to open git-merge-base: $!"; - - my $base = <$mb>; - chomp $base; - - close($mb); - - return $base; -} - -# Construct a set of pseudo parents that are in the same order, -# and the same quantity as the real parents, -# but whose SHA1s are as similar to the logical parents -# as possible. -sub get_pseudo_parents { - my ($all, $fake) = @_; - - my @all = @$all; - my @fake = @$fake; - - my @pseudo; - - my %fake = map {$_ => 1} @fake; - my %seenfake; - - my $fakeidx = 0; - foreach my $p (@all) { - if (exists $fake{$p}) { - if ($fake[$fakeidx] ne $p) { - die sprintf("parent mismatch: %s != %s\nall:%s\nfake:%s\n", - $fake[$fakeidx], $p, - join(", ", @all), - join(", ", @fake), - ); - } - - push @pseudo, $p; - $fakeidx++; - $seenfake{$p}++; - - } else { - my $base = git_merge_base($fake[$fakeidx], $p); - if ($base ne $fake[$fakeidx]) { - die sprintf("Result of merge-base doesn't match fake: %s,%s != %s\n", - $fake[$fakeidx], $p, $base); - } - - # The details of how we parse the diffs - # mean that we cannot have a duplicate - # revision in the list, so if we've already - # seen the revision we would normally add, just use - # the actual revision. - if ($seenfake{$base}) { - push @pseudo, $p; - } else { - push @pseudo, $base; - $seenfake{$base}++; - } - } - } - - return @pseudo; -} - - -# Get a diff between the current revision and a parent. -# Record the commit information that results. -sub git_diff_parse { - my ($parents, $rev, %revinfo) = @_; - - my @pseudo_parents; - my @command = ("git-diff-tree"); - my $revision_spec; - - if (scalar @$parents == 1) { - - $revision_spec = join("..", $parents->[0], $rev); - @pseudo_parents = @$parents; - } else { - my @all_parents = git_find_all_parents($rev); - - if (@all_parents != @$parents) { - @pseudo_parents = get_pseudo_parents(\@all_parents, $parents); - } else { - @pseudo_parents = @$parents; - } - - $revision_spec = $rev; - push @command, "-c"; - } - - my @filenames = ( $revs{$rev}{'filename'} ); - - foreach my $parent (@$parents) { - push @filenames, $revs{$parent}{'filename'}; - } - - push @command, "-p", "-M", $revision_spec, "--", @filenames; - - - my $diff = open_pipe( @command ) - or die "Failed to call git-diff for annotation: $!"; - - _git_diff_parse($diff, \@pseudo_parents, $rev, %revinfo); - - close($diff); -} - -sub _git_diff_parse { - my ($diff, $parents, $rev, %revinfo) = @_; - - my $ri = 0; - - my $slines = $revs{$rev}{'lines'}; - my (%plines, %pi); - - my $gotheader = 0; - my ($remstart); - my $parent_count = @$parents; - - my $diff_header_regexp = "^@"; - $diff_header_regexp .= "@" x @$parents; - $diff_header_regexp .= ' -\d+,\d+' x @$parents; - $diff_header_regexp .= ' \+(\d+),\d+'; - $diff_header_regexp .= " " . ("@" x @$parents); - - my %claim_regexps; - my $allparentplus = '^' . '\\+' x @$parents . '(.*)$'; - - { - my $i = 0; - foreach my $parent (@$parents) { - - $pi{$parent} = 0; - my $r = '^' . '.' x @$parents . '(.*)$'; - my $p = $r; - substr($p,$i+1, 1) = '\\+'; - - my $m = $r; - substr($m,$i+1, 1) = '-'; - - $claim_regexps{$parent}{plus} = $p; - $claim_regexps{$parent}{minus} = $m; - - $plines{$parent} = []; - - $i++; - } - } - - DIFF: - while(<$diff>) { - chomp; - #printf("%d:%s:\n", $gotheader, $_); - if (m/$diff_header_regexp/) { - $remstart = $1 - 1; - # (0-based arrays) - - $gotheader = 1; - - foreach my $parent (@$parents) { - for (my $i = $ri; $i < $remstart; $i++) { - $plines{$parent}[$pi{$parent}++] = $slines->[$i]; - } - } - $ri = $remstart; - - next DIFF; - - } elsif (!$gotheader) { - # Skip over the leadin. - next DIFF; - } - - if (m/^\\/) { - ; - # Skip \No newline at end of file. - # But this can be internationalized, so only look - # for an initial \ - - } else { - my %claims = (); - my $negclaim = 0; - my $allclaimed = 0; - my $line; - - if (m/$allparentplus/) { - claim_line($ri, $rev, $slines, %revinfo); - $allclaimed = 1; - - } - - PARENT: - foreach my $parent (keys %claim_regexps) { - my $m = $claim_regexps{$parent}{minus}; - my $p = $claim_regexps{$parent}{plus}; - - if (m/$m/) { - $line = $1; - $plines{$parent}[$pi{$parent}++] = [ $line, '', '', '', 0 ]; - $negclaim++; - - } elsif (m/$p/) { - $line = $1; - if (get_line($slines, $ri) eq $line) { - # Found a match, claim - $claims{$parent}++; - - } else { - die sprintf("Sync error: %d\n|%s\n|%s\n%s => %s\n", - $ri, $line, - get_line($slines, $ri), - $rev, $parent); - } - } - } - - if (%claims) { - foreach my $parent (@$parents) { - next if $claims{$parent} || $allclaimed; - $plines{$parent}[$pi{$parent}++] = $slines->[$ri]; - #[ $line, '', '', '', 0 ]; - } - $ri++; - - } elsif ($negclaim) { - next DIFF; - - } else { - if (substr($_,scalar @$parents) ne get_line($slines,$ri) ) { - foreach my $parent (@$parents) { - printf("parent %s is on line %d\n", $parent, $pi{$parent}); - } - - my @context; - for (my $i = -2; $i < 2; $i++) { - push @context, get_line($slines, $ri + $i); - } - my $context = join("\n", @context); - - my $justline = substr($_, scalar @$parents); - die sprintf("Line %d, does not match:\n|%s|\n|%s|\n%s\n", - $ri, - $justline, - $context); - } - foreach my $parent (@$parents) { - $plines{$parent}[$pi{$parent}++] = $slines->[$ri]; - } - $ri++; - } - } - } - - for (my $i = $ri; $i < @{$slines} ; $i++) { - foreach my $parent (@$parents) { - push @{$plines{$parent}}, $slines->[$ri]; - } - $ri++; - } - - foreach my $parent (@$parents) { - $revs{$parent}{lines} = $plines{$parent}; - } - - return; -} - -sub get_line { - my ($lines, $index) = @_; - - return ref $lines->[$index] ne '' ? $lines->[$index][0] : $lines->[$index]; -} - -sub git_cat_file { - my ($rev, $filename) = @_; - return () unless defined $rev && defined $filename; - - my $blob = git_ls_tree($rev, $filename); - die "Failed to find a blob for $filename in rev $rev\n" if !defined $blob; - - my $catfile = open_pipe("git","cat-file", "blob", $blob) - or die "Failed to git-cat-file blob $blob (rev $rev, file $filename): " . $!; - - my @lines; - while(<$catfile>) { - chomp; - push @lines, $_; - } - close($catfile); - - return @lines; -} - -sub git_ls_tree { - my ($rev, $filename) = @_; - - my $lstree = open_pipe("git","ls-tree",$rev,$filename) - or die "Failed to call git ls-tree: $!"; - - my ($mode, $type, $blob, $tfilename); - while(<$lstree>) { - chomp; - ($mode, $type, $blob, $tfilename) = split(/\s+/, $_, 4); - last if ($tfilename eq $filename); - } - close($lstree); - - return $blob if ($tfilename eq $filename); - die "git-ls-tree failed to find blob for $filename"; - -} - - - -sub claim_line { - my ($floffset, $rev, $lines, %revinfo) = @_; - my $oline = get_line($lines, $floffset); - @{$lines->[$floffset]} = ( $oline, $rev, - $revinfo{'author'}, $revinfo{'author_date'} ); - #printf("Claiming line %d with rev %s: '%s'\n", - # $floffset, $rev, $oline) if 1; -} - -sub git_commit_info { - my ($rev) = @_; - my $commit = open_pipe("git-cat-file", "commit", $rev) - or die "Failed to call git-cat-file: $!"; - - my %info; - while(<$commit>) { - chomp; - last if (length $_ == 0); - - if (m/^author (.*) <(.*)> (.*)$/) { - $info{'author'} = $1; - $info{'author_email'} = $2; - $info{'author_date'} = $3; - } elsif (m/^committer (.*) <(.*)> (.*)$/) { - $info{'committer'} = $1; - $info{'committer_email'} = $2; - $info{'committer_date'} = $3; - } - } - close($commit); - - return %info; -} - -sub format_date { - if ($rawtime) { - return $_[0]; - } - my ($timestamp, $timezone) = split(' ', $_[0]); - my $minutes = abs($timezone); - $minutes = int($minutes / 100) * 60 + ($minutes % 100); - if ($timezone < 0) { - $minutes = -$minutes; - } - my $t = $timestamp + $minutes * 60; - return strftime("%Y-%m-%d %H:%M:%S " . $timezone, gmtime($t)); -} - -# Copied from git-send-email.perl - We need a Git.pm module.. -sub gitvar { - my ($var) = @_; - my $fh; - my $pid = open($fh, '-|'); - die "$!" unless defined $pid; - if (!$pid) { - exec('git-var', $var) or die "$!"; - } - my ($val) = <$fh>; - close $fh or die "$!"; - chomp($val); - return $val; -} - -sub gitvar_name { - my ($name) = @_; - my $val = gitvar($name); - my @field = split(/\s+/, $val); - return join(' ', @field[0...(@field-4)]); -} - -sub open_pipe { - if ($^O eq '##INSERT_ACTIVESTATE_STRING_HERE##') { - return open_pipe_activestate(@_); - } else { - return open_pipe_normal(@_); - } -} - -sub open_pipe_activestate { - tie *fh, "Git::ActiveStatePipe", @_; - return *fh; -} - -sub open_pipe_normal { - my (@execlist) = @_; - - my $pid = open my $kid, "-|"; - defined $pid or die "Cannot fork: $!"; - - unless ($pid) { - exec @execlist; - die "Cannot exec @execlist: $!"; - } - - return $kid; -} - -package Git::ActiveStatePipe; -use strict; - -sub TIEHANDLE { - my ($class, @params) = @_; - my $cmdline = join " ", @params; - my @data = qx{$cmdline}; - bless { i => 0, data => \@data }, $class; -} - -sub READLINE { - my $self = shift; - if ($self->{i} >= scalar @{$self->{data}}) { - return undef; - } - return $self->{'data'}->[ $self->{i}++ ]; -} - -sub CLOSE { - my $self = shift; - delete $self->{data}; - delete $self->{i}; -} - -sub EOF { - my $self = shift; - return ($self->{i} >= scalar @{$self->{data}}); -} diff --git a/git.c b/git.c index b8e8622..32c356e 100644 --- a/git.c +++ b/git.c @@ -219,6 +219,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) int option; } commands[] = { { "add", cmd_add, RUN_SETUP }, + { "annotate", cmd_annotate, }, { "apply", cmd_apply }, { "archive", cmd_archive }, { "cat-file", cmd_cat_file, RUN_SETUP }, -- cgit v0.10.2-6-g49f6 From d15c55aa052434aee75c73da4ed771af3e3b6f61 Mon Sep 17 00:00:00 2001 From: Luben Tuikov Date: Wed, 11 Oct 2006 00:30:05 -0700 Subject: gitweb: blame porcelain: lineno and orig lineno swapped Signed-off-by: Luben Tuikov Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 68347ac..41d0477 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2533,7 +2533,7 @@ HTML while (1) { $_ = <$fd>; last unless defined $_; - my ($full_rev, $lineno, $orig_lineno, $group_size) = + my ($full_rev, $orig_lineno, $lineno, $group_size) = /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/; if (!exists $metainfo{$full_rev}) { $metainfo{$full_rev} = {}; -- cgit v0.10.2-6-g49f6 From b24642b2f27b2cf01f6e726d816a2a1f5955ae3e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 12 Oct 2006 00:44:27 -0700 Subject: blame: Document and add help text for -f, -n, and -p New options --show-name, --show-number and --porcelain were not documented. Also add -p as a short-hand for --porcelain for consistency. Signed-off-by: Junio C Hamano diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt index e1f8944..9891c1d 100644 --- a/Documentation/git-blame.txt +++ b/Documentation/git-blame.txt @@ -7,7 +7,7 @@ git-blame - Show what revision and author last modified each line of a file SYNOPSIS -------- -'git-blame' [-c] [-l] [-t] [-S ] [--] [] +'git-blame' [-c] [-l] [-t] [-f] [-n] [-p] [-S ] [--] [] DESCRIPTION ----------- @@ -45,10 +45,47 @@ OPTIONS -S, --rev-file :: Use revs from revs-file instead of calling gitlink:git-rev-list[1]. +-f, --show-name:: + Show filename in the original commit. By default + filename is shown if there is any line that came from a + file with different name, due to rename detection. + +-n, --show-number:: + Show line number in the original commit (Default: off). + +-p, --porcelain:: + Show in a format designed for machine consumption. + -h, --help:: Show help message. +THE PORCELAIN FORMAT +-------------------- + +In this format, each line is output after a header; the +header at the minumum 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 +at least 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. + SEE ALSO -------- gitlink:git-annotate[1] diff --git a/blame.c b/blame.c index 314f3e2..e664813 100644 --- a/blame.c +++ b/blame.c @@ -22,11 +22,14 @@ #define DEBUG 0 static const char blame_usage[] = -"git-blame [-c] [-l] [-t] [-S ] [--] file [commit]\n" +"git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-S ] [--] file [commit]\n" " -c, --compatibility Use the same output mode as git-annotate (Default: off)\n" " -l, --long Show long commit SHA1 (Default: off)\n" " -t, --time Show raw timestamp (Default: off)\n" -" -S, --revs-file Use revisions from revs-file instead of calling git-rev-list\n" +" -f, --show-name Show original filename (Default: auto)\n" +" -n, --show-number Show original linenumber (Default: off)\n" +" -p, --porcelain Show in a format designed for machine consumption\n" +" -S revs-file Use revisions from revs-file instead of calling git-rev-list\n" " -h, --help This message"; static struct commit **blame_lines; @@ -951,7 +954,8 @@ int main(int argc, const char **argv) show_number = 1; continue; } - if (!strcmp(argv[i], "--porcelain")) { + if (!strcmp(argv[i], "-p") || + !strcmp(argv[i], "--porcelain")) { porcelain = 1; sha1_len = 40; show_raw_time = 1; -- cgit v0.10.2-6-g49f6 From 48fd688ab0ba7d41ff4286aee20f6f0b86e4c41c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 12 Oct 2006 00:47:03 -0700 Subject: gitweb: spell "blame --porcelain" with -p Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 41d0477..34b88ce 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2504,7 +2504,7 @@ sub git_blame2 { if ($ftype !~ "blob") { die_error("400 Bad Request", "Object is not a blob"); } - open ($fd, "-|", git_cmd(), "blame", '--porcelain', '--', + open ($fd, "-|", git_cmd(), "blame", '-p', '--', $file_name, $hash_base) or die_error(undef, "Open git-blame failed"); git_header_html(); -- cgit v0.10.2-6-g49f6
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) . "