{"thread":{"id":"20224","subject":"[PATCHv2 00/10] gitweb: 'blame' view improvements","startedAt":"2009-07-24T22:44:00Z","lastAt":"2009-07-27T19:06:56Z","messageCount":21,"participants":["Jakub Narebski","Junio C Hamano","Martin Koegler"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"118667","messageId":"1248475450-5668-1-git-send-email-jnareb@gmail.com","threadId":"20224","inReplyTo":null,"subject":"[PATCHv2 00/10] gitweb: 'blame' view improvements","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2009-07-24T22:44:00Z","receivedAt":"2009-07-24T22:44:00Z","isPatch":false,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"This is second version of my improvements to gitweb's 'blame' view,\n  Subject: [PATCH 0/3] gitweb: 'blame' view improvements\n  Message-Id: <200907102354.43232.jnareb@gmail.com>\n  http://article.gmane.org/gmane.comp.version-control.git/123085\n\nincluding some further improvements, and this time including\npreparation and AJAX-y 'blame_incremental' view in series proper.\nIt also finally creates 'blame_incremental' links (last patch in\nseries).\n\nThe changes are also available in the git repository at:\n\n  git://repo.or.cz/git/jnareb-git.git gitweb/web\n\nJakub Narebski (10):\n  gitweb: Make .error style generic\n  gitweb: Mark boundary commits in 'blame' view\n  gitweb: Use \"previous\" header of git-blame -p in 'blame' view\n  gitweb: Mark commits with no \"previous\" in 'blame' view\n  gitweb: Add author initials in 'blame' view, a la \"git gui blame\"\n  gitweb: Use light/dark for class names also in 'blame' view\n  gitweb: Add -partial_query option to href() subroutine\n  gitweb: Add optional \"time to generate page\" info in footer\n  gitweb: Incremental blame (proof of concept)\n  gitweb: Create links leading to 'blame_incremental' using JavaScript\n\n Makefile           |    6 +-\n git-instaweb.sh    |    7 +\n gitweb/blame.js    |  634 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n gitweb/gitweb.css  |   43 +++-\n gitweb/gitweb.perl |  332 ++++++++++++++++++++-------\n 5 files changed, 930 insertions(+), 92 deletions(-)\n create mode 100644 gitweb/blame.js\n\n\nJakub Narebski (10):\n  gitweb: Make .error style generic\n  gitweb: Mark boundary commits in 'blame' view\n  gitweb: Use \"previous\" header of git-blame -p in 'blame' view\n  gitweb: Mark commits with no \"previous\" in 'blame' view\n  gitweb: Add author initials in 'blame' view, a la \"git gui blame\"\n  gitweb: Use light/dark for class names also in 'blame' view\n  gitweb: Add -partial_query option to href() subroutine\n  gitweb: Add optional \"time to generate page\" info in footer\n  gitweb: Incremental blame (proof of concept)\n  gitweb: Create links leading to 'blame_incremental' using JavaScript\n\n Makefile           |    6 +-\n git-instaweb.sh    |    7 +\n gitweb/blame.js    |  634 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n gitweb/gitweb.css  |   43 +++-\n gitweb/gitweb.perl |  332 ++++++++++++++++++++-------\n 5 files changed, 930 insertions(+), 92 deletions(-)\n create mode 100644 gitweb/blame.js\n"},{"id":"118668","messageId":"1248475450-5668-2-git-send-email-jnareb@gmail.com","threadId":"20224","inReplyTo":"1248475450-5668-1-git-send-email-jnareb@gmail.com","subject":"[PATCH 01/10] gitweb: Make .error style generic","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2009-07-24T22:44:01Z","receivedAt":"2009-07-24T22:44:01Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Style for td.error was introduced in 1f1ab5f (gitweb: style done with\nstylesheet, 2006-06-20) to replace inline style for errors in old\nmulti-column \"git annotate\" based 'blame' view.  This view was then\nsince removed (replaced by \"git-blame\" based 'blame' view, with fewer\ncolums), making this style unused.\n\nMake this style more generic by replacing td.error with .error to make\nit apply to any element.  It will be used in 'blame_incremental' view\nto show error messages.\n\nSigned-off-by: Jakub Narebski <jnareb@gmail.com>\n---\nIt is, as you can see, very simple change.  It makes it easier to\nreuse currently \"dead\" style; unused after removing old git_blame by\nRafael Garcia-Suarez in 3a5b919 (gitweb: remove git_blame and rename\ngit_blame2 to git_blame, 2008-06-06).\n\n gitweb/gitweb.css |    2 +-\n 1 files changed, 1 insertions(+), 1 deletions(-)\n\ndiff --git a/gitweb/gitweb.css b/gitweb/gitweb.css\nindex d05bc37..70b7c2f 100644\n--- a/gitweb/gitweb.css\n+++ b/gitweb/gitweb.css\n@@ -262,7 +262,7 @@ td.sha1 {\n \tfont-family: monospace;\n }\n \n-td.error {\n+.error {\n \tcolor: red;\n \tbackground-color: yellow;\n }\n-- \n1.6.3.3\n"},{"id":"118669","messageId":"1248475450-5668-3-git-send-email-jnareb@gmail.com","threadId":"20224","inReplyTo":"1248475450-5668-1-git-send-email-jnareb@gmail.com","subject":"[PATCH 02/10] gitweb: Mark boundary commits in 'blame' view","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2009-07-24T22:44:02Z","receivedAt":"2009-07-24T22:44:02Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Use \"boundary\" class to mark boundary commits, which currently results\nin using bold weight font for SHA-1 of a commit (to be more exact for\nall text in the first cell in row, that contains SHA-1 of a commit).\n\nDetecting boundary commits is done by watching for \"boundary\" header\nin \"git blame -p\" output.  Because this header doesn't carry\nadditional data the regular expression for blame header fields\nhad to be slightly adjusted.\n\nWith current gitweb API only root (parentless) commits can be boundary\ncommits.\n\nSigned-off-by: Jakub Narebski <jnareb@gmail.com>\n---\nFormatting (styling) of boundary commits is currently very minimal.\n\nThis is just resend of previous version of patch.\n\n gitweb/gitweb.css  |    4 ++++\n gitweb/gitweb.perl |    6 ++++--\n 2 files changed, 8 insertions(+), 2 deletions(-)\n\ndiff --git a/gitweb/gitweb.css b/gitweb/gitweb.css\nindex 70b7c2f..f47709b 100644\n--- a/gitweb/gitweb.css\n+++ b/gitweb/gitweb.css\n@@ -242,6 +242,10 @@ tr.dark:hover {\n \tbackground-color: #edece6;\n }\n \n+tr.boundary td.sha1 {\n+\tfont-weight: bold;\n+}\n+\n td {\n \tpadding: 2px 5px;\n \tfont-size: 100%;\ndiff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\nindex 7fbd5ff..3078b92 100755\n--- a/gitweb/gitweb.perl\n+++ b/gitweb/gitweb.perl\n@@ -4826,7 +4826,7 @@ HTML\n \t\twhile ($data = <$fd>) {\n \t\t\tchomp $data;\n \t\t\tlast if ($data =~ s/^\\t//); # contents of line\n-\t\t\tif ($data =~ /^(\\S+) (.*)$/) {\n+\t\t\tif ($data =~ /^(\\S+)(?: (.*))?$/) {\n \t\t\t\t$meta->{$1} = $2;\n \t\t\t}\n \t\t}\n@@ -4838,7 +4838,9 @@ HTML\n \t\tif ($group_size) {\n \t\t\t$current_color = ($current_color + 1) % $num_colors;\n \t\t}\n-\t\tprint \"<tr id=\\\"l$lineno\\\" class=\\\"$rev_color[$current_color]\\\">\\n\";\n+\t\tmy $tr_class = $rev_color[$current_color];\n+\t\t$tr_class .= ' boundary' if (exists $meta->{'boundary'});\n+\t\tprint \"<tr id=\\\"l$lineno\\\" class=\\\"$tr_class\\\">\\n\";\n \t\tif ($group_size) {\n \t\t\tprint \"<td class=\\\"sha1\\\"\";\n \t\t\tprint \" title=\\\"\". esc_html($author) . \", $date\\\"\";\n-- \n1.6.3.3\n"},{"id":"118671","messageId":"1248475450-5668-4-git-send-email-jnareb@gmail.com","threadId":"20224","inReplyTo":"1248475450-5668-1-git-send-email-jnareb@gmail.com","subject":"[PATCHv2 03/10] gitweb: Use \"previous\" header of git-blame -p in 'blame' view","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2009-07-24T22:44:03Z","receivedAt":"2009-07-24T22:44:03Z","isPatch":false,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Luben Tuikov changed 'lineno' link (line number link) from pointing to\n'blame' view at given line at blamed commit, to the one at parent of\nblamed commit in\n  244a70e (Blame \"linenr\" link jumps to previous state at\n           \"orig_lineno\", 2007-01-04).\nThis made it possible to do data mining using 'blame' view, by going\nthrough history of a line using mentioned line number link.\n\nOriginal implementation called \"git rev-parse <commit>^\" to find SHA-1\nof a parent of a given commit once per each blamed line.  In\n  39c19ce (gitweb: cache $parent_commit info in git_blame(),\n           2008-12-11)\nthis was improved so rev-parse was called once per each unique commit\nin git-blame output.  Alternate solution would be to relax validation\nfor 'hb' parameter by allowing extended SHA-1 syntax of the form\n<rev>^ (perhaps redirecting to gitweb URL with <rev>^ resolved, in\npractice moving call to rev-parse to 'the other side of link').\n\nThis solution had a bug that it didn't work for boundary commits.\nBoundary commits don't have parents, so \"git rev-parse <commit>^\"\nreturned literal \"<commit>^\" (which didn't exists).  Gitweb didn't\ndetect this situation and passed this result literally as 'hb'\nparameter in 'linenr' link.  Following such link currently gives\n  400 - Invalid hash base parameter\nerror; 'hb' parameter is restricted via validate_refname to correct\nrefnames and doesn't allow for extended SHA-1 syntax.  This bug could\nhave been fixed alternatively by checking if commit is boundary commit,\nor check if rev-parse result is unchanged (still ends in '^' prefix).\n\nThe solution employing rev-parse to find parent of commit had inherent\nproblem if blamed commit renamed file; then name of file would be\ndifferent in its parent.  Solving this outside git-blame would be\ndifficult and costly (at least cost of additional fork for extra git\ncommand).\n\n\nCurrently gitweb uses information in \"previous\" header, which was\nintroduced by Junio C Hamano in\n  96e1170 (blame: show \"previous\" information in\n           --porcelain/--incremental format, 2008-06-04)\nThis (currently undocumented) header has the following format:\n  \"previous <sha1 of parent commit> <filename at parent>\"\nUsing \"previous\" header solves both problem of performance and the\nproblem that blamed commit could have renaming blamed file.\n\nBecause \"previous\" header can be repeated for the same commit when\nblamed commit is merge (has more than one parent), and we are\ninterested usually in _first_ parent, currently we store only first\nvalue if blame header repeats.  Using first parent (first \"previous\"\nline) was what gitweb did before; without this change gitweb would use\nlast parent instead.\n\nIf there is no previous commit 'linenr' link points to blamed commit\nand blamed filename, making it work correctly for boundary commits.\n\nAcked-by: Luben Tuikov <ltuikov@yahoo.com>\nSigned-off-by: Jakub Narebski <jnareb@gmail.com>\n---\nNote that it does not change \"sha1\" link from 'commit' to 'commitdiff'\nview, as requested by Junio C Hamano.  This is left for later,\nseparate commit.\n\nNew in v2:\n * we don't use totally unnecessary newly introduced unquote_maybe(),\n   as unquote == unquote_maybe (unquotes only when necessary).\n * Added ACK from Luben Tuikov (author of 'data mining' behaviour)\n   http://article.gmane.org/gmane.comp.version-control.git/123154\n\nBTW. I think as a side effect this patch makes code a bit cleaner.\n\n gitweb/gitweb.perl |   27 ++++++++++++++-------------\n 1 files changed, 14 insertions(+), 13 deletions(-)\n\ndiff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\nindex 3078b92..b8a121b 100755\n--- a/gitweb/gitweb.perl\n+++ b/gitweb/gitweb.perl\n@@ -4827,7 +4827,7 @@ HTML\n \t\t\tchomp $data;\n \t\t\tlast if ($data =~ s/^\\t//); # contents of line\n \t\t\tif ($data =~ /^(\\S+)(?: (.*))?$/) {\n-\t\t\t\t$meta->{$1} = $2;\n+\t\t\t\t$meta->{$1} = $2 unless exists $meta->{$1};\n \t\t\t}\n \t\t}\n \t\tmy $short_rev = substr($full_rev, 0, 8);\n@@ -4852,20 +4852,21 @@ HTML\n \t\t\t              esc_html($short_rev));\n \t\t\tprint \"</td>\\n\";\n \t\t}\n-\t\tmy $parent_commit;\n-\t\tif (!exists $meta->{'parent'}) {\n-\t\t\topen (my $dd, \"-|\", git_cmd(), \"rev-parse\", \"$full_rev^\")\n-\t\t\t\tor die_error(500, \"Open git-rev-parse failed\");\n-\t\t\t$parent_commit = <$dd>;\n-\t\t\tclose $dd;\n-\t\t\tchomp($parent_commit);\n-\t\t\t$meta->{'parent'} = $parent_commit;\n-\t\t} else {\n-\t\t\t$parent_commit = $meta->{'parent'};\n-\t\t}\n+\t\t# 'previous' <sha1 of parent commit> <filename at commit>\n+\t\tif (exists $meta->{'previous'} &&\n+\t\t    $meta->{'previous'} =~ /^([a-fA-F0-9]{40}) (.*)$/) {\n+\t\t\t$meta->{'parent'} = $1;\n+\t\t\t$meta->{'file_parent'} = unquote($2);\n+\t\t}\n+\t\tmy $linenr_commit =\n+\t\t\texists($meta->{'parent'}) ?\n+\t\t\t$meta->{'parent'} : $full_rev;\n+\t\tmy $linenr_filename =\n+\t\t\texists($meta->{'file_parent'}) ?\n+\t\t\t$meta->{'file_parent'} : unquote($meta->{'filename'});\n \t\tmy $blamed = href(action => 'blame',\n-\t\t                  file_name => $meta->{'filename'},\n-\t\t                  hash_base => $parent_commit);\n+\t\t                  file_name => $linenr_filename,\n+\t\t                  hash_base => $linenr_commit);\n \t\tprint \"<td class=\\\"linenr\\\">\";\n \t\tprint $cgi->a({ -href => \"$blamed#l$orig_lineno\",\n \t\t                -class => \"linenr\" },\n-- \n1.6.3.3\n"},{"id":"118672","messageId":"1248475450-5668-5-git-send-email-jnareb@gmail.com","threadId":"20224","inReplyTo":"1248475450-5668-1-git-send-email-jnareb@gmail.com","subject":"[PATCH 04/10] gitweb: Mark commits with no \"previous\" in 'blame' view","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2009-07-24T22:44:04Z","receivedAt":"2009-07-24T22:44:04Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Use \"no-previous\" class to mark blamed commits which do not have\n\"previous\" header.  Those are commits in which blamed file was created\n(added); this includes boundary commits.  This means that 'linenr'\nlink leads to blamed commit, not (one of) parent of blamed commit.\nTherefore currently line number for such commit uses bold weight font\nto denote this situation; the effect is subtle.\n\nUse \"multiple-previous\" class in the opposite situation, where blamed\ncommit has multiple \"previous\" headers (is an evil merge).  Currently\nthis class is not used for styling.  In this situation 'linenr' link\nleads to first of \"previous\" commits (first parent).\n\nSigned-off-by: Jakub Narebski <jnareb@gmail.com>\n---\nThis is new commit, which didn't appear in v1 of this series.\n\nIt does what marking \"boundary\" commit meant to do: it marks (via\nadmittedly quite subtle change of style) \"linenr\" links which lead to\n'blame' view at blamed commit, not at parent commit (because there is\nno parent commit).\n\nThis patch is after one using \"previous\" header for finding parents\nfor \"linenr\" links, because it also uses (still undocumented)\n\"previous\" header.\n\n gitweb/gitweb.css  |    3 ++-\n gitweb/gitweb.perl |    7 ++++++-\n 2 files changed, 8 insertions(+), 2 deletions(-)\n\ndiff --git a/gitweb/gitweb.css b/gitweb/gitweb.css\nindex f47709b..4763337 100644\n--- a/gitweb/gitweb.css\n+++ b/gitweb/gitweb.css\n@@ -242,7 +242,8 @@ tr.dark:hover {\n \tbackground-color: #edece6;\n }\n \n-tr.boundary td.sha1 {\n+tr.boundary td.sha1,\n+tr.no-previous td.linenr {\n \tfont-weight: bold;\n }\n \ndiff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\nindex b8a121b..128bddd 100755\n--- a/gitweb/gitweb.perl\n+++ b/gitweb/gitweb.perl\n@@ -4819,7 +4819,7 @@ HTML\n \t\tmy ($full_rev, $orig_lineno, $lineno, $group_size) =\n \t\t   ($line =~ /^([0-9a-f]{40}) (\\d+) (\\d+)(?: (\\d+))?$/);\n \t\tif (!exists $metainfo{$full_rev}) {\n-\t\t\t$metainfo{$full_rev} = {};\n+\t\t\t$metainfo{$full_rev} = { 'nprevious' => 0 };\n \t\t}\n \t\tmy $meta = $metainfo{$full_rev};\n \t\tmy $data;\n@@ -4829,6 +4829,9 @@ HTML\n \t\t\tif ($data =~ /^(\\S+)(?: (.*))?$/) {\n \t\t\t\t$meta->{$1} = $2 unless exists $meta->{$1};\n \t\t\t}\n+\t\t\tif ($data =~ /^previous /) {\n+\t\t\t\t$meta->{'nprevious'}++;\n+\t\t\t}\n \t\t}\n \t\tmy $short_rev = substr($full_rev, 0, 8);\n \t\tmy $author = $meta->{'author'};\n@@ -4840,6 +4843,8 @@ HTML\n \t\t}\n \t\tmy $tr_class = $rev_color[$current_color];\n \t\t$tr_class .= ' boundary' if (exists $meta->{'boundary'});\n+\t\t$tr_class .= ' no-previous' if ($meta->{'nprevious'} == 0);\n+\t\t$tr_class .= ' multiple-previous' if ($meta->{'nprevious'} > 1);\n \t\tprint \"<tr id=\\\"l$lineno\\\" class=\\\"$tr_class\\\">\\n\";\n \t\tif ($group_size) {\n \t\t\tprint \"<td class=\\\"sha1\\\"\";\n-- \n1.6.3.3\n"},{"id":"118673","messageId":"1248475450-5668-6-git-send-email-jnareb@gmail.com","threadId":"20224","inReplyTo":"1248475450-5668-1-git-send-email-jnareb@gmail.com","subject":"[PATCHv2 05/10] gitweb: Add author initials in 'blame' view, a la \"git gui blame\"","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2009-07-24T22:44:05Z","receivedAt":"2009-07-24T22:44:05Z","isPatch":false,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"For example for \"Junio C Hamano\" initials would be \"JH\".  Of course\ninitials are added (below shortened SHA-1 of blamed commit) only if\ngroup of lines that blame the same commit has 2 or more lines in it.\n\nInitials are extracted using i18n /\\b([[:upper:]])\\B/g regexp.\n\nAdditionally initials help to distinguish boundary commits, as they\nuse bold weight font too (in addition to shortened SHA-1 of commit).\n\nSigned-off-by: Jakub Narebski <jnareb@gmail.com>\n---\nThis version of patch differs from previous version only in more\ndetailed commit message.  The change it introduces are identical.\n\n gitweb/gitweb.perl |    8 ++++++++\n 1 files changed, 8 insertions(+), 0 deletions(-)\n\ndiff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\nindex 128bddd..ea1ab5f 100755\n--- a/gitweb/gitweb.perl\n+++ b/gitweb/gitweb.perl\n@@ -4855,6 +4855,14 @@ HTML\n \t\t\t                             hash=>$full_rev,\n \t\t\t                             file_name=>$file_name)},\n \t\t\t              esc_html($short_rev));\n+\t\t\tif ($group_size >= 2) {\n+\t\t\t\tmy @author_initials = ($author =~ /\\b([[:upper:]])\\B/g);\n+\t\t\t\tif (@author_initials) {\n+\t\t\t\t\tprint \"<br />\" .\n+\t\t\t\t\t      esc_html(join('', @author_initials));\n+\t\t\t\t\t#           or join('.', ...)\n+\t\t\t\t}\n+\t\t\t}\n \t\t\tprint \"</td>\\n\";\n \t\t}\n \t\t# 'previous' <sha1 of parent commit> <filename at commit>\n-- \n1.6.3.3\n"},{"id":"118674","messageId":"1248475450-5668-7-git-send-email-jnareb@gmail.com","threadId":"20224","inReplyTo":"1248475450-5668-1-git-send-email-jnareb@gmail.com","subject":"[PATCH/RFC 06/10] gitweb: Use light/dark for class names also in 'blame' view","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2009-07-24T22:44:06Z","receivedAt":"2009-07-24T22:44:06Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Instead of using \"light2\" and \"dark2\" for class names in 'blame' view\n(in place of \"light\" and \"dark\" classes in other places) to avoid\nchanging style on hover in 'blame' view while doing it for other views\n(like 'shortlog'), use more advanced CSS, relying on the fact that\nmore specific selector wins.\n\nWhile at it add a few comments to gitweb CSS file, and consolidate\nsome repeated info.\n\nSigned-off-by: Jakub Narebski <jnareb@gmail.com>\n---\nThis patch is identical to previous version (patch 4/3 in previous\nversion of this series).\n\nThis is an RFC because \n\n1. I am not sure if I did it correctly.  I had to fiddle a bit with CSS\n(using \"table.blame .light:hover\" in place of \"table.blame tr.light:hover\")\nto get the same behaviour (well, the same as far as I have checked it).\n\n2. Commit message could use improvements (single sentence, blergh).\n\n gitweb/gitweb.css  |   17 ++++++++++-------\n gitweb/gitweb.perl |    2 +-\n 2 files changed, 11 insertions(+), 8 deletions(-)\n\ndiff --git a/gitweb/gitweb.css b/gitweb/gitweb.css\nindex 4763337..8f68fe3 100644\n--- a/gitweb/gitweb.css\n+++ b/gitweb/gitweb.css\n@@ -226,22 +226,25 @@ th {\n \ttext-align: left;\n }\n \n-tr.light:hover {\n-\tbackground-color: #edece6;\n-}\n-\n-tr.dark {\n-\tbackground-color: #f6f6f0;\n+/* do not change row style on hover for 'blame' view */\n+tr.light,\n+table.blame .light:hover {\n+\tbackground-color: #ffffff;\n }\n \n-tr.dark2 {\n+tr.dark,\n+table.blame .dark:hover {\n \tbackground-color: #f6f6f0;\n }\n \n+/* currently both use the same, but it can change */\n+tr.light:hover,\n tr.dark:hover {\n \tbackground-color: #edece6;\n }\n \n+/* boundary commits in 'blame' view */\n+/* and commits without \"previous\" */\n tr.boundary td.sha1,\n tr.no-previous td.linenr {\n \tfont-weight: bold;\ndiff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\nindex ea1ab5f..2cb60be 100755\n--- a/gitweb/gitweb.perl\n+++ b/gitweb/gitweb.perl\n@@ -4801,7 +4801,7 @@ sub git_blame {\n \tgit_print_page_path($file_name, $ftype, $hash_base);\n \n \t# page body\n-\tmy @rev_color = qw(light2 dark2);\n+\tmy @rev_color = qw(light dark);\n \tmy $num_colors = scalar(@rev_color);\n \tmy $current_color = 0;\n \tmy %metainfo = ();\n-- \n1.6.3.3\n"},{"id":"118675","messageId":"1248475450-5668-8-git-send-email-jnareb@gmail.com","threadId":"20224","inReplyTo":"1248475450-5668-1-git-send-email-jnareb@gmail.com","subject":"[PATCH 07/10] gitweb: Add -partial_query option to href() subroutine","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2009-07-24T22:44:07Z","receivedAt":"2009-07-24T22:44:07Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"href(..., -partial_query=>1) is meant to generate links which have not\nall parameters filled, and which can be completed by simply appending\n';<param>=<value>'.  This feature was implemented for future AJAX-y\n'blame_incremental' in JavaScript.\n\nOriginally by Petr Baudis as part of \"gitweb: Incremental blame\"\npatch, to deal with path_info URLs in JavaScript part easily and\ncorrectly.\n\nSigned-off-by: Jakub Narebski <jnareb@gmail.com>\n---\nThis change is extracted from incremental blame patch by Petr Baudis.\nIn previous version of this series it was part of 5/3 patch\nintroducing 'blame_incremental' view.\n\nHowever as you can see in incremental blame patch by Matrin Koegler:\n  http://thread.gmane.org/gmane.comp.version-control.git/47902/focus=47905\n\nthis is not strictly necessary. JavaScript (which is sole user of this\n'-partial-query' parameter) can simply check if there is '?' character\nin link already.  On the other hand side it makes JavaScript code a bit\nsimpler.\n\n gitweb/gitweb.perl |    3 ++-\n 1 files changed, 2 insertions(+), 1 deletions(-)\n\ndiff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\nindex 2cb60be..0d91ac7 100755\n--- a/gitweb/gitweb.perl\n+++ b/gitweb/gitweb.perl\n@@ -992,7 +992,8 @@ sub href {\n \t\t\t}\n \t\t}\n \t}\n-\t$href .= \"?\" . join(';', @result) if scalar @result;\n+\t$href .= \"?\" . join(';', @result)\n+\t\tif ($params{-partial_query} or scalar @result);\n \n \treturn $href;\n }\n-- \n1.6.3.3\n"},{"id":"118677","messageId":"1248475450-5668-9-git-send-email-jnareb@gmail.com","threadId":"20224","inReplyTo":"1248475450-5668-1-git-send-email-jnareb@gmail.com","subject":"[PATCH 08/10] gitweb: Add optional \"time to generate page\" info in footer","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2009-07-24T22:44:08Z","receivedAt":"2009-07-24T22:44:08Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Add \"This page took XXXs and Y git commands to generate\" to page\nfooter, if global feature 'timed' is enabled (disabled by default).\nRequires Time::HiRes installed for high precision 'wallclock' time.\n\nThis code is based on example code by Petr 'Pasky' Baudis.\n\nSigned-off-by: Jakub Narebski <jnareb@gmail.com>\n---\nThis patch was extracted from 5/3 patch implementing interactive\nblame, and modified to be conditional on 'timed' feature, and to show\nalso number of git commands used.\n\nNote that setting $t0 variable should be fairly early to have running\ntime of the whole script.  The same for $number_of_git_cmds.\n\nCurrent formatting is very basic, just like before.  It simply uses \n'page_footer' style.\n\nVariable names and name of feature is up to debate.\n\n gitweb/gitweb.perl |   29 +++++++++++++++++++++++++++++\n 1 files changed, 29 insertions(+), 0 deletions(-)\n\ndiff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\nindex 0d91ac7..bd77b31 100755\n--- a/gitweb/gitweb.perl\n+++ b/gitweb/gitweb.perl\n@@ -18,6 +18,12 @@ use File::Find qw();\n use File::Basename qw(basename);\n binmode STDOUT, ':utf8';\n \n+our $t0;\n+if (eval { require Time::HiRes; 1; }) {\n+\t$t0 = [Time::HiRes::gettimeofday()];\n+}\n+our $number_of_git_cmds = 0;\n+\n BEGIN {\n \tCGI->compile() if $ENV{'MOD_PERL'};\n }\n@@ -394,6 +400,13 @@ our %feature = (\n \t\t'sub' => \\&feature_avatar,\n \t\t'override' => 0,\n \t\t'default' => ['']},\n+\n+\t# Enable displaying how much time and how many git commands\n+\t# it took to generate and display page.  Disabled by default.\n+\t# Project specific override is not supported.\n+\t'timed' => {\n+\t\t'override' => 0,\n+\t\t'default' => [0]},\n );\n \n sub gitweb_get_feature {\n@@ -507,6 +520,7 @@ if (-e $GITWEB_CONFIG) {\n \n # version of the core git binary\n our $git_version = qx(\"$GIT\" --version) =~ m/git version (.*)$/ ? $1 : \"unknown\";\n+$number_of_git_cmds++;\n \n $projects_list ||= $projectroot;\n \n@@ -1956,6 +1970,7 @@ sub get_feed_info {\n \n # returns path to the core git executable and the --git-dir parameter as list\n sub git_cmd {\n+\t$number_of_git_cmds++;\n \treturn $GIT, '--git-dir='.$git_dir;\n }\n \n@@ -3206,6 +3221,20 @@ sub git_footer_html {\n \t}\n \tprint \"</div>\\n\"; # class=\"page_footer\"\n \n+\tif (defined $t0 && gitweb_check_feature('timed')) {\n+\t\tprint \"<div id=\\\"generate_info\\\" class=\\\"page_footer\\\">\\n\";\n+\t\tprint 'This page took '.\n+\t\t      '<span id=\"generate_time\" class=\"time_span\">'.\n+\t\t      Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]).'s'.\n+\t\t      '</span>'.\n+\t\t      ' and '.\n+\t\t      '<span id=\"generate_cmd\">'.\n+\t\t      $number_of_git_cmds.\n+\t\t      '</span> git commands '.\n+\t\t      \" to generate.\\n\";\n+\t\tprint \"</div>\\n\"; # class=\"page_footer\"\n+\t}\n+\n \tif (-f $site_footer) {\n \t\tinsert_file($site_footer);\n \t}\n-- \n1.6.3.3\n"},{"id":"118678","messageId":"1248475450-5668-10-git-send-email-jnareb@gmail.com","threadId":"20224","inReplyTo":"1248475450-5668-1-git-send-email-jnareb@gmail.com","subject":"[PATCHv2/RFC 09/10] gitweb: Incremental blame (proof of concept)","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2009-07-24T22:44:09Z","receivedAt":"2009-07-24T22:44:09Z","isPatch":false,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"This is tweaked up version of Petr Baudis <pasky@suse.cz> patch, which\nin turn was tweaked up version of Fredrik Kuivinen <frekui@gmail.com>'s\nproof of concept patch.  It adds 'blame_incremental' view, which\nincrementally displays line data in blame view using JavaScript (AJAX).\n\nThis patch does not (contrary to the one by Petr Baudis) enable this\nview in gitweb: there are no links leading to 'blame_incremental'\naction.  You would have to generate URL 'by hand' (e.g. changing 'blame'\nor 'blob' in gitweb URL to 'blame_incremental').  Having links in gitweb\nlead to this new action (e.g. by rewriting them like in previous patch,\nif JavaScript is enabled in browser) is left for later.\n\nLike earlier patch by Per Baudis it avoids code duplication, but it goes\none step further and use git_blame_common for ordinary blame view, for\nincremental blame, and (which is change from previous patch) for\nincremental blame data.\n\nHow the 'blame_incremental' view works:\n* gitweb generates initial info by putting file contents (from\n  git-cat-file) together with line numbers in blame table\n* then gitweb makes web browser JavaScript engine call startBlame()\n  function from blame.js\n* startBlame() opens connection to 'blame_data' view, which in turn\n  calls \"git blame --incremental\" for a file, and streams output of\n  git-blame to JavaScript (blame.js)\n* blame.js updates line info in blame view, coloring it, and updating\n  progress info; note that it has to use 3 colors to ensure that\n  different neighbour groups have different styles\n* when 'blame_data' ends, and blame.js finishes updating line info,\n  it fixes colors to match (as far as possible) ordinary 'blame' view,\n  and updates generating time info.\n\nIt deals with streamed 'blame_data' server error by notifying about them\nin the progress info area (just in case).\n\nDifferences between 'blame_incremental' and original 'blame' view:\n* 'blame_incremental' always used (partial) query form for links\n  generated by JavaScript.  The difference is visible if we use path_info\n  link (pass some or all arguments in path_info), e.g. in 'blame' view\n  called using:\n    http://git.example.com/w/git.git/blame/HEAD:/README\n  we have 'linenr' links using the same form:\n    http://git.example.com/w/git.git/blame/e83c5163316f89bfbde7d9ab23ca2e25604af290:/README#l4\n  while in 'blame_incremental' view called with:\n    http://git.example.com/w/git.git/blame_incremental/HEAD:/README\n  we have \"partial query\" form\n    http://git.example.com/w/git.git?;a=blame_incremental;hb=e83c5163316f89bfbde7d9ab23ca2e25604af290;f=README#l4\n  Changing this would require implementing something akin to href()\n  subroutine from gitweb.perl in JavaScript\n* 'blame_incremental' always uses \"rowspan\" appribite, even if\n  rowspan=\"1\".  This simplifies code, and is not visible to user.\n\nThis patch adds GITWEB_BLAMEJS compile configuration option, and\nmodifies git-instaweb.sh to take blame.js into account, but it does not\nupdate gitweb/README file (as it is only proof of concept patch).  The\ncode for git-instaweb.sh was taken from Pasky's patch.\n\nSigned-off-by: Fredrik Kuivinen <frekui@gmail.com>\nSigned-off-by: Petr Baudis <pasky@suse.cz>\nSigned-off-by: Jakub Narebski <jnareb@gmail.com>\n---\nRefrences:\n1. Original patch by Frederik Kuivinen\n   http://article.gmane.org/gmane.comp.version-control.git/41361\n2. Tweaked up version by Petr Baudis\n   http://article.gmane.org/gmane.comp.version-control.git/47614\n   http://article.gmane.org/gmane.comp.version-control.git/56657\n3. New link rewriting and some optimization in Matrin Koegler\n   series introducing some JavaScript support in Git\n   http://thread.gmane.org/gmane.comp.version-control.git/47902/focus=47905   \n4. First, second and third version by me (Jakub Narebski)\n   http://thread.gmane.org/gmane.comp.version-control.git/102657/focus=102712\n   http://article.gmane.org/gmane.comp.version-control.git/123202\n\nChanges compared to the last version:\n* simplified spacePad function in blame.js; add padLeft (unused)\n* deal with lack of 'timed' info from server, and with expanded\n  information including number of git commands used\n* a few style fixes (like declaring previously un-declared variables). \n* fixed escape code sequences; they are different for JavaScript than\n  for Perl (and for C).\n* refactor communicating error message to user into errorInfo(str)\n* use /regex/ instead of new RegExp(\"regex\")\n* use DOM2 HTML (faster) in place of DOM2 Core when possible,\n  e.g. elem.className instead of elem.getAttribute('class') \n* use === instead of == and !== instead of !=\n* first column cells doesn't need now (at least in theory) 'rowspan'\n  attribute to be present (set)\n* instead of creating list of classes in fixColorsAndGroups(), replace\n  colorN by light/dark in class name(s).\n* add unquote() to deal with quoted filenames (e.g. with TAB character\n  encoded as C escape sequence \"\\t\")\n* set and deal with \"no-previous\" and \"multiple-previous\" classes;\n  this was somewhat-ported from 'blame' view in gitweb.perl\n\nTODO list:\n* Using 'application/xhtml+xml' Content-Type, together with \n  XHTML 1.0 Strict doctype turns on strict validation (and not\n  continuing on errors, if possible) in some web browsers.\n  Unfortunately the web browser I use (Mozilla 1.17.2) turns on\n  strict mode also for _JavaScript_, which means no innerHTML.\n  \n  In current patch gitweb is modified to always return 'text/html'.\n  Better solution would be to return 'text/html' only for\n  'blame_incremental' view, or replace innerHTML by DOM2 manipulations\n  (which are unfortunately usually slower).\n\n* handleResponse is used both as onreadystatechange and pollTimer;\n  if onreadystatechange works for partial responses we can turn off\n  the timer.  It is protected from concurrent running by global\n  inProgress variable; we could instead pass XMLHttpResponse object\n  as a parameter (see comment in startBlame).\n* Probably 'blame_data' should use multipart/x-mixed-replace as\n  content type, instead of (in addition to?) text/plain.  I am not\n  sure about that; I am not knowledgeable in AJAX and Comet.\n* Remove or move to separate commit changes which help CPerl\n  mode for GNU Emacs to deal with syntax highlighting (#', #\").\n  Best via simplifying complex regexp, as in \"Perl best Practices\".\n* Remove (fade out) progress bar and progress info after blame\n  incremental finished run (left for a separate commit).\n* Instead of running startBlame, put it in window.onload handler\n\n Makefile           |    6 +-\n git-instaweb.sh    |    7 +\n gitweb/blame.js    |  632 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n gitweb/gitweb.css  |   19 ++\n gitweb/gitweb.perl |  287 ++++++++++++++++--------\n 5 files changed, 856 insertions(+), 95 deletions(-)\n create mode 100644 gitweb/blame.js\n\ndiff --git a/Makefile b/Makefile\nindex bde27ed..95b577c 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -265,6 +265,7 @@ GITWEB_HOMETEXT = indextext.html\n GITWEB_CSS = gitweb.css\n GITWEB_LOGO = git-logo.png\n GITWEB_FAVICON = git-favicon.png\n+GITWEB_BLAMEJS = blame.js\n GITWEB_SITE_HEADER =\n GITWEB_SITE_FOOTER =\n \n@@ -1406,13 +1407,14 @@ gitweb/gitweb.cgi: gitweb/gitweb.perl\n \t    -e 's|++GITWEB_CSS++|$(GITWEB_CSS)|g' \\\n \t    -e 's|++GITWEB_LOGO++|$(GITWEB_LOGO)|g' \\\n \t    -e 's|++GITWEB_FAVICON++|$(GITWEB_FAVICON)|g' \\\n+\t    -e 's|++GITWEB_BLAMEJS++|$(GITWEB_BLAMEJS)|g' \\\n \t    -e 's|++GITWEB_SITE_HEADER++|$(GITWEB_SITE_HEADER)|g' \\\n \t    -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' \\\n \t    $< >$@+ && \\\n \tchmod +x $@+ && \\\n \tmv $@+ $@\n \n-git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css\n+git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css gitweb/blame.js\n \t$(QUIET_GEN)$(RM) $@ $@+ && \\\n \tsed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \\\n \t    -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \\\n@@ -1421,6 +1423,8 @@ git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css\n \t    -e '/@@GITWEB_CGI@@/d' \\\n \t    -e '/@@GITWEB_CSS@@/r gitweb/gitweb.css' \\\n \t    -e '/@@GITWEB_CSS@@/d' \\\n+\t    -e '/@@GITWEB_BLAMEJS@@/r gitweb/blame.js' \\\n+\t    -e '/@@GITWEB_BLAMEJS@@/d' \\\n \t    -e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \\\n \t    $@.sh > $@+ && \\\n \tchmod +x $@+ && \\\ndiff --git a/git-instaweb.sh b/git-instaweb.sh\nindex 5f4419b..fd6341a 100755\n--- a/git-instaweb.sh\n+++ b/git-instaweb.sh\n@@ -331,8 +331,15 @@ gitweb_css () {\n EOFGITWEB\n }\n \n+gitweb_blamejs () {\n+\tcat > \"$1\" <<\\EOFGITWEB\n+@@GITWEB_BLAMEJS@@\n+EOFGITWEB\n+}\n+\n gitweb_cgi \"$GIT_DIR/gitweb/gitweb.cgi\"\n gitweb_css \"$GIT_DIR/gitweb/gitweb.css\"\n+gitweb_blamejs \"$GIT_DIR/gitweb/blame.js\"\n \n case \"$httpd\" in\n *lighttpd*)\ndiff --git a/gitweb/blame.js b/gitweb/blame.js\nnew file mode 100644\nindex 0000000..5a8a29f\n--- /dev/null\n+++ b/gitweb/blame.js\n@@ -0,0 +1,632 @@\n+// Copyright (C) 2007, Fredrik Kuivinen <frekui@gmail.com>\n+\n+/* ============================================================ */\n+/* generic utility functions */\n+\n+var DEBUG = 0;\n+function debug(str) {\n+\tif (DEBUG) {\n+\t\talert(str);\n+\t}\n+}\n+\n+// convert month or day of the month to string, padding it with\n+// '0' (zero) to two characters width if necessary, e.g. 2 -> '02'\n+function zeroPad(n) {\n+\tif (n < 10) {\n+\t\treturn '0' + n;\n+\t} else {\n+\t\treturn n.toString();\n+\t}\n+}\n+\n+// pad number N with nonbreakable spaces on the left, to WIDTH characters\n+// example: spacePad(12, 3) == '&nbsp;12' ('&nbsp;' is nonbreakable space)\n+function spacePad(n, width) {\n+\tvar prefix = '';\n+\n+\twidth -= n.toString().length;\n+\twhile (width > 1) {\n+\t\tprefix += '&nbsp;';\n+\t\twidth--;\n+\t}\n+\treturn prefix + n;\n+}\n+\n+/**\n+ * @param {string} input: input value converted to string.\n+ * @param {number} size: desired length of output.\n+ * @param {string} ch: single character to prefix to s.\n+ */\n+function padLeft(input, size, ch) {\n+\tvar s = input + \"\";\n+\twhile (s.length < size) {\n+\t\ts = ch + s;\n+\t}\n+\treturn s;\n+}\n+\n+// create XMLHttpRequest object in cross-browser way\n+function createRequestObject() {\n+\ttry {\n+\t\treturn new XMLHttpRequest();\n+\t} catch (e) {}\n+\ttry {\n+\t\treturn new ActiveXObject(\"Msxml2.XMLHTTP\");\n+\t} catch (e) {}\n+\ttry {\n+\t\treturn new ActiveXObject(\"Microsoft.XMLHTTP\");\n+\t} catch (e) {}\n+\n+\tdebug(\"XMLHttpRequest not supported\");\n+\treturn null;\n+}\n+\n+/* ============================================================ */\n+/* utility/helper functions (and variables) */\n+\n+var http;       // XMLHttpRequest object\n+var projectUrl; // partial query\n+\n+// 'commits' is an associative map. It maps SHA1s to Commit objects.\n+var commits = {};\n+\n+// constructor for Commit objects, used in 'blame'\n+function Commit(sha1) {\n+\tthis.sha1 = sha1;\n+\tthis.nprevious = 0; /* blame-specific */\n+}\n+\n+/* ............................................................ */\n+/* progress info, timing, error reporting */\n+\n+var blamedLines = 0;\n+var totalLines  = '???';\n+var div_progress_bar;\n+var div_progress_info;\n+\n+// how many lines does a file have, used in progress info\n+function countLines() {\n+\tvar table =\n+\t\tdocument.getElementById('blame_table') ||\n+\t\tdocument.getElementsByTagName('table')[0];\n+\n+\tif (table) {\n+\t\treturn table.getElementsByTagName('tr').length - 1; // for header\n+\t} else {\n+\t\treturn '...';\n+\t}\n+}\n+\n+// update progress info and length (width) of progress bar\n+function updateProgressInfo() {\n+\tif (!div_progress_info) {\n+\t\tdiv_progress_info = document.getElementById('progress_info');\n+\t}\n+\tif (!div_progress_bar) {\n+\t\tdiv_progress_bar = document.getElementById('progress_bar');\n+\t}\n+\tif (!div_progress_info && !div_progress_bar) {\n+\t\treturn;\n+\t}\n+\n+\tvar percentage = Math.floor(100.0*blamedLines/totalLines);\n+\n+\tif (div_progress_info) {\n+\t\t//div_progress_info.firstChild.data = ... /* text node */ ???\n+\t\tdiv_progress_info.innerHTML  = blamedLines + ' / ' + totalLines +\n+\t\t\t' (' + spacePad(percentage, 3) + '%)';\n+\t}\n+\n+\tif (div_progress_bar) {\n+\t\t//div_progress_bar.setAttribute('style', 'width: '+percentage+'%;');\n+\t\tdiv_progress_bar.style.width = percentage + '%';\n+\t}\n+}\n+\n+\n+var t_interval_server = '';\n+var cmds_server = '';\n+var t0 = new Date();\n+\n+// write how much it took to generate data, and to run script\n+function writeTimeInterval() {\n+\tvar info_time = document.getElementById('generate_time');\n+\tif (!info_time || !t_interval_server) {\n+\t\treturn;\n+\t}\n+\tvar t1 = new Date();\n+\t//info_time.firstChild.data = ... /* text node */ ???\n+\tinfo_time.innerHTML += ' + (' +\n+\t\tt_interval_server + 's server blame_data / ' +\n+\t\t(t1.getTime() - t0.getTime())/1000 + 's client JavaScript)';\n+\n+\tvar info_cmds = document.getElementById('generate_cmd');\n+\tif (!info_time || !cmds_server) {\n+\t\treturn;\n+\t}\n+\tinfo_cmds.innerHTML += ' + ' + cmds_server;\n+}\n+\n+// show an error message alert to user within page\n+function errorInfo(str) {\n+\tif (!div_progress_info) {\n+\t\tdiv_progress_info = document.getElementById('progress_info');\n+\t}\n+\tif (div_progress_info) {\n+\t\tdiv_progress_info.className = 'error';\n+\t\tdiv_progress_info.innerHTML = str; /* can contain HTML */\n+\t}\n+}\n+\n+/* ............................................................ */\n+/* coloring rows during blame_data (git blame --incremental) run */\n+\n+// used to extract N from colorN, where N is a number,\n+var colorRe = /\\bcolor([0-9]*)\\b/;\n+\n+// return N if <tr class=\"colorN\">, otherwise return null\n+// (some browsers require CSS class names to begin with letter)\n+function getColorNo(tr) {\n+\tif (!tr) {\n+\t\treturn null;\n+\t}\n+\tvar className = tr.className;\n+\tif (className) {\n+\t\tvar match = colorRe.exec(className);\n+\t\tif (match) {\n+\t\t\treturn parseInt(match[1], 10);\n+\t\t}\n+\t}\n+\treturn null;\n+}\n+\n+// return one of given possible colors (curently least used one)\n+// example: chooseColorNoFrom(2, 3) returns 2 or 3\n+var colorsFreq = [0, 0, 0];\n+// assumes that  1 <= arguments[i] <= colorsFreq.length\n+function chooseColorNoFrom() {\n+\t// choose the color which is least used\n+\tvar colorNo = arguments[0];\n+\tfor (var i = 1; i < arguments.length; i++) {\n+\t\tif (colorsFreq[arguments[i]-1] < colorsFreq[colorNo-1]) {\n+\t\t\tcolorNo = arguments[i];\n+\t\t}\n+\t}\n+\tcolorsFreq[colorNo-1]++;\n+\treturn colorNo;\n+}\n+\n+// given two neigbour <tr> elements, find color which would be different\n+// from color of both of neighbours; used to 3-color blame table\n+function findColorNo(tr_prev, tr_next) {\n+\tvar color_prev = getColorNo(tr_prev);\n+\tvar color_next = getColorNo(tr_next);\n+\n+\n+\t// neither of neighbours has color set\n+\t// THEN we can use any of 3 possible colors\n+\tif (!color_prev && !color_next) {\n+\t\treturn chooseColorNoFrom(1,2,3);\n+\t}\n+\n+\t// either both neighbours have the same color,\n+\t// or only one of neighbours have color set\n+\t// THEN we can use any color except given\n+\tvar color;\n+\tif (color_prev === color_next) {\n+\t\tcolor = color_prev; // = color_next;\n+\t} else if (!color_prev) {\n+\t\tcolor = color_next;\n+\t} else if (!color_next) {\n+\t\tcolor = color_prev;\n+\t}\n+\tif (color) {\n+\t\treturn chooseColorNoFrom((color % 3) + 1, ((color+1) % 3) + 1);\n+\t}\n+\n+\t// neighbours have different colors\n+\t// THEN there is only one color left\n+\treturn (3 - ((color_prev + color_next) % 3));\n+}\n+\n+/* ............................................................ */\n+/* coloring rows like 'blame' after 'blame_data' finishes */\n+\n+// returns true if given row element (tr) is first in commit group\n+function isStartOfGroup(tr) {\n+\treturn tr.firstChild.className === 'sha1';\n+}\n+\n+// change colors to use zebra coloring (2 colors) instead of 3 colors\n+// concatenate neighbour commit groups belonging to the same commit\n+function fixColorsAndGroups() {\n+\tvar colorClasses = ['light', 'dark'];\n+\tvar linenum = 1;\n+\tvar tr, prev_group;\n+\tvar colorClass = 0;\n+\tvar table =\n+\t\tdocument.getElementById('blame_table') ||\n+\t\tdocument.getElementsByTagName('table')[0];\n+\n+\twhile ((tr = document.getElementById('l'+linenum))) {\n+\t// index origin is 0, which is table header\n+\t//while ((tr = table.rows[linenum])) {\n+\t\tif (isStartOfGroup(tr, linenum, document)) {\n+\t\t\tif (prev_group &&\n+\t\t\t    prev_group.firstChild.firstChild.href ===\n+\t\t\t            tr.firstChild.firstChild.href) {\n+\t\t\t\t// we have to concatenate groups\n+\t\t\t\tvar prev_rows = prev_group.firstChild.rowSpan || 1;\n+\t\t\t\tvar curr_rows =         tr.firstChild.rowSpan || 1;\n+\t\t\t\tprev_group.firstChild.rowSpan = prev_rows + curr_rows;\n+\t\t\t\t//tr.removeChild(tr.firstChild);\n+\t\t\t\ttr.deleteCell(0); // DOM2 HTML way\n+\t\t\t} else {\n+\t\t\t\tcolorClass = (colorClass + 1) % 2;\n+\t\t\t\tprev_group = tr;\n+\t\t\t}\n+\t\t}\n+\t\tvar tr_class = tr.className;\n+\t\ttr.className = tr_class.replace(colorRe, colorClasses[colorClass]);\n+\t\tlinenum++;\n+\t}\n+}\n+\n+/* ............................................................ */\n+/* time and data */\n+\n+// used to extract hours and minutes from timezone info, e.g '-0900'\n+var tzRe = /^([+-][0-9][0-9])([0-9][0-9])$/;\n+\n+// return date in local time formatted in iso-8601 like format\n+// 'yyyy-mm-dd HH:MM:SS +/-ZZZZ' e.g. '2005-08-07 21:49:46 +0200'\n+function formatDateISOLocal(epoch, timezoneInfo) {\n+\tvar match = tzRe.exec(timezoneInfo);\n+\t// date corrected by timezone\n+\tvar localDate = new Date(1000 * (epoch +\n+\t\t(parseInt(match[1],10)*3600 + parseInt(match[2],10)*60)));\n+\tvar localDateStr = // e.g. '2005-08-07'\n+\t\tlocalDate.getUTCFullYear()         + '-' +\n+\t\tzeroPad(localDate.getUTCMonth()+1) + '-' +\n+\t\tzeroPad(localDate.getUTCDate());\n+\tvar localTimeStr = // e.g. '21:49:46'\n+\t\tzeroPad(localDate.getUTCHours())   + ':' +\n+\t\tzeroPad(localDate.getUTCMinutes()) + ':' +\n+\t\tzeroPad(localDate.getUTCSeconds());\n+\n+\treturn localDateStr + ' ' + localTimeStr + ' ' + timezoneInfo;\n+}\n+\n+/* ............................................................ */\n+/* unquoting/unescaping filenames */\n+\n+var escCodeRe = /\\\\([^0-7]|[0-7]{1,3})/g;\n+\n+// unquote maybe git-quoted filename\n+function unquote(str) {\n+\tfunction unq(seq) {\n+\t\tvar es = { // character escape codes, aka escape sequences\n+\t\t\tt: \"\\t\",   // tab            (HT, TAB)\n+\t\t\tn: \"\\n\",   // newline        (NL)\n+\t\t\tr: \"\\r\",   // return         (CR)\n+\t\t\tf: \"\\f\",   // form feed      (FF)\n+\t\t\tb: \"\\b\",   // backspace      (BS)\n+\t\t\ta: \"\\x07\", // alarm (bell)   (BEL)\n+\t\t\te: \"\\x1B\", // escape         (ESC)\n+\t\t\tv: \"\\v\"    // vertical tab   (VT)\n+\t\t};\n+\n+\t\tif (seq.search(/^[0-7]{1,3}$/) !== -1) {\n+\t\t\t// octal char sequence\n+\t\t\treturn String.fromCharCode(parseInt(seq, 8));\n+\t\t} else if (seq in es) {\n+\t\t\t// C escape sequence, aka character escape code\n+\t\t\treturn es[seq];\n+\t\t}\n+\t\t// quoted ordinary character\n+\t\treturn seq;\n+\t}\n+\n+\tvar match = str.match(/^\\\"(.*)\\\"$/);\n+\tif (match) {\n+\t\tstr = match[1];\n+\t\t// perhaps str = eval('\"'+str+'\"'); would be enough?\n+\t\tstr = str.replace(escCodeRe,\n+\t\t\tfunction (substr, p1, offset, s) { return unq(p1); });\n+\t}\n+\treturn str;\n+}\n+\n+/* ============================================================ */\n+/* main part: parsing response */\n+\n+// called for each blame entry, as soon as it finishes\n+function handleLine(commit) {\n+\t/* \n+\t   This is the structure of the HTML fragment we are working\n+\t   with:\n+\n+\t   <tr id=\"l123\" class=\"\">\n+\t     <td class=\"sha1\" title=\"\"><a href=\"\"></a></td>\n+\t     <td class=\"linenr\"><a class=\"linenr\" href=\"\">123</a></td>\n+\t     <td class=\"pre\"># times (my ext3 doesn&#39;t).</td>\n+\t   </tr>\n+\t*/\n+\n+\tvar resline = commit.resline;\n+\n+\t// format date and time string only once per commit\n+\tif (!commit.info) {\n+\t\t/* e.g. 'Kay Sievers, 2005-08-07 21:49:46 +0200' */\n+\t\tcommit.info = commit.author + ', ' +\n+\t\t\tformatDateISOLocal(commit.authorTime, commit.authorTimezone);\n+\t}\n+\n+\t// color depends on group of lines, not only on blamed commit\n+\tvar colorNo = findColorNo(\n+\t\tdocument.getElementById('l'+(resline-1)),\n+\t\tdocument.getElementById('l'+(resline+commit.numlines))\n+\t);\n+\n+\t// loop over lines in commit group\n+\tfor (var i = 0; i < commit.numlines; i++) {\n+\t\tvar tr = document.getElementById('l'+resline);\n+\t\tif (!tr) {\n+\t\t\tdebug('tr is null! resline: ' + resline);\n+\t\t\tbreak;\n+\t\t}\n+\t\t/*\n+\t\t\t<tr id=\"l123\" class=\"\">\n+\t\t\t  <td class=\"sha1\" title=\"\"><a href=\"\"></a></td>\n+\t\t\t  <td class=\"linenr\"><a class=\"linenr\" href=\"\">123</a></td>\n+\t\t\t  <td class=\"pre\"># times (my ext3 doesn&#39;t).</td>\n+\t\t\t</tr>\n+\t\t*/\n+\t\tvar td_sha1  = tr.firstChild;\n+\t\tvar a_sha1   = td_sha1.firstChild;\n+\t\tvar a_linenr = td_sha1.nextSibling.firstChild;\n+\n+\t\t/* <tr id=\"l123\" class=\"\"> */\n+\t\tvar tr_class = '';\n+\t\tif (colorNo !== null) {\n+\t\t\ttr_class = 'color'+colorNo;\n+\t\t}\n+\t\tif (commit.boundary) {\n+\t\t\ttr_class += ' boundary';\n+\t\t}\n+\t\tif (commit.nprevious === 0) {\n+\t\t\ttr_class += ' no-previous';\n+\t\t} else if (commit.nprevious > 1) {\n+\t\t\ttr_class += ' multiple-previous';\n+\t\t}\n+\t\ttr.className = tr_class;\n+\n+\t\t/* <td class=\"sha1\" title=\"?\" rowspan=\"?\"><a href=\"?\">?</a></td> */\n+\t\tif (i === 0) {\n+\t\t\ttd_sha1.title = commit.info;\n+\t\t\ttd_sha1.rowSpan = commit.numlines;\n+\n+\t\t\ta_sha1.href = projectUrl + ';a=commit;h=' + commit.sha1;\n+\t\t\t//a_sha1.firstChild.data = ... /* text node */ ???\n+\t\t\ta_sha1.innerHTML = commit.sha1.substr(0, 8);\n+\t\t\tif (commit.numlines >= 2) {\n+\t\t\t\tvar br   = document.createElement(\"br\");\n+\t\t\t\tvar text = document.createTextNode(\n+\t\t\t\t\tcommit.author.match(/\\b([A-Z])\\B/g).join(''));\n+\t\t\t\tif (br && text) {\n+\t\t\t\t\ttd_sha1.appendChild(br);\n+\t\t\t\t\ttd_sha1.appendChild(text);\n+\t\t\t\t}\n+\t\t\t}\n+\t\t} else {\n+\t\t\t//tr.removeChild(td_sha1); // DOM2 Core way\n+\t\t\ttr.deleteCell(0); // DOM2 HTML way\n+\t\t}\n+\n+\t\t/* <td class=\"linenr\"><a class=\"linenr\" href=\"?\">123</a></td> */\n+\t\tvar linenr_commit =\n+\t\t\t('previous' in commit ? commit.previous : commit.sha1);\n+\t\tvar linenr_filename =\n+\t\t\t('file_parent' in commit ? commit.file_parent : commit.filename);\n+\t\ta_linenr.href = projectUrl + ';a=blame_incremental' +\n+\t\t\t';hb=' + linenr_commit +\n+\t\t\t';f='  + encodeURIComponent(linenr_filename) +\n+\t\t\t'#l' + (commit.srcline + i);\n+\n+\t\tresline++;\n+\t\tblamedLines++;\n+\n+\t\t//updateProgressInfo();\n+\t}\n+}\n+\n+// ----------------------------------------------------------------------\n+\n+var prevDataLength = -1;\n+var nextLine = 0;\n+var inProgress = false;\n+\n+var sha1Re = /^([0-9a-f]{40}) ([0-9]+) ([0-9]+) ([0-9]+)/;\n+var infoRe = /^([a-z-]+) ?(.*)/;\n+var endRe  = /^END ?([^ ]*) ?(.*)/;\n+var curCommit = new Commit();\n+\n+var pollTimer = null;\n+\n+// handler for XMLHttpRequest onreadystatechange events\n+function handleResponse() {\n+\tdebug('handleResp ready: ' + http.readyState +\n+\t      ' respText null?: ' + (http.responseText === null) +\n+\t      ' progress: ' + inProgress);\n+\n+\tif (http.readyState !== 4 && http.readyState !== 3) {\n+\t\treturn;\n+\t}\n+\n+\t// the server returned error\n+\tif (http.readyState === 3 && http.status !== 200) {\n+\t\treturn;\n+\t}\n+\tif (http.readyState === 4 && http.status !== 200) {\n+\t\tif (!div_progress_info) {\n+\t\t\tdiv_progress_info = document.getElementById('progress_info');\n+\t\t}\n+\n+\t\terrorInfo('Server error: ' +\n+\t\t\thttp.status + ' - ' + (http.statusText || 'Error contacting server'));\n+\n+\t\tclearInterval(pollTimer);\n+\t\tinProgress = false;\n+\t}\n+\n+\t// In konqueror http.responseText is sometimes null here...\n+\tif (http.responseText === null) {\n+\t\treturn;\n+\t}\n+\n+\t// in case we were called before finished processing\n+\tif (inProgress) {\n+\t\treturn;\n+\t} else {\n+\t\tinProgress = true;\n+\t}\n+\n+\twhile (prevDataLength !== http.responseText.length) {\n+\t\tif (http.readyState === 4 &&\n+\t\t    prevDataLength === http.responseText.length) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tprevDataLength = http.responseText.length;\n+\t\tvar response = http.responseText.substring(nextLine);\n+\t\tvar lines = response.split('\\n');\n+\t\tnextLine = nextLine + response.lastIndexOf('\\n') + 1;\n+\t\tif (response[response.length-1] !== '\\n') {\n+\t\t\tlines.pop();\n+\t\t}\n+\n+\t\tfor (var i = 0; i < lines.length; i++) {\n+\t\t\tvar match = sha1Re.exec(lines[i]);\n+\t\t\tif (match) {\n+\t\t\t\tvar sha1 = match[1];\n+\t\t\t\tvar srcline  = parseInt(match[2], 10);\n+\t\t\t\tvar resline  = parseInt(match[3], 10);\n+\t\t\t\tvar numlines = parseInt(match[4], 10);\n+\t\t\t\tvar c = commits[sha1];\n+\t\t\t\tif (!c) {\n+\t\t\t\t\tc = new Commit(sha1);\n+\t\t\t\t\tcommits[sha1] = c;\n+\t\t\t\t}\n+\n+\t\t\t\tc.srcline = srcline;\n+\t\t\t\tc.resline = resline;\n+\t\t\t\tc.numlines = numlines;\n+\t\t\t\tcurCommit = c;\n+\n+\t\t\t} else if ((match = infoRe.exec(lines[i]))) {\n+\t\t\t\tvar info = match[1];\n+\t\t\t\tvar data = match[2];\n+\t\t\t\tswitch (info) {\n+\t\t\t\tcase 'filename':\n+\t\t\t\t\tcurCommit.filename = unquote(data);\n+\t\t\t\t\t// 'filename' information terminates the entry\n+\t\t\t\t\thandleLine(curCommit);\n+\t\t\t\t\tupdateProgressInfo();\n+\t\t\t\t\tbreak;\n+\t\t\t\tcase 'author':\n+\t\t\t\t\tcurCommit.author = data;\n+\t\t\t\t\tbreak;\n+\t\t\t\tcase 'author-time':\n+\t\t\t\t\tcurCommit.authorTime = parseInt(data, 10);\n+\t\t\t\t\tbreak;\n+\t\t\t\tcase 'author-tz':\n+\t\t\t\t\tcurCommit.authorTimezone = data;\n+\t\t\t\t\tbreak;\n+\t\t\t\tcase 'previous':\n+\t\t\t\t\tcurCommit.nprevious++;\n+\t\t\t\t\tif (!'previous' in curCommit) {\n+\t\t\t\t\t\tvar parts = data.split(' ', 2);\n+\t\t\t\t\t\tcurCommit.previous    = parts[0];\n+\t\t\t\t\t\tcurCommit.file_parent = unquote(parts[1]);\n+\t\t\t\t\t}\n+\t\t\t\t\tbreak;\n+\t\t\t\tcase 'boundary':\n+\t\t\t\t\tdebug('Boundary commit: '+curCommit.sha1);\n+\t\t\t\t\tcurCommit.boundary = true;\n+\t\t\t\t\tbreak;\n+\t\t\t\t} // end switch\n+\n+\t\t\t} else if ((match = endRe.exec(lines[i]))) {\n+\t\t\t\tt_interval_server = match[1];\n+\t\t\t\tcmds_server = match[2];\n+\t\t\t\tdebug('END: '+lines[i]);\n+\t\t\t} else if (lines[i] !== '') {\n+\t\t\t\tdebug('malformed line: ' + lines[i]);\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\t// did we finish work?\n+\tif (http.readyState === 4 &&\n+\t    prevDataLength === http.responseText.length) {\n+\t\tclearInterval(pollTimer);\n+\n+\t\tfixColorsAndGroups();\n+\t\twriteTimeInterval();\n+\t\tcommits = {}; // free memory\n+\t}\n+\n+\tinProgress = false;\n+}\n+\n+// ============================================================\n+// ------------------------------------------------------------\n+\n+/*\n+\tFunction: startBlame\n+\n+\tIncrementally update line data in blame_incremental view in gitweb.\n+\n+\tParameters:\n+\n+\t\tblamedataUrl - URL to server script generating blame data.\n+\t\tbUrl -partial URL to project, used to generate links in blame.\n+\n+\tComments:\n+\n+\tCalled from 'blame_incremental' view after loading table with\n+\tfile contents, a base for blame view.\n+*/\n+function startBlame(blamedataUrl, bUrl) {\n+\tdebug('startBlame('+blamedataUrl+', '+bUrl+')');\n+\n+\thttp = createRequestObject();\n+\tif (!http) {\n+\t\terrorInfo('<b>ERROR:</b> XMLHttpRequest not supported');\n+\t\treturn;\n+\t}\n+\n+\tt0 = new Date();\n+\tprojectUrl = bUrl;\n+\tif ((div_progress_bar = document.getElementById('progress_bar'))) {\n+\t\tdiv_progress_bar.setAttribute('style', 'width: 100%;');\n+\t\t//div_progress_bar.style.value = 'width: 100%;';\n+\t}\n+\ttotalLines = countLines();\n+\tupdateProgressInfo();\n+\n+\thttp.open('get', blamedataUrl);\n+\thttp.setRequestHeader('Accept', 'text/plain'); // in case of future changes\n+\t// perhaps also 'multipart/x-mixed-replace'\n+\thttp.onreadystatechange = handleResponse;\n+\t//http.onreadystatechange = function () { handleResponse(http); };\n+\thttp.send(null);\n+\n+\t// not all browsers call onreadystatechange event on each server flush\n+\tif (!DEBUG) {\n+\t\tpollTimer = setInterval(handleResponse, 1000);\n+\t}\n+}\n+\n+// end of blame.js\ndiff --git a/gitweb/gitweb.css b/gitweb/gitweb.css\nindex 8f68fe3..8b05604 100644\n--- a/gitweb/gitweb.css\n+++ b/gitweb/gitweb.css\n@@ -250,6 +250,14 @@ tr.no-previous td.linenr {\n \tfont-weight: bold;\n }\n \n+tr.color1:hover { background-color: #e6ede6; }\n+tr.color2:hover { background-color: #e6e6ed; }\n+tr.color3:hover { background-color: #ede6e6; }\n+\n+tr.color1 { background-color: #f6fff6; }\n+tr.color2 { background-color: #f6f6ff; }\n+tr.color3 { background-color: #fff6f6; }\n+\n td {\n \tpadding: 2px 5px;\n \tfont-size: 100%;\n@@ -341,6 +349,17 @@ td.mode {\n \tfont-family: monospace;\n }\n \n+/* progress of blame_interactive */\n+div#progress_bar {\n+\theight: 2px;\n+\tmargin-bottom: -2px;\n+\tbackground-color: #d8d9d0;\n+}\n+div#progress_info {\n+\tfloat: right;\n+\ttext-align: right;\n+}\n+\n /* styling of diffs (patchsets): commitdiff and blobdiff views */\n div.diff.header,\n div.diff.extended_header {\ndiff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\nindex bd77b31..036f8da 100755\n--- a/gitweb/gitweb.perl\n+++ b/gitweb/gitweb.perl\n@@ -96,6 +96,8 @@ our $stylesheet = undef;\n our $logo = \"++GITWEB_LOGO++\";\n # URI of GIT favicon, assumed to be image/png type\n our $favicon = \"++GITWEB_FAVICON++\";\n+# URI of blame.js\n+our $blamejs = \"++GITWEB_BLAMEJS++\";\n \n # URI and label (title) of GIT logo link\n #our $logo_url = \"http://www.kernel.org/pub/software/scm/git/docs/\";\n@@ -564,6 +566,8 @@ our %cgi_param_mapping = @cgi_param_mapping;\n # we will also need to know the possible actions, for validation\n our %actions = (\n \t\"blame\" => \\&git_blame,\n+\t\"blame_incremental\" => \\&git_blame_incremental,\n+\t\"blame_data\" => \\&git_blame_data,\n \t\"blobdiff\" => \\&git_blobdiff,\n \t\"blobdiff_plain\" => \\&git_blobdiff_plain,\n \t\"blob\" => \\&git_blob,\n@@ -1721,7 +1725,7 @@ sub format_diff_from_to_header {\n \t# no extra formatting for \"^--- /dev/null\"\n \tif (! $diffinfo->{'nparents'}) {\n \t\t# ordinary (single parent) diff\n-\t\tif ($line =~ m!^--- \"?a/!) {\n+\t\tif ($line =~ m!^--- \"?a/!) {#\"\n \t\t\tif ($from->{'href'}) {\n \t\t\t\t$line = '--- a/' .\n \t\t\t\t        $cgi->a({-href=>$from->{'href'}, -class=>\"path\"},\n@@ -3048,13 +3052,13 @@ sub git_header_html {\n \t# 'application/xhtml+xml', otherwise send it as plain old 'text/html'.\n \t# we have to do this because MSIE sometimes globs '*/*', pretending to\n \t# support xhtml+xml but choking when it gets what it asked for.\n-\tif (defined $cgi->http('HTTP_ACCEPT') &&\n-\t    $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\\s|^)application\\/xhtml\\+xml(,|;|\\s|$)/ &&\n-\t    $cgi->Accept('application/xhtml+xml') != 0) {\n-\t\t$content_type = 'application/xhtml+xml';\n-\t} else {\n+\t#if (defined $cgi->http('HTTP_ACCEPT') &&\n+\t#    $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\\s|^)application\\/xhtml\\+xml(,|;|\\s|$)/ &&\n+\t#    $cgi->Accept('application/xhtml+xml') != 0) {\n+\t#\t$content_type = 'application/xhtml+xml';\n+\t#} else {\n \t\t$content_type = 'text/html';\n-\t}\n+\t#}\n \tprint $cgi->header(-type=>$content_type, -charset => 'utf-8',\n \t                   -status=> $status, -expires => $expires);\n \tmy $mod_perl_version = $ENV{'MOD_PERL'} ? \" $ENV{'MOD_PERL'}\" : '';\n@@ -4024,7 +4028,7 @@ sub git_patchset_body {\n \twhile ($patch_line) {\n \n \t\t# parse \"git diff\" header line\n-\t\tif ($patch_line =~ m/^diff --git (\\\"(?:[^\\\\\\\"]*(?:\\\\.[^\\\\\\\"]*)*)\\\"|[^ \"]*) (.*)$/) {\n+\t\tif ($patch_line =~ m/^diff --git (\\\"(?:[^\\\\\\\"]*(?:\\\\.[^\\\\\\\"]*)*)\\\"|[^ \"]*) (.*)$/) {#\"\n \t\t\t# $1 is from_name, which we do not use\n \t\t\t$to_name = unquote($2);\n \t\t\t$to_name =~ s!^b/!!;\n@@ -4788,7 +4792,9 @@ sub git_tag {\n \tgit_footer_html();\n }\n \n-sub git_blame {\n+sub git_blame_common {\n+\tmy $format = shift || 'porcelain';\n+\n \t# permissions\n \tgitweb_check_feature('blame')\n \t\tor die_error(403, \"Blame view not allowed\");\n@@ -4810,10 +4816,43 @@ sub git_blame {\n \t\t}\n \t}\n \n-\t# run git-blame --porcelain\n-\topen my $fd, \"-|\", git_cmd(), \"blame\", '-p',\n-\t\t$hash_base, '--', $file_name\n-\t\tor die_error(500, \"Open git-blame failed\");\n+\tmy $fd;\n+\tif ($format eq 'incremental') {\n+\t\t# get file contents (as base)\n+\t\topen $fd, \"-|\", git_cmd(), 'cat-file', 'blob', $hash\n+\t\t\tor die_error(500, \"Open git-cat-file failed\");\n+\t} elsif ($format eq 'data') {\n+\t\t# run git-blame --incremental\n+\t\topen $fd, \"-|\", git_cmd(), \"blame\", \"--incremental\",\n+\t\t\t$hash_base, \"--\", $file_name\n+\t\t\tor die_error(500, \"Open git-blame --incremental failed\");\n+\t} else {\n+\t\t# run git-blame --porcelain\n+\t\topen $fd, \"-|\", git_cmd(), \"blame\", '-p',\n+\t\t\t$hash_base, '--', $file_name\n+\t\t\tor die_error(500, \"Open git-blame --porcelain failed\");\n+\t}\n+\n+\t# incremental blame data returns early\n+\tif ($format eq 'data') {\n+\t\tprint $cgi->header(\n+\t\t\t-type=>\"text/plain\", -charset => \"utf-8\",\n+\t\t\t-status=> \"200 OK\");\n+\t\tlocal $| = 1; # output autoflush\n+\t\tprint while <$fd>;\n+\t\tclose $fd\n+\t\t\tor print \"ERROR $!\\n\";\n+\n+\t\tprint 'END';\n+\t\tif (defined $t0 && gitweb_check_feature('timed')) {\n+\t\t\tprint ' '.\n+\t\t\t      Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]).\n+\t\t\t      ' '.$number_of_git_cmds;\n+\t\t}\n+\t\tprint \"\\n\";\n+\n+\t\treturn;\n+\t}\n \n \t# page header\n \tgit_header_html();\n@@ -4824,109 +4863,169 @@ sub git_blame {\n \t\t$cgi->a({-href => href(action=>\"history\", -replay=>1)},\n \t\t        \"history\") .\n \t\t\" | \" .\n-\t\t$cgi->a({-href => href(action=>\"blame\", file_name=>$file_name)},\n+\t\t$cgi->a({-href => href(action=>$action, file_name=>$file_name)},\n \t\t        \"HEAD\");\n \tgit_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);\n \tgit_print_header_div('commit', esc_html($co{'title'}), $hash_base);\n \tgit_print_page_path($file_name, $ftype, $hash_base);\n \n \t# page body\n+\tif ($format eq 'incremental') {\n+\t\tprint \"<noscript>\\n<div class=\\\"error\\\"><center><b>\\n\".\n+\t\t      \"This page requires JavaScript to run\\nUse \".\n+\t\t      $cgi->a({-href => href(action=>'blame',-replay=>1)}, 'this page').\n+\t\t      \" instead.\\n\".\n+\t\t      \"</b></center></div>\\n</noscript>\\n\";\n+\n+\t\tprint qq!<div id=\"progress_bar\" style=\"width: 100%; background-color: yellow\"></div>\\n!;\n+\t}\n+\n+\tprint qq!<div class=\"page_body\">\\n!;\n+\tprint qq!<div id=\"progress_info\">... / ...</div>\\n!\n+\t\tif ($format eq 'incremental');\n+\tprint qq!<table id=\"blame_table\" class=\"blame\" width=\"100%\">\\n!.\n+\t      #qq!<col width=\"5.5em\" /><col width=\"2.5em\" /><col width=\"*\" />\\n!.\n+\t      qq!<thead>\\n!.\n+\t      qq!<tr><th>Commit</th><th>Line</th><th>Data</th></tr>\\n!.\n+\t      qq!</thead>\\n!.\n+\t      qq!<tbody>\\n!;\n+\n \tmy @rev_color = qw(light dark);\n \tmy $num_colors = scalar(@rev_color);\n \tmy $current_color = 0;\n-\tmy %metainfo = ();\n \n-\tprint <<HTML;\n-<div class=\"page_body\">\n-<table class=\"blame\">\n-<tr><th>Commit</th><th>Line</th><th>Data</th></tr>\n-HTML\n- LINE:\n-\twhile (my $line = <$fd>) {\n-\t\tchomp $line;\n-\t\t# the header: <SHA-1> <src lineno> <dst lineno> [<lines in group>]\n-\t\t# no <lines in group> for subsequent lines in group of lines\n-\t\tmy ($full_rev, $orig_lineno, $lineno, $group_size) =\n-\t\t   ($line =~ /^([0-9a-f]{40}) (\\d+) (\\d+)(?: (\\d+))?$/);\n-\t\tif (!exists $metainfo{$full_rev}) {\n-\t\t\t$metainfo{$full_rev} = { 'nprevious' => 0 };\n-\t\t}\n-\t\tmy $meta = $metainfo{$full_rev};\n-\t\tmy $data;\n-\t\twhile ($data = <$fd>) {\n-\t\t\tchomp $data;\n-\t\t\tlast if ($data =~ s/^\\t//); # contents of line\n-\t\t\tif ($data =~ /^(\\S+)(?: (.*))?$/) {\n-\t\t\t\t$meta->{$1} = $2 unless exists $meta->{$1};\n+\tif ($format eq 'incremental') {\n+\t\tmy $color_class = $rev_color[$current_color];\n+\n+\t\t#contents of a file\n+\t\tmy $linenr = 0;\n+\tLINE:\n+\t\twhile (my $line = <$fd>) {\n+\t\t\tchomp $line;\n+\t\t\t$linenr++;\n+\n+\t\t\tprint qq!<tr id=\"l$linenr\" class=\"$color_class\">!.\n+\t\t\t      qq!<td class=\"sha1\"><a href=\"\"></a></td>!.\n+\t\t\t      qq!<td class=\"linenr\">!.\n+\t\t\t      qq!<a class=\"linenr\" href=\"\">$linenr</a></td>!;\n+\t\t\tprint qq!<td class=\"pre\">! . esc_html($line) . \"</td>\\n\";\n+\t\t\tprint qq!</tr>\\n!;\n+\t\t}\n+\n+\t} else { # porcelain, i.e. ordinary blame\n+\t\tmy %metainfo = (); # saves information about commits\n+\n+\t\t# blame data\n+\tLINE:\n+\t\twhile (my $line = <$fd>) {\n+\t\t\tchomp $line;\n+\t\t\t# the header: <SHA-1> <src lineno> <dst lineno> [<lines in group>]\n+\t\t\t# no <lines in group> for subsequent lines in group of lines\n+\t\t\tmy ($full_rev, $orig_lineno, $lineno, $group_size) =\n+\t\t\t   ($line =~ /^([0-9a-f]{40}) (\\d+) (\\d+)(?: (\\d+))?$/);\n+\t\t\tif (!exists $metainfo{$full_rev}) {\n+\t\t\t\t$metainfo{$full_rev} = { 'nprevious' => 0 };\n \t\t\t}\n-\t\t\tif ($data =~ /^previous /) {\n-\t\t\t\t$meta->{'nprevious'}++;\n+\t\t\tmy $meta = $metainfo{$full_rev};\n+\t\t\tmy $data;\n+\t\t\twhile ($data = <$fd>) {\n+\t\t\t\tchomp $data;\n+\t\t\t\tlast if ($data =~ s/^\\t//); # contents of line\n+\t\t\t\tif ($data =~ /^(\\S+)(?: (.*))?$/) {\n+\t\t\t\t\t$meta->{$1} = $2 unless exists $meta->{$1};\n+\t\t\t\t}\n+\t\t\t\tif ($data =~ /^previous /) {\n+\t\t\t\t\t$meta->{'nprevious'}++;\n+\t\t\t\t}\n \t\t\t}\n-\t\t}\n-\t\tmy $short_rev = substr($full_rev, 0, 8);\n-\t\tmy $author = $meta->{'author'};\n-\t\tmy %date =\n-\t\t\tparse_date($meta->{'author-time'}, $meta->{'author-tz'});\n-\t\tmy $date = $date{'iso-tz'};\n-\t\tif ($group_size) {\n-\t\t\t$current_color = ($current_color + 1) % $num_colors;\n-\t\t}\n-\t\tmy $tr_class = $rev_color[$current_color];\n-\t\t$tr_class .= ' boundary' if (exists $meta->{'boundary'});\n-\t\t$tr_class .= ' no-previous' if ($meta->{'nprevious'} == 0);\n-\t\t$tr_class .= ' multiple-previous' if ($meta->{'nprevious'} > 1);\n-\t\tprint \"<tr id=\\\"l$lineno\\\" class=\\\"$tr_class\\\">\\n\";\n-\t\tif ($group_size) {\n-\t\t\tprint \"<td class=\\\"sha1\\\"\";\n-\t\t\tprint \" title=\\\"\". esc_html($author) . \", $date\\\"\";\n-\t\t\tprint \" rowspan=\\\"$group_size\\\"\" if ($group_size > 1);\n-\t\t\tprint \">\";\n-\t\t\tprint $cgi->a({-href => href(action=>\"commit\",\n-\t\t\t                             hash=>$full_rev,\n-\t\t\t                             file_name=>$file_name)},\n-\t\t\t              esc_html($short_rev));\n-\t\t\tif ($group_size >= 2) {\n-\t\t\t\tmy @author_initials = ($author =~ /\\b([[:upper:]])\\B/g);\n-\t\t\t\tif (@author_initials) {\n-\t\t\t\t\tprint \"<br />\" .\n-\t\t\t\t\t      esc_html(join('', @author_initials));\n-\t\t\t\t\t#           or join('.', ...)\n+\t\t\tmy $short_rev = substr($full_rev, 0, 8);\n+\t\t\tmy $author = $meta->{'author'};\n+\t\t\tmy %date =\n+\t\t\t\tparse_date($meta->{'author-time'}, $meta->{'author-tz'});\n+\t\t\tmy $date = $date{'iso-tz'};\n+\t\t\tif ($group_size) {\n+\t\t\t\t$current_color = ($current_color + 1) % $num_colors;\n+\t\t\t}\n+\t\t\tmy $tr_class = $rev_color[$current_color];\n+\t\t\t$tr_class .= ' boundary' if (exists $meta->{'boundary'});\n+\t\t\t$tr_class .= ' no-previous' if ($meta->{'nprevious'} == 0);\n+\t\t\t$tr_class .= ' multiple-previous' if ($meta->{'nprevious'} > 1);\n+\t\t\tprint \"<tr id=\\\"l$lineno\\\" class=\\\"$tr_class\\\">\\n\";\n+\t\t\tif ($group_size) {\n+\t\t\t\tprint \"<td class=\\\"sha1\\\"\";\n+\t\t\t\tprint \" title=\\\"\". esc_html($author) . \", $date\\\"\";\n+\t\t\t\tprint \" rowspan=\\\"$group_size\\\"\" if ($group_size > 1);\n+\t\t\t\tprint \">\";\n+\t\t\t\tprint $cgi->a({-href => href(action=>\"commit\",\n+\t\t\t\t                             hash=>$full_rev,\n+\t\t\t\t                             file_name=>$file_name)},\n+\t\t\t\t              esc_html($short_rev));\n+\t\t\t\tif ($group_size >= 2) {\n+\t\t\t\t\tmy @author_initials = ($author =~ /\\b([[:upper:]])\\B/g);\n+\t\t\t\t\tif (@author_initials) {\n+\t\t\t\t\t\tprint \"<br />\" .\n+\t\t\t\t\t\t      esc_html(join('', @author_initials));\n+\t\t\t\t\t\t#           or join('.', ...)\n+\t\t\t\t\t}\n \t\t\t\t}\n+\t\t\t\tprint \"</td>\\n\";\n \t\t\t}\n-\t\t\tprint \"</td>\\n\";\n-\t\t}\n-\t\t# 'previous' <sha1 of parent commit> <filename at commit>\n-\t\tif (exists $meta->{'previous'} &&\n-\t\t    $meta->{'previous'} =~ /^([a-fA-F0-9]{40}) (.*)$/) {\n-\t\t\t$meta->{'parent'} = $1;\n-\t\t\t$meta->{'file_parent'} = unquote($2);\n-\t\t}\n-\t\tmy $linenr_commit =\n-\t\t\texists($meta->{'parent'}) ?\n-\t\t\t$meta->{'parent'} : $full_rev;\n-\t\tmy $linenr_filename =\n-\t\t\texists($meta->{'file_parent'}) ?\n-\t\t\t$meta->{'file_parent'} : unquote($meta->{'filename'});\n-\t\tmy $blamed = href(action => 'blame',\n-\t\t                  file_name => $linenr_filename,\n-\t\t                  hash_base => $linenr_commit);\n-\t\tprint \"<td class=\\\"linenr\\\">\";\n-\t\tprint $cgi->a({ -href => \"$blamed#l$orig_lineno\",\n-\t\t                -class => \"linenr\" },\n-\t\t              esc_html($lineno));\n-\t\tprint \"</td>\";\n-\t\tprint \"<td class=\\\"pre\\\">\" . esc_html($data) . \"</td>\\n\";\n-\t\tprint \"</tr>\\n\";\n+\t\t\t# 'previous' <sha1 of parent commit> <filename at commit>\n+\t\t\tif (exists $meta->{'previous'} &&\n+\t\t\t    $meta->{'previous'} =~ /^([a-fA-F0-9]{40}) (.*)$/) {\n+\t\t\t\t$meta->{'parent'} = $1;\n+\t\t\t\t$meta->{'file_parent'} = unquote($2);\n+\t\t\t}\n+\t\t\tmy $linenr_commit =\n+\t\t\t\texists($meta->{'parent'}) ?\n+\t\t\t\t$meta->{'parent'} : $full_rev;\n+\t\t\tmy $linenr_filename =\n+\t\t\t\texists($meta->{'file_parent'}) ?\n+\t\t\t\t$meta->{'file_parent'} : unquote($meta->{'filename'});\n+\t\t\tmy $blamed = href(action => 'blame',\n+\t\t\t                  file_name => $linenr_filename,\n+\t\t\t                  hash_base => $linenr_commit);\n+\t\t\tprint \"<td class=\\\"linenr\\\">\";\n+\t\t\tprint $cgi->a({ -href => \"$blamed#l$orig_lineno\",\n+\t\t\t                -class => \"linenr\" },\n+\t\t\t              esc_html($lineno));\n+\t\t\tprint \"</td>\";\n+\t\t\tprint \"<td class=\\\"pre\\\">\" . esc_html($data) . \"</td>\\n\";\n+\t\t\tprint \"</tr>\\n\";\n+\t\t} # end while\n+\n \t}\n-\tprint \"</table>\\n\";\n-\tprint \"</div>\";\n+\n+\t# footer\n+\tprint \"</tbody>\\n\".\n+\t      \"</table>\\n\"; # class=\"blame\"\n+\tprint \"</div>\\n\";   # class=\"blame_body\"\n \tclose $fd\n \t\tor print \"Reading blob failed\\n\";\n \n-\t# page footer\n+\tif ($format eq 'incremental') {\n+\t\tprint qq!<script type=\"text/javascript\" src=\"$blamejs\"></script>\\n!.\n+\t\t      qq!<script type=\"text/javascript\">\\n!.\n+\t\t      qq!startBlame(\"!. href(action=>\"blame_data\", -replay=>1) .qq!\",\\n!.\n+\t\t      qq!           \"!. href(-partial_query=>1) .qq!\");\\n!.\n+\t\t      qq!</script>\\n!;\n+\t}\n+\n \tgit_footer_html();\n }\n \n+sub git_blame {\n+\tgit_blame_common();\n+}\n+\n+sub git_blame_incremental {\n+\tgit_blame_common('incremental');\n+}\n+\n+sub git_blame_data {\n+\tgit_blame_common('data');\n+}\n+\n sub git_tags {\n \tmy $head = git_get_head_hash($project);\n \tgit_header_html();\n-- \n1.6.3.3\n"},{"id":"118679","messageId":"1248475450-5668-11-git-send-email-jnareb@gmail.com","threadId":"20224","inReplyTo":"1248475450-5668-1-git-send-email-jnareb@gmail.com","subject":"[PATCH/RFC 10/10] gitweb: Create links leading to 'blame_incremental' using JavaScript","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2009-07-24T22:44:10Z","receivedAt":"2009-07-24T22:44:10Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"The new 'blame_incremental' view requires JavaScript to run.  Not all\nweb browsers implement JavaScript (e.g. text browsers such as Lynx),\nand not all users have JavaScript enabled.  Therefore instead of\nunconditionally link to 'blame_incremental' view, we use JavaScript to\nconvert those links to lead to view utilizing JavaScript, by adding\n'js=1' to link.\n\nThe only JavaScript-aware/using view is currently 'blame_incremental'.\nAs first, it might want to have links to non-JavaScript version, and\nsecond, it should also use window.onload, we do not add nor run\nfixLinks() for such views (currently hardcoded 'blame_incremental')\n\nPossible enhancement would be to do JavaScript redirect by setting\nwindow.location instead of modifying $format and $action in\ngit_blame_common() subroutine.\n\n\nThis idea was originally implemented by Petr Baudis in\n  http://article.gmane.org/gmane.comp.version-control.git/47614\nbut it added <script> element with fixBlameLinks() function in page\nheader, to be added as onload event using 'onload' attribute of HTML\n'body' element: <body onload=\"fixBlameLinks();\">.  This version adds\nscript at then end of page (in the page footer), and uses JavaScript\n'window.onload=fixLinks();'.  Also in Petr version only links marked\nwith 'blamelink' class were modified, and they were modified by\nreplacing \"a=blame\" by \"a=blame_incremental\"... which doesn't work for\npath_info links, and might replace wrong part if there is \"a=blame\" in\nproject name, ref name or file name.\n\nSlightly different solution was implemented by Martin Koegler in\n  http://thread.gmane.org/gmane.comp.version-control.git/47902/focus=47905\nHere GitAddLinks() function was in gitweb.js file, not as contents of\n<script> element.  This might be a better solution (although I think\nit would be better to split JavaScript file and load only parts that\nare required).  It was also included in page header (in <head>\nelement) though, which means waiting for a script to load (and run).\nIt was smarter in that to \"fix\" (modify) link, it split URL, modified\nvalue of 'a' parameter, and then recreated modified link.  It avoids\ntrouble with \"a=blame\" as substring in project name or file name, but\nit doesn't work with path_info URL/link in the way it was written.\n\nSigned-off-by: Jakub Narebski <jnareb@gmail.com>\n---\nThis patch is new, and didn't appear in any way in any of my earlier\nseries dealing with incremental blame view.\n\nTODO list:\n* Extract and remove unrelated changes, like updating copyright\n* Perhaps put fixLinks() function in separate file gitweb.js.\n  Should gitweb use single JavaScript file, or should it be split into\n  more than one file?\n* Better solution to \"don't invoke for JavaScript-aware actions\"\n  problem.  Currently hardcoded 'blame_incremental'.\n\n gitweb/blame.js    |    8 +++++---\n gitweb/gitweb.perl |   21 +++++++++++++++++++++\n 2 files changed, 26 insertions(+), 3 deletions(-)\n\ndiff --git a/gitweb/blame.js b/gitweb/blame.js\nindex 5a8a29f..d8ecee1 100644\n--- a/gitweb/blame.js\n+++ b/gitweb/blame.js\n@@ -1,4 +1,6 @@\n // Copyright (C) 2007, Fredrik Kuivinen <frekui@gmail.com>\n+//               2007, Petr Baudis <pasky@suse.cz>\n+//          2008-2009, Jakub Narebski <jnareb@gmail.com>\n \n /* ============================================================ */\n /* generic utility functions */\n@@ -36,7 +38,7 @@ function spacePad(n, width) {\n /**\n  * @param {string} input: input value converted to string.\n  * @param {number} size: desired length of output.\n- * @param {string} ch: single character to prefix to s.\n+ * @param {string} ch: single character to prefix to string.\n  */\n function padLeft(input, size, ch) {\n \tvar s = input + \"\";\n@@ -611,14 +613,14 @@ function startBlame(blamedataUrl, bUrl) {\n \tprojectUrl = bUrl;\n \tif ((div_progress_bar = document.getElementById('progress_bar'))) {\n \t\tdiv_progress_bar.setAttribute('style', 'width: 100%;');\n-\t\t//div_progress_bar.style.value = 'width: 100%;';\n+\t\t//div_progress_bar.style.value = 'width: 100%;'; // doesn't work\n \t}\n \ttotalLines = countLines();\n \tupdateProgressInfo();\n \n \thttp.open('get', blamedataUrl);\n \thttp.setRequestHeader('Accept', 'text/plain'); // in case of future changes\n-\t// perhaps also 'multipart/x-mixed-replace'\n+\t// perhaps also, in the future, 'multipart/x-mixed-replace' (not standard)\n \thttp.onreadystatechange = handleResponse;\n \t//http.onreadystatechange = function () { handleResponse(http); };\n \thttp.send(null);\ndiff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\nindex 036f8da..38492d0 100755\n--- a/gitweb/gitweb.perl\n+++ b/gitweb/gitweb.perl\n@@ -3243,6 +3243,23 @@ sub git_footer_html {\n \t\tinsert_file($site_footer);\n \t}\n \n+\tif ($action ne 'blame_incremental') {\n+\t\tprint <<'HTML';\n+<script type=\"text/javascript\">/* <![CDATA[ */\n+function fixLinks() {\n+\t//var allLinks = document.getElementsByTagName(\"a\");\n+\tvar allLinks = document.links;\n+\tfor (var i = 0; i < allLinks.length; i++) {\n+\t\tvar link = allLinks[i];\n+\t\tlink.href +=\n+\t\t\t(link.href.indexOf('?') === -1 ? '?' : ';') + 'js=1';\n+\t}\n+}\n+window.onload = fixLinks;\n+/* ]]> */</script>\n+HTML\n+\t}\n+\n \tprint \"</body>\\n\" .\n \t      \"</html>\";\n }\n@@ -4794,6 +4811,10 @@ sub git_tag {\n \n sub git_blame_common {\n \tmy $format = shift || 'porcelain';\n+\tif ($format eq 'porcelain' && $cgi->param('js')) {\n+\t\t$format = 'incremental';\n+\t\t$action = 'blame_incremental'; # for page title etc\n+\t}\n \n \t# permissions\n \tgitweb_check_feature('blame')\n-- \n1.6.3.3\n"},{"id":"118690","messageId":"7vskgl4p9t.fsf@alter.siamese.dyndns.org","threadId":"20224","inReplyTo":"1248475450-5668-1-git-send-email-jnareb@gmail.com","subject":"Re: [PATCHv2 00/10] gitweb: 'blame' view improvements","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-07-24T23:47:26Z","receivedAt":"2009-07-24T23:47:26Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jakub Narebski <jnareb@gmail.com> writes:\n\n> This is second version of my improvements to gitweb's 'blame' view,\n>   Subject: [PATCH 0/3] gitweb: 'blame' view improvements\n>   Message-Id: <200907102354.43232.jnareb@gmail.com>\n>   http://article.gmane.org/gmane.comp.version-control.git/123085\n>\n> including some further improvements, and this time including\n> preparation and AJAX-y 'blame_incremental' view in series proper.\n> It also finally creates 'blame_incremental' links (last patch in\n> series).\n\nI understand that this series replaces the four-patch series between\n\"git log --oneline a6be48b^..3643cc0\"---am I correct?\n"},{"id":"118692","messageId":"200907250210.45225.jnareb@gmail.com","threadId":"20224","inReplyTo":"7vskgl4p9t.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCHv2 00/10] gitweb: 'blame' view improvements","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2009-07-25T00:10:42Z","receivedAt":"2009-07-25T00:10:42Z","isPatch":false,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Dnia sobota 25. lipca 2009 01:47, Junio C Hamano napisał:\n> Jakub Narebski <jnareb@gmail.com> writes:\n> \n> > This is second version of my improvements to gitweb's 'blame' view,\n> >   Subject: [PATCH 0/3] gitweb: 'blame' view improvements\n> >   Message-Id: <200907102354.43232.jnareb@gmail.com>\n> >   http://article.gmane.org/gmane.comp.version-control.git/123085\n> >\n> > including some further improvements, and this time including\n> > preparation and AJAX-y 'blame_incremental' view in series proper.\n> > It also finally creates 'blame_incremental' links (last patch in\n> > series).\n> \n> I understand that this series replaces the four-patch series between\n> \"git log --oneline a6be48b^..3643cc0\"---am I correct?\n\nYes, it does.  In particular it fixes my mistake about need for in \nunquote_maybe in \"gitweb: Use \"previous\" header of git-blame -p in \n'blame' view\".  And that is the only change (except extending commit \nmessage) in a6be48b^..3643cc0.\n\nI see that I forgot to mark \"gitweb: Add -partial_query option to href() \nsubroutine\" as RFC; it isn't I think strictly necessary.  But that is \nwhy I put it in separate commit: to easy delete it or revert if deemed \nnot needed.\n\n-- \nJakub Narebski\nPoland\n"},{"id":"118693","messageId":"7vocr94o2t.fsf@alter.siamese.dyndns.org","threadId":"20224","inReplyTo":"1248475450-5668-3-git-send-email-jnareb@gmail.com","subject":"Re: [PATCH 02/10] gitweb: Mark boundary commits in 'blame' view","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-07-25T00:13:14Z","receivedAt":"2009-07-25T00:13:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jakub Narebski <jnareb@gmail.com> writes:\n\n> Use \"boundary\" class to mark boundary commits, which currently results\n> in using bold weight font for SHA-1 of a commit (to be more exact for\n> all text in the first cell in row, that contains SHA-1 of a commit).\n> ...\n> diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css\n> index 70b7c2f..f47709b 100644\n> --- a/gitweb/gitweb.css\n> +++ b/gitweb/gitweb.css\n> @@ -242,6 +242,10 @@ tr.dark:hover {\n>  \tbackground-color: #edece6;\n>  }\n>  \n> +tr.boundary td.sha1 {\n> +\tfont-weight: bold;\n> +}\n> +\n\n\"boundary\" means that \"blame low..hight file\" attributed the line to the\n\"low\" commit, not because the commit introduced the line, but because the\nuser said not to bother digging further.\n\nI had an assumption that in such a bounded blame, lines attributed to the\nboundary commit are not very interesting (they belong to a distant stable\npast that the user does not care much about, as opposed to more recent\nbreakages), and that is exactly the same reasoning behind the -b option of\n\"git blame\" command.\n\nI would have expected the boundary to be shown in weaker decoration\n(e.g. gray letters as opposed to black), not in stronger annotation.\n\nPerhaps you are talking about something different?  I am a bit puzzled.\n"},{"id":"118695","messageId":"200907250232.28561.jnareb@gmail.com","threadId":"20224","inReplyTo":"7vocr94o2t.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 02/10] gitweb: Mark boundary commits in 'blame' view","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2009-07-25T00:32:27Z","receivedAt":"2009-07-25T00:32:27Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"On Sat, 25 July 2009, Junio C Hamano wrote:\n> Jakub Narebski <jnareb@gmail.com> writes:\n> \n> > Use \"boundary\" class to mark boundary commits, which currently results\n> > in using bold weight font for SHA-1 of a commit (to be more exact for\n> > all text in the first cell in row, that contains SHA-1 of a commit).\n> > ...\n> > diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css\n> > index 70b7c2f..f47709b 100644\n> > --- a/gitweb/gitweb.css\n> > +++ b/gitweb/gitweb.css\n> > @@ -242,6 +242,10 @@ tr.dark:hover {\n> >  \tbackground-color: #edece6;\n> >  }\n> >  \n> > +tr.boundary td.sha1 {\n> > +\tfont-weight: bold;\n> > +}\n> > +\n> \n> \"boundary\" means that \"blame low..hight file\" attributed the line to the\n> \"low\" commit, not because the commit introduced the line, but because the\n> user said not to bother digging further.\n\nWell, currently 'blame' view in gitweb doesn't allow to limit revision\nrange from below, i.e. to state \"low\" commit; it doesn't use 'hpb' \n(hash_parent_base) parameter.  So boundary commit means root commit.\n\n> \n> I had an assumption that in such a bounded blame, lines attributed to the\n> boundary commit are not very interesting (they belong to a distant stable\n> past that the user does not care much about, as opposed to more recent\n> breakages), and that is exactly the same reasoning behind the -b option of\n> \"git blame\" command.\n> \n> I would have expected the boundary to be shown in weaker decoration\n> (e.g. gray letters as opposed to black), not in stronger annotation.\n\nWell, weaker decoration is, I think, actually harder to do in CSS...\n\n> Perhaps you are talking about something different?  I am a bit puzzled.\n\nWell, I have thought that only boundary commits can be without previous\n[blame] commit, but I noticed that it is not the case: see 04/10.  But\nsome of that remains of my mistaken belief can resonate in commit \nmessage... ;-)\n\n-- \nJakub Narebski\nPoland\n"},{"id":"118696","messageId":"7vskgl38ao.fsf@alter.siamese.dyndns.org","threadId":"20224","inReplyTo":"200907250232.28561.jnareb@gmail.com","subject":"Re: [PATCH 02/10] gitweb: Mark boundary commits in 'blame' view","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-07-25T00:39:27Z","receivedAt":"2009-07-25T00:39:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jakub Narebski <jnareb@gmail.com> writes:\n\n> On Sat, 25 July 2009, Junio C Hamano wrote:\n>> Jakub Narebski <jnareb@gmail.com> writes:\n>> \n>> > Use \"boundary\" class to mark boundary commits, which currently results\n>> > in using bold weight font for SHA-1 of a commit (to be more exact for\n>> > all text in the first cell in row, that contains SHA-1 of a commit).\n>> > ...\n>> > diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css\n>> > index 70b7c2f..f47709b 100644\n>> > --- a/gitweb/gitweb.css\n>> > +++ b/gitweb/gitweb.css\n>> > @@ -242,6 +242,10 @@ tr.dark:hover {\n>> >  \tbackground-color: #edece6;\n>> >  }\n>> >  \n>> > +tr.boundary td.sha1 {\n>> > +\tfont-weight: bold;\n>> > +}\n>> > +\n>> \n>> \"boundary\" means that \"blame low..hight file\" attributed the line to the\n>> \"low\" commit, not because the commit introduced the line, but because the\n>> user said not to bother digging further.\n>\n> Well, currently 'blame' view in gitweb doesn't allow to limit revision\n> range from below, i.e. to state \"low\" commit; it doesn't use 'hpb' \n> (hash_parent_base) parameter.  So boundary commit means root commit.\n\nYes, but I think it is the same thing.  The initial import of a tarball,\nthat is \"a distant stable past as opposed to more recent breakages\".\n"},{"id":"118711","messageId":"20090725104622.GA7337@auto.tuwien.ac.at","threadId":"20224","inReplyTo":"1248475450-5668-11-git-send-email-jnareb@gmail.com","subject":"Re: [PATCH/RFC 10/10] gitweb: Create links leading to 'blame_incremental' using JavaScript","fromName":"Martin Koegler","fromEmail":"mkoegler@auto.tuwien.ac.at","sentAt":"2009-07-25T10:46:22Z","receivedAt":"2009-07-25T10:46:22Z","isPatch":true,"sender":{"key":"mkoegler@auto.tuwien.ac.at","avatar":null},"body":"On Sat, Jul 25, 2009 at 12:44:10AM +0200, Jakub Narebski wrote:\n> TODO list:\n> * Perhaps put fixLinks() function in separate file gitweb.js.\n>   Should gitweb use single JavaScript file, or should it be split into\n>   more than one file?\n\nThe same question can be asked for gitweb itself:\n\nWhy is it a single perl file and not splited in many different\nmodules?\n\nmfg Martin Kögler\n"},{"id":"118770","messageId":"200907252128.25631.jnareb@gmail.com","threadId":"20224","inReplyTo":"1248475450-5668-10-git-send-email-jnareb@gmail.com","subject":"Re: [PATCHv2/RFC 09/10] gitweb: Incremental blame (proof of concept)","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2009-07-25T19:28:22Z","receivedAt":"2009-07-25T19:28:22Z","isPatch":false,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"On Sat, 25 July 2009, Jakub Narebski wrote:\n\n> * set and deal with \"no-previous\" and \"multiple-previous\" classes;\n>   this was somewhat-ported from 'blame' view in gitweb.perl\n\nNote that it is something that we unfortunately would have to deal with, \nnamely playing catch-up with new features in gitweb's 'blame' view, \n\"duplicating\" quite a bit of code (well, rewriting Perl code in \nJavaScript).  But this is, I think, unfortunately unavoidable, unless \nwe move to generating JavaScript code from Perl, e.g. using CGI::Ajax\n(something that GWT does with Java and JavaScript).\n\n-- \nJakub Narebski\nPoland\n"},{"id":"118825","messageId":"200907261206.15646.jnareb@gmail.com","threadId":"20224","inReplyTo":"20090725104622.GA7337@auto.tuwien.ac.at","subject":"Re: [PATCH/RFC 10/10] gitweb: Create links leading to 'blame_incremental' using JavaScript","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2009-07-26T10:06:11Z","receivedAt":"2009-07-26T10:06:11Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"On Sat, 25 Jul 2009, Martin Koegler wrote:\n> On Sat, Jul 25, 2009 at 12:44:10AM +0200, Jakub Narebski wrote:\n\n> > TODO list:\n> > * Perhaps put fixLinks() function in separate file gitweb.js.\n> >   Should gitweb use single JavaScript file, or should it be split into\n> >   more than one file?\n> \n> The same question can be asked for gitweb itself:\n\nWell, there is one important difference: gitweb itself is not send\nover network to client.  JavaScript is.  (Although I'm not sure how\ngreat it is of an issue, with browsers caching JavaScript.  Perhaps\none single file would be better idea.)\n\n> \n> Why is it a single perl file and not splited in many different\n> modules?\n\nThere are a few causes.\n\n1. Gitweb (then gitweb.cgi) started as single file.  There is a bit\n   of resistance to changing this, especially that splitting it might\n   make it harder to interate changes from other people who still use\n   single file gitweb (see for example gitweb fork at git.kernel.org\n   history).\n\n2. Having it all in single file make its easy to install and update.\n   Well, it made more sense when only way to configure gitweb was to\n   edit gitweb.cgi.  Now building gitweb.cgi is the task for build\n   system, and the only thing left is to copy files in correct place\n   (I think that there are distribution specific packages which makes\n   installing gitweb as easy as \"xxx install gitweb\").\n\n3. You would have to decide _how_ to split it into many different \n   modules.  Do you know any good examples?\n\n-- \nJakub Narebski\nPoland\n"},{"id":"118887","messageId":"20090727181046.GA30365@auto.tuwien.ac.at","threadId":"20224","inReplyTo":"200907261206.15646.jnareb@gmail.com","subject":"Re: [PATCH/RFC 10/10] gitweb: Create links leading to 'blame_incremental' using JavaScript","fromName":"Martin Koegler","fromEmail":"mkoegler@auto.tuwien.ac.at","sentAt":"2009-07-27T18:10:46Z","receivedAt":"2009-07-27T18:10:46Z","isPatch":true,"sender":{"key":"mkoegler@auto.tuwien.ac.at","avatar":null},"body":"On Sun, Jul 26, 2009 at 12:06:11PM +0200, Jakub Narebski wrote:\n> On Sat, 25 Jul 2009, Martin Koegler wrote:\n> > On Sat, Jul 25, 2009 at 12:44:10AM +0200, Jakub Narebski wrote:\n> \n> > > TODO list:\n> > > * Perhaps put fixLinks() function in separate file gitweb.js.\n> > >   Should gitweb use single JavaScript file, or should it be split into\n> > >   more than one file?\n> > \n> > The same question can be asked for gitweb itself:\n> \n> Well, there is one important difference: gitweb itself is not send\n> over network to client.  JavaScript is.  (Although I'm not sure how\n> great it is of an issue, with browsers caching JavaScript.  Perhaps\n> one single file would be better idea.)\n\nMore files mean more request on the server. If the browser is\nconfigured to check at each request, it will issue a GET for each\nJavaScript file, which will be answered by a 304 after the first\nrequest. In the \"automatic mode\", the browser waits for some time\n(determined by a heuristic), before it will issue a GET for each file\nrequest again.\n\nSo in my option, on (bigger) file is better, as it means fewer request.\n\n> > \n> > Why is it a single perl file and not splited in many different\n> > modules?\n> \n> 2. Having it all in single file make its easy to install and update.\n>    Well, it made more sense when only way to configure gitweb was to\n>    edit gitweb.cgi.  Now building gitweb.cgi is the task for build\n>    system, and the only thing left is to copy files in correct place\n>    (I think that there are distribution specific packages which makes\n>    installing gitweb as easy as \"xxx install gitweb\").\n\nYes, there are gitweb packages, which automaticially server\nrepositories under a specific path (eg. /srv/git). For such packages,\nthe js layout is irrelavant.\n\nWhen manually installing, copying only one javascript file simplifies\nthe deployment.\n\n> 3. You would have to decide _how_ to split it into many different \n>    modules.  Do you know any good examples?\n\nJavascript uses on global namespace. If it is one file, its implicitly\nclear, that everything (functions, variables) are in one scope.  If\nyou split it into multiple files, you have to remember, what the other\nfiles contain.\n\nmfg Martin Kögler\n"},{"id":"118896","messageId":"200907272106.57484.jnareb@gmail.com","threadId":"20224","inReplyTo":"20090727181046.GA30365@auto.tuwien.ac.at","subject":"Re: [PATCH/RFC 10/10] gitweb: Create links leading to 'blame_incremental' using JavaScript","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2009-07-27T19:06:56Z","receivedAt":"2009-07-27T19:06:56Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"On Mon, 27 July 2009, Martin Koegler wrote:\n> On Sun, Jul 26, 2009 at 12:06:11PM +0200, Jakub Narebski wrote:\n>> On Sat, 25 Jul 2009, Martin Koegler wrote:\n>>> On Sat, Jul 25, 2009 at 12:44:10AM +0200, Jakub Narebski wrote:\n>> \n>>>> TODO list:\n>>>> * Perhaps put fixLinks() function in separate file gitweb.js.\n>>>>   Should gitweb use single JavaScript file, or should it be split into\n>>>>   more than one file?\n>>> \n>>> The same question can be asked for gitweb itself:\n>> \n>> Well, there is one important difference: gitweb itself is not send\n>> over network to client.  JavaScript is.  (Although I'm not sure how\n>> great it is of an issue, with browsers caching JavaScript.  Perhaps\n>> one single file would be better idea.)\n> \n> More files mean more request on the server. If the browser is\n> configured to check at each request, it will issue a GET for each\n> JavaScript file, which will be answered by a 304 after the first\n> request. In the \"automatic mode\", the browser waits for some time\n> (determined by a heuristic), before it will issue a GET for each file\n> request again.\n> \n> So in my option, on (bigger) file is better, as it means fewer request.\n\nI didn't means splitting JavaScript file based on functionality, like\none would do with modules in C, for example.  I was wondering if it \nwould be worth to keep gitweb.js with common utility functions and \nfunctions used in all or nearly all views separate from gitweb-blame.js\nwhich would be loaded and used only for 'blame_incremental' view.\n\nBut I am not sure if it is worth complications with build procedure:\ninstallation and configuration.  With single JavaScript file it is\nenough to have GITWEB_JAVASCRIPT or GITWEB_JS build variable/option,\nand that would be enough.  Not so if we have gitweb-blame.js and \nperhaps other such files.\n\n\nBTW. we can minify JavaScript file during the build (for example using\nJSMin, or one of its derivatives), and e.g. use \"gitweb.min.js\" in\nHTML generated by gitweb.perl / gitweb.cgi.\n\n>>> \n>>> Why is it a single perl file and not splited in many different\n>>> modules?\n>> \n>> 2. Having it all in single file make its easy to install and update.\n>>    Well, it made more sense when only way to configure gitweb was to\n>>    edit gitweb.cgi.  Now building gitweb.cgi is the task for build\n>>    system, and the only thing left is to copy files in correct place\n>>    (I think that there are distribution specific packages which makes\n>>    installing gitweb as easy as \"xxx install gitweb\").\n> \n> Yes, there are gitweb packages, which automaticially server\n> repositories under a specific path (eg. /srv/git). For such packages,\n> the js layout is irrelavant.\n> \n> When manually installing, copying only one javascript file simplifies\n> the deployment.\n\nOTOH if we split Perl source of gitweb (it is second in size only to\ngitk; fourth in size git-gui got already split into smaller modules)\nthen dealing with multiple JavaScript wouldn't be the problem in build.\n\n> \n>> 3. You would have to decide _how_ to split it into many different \n>>    modules.  Do you know any good examples?\n> \n> Javascript uses on global namespace. If it is one file, its implicitly\n> clear, that everything (functions, variables) are in one scope.  If\n> you split it into multiple files, you have to remember, what the other\n> files contain.\n\nTrue.  You can however use single global object and store what would\nbe global variables as properties (fields) of such object...\n\nBesides I'm not advocating splitting into many small files.  Only that\nmuch files as really necessary.\n\n-- \nJakub Narebski\nPoland\n"}]}