{"thread":{"id":"20808","subject":"[PATCH 0/5] gitweb: Incremental blame series (1 Sep 09)","startedAt":"2009-09-01T11:39:15Z","lastAt":"2009-11-12T09:22:39Z","messageCount":12,"participants":["Jakub Narebski","Petr Baudis","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":5},"messages":[{"id":"122234","messageId":"1251805160-5303-1-git-send-email-jnareb@gmail.com","threadId":"20808","inReplyTo":null,"subject":"[PATCH 0/5] gitweb: Incremental blame series (1 Sep 09)","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2009-09-01T11:39:15Z","receivedAt":"2009-09-01T11:39:15Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"This series is meant to replace the 'jn/gitweb-blame' topic branch\n(it is merged into 'pu', b08ae28).\n\nJakub Narebski (5):\n  gitweb: Add optional \"time to generate page\" info in footer\n  gitweb: Incremental blame (using JavaScript)\n  gitweb: Colorize 'blame_incremental' view during processing\n  gitweb: Create links leading to 'blame_incremental' using JavaScript\n  gitweb: Minify gitweb.js if JSMIN is defined\n\nThe \"time to generate page\" info got its own style, and don't looks\nbolted on now.  Incremental blame patch is no longer marked as proof\nof concept, or as a work in progress; I think it is mature enough.\n\nLast two patches (creating links and JSMIN) are in active development,\nand should be considered work in progress (or even proof of concept\ncode).\n\nThis series has 3-color colorizing of 'blame_incremental' view is put\ninto separate patch.  JavaScript code used to create links leading to\n'blame_incremental' view is now put in gitweb.js, which is meant to\ncontain all JavaScript code used by Git.\n\nThe last patch (modifying Makefile) was added because of concern that\nheavily commented gitweb.js could be a burden on server.\n\n Makefile           |   26 ++-\n git-instaweb.sh    |    7 +\n gitweb/README      |    4 +\n gitweb/gitweb.css  |   23 ++\n gitweb/gitweb.js   |  862 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n gitweb/gitweb.perl |  311 ++++++++++++++------\n 6 files changed, 1146 insertions(+), 87 deletions(-)\n create mode 100644 gitweb/gitweb.js\n\n-- \nJakub Narebski\nPoland\n"},{"id":"122235","messageId":"1251805160-5303-2-git-send-email-jnareb@gmail.com","threadId":"20808","inReplyTo":"1251805160-5303-1-git-send-email-jnareb@gmail.com","subject":"[PATCHv2 1/5] gitweb: Add optional \"time to generate page\" info in footer","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2009-09-01T11:39:16Z","receivedAt":"2009-09-01T11:39:16Z","isPatch":false,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Add \"This page took XXX seconds and Y git commands to generate\"\nto page footer, if global feature 'timed' is enabled (disabled\nby default).  Requires Time::HiRes installed for high precision\n'wallclock' time.\n\nNote that Time::HiRes is being required unconditionally of 'timed'\nfeature, because setting $t0 variable should be fairly early to have\nrunning time of the whole script.  If Time::HiRes module was required\nonly if 'timed' feature is enabled, the earliest place where starting\ntime ($t0) could be calculated would be after reading gitweb config,\nmaking \"time to generate page\" info inaccurate.\n\nThis code is based on example code by Petr 'Pasky' Baudis.\n\nSigned-off-by: Jakub Narebski <jnareb@gmail.com>\n---\nThis version adds custom formatting for \"time to generate page\"\ninformation in the page footer, instead of reusing 'page_footer'\nstyle.  It is now clearly separated from the gitweb footer proper.\n\nWhile at it the ids were changed from \"generate_info\" etc. to\n\"generating_info\" etc.\n\nThere is still a question whether Time::HiRes should be require'd\nunconditionally, but otherwise this patch is out of RFC stage.\n\n\nSidenote: I have noticed that gitweb's CSS rules are unnecessary\nstrict, making rendering slower because of extra non needed checks\n(the rule is that selectors should be as simple as possible).\n\n gitweb/gitweb.css  |    7 +++++++\n gitweb/gitweb.perl |   29 +++++++++++++++++++++++++++++\n 2 files changed, 36 insertions(+), 0 deletions(-)\n\ndiff --git a/gitweb/gitweb.css b/gitweb/gitweb.css\nindex b59fcaf..00e2e4c 100644\n--- a/gitweb/gitweb.css\n+++ b/gitweb/gitweb.css\n@@ -75,6 +75,13 @@ div.page_footer_text {\n \tfont-style: italic;\n }\n \n+div#generating_info {\n+\tmargin: 4px;\n+\tfont-size: smaller;\n+\ttext-align: center;\n+\tcolor: #505050;\n+}\n+\n div.page_body {\n \tpadding: 8px;\n \tfont-family: monospace;\ndiff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\nindex ff2d42a..08d410d 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@@ -404,6 +410,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@@ -518,6 +531,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@@ -1969,6 +1983,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@@ -3218,6 +3233,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=\\\"generating_info\\\">\\n\";\n+\t\tprint 'This page took '.\n+\t\t      '<span id=\"generating_time\" class=\"time_span\">'.\n+\t\t      Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]).\n+\t\t      ' seconds </span>'.\n+\t\t      ' and '.\n+\t\t      '<span id=\"generating_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":"122239","messageId":"1251805160-5303-3-git-send-email-jnareb@gmail.com","threadId":"20808","inReplyTo":"1251805160-5303-2-git-send-email-jnareb@gmail.com","subject":"[PATCHv5 2/5] gitweb: Incremental blame (using JavaScript)","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2009-09-01T11:39:17Z","receivedAt":"2009-09-01T11:39:17Z","isPatch":false,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Add 'blame_incremental' view, which uses \"git blame --incremental\"\nand JavaScript (Ajax), where 'blame' use \"git blame --porcelain\".\n\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 gitweb.js\n* startBlame() opens XMLHttpRequest connection to 'blame_data' view,\n  which in turn calls \"git blame --incremental\" for a file, and\n  streams output of git-blame to JavaScript (gitweb.js)\n* XMLHttpRequest event handler updates line info in blame view as soon\n  as it gets data from 'blame_data' (from server), and it also updates\n  progress info\n* when 'blame_data' ends, and gitweb.js finishes updating line info,\n  it fixes colors to match (as far as possible) ordinary 'blame' view,\n  and updates information about how long it took to generate page.\n\nGitweb deals with streamed 'blame_data' server errors by displaying\nthem in the progress info area (just in case).\n\nThe 'blame_incremental' view tries to be equivalent to 'blame' action;\nthere are however a few differences in output between 'blame' and\n'blame_incremental' view:\n* 'blame_incremental' always used query form for this part of link(s)\n  which is generated by JavaScript code.  The difference is visible\n  if we use path_info link (pass some or all arguments in path_info).\n  Changing this would require implementing something akin to href()\n  subroutine from gitweb.perl in JavaScript (in gitweb.js).\n* 'blame_incremental' always uses \"rowspan\" attribute, even if\n  rowspan=\"1\".  This simplifies code, and is not visible to user.\n* The progress bar and progress info are still there even after\n  JavaScript part of 'blame_incremental' finishes work.\n\nNote that currently no link generated by gitweb leads to this new\nview.\n\n\nThis code is based on patch by Petr Baudis <pasky@suse.cz> patch, which\nin turn was tweaked up version of Fredrik Kuivinen <frekui@gmail.com>'s\nproof of concept patch.\n\nThis patch adds GITWEB_JS compile configuration option, and modifies\ngit-instaweb.sh to take gitweb.js into account.  The code for\ngit-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---\nReferences:\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. My earlier patches\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   http://thread.gmane.org/gmane.comp.version-control.git/123957/focus=123968\n   http://thread.gmane.org/gmane.comp.version-control.git/125096/focus=125717\n\nChanges compared to last version (v4):\n* The file with JavaScript code for 'blame_incremental' got renamed\n  from 'blame.js' to 'gitweb.js' -- it is now meant to contain all\n  JavaScript code, as per Google and Yahoo! guidelines (there should\n  be one file with JavaScript code, to be cached, as JavaScript\n  loading is blocking).\n* Move coloring rows duting 'blame_data' run to a separate commit.\n* The debug(str) function and its use (even commented out) was removed;\n  the code is now meant to be in production, and should have leftover\n  debugging statements.\n* spacePad(input, width) function, which pads with '&nbsp;' got\n  replaced by more generic padLeftStr(input, width, padstr) function.\n* Added information about global variables used by function via \n  @globals annotation (non in JSDoc standard).\n* Make prevDataLength and nextReadPos properties (fields) of\n  XMLHttpRequest object, instead of being global variables.\n* Pass xhr as parameter to handleError and responseLoaded functions\n  (it shadows xhr as a global variable).\n* Move setting onreadystatechange handler before xhr.open()\n* Add information about GITWEB_JS Makefile configuration variable to\n  gitweb/README\n* Remove unnecessary 'reminder' comments in gitweb.js\n\nTODO for future commits:\n* Use W3C Progress Events (progress, error, load) in addition to\n  currently used readystatechange event\n    http://www.w3.org/TR/progress-events/\n    http://www.w3.org/TR/XMLHttpRequest2/\n    http://www.nczonline.net/blog/2009/07/09/firefox-35firebug-xmlhttprequest-and-readystatechange-bug/\n  if XMLHttpResponse supports it.\n* handleResponse is used both as onreadystatechange and pollTimer;\n  if onreadystatechange works for partial responses we can turn off\n  the timer.\n* JavaScript is single theraded, therefore there is no need for\n  critical section; remove inProgress global variable (flag), and\n  busy-read while loop.\n* Remove (fade out) progress bar and progress info after blame\n  incremental finished run, if possible with large amount of code to\n  deal with browser incompatibilities.\n\nTODO and possible extensions:\n* Profile gitweb.js using YUI Profiler (or other JavaScript profile tool)\n    http://developer.yahoo.com/yui/profiler/\n  to check whether performance improvements described in articles\n  on NCZOnline blog by Nicholas C. Zakas are required, and would help\n    http://www.nczonline.net/blog/\n* Get rid of global variables, if possible.\n* Instead of running startBlame, put it in window.onload handler.\n\nRoads not taken (perhaps that should be part of commit message?):\n* Move most (or all) of \"git blame --incremental\" output parsing to\n  server side, and instead of sending direct output in text/plain,\n  send processed data in JSON format, e.g.\n\n    {\"commit\": {\n       \"sha1\": \"e83c5163316f89bfbde7d9ab23ca2e25604af290\",\n       \"info\": \"Kay Sievers, 2005-08-07 21:49:46 +0200\",\n       \"author-initials\": \"KS\",\n       ...\n     },\n     \"src-line\": 13,\n     \"dst-line\": 16,\n     \"numlines\": 3,\n     \"filename\": \"README\"\n     }\n\n  (line wrapping added for readibility).  This would require however\n  taking care on Perl side to send properly formatted JSON, and on\n  JavaScript side including json2.js code to read JSON in gitweb.js\n  (unless we rely on eval).\n* Using some lightweight JavaScript library (framework), like jQuery,\n  Prototype, ExtJS, MooTools, etc.  One one hand side this means not\n  having to worry about browser incompatibilities as this would be\n  taken care of by library; on the other hand side we want gitweb to\n  have as few dependences as possible.\n* Use 'multipart/x-mixed-replace': first it is not standard, second\n  I don't think this would be a good solution for our problem.\n\n Makefile           |    6 +-\n git-instaweb.sh    |    7 +\n gitweb/README      |    4 +\n gitweb/gitweb.css  |   11 +\n gitweb/gitweb.js   |  726 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n gitweb/gitweb.perl |  272 ++++++++++++++------\n 6 files changed, 939 insertions(+), 87 deletions(-)\n create mode 100644 gitweb/gitweb.js\n\ndiff --git a/Makefile b/Makefile\nindex a614347..407b35c 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -261,6 +261,7 @@ GITWEB_HOMETEXT = indextext.html\n GITWEB_CSS = gitweb.css\n GITWEB_LOGO = git-logo.png\n GITWEB_FAVICON = git-favicon.png\n+GITWEB_JS = gitweb.js\n GITWEB_SITE_HEADER =\n GITWEB_SITE_FOOTER =\n \n@@ -1393,13 +1394,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_JS++|$(GITWEB_JS)|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/gitweb.js\n \t$(QUIET_GEN)$(RM) $@ $@+ && \\\n \tsed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \\\n \t    -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \\\n@@ -1408,6 +1410,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_JS@@/r gitweb/gitweb.js' \\\n+\t    -e '/@@GITWEB_JS@@/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 d96eddb..701c0d7 100755\n--- a/git-instaweb.sh\n+++ b/git-instaweb.sh\n@@ -375,8 +375,15 @@ gitweb_css () {\n EOFGITWEB\n }\n \n+gitweb_js () {\n+\tcat > \"$1\" <<\\EOFGITWEB\n+@@GITWEB_JS@@\n+EOFGITWEB\n+}\n+\n gitweb_cgi \"$GIT_DIR/gitweb/gitweb.cgi\"\n gitweb_css \"$GIT_DIR/gitweb/gitweb.css\"\n+gitweb_js  \"$GIT_DIR/gitweb/gitweb.js\"\n \n case \"$httpd\" in\n *lighttpd*)\ndiff --git a/gitweb/README b/gitweb/README\nindex 66c6a93..b69b0e5 100644\n--- a/gitweb/README\n+++ b/gitweb/README\n@@ -92,6 +92,10 @@ You can specify the following configuration variables when building GIT:\n    web browsers that support favicons (website icons) may display them\n    in the browser's URL bar and next to site name in bookmarks).  Relative\n    to base URI of gitweb.  [Default: git-favicon.png]\n+ * GITWEB_JS\n+   Points to the localtion where you put gitweb.js on your web server\n+   (or to be more generic URI of JavaScript code used by gitweb).\n+   Relative to base URI of gitweb.  [Default: gitweb.js]\n  * GITWEB_CONFIG\n    This Perl file will be loaded using 'do' and can be used to override any\n    of the options above as well as some other options -- see the \"Runtime\ndiff --git a/gitweb/gitweb.css b/gitweb/gitweb.css\nindex 00e2e4c..69ef119 100644\n--- a/gitweb/gitweb.css\n+++ b/gitweb/gitweb.css\n@@ -353,6 +353,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.js b/gitweb/gitweb.js\nnew file mode 100644\nindex 0000000..c8411e7\n--- /dev/null\n+++ b/gitweb/gitweb.js\n@@ -0,0 +1,726 @@\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+ * @fileOverview JavaScript code for gitweb (git web interface).\n+ * @license GPLv2 or later\n+ */\n+\n+/*\n+ * This code uses DOM methods instead of (nonstandard) innerHTML\n+ * to modify page.\n+ *\n+ * innerHTML is non-standard IE extension, though supported by most\n+ * browsers; however Firefox up to version 1.5 didn't implement it in\n+ * a strict mode (application/xml+xhtml mimetype).\n+ *\n+ * Also my simple benchmarks show that using elem.firstChild.data =\n+ * 'content' is slightly faster than elem.innerHTML = 'content'.  It\n+ * is however more fragile (text element fragment must exists), and\n+ * less feature-rich (we cannot add HTML).\n+ *\n+ * Note that DOM 2 HTML is preferred over generic DOM 2 Core; the\n+ * equivalent using DOM 2 Core is usually shown in comments.\n+ */\n+\n+\n+/* ============================================================ */\n+/* generic utility functions */\n+\n+\n+/**\n+ * pad number N with nonbreakable spaces on the left, to WIDTH characters\n+ * example: padLeftStr(12, 3, '&nbsp;') == '&nbsp;12'\n+ *          ('&nbsp;' is nonbreakable space)\n+ *\n+ * @param {Number|String} input: number to pad\n+ * @param {Number} width: visible width of output\n+ * @param {String} str: string to prefix to string, e.g. '&nbsp;'\n+ * @returns {String} INPUT prefixed with (WIDTH - INPUT.length) x STR\n+ */\n+function padLeftStr(input, width, str) {\n+\tvar prefix = '';\n+\n+\twidth -= input.toString().length;\n+\twhile (width > 1) {\n+\t\tprefix += str;\n+\t\twidth--;\n+\t}\n+\treturn prefix + input;\n+}\n+\n+/**\n+ * Pad INPUT on the left to SIZE width, using given padding character CH,\n+ * for example padLeft('a', 3, '_') is '__a'.\n+ *\n+ * @param {String} input: input value converted to string.\n+ * @param {Number} width: desired length of output.\n+ * @param {String} ch: single character to prefix to string.\n+ *\n+ * @returns {String} Modified string, at least SIZE length.\n+ */\n+function padLeft(input, width, ch) {\n+\tvar s = input + \"\";\n+\twhile (s.length < width) {\n+\t\ts = ch + s;\n+\t}\n+\treturn s;\n+}\n+\n+/**\n+ * Create XMLHttpRequest object in cross-browser way\n+ * @returns XMLHttpRequest object, or null\n+ */\n+function createRequestObject() {\n+\ttry {\n+\t\treturn new XMLHttpRequest();\n+\t} catch (e) {}\n+\ttry {\n+\t\treturn window.createRequest();\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+\treturn null;\n+}\n+\n+/* ============================================================ */\n+/* utility/helper functions (and variables) */\n+\n+var xhr;        // XMLHttpRequest object\n+var projectUrl; // partial query + separator ('?' or ';')\n+\n+// 'commits' is an associative map. It maps SHA1s to Commit objects.\n+var commits = {};\n+\n+/**\n+ * constructor for Commit objects, used in 'blame'\n+ * @class Represents a blamed commit\n+ * @param {String} sha1: SHA-1 identifier of a commit\n+ */\n+function Commit(sha1) {\n+\tif (this instanceof Commit) {\n+\t\tthis.sha1 = sha1;\n+\t\tthis.nprevious = 0; /* number of 'previous', effective parents */\n+\t} else {\n+\t\treturn new Commit(sha1);\n+\t}\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+/**\n+ * Detects how many lines does a blamed file have,\n+ * This information is used in progress info\n+ *\n+ * @returns {Number|String} Number of lines in file, or string '...'\n+ */\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+/**\n+ * update progress info and length (width) of progress bar\n+ *\n+ * @globals div_progress_info, div_progress_bar, blamedLines, totalLines\n+ */\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\tdiv_progress_info.firstChild.data  = blamedLines + ' / ' + totalLines +\n+\t\t\t' (' + padLeftStr(percentage, 3, '&nbsp;') + '%)';\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+/**\n+ * write how much it took to generate data, and to run script\n+ *\n+ * @globals t0, t_interval_server, cmds_server\n+ */\n+function writeTimeInterval() {\n+\tvar info_time = document.getElementById('generating_time');\n+\tif (!info_time || !t_interval_server) {\n+\t\treturn;\n+\t}\n+\tvar t1 = new Date();\n+\tinfo_time.firstChild.data += ' + (' +\n+\t\tt_interval_server + ' sec server blame_data / ' +\n+\t\t(t1.getTime() - t0.getTime())/1000 + ' sec client JavaScript)';\n+\n+\tvar info_cmds = document.getElementById('generating_cmd');\n+\tif (!info_time || !cmds_server) {\n+\t\treturn;\n+\t}\n+\tinfo_cmds.firstChild.data += ' + ' + cmds_server;\n+}\n+\n+/**\n+ * show an error message alert to user within page (in prohress info area)\n+ * @param {String} str: plain text error message (no HTML)\n+ *\n+ * @globals div_progress_info\n+ */\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.firstChild.data = str;\n+\t}\n+}\n+\n+/* ............................................................ */\n+/* coloring rows like 'blame' after 'blame_data' finishes */\n+\n+/**\n+ * returns true if given row element (tr) is first in commit group\n+ * to be used only after 'blame_data' finishes (after processing)\n+ *\n+ * @param {HTMLElement} tr: table row\n+ * @returns {Boolean} true if TR is first in commit group\n+ */\n+function isStartOfGroup(tr) {\n+\treturn tr.firstChild.className === 'sha1';\n+}\n+\n+var colorRe = /(?:light|dark)/;\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+ *\n+ * @globals colorRe\n+ */\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; start from 1\n+\t//while ((tr = table.rows[linenum])) { // <- it is slower\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+/**\n+ * used to extract hours and minutes from timezone info, e.g '-0900'\n+ * @constant\n+ */\n+var tzRe = /^([+-][0-9][0-9])([0-9][0-9])$/;\n+\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+ *\n+ * @param {Number} epoch: seconds since '00:00:00 1970-01-01 UTC'\n+ * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM'\n+ * @returns {String} date in local time in iso-8601 like format\n+ *\n+ * @globals tzRe\n+ */\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\tpadLeft(localDate.getUTCMonth()+1, 2, '0') + '-' +\n+\t\tpadLeft(localDate.getUTCDate(),    2, '0');\n+\tvar localTimeStr = // e.g. '21:49:46'\n+\t\tpadLeft(localDate.getUTCHours(),   2, '0') + ':' +\n+\t\tpadLeft(localDate.getUTCMinutes(), 2, '0') + ':' +\n+\t\tpadLeft(localDate.getUTCSeconds(), 2, '0');\n+\n+\treturn localDateStr + ' ' + localTimeStr + ' ' + timezoneInfo;\n+}\n+\n+/* ............................................................ */\n+/* unquoting/unescaping filenames */\n+\n+/**#@+\n+ * @constant\n+ */\n+var escCodeRe = /\\\\([^0-7]|[0-7]{1,3})/g;\n+var octEscRe = /^[0-7]{1,3}$/;\n+var maybeQuotedRe = /^\\\"(.*)\\\"$/;\n+/**#@-*/\n+\n+/**\n+ * unquote maybe git-quoted filename\n+ * e.g. 'aa' -> 'aa', '\"a\\ta\"' -> 'a\ta'\n+ *\n+ * @param {String} str: git-quoted string\n+ * @returns {String} Unquoted and unescaped string\n+ *\n+ * @globals escCodeRe, octEscRe, maybeQuotedRe\n+ */\n+function unquote(str) {\n+\tfunction unq(seq) {\n+\t\tvar es = {\n+\t\t\t// character escape codes, aka escape sequences (from C)\n+\t\t\t// replacements are to some extent JavaScript specific\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(octEscRe) !== -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(maybeQuotedRe);\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+/**\n+ * Function called for each blame entry, as soon as it finishes.\n+ * It updates page via DOM manipulation, adding sha1 info, etc.\n+ *\n+ * @param {Commit} commit: blamed commit\n+ * @param {Object} group: object representing group of lines,\n+ *                        which blame the same commit (blame entry)\n+ *\n+ * @globals blamedLines\n+ */\n+function handleLine(commit, group) {\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 = group.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// loop over lines in commit group\n+\tfor (var i = 0; i < group.numlines; i++, resline++) {\n+\t\tvar tr = document.getElementById('l'+resline);\n+\t\tif (!tr) {\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 = 'light'; // or tr.className\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 = group.numlines;\n+\n+\t\t\ta_sha1.href = projectUrl + 'a=commit;h=' + commit.sha1;\n+\t\t\ta_sha1.firstChild.data = commit.sha1.substr(0, 8);\n+\t\t\tif (group.numlines >= 2) {\n+\t\t\t\tvar fragment = document.createDocumentFragment();\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\tvar elem = fragment || td_sha1;\n+\t\t\t\t\telem.appendChild(br);\n+\t\t\t\t\telem.appendChild(text);\n+\t\t\t\t\tif (fragment) {\n+\t\t\t\t\t\ttd_sha1.appendChild(fragment);\n+\t\t\t\t\t}\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' + (group.srcline + i);\n+\n+\t\tblamedLines++;\n+\n+\t\t//updateProgressInfo();\n+\t}\n+}\n+\n+// ----------------------------------------------------------------------\n+\n+var inProgress = false;   // are we processing response\n+\n+/**#@+\n+ * @constant\n+ */\n+var sha1Re = /^([0-9a-f]{40}) ([0-9]+) ([0-9]+) ([0-9]+)/;\n+var infoRe = /^([a-z-]+) ?(.*)/;\n+var endRe  = /^END ?([^ ]*) ?(.*)/;\n+/**@-*/\n+\n+var curCommit = new Commit();\n+var curGroup  = {};\n+\n+var pollTimer = null;\n+\n+/**\n+ * Parse output from 'git blame --incremental [...]', received via\n+ * XMLHttpRequest from server (blamedataUrl), and call handleLine\n+ * (which updates page) as soon as blame entry is completed.\n+ *\n+ * @param {String[]} lines: new complete lines from blamedata server\n+ *\n+ * @globals commits, curCommit, curGroup, t_interval_server, cmds_server\n+ * @globals sha1Re, infoRe, endRe\n+ */\n+function processBlameLines(lines) {\n+\tvar match;\n+\n+\tfor (var i = 0, len = lines.length; i < len; i++) {\n+\n+\t\tif ((match = sha1Re.exec(lines[i]))) {\n+\t\t\tvar sha1 = match[1];\n+\t\t\tvar srcline  = parseInt(match[2], 10);\n+\t\t\tvar resline  = parseInt(match[3], 10);\n+\t\t\tvar numlines = parseInt(match[4], 10);\n+\n+\t\t\tvar c = commits[sha1];\n+\t\t\tif (!c) {\n+\t\t\t\tc = new Commit(sha1);\n+\t\t\t\tcommits[sha1] = c;\n+\t\t\t}\n+\t\t\tcurCommit = c;\n+\n+\t\t\tcurGroup.srcline = srcline;\n+\t\t\tcurGroup.resline = resline;\n+\t\t\tcurGroup.numlines = numlines;\n+\n+\t\t} else if ((match = infoRe.exec(lines[i]))) {\n+\t\t\tvar info = match[1];\n+\t\t\tvar data = match[2];\n+\t\t\tswitch (info) {\n+\t\t\tcase 'filename':\n+\t\t\t\tcurCommit.filename = unquote(data);\n+\t\t\t\t// 'filename' information terminates the entry\n+\t\t\t\thandleLine(curCommit, curGroup);\n+\t\t\t\tupdateProgressInfo();\n+\t\t\t\tbreak;\n+\t\t\tcase 'author':\n+\t\t\t\tcurCommit.author = data;\n+\t\t\t\tbreak;\n+\t\t\tcase 'author-time':\n+\t\t\t\tcurCommit.authorTime = parseInt(data, 10);\n+\t\t\t\tbreak;\n+\t\t\tcase 'author-tz':\n+\t\t\t\tcurCommit.authorTimezone = data;\n+\t\t\t\tbreak;\n+\t\t\tcase 'previous':\n+\t\t\t\tcurCommit.nprevious++;\n+\t\t\t\t// store only first 'previous' header\n+\t\t\t\tif (!'previous' in curCommit) {\n+\t\t\t\t\tvar parts = data.split(' ', 2);\n+\t\t\t\t\tcurCommit.previous    = parts[0];\n+\t\t\t\t\tcurCommit.file_parent = unquote(parts[1]);\n+\t\t\t\t}\n+\t\t\t\tbreak;\n+\t\t\tcase 'boundary':\n+\t\t\t\tcurCommit.boundary = true;\n+\t\t\t\tbreak;\n+\t\t\t} // end switch\n+\n+\t\t} else if ((match = endRe.exec(lines[i]))) {\n+\t\t\tt_interval_server = match[1];\n+\t\t\tcmds_server = match[2];\n+\n+\t\t} else if (lines[i] !== '') {\n+\t\t\t// malformed line\n+\n+\t\t} // end if (match)\n+\n+\t} // end for (lines)\n+}\n+\n+/**\n+ * Process new data and return pointer to end of processed part\n+ *\n+ * @param {String} unprocessed: new data (from nextReadPos)\n+ * @param {Number} nextReadPos: end of last processed data\n+ * @return {Number} end of processed data (new value for nextReadPos)\n+ */\n+function processData(unprocessed, nextReadPos) {\n+\tvar lastLineEnd = unprocessed.lastIndexOf('\\n');\n+\tif (lastLineEnd !== -1) {\n+\t\tvar lines = unprocessed.substring(0, lastLineEnd).split('\\n');\n+\t\tnextReadPos += lastLineEnd + 1 /* 1 == '\\n'.length */;\n+\n+\t\tprocessBlameLines(lines);\n+\t} // end if\n+\n+\treturn nextReadPos;\n+}\n+\n+/**\n+ * Handle XMLHttpRequest errors\n+ *\n+ * @param {XMLHttpRequest} xhr: XMLHttpRequest object\n+ *\n+ * @globals pollTimer, commits, inProgress\n+ */\n+function handleError(xhr) {\n+\terrorInfo('Server error: ' +\n+\t\txhr.status + ' - ' + (xhr.statusText || 'Error contacting server'));\n+\n+\tclearInterval(pollTimer);\n+\tcommits = {}; // free memory\n+\n+\tinProgress = false;\n+}\n+\n+/**\n+ * Called after XMLHttpRequest finishes (loads)\n+ *\n+ * @param {XMLHttpRequest} xhr: XMLHttpRequest object (unused)\n+ *\n+ * @globals pollTimer, commits, inProgress\n+ */\n+function responseLoaded(xhr) {\n+\tclearInterval(pollTimer);\n+\n+\tfixColorsAndGroups();\n+\twriteTimeInterval();\n+\tcommits = {}; // free memory\n+\n+\tinProgress = false;\n+}\n+\n+/**\n+ * handler for XMLHttpRequest onreadystatechange event\n+ * @see startBlame\n+ *\n+ * @globals xhr, inProgress\n+ */\n+function handleResponse() {\n+\n+\t/*\n+\t * xhr.readyState\n+\t *\n+\t *  Value  Constant (W3C)    Description\n+\t *  -------------------------------------------------------------------\n+\t *  0      UNSENT            open() has not been called yet.\n+\t *  1      OPENED            send() has not been called yet.\n+\t *  2      HEADERS_RECEIVED  send() has been called, and headers\n+\t *                           and status are available.\n+\t *  3      LOADING           Downloading; responseText holds partial data.\n+\t *  4      DONE              The operation is complete.\n+\t */\n+\n+\tif (xhr.readyState !== 4 && xhr.readyState !== 3) {\n+\t\treturn;\n+\t}\n+\n+\t// the server returned error\n+\tif (xhr.readyState === 3 && xhr.status !== 200) {\n+\t\treturn;\n+\t}\n+\tif (xhr.readyState === 4 && xhr.status !== 200) {\n+\t\thandleError(xhr);\n+\t\treturn;\n+\t}\n+\n+\t// In konqueror xhr.responseText is sometimes null here...\n+\tif (xhr.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+\t// extract new whole (complete) lines, and process them\n+\twhile (xhr.prevDataLength !== xhr.responseText.length) {\n+\t\tif (xhr.readyState === 4 &&\n+\t\t    xhr.prevDataLength === xhr.responseText.length) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\txhr.prevDataLength = xhr.responseText.length;\n+\t\tvar unprocessed = xhr.responseText.substring(xhr.nextReadPos);\n+\t\txhr.nextReadPos = processData(unprocessed, xhr.nextReadPos);\n+\t} // end while\n+\n+\t// did we finish work?\n+\tif (xhr.readyState === 4 &&\n+\t    xhr.prevDataLength === xhr.responseText.length) {\n+\t\tresponseLoaded(xhr);\n+\t}\n+\n+\tinProgress = false;\n+}\n+\n+// ============================================================\n+// ------------------------------------------------------------\n+\n+/**\n+ * Incrementally update line data in blame_incremental view in gitweb.\n+ *\n+ * @param {String} blamedataUrl: URL to server script generating blame data.\n+ * @param {String} bUrl: partial URL to project, used to generate links.\n+ *\n+ * Called from 'blame_incremental' view after loading table with\n+ * file contents, a base for blame view.\n+ *\n+ * @globals xhr, t0, projectUrl, div_progress_bar, totalLines, pollTimer\n+*/\n+function startBlame(blamedataUrl, bUrl) {\n+\n+\txhr = createRequestObject();\n+\tif (!xhr) {\n+\t\terrorInfo('ERROR: XMLHttpRequest not supported');\n+\t\treturn;\n+\t}\n+\n+\tt0 = new Date();\n+\tprojectUrl = bUrl + (bUrl.indexOf('?') === -1 ? '?' : ';');\n+\tif ((div_progress_bar = document.getElementById('progress_bar'))) {\n+\t\t//div_progress_bar.setAttribute('style', 'width: 100%;');\n+\t\tdiv_progress_bar.style.cssText = 'width: 100%;';\n+\t}\n+\ttotalLines = countLines();\n+\tupdateProgressInfo();\n+\n+\t/* add extra properties to xhr object to help processing response */\n+\txhr.prevDataLength = -1;  // used to detect if we have new data\n+\txhr.nextReadPos = 0;      // where unread part of response starts\n+\n+\txhr.onreadystatechange = handleResponse;\n+\t//xhr.onreadystatechange = function () { handleResponse(xhr); };\n+\n+\txhr.open('GET', blamedataUrl);\n+\txhr.setRequestHeader('Accept', 'text/plain');\n+\txhr.send(null);\n+\n+\t// not all browsers call onreadystatechange event on each server flush\n+\t// poll response using timer every second to handle this issue\n+\tpollTimer = setInterval(xhr.onreadystatechange, 1000);\n+}\n+\n+// end of gitweb.js\ndiff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\nindex 08d410d..88c91ff 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 gitweb.js (JavaScript code for gitweb)\n+our $javascript = \"++GITWEB_JS++\";\n \n # URI and label (title) of GIT logo link\n #our $logo_url = \"http://www.kernel.org/pub/software/scm/git/docs/\";\n@@ -575,6 +577,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@@ -4800,7 +4804,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@@ -4822,10 +4828,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@@ -4836,109 +4875,170 @@ 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.\\n Use \".\n+\t\t      $cgi->a({-href => href(action=>'blame',-replay=>1)},\n+\t\t              '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=\"$javascript\"></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() .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":"122238","messageId":"1251805160-5303-4-git-send-email-jnareb@gmail.com","threadId":"20808","inReplyTo":"1251805160-5303-3-git-send-email-jnareb@gmail.com","subject":"[PATCHv1 3/5] gitweb: Colorize 'blame_incremental' view during processing","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2009-09-01T11:39:18Z","receivedAt":"2009-09-01T11:39:18Z","isPatch":false,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"This requires using 3 colors, not only two, to be able to choose color\ndifferent from colors of up to 2 neigbours.\n\ngitweb.js select least used color, if more than one color is possible.\n\nSigned-off-by: Jakub Narebski <jnareb@gmail.com>\n---\nThis was earlier part of previous commit (adding 'blame_incremental'\nview).\n\n gitweb/gitweb.css |    5 ++\n gitweb/gitweb.js  |  108 +++++++++++++++++++++++++++++++++++++++++++++++++++-\n 2 files changed, 110 insertions(+), 3 deletions(-)\n\ndiff --git a/gitweb/gitweb.css b/gitweb/gitweb.css\nindex 69ef119..977a013 100644\n--- a/gitweb/gitweb.css\n+++ b/gitweb/gitweb.css\n@@ -262,6 +262,11 @@ tr.no-previous td.linenr {\n \tfont-weight: bold;\n }\n \n+/* for 'blame_incremental', during processing */\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%;\ndiff --git a/gitweb/gitweb.js b/gitweb/gitweb.js\nindex c8411e7..bf38216 100644\n--- a/gitweb/gitweb.js\n+++ b/gitweb/gitweb.js\n@@ -211,6 +211,101 @@ function errorInfo(str) {\n }\n \n /* ............................................................ */\n+/* coloring rows during blame_data (git blame --incremental) run */\n+\n+/**\n+ * used to extract N from 'colorN', where N is a number,\n+ * @constant\n+ */\n+var colorRe = /\\bcolor([0-9]*)\\b/;\n+\n+/**\n+ * return N if <tr class=\"colorN\">, otherwise return null\n+ * (some browsers require CSS class names to begin with letter)\n+ *\n+ * @param {HTMLElement} tr: table row element to check\n+ * @param {String} tr.className: 'class' attribute of tr element\n+ * @returns {Number|null} N if tr.className == 'colorN', otherwise null\n+ *\n+ * @globals colorRe\n+ */\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+var colorsFreq = [0, 0, 0];\n+/**\n+ * return one of given possible colors (curently least used one)\n+ * example: chooseColorNoFrom(2, 3) returns 2 or 3\n+ *\n+ * @param {Number[]} arguments: one or more numbers\n+ *        assumes that  1 <= arguments[i] <= colorsFreq.length\n+ * @returns {Number} Least used color number from arguments\n+ * @globals colorsFreq\n+ */\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+/**\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+ *\n+ * @param {HTMLElement} tr_prev\n+ * @param {HTMLElement} tr_next\n+ * @returns {Number} color number N such that\n+ * colorN != tr_prev.className && colorN != tr_next.className\n+ */\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 /**\n@@ -224,8 +319,6 @@ function isStartOfGroup(tr) {\n \treturn tr.firstChild.className === 'sha1';\n }\n \n-var colorRe = /(?:light|dark)/;\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@@ -391,6 +484,12 @@ function handleLine(commit, group) {\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+group.numlines))\n+\t);\n+\n \t// loop over lines in commit group\n \tfor (var i = 0; i < group.numlines; i++, resline++) {\n \t\tvar tr = document.getElementById('l'+resline);\n@@ -409,7 +508,10 @@ function handleLine(commit, group) {\n \t\tvar a_linenr = td_sha1.nextSibling.firstChild;\n \n \t\t/* <tr id=\"l123\" class=\"\"> */\n-\t\tvar tr_class = 'light'; // or tr.className\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-- \n1.6.3.3\n"},{"id":"122237","messageId":"1251805160-5303-5-git-send-email-jnareb@gmail.com","threadId":"20808","inReplyTo":"1251805160-5303-4-git-send-email-jnareb@gmail.com","subject":"[PATCHv3/RFC 4/5] gitweb: Create links leading to 'blame_incremental' using JavaScript","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2009-09-01T11:39:19Z","receivedAt":"2009-09-01T11:39:19Z","isPatch":false,"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 linking to 'blame_incremental' view, we use JavaScript\nto convert those links to lead to view utilizing JavaScript, by adding\n'js=1' to link.\n\nCurrently the only action that takes 'js=1' into account is 'blame',\nwhich then acts as if it was called as 'blame_incremental' action.\nPossible enhancement would be to do JavaScript redirect by setting\nwindow.location instead of modifying $format and $action in\ngit_blame_common() subroutine.\n\nThe only JavaScript-aware/using view is currently 'blame_incremental'.\nWhile at it move reading JavaScript to git_footer_html() subroutine.\nNote that in this view we do not add 'js=1' currently (even though\nperhaps we should; note that for consistency we should also add 'js=1'\nin links added by JavaScript part of 'blame_incremental').\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.  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 in RFC stage.\n\nCurrently fixLinks() is not invoked for 'blame_incremental', but links\ngenerated by startBlame (or to be more exact by event handler in\ngitweb.js) lead to 'blame_incremental' directly.  Also the list of\n\"JavaScript-aware actions\" is currently hardcoded to\n'blame_incremental' (and relies on 'blame' + 'js=1' to change\n$action).\n\nAlso fixLinks() adds 'js=1' to too many links, including $home_link,\nand links to feeds (where JavaScript is not supported anyway).\nAlternate solution would be to mark links which lead to action which\nhas JavaScript-requiring alternative, either marking it via 'class'\nattribute, or providing for example 'alt_href' or 'jshref' attribute\nwith link to JavaScript-requiring variant.\n\n\nDifferences from previous version (v2):\n* fixLinks() function is put in gitweb.js together with all JavaScript\n  code required for 'blame_incremental' view, instead of being defined\n  inside <script> element.\n* Running startBlame() was moved to git_footer_html, instead of being\n  in git_blame_common\n* The link to plain 'blame' view (which do not require JavaScript\n  support enabled in web browser) inside <noscript> element is marked\n  with 'js=0'.\n* fixLinks() now doesn't add 'js=1' if links ends with '[?;]js=[01]'\n  (so that e.g. 'js=0' means link to view which do not require\n  JavaScript).\n* performance: calculate number of elements in a list before loop\n\n\nTODO for future commits:\n* Use 'click' event to change links to jave 'js=1' parameter appended;\n  this way we would check if JavaScript is enabled at the moment of\n  following (clicking) link, not at the moment of loading the page.\n\n  Unfortunately adding event listeners (much better solution than\n  providing/adding 'onclick' attribute) is different in different\n  browsers.  Some kind of wrapper around browser incompatibilities\n  would be required.\n\n gitweb/gitweb.js   |   34 ++++++++++++++++++++++++++++++++++\n gitweb/gitweb.perl |   28 +++++++++++++++++++---------\n 2 files changed, 53 insertions(+), 9 deletions(-)\n\ndiff --git a/gitweb/gitweb.js b/gitweb/gitweb.js\nindex bf38216..66fd64e 100644\n--- a/gitweb/gitweb.js\n+++ b/gitweb/gitweb.js\n@@ -7,6 +7,39 @@\n  * @license GPLv2 or later\n  */\n \n+/* ============================================================ */\n+/* functions for generic gitweb actions and views */\n+\n+/**\n+ * used to check if link has 'js' query parameter already (at end),\n+ * and other reasons to not add 'js=1' param at the end of link\n+ * @constant\n+ */\n+var jsExceptionsRe = /[;?]js=[01]$/;\n+\n+/**\n+ * Add '?js=1' or ';js=1' to the end of every link in the document\n+ * that doesn't have 'js' query parameter set already.\n+ *\n+ * Links with 'js=1' lead to JavaScript version of given action, if it\n+ * exists (currently there is only 'blame_incremental' for 'blame')\n+ *\n+ * @globals jsExceptionsRe\n+ */\n+function fixLinks() {\n+\tvar allLinks = document.getElementsByTagName(\"a\") || document.links;\n+\tfor (var i = 0, len = allLinks.length; i < len; i++) {\n+\t\tvar link = allLinks[i];\n+\t\tif (!jsExceptionsRe.test(link)) { // =~ /[;?]js=[01]$/;\n+\t\t\tlink.href +=\n+\t\t\t\t(link.href.indexOf('?') === -1 ? '?' : ';') + 'js=1';\n+\t\t}\n+\t}\n+}\n+\n+\n+/* ============================================================ */\n+\n /*\n  * This code uses DOM methods instead of (nonstandard) innerHTML\n  * to modify page.\n@@ -89,6 +122,7 @@ function createRequestObject() {\n \treturn null;\n }\n \n+\n /* ============================================================ */\n /* utility/helper functions (and variables) */\n \ndiff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\nindex 88c91ff..0adfd3f 100755\n--- a/gitweb/gitweb.perl\n+++ b/gitweb/gitweb.perl\n@@ -571,6 +571,8 @@ our @cgi_param_mapping = (\n \tsnapshot_format => \"sf\",\n \textra_options => \"opt\",\n \tsearch_use_regexp => \"sr\",\n+\t# this must be last entry (for manipulation from JavaScript)\n+\tjavascript => \"js\"\n );\n our %cgi_param_mapping = @cgi_param_mapping;\n \n@@ -3255,6 +3257,18 @@ sub git_footer_html {\n \t\tinsert_file($site_footer);\n \t}\n \n+\tprint qq!<script type=\"text/javascript\" src=\"$javascript\"></script>\\n!;\n+\tif ($action eq 'blame_incremental') {\n+\t\tprint qq!<script type=\"text/javascript\">\\n!.\n+\t\t      qq!startBlame(\"!. href(action=>\"blame_data\", -replay=>1) .qq!\",\\n!.\n+\t\t      qq!           \"!. href() .qq!\");\\n!.\n+\t\t      qq!</script>\\n!;\n+\t} else {\n+\t\tprint qq!<script type=\"text/javascript\">\\n!.\n+\t\t      qq!window.onload = fixLinks;\\n!.\n+\t\t      qq!</script>\\n!;\n+\t}\n+\n \tprint \"</body>\\n\" .\n \t      \"</html>\";\n }\n@@ -4806,6 +4820,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@@ -4885,7 +4903,7 @@ sub git_blame_common {\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.\\n Use \".\n-\t\t      $cgi->a({-href => href(action=>'blame',-replay=>1)},\n+\t\t      $cgi->a({-href => href(action=>'blame',javascript=>0,-replay=>1)},\n \t\t              'this page').\n \t\t      \" instead.\\n\".\n \t\t      \"</b></center></div>\\n</noscript>\\n\";\n@@ -5016,14 +5034,6 @@ sub git_blame_common {\n \tclose $fd\n \t\tor print \"Reading blob failed\\n\";\n \n-\tif ($format eq 'incremental') {\n-\t\tprint qq!<script type=\"text/javascript\" src=\"$javascript\"></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() .qq!\");\\n!.\n-\t\t      qq!</script>\\n!;\n-\t}\n-\n \tgit_footer_html();\n }\n \n-- \n1.6.3.3\n"},{"id":"122236","messageId":"1251805160-5303-6-git-send-email-jnareb@gmail.com","threadId":"20808","inReplyTo":"1251805160-5303-5-git-send-email-jnareb@gmail.com","subject":"[PATCHv1/RFC 5/5] gitweb: Minify gitweb.js if JSMIN is defined","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2009-09-01T11:39:20Z","receivedAt":"2009-09-01T11:39:20Z","isPatch":false,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"It requires that $JSMIN command can function as a filter.\n\nSigned-off-by: Jakub Narebski <jnareb@gmail.com>\n---\nThis patch is new in series.  Comments welcome.\n\nI have not tested it extensively, but JSMIN=cat seems to work\ncorrectly.\n\n Makefile |   20 ++++++++++++++++++++\n 1 files changed, 20 insertions(+), 0 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex 407b35c..f149a36 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -194,6 +194,9 @@ all::\n # memory allocators with the nedmalloc allocator written by Niall Douglas.\n #\n # Define NO_REGEX if you have no or inferior regex support in your C library.\n+#\n+# Define JSMIN to point to JavaScript minifier that functions as\n+# a filter to have gitweb.js minified.\n \n GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE\n \t@$(SHELL_PATH) ./GIT-VERSION-GEN\n@@ -246,6 +249,9 @@ lib = lib\n # DESTDIR=\n pathsep = :\n \n+# JavaScript minifier invocation that can function as filter\n+JSMIN =\n+\n # default configuration for gitweb\n GITWEB_CONFIG = gitweb_config.perl\n GITWEB_CONFIG_SYSTEM = /etc/gitweb.conf\n@@ -261,7 +267,11 @@ GITWEB_HOMETEXT = indextext.html\n GITWEB_CSS = gitweb.css\n GITWEB_LOGO = git-logo.png\n GITWEB_FAVICON = git-favicon.png\n+ifdef JSMIN\n+GITWEB_JS = gitweb.min.js\n+else\n GITWEB_JS = gitweb.js\n+endif\n GITWEB_SITE_HEADER =\n GITWEB_SITE_FOOTER =\n \n@@ -1374,8 +1384,13 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl\n \tchmod +x $@+ && \\\n \tmv $@+ $@\n \n+ifdef JSMIN\n+OTHER_PROGRAMS += gitweb/gitweb.cgi   gitweb/gitweb.min.js\n+gitweb/gitweb.cgi: gitweb/gitweb.perl gitweb/gitweb.min.js\n+else\n OTHER_PROGRAMS += gitweb/gitweb.cgi\n gitweb/gitweb.cgi: gitweb/gitweb.perl\n+endif\n \t$(QUIET_GEN)$(RM) $@ $@+ && \\\n \tsed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \\\n \t    -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \\\n@@ -1426,6 +1441,11 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)) git-instaweb: % : unimplemented.sh\n \tmv $@+ $@\n endif # NO_PERL\n \n+ifdef JSMIN\n+gitweb/gitweb.min.js: gitweb/gitweb.js\n+\t$(QUIET_GEN)$(JSMIN) <$< >$@\n+endif # JSMIN\n+\n configure: configure.ac\n \t$(QUIET_GEN)$(RM) $@ $<+ && \\\n \tsed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \\\n-- \n1.6.3.3\n"},{"id":"126921","messageId":"20091105202202.GC17748@machine.or.cz","threadId":"20808","inReplyTo":"1251805160-5303-3-git-send-email-jnareb@gmail.com","subject":"Re: [PATCHv5 2/5] gitweb: Incremental blame (using JavaScript)","fromName":"Petr Baudis","fromEmail":"pasky@suse.cz","sentAt":"2009-11-05T20:22:03Z","receivedAt":"2009-11-05T20:22:03Z","isPatch":false,"sender":{"key":"pasky@ucw.cz","avatar":"https://avatars.githubusercontent.com/u/18439?v=4"},"body":"  Hi!\n\n  Many thanks for nurturing this patch.\n\nOn Tue, Sep 01, 2009 at 01:39:17PM +0200, Jakub Narebski wrote:\n> Roads not taken (perhaps that should be part of commit message?):\n> * Move most (or all) of \"git blame --incremental\" output parsing to\n>   server side, and instead of sending direct output in text/plain,\n>   send processed data in JSON format, e.g.\n> \n>     {\"commit\": {\n>        \"sha1\": \"e83c5163316f89bfbde7d9ab23ca2e25604af290\",\n>        \"info\": \"Kay Sievers, 2005-08-07 21:49:46 +0200\",\n>        \"author-initials\": \"KS\",\n>        ...\n>      },\n>      \"src-line\": 13,\n>      \"dst-line\": 16,\n>      \"numlines\": 3,\n>      \"filename\": \"README\"\n>      }\n> \n>   (line wrapping added for readibility).  This would require however\n>   taking care on Perl side to send properly formatted JSON, and on\n>   JavaScript side including json2.js code to read JSON in gitweb.js\n>   (unless we rely on eval).\n\n  I don't know that much about web programming, what is wrong with\nrelying on eval?\n\n  (BTW, I have always thought that somewhat inevitable course in the\nfuture will be to provide a web API, and basing it on JSON is probably\nmost natural choice. However, such an initiative needs to come from\nactual web programmers.)\n\n> * Using some lightweight JavaScript library (framework), like jQuery,\n>   Prototype, ExtJS, MooTools, etc.  One one hand side this means not\n>   having to worry about browser incompatibilities as this would be\n>   taken care of by library; on the other hand side we want gitweb to\n>   have as few dependences as possible.\n\n  Normally, particular version of the library is simply included within\nthe project. E.g. in Girocco, I use MooTools for the tiny bit of\njavascript I do. It is probably overkill to include it just for\nincremental blame, but if we ever do much more, I think the much easier\nweb programming is worth the little trouble.\n\n-- \n\t\t\t\tPetr \"Pasky\" Baudis\nA lot of people have my books on their bookshelves.\nThat's the problem, they need to read them. -- Don Knuth\n"},{"id":"126924","messageId":"20091105203316.GD17748@machine.or.cz","threadId":"20808","inReplyTo":"1251805160-5303-5-git-send-email-jnareb@gmail.com","subject":"Re: [PATCHv3/RFC 4/5] gitweb: Create links leading to 'blame_incremental' using JavaScript","fromName":"Petr Baudis","fromEmail":"pasky@suse.cz","sentAt":"2009-11-05T20:33:16Z","receivedAt":"2009-11-05T20:33:16Z","isPatch":false,"sender":{"key":"pasky@ucw.cz","avatar":"https://avatars.githubusercontent.com/u/18439?v=4"},"body":"  Hi!\n\nOn Tue, Sep 01, 2009 at 01:39:19PM +0200, Jakub Narebski wrote:\n> @@ -4806,6 +4820,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\n  I'm a bit concerned here. I have somewhat backed out of incremental\nblame myself because I have found (in accord with Junio's old findings)\nthat in most cases, incremental blame can be actually slower than normal\nblame because of slow browsers where it takes long to update the page in\neach step.\n\n  I'm sorry if I missed this in one of your mails, but how fast is\nincremental blame in your implementation? If this still might be an\nissue, I think it should be configurable whether to use incremental\nblame, or perhaps use some quick heuristic wrt. file size (negative\nbias) and history length (positive bias) [not sure if that information\nis quickly available].\n\n-- \n\t\t\t\tPetr \"Pasky\" Baudis\nA lot of people have my books on their bookshelves.\nThat's the problem, they need to read them. -- Don Knuth\n"},{"id":"126996","messageId":"200911061905.52285.jnareb@gmail.com","threadId":"20808","inReplyTo":"20091105203316.GD17748@machine.or.cz","subject":"Re: [PATCHv3/RFC 4/5] gitweb: Create links leading to 'blame_incremental' using JavaScript","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2009-11-06T18:05:51Z","receivedAt":"2009-11-06T18:05:51Z","isPatch":false,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"On Thu, Nov 05, 2009, Petr Baudis wrote:\n> On Tue, Sep 01, 2009 at 01:39:19PM +0200, Jakub Narebski wrote:\n> > @@ -4806,6 +4820,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> \n>   I'm a bit concerned here. I have somewhat backed out of incremental\n> blame myself because I have found (in accord with Junio's old findings)\n> that in most cases, incremental blame can be actually slower than normal\n> blame because of slow browsers where it takes long to update the page in\n> each step.\n> \n>   I'm sorry if I missed this in one of your mails, but how fast is\n> incremental blame in your implementation? If this still might be an\n> issue, I think it should be configurable whether to use incremental\n> blame, or perhaps use some quick heuristic wrt. file size (negative\n> bias) and history length (positive bias) [not sure if that information\n> is quickly available].\n\nUnfortunately I can't benchmark the speed of incremental blame well\nbecause of testing it on a single computer.\n\nWhat I have found that incremental blame spares at least _server time_,\nwhich means that the time to prepare starting view for incremental blame\n(with the contents of file in blame format, unblamed) plus the time to\ngenerate incremental blame data is usually about the same or faster\nthan the time to generate ordinary blame view.  Quite faster if file\nhave large number of blamed commits:\n  $ git blame -p <file> | grep author-time | wc -l\n\nBut even if incremental blame turns out to be slower than incremental\nblame it still has the advantage of being _incremental_.  You have at \nleast some result soon.  Even more with current implementation which\nincludes progress report for incremental blame.\n\n\nWhat needs to be addressed however is to remove totally unnecessary \ncritical section / locking code, as JavaScript is single threaded.\nWe should take care however that JavaScript code of interactive blame\ndoesn't take all CPU, for example using technique presented in\n  \"Timed array processing in JavaScript\" by Nicholas C. Zakas\n  http://www.nczonline.net/blog/2009/08/11/timed-array-processing-in-javascript/\n\n\n.....................................................................\n\nIf you want below there are very simple benchmark of blame and\nincremental blame on a _single_ computer:\n  AMD Athlon 1 GHz, 512MB RAM, Linux 2.6.14-11.1.aur.2, Apache 2.0.54\nI don't remember however if it is for the most current code.\n\nFile               | 'blame'[1] | 'blame_incremental'[2]\n================================================================\t\nblob.h             |     2.346s |  0.443s +  (2.244s /   2.921s)\nGIT-VERSION-GEN    |     2.449s |  1.346s +  (3.157s /   3.876s)\nREADME             |     2.713s |  0.508s +  (2.952s /   3.659s)\nrevision.c         |    19.964s |  4.872s + (11.306s /  32.124s)\ngitweb/gitweb.perl |    83.912s | 12.069s + (52.922s / 223.133s)\n\n$ git blame --porcelain   gitweb/gitweb.perl >/dev/null\n  0m11.437s user + 0m0.740s sys, 66300minor pagefaults\n$ git blame --incremental gitweb/gitweb.perl >/dev/null\n  0m11.477s user + 0m0.816s sys, 68945minor pagefaults\n\nFootnotes:\n~~~~~~~~~~\n[1] Total wall-clock time as returned by gitweb in the page footer.\n[2] XXs + (XXs server blame_data / XXs client JavaScript).\n\n-- \nJakub Narebski\nPoland\n"},{"id":"127037","messageId":"200911071204.53550.jnareb@gmail.com","threadId":"20808","inReplyTo":"20091105202202.GC17748@machine.or.cz","subject":"Re: [PATCHv5 2/5] gitweb: Incremental blame (using JavaScript)","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2009-11-07T11:04:51Z","receivedAt":"2009-11-07T11:04:51Z","isPatch":false,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"On Thu, 5 Nov 2009, Petr Baudis wrote:\n> \n>   Many thanks for nurturing this patch.\n\nYou are welcome.\n\nI have learned quite a bit about JavaScript when working on this\npatch...\n \n> On Tue, Sep 01, 2009 at 01:39:17PM +0200, Jakub Narebski wrote:\n> > Roads not taken (perhaps that should be part of commit message?):\n> > * Move most (or all) of \"git blame --incremental\" output parsing to\n> >   server side, and instead of sending direct output in text/plain,\n> >   send processed data in JSON format, e.g.\n> > \n> >     {\"commit\": {\n> >        \"sha1\": \"e83c5163316f89bfbde7d9ab23ca2e25604af290\",\n> >        \"info\": \"Kay Sievers, 2005-08-07 21:49:46 +0200\",\n> >        \"author-initials\": \"KS\",\n> >        ...\n> >      },\n> >      \"src-line\": 13,\n> >      \"dst-line\": 16,\n> >      \"numlines\": 3,\n> >      \"filename\": \"README\"\n> >      }\n> > \n> >   (line wrapping added for readibility).  This would require however\n> >   taking care on Perl side to send properly formatted JSON, and on\n> >   JavaScript side including json2.js code to read JSON in gitweb.js\n> >   (unless we rely on eval).\n> \n>   I don't know that much about web programming, what is wrong with\n> relying on eval?\n\nIn general it is insecure.  In this specific situation it shouldn't.\n\n[...]\n> > * Using some lightweight JavaScript library (framework), like jQuery,\n> >   Prototype, ExtJS, MooTools, etc.  One one hand side this means not\n> >   having to worry about browser incompatibilities as this would be\n> >   taken care of by library; on the other hand side we want gitweb to\n> >   have as few dependences as possible.\n> \n>   Normally, particular version of the library is simply included within\n> the project. E.g. in Girocco, I use MooTools for the tiny bit of\n> javascript I do. It is probably overkill to include it just for\n> incremental blame, but if we ever do much more, I think the much easier\n> web programming is worth the little trouble.\n\nAlternate solution would be to load for example jQuery for example hosted\non Google as described in http://code.google.com/apis/ajaxlibs/documentation/\n\n-- \nJakub Narebski\nPoland\n"},{"id":"127418","messageId":"7vtyx0f9rd.fsf@alter.siamese.dyndns.org","threadId":"20808","inReplyTo":"200911061905.52285.jnareb@gmail.com","subject":"Re: [PATCHv3/RFC 4/5] gitweb: Create links leading to 'blame_incremental' using JavaScript","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-11-12T08:05:42Z","receivedAt":"2009-11-12T08:05:42Z","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> But even if incremental blame turns out to be slower than incremental\n> blame it still has the advantage of being _incremental_.  You have at \n> least some result soon.\n\nIt wasn't it was slow that bothered me, but early implementations of\nincremental blame I tried didn't _appear_ as incremental.  That was the\ndissapointing part.\n\nAt the protocol and implementation level it certainly was feeding data\nincrementally to the browser, but the end user experience on the screen\nwas \"click....wait...wait...wait...voila the whole blame appears\", not\n\"click...trickle...trickle...trickle...ah everything is filled\".  The\nlatter obviously is what an incremental one should be aiming for.\n\nNo I haven't tried your latest code.  Probably I should.\n"},{"id":"127424","messageId":"200911121022.40893.jnareb@gmail.com","threadId":"20808","inReplyTo":"7vtyx0f9rd.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCHv3/RFC 4/5] gitweb: Create links leading to 'blame_incremental' using JavaScript","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2009-11-12T09:22:39Z","receivedAt":"2009-11-12T09:22:39Z","isPatch":false,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"On Thu, 12 Nov 2009, Junio C Hamano wrote:\n> Jakub Narebski <jnareb@gmail.com> writes:\n> \n> > But even if incremental blame turns out to be slower than incremental\n> > blame it still has the advantage of being _incremental_.  You have at \n> > least some result soon.\n> \n> It wasn't it was slow that bothered me, but early implementations of\n> incremental blame I tried didn't _appear_ as incremental.  That was the\n> dissapointing part.\n> \n> At the protocol and implementation level it certainly was feeding data\n> incrementally to the browser, but the end user experience on the screen\n> was \"click....wait...wait...wait...voila the whole blame appears\", not\n> \"click...trickle...trickle...trickle...ah everything is filled\".  The\n> latter obviously is what an incremental one should be aiming for.\n> \n> No I haven't tried your latest code.  Probably I should.\n\nThe problem with earliest versions of incremental blame _in some browsers_\nwas that 'onreadystatechange' event was not fired as soon as new part of\nblame data was available (truth to be said the definition of this event\nis a bit underspecified).  That is why newer versions of interactive blame\nuse timer (alarm) to check every 1 second if there is something new.\n\nPerhaps interactive blame should use 'onprogress' event instead, which is\nwell defined... but is in _draft_ of standard (XHR 2.0), and not yet in\nestablished standard.\n\n-- \nJakub Narebski\nPoland\n"}]}