| 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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143 | 1
1
1
1
1
1
1
1
324
1
1
27
27
27
27
27
27
27
27
27
27
1
1
27
27
1
27
27
1
1
27
1
1
1
1
1
1
27
27
27
27
1
1
1
1
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');
var quote = require('quote');
var R = require('ramda');
function isFileNameLine(line) {
return /^filename/.test(line);
}
var findFilenameLine = R.find(isFileNameLine);
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);
var info = {
commit: lines[0].split(' ')[0],
author: lines[1].replace('author ', ''),
committer: lines[5].replace('committer ', ''),
summary: lines[9].replace('summary ', '')
};
var filename = findFilenameLine(lines);
la(/^filename/.test(filename), 'could not find filename line from', quote(filename),
'from', lines.length, 'lines\n---\n' + lines.join('\n') + '\n---');
info.filename = filename.replace('filename ', ''); // wrt repo root
var line = lines[lines.length - 1];
info.line = line;
return info;
}
// 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 hasBoundaryLine(lines) {
la(check.array(lines), 'expected string lines', lines);
return /^boundary/.test(lines[10]);
}
function linesPerCommit(lines) {
return isUncommittedLine(lines[0]) ||
hasPreviousCommitId(lines) ||
hasBoundaryLine(lines) ? 13 : 12;
}
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 = linesPerCommit(lines);
la(check.positiveNumber(linesPerSourceLine));
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');
Iif (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;
|