UNPKG

4.58 kBJavaScriptView Raw
1var la = require('lazy-ass')
2var check = require('check-more-types')
3var exec = require('./exec')
4var path = require('path')
5var fs = require('fs')
6var quote = require('quote')
7var R = require('ramda')
8
9function isFileNameLine (line) {
10 return /^filename/.test(line)
11}
12var findFilenameLine = R.find(isFileNameLine)
13
14function linesToBlameInfo (lines) {
15 la(check.array(lines), 'expected lines', lines)
16 la(lines.length > 3, 'few lines in output', lines)
17 la(/^author/.test(lines[1]), 'cannot find author line in', lines)
18
19 var info = {
20 commit: lines[0].split(' ')[0],
21 author: lines[1].replace('author ', ''),
22 committer: lines[5].replace('committer ', ''),
23 summary: lines[9].replace('summary ', '')
24 }
25
26 var filename = findFilenameLine(lines)
27 la(
28 /^filename/.test(filename),
29 'could not find filename line from',
30 quote(filename),
31 'from',
32 lines.length,
33 'lines\n---\n' + lines.join('\n') + '\n---'
34 )
35 info.filename = filename.replace('filename ', '') // wrt repo root
36
37 var line = lines[lines.length - 1]
38 info.line = line
39 return info
40}
41
42// from git blame --porcelain single line string output to object
43function toBlameInfo (blamePorcelainOutput) {
44 la(
45 check.unemptyString(blamePorcelainOutput),
46 'expected string output',
47 blamePorcelainOutput
48 )
49 /*
50 full-commit-id 73 79 1
51 author Joe
52 author-mail <joe@email.com>
53 author-time 1401904426
54 author-tz -0400
55 committer Andy
56 committer-mail <andy@email.com>
57 committer-time 1401904426
58 committer-tz -0400
59 summary Blah blah blah
60 filename path/to/file/from/repo/root
61 // current line from file
62 */
63 var lines = blamePorcelainOutput.trim().split('\n')
64 return linesToBlameInfo(lines)
65}
66
67function isUncommittedLine (line) {
68 la(check.unemptyString(line), 'expected commit id line', line)
69 return /^00000/.test(line)
70}
71
72function hasPreviousCommitId (lines) {
73 la(check.array(lines), 'expected string lines', lines)
74 return /^previous/.test(lines[10])
75}
76
77function hasBoundaryLine (lines) {
78 la(check.array(lines), 'expected string lines', lines)
79 return /^boundary/.test(lines[10])
80}
81
82function linesPerCommit (lines) {
83 return isUncommittedLine(lines[0]) ||
84 hasPreviousCommitId(lines) ||
85 hasBoundaryLine(lines)
86 ? 13
87 : 12
88}
89
90function toBlameInfoFile (blamePorcelainOutput) {
91 la(
92 check.unemptyString(blamePorcelainOutput),
93 'expected string output',
94 blamePorcelainOutput
95 )
96 /*
97 each 12 lines will have information about the commit, something like
98
99 6e65f8ec5ed63cac92ed130b1246d9c23223c04e 1 1 6
100 author Gleb Bahmutov
101 author-mail <gleb.bahmutov@gmail.com>
102 author-time 1410319209
103 author-tz -0400
104 committer Gleb Bahmutov
105 committer-mail <gleb.bahmutov@gmail.com>
106 committer-time 1410319209
107 committer-tz -0400
108 summary adding blame feature
109 filename test/blame.js
110 var blame = require('../index').blame;
111 */
112 var lines = blamePorcelainOutput.trim().split('\n')
113 la(lines.length > 3, 'few lines in output', blamePorcelainOutput)
114
115 var perLineInfo = []
116 var lineInfo
117 while (lines.length > 0) {
118 var linesPerSourceLine = linesPerCommit(lines)
119 la(check.positiveNumber(linesPerSourceLine))
120 lineInfo = lines.splice(0, linesPerSourceLine)
121 // console.log(lineInfo);
122 perLineInfo.push(linesToBlameInfo(lineInfo))
123 }
124
125 return perLineInfo
126}
127
128function blameOneLine (filename, lineNumber) {
129 la(check.unemptyString(filename), 'missing filename')
130 la(
131 check.positiveNumber(lineNumber),
132 'file',
133 filename,
134 'missing line number (starting with 1)',
135 lineNumber
136 )
137 var fullFilename = path.resolve(filename)
138 la(
139 fs.existsSync(filename),
140 'file',
141 fullFilename,
142 'not found, based on',
143 filename
144 )
145
146 console.log('who to blame for', fullFilename, lineNumber)
147 // http://git-scm.com/docs/git-blame
148 var cmd =
149 'git blame --porcelain -L ' +
150 lineNumber +
151 ',' +
152 lineNumber +
153 ' ' +
154 fullFilename
155 return exec(cmd).then(toBlameInfo)
156}
157
158function blame (filename, lineNumber) {
159 la(check.unemptyString(filename), 'missing filename')
160
161 if (lineNumber) {
162 return blameOneLine(filename, lineNumber)
163 }
164
165 var fullFilename = path.resolve(filename)
166 la(
167 fs.existsSync(filename),
168 'file',
169 fullFilename,
170 'not found, based on',
171 filename
172 )
173
174 console.log('who to blame for', fullFilename)
175 // http://git-scm.com/docs/git-blame
176 var cmd = 'git blame --porcelain --line-porcelain ' + fullFilename
177 return exec(cmd).then(toBlameInfoFile)
178}
179
180module.exports = blame