{"thread":{"id":"5435","subject":"[PATCH 1/4] gitweb: Move git-ls-tree output parsing to parse_ls_tree_line","startedAt":"2006-08-30T22:32:15Z","lastAt":"2006-08-30T22:36:54Z","messageCount":4,"participants":["Jakub Narebski"],"isPatch":true,"patchVersion":1,"patchTotal":4},"messages":[{"id":"26117","messageId":"200608310032.15543.jnareb@gmail.com","threadId":"5435","inReplyTo":"200608310030.33512.jnareb@gmail.com","subject":"[PATCH 1/4] gitweb: Move git-ls-tree output parsing to parse_ls_tree_line","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2006-08-30T22:32:15Z","receivedAt":"2006-08-30T22:32:15Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Add new subroutine parse_ls_tree_line and use it in git_tree.\n\nSigned-off-by: Jakub Narebski <jnareb@gmail.com>\n---\nThis patch could be applied regardless of the fact that this \n\"tree_blame\" view series is proof-of-concept series.\n\n gitweb/gitweb.perl |   62 ++++++++++++++++++++++++++++++++++++----------------\n 1 files changed, 43 insertions(+), 19 deletions(-)\n\ndiff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\nindex fa7f62a..84a13fd 100755\n--- a/gitweb/gitweb.perl\n+++ b/gitweb/gitweb.perl\n@@ -1021,6 +1021,27 @@ sub parse_difftree_raw_line {\n \treturn wantarray ? %res : \\%res;\n }\n \n+# parse line of git-ls-tree output\n+sub parse_ls_tree_line ($;%) {\n+\tmy $line = shift;\n+\tmy %opts = @_;\n+\tmy %res;\n+\n+\t#'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa\tpanic.c'\n+\t$line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\\t(.+)$/;\n+\n+\t$res{'mode'} = $1;\n+\t$res{'type'} = $2;\n+\t$res{'hash'} = $3;\n+\tif ($opts{'-z'}) {\n+\t\t$res{'name'} = $4;\n+\t} else {\n+\t\t$res{'name'} = unquote($4);\n+\t}\n+\n+\treturn wantarray ? %res : \\%res;\n+}\n+\n ## ......................................................................\n ## parse to array of hashes functions\n \n@@ -2498,51 +2519,54 @@ sub git_tree {\n \tprint \"<table cellspacing=\\\"0\\\">\\n\";\n \tmy $alternate = 0;\n \tforeach my $line (@entries) {\n-\t\t#'100644\tblob\t0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa\tpanic.c'\n-\t\t$line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\\t(.+)$/;\n-\t\tmy $t_mode = $1;\n-\t\tmy $t_type = $2;\n-\t\tmy $t_hash = $3;\n-\t\tmy $t_name = validate_input($4);\n+\t\tmy %t = parse_ls_tree_line($line, -z => 1);\n+\n \t\tif ($alternate) {\n \t\t\tprint \"<tr class=\\\"dark\\\">\\n\";\n \t\t} else {\n \t\t\tprint \"<tr class=\\\"light\\\">\\n\";\n \t\t}\n \t\t$alternate ^= 1;\n-\t\tprint \"<td class=\\\"mode\\\">\" . mode_str($t_mode) . \"</td>\\n\";\n-\t\tif ($t_type eq \"blob\") {\n+\n+\t\tprint \"<td class=\\\"mode\\\">\" . mode_str($t{'mode'}) . \"</td>\\n\";\n+\t\tif ($t{'type'} eq \"blob\") {\n \t\t\tprint \"<td class=\\\"list\\\">\" .\n-\t\t\t      $cgi->a({-href => href(action=>\"blob\", hash=>$t_hash, file_name=>\"$base$t_name\", %base_key),\n-\t\t\t              -class => \"list\"}, esc_html($t_name)) .\n+\t\t\t      $cgi->a({-href => href(action=>\"blob\", hash=>$t{'hash'},\n+\t\t\t                             file_name=>\"$base$t{'name'}\", %base_key),\n+\t\t\t              -class => \"list\"}, esc_html($t{'name'})) .\n \t\t\t      \"</td>\\n\" .\n \t\t\t      \"<td class=\\\"link\\\">\" .\n-\t\t\t      $cgi->a({-href => href(action=>\"blob\", hash=>$t_hash, file_name=>\"$base$t_name\", %base_key)},\n+\t\t\t      $cgi->a({-href => href(action=>\"blob\", hash=>$t{'hash'},\n+\t\t\t                             file_name=>\"$base$t{'name'}\", %base_key)},\n \t\t\t              \"blob\");\n \t\t\tif ($have_blame) {\n \t\t\t\tprint \" | \" .\n-\t\t\t\t\t$cgi->a({-href => href(action=>\"blame\", hash=>$t_hash, file_name=>\"$base$t_name\", %base_key)},\n+\t\t\t\t\t$cgi->a({-href => href(action=>\"blame\", hash=>$t{'hash'},\n+\t\t\t\t\t                       file_name=>\"$base$t{'name'}\", %base_key)},\n \t\t\t\t\t        \"blame\");\n \t\t\t}\n \t\t\tprint \" | \" .\n \t\t\t      $cgi->a({-href => href(action=>\"history\", hash_base=>$hash_base,\n-\t\t\t                             hash=>$t_hash, file_name=>\"$base$t_name\")},\n+\t\t\t                             hash=>$t{'hash'}, file_name=>\"$base$t{'name'}\")},\n \t\t\t              \"history\") .\n \t\t\t      \" | \" .\n \t\t\t      $cgi->a({-href => href(action=>\"blob_plain\",\n-\t\t\t                             hash=>$t_hash, file_name=>\"$base$t_name\")},\n+\t\t\t                             hash=>$t{'hash'}, file_name=>\"$base$t{'name'}\")},\n \t\t\t              \"raw\") .\n \t\t\t      \"</td>\\n\";\n-\t\t} elsif ($t_type eq \"tree\") {\n+\t\t} elsif ($t{'type'} eq \"tree\") {\n \t\t\tprint \"<td class=\\\"list\\\">\" .\n-\t\t\t      $cgi->a({-href => href(action=>\"tree\", hash=>$t_hash, file_name=>\"$base$t_name\", %base_key)},\n-\t\t\t              esc_html($t_name)) .\n+\t\t\t      $cgi->a({-href => href(action=>\"tree\", hash=>$t{'hash'},\n+\t\t\t                             file_name=>\"$base$t{'name'}\", %base_key)},\n+\t\t\t              esc_html($t{'name'})) .\n \t\t\t      \"</td>\\n\" .\n \t\t\t      \"<td class=\\\"link\\\">\" .\n-\t\t\t      $cgi->a({-href => href(action=>\"tree\", hash=>$t_hash, file_name=>\"$base$t_name\", %base_key)},\n+\t\t\t      $cgi->a({-href => href(action=>\"tree\", hash=>$t{'hash'},\n+\t\t\t                             file_name=>\"$base$t{'name'}\", %base_key)},\n \t\t\t              \"tree\") .\n \t\t\t      \" | \" .\n-\t\t\t      $cgi->a({-href => href(action=>\"history\", hash_base=>$hash_base, file_name=>\"$base$t_name\")},\n+\t\t\t      $cgi->a({-href => href(action=>\"history\", hash_base=>$hash_base,\n+\t\t\t                             file_name=>\"$base$t{'name'}\")},\n \t\t\t              \"history\") .\n \t\t\t      \"</td>\\n\";\n \t\t}\n-- \n1.4.1.1\n"},{"id":"26118","messageId":"200608310035.07585.jnareb@gmail.com","threadId":"5435","inReplyTo":"200608310030.33512.jnareb@gmail.com","subject":"[PATCH 2/4] gitweb: Separate printing of git_tree row into git_print_tree_entry","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2006-08-30T22:35:07Z","receivedAt":"2006-08-30T22:35:07Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"This is preparation for \"tree blame\" (similar to what ViewVC shows)\noutput, i.e. for each entry give commit where it was changed.\n\nSigned-off-by: Jakub Narebski <jnareb@gmail.com>\n---\nThis could be applied even though it is proof-of-concept series,\nbut I think it would be better to separate git_tree_body rather,\nalthough perhaps something like git_print_tree_entry subroutine\nis a good idea.\n\n gitweb/gitweb.perl |  103 ++++++++++++++++++++++++++++++----------------------\n 1 files changed, 59 insertions(+), 44 deletions(-)\n\ndiff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\nindex 84a13fd..d8b94a1 100755\n--- a/gitweb/gitweb.perl\n+++ b/gitweb/gitweb.perl\n@@ -1462,6 +1462,62 @@ sub git_print_simplified_log {\n \t\t-remove_title => $remove_title);\n }\n \n+# print tree entry (row of git_tree), but without encompassing <tr> element\n+sub git_print_tree_entry {\n+\tmy ($t, $basedir, $hash_base, $have_blame) = @_;\n+\n+\tmy %base_key = ();\n+\t$base_key{hash_base} = $hash_base if defined $hash_base;\n+\n+\tprint \"<td class=\\\"mode\\\">\" . mode_str($t->{'mode'}) . \"</td>\\n\";\n+\tif ($t->{'type'} eq \"blob\") {\n+\t\tprint \"<td class=\\\"list\\\">\" .\n+\t\t      $cgi->a({-href => href(action=>\"blob\", hash=>$t->{'hash'},\n+\t\t                             file_name=>\"$basedir$t->{'name'}\", %base_key),\n+\t\t              -class => \"list\"}, esc_html($t->{'name'})) .\n+\t\t      \"</td>\\n\" .\n+\t\t      \"<td class=\\\"link\\\">\" .\n+\t\t      $cgi->a({-href => href(action=>\"blob\", hash=>$t->{'hash'},\n+\t\t                             file_name=>\"$basedir$t->{'name'}\", %base_key)},\n+\t\t              \"blob\");\n+\t\tif ($have_blame) {\n+\t\t\tprint \" | \" .\n+\t\t\t\t$cgi->a({-href => href(action=>\"blame\", hash=>$t->{'hash'},\n+\t\t\t\t                       file_name=>\"$basedir$t->{'name'}\", %base_key)},\n+\t\t\t\t        \"blame\");\n+\t\t}\n+\t\tif (defined $hash_base) {\n+\t\t\tprint \" | \" .\n+\t\t\t      $cgi->a({-href => href(action=>\"history\", hash_base=>$hash_base,\n+\t\t\t                             hash=>$t->{'hash'}, file_name=>\"$basedir$t->{'name'}\")},\n+\t\t\t              \"history\");\n+\t\t}\n+\t\tprint \" | \" .\n+\t\t      $cgi->a({-href => href(action=>\"blob_plain\",\n+\t\t                             hash=>$t->{'hash'}, file_name=>\"$basedir$t->{'name'}\")},\n+\t\t              \"raw\") .\n+\t\t      \"</td>\\n\";\n+\n+\t} elsif ($t->{'type'} eq \"tree\") {\n+\t\tprint \"<td class=\\\"list\\\">\" .\n+\t\t      $cgi->a({-href => href(action=>\"tree\", hash=>$t->{'hash'},\n+\t\t                             file_name=>\"$basedir$t->{'name'}\", %base_key)},\n+\t\t              esc_html($t->{'name'})) .\n+\t\t      \"</td>\\n\" .\n+\t\t      \"<td class=\\\"link\\\">\" .\n+\t\t      $cgi->a({-href => href(action=>\"tree\", hash=>$t->{'hash'},\n+\t\t                             file_name=>\"$basedir$t->{'name'}\", %base_key)},\n+\t\t              \"tree\");\n+\t\tif (defined $hash_base) {\n+\t\t\tprint \" | \" .\n+\t\t\t      $cgi->a({-href => href(action=>\"history\", hash_base=>$hash_base,\n+\t\t\t                             file_name=>\"$basedir$t->{'name'}\")},\n+\t\t\t              \"history\");\n+\t\t}\n+\t\tprint \"</td>\\n\";\n+\t}\n+}\n+\n ## ......................................................................\n ## functions printing large fragments of HTML\n \n@@ -2499,14 +2555,13 @@ sub git_tree {\n \tmy $refs = git_get_references();\n \tmy $ref = format_ref_marker($refs, $hash_base);\n \tgit_header_html();\n-\tmy %base_key = ();\n \tmy $base = \"\";\n \tmy $have_blame = gitweb_check_feature('blame');\n \tif (defined $hash_base && (my %co = parse_commit($hash_base))) {\n-\t\t$base_key{hash_base} = $hash_base;\n \t\tgit_print_page_nav('tree','', $hash_base);\n \t\tgit_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base);\n \t} else {\n+\t\tundef $hash_base;\n \t\tprint \"<div class=\\\"page_nav\\\">\\n\";\n \t\tprint \"<br/><br/></div>\\n\";\n \t\tprint \"<div class=\\\"title\\\">$hash</div>\\n\";\n@@ -2528,48 +2583,8 @@ sub git_tree {\n \t\t}\n \t\t$alternate ^= 1;\n \n-\t\tprint \"<td class=\\\"mode\\\">\" . mode_str($t{'mode'}) . \"</td>\\n\";\n-\t\tif ($t{'type'} eq \"blob\") {\n-\t\t\tprint \"<td class=\\\"list\\\">\" .\n-\t\t\t      $cgi->a({-href => href(action=>\"blob\", hash=>$t{'hash'},\n-\t\t\t                             file_name=>\"$base$t{'name'}\", %base_key),\n-\t\t\t              -class => \"list\"}, esc_html($t{'name'})) .\n-\t\t\t      \"</td>\\n\" .\n-\t\t\t      \"<td class=\\\"link\\\">\" .\n-\t\t\t      $cgi->a({-href => href(action=>\"blob\", hash=>$t{'hash'},\n-\t\t\t                             file_name=>\"$base$t{'name'}\", %base_key)},\n-\t\t\t              \"blob\");\n-\t\t\tif ($have_blame) {\n-\t\t\t\tprint \" | \" .\n-\t\t\t\t\t$cgi->a({-href => href(action=>\"blame\", hash=>$t{'hash'},\n-\t\t\t\t\t                       file_name=>\"$base$t{'name'}\", %base_key)},\n-\t\t\t\t\t        \"blame\");\n-\t\t\t}\n-\t\t\tprint \" | \" .\n-\t\t\t      $cgi->a({-href => href(action=>\"history\", hash_base=>$hash_base,\n-\t\t\t                             hash=>$t{'hash'}, file_name=>\"$base$t{'name'}\")},\n-\t\t\t              \"history\") .\n-\t\t\t      \" | \" .\n-\t\t\t      $cgi->a({-href => href(action=>\"blob_plain\",\n-\t\t\t                             hash=>$t{'hash'}, file_name=>\"$base$t{'name'}\")},\n-\t\t\t              \"raw\") .\n-\t\t\t      \"</td>\\n\";\n-\t\t} elsif ($t{'type'} eq \"tree\") {\n-\t\t\tprint \"<td class=\\\"list\\\">\" .\n-\t\t\t      $cgi->a({-href => href(action=>\"tree\", hash=>$t{'hash'},\n-\t\t\t                             file_name=>\"$base$t{'name'}\", %base_key)},\n-\t\t\t              esc_html($t{'name'})) .\n-\t\t\t      \"</td>\\n\" .\n-\t\t\t      \"<td class=\\\"link\\\">\" .\n-\t\t\t      $cgi->a({-href => href(action=>\"tree\", hash=>$t{'hash'},\n-\t\t\t                             file_name=>\"$base$t{'name'}\", %base_key)},\n-\t\t\t              \"tree\") .\n-\t\t\t      \" | \" .\n-\t\t\t      $cgi->a({-href => href(action=>\"history\", hash_base=>$hash_base,\n-\t\t\t                             file_name=>\"$base$t{'name'}\")},\n-\t\t\t              \"history\") .\n-\t\t\t      \"</td>\\n\";\n-\t\t}\n+\t\tgit_print_tree_entry(\\%t, $base, $hash_base, $have_blame);\n+\n \t\tprint \"</tr>\\n\";\n \t}\n \tprint \"</table>\\n\" .\n-- \n1.4.1.1\n"},{"id":"26120","messageId":"200608310036.04302.jnareb@gmail.com","threadId":"5435","inReplyTo":"200608310030.33512.jnareb@gmail.com","subject":"[PATCH 3/4] gitweb: Extend parse_difftree_raw_line to save commit info","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2006-08-30T22:36:04Z","receivedAt":"2006-08-30T22:36:04Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Extend parse_difftree_raw_line to save commit info from when\ngit-diff-tree is given only one <tree-ish>, for example when fed\nfrom git-rev-list using --stdin option.\n\ngit-diff-tree outputs a line with the commit ID when applicable.\n\nSigned-off-by: Jakub Narebski <jnareb@gmail.com>\n---\nThis can be applied regardless that it is proof-of-concept series.\nNeed checking if it doesn't break anything.\n\n gitweb/gitweb.perl |    6 +++---\n 1 files changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\nindex d8b94a1..b193bc6 100755\n--- a/gitweb/gitweb.perl\n+++ b/gitweb/gitweb.perl\n@@ -1014,9 +1014,9 @@ sub parse_difftree_raw_line {\n \t\t}\n \t}\n \t# 'c512b523472485aef4fff9e57b229d9d243c967f'\n-\t#elsif ($line =~ m/^([0-9a-fA-F]{40})$/) {\n-\t#\t$res{'commit'} = $1;\n-\t#}\n+\telsif ($line =~ m/^([0-9a-fA-F]{40})$/) {\n+\t\t$res{'commit'} = $1;\n+\t}\n \n \treturn wantarray ? %res : \\%res;\n }\n-- \n1.4.1.1\n"},{"id":"26119","messageId":"200608310036.54710.jnareb@gmail.com","threadId":"5435","inReplyTo":"200608310030.33512.jnareb@gmail.com","subject":"[PATCH 4/4] gitweb: Add \"tree_blame\" view (WIP)","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2006-08-30T22:36:54Z","receivedAt":"2006-08-30T22:36:54Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Adds git_tree_blame subroutine, for the \"tree_blame\" view, similar to\nthe ViewVC output.  It means having the commit, date and author of\nlast change to the file (blob) or directory (tree) in given tree in\naddition to the default \"tree\" view info.\n\nThe git_tree_blame subroutine contains 3 implementations: one using\ngit-diff-tree to detect when file was changed (works only on blobs,\nand not on trees/directories), one comparing output of git-ls-tree to\ncheck what changed between revisions, and one (default used) looking\nat two first commits in the blob or tree history.\n\nWhile at it, add 'year' field to the parse_date output; add \"blame\"\nlink to the list of links for tree entries in directory listing in\ngit_print_tree_entry; add support for 'order' parameter to href().\n\nSigned-off-by: Jakub Narebski <jnareb@gmail.com>\n---\n gitweb/gitweb.perl |  344 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 344 insertions(+), 0 deletions(-)\n\ndiff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\nindex b193bc6..130609b 100755\n--- a/gitweb/gitweb.perl\n+++ b/gitweb/gitweb.perl\n@@ -264,6 +264,7 @@ my %actions = (\n \t\"tag\" => \\&git_tag,\n \t\"tags\" => \\&git_tags,\n \t\"tree\" => \\&git_tree,\n+\t\"tree_blame\" => \\&git_tree_blame,\n \t\"snapshot\" => \\&git_snapshot,\n \t# those below don't need $project\n \t\"opml\" => \\&git_opml,\n@@ -298,6 +299,7 @@ sub href(%) {\n \t\thash_parent_base => \"hpb\",\n \t\tpage => \"pg\",\n \t\tsearchtext => \"s\",\n+\t\torder => \"o\",\n \t);\n \tmy %mapping = @mapping;\n \n@@ -804,6 +806,7 @@ sub parse_date {\n \t$date{'hour'} = $hour;\n \t$date{'minute'} = $min;\n \t$date{'mday'} = $mday;\n+\t$date{'year'} = 1900+$year;\n \t$date{'day'} = $days[$wday];\n \t$date{'month'} = $months[$mon];\n \t$date{'rfc2822'} = sprintf \"%s, %d %s %4d %02d:%02d:%02d +0000\",\n@@ -1508,6 +1511,12 @@ sub git_print_tree_entry {\n \t\t      $cgi->a({-href => href(action=>\"tree\", hash=>$t->{'hash'},\n \t\t                             file_name=>\"$basedir$t->{'name'}\", %base_key)},\n \t\t              \"tree\");\n+\t\tif ($have_blame) {\n+\t\t\tprint \" | \" .\n+\t\t\t\t$cgi->a({-href => href(action=>\"tree_blame\", hash=>$t->{'hash'},\n+\t\t\t\t                       file_name=>\"$basedir$t->{'name'}\", %base_key)},\n+\t\t\t\t        \"blame\");\n+\t\t}\n \t\tif (defined $hash_base) {\n \t\t\tprint \" | \" .\n \t\t\t      $cgi->a({-href => href(action=>\"history\", hash_base=>$hash_base,\n@@ -2592,6 +2601,341 @@ sub git_tree {\n \tgit_footer_html();\n }\n \n+sub git_tree_blame {\n+\tmy $version = shift || 'rev-list';\n+\n+\t# check permission\n+\tmy $have_blame = gitweb_check_feature('blame');\n+\tif (!$have_blame) {\n+\t\tdie_error('403 Permission denied', \"Permission denied\");\n+\t}\n+\t# get sort order parameter\n+\tmy $order = $cgi->param('o');\n+\tif (defined $order && $order !~ m/[+-]?age|[+-]?author|[+-]?mode|[+-]?name/) {\n+\t\tdie_error(undef, \"Unknown order parameter\");\n+\t}\n+\t# check hash\n+\tif (!defined $hash) {\n+\t\t$hash = git_get_head_hash($project);\n+\t\tif (defined $file_name) {\n+\t\t\tmy $base = $hash_base || $hash;\n+\t\t\t$hash = git_get_hash_by_path($base, $file_name, \"tree\");\n+\t\t}\n+\t}\n+\t# check hash_base\n+\tif (!defined $hash_base) {\n+\t\t$hash_base = $hash;\n+\t}\n+\t# check if we have starting (base) commit\n+\tmy %co = parse_commit($hash_base);\n+\tif (!%co) {\n+\t\tdie_error(undef, \"Base commit not defined\");\n+\t}\n+\t# set basedir (dirname for entries)\n+\tmy $basedir;\n+\tif (defined $file_name) {\n+\t\t$basedir = \"$file_name/\";\n+\t}\n+\n+\t# read tree\n+\t$/ = \"\\0\";\n+\topen my $fd, \"-|\", $GIT, \"ls-tree\", '-z', $hash\n+\t\tor die_error(undef, \"Open git-ls-tree failed\");\n+\tmy @entries = map { chomp; $_ } <$fd>;\n+\tclose $fd\n+\t\tor die_error(undef, \"Reading git-ls-tree failed\");\n+\t$/ = \"\\n\";\n+\n+\t# parse tree\n+\t@entries = map {\n+\t\tscalar parse_ls_tree_line($_, -z => 1);\n+\t} @entries;\n+\n+\t## !!! two versions: one using git-diff-tree, without directories,\n+\t## !!! second using git-ls-files, with directories, slower.\n+\n+\t# blame\n+\tmy $tofind = scalar @entries;\n+\tmy %idx;\n+\tfor (my $i = 0; $i < @entries; $i++) {\n+\t\t$idx{\"$basedir$entries[$i]{'name'}\"} = $i;\n+\t}\n+\tmy $commit = $hash_base;\n+\n+\tif ($version eq 'diff-tree') {\n+\t\topen $fd, \"-|\", \"$GIT rev-list $commit -- $basedir/\" .\n+\t\t\t\"| $GIT diff-tree -r -- $basedir/\"\n+\t\t\tor die_error(undef, \"Open git-rev-list or git-diff-tree failed\");\n+\n+\t\twhile ($tofind > 0 && (my $line = <$fd>)) {\n+\t\t\tchomp $line;\n+\t\t\tmy %diffinfo = parse_difftree_raw_line($line);\n+\t\t\tif ($diffinfo{'commit'}) {\n+\t\t\t\t$commit = $diffinfo{'commit'};\n+\t\t\t} else {\n+\t\t\t\tmy $name = $diffinfo{'to_name'} || $diffinfo{'name'};\n+\t\t\t\tmy $fullname = $basedir . $name;\n+\t\t\t\tif (!exists $entries[$idx{$fullname}]{'commit'}) {\n+\t\t\t\t\t$entries[$idx{$fullname}]{'commit'} = $commit;\n+\t\t\t\t\t$entries[$idx{$fullname}]{'added'} =\n+\t\t\t\t\t\t($diffinfo{'status'} eq 'A');\n+\t\t\t\t\t$tofind--;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\n+\t} elsif ($version eq 'ls-tree') {\n+\n+\tTOFIND:\n+\t\twhile ($tofind > 0) {\n+\t\t\t# workaround the lack of git-ls-tree --stdin\n+\t\t\topen $fd, \"-|\", $GIT, \"rev-list\", \"--max-count=100\", \"--remove-empty\",\n+\t\t\t\t\"$commit^\", \"--\", $basedir\n+\t\t\t\t\tor die_error(undef, \"Open git-rev-list failed\");\n+\t\t\tmy @revlist = map { chomp; $_ } <$fd>;\n+\t\t\tclose $fd\n+\t\t\t\tor die_error(undef, \"Reading git-rev-list failed\");\n+\n+\t\t\t# exit if no revisions found\n+\t\t\tlast unless @revlist;\n+\n+\t\t\t$/ = \"\\0\";\n+\t\t\tmy $prev_commit = $commit;\n+\t\tCOMMIT:\n+\t\t\tforeach $commit (@revlist) {\n+\t\t\t\t# read tree\n+\t\t\t\topen my $fd, \"-|\", $GIT, \"ls-tree\", '-z', $commit, \"--\", $basedir\n+\t\t\t\t\tor die_error(undef, \"Open git-ls-tree failed\");\n+\t\t\t\tmy @ls_tree = map {\tchomp; $_ } <$fd>;\n+\t\t\t\tclose $fd\n+\t\t\t\t\tor die_error(undef, \"Reading git-ls-tree failed\");\n+\t\t\t\t# parse tree\n+\t\t\t\t@ls_tree = map {\n+\t\t\t\t\tscalar parse_ls_tree_line($_, -z => 1);\n+\t\t\t\t} @ls_tree;\n+\n+\t\t\t\t# find unchanged\n+\t\t\t\tmy %unchanged;\n+\t\t\t\tmy %changed;\n+\t\t\t\tforeach my $entry (@ls_tree) {\n+\t\t\t\t\tif (exists $idx{\"$entry->{'name'}\"} &&\n+\t\t\t\t\t    $entry->{'hash'} eq $entries[$idx{\"$entry->{'name'}\"}]{'hash'}) {\n+\t\t\t\t\t\t$unchanged{\"$entry->{'name'}\"} = 1;\n+\t\t\t\t\t}\n+\t\t\t\t\tif (exists $idx{\"$entry->{'name'}\"} &&\n+\t\t\t\t\t    $entry->{'hash'} ne $entries[$idx{\"$entry->{'name'}\"}]{'hash'}) {\n+\t\t\t\t\t\t$changed{\"$entry->{'name'}\"} = 1;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\n+\t\t\t\t# find those with changed id, and those that vanished\n+\t\t\t\tforeach my $entry (@entries) {\n+\t\t\t\t\tmy $fullname = \"$basedir$entry->{'name'}\";\n+\t\t\t\t\tif (!$unchanged{$fullname} &&\n+\t\t\t\t\t    !exists $entry->{'commit'}) {\n+\t\t\t\t\t\t$entry->{'commit'} = $prev_commit;\n+\t\t\t\t\t\t$entry->{'added'} =\n+\t\t\t\t\t\t\t!$changed{$fullname};\n+\t\t\t\t\t\t$tofind--;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t\tlast TOFIND if $tofind <= 0;\n+\n+\t\t\t} continue {\n+\t\t\t\t$prev_commit = $commit;\n+\t\t\t}\n+\t\t\t$/ = \"\\n\";\n+\n+\t\t\t$commit = $revlist[-1];\n+\t\t}\n+\n+\t\t# 'import' case: we attribute 'not found yet' to the last commit\n+\t\tif ($tofind > 0) {\n+\t\t\tforeach my $entry (@entries) {\n+\t\t\t\tif (!exists $entry->{'commit'}) {\n+\t\t\t\t\t$entry->{'commit'} = $commit;\n+\t\t\t\t\t$entry->{'added'} = 1;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\n+\t} elsif ($version eq 'rev-list') {\n+\n+\t\tforeach my $entry (@entries) {\n+\t\t\tmy $fullname = \"$basedir$entry->{'name'}\";\n+\n+\t\t\topen $fd, \"-|\", $GIT, \"rev-list\", \"--max-count=2\",\n+\t\t\t\t$hash_base, \"--\", $fullname\n+\t\t\t\tor die_error(undef, \"Open git-rev-list failed\");\n+\t\t\tmy @revlist = map { chomp; $_ } <$fd>;\n+\t\t\tclose $fd\n+\t\t\t\tor die_error(undef, \"Reading git-rev-list failed\");\n+\n+\t\t\tif ($#revlist >= 0) {\n+\t\t\t\t$entry->{'commit'} = $revlist[0];\n+\t\t\t\t$entry->{'added'} = 1\n+\t\t\t\t\tif (@revlist == 1);\n+\t\t\t}\n+\t\t}\n+\n+\t} else {\n+\t\tdie_error(undef, \"Unknown tree_blame version parameter\");\n+\t}\n+\n+\t# blame postprocessing\n+\tforeach my $entry (@entries) {\n+\t\tif (my %entry_co = parse_commit($entry->{'commit'})) {\n+\t\t\t$entry->{'title'}  = $entry_co{'title'};\n+\t\t\t$entry->{'epoch'}  = $entry_co{'author_epoch'};\n+\t\t\t$entry->{'author'} = $entry_co{'author_name'};\n+\t\t\tif ($entry_co{'author'} =~ m/<([^@>]+)\\@/) {\n+\t\t\t\t$entry->{'author'} = $1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\n+\t### ----------------------------------------------\n+\t# page header\n+\tmy $refs = git_get_references();\n+\tmy $ref = format_ref_marker($refs, $hash_base);\n+\tgit_header_html();\n+\tgit_print_page_nav('tree_blame','', $hash_base, $hash, $hash_base);\n+\tgit_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base);\n+\tgit_print_page_path($file_name, 'tree', $hash_base);\n+\tprint \"<div class=\\\"page_body\\\">\\n\";\n+\tprint \"<table class=\\\"tree blame\\\" cellspacing=\\\"0\\\">\\n\";\n+\n+\t# sort and print table header\n+\tprint \"<thead>\\n\" .\n+\t      \"<tr>\\n\";\n+\tprint \"<th>Commit</th>\\n\";\n+\tif ($order =~ m/^([-+]?)age$/) {\n+\t\tmy $sign = \"$1\" ? \"${1}1\" : -1;\n+\t\t@entries = sort {\n+\t\t\tif (exists $a->{'commit'} && exists $b->{'commit'}) {\n+\t\t\t\t$sign*($a->{'epoch'} <=> $b->{'epoch'});\n+\t\t\t} else {\n+\t\t\t\t0;\n+\t\t\t}\n+\t\t} @entries;\n+\t\tprint \"<th>Date</th>\\n\";\n+\t} else {\n+\t\tprint \"<th>\" .\n+\t\t      $cgi->a({-href => href(action=>$action, hash=>$hash,\n+\t\t                             hash_base=>$hash_base, file_name=>$file_name,\n+\t\t                             order=>'age'),\n+\t\t              -class => \"header\"}, \"Date\") .\n+\t\t      \"</th>\\n\";\n+\t}\n+\tif ($order =~ m/^([-+]?)author$/) {\n+\t\tmy $sign = \"${1}1\";\n+\t\t@entries = sort {\n+\t\t\tif (exists $a->{'commit'} && exists $b->{'commit'}) {\n+\t\t\t\t$sign*($a->{'author'} cmp $b->{'author'});\n+\t\t\t} else {\n+\t\t\t\t0;\n+\t\t\t}\n+\t\t} @entries;\n+\t\tprint \"<th>Author</th>\\n\";\n+\t} else {\n+\t\tprint \"<th>\" .\n+\t\t      $cgi->a({-href => href(action=>$action, hash=>$hash,\n+\t\t                             hash_base=>$hash_base, file_name=>$file_name,\n+\t\t                             order=>'author'),\n+\t\t              -class => \"header\"}, \"Author\") .\n+\t\t      \"</th>\\n\";\n+\t}\n+\tif ($order =~ m/^([-+]?)mode$/) {\n+\t\tmy $sign = \"${1}1\";\n+\t\t@entries = sort {\n+\t\t\tif (exists $a->{'commit'} && exists $b->{'commit'}) {\n+\t\t\t\t$sign*($a->{'mode'} <=> $b->{'mode'});\n+\t\t\t} else {\n+\t\t\t\t0;\n+\t\t\t}\n+\t\t} @entries;\n+\t\tprint \"<th>Mode</th>\\n\";\n+\t} else {\n+\t\tprint \"<th>\" .\n+\t\t      $cgi->a({-href => href(action=>$action, hash=>$hash,\n+\t\t                             hash_base=>$hash_base, file_name=>$file_name,\n+\t\t                             order=>'mode'),\n+\t\t              -class => \"header\"}, \"Mode\") .\n+\t\t      \"</th>\\n\";\n+\t}\n+\tif ($order =~ m/^([-+]?)name$/) {\n+\t\tmy $sign = \"${1}1\";\n+\t\t@entries = sort {\n+\t\t\tif (exists $a->{'commit'} && exists $b->{'commit'}) {\n+\t\t\t\t$sign*($a->{'name'} cmp $b->{'name'});\n+\t\t\t} else {\n+\t\t\t\t0;\n+\t\t\t}\n+\t\t} @entries;\n+\t\tprint \"<th>Filename</th>\\n\";\n+\t} else {\n+\t\tprint \"<th>\" .\n+\t\t      $cgi->a({-href => href(action=>$action, hash=>$hash,\n+\t\t                             hash_base=>$hash_base, file_name=>$file_name,\n+\t\t                             order=>'name'),\n+\t\t              -class => \"header\"}, \"Filename\") .\n+\t\t      \"</th>\\n\";\n+\t}\n+\tprint <<HTML;\n+<th>Link</th>\n+</tr>\n+</thead>\n+<tbody>\n+HTML\n+\n+\t# print tree\n+\tmy $alternate = 0;\n+\tforeach my $entry (@entries) {\n+\t\tif ($alternate) {\n+\t\t\tprint \"<tr class=\\\"dark\\\">\\n\";\n+\t\t} else {\n+\t\t\tprint \"<tr class=\\\"light\\\">\\n\";\n+\t\t}\n+\t\t$alternate ^= 1;\n+\n+\t\tif ($entry->{'commit'}) {\n+\t\t\tprint $cgi->start_td({-class => \"sha1\", -title => $entry->{'title'}}) .\n+\t\t\t      ($entry->{'added'} ? \"<strike>\" : \"\") .\n+\t\t\t      $cgi->a({-href => href(action=>\"commit\", hash=>$entry->{'commit'},\n+\t\t\t                             file_name=>$entry->{'name'})},\n+\t\t\t              esc_html(substr($entry->{'commit'}, 0, 8))) .\n+\t\t\t      ($entry->{'added'} ? \"</strike>\" : \"\") .\n+\t\t\t      \"</td>\";\n+\t\t\tmy %ad = parse_date($entry->{'epoch'});\n+\t\t\tmy (undef,undef,undef,undef,undef,$current_year) = gmtime();\n+\t\t\tmy $date_str;\n+\t\t\tif ($ad{'year'} == 1900+$current_year) {\n+\t\t\t\t$date_str = sprintf \"%3s % 2u %02d:%02d\",\n+\t\t\t\t\t$ad{'month'}, $ad{'mday'}, $ad{'hour'}, $ad{'minute'};\n+\t\t\t} else {\n+\t\t\t\t$date_str = sprintf \"%3s % 2u % 5d\",\n+\t\t\t\t\t$ad{'month'}, $ad{'mday'}, $ad{'year'};\n+\t\t\t}\n+\t\t\t$date_str =~ s/ /&nbsp;/g;\n+\t\t\tprint \"<td>$date_str</td>\\n\";\n+\t\t\tprint \"<td>$entry->{'author'}</td>\\n\";\n+\t\t} else {\n+\t\t\tprint \"<td></td>\\n\" .\n+\t\t\t      \"<td></td>\\n\" .\n+\t\t\t      \"<td></td>\\n\";\n+\t\t}\n+\t\tgit_print_tree_entry($entry, $basedir, $hash_base,\n+\t\t                     $have_blame && defined $hash_base);\n+\n+\t\tprint \"</tr>\\n\";\n+\t}\n+\tprint \"</tbody>\\n\" .\n+\t      \"</table>\\n\" .\n+\t      \"</div>\";\n+\tgit_footer_html();\n+}\n+\n sub git_snapshot {\n \n \tmy ($ctype, $suffix, $command) = gitweb_check_feature('snapshot');\n-- \n1.4.1.1\n"}]}