| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116 | 1
1
1
1
1
1
45
45
45
45
1
1
1
1
1
44
44
1
22
22
1
1
1
1
1
1
44
44
44
1
1
1
1
1
1
1
1
1
1
2
2
1
1
1
1
1
1
1
| require('lazy-ass');
var check = require('check-types');
var exec = require('./exec');
var path = require('path');
var fs = require('fs');
function linesToBlameInfo(lines) {
la(check.array(lines), 'expected lines', lines);
la(lines.length > 3, 'few lines in output', lines);
la(/^author/.test(lines[1]), 'cannot find author line in', lines);
return {
commit: lines[0].split(' ')[0],
author: lines[1].replace('author ', ''),
committer: lines[5].replace('committer ', ''),
summary: lines[9].replace('summary ', ''),
filename: lines[10].replace('filename ', ''), // wrt repo root
line: lines[11].trim()
};
}
// from git blame --porcelain single line string output to object
function toBlameInfo(blamePorcelainOutput) {
la(check.unemptyString(blamePorcelainOutput),
'expected string output', blamePorcelainOutput);
/*
full-commit-id 73 79 1
author Joe
author-mail <joe@email.com>
author-time 1401904426
author-tz -0400
committer Andy
committer-mail <andy@email.com>
committer-time 1401904426
committer-tz -0400
summary Blah blah blah
filename path/to/file/from/repo/root
// current line from file
*/
var lines = blamePorcelainOutput.trim().split('\n');
return linesToBlameInfo(lines);
}
function isUncommittedLine(line) {
la(check.unemptyString(line), 'expected commit id line', line);
return /^00000/.test(line);
}
function hasPreviousCommitId(lines) {
la(check.array(lines), 'expected string lines', lines);
return /^previous/.test(lines[10]);
}
function toBlameInfoFile(blamePorcelainOutput) {
la(check.unemptyString(blamePorcelainOutput),
'expected string output', blamePorcelainOutput);
/*
each 12 lines will have information about the commit, something like
6e65f8ec5ed63cac92ed130b1246d9c23223c04e 1 1 6
author Gleb Bahmutov
author-mail <gleb.bahmutov@gmail.com>
author-time 1410319209
author-tz -0400
committer Gleb Bahmutov
committer-mail <gleb.bahmutov@gmail.com>
committer-time 1410319209
committer-tz -0400
summary adding blame feature
filename test/blame.js
var blame = require('../index').blame;
*/
var lines = blamePorcelainOutput.trim().split('\n');
la(lines.length > 3, 'few lines in output', blamePorcelainOutput);
var perLineInfo = [], lineInfo;
while (lines.length > 0) {
var linesPerSourceLine = isUncommittedLine(lines[0]) || hasPreviousCommitId(lines) ? 13 : 12;
lineInfo = lines.splice(0, linesPerSourceLine);
// console.log(lineInfo);
perLineInfo.push(linesToBlameInfo(lineInfo));
};
return perLineInfo;
}
function blameOneLine(filename, lineNumber) {
la(check.unemptyString(filename), 'missing filename');
la(check.positiveNumber(lineNumber),
'file', filename, 'missing line number (starting with 1)', lineNumber);
var fullFilename = path.resolve(filename);
la(fs.existsSync(filename), 'file', fullFilename, 'not found, based on', filename);
console.log('who to blame for', fullFilename, lineNumber);
// http://git-scm.com/docs/git-blame
var cmd = 'git blame --porcelain -L ' + lineNumber + ',' + lineNumber + ' ' + fullFilename;
return exec(cmd).then(toBlameInfo);
}
function blame(filename, lineNumber) {
la(check.unemptyString(filename), 'missing filename');
if (lineNumber) {
return blameOneLine(filename, lineNumber);
}
var fullFilename = path.resolve(filename);
la(fs.existsSync(filename), 'file', fullFilename, 'not found, based on', filename);
console.log('who to blame for', fullFilename);
// http://git-scm.com/docs/git-blame
var cmd = 'git blame --porcelain --line-porcelain ' + fullFilename;
return exec(cmd).then(toBlameInfoFile);
}
module.exports = blame;
|