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