]>
Lady’s Gitweb - Git/blob - static/gitweb.js
   2 SPDX-FileCopyrightText: 2007 Fredrik Kuivinen <frekui@gmail.com> 
   3 SPDX-FileCopyrightText: 2007 Petr Baudis <pasky@suse.cz> 
   4 SPDX-FileCopyrightText: 2008-2011 Jakub Narebski <jnareb@gmail.com> 
   5 SPDX-FileCopyrightText: 2011 John 'Warthog9' Hawley <warthog9@eaglescrag.net> 
   6 SPDX-License-Identifier: GPL-2.0-or-later 
   9 // Copyright (C) 2007, Fredrik Kuivinen <frekui@gmail.com> 
  10 //               2007, Petr Baudis <pasky@suse.cz> 
  11 //          2008-2011, Jakub Narebski <jnareb@gmail.com> 
  14  * @fileOverview Generic JavaScript code (helper functions) 
  15  * @license GPLv2 or later 
  19 /* ============================================================ */ 
  20 /* ............................................................ */ 
  24  * pad INPUT on the left with STR that is assumed to have visible 
  25  * width of single character (for example nonbreakable spaces), 
  28  * example: padLeftStr(12, 3, '\u00A0') == '\u00A012' 
  29  *          ('\u00A0' is nonbreakable space) 
  31  * @param {Number|String} input: number to pad 
  32  * @param {Number} width: visible width of output 
  33  * @param {String} str: string to prefix to string, defaults to '\u00A0' 
  34  * @returns {String} INPUT prefixed with STR x (WIDTH - INPUT.length) 
  36 function padLeftStr(input
, width
, str
) { 
  38         if (typeof str 
=== 'undefined') { 
  39                 ch 
= '\u00A0'; // using ' ' doesn't work in all browsers 
  42         width 
-= input
.toString().length
; 
  47         return prefix 
+ input
; 
  51  * Pad INPUT on the left to WIDTH, using given padding character CH, 
  52  * for example padLeft('a', 3, '_') is '__a' 
  53  *             padLeft(4, 2) is '04' (same as padLeft(4, 2, '0')) 
  55  * @param {String} input: input value converted to string. 
  56  * @param {Number} width: desired length of output. 
  57  * @param {String} ch: single character to prefix to string, defaults to '0'. 
  59  * @returns {String} Modified string, at least SIZE length. 
  61 function padLeft(input
, width
, ch
) { 
  63         if (typeof ch 
=== 'undefined') { 
  67         while (s
.length 
< width
) { 
  74 /* ............................................................ */ 
  75 /* Handling browser incompatibilities */ 
  78  * Create XMLHttpRequest object in cross-browser way 
  79  * @returns XMLHttpRequest object, or null 
  81 function createRequestObject() { 
  83                 return new XMLHttpRequest(); 
  86                 return window
.createRequest(); 
  89                 return new ActiveXObject("Msxml2.XMLHTTP"); 
  92                 return new ActiveXObject("Microsoft.XMLHTTP"); 
 100  * Insert rule giving specified STYLE to given SELECTOR at the end of 
 101  * first CSS stylesheet. 
 103  * @param {String} selector: CSS selector, e.g. '.class' 
 104  * @param {String} style: rule contents, e.g. 'background-color: red;' 
 106 function addCssRule(selector
, style
) { 
 107         var stylesheet 
= document
.styleSheets
[0]; 
 110         if (stylesheet
.cssRules
) {     // W3C way 
 111                 theRules 
= stylesheet
.cssRules
; 
 112         } else if (stylesheet
.rules
) { // IE way 
 113                 theRules 
= stylesheet
.rules
; 
 116         if (stylesheet
.insertRule
) {    // W3C way 
 117                 stylesheet
.insertRule(selector 
+ ' { ' + style 
+ ' }', theRules
.length
); 
 118         } else if (stylesheet
.addRule
) { // IE way 
 119                 stylesheet
.addRule(selector
, style
); 
 124 /* ............................................................ */ 
 125 /* Support for legacy browsers */ 
 128  * Provides getElementsByClassName method, if there is no native 
 129  * implementation of this method. 
 131  * NOTE that there are limits and differences compared to native 
 132  * getElementsByClassName as defined by e.g.: 
 133  *   https://developer.mozilla.org/en/DOM/document.getElementsByClassName 
 134  *   http://www.whatwg.org/specs/web-apps/current-work/multipage/dom.html#dom-getelementsbyclassname 
 135  *   http://www.whatwg.org/specs/web-apps/current-work/multipage/dom.html#dom-document-getelementsbyclassname 
 137  * Namely, this implementation supports only single class name as 
 138  * argument and not set of space-separated tokens representing classes, 
 139  * it returns Array of nodes rather than live NodeList, and has 
 140  * additional optional argument where you can limit search to given tags 
 141  * (via getElementsByTagName). 
 144  *   http://code.google.com/p/getelementsbyclassname/ 
 145  *   http://www.dustindiaz.com/getelementsbyclass/ 
 146  *   http://stackoverflow.com/questions/1818865/do-we-have-getelementsbyclassname-in-javascript 
 148  * See also http://ejohn.org/blog/getelementsbyclassname-speed-comparison/ 
 150  * @param {String} class: name of _single_ class to find 
 151  * @param {String} [taghint] limit search to given tags 
 152  * @returns {Node[]} array of matching elements 
 154 if (!('getElementsByClassName' in document
)) { 
 155         document
.getElementsByClassName = function (classname
, taghint
) { 
 156                 taghint 
= taghint 
|| "*"; 
 157                 var elements 
= (taghint 
=== "*" && document
.all
) ? 
 159                                document
.getElementsByTagName(taghint
); 
 160                 var pattern 
= new RegExp("(^|\\s)" + classname 
+ "(\\s|$)"); 
 162                 for (var i 
= 0, j 
= 0, n 
= elements
.length
; i 
< n
; i
++) { 
 164                         if (el
.className 
&& pattern
.test(el
.className
)) { 
 175 /* ............................................................ */ 
 176 /* unquoting/unescaping filenames */ 
 181 var escCodeRe 
= /\\([^0-7]|[0-7]{1,3})/g; 
 182 var octEscRe 
= /^[0-7]{1,3}$/; 
 183 var maybeQuotedRe 
= /^\"(.*)\"$/; 
 187  * unquote maybe C-quoted filename (as used by git, i.e. it is 
 188  * in double quotes '"' if there is any escape character used) 
 189  * e.g. 'aa' -> 'aa', '"a\ta"' -> 'a    a' 
 191  * @param {String} str: git-quoted string 
 192  * @returns {String} Unquoted and unescaped string 
 194  * @globals escCodeRe, octEscRe, maybeQuotedRe 
 196 function unquote(str
) { 
 199                         // character escape codes, aka escape sequences (from C) 
 200                         // replacements are to some extent JavaScript specific 
 201                         t: "\t",   // tab            (HT, TAB) 
 202                         n: "\n",   // newline        (NL) 
 203                         r: "\r",   // return         (CR) 
 204                         f: "\f",   // form feed      (FF) 
 205                         b: "\b",   // backspace      (BS) 
 206                         a: "\x07", // alarm (bell)   (BEL) 
 207                         e: "\x1B", // escape         (ESC) 
 208                         v: "\v"    // vertical tab   (VT) 
 211                 if (seq
.search(octEscRe
) !== -1) { 
 212                         // octal char sequence 
 213                         return String
.fromCharCode(parseInt(seq
, 8)); 
 214                 } else if (seq 
in es
) { 
 215                         // C escape sequence, aka character escape code 
 218                 // quoted ordinary character 
 222         var match 
= str
.match(maybeQuotedRe
); 
 225                 // perhaps str = eval('"'+str+'"'); would be enough? 
 226                 str 
= str
.replace(escCodeRe
, 
 227                         function (substr
, p1
, offset
, s
) { return unq(p1
); }); 
 232 /* end of common-lib.js */ 
 233 // Copyright (C) 2007, Fredrik Kuivinen <frekui@gmail.com> 
 234 //               2007, Petr Baudis <pasky@suse.cz> 
 235 //          2008-2011, Jakub Narebski <jnareb@gmail.com> 
 238  * @fileOverview Datetime manipulation: parsing and formatting 
 239  * @license GPLv2 or later 
 243 /* ............................................................ */ 
 244 /* parsing and retrieving datetime related information */ 
 247  * used to extract hours and minutes from timezone info, e.g '-0900' 
 250 var tzRe 
= /^([+\-])([0-9][0-9])([0-9][0-9])$/; 
 253  * convert numeric timezone +/-ZZZZ to offset from UTC in seconds 
 255  * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM' 
 256  * @returns {Number} offset from UTC in seconds for timezone 
 260 function timezoneOffset(timezoneInfo
) { 
 261         var match 
= tzRe
.exec(timezoneInfo
); 
 262         var tz_sign 
= (match
[1] === '-' ? -1 : +1); 
 263         var tz_hour 
= parseInt(match
[2],10); 
 264         var tz_min  
= parseInt(match
[3],10); 
 266         return tz_sign
*(((tz_hour
*60) + tz_min
)*60); 
 270  * return local (browser) timezone as offset from UTC in seconds 
 272  * @returns {Number} offset from UTC in seconds for local timezone 
 274 function localTimezoneOffset() { 
 275         // getTimezoneOffset returns the time-zone offset from UTC, 
 276         // in _minutes_, for the current locale 
 277         return ((new Date()).getTimezoneOffset() * -60); 
 281  * return local (browser) timezone as numeric timezone '(+|-)HHMM' 
 283  * @returns {String} locat timezone as -/+ZZZZ 
 285 function localTimezoneInfo() { 
 286         var tzOffsetMinutes 
= (new Date()).getTimezoneOffset() * -1; 
 288         return formatTimezoneInfo(0, tzOffsetMinutes
); 
 293  * Parse RFC-2822 date into a Unix timestamp (into epoch) 
 295  * @param {String} date: date in RFC-2822 format, e.g. 'Thu, 21 Dec 2000 16:01:07 +0200' 
 296  * @returns {Number} epoch i.e. seconds since '00:00:00 1970-01-01 UTC' 
 298 function parseRFC2822Date(date
) { 
 299         // Date.parse accepts the IETF standard (RFC 1123 Section 5.2.14 and elsewhere) 
 300         // date syntax, which is defined in RFC 2822 (obsoletes RFC 822) 
 301         // and returns number of _milli_seconds since January 1, 1970, 00:00:00 UTC 
 302         return Date
.parse(date
) / 1000; 
 306 /* ............................................................ */ 
 307 /* formatting date */ 
 310  * format timezone offset as numerical timezone '(+|-)HHMM' or '(+|-)HH:MM' 
 312  * @param {Number} hours:    offset in hours, e.g. 2 for '+0200' 
 313  * @param {Number} [minutes] offset in minutes, e.g. 30 for '-4030'; 
 314  *                           it is split into hours if not 0 <= minutes < 60, 
 315  *                           for example 1200 would give '+0100'; 
 317  * @param {String} [sep] separator between hours and minutes part, 
 318  *                       default is '', might be ':' for W3CDTF (rfc-3339) 
 319  * @returns {String} timezone in '(+|-)HHMM' or '(+|-)HH:MM' format 
 321 function formatTimezoneInfo(hours
, minutes
, sep
) { 
 322         minutes 
= minutes 
|| 0; // to be able to use formatTimezoneInfo(hh) 
 323         sep 
= sep 
|| ''; // default format is +/-ZZZZ 
 325         if (minutes 
< 0 || minutes 
> 59) { 
 326                 hours 
= minutes 
> 0 ? Math
.floor(minutes 
/ 60) : Math
.ceil(minutes 
/ 60); 
 327                 minutes 
= Math
.abs(minutes 
- 60*hours
); // sign of minutes is sign of hours 
 328                 // NOTE: this works correctly because there is no UTC-00:30 timezone 
 331         var tzSign 
= hours 
>= 0 ? '+' : '-'; 
 333                 hours 
= -hours
; // sign is stored in tzSign 
 336         return tzSign 
+ padLeft(hours
, 2, '0') + sep 
+ padLeft(minutes
, 2, '0'); 
 340  * translate 'utc' and 'local' to numerical timezone 
 341  * @param {String} timezoneInfo: might be 'utc' or 'local' (browser) 
 343 function normalizeTimezoneInfo(timezoneInfo
) { 
 344         switch (timezoneInfo
) { 
 347         case 'local': // 'local' is browser timezone 
 348                 return localTimezoneInfo(); 
 355  * return date in local time formatted in iso-8601 like format 
 356  * 'yyyy-mm-dd HH:MM:SS +/-ZZZZ' e.g. '2005-08-07 21:49:46 +0200' 
 358  * @param {Number} epoch: seconds since '00:00:00 1970-01-01 UTC' 
 359  * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM' 
 360  * @returns {String} date in local time in iso-8601 like format 
 362 function formatDateISOLocal(epoch
, timezoneInfo
) { 
 363         // date corrected by timezone 
 364         var localDate 
= new Date(1000 * (epoch 
+ 
 365                 timezoneOffset(timezoneInfo
))); 
 366         var localDateStr 
= // e.g. '2005-08-07' 
 367                 localDate
.getUTCFullYear()                 + '-' + 
 368                 padLeft(localDate
.getUTCMonth()+1, 2, '0') + '-' + 
 369                 padLeft(localDate
.getUTCDate(),    2, '0'); 
 370         var localTimeStr 
= // e.g. '21:49:46' 
 371                 padLeft(localDate
.getUTCHours(),   2, '0') + ':' + 
 372                 padLeft(localDate
.getUTCMinutes(), 2, '0') + ':' + 
 373                 padLeft(localDate
.getUTCSeconds(), 2, '0'); 
 375         return localDateStr 
+ ' ' + localTimeStr 
+ ' ' + timezoneInfo
; 
 379  * return date in local time formatted in rfc-2822 format 
 380  * e.g. 'Thu, 21 Dec 2000 16:01:07 +0200' 
 382  * @param {Number} epoch: seconds since '00:00:00 1970-01-01 UTC' 
 383  * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM' 
 384  * @param {Boolean} [padDay] e.g. 'Sun, 07 Aug' if true, 'Sun, 7 Aug' otherwise 
 385  * @returns {String} date in local time in rfc-2822 format 
 387 function formatDateRFC2882(epoch
, timezoneInfo
, padDay
) { 
 388         // A short textual representation of a month, three letters 
 389         var months 
= ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; 
 390         // A textual representation of a day, three letters 
 391         var days 
= ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; 
 392         // date corrected by timezone 
 393         var localDate 
= new Date(1000 * (epoch 
+ 
 394                 timezoneOffset(timezoneInfo
))); 
 395         var localDateStr 
= // e.g. 'Sun, 7 Aug 2005' or 'Sun, 07 Aug 2005' 
 396                 days
[localDate
.getUTCDay()] + ', ' + 
 397                 (padDay 
? padLeft(localDate
.getUTCDate(),2,'0') : localDate
.getUTCDate()) + ' ' + 
 398                 months
[localDate
.getUTCMonth()] + ' ' + 
 399                 localDate
.getUTCFullYear(); 
 400         var localTimeStr 
= // e.g. '21:49:46' 
 401                 padLeft(localDate
.getUTCHours(),   2, '0') + ':' + 
 402                 padLeft(localDate
.getUTCMinutes(), 2, '0') + ':' + 
 403                 padLeft(localDate
.getUTCSeconds(), 2, '0'); 
 405         return localDateStr 
+ ' ' + localTimeStr 
+ ' ' + timezoneInfo
; 
 408 /* end of datetime.js */ 
 410  * @fileOverview Accessing cookies from JavaScript 
 411  * @license GPLv2 or later 
 415  * Based on subsection "Cookies in JavaScript" of "Professional 
 416  * JavaScript for Web Developers" by Nicholas C. Zakas and cookie 
 417  * plugin from jQuery (dual licensed under the MIT and GPL licenses) 
 422  * Create a cookie with the given name and value, 
 423  * and other optional parameters. 
 426  *   setCookie('foo', 'bar'); // will be deleted when browser exits 
 427  *   setCookie('foo', 'bar', { expires: new Date(Date.parse('Jan 1, 2012')) }); 
 428  *   setCookie('foo', 'bar', { expires: 7 }); // 7 days = 1 week 
 429  *   setCookie('foo', 'bar', { expires: 14, path: '/' }); 
 431  * @param {String} sName:    Unique name of a cookie (letters, numbers, underscores). 
 432  * @param {String} sValue:   The string value stored in a cookie. 
 433  * @param {Object} [options] An object literal containing key/value pairs 
 434  *                           to provide optional cookie attributes. 
 435  * @param {String|Number|Date} [options.expires] Either literal string to be used as cookie expires, 
 436  *                            or an integer specifying the expiration date from now on in days, 
 437  *                            or a Date object to be used as cookie expiration date. 
 438  *                            If a negative value is specified or a date in the past), 
 439  *                            the cookie will be deleted. 
 440  *                            If set to null or omitted, the cookie will be a session cookie 
 441  *                            and will not be retained when the browser exits. 
 442  * @param {String} [options.path] Restrict access of a cookie to particular directory 
 443  *                               (default: path of page that created the cookie). 
 444  * @param {String} [options.domain] Override what web sites are allowed to access cookie 
 445  *                                  (default: domain of page that created the cookie). 
 446  * @param {Boolean} [options.secure] If true, the secure attribute of the cookie will be set 
 447  *                                   and the cookie would be accessible only from secure sites 
 448  *                                   (cookie transmission will require secure protocol like HTTPS). 
 450 function setCookie(sName
, sValue
, options
) { 
 451         options 
= options 
|| {}; 
 452         if (sValue 
=== null) { 
 454                 option
.expires 
= 'delete'; 
 457         var sCookie 
= sName 
+ '=' + encodeURIComponent(sValue
); 
 459         if (options
.expires
) { 
 460                 var oExpires 
= options
.expires
, sDate
; 
 461                 if (oExpires 
=== 'delete') { 
 462                         sDate 
= 'Thu, 01 Jan 1970 00:00:00 GMT'; 
 463                 } else if (typeof oExpires 
=== 'string') { 
 467                         if (typeof oExpires 
=== 'number') { 
 469                                 oDate
.setTime(oDate
.getTime() + (oExpires 
* 24 * 60 * 60 * 1000)); // days to ms 
 473                         sDate 
= oDate
.toGMTString(); 
 475                 sCookie 
+= '; expires=' + sDate
; 
 479                 sCookie 
+= '; path=' + (options
.path
); 
 481         if (options
.domain
) { 
 482                 sCookie 
+= '; domain=' + (options
.domain
); 
 484         if (options
.secure
) { 
 485                 sCookie 
+= '; secure'; 
 487         document
.cookie 
= sCookie
; 
 491  * Get the value of a cookie with the given name. 
 493  * @param {String} sName: Unique name of a cookie (letters, numbers, underscores) 
 494  * @returns {String|null} The string value stored in a cookie 
 496 function getCookie(sName
) { 
 497         var sRE 
= '(?:; )?' + sName 
+ '=([^;]*);?'; 
 498         var oRE 
= new RegExp(sRE
); 
 499         if (oRE
.test(document
.cookie
)) { 
 500                 return decodeURIComponent(RegExp
['$1']); 
 507  * Delete cookie with given name 
 509  * @param {String} sName:    Unique name of a cookie (letters, numbers, underscores) 
 510  * @param {Object} [options] An object literal containing key/value pairs 
 511  *                           to provide optional cookie attributes. 
 512  * @param {String} [options.path]   Must be the same as when setting a cookie 
 513  * @param {String} [options.domain] Must be the same as when setting a cookie 
 515 function deleteCookie(sName
, options
) { 
 516         options 
= options 
|| {}; 
 517         options
.expires 
= 'delete'; 
 519         setCookie(sName
, '', options
); 
 522 /* end of cookies.js */ 
 523 // Copyright (C) 2007, Fredrik Kuivinen <frekui@gmail.com> 
 524 //               2007, Petr Baudis <pasky@suse.cz> 
 525 //          2008-2011, Jakub Narebski <jnareb@gmail.com> 
 528  * @fileOverview Detect if JavaScript is enabled, and pass it to server-side 
 529  * @license GPLv2 or later 
 533 /* ============================================================ */ 
 534 /* Manipulating links */ 
 537  * used to check if link has 'js' query parameter already (at end), 
 538  * and other reasons to not add 'js=1' param at the end of link 
 541 var jsExceptionsRe 
= /[;?]js=[01](#.*)?$/; 
 544  * Add '?js=1' or ';js=1' to the end of every link in the document 
 545  * that doesn't have 'js' query parameter set already. 
 547  * Links with 'js=1' lead to JavaScript version of given action, if it 
 548  * exists (currently there is only 'blame_incremental' for 'blame') 
 550  * To be used as `window.onload` handler 
 552  * @globals jsExceptionsRe 
 554 function fixLinks() { 
 555         var allLinks 
= document
.getElementsByTagName("a") || document
.links
; 
 556         for (var i 
= 0, len 
= allLinks
.length
; i 
< len
; i
++) { 
 557                 var link 
= allLinks
[i
]; 
 558                 if (!jsExceptionsRe
.test(link
)) { 
 559                         link
.href 
= link
.href
.replace(/(#|$)/, 
 560                                 (link
.href
.indexOf('?') === -1 ? '?' : ';') + 'js=1$1'); 
 565 /* end of javascript-detection.js */ 
 566 // Copyright (C) 2011, John 'Warthog9' Hawley <warthog9@eaglescrag.net> 
 567 //               2011, Jakub Narebski <jnareb@gmail.com> 
 570  * @fileOverview Manipulate dates in gitweb output, adjusting timezone 
 571  * @license GPLv2 or later 
 575  * Get common timezone, add UI for changing timezones, and adjust 
 576  * dates to use requested common timezone. 
 578  * This function is called during onload event (added to window.onload). 
 580  * @param {String} tzDefault: default timezone, if there is no cookie 
 581  * @param {Object} tzCookieInfo: object literal with info about cookie to store timezone 
 582  * @param {String} tzCookieInfo.name: name of cookie to store timezone 
 583  * @param {String} tzClassName: denotes elements with date to be adjusted 
 585 function onloadTZSetup(tzDefault
, tzCookieInfo
, tzClassName
) { 
 586         var tzCookieTZ 
= getCookie(tzCookieInfo
.name
, tzCookieInfo
); 
 590                 // set timezone to value saved in a cookie 
 592                 // refresh cookie, so its expiration counts from last use of gitweb 
 593                 setCookie(tzCookieInfo
.name
, tzCookieTZ
, tzCookieInfo
); 
 596         // add UI for changing timezone 
 597         addChangeTZ(tz
, tzCookieInfo
, tzClassName
); 
 599         // server-side of gitweb produces datetime in UTC, 
 600         // so if tz is 'utc' there is no need for changes 
 601         var nochange 
= tz 
=== 'utc'; 
 603         // adjust dates to use specified common timezone 
 604         fixDatetimeTZ(tz
, tzClassName
, nochange
); 
 608 /* ...................................................................... */ 
 609 /* Changing dates to use requested timezone */ 
 612  * Replace RFC-2822 dates contained in SPAN elements with tzClassName 
 613  * CSS class with equivalent dates in given timezone. 
 615  * @param {String} tz: numeric timezone in '(-|+)HHMM' format, or 'utc', or 'local' 
 616  * @param {String} tzClassName: specifies elements to be changed 
 617  * @param {Boolean} nochange: markup for timezone change, but don't change it 
 619 function fixDatetimeTZ(tz
, tzClassName
, nochange
) { 
 620         // sanity check, method should be ensured by common-lib.js 
 621         if (!document
.getElementsByClassName
) { 
 625         // translate to timezone in '(-|+)HHMM' format 
 626         tz 
= normalizeTimezoneInfo(tz
); 
 628         // NOTE: result of getElementsByClassName should probably be cached 
 629         var classesFound 
= document
.getElementsByClassName(tzClassName
, "span"); 
 630         for (var i 
= 0, len 
= classesFound
.length
; i 
< len
; i
++) { 
 631                 var curElement 
= classesFound
[i
]; 
 633                 curElement
.title 
= 'Click to change timezone'; 
 635                         // we use *.firstChild.data (W3C DOM) instead of *.innerHTML 
 636                         // as the latter doesn't always work everywhere in every browser 
 637                         var epoch 
= parseRFC2822Date(curElement
.firstChild
.data
); 
 638                         var adjusted 
= formatDateRFC2882(epoch
, tz
); 
 640                         curElement
.firstChild
.data 
= adjusted
; 
 646 /* ...................................................................... */ 
 647 /* Adding triggers, generating timezone menu, displaying and hiding */ 
 650  * Adds triggers for UI to change common timezone used for dates in 
 651  * gitweb output: it marks up and/or creates item to click to invoke 
 652  * timezone change UI, creates timezone UI fragment to be attached, 
 653  * and installs appropriate onclick trigger (via event delegation). 
 655  * @param {String} tzSelected: pre-selected timezone, 
 656  *                             'utc' or 'local' or '(-|+)HHMM' 
 657  * @param {Object} tzCookieInfo: object literal with info about cookie to store timezone 
 658  * @param {String} tzClassName: specifies elements to install trigger 
 660 function addChangeTZ(tzSelected
, tzCookieInfo
, tzClassName
) { 
 661         // make link to timezone UI discoverable 
 662         addCssRule('.'+tzClassName 
+ ':hover', 
 663                    'text-decoration: underline; cursor: help;'); 
 665         // create form for selecting timezone (to be saved in a cookie) 
 666         var tzSelectFragment 
= document
.createDocumentFragment(); 
 667         tzSelectFragment 
= createChangeTZForm(tzSelectFragment
, 
 668                                               tzSelected
, tzCookieInfo
, tzClassName
); 
 670         // event delegation handler for timezone selection UI (clicking on entry) 
 671         // see http://www.nczonline.net/blog/2009/06/30/event-delegation-in-javascript/ 
 672         // assumes that there is no existing document.onclick handler 
 673         document
.onclick 
= function onclickHandler(event
) { 
 674                 //IE doesn't pass in the event object 
 675                 event 
= event 
|| window
.event
; 
 677                 //IE uses srcElement as the target 
 678                 var target 
= event
.target 
|| event
.srcElement
; 
 680                 switch (target
.className
) { 
 682                         // don't display timezone menu if it is already displayed 
 683                         if (tzSelectFragment
.childNodes
.length 
> 0) { 
 684                                 displayChangeTZForm(target
, tzSelectFragment
); 
 692  * Create DocumentFragment with UI for changing common timezone in 
 693  * which dates are shown in. 
 695  * @param {DocumentFragment} documentFragment: where attach UI 
 696  * @param {String} tzSelected: default (pre-selected) timezone 
 697  * @param {Object} tzCookieInfo: object literal with info about cookie to store timezone 
 698  * @returns {DocumentFragment} 
 700 function createChangeTZForm(documentFragment
, tzSelected
, tzCookieInfo
, tzClassName
) { 
 701         var div 
= document
.createElement("div"); 
 702         div
.className 
= 'popup'; 
 704         /* '<div class="close-button" title="(click on this box to close)">X</div>' */ 
 705         var closeButton 
= document
.createElement('div'); 
 706         closeButton
.className 
= 'close-button'; 
 707         closeButton
.title 
= '(click on this box to close)'; 
 708         closeButton
.appendChild(document
.createTextNode('X')); 
 709         closeButton
.onclick 
= closeTZFormHandler(documentFragment
, tzClassName
); 
 710         div
.appendChild(closeButton
); 
 712         /* 'Select timezone: <br clear="all">' */ 
 713         div
.appendChild(document
.createTextNode('Select timezone: ')); 
 714         var br 
= document
.createElement('br'); 
 718         /* '<select name="tzoffset"> 
 720          *    <option value="-0700">UTC-07:00</option> 
 721          *    <option value="-0600">UTC-06:00</option> 
 724         var select 
= document
.createElement("select"); 
 725         select
.name 
= "tzoffset"; 
 726         //select.style.clear = 'all'; 
 727         select
.appendChild(generateTZOptions(tzSelected
)); 
 728         select
.onchange 
= selectTZHandler(documentFragment
, tzCookieInfo
, tzClassName
); 
 729         div
.appendChild(select
); 
 731         documentFragment
.appendChild(div
); 
 733         return documentFragment
; 
 738  * Hide (remove from DOM) timezone change UI, ensuring that it is not 
 739  * garbage collected and that it can be re-enabled later. 
 741  * @param {DocumentFragment} documentFragment: contains detached UI 
 742  * @param {HTMLSelectElement} target: select element inside of UI 
 743  * @param {String} tzClassName: specifies element where UI was installed 
 744  * @returns {DocumentFragment} documentFragment 
 746 function removeChangeTZForm(documentFragment
, target
, tzClassName
) { 
 747         // find containing element, where we appended timezone selection UI 
 748         // `target' is somewhere inside timezone menu 
 749         var container 
= target
.parentNode
, popup 
= target
; 
 751                container
.className 
!== tzClassName
) { 
 753                 container 
= container
.parentNode
; 
 755         // safety check if we found correct container, 
 756         // and if it isn't deleted already 
 757         if (!container 
|| !popup 
|| 
 758             container
.className 
!== tzClassName 
|| 
 759             popup
.className     
!== 'popup') { 
 760                 return documentFragment
; 
 763         // timezone selection UI was appended as last child 
 764         // see also displayChangeTZForm function 
 765         var removed 
= popup
.parentNode
.removeChild(popup
); 
 766         if (documentFragment
.firstChild 
!== removed
) { // the only child 
 767                 // re-append it so it would be available for next time 
 768                 documentFragment
.appendChild(removed
); 
 770         // all of inline style was added by this script 
 771         // it is not really needed to remove it, but it is a good practice 
 772         container
.removeAttribute('style'); 
 774         return documentFragment
; 
 779  * Display UI for changing common timezone for dates in gitweb output. 
 780  * To be used from 'onclick' event handler. 
 782  * @param {HTMLElement} target: where to install/display UI 
 783  * @param {DocumentFragment} tzSelectFragment: timezone selection UI 
 785 function displayChangeTZForm(target
, tzSelectFragment
) { 
 786         // for absolute positioning to be related to target element 
 787         target
.style
.position 
= 'relative'; 
 788         target
.style
.display 
= 'inline-block'; 
 790         // show/display UI for changing timezone 
 791         target
.appendChild(tzSelectFragment
); 
 795 /* ...................................................................... */ 
 796 /* List of timezones for timezone selection menu */ 
 799  * Generate list of timezones for creating timezone select UI 
 801  * @returns {Object[]} list of e.g. { value: '+0100', descr: 'GMT+01:00' } 
 803 function generateTZList() { 
 805                 { value: "utc",   descr: "UTC/GMT"}, 
 806                 { value: "local", descr: "Local (per browser)"} 
 809         // generate all full hour timezones (no fractional timezones) 
 810         for (var x 
= -12, idx 
= timezones
.length
; x 
<= +14; x
++, idx
++) { 
 811                 var hours 
= (x 
>= 0 ? '+' : '-') + padLeft(x 
>=0 ? x : -x
, 2); 
 812                 timezones
[idx
] = { value: hours 
+ '00', descr: 'UTC' + hours 
+ ':00'}; 
 814                         timezones
[idx
].descr 
= 'UTC\u00B100:00'; // 'UTC±00:00' 
 822  * Generate <options> elements for timezone select UI 
 824  * @param {String} tzSelected: default timezone 
 825  * @returns {DocumentFragment} list of options elements to appendChild 
 827 function generateTZOptions(tzSelected
) { 
 828         var elems 
= document
.createDocumentFragment(); 
 829         var timezones 
= generateTZList(); 
 831         for (var i 
= 0, len 
= timezones
.length
; i 
< len
; i
++) { 
 832                 var tzone 
= timezones
[i
]; 
 833                 var option 
= document
.createElement("option"); 
 834                 if (tzone
.value 
=== tzSelected
) { 
 835                         option
.defaultSelected 
= true; 
 837                 option
.value 
= tzone
.value
; 
 838                 option
.appendChild(document
.createTextNode(tzone
.descr
)); 
 840                 elems
.appendChild(option
); 
 847 /* ...................................................................... */ 
 848 /* Event handlers and/or their generators */ 
 851  * Create event handler that select timezone and closes timezone select UI. 
 852  * To be used as $('select[name="tzselect"]').onchange handler. 
 854  * @param {DocumentFragment} tzSelectFragment: timezone selection UI 
 855  * @param {Object} tzCookieInfo: object literal with info about cookie to store timezone 
 856  * @param {String} tzCookieInfo.name: name of cookie to save result of selection 
 857  * @param {String} tzClassName: specifies element where UI was installed 
 858  * @returns {Function} event handler 
 860 function selectTZHandler(tzSelectFragment
, tzCookieInfo
, tzClassName
) { 
 861         //return function selectTZ(event) { 
 862         return function (event
) { 
 863                 event 
= event 
|| window
.event
; 
 864                 var target 
= event
.target 
|| event
.srcElement
; 
 866                 var selected 
= target
.options
.item(target
.selectedIndex
); 
 867                 removeChangeTZForm(tzSelectFragment
, target
, tzClassName
); 
 870                         selected
.defaultSelected 
= true; 
 871                         setCookie(tzCookieInfo
.name
, selected
.value
, tzCookieInfo
); 
 872                         fixDatetimeTZ(selected
.value
, tzClassName
); 
 878  * Create event handler that closes timezone select UI. 
 879  * To be used e.g. as $('.closebutton').onclick handler. 
 881  * @param {DocumentFragment} tzSelectFragment: timezone selection UI 
 882  * @param {String} tzClassName: specifies element where UI was installed 
 883  * @returns {Function} event handler 
 885 function closeTZFormHandler(tzSelectFragment
, tzClassName
) { 
 886         //return function closeTZForm(event) { 
 887         return function (event
) { 
 888                 event 
= event 
|| window
.event
; 
 889                 var target 
= event
.target 
|| event
.srcElement
; 
 891                 removeChangeTZForm(tzSelectFragment
, target
, tzClassName
); 
 895 /* end of adjust-timezone.js */ 
 896 // Copyright (C) 2007, Fredrik Kuivinen <frekui@gmail.com> 
 897 //               2007, Petr Baudis <pasky@suse.cz> 
 898 //          2008-2011, Jakub Narebski <jnareb@gmail.com> 
 901  * @fileOverview JavaScript side of Ajax-y 'blame_incremental' view in gitweb 
 902  * @license GPLv2 or later 
 905 /* ============================================================ */ 
 907  * This code uses DOM methods instead of (nonstandard) innerHTML 
 910  * innerHTML is non-standard IE extension, though supported by most 
 911  * browsers; however Firefox up to version 1.5 didn't implement it in 
 912  * a strict mode (application/xml+xhtml mimetype). 
 914  * Also my simple benchmarks show that using elem.firstChild.data = 
 915  * 'content' is slightly faster than elem.innerHTML = 'content'.  It 
 916  * is however more fragile (text element fragment must exists), and 
 917  * less feature-rich (we cannot add HTML). 
 919  * Note that DOM 2 HTML is preferred over generic DOM 2 Core; the 
 920  * equivalent using DOM 2 Core is usually shown in comments. 
 924 /* ............................................................ */ 
 925 /* utility/helper functions (and variables) */ 
 927 var projectUrl
; // partial query + separator ('?' or ';') 
 929 // 'commits' is an associative map. It maps SHA1s to Commit objects. 
 933  * constructor for Commit objects, used in 'blame' 
 934  * @class Represents a blamed commit 
 935  * @param {String} sha1: SHA-1 identifier of a commit 
 937 function Commit(sha1
) { 
 938         if (this instanceof Commit
) { 
 940                 this.nprevious 
= 0; /* number of 'previous', effective parents */ 
 942                 return new Commit(sha1
); 
 946 /* ............................................................ */ 
 947 /* progress info, timing, error reporting */ 
 950 var totalLines  
= '???'; 
 951 var div_progress_bar
; 
 952 var div_progress_info
; 
 955  * Detects how many lines does a blamed file have, 
 956  * This information is used in progress info 
 958  * @returns {Number|String} Number of lines in file, or string '...' 
 960 function countLines() { 
 962                 document
.getElementById('blame_table') || 
 963                 document
.getElementsByTagName('table')[0]; 
 966                 return table
.getElementsByTagName('tr').length 
- 1; // for header 
 973  * update progress info and length (width) of progress bar 
 975  * @globals div_progress_info, div_progress_bar, blamedLines, totalLines 
 977 function updateProgressInfo() { 
 978         if (!div_progress_info
) { 
 979                 div_progress_info 
= document
.getElementById('progress_info'); 
 981         if (!div_progress_bar
) { 
 982                 div_progress_bar 
= document
.getElementById('progress_bar'); 
 984         if (!div_progress_info 
&& !div_progress_bar
) { 
 988         var percentage 
= Math
.floor(100.0*blamedLines
/totalLines
); 
 990         if (div_progress_info
) { 
 991                 div_progress_info
.firstChild
.data  
= blamedLines 
+ ' / ' + totalLines 
+ 
 992                         ' (' + padLeftStr(percentage
, 3, '\u00A0') + '%)'; 
 995         if (div_progress_bar
) { 
 996                 //div_progress_bar.setAttribute('style', 'width: '+percentage+'%;'); 
 997                 div_progress_bar
.style
.width 
= percentage 
+ '%'; 
1002 var t_interval_server 
= ''; 
1003 var cmds_server 
= ''; 
1004 var t0 
= new Date(); 
1007  * write how much it took to generate data, and to run script 
1009  * @globals t0, t_interval_server, cmds_server 
1011 function writeTimeInterval() { 
1012         var info_time 
= document
.getElementById('generating_time'); 
1013         if (!info_time 
|| !t_interval_server
) { 
1016         var t1 
= new Date(); 
1017         info_time
.firstChild
.data 
+= ' + (' + 
1018                 t_interval_server 
+ ' sec server blame_data / ' + 
1019                 (t1
.getTime() - t0
.getTime())/1000 + ' sec client JavaScript)'; 
1021         var info_cmds 
= document
.getElementById('generating_cmd'); 
1022         if (!info_time 
|| !cmds_server
) { 
1025         info_cmds
.firstChild
.data 
+= ' + ' + cmds_server
; 
1029  * show an error message alert to user within page (in progress info area) 
1030  * @param {String} str: plain text error message (no HTML) 
1032  * @globals div_progress_info 
1034 function errorInfo(str
) { 
1035         if (!div_progress_info
) { 
1036                 div_progress_info 
= document
.getElementById('progress_info'); 
1038         if (div_progress_info
) { 
1039                 div_progress_info
.className 
= 'error'; 
1040                 div_progress_info
.firstChild
.data 
= str
; 
1044 /* ............................................................ */ 
1045 /* coloring rows during blame_data (git blame --incremental) run */ 
1048  * used to extract N from 'colorN', where N is a number, 
1051 var colorRe 
= /\bcolor([0-9]*)\b/; 
1054  * return N if <tr class="colorN">, otherwise return null 
1055  * (some browsers require CSS class names to begin with letter) 
1057  * @param {HTMLElement} tr: table row element to check 
1058  * @param {String} tr.className: 'class' attribute of tr element 
1059  * @returns {Number|null} N if tr.className == 'colorN', otherwise null 
1063 function getColorNo(tr
) { 
1067         var className 
= tr
.className
; 
1069                 var match 
= colorRe
.exec(className
); 
1071                         return parseInt(match
[1], 10); 
1077 var colorsFreq 
= [0, 0, 0]; 
1079  * return one of given possible colors (currently least used one) 
1080  * example: chooseColorNoFrom(2, 3) returns 2 or 3 
1082  * @param {Number[]} arguments: one or more numbers 
1083  *        assumes that  1 <= arguments[i] <= colorsFreq.length 
1084  * @returns {Number} Least used color number from arguments 
1085  * @globals colorsFreq 
1087 function chooseColorNoFrom() { 
1088         // choose the color which is least used 
1089         var colorNo 
= arguments
[0]; 
1090         for (var i 
= 1; i 
< arguments
.length
; i
++) { 
1091                 if (colorsFreq
[arguments
[i
]-1] < colorsFreq
[colorNo
-1]) { 
1092                         colorNo 
= arguments
[i
]; 
1095         colorsFreq
[colorNo
-1]++; 
1100  * given two neighbor <tr> elements, find color which would be different 
1101  * from color of both of neighbors; used to 3-color blame table 
1103  * @param {HTMLElement} tr_prev 
1104  * @param {HTMLElement} tr_next 
1105  * @returns {Number} color number N such that 
1106  * colorN != tr_prev.className && colorN != tr_next.className 
1108 function findColorNo(tr_prev
, tr_next
) { 
1109         var color_prev 
= getColorNo(tr_prev
); 
1110         var color_next 
= getColorNo(tr_next
); 
1113         // neither of neighbors has color set 
1114         // THEN we can use any of 3 possible colors 
1115         if (!color_prev 
&& !color_next
) { 
1116                 return chooseColorNoFrom(1,2,3); 
1119         // either both neighbors have the same color, 
1120         // or only one of neighbors have color set 
1121         // THEN we can use any color except given 
1123         if (color_prev 
=== color_next
) { 
1124                 color 
= color_prev
; // = color_next; 
1125         } else if (!color_prev
) { 
1127         } else if (!color_next
) { 
1131                 return chooseColorNoFrom((color 
% 3) + 1, ((color
+1) % 3) + 1); 
1134         // neighbors have different colors 
1135         // THEN there is only one color left 
1136         return (3 - ((color_prev 
+ color_next
) % 3)); 
1139 /* ............................................................ */ 
1140 /* coloring rows like 'blame' after 'blame_data' finishes */ 
1143  * returns true if given row element (tr) is first in commit group 
1144  * to be used only after 'blame_data' finishes (after processing) 
1146  * @param {HTMLElement} tr: table row 
1147  * @returns {Boolean} true if TR is first in commit group 
1149 function isStartOfGroup(tr
) { 
1150         return tr
.firstChild
.className 
=== 'sha1'; 
1154  * change colors to use zebra coloring (2 colors) instead of 3 colors 
1155  * concatenate neighbor commit groups belonging to the same commit 
1159 function fixColorsAndGroups() { 
1160         var colorClasses 
= ['light', 'dark']; 
1165                 document
.getElementById('blame_table') || 
1166                 document
.getElementsByTagName('table')[0]; 
1168         while ((tr 
= document
.getElementById('l'+linenum
))) { 
1169         // index origin is 0, which is table header; start from 1 
1170         //while ((tr = table.rows[linenum])) { // <- it is slower 
1171                 if (isStartOfGroup(tr
, linenum
, document
)) { 
1173                             prev_group
.firstChild
.firstChild
.href 
=== 
1174                                     tr
.firstChild
.firstChild
.href
) { 
1175                                 // we have to concatenate groups 
1176                                 var prev_rows 
= prev_group
.firstChild
.rowSpan 
|| 1; 
1177                                 var curr_rows 
=         tr
.firstChild
.rowSpan 
|| 1; 
1178                                 prev_group
.firstChild
.rowSpan 
= prev_rows 
+ curr_rows
; 
1179                                 //tr.removeChild(tr.firstChild); 
1180                                 tr
.deleteCell(0); // DOM2 HTML way 
1182                                 colorClass 
= (colorClass 
+ 1) % 2; 
1186                 var tr_class 
= tr
.className
; 
1187                 tr
.className 
= tr_class
.replace(colorRe
, colorClasses
[colorClass
]); 
1193 /* ============================================================ */ 
1194 /* main part: parsing response */ 
1197  * Function called for each blame entry, as soon as it finishes. 
1198  * It updates page via DOM manipulation, adding sha1 info, etc. 
1200  * @param {Commit} commit: blamed commit 
1201  * @param {Object} group: object representing group of lines, 
1202  *                        which blame the same commit (blame entry) 
1204  * @globals blamedLines 
1206 function handleLine(commit
, group
) { 
1208            This is the structure of the HTML fragment we are working 
1211            <tr id="l123" class=""> 
1212              <td class="sha1" title=""><a href=""> </a></td> 
1213              <td class="linenr"><a class="linenr" href="">123</a></td> 
1214              <td class="pre"># times (my ext3 doesn't).</td> 
1218         var resline 
= group
.resline
; 
1220         // format date and time string only once per commit 
1222                 /* e.g. 'Kay Sievers, 2005-08-07 21:49:46 +0200' */ 
1223                 commit
.info 
= commit
.author 
+ ', ' + 
1224                         formatDateISOLocal(commit
.authorTime
, commit
.authorTimezone
); 
1227         // color depends on group of lines, not only on blamed commit 
1228         var colorNo 
= findColorNo( 
1229                 document
.getElementById('l'+(resline
-1)), 
1230                 document
.getElementById('l'+(resline
+group
.numlines
)) 
1233         // loop over lines in commit group 
1234         for (var i 
= 0; i 
< group
.numlines
; i
++, resline
++) { 
1235                 var tr 
= document
.getElementById('l'+resline
); 
1240                         <tr id="l123" class=""> 
1241                           <td class="sha1" title=""><a href=""> </a></td> 
1242                           <td class="linenr"><a class="linenr" href="">123</a></td> 
1243                           <td class="pre"># times (my ext3 doesn't).</td> 
1246                 var td_sha1  
= tr
.firstChild
; 
1247                 var a_sha1   
= td_sha1
.firstChild
; 
1248                 var a_linenr 
= td_sha1
.nextSibling
.firstChild
; 
1250                 /* <tr id="l123" class=""> */ 
1252                 if (colorNo 
!== null) { 
1253                         tr_class 
= 'color'+colorNo
; 
1255                 if (commit
.boundary
) { 
1256                         tr_class 
+= ' boundary'; 
1258                 if (commit
.nprevious 
=== 0) { 
1259                         tr_class 
+= ' no-previous'; 
1260                 } else if (commit
.nprevious 
> 1) { 
1261                         tr_class 
+= ' multiple-previous'; 
1263                 tr
.className 
= tr_class
; 
1265                 /* <td class="sha1" title="?" rowspan="?"><a href="?">?</a></td> */ 
1267                         td_sha1
.title 
= commit
.info
; 
1268                         td_sha1
.rowSpan 
= group
.numlines
; 
1270                         a_sha1
.href 
= projectUrl 
+ 'a=commit;h=' + commit
.sha1
; 
1271                         if (a_sha1
.firstChild
) { 
1272                                 a_sha1
.firstChild
.data 
= commit
.sha1
.substr(0, 8); 
1275                                         document
.createTextNode(commit
.sha1
.substr(0, 8))); 
1277                         if (group
.numlines 
>= 2) { 
1278                                 var fragment 
= document
.createDocumentFragment(); 
1279                                 var br   
= document
.createElement("br"); 
1280                                 var match 
= commit
.author
.match(/\b([A-Z])\B/g); 
1282                                         var text 
= document
.createTextNode( 
1286                                         var elem 
= fragment 
|| td_sha1
; 
1287                                         elem
.appendChild(br
); 
1288                                         elem
.appendChild(text
); 
1290                                                 td_sha1
.appendChild(fragment
); 
1295                         //tr.removeChild(td_sha1); // DOM2 Core way 
1296                         tr
.deleteCell(0); // DOM2 HTML way 
1299                 /* <td class="linenr"><a class="linenr" href="?">123</a></td> */ 
1301                         ('previous' in commit 
? commit
.previous : commit
.sha1
); 
1302                 var linenr_filename 
= 
1303                         ('file_parent' in commit 
? commit
.file_parent : commit
.filename
); 
1304                 a_linenr
.href 
= projectUrl 
+ 'a=blame_incremental' + 
1305                         ';hb=' + linenr_commit 
+ 
1306                         ';f='  + encodeURIComponent(linenr_filename
) + 
1307                         '#l' + (group
.srcline 
+ i
); 
1311                 //updateProgressInfo(); 
1315 // ---------------------------------------------------------------------- 
1320 var sha1Re 
= /^([0-9a
-f
]{40}) ([0-9]+) ([0-9]+) ([0-9]+)/; 
1321 var infoRe 
= /^([a
-z
-]+) ?(.*)/; 
1322 var endRe  
= /^END 
?([^ ]*) ?(.*)/; 
1325 var curCommit 
= new Commit(); 
1329  * Parse output from 'git blame --incremental [...]', received via 
1330  * XMLHttpRequest from server (blamedataUrl), and call handleLine 
1331  * (which updates page) as soon as blame entry is completed. 
1333  * @param {String[]} lines: new complete lines from blamedata server 
1335  * @globals commits, curCommit, curGroup, t_interval_server, cmds_server 
1336  * @globals sha1Re, infoRe, endRe 
1338 function processBlameLines(lines
) { 
1341         for (var i 
= 0, len 
= lines
.length
; i 
< len
; i
++) { 
1343                 if ((match 
= sha1Re
.exec(lines
[i
]))) { 
1344                         var sha1 
= match
[1]; 
1345                         var srcline  
= parseInt(match
[2], 10); 
1346                         var resline  
= parseInt(match
[3], 10); 
1347                         var numlines 
= parseInt(match
[4], 10); 
1349                         var c 
= commits
[sha1
]; 
1351                                 c 
= new Commit(sha1
); 
1356                         curGroup
.srcline 
= srcline
; 
1357                         curGroup
.resline 
= resline
; 
1358                         curGroup
.numlines 
= numlines
; 
1360                 } else if ((match 
= infoRe
.exec(lines
[i
]))) { 
1361                         var info 
= match
[1]; 
1362                         var data 
= match
[2]; 
1365                                 curCommit
.filename 
= unquote(data
); 
1366                                 // 'filename' information terminates the entry 
1367                                 handleLine(curCommit
, curGroup
); 
1368                                 updateProgressInfo(); 
1371                                 curCommit
.author 
= data
; 
1374                                 curCommit
.authorTime 
= parseInt(data
, 10); 
1377                                 curCommit
.authorTimezone 
= data
; 
1380                                 curCommit
.nprevious
++; 
1381                                 // store only first 'previous' header 
1382                                 if (!('previous' in curCommit
)) { 
1383                                         var parts 
= data
.split(' ', 2); 
1384                                         curCommit
.previous    
= parts
[0]; 
1385                                         curCommit
.file_parent 
= unquote(parts
[1]); 
1389                                 curCommit
.boundary 
= true; 
1393                 } else if ((match 
= endRe
.exec(lines
[i
]))) { 
1394                         t_interval_server 
= match
[1]; 
1395                         cmds_server 
= match
[2]; 
1397                 } else if (lines
[i
] !== '') { 
1402         } // end for (lines) 
1406  * Process new data and return pointer to end of processed part 
1408  * @param {String} unprocessed: new data (from nextReadPos) 
1409  * @param {Number} nextReadPos: end of last processed data 
1410  * @return {Number} end of processed data (new value for nextReadPos) 
1412 function processData(unprocessed
, nextReadPos
) { 
1413         var lastLineEnd 
= unprocessed
.lastIndexOf('\n'); 
1414         if (lastLineEnd 
!== -1) { 
1415                 var lines 
= unprocessed
.substring(0, lastLineEnd
).split('\n'); 
1416                 nextReadPos 
+= lastLineEnd 
+ 1 /* 1 == '\n'.length */; 
1418                 processBlameLines(lines
); 
1425  * Handle XMLHttpRequest errors 
1427  * @param {XMLHttpRequest} xhr: XMLHttpRequest object 
1428  * @param {Number} [xhr.pollTimer] ID of the timeout to clear 
1432 function handleError(xhr
) { 
1433         errorInfo('Server error: ' + 
1434                 xhr
.status 
+ ' - ' + (xhr
.statusText 
|| 'Error contacting server')); 
1436         if (typeof xhr
.pollTimer 
=== "number") { 
1437                 clearTimeout(xhr
.pollTimer
); 
1438                 delete xhr
.pollTimer
; 
1440         commits 
= {}; // free memory 
1444  * Called after XMLHttpRequest finishes (loads) 
1446  * @param {XMLHttpRequest} xhr: XMLHttpRequest object 
1447  * @param {Number} [xhr.pollTimer] ID of the timeout to clear 
1451 function responseLoaded(xhr
) { 
1452         if (typeof xhr
.pollTimer 
=== "number") { 
1453                 clearTimeout(xhr
.pollTimer
); 
1454                 delete xhr
.pollTimer
; 
1457         fixColorsAndGroups(); 
1458         writeTimeInterval(); 
1459         commits 
= {}; // free memory 
1463  * handler for XMLHttpRequest onreadystatechange event 
1466  * @param {XMLHttpRequest} xhr: XMLHttpRequest object 
1467  * @param {Number} xhr.prevDataLength: previous value of xhr.responseText.length 
1468  * @param {Number} xhr.nextReadPos: start of unread part of xhr.responseText 
1469  * @param {Number} [xhr.pollTimer] ID of the timeout (to reset or cancel) 
1470  * @param {Boolean} fromTimer: if handler was called from timer 
1472 function handleResponse(xhr
, fromTimer
) { 
1477          *  Value  Constant (W3C)    Description 
1478          *  ------------------------------------------------------------------- 
1479          *  0      UNSENT            open() has not been called yet. 
1480          *  1      OPENED            send() has not been called yet. 
1481          *  2      HEADERS_RECEIVED  send() has been called, and headers 
1482          *                           and status are available. 
1483          *  3      LOADING           Downloading; responseText holds partial data. 
1484          *  4      DONE              The operation is complete. 
1487         if (xhr
.readyState 
!== 4 && xhr
.readyState 
!== 3) { 
1491         // the server returned error 
1492         // try ... catch block is to work around bug in IE8 
1494                 if (xhr
.readyState 
=== 3 && xhr
.status 
!== 200) { 
1500         if (xhr
.readyState 
=== 4 && xhr
.status 
!== 200) { 
1505         // In konqueror xhr.responseText is sometimes null here... 
1506         if (xhr
.responseText 
=== null) { 
1511         // extract new whole (complete) lines, and process them 
1512         if (xhr
.prevDataLength 
!== xhr
.responseText
.length
) { 
1513                 xhr
.prevDataLength 
= xhr
.responseText
.length
; 
1514                 var unprocessed 
= xhr
.responseText
.substring(xhr
.nextReadPos
); 
1515                 xhr
.nextReadPos 
= processData(unprocessed
, xhr
.nextReadPos
); 
1518         // did we finish work? 
1519         if (xhr
.readyState 
=== 4) { 
1520                 responseLoaded(xhr
); 
1524         // if we get from timer, we have to restart it 
1525         // otherwise onreadystatechange gives us partial response, timer not needed 
1527                 setTimeout(function () { 
1528                         handleResponse(xhr
, true); 
1531         } else if (typeof xhr
.pollTimer 
=== "number") { 
1532                 clearTimeout(xhr
.pollTimer
); 
1533                 delete xhr
.pollTimer
; 
1537 // ============================================================ 
1538 // ------------------------------------------------------------ 
1541  * Incrementally update line data in blame_incremental view in gitweb. 
1543  * @param {String} blamedataUrl: URL to server script generating blame data. 
1544  * @param {String} bUrl: partial URL to project, used to generate links. 
1546  * Called from 'blame_incremental' view after loading table with 
1547  * file contents, a base for blame view. 
1549  * @globals t0, projectUrl, div_progress_bar, totalLines 
1551 function startBlame(blamedataUrl
, bUrl
) { 
1553         var xhr 
= createRequestObject(); 
1555                 errorInfo('ERROR: XMLHttpRequest not supported'); 
1560         projectUrl 
= bUrl 
+ (bUrl
.indexOf('?') === -1 ? '?' : ';'); 
1561         if ((div_progress_bar 
= document
.getElementById('progress_bar'))) { 
1562                 //div_progress_bar.setAttribute('style', 'width: 100%;'); 
1563                 div_progress_bar
.style
.cssText 
= 'width: 100%;'; 
1565         totalLines 
= countLines(); 
1566         updateProgressInfo(); 
1568         /* add extra properties to xhr object to help processing response */ 
1569         xhr
.prevDataLength 
= -1;  // used to detect if we have new data 
1570         xhr
.nextReadPos 
= 0;      // where unread part of response starts 
1572         xhr
.onreadystatechange = function () { 
1573                 handleResponse(xhr
, false); 
1576         xhr
.open('GET', blamedataUrl
); 
1577         xhr
.setRequestHeader('Accept', 'text/plain'); 
1580         // not all browsers call onreadystatechange event on each server flush 
1581         // poll response using timer every second to handle this issue 
1582         xhr
.pollTimer 
= setTimeout(function () { 
1583                 handleResponse(xhr
, true); 
1587 /* end of blame_incremental.js */ 
 
This page took 0.240525 seconds  and 5 git commands  to generate.