1
+// Copyright (C) 2007, Fredrik Kuivinen <frekui@gmail.com>
2
+// 2007, Petr Baudis <pasky@suse.cz>
3
+// 2008-2009, Jakub Narebski <jnareb@gmail.com>
4
+
5
+/**
6
+ * @fileOverview JavaScript code for gitweb (git web interface).
7
+ * @license GPLv2 or later
8
+ */
9
+
10
+/*
11
+ * This code uses DOM methods instead of (nonstandard) innerHTML
12
+ * to modify page.
13
+ *
14
+ * innerHTML is non-standard IE extension, though supported by most
15
+ * browsers; however Firefox up to version 1.5 didn't implement it in
16
+ * a strict mode (application/xml+xhtml mimetype).
17
+ *
18
+ * Also my simple benchmarks show that using elem.firstChild.data =
19
+ * 'content' is slightly faster than elem.innerHTML = 'content'. It
20
+ * is however more fragile (text element fragment must exists), and
21
+ * less feature-rich (we cannot add HTML).
22
+ *
23
+ * Note that DOM 2 HTML is preferred over generic DOM 2 Core; the
24
+ * equivalent using DOM 2 Core is usually shown in comments.
25
+ */
26
+
27
+
28
+/* ============================================================ */
29
+/* generic utility functions */
30
+
31
+
32
+/**
33
+ * pad number N with nonbreakable spaces on the left, to WIDTH characters
34
+ * example: padLeftStr(12, 3, ' ') == ' 12'
35
+ * (' ' is nonbreakable space)
36
+ *
37
+ * @param {Number|String} input: number to pad
38
+ * @param {Number} width: visible width of output
39
+ * @param {String} str: string to prefix to string, e.g. ' '
40
+ * @returns {String} INPUT prefixed with (WIDTH - INPUT.length) x STR
41
+ */
42
+function padLeftStr(input, width, str) {
43
+ var prefix = '';
44
+
45
+ width -= input.toString().length;
46
+ while (width > 1) {
47
+ prefix += str;
48
+ width--;
49
+ }
50
+ return prefix + input;
51
+}
52
+
53
+/**
54
+ * Pad INPUT on the left to SIZE width, using given padding character CH,
55
+ * for example padLeft('a', 3, '_') is '__a'.
56
+ *
57
+ * @param {String} input: input value converted to string.
58
+ * @param {Number} width: desired length of output.
59
+ * @param {String} ch: single character to prefix to string.
60
+ *
61
+ * @returns {String} Modified string, at least SIZE length.
62
+ */
63
+function padLeft(input, width, ch) {
64
+ var s = input + "";
65
+ while (s.length < width) {
66
+ s = ch + s;
67
+ }
68
+ return s;
69
+}
70
+
71
+/**
72
+ * Create XMLHttpRequest object in cross-browser way
73
+ * @returns XMLHttpRequest object, or null
74
+ */
75
+function createRequestObject() {
76
+ try {
77
+ return new XMLHttpRequest();
78
+ } catch (e) {}
79
+ try {
80
+ return window.createRequest();
81
+ } catch (e) {}
82
+ try {
83
+ return new ActiveXObject("Msxml2.XMLHTTP");
84
+ } catch (e) {}
85
+ try {
86
+ return new ActiveXObject("Microsoft.XMLHTTP");
87
+ } catch (e) {}
88
+
89
+ return null;
90
+}
91
+
92
+/* ============================================================ */
93
+/* utility/helper functions (and variables) */
94
+
95
+var xhr; // XMLHttpRequest object
96
+var projectUrl; // partial query + separator ('?' or ';')
97
+
98
+// 'commits' is an associative map. It maps SHA1s to Commit objects.
99
+var commits = {};
100
+
101
+/**
102
+ * constructor for Commit objects, used in 'blame'
103
+ * @class Represents a blamed commit
104
+ * @param {String} sha1: SHA-1 identifier of a commit
105
+ */
106
+function Commit(sha1) {
107
+ if (this instanceof Commit) {
108
+ this.sha1 = sha1;
109
+ this.nprevious = 0; /* number of 'previous', effective parents */
110
+ } else {
111
+ return new Commit(sha1);
112
+ }
113
+}
114
+
115
+/* ............................................................ */
116
+/* progress info, timing, error reporting */
117
+
118
+var blamedLines = 0;
119
+var totalLines = '???';
120
+var div_progress_bar;
121
+var div_progress_info;
122
+
123
+/**
124
+ * Detects how many lines does a blamed file have,
125
+ * This information is used in progress info
126
+ *
127
+ * @returns {Number|String} Number of lines in file, or string '...'
128
+ */
129
+function countLines() {
130
+ var table =
131
+ document.getElementById('blame_table') ||
132
+ document.getElementsByTagName('table')[0];
133
+
134
+ if (table) {
135
+ return table.getElementsByTagName('tr').length - 1; // for header
136
+ } else {
137
+ return '...';
138
+ }
139
+}
140
+
141
+/**
142
+ * update progress info and length (width) of progress bar
143
+ *
144
+ * @globals div_progress_info, div_progress_bar, blamedLines, totalLines
145
+ */
146
+function updateProgressInfo() {
147
+ if (!div_progress_info) {
148
+ div_progress_info = document.getElementById('progress_info');
149
+ }
150
+ if (!div_progress_bar) {
151
+ div_progress_bar = document.getElementById('progress_bar');
152
+ }
153
+ if (!div_progress_info && !div_progress_bar) {
154
+ return;
155
+ }
156
+
157
+ var percentage = Math.floor(100.0*blamedLines/totalLines);
158
+
159
+ if (div_progress_info) {
160
+ div_progress_info.firstChild.data = blamedLines + ' / ' + totalLines +
161
+ ' (' + padLeftStr(percentage, 3, ' ') + '%)';
162
+ }
163
+
164
+ if (div_progress_bar) {
165
+ //div_progress_bar.setAttribute('style', 'width: '+percentage+'%;');
166
+ div_progress_bar.style.width = percentage + '%';
167
+ }
168
+}
169
+
170
+
171
+var t_interval_server = '';
172
+var cmds_server = '';
173
+var t0 = new Date();
174
+
175
+/**
176
+ * write how much it took to generate data, and to run script
177
+ *
178
+ * @globals t0, t_interval_server, cmds_server
179
+ */
180
+function writeTimeInterval() {
181
+ var info_time = document.getElementById('generating_time');
182
+ if (!info_time || !t_interval_server) {
183
+ return;
184
+ }
185
+ var t1 = new Date();
186
+ info_time.firstChild.data += ' + (' +
187
+ t_interval_server + ' sec server blame_data / ' +
188
+ (t1.getTime() - t0.getTime())/1000 + ' sec client JavaScript)';
189
+
190
+ var info_cmds = document.getElementById('generating_cmd');
191
+ if (!info_time || !cmds_server) {
192
+ return;
193
+ }
194
+ info_cmds.firstChild.data += ' + ' + cmds_server;
195
+}
196
+
197
+/**
198
+ * show an error message alert to user within page (in prohress info area)
199
+ * @param {String} str: plain text error message (no HTML)
200
+ *
201
+ * @globals div_progress_info
202
+ */
203
+function errorInfo(str) {
204
+ if (!div_progress_info) {
205
+ div_progress_info = document.getElementById('progress_info');
206
+ }
207
+ if (div_progress_info) {
208
+ div_progress_info.className = 'error';
209
+ div_progress_info.firstChild.data = str;
210
+ }
211
+}
212
+
213
+/* ............................................................ */
214
+/* coloring rows like 'blame' after 'blame_data' finishes */
215
+
216
+/**
217
+ * returns true if given row element (tr) is first in commit group
218
+ * to be used only after 'blame_data' finishes (after processing)
219
+ *
220
+ * @param {HTMLElement} tr: table row
221
+ * @returns {Boolean} true if TR is first in commit group
222
+ */
223
+function isStartOfGroup(tr) {
224
+ return tr.firstChild.className === 'sha1';
225
+}
226
+
227
+var colorRe = /(?:light|dark)/;
228
+
229
+/**
230
+ * change colors to use zebra coloring (2 colors) instead of 3 colors
231
+ * concatenate neighbour commit groups belonging to the same commit
232
+ *
233
+ * @globals colorRe
234
+ */
235
+function fixColorsAndGroups() {
236
+ var colorClasses = ['light', 'dark'];
237
+ var linenum = 1;
238
+ var tr, prev_group;
239
+ var colorClass = 0;
240
+ var table =
241
+ document.getElementById('blame_table') ||
242
+ document.getElementsByTagName('table')[0];
243
+
244
+ while ((tr = document.getElementById('l'+linenum))) {
245
+ // index origin is 0, which is table header; start from 1
246
+ //while ((tr = table.rows[linenum])) { // <- it is slower
247
+ if (isStartOfGroup(tr, linenum, document)) {
248
+ if (prev_group &&
249
+ prev_group.firstChild.firstChild.href ===
250
+ tr.firstChild.firstChild.href) {
251
+ // we have to concatenate groups
252
+ var prev_rows = prev_group.firstChild.rowSpan || 1;
253
+ var curr_rows = tr.firstChild.rowSpan || 1;
254
+ prev_group.firstChild.rowSpan = prev_rows + curr_rows;
255
+ //tr.removeChild(tr.firstChild);
256
+ tr.deleteCell(0); // DOM2 HTML way
257
+ } else {
258
+ colorClass = (colorClass + 1) % 2;
259
+ prev_group = tr;
260
+ }
261
+ }
262
+ var tr_class = tr.className;
263
+ tr.className = tr_class.replace(colorRe, colorClasses[colorClass]);
264
+ linenum++;
265
+ }
266
+}
267
+
268
+/* ............................................................ */
269
+/* time and data */
270
+
271
+/**
272
+ * used to extract hours and minutes from timezone info, e.g '-0900'
273
+ * @constant
274
+ */
275
+var tzRe = /^([+-][0-9][0-9])([0-9][0-9])$/;
276
+
277
+/**
278
+ * return date in local time formatted in iso-8601 like format
279
+ * 'yyyy-mm-dd HH:MM:SS +/-ZZZZ' e.g. '2005-08-07 21:49:46 +0200'
280
+ *
281
+ * @param {Number} epoch: seconds since '00:00:00 1970-01-01 UTC'
282
+ * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM'
283
+ * @returns {String} date in local time in iso-8601 like format
284
+ *
285
+ * @globals tzRe
286
+ */
287
+function formatDateISOLocal(epoch, timezoneInfo) {
288
+ var match = tzRe.exec(timezoneInfo);
289
+ // date corrected by timezone
290
+ var localDate = new Date(1000 * (epoch +
291
+ (parseInt(match[1],10)*3600 + parseInt(match[2],10)*60)));
292
+ var localDateStr = // e.g. '2005-08-07'
293
+ localDate.getUTCFullYear() + '-' +
294
+ padLeft(localDate.getUTCMonth()+1, 2, '0') + '-' +
295
+ padLeft(localDate.getUTCDate(), 2, '0');
296
+ var localTimeStr = // e.g. '21:49:46'
297
+ padLeft(localDate.getUTCHours(), 2, '0') + ':' +
298
+ padLeft(localDate.getUTCMinutes(), 2, '0') + ':' +
299
+ padLeft(localDate.getUTCSeconds(), 2, '0');
300
+
301
+ return localDateStr + ' ' + localTimeStr + ' ' + timezoneInfo;
302
+}
303
+
304
+/* ............................................................ */
305
+/* unquoting/unescaping filenames */
306
+
307
+/**#@+
308
+ * @constant
309
+ */
310
+var escCodeRe = /\\([^0-7]|[0-7]{1,3})/g;
311
+var octEscRe = /^[0-7]{1,3}$/;
312
+var maybeQuotedRe = /^\"(.*)\"$/;
313
+/**#@-*/
314
+
315
+/**
316
+ * unquote maybe git-quoted filename
317
+ * e.g. 'aa' -> 'aa', '"a\ta"' -> 'a a'
318
+ *
319
+ * @param {String} str: git-quoted string
320
+ * @returns {String} Unquoted and unescaped string
321
+ *
322
+ * @globals escCodeRe, octEscRe, maybeQuotedRe
323
+ */
324
+function unquote(str) {
325
+ function unq(seq) {
326
+ var es = {
327
+ // character escape codes, aka escape sequences (from C)
328
+ // replacements are to some extent JavaScript specific
329
+ t: "\t", // tab (HT, TAB)
330
+ n: "\n", // newline (NL)
331
+ r: "\r", // return (CR)
332
+ f: "\f", // form feed (FF)
333
+ b: "\b", // backspace (BS)
334
+ a: "\x07", // alarm (bell) (BEL)
335
+ e: "\x1B", // escape (ESC)
336
+ v: "\v" // vertical tab (VT)
337
+ };
338
+
339
+ if (seq.search(octEscRe) !== -1) {
340
+ // octal char sequence
341
+ return String.fromCharCode(parseInt(seq, 8));
342
+ } else if (seq in es) {
343
+ // C escape sequence, aka character escape code
344
+ return es[seq];
345
+ }
346
+ // quoted ordinary character
347
+ return seq;
348
+ }
349
+
350
+ var match = str.match(maybeQuotedRe);
351
+ if (match) {
352
+ str = match[1];
353
+ // perhaps str = eval('"'+str+'"'); would be enough?
354
+ str = str.replace(escCodeRe,
355
+ function (substr, p1, offset, s) { return unq(p1); });
356
+ }
357
+ return str;
358
+}
359
+
360
+/* ============================================================ */
361
+/* main part: parsing response */
362
+
363
+/**
364
+ * Function called for each blame entry, as soon as it finishes.
365
+ * It updates page via DOM manipulation, adding sha1 info, etc.
366
+ *
367
+ * @param {Commit} commit: blamed commit
368
+ * @param {Object} group: object representing group of lines,
369
+ * which blame the same commit (blame entry)
370
+ *
371
+ * @globals blamedLines
372
+ */
373
+function handleLine(commit, group) {
374
+ /*
375
+ This is the structure of the HTML fragment we are working
376
+ with:
377
+
378
+ <tr id="l123" class="">
379
+ <td class="sha1" title=""><a href=""> </a></td>
380
+ <td class="linenr"><a class="linenr" href="">123</a></td>
381
+ <td class="pre"># times (my ext3 doesn't).</td>
382
+ </tr>
383
+ */
384
+
385
+ var resline = group.resline;
386
+
387
+ // format date and time string only once per commit
388
+ if (!commit.info) {
389
+ /* e.g. 'Kay Sievers, 2005-08-07 21:49:46 +0200' */
390
+ commit.info = commit.author + ', ' +
391
+ formatDateISOLocal(commit.authorTime, commit.authorTimezone);
392
+ }
393
+
394
+ // loop over lines in commit group
395
+ for (var i = 0; i < group.numlines; i++, resline++) {
396
+ var tr = document.getElementById('l'+resline);
397
+ if (!tr) {
398
+ break;
399
+ }
400
+ /*
401
+ <tr id="l123" class="">
402
+ <td class="sha1" title=""><a href=""> </a></td>
403
+ <td class="linenr"><a class="linenr" href="">123</a></td>
404
+ <td class="pre"># times (my ext3 doesn't).</td>
405
+ </tr>
406
+ */
407
+ var td_sha1 = tr.firstChild;
408
+ var a_sha1 = td_sha1.firstChild;
409
+ var a_linenr = td_sha1.nextSibling.firstChild;
410
+
411
+ /* <tr id="l123" class=""> */
412
+ var tr_class = 'light'; // or tr.className
413
+ if (commit.boundary) {
414
+ tr_class += ' boundary';
415
+ }
416
+ if (commit.nprevious === 0) {
417
+ tr_class += ' no-previous';
418
+ } else if (commit.nprevious > 1) {
419
+ tr_class += ' multiple-previous';
420
+ }
421
+ tr.className = tr_class;
422
+
423
+ /* <td class="sha1" title="?" rowspan="?"><a href="?">?</a></td> */
424
+ if (i === 0) {
425
+ td_sha1.title = commit.info;
426
+ td_sha1.rowSpan = group.numlines;
427
+
428
+ a_sha1.href = projectUrl + 'a=commit;h=' + commit.sha1;
429
+ a_sha1.firstChild.data = commit.sha1.substr(0, 8);
430
+ if (group.numlines >= 2) {
431
+ var fragment = document.createDocumentFragment();
432
+ var br = document.createElement("br");
433
+ var text = document.createTextNode(
434
+ commit.author.match(/\b([A-Z])\B/g).join(''));
435
+ if (br && text) {
436
+ var elem = fragment || td_sha1;
437
+ elem.appendChild(br);
438
+ elem.appendChild(text);
439
+ if (fragment) {
440
+ td_sha1.appendChild(fragment);
441
+ }
442
+ }
443
+ }
444
+ } else {
445
+ //tr.removeChild(td_sha1); // DOM2 Core way
446
+ tr.deleteCell(0); // DOM2 HTML way
447
+ }
448
+
449
+ /* <td class="linenr"><a class="linenr" href="?">123</a></td> */
450
+ var linenr_commit =
451
+ ('previous' in commit ? commit.previous : commit.sha1);
452
+ var linenr_filename =
453
+ ('file_parent' in commit ? commit.file_parent : commit.filename);
454
+ a_linenr.href = projectUrl + 'a=blame_incremental' +
455
+ ';hb=' + linenr_commit +
456
+ ';f=' + encodeURIComponent(linenr_filename) +
457
+ '#l' + (group.srcline + i);
458
+
459
+ blamedLines++;
460
+
461
+ //updateProgressInfo();
462
+ }
463
+}
464
+
465
+// ----------------------------------------------------------------------
466
+
467
+var inProgress = false; // are we processing response
468
+
469
+/**#@+
470
+ * @constant
471
+ */
472
+var sha1Re = /^([0-9a-f]{40}) ([0-9]+) ([0-9]+) ([0-9]+)/;
473
+var infoRe = /^([a-z-]+) ?(.*)/;
474
+var endRe = /^END ?([^ ]*) ?(.*)/;
475
+/**@-*/
476
+
477
+var curCommit = new Commit();
478
+var curGroup = {};
479
+
480
+var pollTimer = null;
481
+
482
+/**
483
+ * Parse output from 'git blame --incremental [...]', received via
484
+ * XMLHttpRequest from server (blamedataUrl), and call handleLine
485
+ * (which updates page) as soon as blame entry is completed.
486
+ *
487
+ * @param {String[]} lines: new complete lines from blamedata server
488
+ *
489
+ * @globals commits, curCommit, curGroup, t_interval_server, cmds_server
490
+ * @globals sha1Re, infoRe, endRe
491
+ */
492
+function processBlameLines(lines) {
493
+ var match;
494
+
495
+ for (var i = 0, len = lines.length; i < len; i++) {
496
+
497
+ if ((match = sha1Re.exec(lines[i]))) {
498
+ var sha1 = match[1];
499
+ var srcline = parseInt(match[2], 10);
500
+ var resline = parseInt(match[3], 10);
501
+ var numlines = parseInt(match[4], 10);
502
+
503
+ var c = commits[sha1];
504
+ if (!c) {
505
+ c = new Commit(sha1);
506
+ commits[sha1] = c;
507
+ }
508
+ curCommit = c;
509
+
510
+ curGroup.srcline = srcline;
511
+ curGroup.resline = resline;
512
+ curGroup.numlines = numlines;
513
+
514
+ } else if ((match = infoRe.exec(lines[i]))) {
515
+ var info = match[1];
516
+ var data = match[2];
517
+ switch (info) {
518
+ case 'filename':
519
+ curCommit.filename = unquote(data);
520
+ // 'filename' information terminates the entry
521
+ handleLine(curCommit, curGroup);
522
+ updateProgressInfo();
523
+ break;
524
+ case 'author':
525
+ curCommit.author = data;
526
+ break;
527
+ case 'author-time':
528
+ curCommit.authorTime = parseInt(data, 10);
529
+ break;
530
+ case 'author-tz':
531
+ curCommit.authorTimezone = data;
532
+ break;
533
+ case 'previous':
534
+ curCommit.nprevious++;
535
+ // store only first 'previous' header
536
+ if (!'previous' in curCommit) {
537
+ var parts = data.split(' ', 2);
538
+ curCommit.previous = parts[0];
539
+ curCommit.file_parent = unquote(parts[1]);
540
+ }
541
+ break;
542
+ case 'boundary':
543
+ curCommit.boundary = true;
544
+ break;
545
+ } // end switch
546
+
547
+ } else if ((match = endRe.exec(lines[i]))) {
548
+ t_interval_server = match[1];
549
+ cmds_server = match[2];
550
+
551
+ } else if (lines[i] !== '') {
552
+ // malformed line
553
+
554
+ } // end if (match)
555
+
556
+ } // end for (lines)
557
+}
558
+
559
+/**
560
+ * Process new data and return pointer to end of processed part
561
+ *
562
+ * @param {String} unprocessed: new data (from nextReadPos)
563
+ * @param {Number} nextReadPos: end of last processed data
564
+ * @return {Number} end of processed data (new value for nextReadPos)
565
+ */
566
+function processData(unprocessed, nextReadPos) {
567
+ var lastLineEnd = unprocessed.lastIndexOf('\n');
568
+ if (lastLineEnd !== -1) {
569
+ var lines = unprocessed.substring(0, lastLineEnd).split('\n');
570
+ nextReadPos += lastLineEnd + 1 /* 1 == '\n'.length */;
571
+
572
+ processBlameLines(lines);
573
+ } // end if
574
+
575
+ return nextReadPos;
576
+}
577
+
578
+/**
579
+ * Handle XMLHttpRequest errors
580
+ *
581
+ * @param {XMLHttpRequest} xhr: XMLHttpRequest object
582
+ *
583
+ * @globals pollTimer, commits, inProgress
584
+ */
585
+function handleError(xhr) {
586
+ errorInfo('Server error: ' +
587
+ xhr.status + ' - ' + (xhr.statusText || 'Error contacting server'));
588
+
589
+ clearInterval(pollTimer);
590
+ commits = {}; // free memory
591
+
592
+ inProgress = false;
593
+}
594
+
595
+/**
596
+ * Called after XMLHttpRequest finishes (loads)
597
+ *
598
+ * @param {XMLHttpRequest} xhr: XMLHttpRequest object (unused)
599
+ *
600
+ * @globals pollTimer, commits, inProgress
601
+ */
602
+function responseLoaded(xhr) {
603
+ clearInterval(pollTimer);
604
+
605
+ fixColorsAndGroups();
606
+ writeTimeInterval();
607
+ commits = {}; // free memory
608
+
609
+ inProgress = false;
610
+}
611
+
612
+/**
613
+ * handler for XMLHttpRequest onreadystatechange event
614
+ * @see startBlame
615
+ *
616
+ * @globals xhr, inProgress
617
+ */
618
+function handleResponse() {
619
+
620
+ /*
621
+ * xhr.readyState
622
+ *
623
+ * Value Constant (W3C) Description
624
+ * -------------------------------------------------------------------
625
+ * 0 UNSENT open() has not been called yet.
626
+ * 1 OPENED send() has not been called yet.
627
+ * 2 HEADERS_RECEIVED send() has been called, and headers
628
+ * and status are available.
629
+ * 3 LOADING Downloading; responseText holds partial data.
630
+ * 4 DONE The operation is complete.
631
+ */
632
+
633
+ if (xhr.readyState !== 4 && xhr.readyState !== 3) {
634
+ return;
635
+ }
636
+
637
+ // the server returned error
638
+ if (xhr.readyState === 3 && xhr.status !== 200) {
639
+ return;
640
+ }
641
+ if (xhr.readyState === 4 && xhr.status !== 200) {
642
+ handleError(xhr);
643
+ return;
644
+ }
645
+
646
+ // In konqueror xhr.responseText is sometimes null here...
647
+ if (xhr.responseText === null) {
648
+ return;
649
+ }
650
+
651
+ // in case we were called before finished processing
652
+ if (inProgress) {
653
+ return;
654
+ } else {
655
+ inProgress = true;
656
+ }
657
+
658
+ // extract new whole (complete) lines, and process them
659
+ while (xhr.prevDataLength !== xhr.responseText.length) {
660
+ if (xhr.readyState === 4 &&
661
+ xhr.prevDataLength === xhr.responseText.length) {
662
+ break;
663
+ }
664
+
665
+ xhr.prevDataLength = xhr.responseText.length;
666
+ var unprocessed = xhr.responseText.substring(xhr.nextReadPos);
667
+ xhr.nextReadPos = processData(unprocessed, xhr.nextReadPos);
668
+ } // end while
669
+
670
+ // did we finish work?
671
+ if (xhr.readyState === 4 &&
672
+ xhr.prevDataLength === xhr.responseText.length) {
673
+ responseLoaded(xhr);
674
+ }
675
+
676
+ inProgress = false;
677
+}
678
+
679
+// ============================================================
680
+// ------------------------------------------------------------
681
+
682
+/**
683
+ * Incrementally update line data in blame_incremental view in gitweb.
684
+ *
685
+ * @param {String} blamedataUrl: URL to server script generating blame data.
686
+ * @param {String} bUrl: partial URL to project, used to generate links.
687
+ *
688
+ * Called from 'blame_incremental' view after loading table with
689
+ * file contents, a base for blame view.
690
+ *
691
+ * @globals xhr, t0, projectUrl, div_progress_bar, totalLines, pollTimer
692
+*/
693
+function startBlame(blamedataUrl, bUrl) {
694
+
695
+ xhr = createRequestObject();
696
+ if (!xhr) {
697
+ errorInfo('ERROR: XMLHttpRequest not supported');
698
+ return;
699
+ }
700
+
701
+ t0 = new Date();
702
+ projectUrl = bUrl + (bUrl.indexOf('?') === -1 ? '?' : ';');
703
+ if ((div_progress_bar = document.getElementById('progress_bar'))) {
704
+ //div_progress_bar.setAttribute('style', 'width: 100%;');
705
+ div_progress_bar.style.cssText = 'width: 100%;';
706
+ }
707
+ totalLines = countLines();
708
+ updateProgressInfo();
709
+
710
+ /* add extra properties to xhr object to help processing response */
711
+ xhr.prevDataLength = -1; // used to detect if we have new data
712
+ xhr.nextReadPos = 0; // where unread part of response starts
713
+
714
+ xhr.onreadystatechange = handleResponse;
715
+ //xhr.onreadystatechange = function () { handleResponse(xhr); };
716
+
717
+ xhr.open('GET', blamedataUrl);
718
+ xhr.setRequestHeader('Accept', 'text/plain');
719
+ xhr.send(null);
720
+
721
+ // not all browsers call onreadystatechange event on each server flush
722
+ // poll response using timer every second to handle this issue
723
+ pollTimer = setInterval(xhr.onreadystatechange, 1000);
724
+}
725
+
726
+// end of gitweb.js