1 // Copyright (C) 2007, Fredrik Kuivinen <frekui@gmail.com>
9 function createRequestObject() {
11 if (window.XMLHttpRequest) {
12 ro = new XMLHttpRequest();
14 ro = new ActiveXObject("Microsoft.XMLHTTP");
17 debug("Couldn't start XMLHttpRequest object");
24 // 'commits' is an associative map. It maps SHA1s to Commit objects.
25 var commits = new Object();
27 function Commit(sha1) {
31 // convert month or day of the month to string, padding it with
32 // '0' (zero) to two characters width if necessary
40 function spacePad(n,width) {
50 return str + n.toString();
55 var totalLines = '???';
57 var div_progress_info;
59 function countLines() {
60 var table = document.getElementById('blame_table');
62 table = document.getElementsByTagName('table').item(0);
65 return table.getElementsByTagName('tr').length - 1; // for header
70 function updateProgressInfo() {
71 if (!div_progress_info)
72 div_progress_info = document.getElementById('progress_info');
73 if (!div_progress_bar)
74 div_progress_bar = document.getElementById('progress_bar');
75 if (!div_progress_info && !div_progress_bar)
78 var percentage = Math.floor(100.0*blamedLines/totalLines);
80 if (div_progress_info) {
81 div_progress_info.innerHTML = blamedLines + ' / ' + totalLines
82 + ' ('+spacePad(percentage,3)+'%)';
85 if (div_progress_bar) {
86 div_progress_bar.setAttribute('style', 'width: '+percentage+'%;');
90 var colorRe = new RegExp('color([0-9]*)');
91 /* return N if <tr class="colorN">, otherwise return null */
92 function getColorNo(tr) {
93 var className = tr.getAttribute('class');
95 match = colorRe.exec(className);
97 return parseInt(match[1]);
102 function findColorNo(tr_prev, tr_next) {
107 color_prev = getColorNo(tr_prev);
109 color_next = getColorNo(tr_next);
111 if (!color_prev && !color_next)
113 if (color_prev == color_next)
114 return ((color_prev % 3) + 1);
116 return ((color_next % 3) + 1);
118 return ((color_prev % 3) + 1);
119 return (3 - ((color_prev + color_next) % 3));
122 var tzRe = new RegExp('^([+-][0-9][0-9])([0-9][0-9])$');
123 // called for each blame entry, as soon as it finishes
124 function handleLine(commit) {
125 /* This is the structure of the HTML fragment we are working
128 <tr id="l123" class="">
129 <td class="sha1" title=""><a href=""></a></td>
130 <td class="linenr"><a class="linenr" href="">123</a></td>
131 <td class="pre"># times (my ext3 doesn't).</td>
135 var resline = commit.resline;
138 var date = new Date();
139 date.setTime(commit.authorTime * 1000); // milliseconds
141 date.getUTCFullYear() + '-'
142 + zeroPad(date.getUTCMonth()+1) + '-'
143 + zeroPad(date.getUTCDate());
145 zeroPad(date.getUTCHours()) + ':'
146 + zeroPad(date.getUTCMinutes()) + ':'
147 + zeroPad(date.getUTCSeconds());
149 var localDate = new Date();
150 var match = tzRe.exec(commit.authorTimezone);
151 localDate.setTime(1000 * (commit.authorTime
152 + (parseInt(match[1],10)*3600 + parseInt(match[2],10)*60)));
154 zeroPad(localDate.getUTCHours()) + ':'
155 + zeroPad(localDate.getUTCMinutes()) + ':'
156 + zeroPad(localDate.getUTCSeconds());
158 /* e.g. 'Kay Sievers, 2005-08-07 19:49:46 +0000 (21:49:46 +0200)' */
159 commit.info = commit.author + ', ' + dateStr + ' '
161 + ' (' + localTimeStr + ' ' + commit.authorTimezone + ')';
164 var color = findColorNo(
165 document.getElementById('l'+(resline-1)),
166 document.getElementById('l'+(resline+commit.numlines))
170 for (var i = 0; i < commit.numlines; i++) {
171 var tr = document.getElementById('l'+resline);
173 debug('tr is null! resline: ' + resline);
177 <tr id="l123" class="">
178 <td class="sha1" title=""><a href=""></a></td>
179 <td class="linenr"><a class="linenr" href="">123</a></td>
180 <td class="pre"># times (my ext3 doesn't).</td>
183 var td_sha1 = tr.firstChild;
184 var a_sha1 = td_sha1.firstChild;
185 var a_linenr = td_sha1.nextSibling.firstChild;
187 /* <tr id="l123" class=""> */
189 tr.setAttribute('class', 'color'+color.toString());
190 // Internet Explorer needs this
191 //tr.setAttribute('className', color.toString);
193 /* <td class="sha1" title="?" rowspan="?"><a href="?">?</a></td> */
195 td_sha1.title = commit.info;
196 td_sha1.setAttribute('rowspan', commit.numlines);
198 a_sha1.href = baseUrl + '?a=commit;h=' + commit.sha1;
199 a_sha1.innerHTML = commit.sha1.substr(0, 8);
200 if (commit.numlines >= 2) {
201 var br = document.createElement("br");
202 var text = document.createTextNode(commit.author.match(/\b([A-Z])\B/g).join(''));
204 td_sha1.appendChild(br);
205 td_sha1.appendChild(text);
209 tr.removeChild(td_sha1);
212 /* <td class="linenr"><a class="linenr" href="?">123</a></td> */
213 a_linenr.href = baseUrl + '?a=blame;hb=' + commit.sha1
214 + ';f=' + commit.filename + '#l' + (commit.srcline + i);
219 //updateProgressInfo();
223 function startOfGroup(tr) {
224 return tr.firstChild.getAttribute('class') == 'sha1';
227 function fixColors() {
228 var colorClasses = ['light2', 'dark2'];
233 while ((tr = document.getElementById('l'+linenum))) {
234 if (startOfGroup(tr, linenum, document)) {
235 colorClass = (colorClass + 1) % 2;
237 tr.setAttribute('class', colorClasses[colorClass]);
238 // Internet Explorer needs this
239 tr.setAttribute('className', colorClasses[colorClass]);
244 var t_interval_server = '';
247 function writeTimeInterval() {
248 var info = document.getElementById('generate_time');
253 info.innerHTML += ' + '
254 + t_interval_server+'s server (blame_data) / '
255 + (t1.getTime() - t0.getTime())/1000 + 's client (JavaScript)';
258 // ----------------------------------------------------------------------
260 var prevDataLength = -1;
262 var inProgress = false;
264 var sha1Re = new RegExp('([0-9a-f]{40}) ([0-9]+) ([0-9]+) ([0-9]+)');
265 var infoRe = new RegExp('([a-z-]+) ?(.*)');
266 var endRe = new RegExp('END ?(.*)');
267 var curCommit = new Commit();
269 var pollTimer = null;
271 function handleResponse() {
272 debug('handleResp ready: ' + http.readyState
273 + ' respText null?: ' + (http.responseText === null)
274 + ' progress: ' + inProgress);
276 if (http.readyState != 4 && http.readyState != 3)
279 // the server stream is incorrect
280 if (http.readyState == 3 && http.status != 200)
282 if (http.readyState == 4 && http.status != 200) {
283 if (!div_progress_info)
284 div_progress_info = document.getElementById('progress_info');
286 if (div_progress_info) {
287 div_progress_info.setAttribute('class', 'error');
288 div_progress_info.innerHTML =
289 http.status+' - Error contacting server\n';
291 document.write("<b>ERROR:</b> HTTP status is "+http.status+"<br />\n");
294 clearInterval(pollTimer);
298 // In konqueror http.responseText is sometimes null here...
299 if (http.responseText === null)
303 var resp = document.getElementById('state');
305 resp.innerHTML = http.readyState + ' : ' + http.status
306 + '<br />len = ' + http.responseText.length
307 + '; inProgress='+inProgress;
317 while (prevDataLength != http.responseText.length) {
318 if (http.readyState == 4
319 && prevDataLength == http.responseText.length) {
323 prevDataLength = http.responseText.length;
324 var response = http.responseText.substring(nextLine);
325 var lines = response.split('\n');
326 nextLine = nextLine + response.lastIndexOf('\n') + 1;
327 if (response[response.length-1] != '\n') {
331 for (var i = 0; i < lines.length; i++) {
332 var match = sha1Re.exec(lines[i]);
335 var srcline = parseInt(match[2]);
336 var resline = parseInt(match[3]);
337 var numlines = parseInt(match[4]);
338 var c = commits[sha1];
340 c = new Commit(sha1);
346 c.numlines = numlines;
348 } else if ((match = infoRe.exec(lines[i]))) {
351 if (info == 'filename') {
352 curCommit.filename = data;
353 handleLine(curCommit);
354 updateProgressInfo();
355 } else if (info == 'author') {
356 curCommit.author = data;
357 } else if (info == 'author-time') {
358 curCommit.authorTime = parseInt(data);
359 } else if (info == 'author-tz') {
360 curCommit.authorTimezone = data;
362 } else if ((match = endRe.exec(lines[i]))) {
363 t_interval_server = match[1];
364 debug('END: '+lines[i]);
365 } else if (lines[i] != '') {
366 debug('malformed line: ' + lines[i]);
371 if (http.readyState == 4 &&
372 prevDataLength == http.responseText.length) {
373 clearInterval(pollTimer);
382 function startBlame(blamedataUrl, bUrl) {
383 debug('startBlame('+blamedataUrl+', '+bUrl+')');
387 totalLines = countLines();
388 updateProgressInfo();
390 http = createRequestObject();
391 http.open('get', blamedataUrl);
392 http.onreadystatechange = handleResponse;
395 pollTimer = setInterval(handleResponse, 1000);