UNPKG

7.36 kBJavaScriptView Raw
1'use strict';
2
3const childProcess = require('child_process');
4
5const gitAuthorEmailCommand = 'git log --format="%ae" -n 1';
6const gitBlameCommand = 'git blame --porcelain';
7const gitBlameEmailClean = /[<>]/g;
8const gitBranchChangesCommand = 'git diff --numstat $(git merge-base master HEAD)';
9const gitBranchCommand = 'git rev-parse --abbrev-ref HEAD';
10const gitChangeSetCommand = 'git log --first-parent --pretty="format:%H, %aE, %cN, %s"';
11const gitChangeSetRegExp = /^(\w{40}),\s(.*?),\s(.*?),\s(.*)$/;
12const gitHashCommand = 'git rev-parse HEAD';
13const gitMergeBaseCommand = 'git merge-base master HEAD';
14const gitOrigin = /^origin\//;
15const gitStatusCommand = 'git status --branch --porcelain --untracked-files=all';
16
17const emailRegExp = /(@.*)$/;
18
19function gitBranch () {
20 let branch = process.env.GIT_BRANCH || process.env.BRANCH_NAME;
21 if (!branch) {
22 branch = childProcess.execSync(gitBranchCommand, { cwd: process.cwd() }).toString();
23 }
24 branch = branch || 'detached HEAD';
25 return branch.trim().replace(gitOrigin, '');
26}
27
28function gitHash () {
29 return childProcess.execSync(gitHashCommand, { cwd: process.cwd() }).toString().
30 trim();
31}
32
33function gitAuthorEmail () {
34 return childProcess.execSync(gitAuthorEmailCommand, { cwd: process.cwd() }).toString().
35 trim();
36}
37
38function gitBranchChanges () {
39 return childProcess.execSync(gitBranchChangesCommand, { cwd: process.cwd() }).toString().
40 trim().
41 split('\n').
42 map((line) => {
43 const [ additions, deletions, file ] = line.split(/\s+/);
44 return {
45 file,
46 additions,
47 deletions
48 };
49 });
50}
51
52function gitBlame (file, lineNumber) {
53 const summary = childProcess.execSync(
54 `${ gitBlameCommand } -L${ lineNumber },${ lineNumber } ${ file }`,
55 { cwd: process.cwd() }).toString().
56 trim().
57 split(/\n/);
58
59 const hashInformation = summary[0].split(/\s+/);
60 const blame = {
61 hash: hashInformation[0],
62 additions: hashInformation[1],
63 deletions: hashInformation[2],
64 author: { },
65 committer: {}
66 };
67
68 for (let i = 1; i < summary.length - 1; i++) {
69 const [ , type, value ] = summary[i].match(/^(.*?)\s(.*)$/);
70 let previousInformation;
71
72 switch (type) {
73 case 'author':
74 blame.author.name = value;
75 break;
76 case 'author-mail':
77 blame.author.email = value.replace(gitBlameEmailClean, '');
78 break;
79 case 'author-time':
80 blame.author.date = new Date(Number(value) * 1000);
81 break;
82 case 'committer':
83 blame.committer.name = value;
84 break;
85 case 'committer-mail':
86 blame.committer.email = value.replace(gitBlameEmailClean, '');
87 break;
88 case 'committer-time':
89 blame.committer.date = new Date(Number(value) * 1000);
90 break;
91 case 'summary':
92 blame.summary = value;
93 break;
94 case 'previous':
95 previousInformation = value.split(/\s+/);
96 blame.previous = {
97 hash: previousInformation[0],
98 file: previousInformation[1]
99 };
100 break;
101 case 'filename':
102 blame.file = value;
103 break;
104 default:
105 break;
106 }
107 }
108
109 blame.line = summary[summary.length - 1].slice(1);
110
111 return blame;
112}
113
114function gitAddNotes (message, prefix, force) {
115 try {
116 let gitNotesCommand = 'git notes';
117 if (prefix) {
118 gitNotesCommand += ` --ref=${ prefix }`;
119 }
120
121 gitNotesCommand += ` add -m "${ message }"`;
122
123 if (force) {
124 gitNotesCommand += ' -f';
125 }
126
127 childProcess.execSync(gitNotesCommand, {
128 cwd: process.cwd(),
129 stdio: 'ignore'
130 });
131
132 return true;
133 } catch (error) {
134 return false;
135 }
136}
137
138function gitRemoveNotes (prefix) {
139 try {
140 let gitNotesCommand = 'git notes';
141 if (prefix) {
142 gitNotesCommand += ` --ref=${ prefix }`;
143 }
144 gitNotesCommand += ' remove';
145
146 childProcess.execSync(gitNotesCommand, {
147 cwd: process.cwd(),
148 stdio: 'ignore'
149 });
150
151 return true;
152 } catch (error) {
153 return false;
154 }
155}
156
157function gitShowNotes (prefix) {
158 try {
159 let gitNotesCommand = 'git notes';
160 if (prefix) {
161 gitNotesCommand += ` --ref=${ prefix }`;
162 }
163 gitNotesCommand += ' show';
164
165 const notes = childProcess.execSync(gitNotesCommand, {
166 cwd: process.cwd(),
167 stdio: [ 'pipe', 'pipe', 'ignore' ]
168 }).toString().
169 trim();
170
171 if (notes.startsWith('error: No note found')) {
172 return null;
173 }
174 return notes;
175 } catch (error) {
176 return null;
177 }
178}
179
180function gitStatus () {
181 const status = childProcess.execSync(gitStatusCommand, { cwd: process.cwd() }).toString().
182 trim().
183 split('\n');
184
185 let [ branch, ...changes ] = status;
186
187 branch = branch.replace('## ', '');
188 changes = changes.map((item) => {
189 return item.replace(/^[ ][MD]\s+(.*)$/, '$1 (modified)').
190 replace(/^M[ MD]\s+(.*)$/, '$1 (modified in index)').
191 replace(/^A[ MD]\s+(.*)$/, '$1 (added)').
192 replace(/^D[ M]\s+(.*)$/, '$1 (deleted)').
193 replace(/^R[ MD]\s+(.*)$/, '$1 (renamed)').
194 replace(/^C[ MD]\s+(.*)$/, '$1 (copied)').
195 replace(/^[MARC][ ]\s+(.*)$/, '$1 (index and work tree matches)').
196 replace(/^[ MARC]M\s+(.*)$/, '$1 (work tree changed since index)').
197 replace(/^[ MARC]D\s+(.*)$/, '$1 (deleted in work tree)').
198 replace(/^DD\s+(.*)$/, '$1 (unmerged, both deleted)').
199 replace(/^AU\s+(.*)$/, '$1 (unmerged, added by us)').
200 replace(/^UD\s+(.*)$/, '$1 (unmerged, deleted by them)').
201 replace(/^UA\s+(.*)$/, '$1 (unmerged, added by them)').
202 replace(/^DU\s+(.*)$/, '$1 (unmerged, deleted by us)').
203 replace(/^AA\s+(.*)$/, '$1 (unmerged, both added)').
204 replace(/^UU\s+(.*)$/, '$1 (unmerged, both modified)').
205 replace(/^\?\?\s+(.*)$/, '$1 (untracked)').
206 replace(/^!!\s+(.*)$/, '$1 (ignored)');
207 }).sort();
208
209 return {
210 branch,
211 changes,
212 clean: !changes.length
213 };
214}
215
216function gitMergeBase () {
217 try {
218 return childProcess.execSync(gitMergeBaseCommand, {
219 cwd: process.cwd(),
220 stdio: [ 'pipe', 'pipe', 'ignore' ]
221 }).toString().
222 trim();
223 } catch (error) { // Likely a shallow clone
224 return null;
225 }
226}
227
228function gitChangeSet (initialCommit) {
229 let command = gitChangeSetCommand;
230 if (initialCommit) {
231 command += ` "${ initialCommit }..HEAD"`;
232 } else if (process.env.LAST_SUCCESSFUL_COMMIT) {
233 command += ` "${ process.env.LAST_SUCCESSFUL_COMMIT }..HEAD"`;
234 } else {
235 return null;
236 }
237
238 const changes = childProcess.execSync(command, { cwd: process.cwd() }).toString().
239 trim().
240 split('\n');
241
242 const changeSet = {};
243
244 changes.forEach((change) => {
245 if (gitChangeSetRegExp.test(change)) {
246 const [ , hash, email, name, title ] = change.match(gitChangeSetRegExp);
247
248 if (emailRegExp.test(email)) {
249 const id = email.replace(emailRegExp, '').toLowerCase();
250
251 changeSet[id] = changeSet[id] || {
252 id,
253 name,
254 email,
255 changes: []
256 };
257
258 changeSet[id].changes.push({
259 short: hash.substring(0, 12),
260 hash,
261 title
262 });
263 }
264 }
265 });
266
267 return changeSet;
268}
269
270module.exports = {
271 gitAddNotes,
272 gitAuthorEmail,
273 gitBlame,
274 gitBranch,
275 gitBranchChanges,
276 gitChangeSet,
277 gitHash,
278 gitMergeBase,
279 gitRemoveNotes,
280 gitShowNotes,
281 gitStatus
282};