UNPKG

18.7 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3var Fs = require("fs-extra");
4var Minimist = require("minimist");
5var Path = require("path");
6var git_1 = require("./git");
7var local_storage_1 = require("./local-storage");
8var paths_1 = require("./paths");
9var utils_1 = require("./utils");
10// Get recent commit by specified arguments
11function getRecentCommit(offset, format, grep) {
12 if (typeof offset === 'string') {
13 if (!grep) {
14 grep = format;
15 }
16 format = offset;
17 offset = 0;
18 }
19 var argv = [];
20 if (format) {
21 argv.push("--format=" + format);
22 }
23 if (grep) {
24 argv.push("--grep=" + grep);
25 }
26 return git_1.Git.recentCommit(offset, argv);
27}
28// Get the recent step commit
29function getRecentStepCommit(offset, format) {
30 return getRecentCommit(offset, format, '^Step [0-9]\\+');
31}
32// Get the recent super step commit
33function getRecentSuperStepCommit(offset, format) {
34 return getRecentCommit(offset, format, '^Step [0-9]\\+:');
35}
36// Get the recent sub step commit
37function getRecentSubStepCommit(offset, format) {
38 return getRecentCommit(offset, format, '^Step [0-9]\\+\\.[0-9]\\+:');
39}
40// Extract step json from message
41function getStepDescriptor(message) {
42 if (message == null) {
43 throw TypeError('A message must be provided');
44 }
45 var match = message.match(/^Step (\d+(?:\.\d+)?)\: ((?:.|\n)*)$/);
46 return match && {
47 number: match[1],
48 message: match[2],
49 type: match[1].split('.')[1] ? 'sub' : 'super',
50 };
51}
52// Extract super step json from message
53function getSuperStepDescriptor(message) {
54 if (message == null) {
55 throw TypeError('A message must be provided');
56 }
57 var match = message.match(/^Step (\d+)\: ((?:.|\n)*)$/);
58 return match && {
59 number: Number(match[1]),
60 message: match[2],
61 };
62}
63// Extract sub step json from message
64function getSubStepDescriptor(message) {
65 if (message == null) {
66 throw TypeError('A message must be provided');
67 }
68 var match = message.match(/^Step ((\d+)\.(\d+))\: ((?:.|\n)*)$/);
69 return match && {
70 number: match[1],
71 superNumber: Number(match[2]),
72 subNumber: Number(match[3]),
73 message: match[4],
74 };
75}
76// Push a new step with the provided message
77function pushStep(message, options) {
78 var step = getNextStep();
79 commitStep(step, message, options);
80 // Meta-data for step editing
81 local_storage_1.localStorage.setItem('REBASE_NEW_STEP', step);
82}
83// Pop the last step
84function popStep() {
85 var headHash = git_1.Git(['rev-parse', 'HEAD']);
86 var rootHash = git_1.Git.rootHash();
87 if (headHash === rootHash) {
88 throw Error("Can't remove root");
89 }
90 var removedCommitMessage = git_1.Git.recentCommit(['--format=%s']);
91 var stepDescriptor = getStepDescriptor(removedCommitMessage);
92 git_1.Git.print(['reset', '--hard', 'HEAD~1']);
93 // Meta-data for step editing
94 if (stepDescriptor) {
95 local_storage_1.localStorage.setItem('REBASE_NEW_STEP', getCurrentStep());
96 // This will be used later on to update the manuals
97 if (ensureStepMap()) {
98 updateStepMap('remove', { step: stepDescriptor.number });
99 }
100 // Delete branch referencing the super step unless we're rebasing, in which case the
101 // branches will be reset automatically at the end of the rebase
102 if (stepDescriptor.type === 'super' && !git_1.Git.rebasing()) {
103 var branch = git_1.Git.activeBranchName();
104 git_1.Git(['branch', '-D', branch + "-step" + stepDescriptor.number]);
105 }
106 }
107 else {
108 console.warn('Removed commit was not a step');
109 return;
110 }
111}
112// Finish the current with the provided message and tag it
113function tagStep(message) {
114 var step = getNextSuperStep();
115 var tag = "step" + step;
116 var manualFile = tag + ".tmpl";
117 var manualTemplatePath = Path.resolve(paths_1.Paths.manuals.templates, manualFile);
118 Fs.ensureDirSync(paths_1.Paths.manuals.templates);
119 Fs.ensureDirSync(paths_1.Paths.manuals.views);
120 Fs.writeFileSync(manualTemplatePath, '');
121 git_1.Git(['add', manualTemplatePath]);
122 commitStep(step, message);
123 // If we're in edit mode all the branches will be set after the rebase
124 if (!git_1.Git.rebasing()) {
125 var branch = git_1.Git.activeBranchName();
126 // This branch will be used to run integration testing
127 git_1.Git(['branch', branch + "-step" + step]);
128 }
129 // Meta-data for step editing
130 local_storage_1.localStorage.setItem('REBASE_NEW_STEP', step);
131}
132// Get the hash of the step followed by ~1, mostly useful for a rebase
133function getStepBase(step) {
134 if (!step) {
135 var message = getRecentStepCommit('%s');
136 if (!message) {
137 return '--root';
138 }
139 step = getStepDescriptor(message).number;
140 }
141 if (step === 'root') {
142 return '--root';
143 }
144 var hash = git_1.Git.recentCommit([
145 "--grep=^Step " + step + ":",
146 '--format=%h',
147 ]);
148 if (!hash) {
149 throw Error('Step not found');
150 }
151 return hash + "~1";
152}
153// Edit the provided step
154function editStep(steps, options) {
155 if (options === void 0) { options = {}; }
156 var rootSha1 = git_1.Git.rootHash();
157 var allSteps = getAllSteps();
158 steps = [].concat(steps).filter(Boolean);
159 // Unwrap ranges, e.g.
160 // 1...3.1 may become 1 2.1 2.2 2.3 2 3.1
161 steps = steps.reduce(function (flattened, step) {
162 var range = step.match(/(\d+(?:\.\d+)?)?\.\.(?:\.+)?(\d+(?:\.\d+)?)?/);
163 if (!range) {
164 return flattened.concat(step);
165 }
166 var start = range[1] || 'root';
167 var end = range[2] || allSteps[allSteps.length - 1];
168 var startIndex = allSteps.findIndex(function (s) { return s === start; });
169 var endIndex = allSteps.findIndex(function (s) { return s === end; });
170 if (startIndex === -1) {
171 startIndex = 0;
172 }
173 if (endIndex === -1) {
174 startIndex = Infinity;
175 }
176 return flattened.concat(allSteps.slice(startIndex, endIndex + 1));
177 }, []);
178 // Map git-refs to step indexes
179 steps = steps.map(function (step) {
180 // If an index was provided, return it; otherwise try to find the index by SHA1
181 if (/^\d{1,5}(\.\d+)?$/.test(step) || step === 'root') {
182 return step;
183 }
184 if (step === rootSha1) {
185 return 'root';
186 }
187 var commitMessage = git_1.Git(['log', step, '-1', '--format=%s']);
188 var descriptor = getStepDescriptor(commitMessage);
189 return descriptor && descriptor.number;
190 }).filter(Boolean);
191 steps = steps.slice().sort(function (a, b) {
192 var _a = a.split('.').concat('Infinity'), superA = _a[0], subA = _a[1];
193 var _b = b.split('.').concat('Infinity'), superB = _b[0], subB = _b[1];
194 // Always put the root on top
195 if (a === 'root') {
196 return -1;
197 }
198 if (b === 'root') {
199 return 1;
200 }
201 // Put first steps first
202 return ((superA - superB) ||
203 (subA - subB));
204 });
205 // The would always have to start from the first step
206 var base = getStepBase(steps[0]);
207 // '--root' might be fetched in case no steps where provided. We need to fill up
208 // this missing information in the steps array
209 if (!steps.length && base === '--root') {
210 steps[0] = 'root';
211 }
212 var argv = [paths_1.Paths.tortilla.editor, 'edit'].concat(steps);
213 // Update diffSteps
214 if (options.udiff != null) {
215 argv.push('--udiff');
216 }
217 // Update diffSteps in another repo
218 if (options.udiff) {
219 argv.push(options.udiff.toString());
220 }
221 // Storing locally so it can be used in further processes
222 // Indicates that this operation is hooked into a submodule
223 if (process.env.TORTILLA_SUBMODULE_CWD) {
224 local_storage_1.localStorage.setItem('SUBMODULE_CWD', process.env.TORTILLA_SUBMODULE_CWD);
225 }
226 git_1.Git.print(['rebase', '-i', base, '--keep-empty'], {
227 env: {
228 GIT_SEQUENCE_EDITOR: "node " + argv.join(' '),
229 },
230 });
231}
232// Adjust all the step indexes from the provided step
233function sortStep(step) {
234 // If no step was provided, take the most recent one
235 if (!step) {
236 step = getRecentStepCommit('%s');
237 step = getStepDescriptor(step);
238 step = step ? step.number : 'root';
239 }
240 var newStep;
241 var oldStep;
242 var base;
243 // If root, make sure to sort all step indexes since the beginning of history
244 if (step === 'root') {
245 newStep = '1';
246 oldStep = 'root';
247 base = '--root';
248 }
249 else { // Else, adjust only the steps in the given super step
250 newStep = step.split('.').map(Number)[0];
251 oldStep = newStep - 1 || 'root';
252 newStep = newStep + "." + 1;
253 base = getStepBase(newStep);
254 }
255 // Setting local storage variables so re-sortment could be done properly
256 local_storage_1.localStorage.setItem('REBASE_NEW_STEP', newStep);
257 local_storage_1.localStorage.setItem('REBASE_OLD_STEP', oldStep);
258 git_1.Git.print(['rebase', '-i', base, '--keep-empty'], {
259 env: {
260 GIT_SEQUENCE_EDITOR: "node " + paths_1.Paths.tortilla.editor + " sort",
261 },
262 });
263}
264// Reword the provided step with the provided message
265function rewordStep(step, message) {
266 var base = getStepBase(step);
267 var argv = [paths_1.Paths.tortilla.editor, 'reword'];
268 if (message) {
269 argv.push('-m', "\"" + message + "\"");
270 }
271 git_1.Git.print(['rebase', '-i', base, '--keep-empty'], {
272 env: {
273 GIT_SEQUENCE_EDITOR: "node " + argv.join(' '),
274 },
275 });
276}
277// Run git-show for given step index
278function showStep(step) {
279 var args = [];
280 for (var _i = 1; _i < arguments.length; _i++) {
281 args[_i - 1] = arguments[_i];
282 }
283 assertStep(step);
284 step = step.split('.').join('\\.');
285 var hash = git_1.Git(['log', "--grep=^Step " + step, '--format=%H']);
286 if (!hash) {
287 throw Error('Step not found');
288 }
289 git_1.Git.print(['show', hash].concat(args));
290}
291// Asserts whether provided string is a step index or not
292function assertStep(step, silent) {
293 if (silent === void 0) { silent = false; }
294 if (typeof step !== 'string' && typeof step !== 'number') {
295 if (silent) {
296 return false;
297 }
298 throw TypeError('Provided argument is not of type string or number');
299 }
300 step = step.toString();
301 if (!/\d+/.test(step) && !/\d+\.\d+/.test(step)) {
302 if (silent) {
303 return false;
304 }
305 throw TypeError('Provided argument is not a step');
306 }
307 return true;
308}
309// Add a new commit of the provided step with the provided message
310function commitStep(step, message, options) {
311 if (options === void 0) { options = {}; }
312 var argv = ['commit'];
313 if (message) {
314 argv.push('-m', message);
315 }
316 if (options.allowEmpty) {
317 argv.push('--allow-empty');
318 }
319 // Specified step is gonna be used for when forming the commit message
320 local_storage_1.localStorage.setItem('HOOK_STEP', step);
321 try {
322 // commit
323 git_1.Git.print(argv);
324 }
325 catch (err) {
326 // Clearing storage to prevent conflicts with upcoming commits
327 local_storage_1.localStorage.removeItem('HOOK_STEP');
328 throw err;
329 }
330}
331// Get the current step
332function getCurrentStep() {
333 // Probably root commit
334 var recentStepCommit = getRecentStepCommit('%s');
335 if (!recentStepCommit) {
336 return 'root';
337 }
338 // Cover unexpected behavior
339 var descriptor = getStepDescriptor(recentStepCommit);
340 if (!descriptor) {
341 return 'root';
342 }
343 return descriptor.number;
344}
345// Get the current super step
346function getCurrentSuperStep() {
347 // Probably root commit
348 var recentStepCommit = getRecentSuperStepCommit('%s');
349 if (!recentStepCommit) {
350 return 'root';
351 }
352 // Cover unexpected behavior
353 var descriptor = getSuperStepDescriptor(recentStepCommit);
354 if (!descriptor) {
355 return 'root';
356 }
357 return descriptor.number;
358}
359// Get the next step
360function getNextStep(offset) {
361 // Fetch data about recent step commit
362 var stepCommitMessage = getRecentStepCommit(offset, '%s');
363 var followedByStep = !!stepCommitMessage;
364 // If no previous steps found return the first one
365 if (!followedByStep) {
366 return '1.1';
367 }
368 // Fetch data about current step
369 var stepDescriptor = getStepDescriptor(stepCommitMessage);
370 var stepNumbers = stepDescriptor.number.split('.');
371 var superStepNumber = Number(stepNumbers[0]);
372 var subStepNumber = Number(stepNumbers[1]);
373 var isSuperStep = !subStepNumber;
374 if (!offset) {
375 // If this is a super step return the first sub step of a new step
376 if (isSuperStep) {
377 return superStepNumber + 1 + "." + 1;
378 }
379 // Else, return the next step as expected
380 return superStepNumber + "." + (subStepNumber + 1);
381 }
382 // Fetch data about next step
383 var nextStepCommitMessage = getRecentStepCommit(offset - 1, '%s');
384 var nextStepDescriptor = getStepDescriptor(nextStepCommitMessage);
385 var nextStepNumbers = nextStepDescriptor.number.split('.');
386 var nextSubStepNumber = Number(nextStepNumbers[1]);
387 var isNextSuperStep = !nextSubStepNumber;
388 if (isNextSuperStep) {
389 // If this is a super step return the next super step right away
390 if (isSuperStep) {
391 return (superStepNumber + 1).toString();
392 }
393 // Else, return the current super step
394 return superStepNumber.toString();
395 }
396 // If this is a super step return the first sub step of the next step
397 if (isSuperStep) {
398 return superStepNumber + 1 + "." + 1;
399 }
400 // Else, return the next step as expected
401 return superStepNumber + "." + (subStepNumber + 1);
402}
403// Get the next super step
404function getNextSuperStep(offset) {
405 return getNextStep(offset).split('.')[0];
406}
407// Pending flag indicates that this step map will be used in another tortilla repo
408function initializeStepMap(pending) {
409 var map = git_1.Git([
410 'log', '--format=%s', '--grep=^Step [0-9]\\+',
411 ])
412 .split('\n')
413 .filter(Boolean)
414 .reduce(function (m, subject) {
415 var num = getStepDescriptor(subject).number;
416 m[num] = num;
417 return m;
418 }, {});
419 local_storage_1.localStorage.setItem('STEP_MAP', JSON.stringify(map));
420 if (pending) {
421 local_storage_1.localStorage.setItem('STEP_MAP_PENDING', true);
422 }
423 else {
424 local_storage_1.localStorage.removeItem('STEP_MAP_PENDING');
425 }
426}
427// First argument represents the module we would like to read the steps map from
428function getStepMap(submoduleCwd, checkPending) {
429 var localStorage;
430 // In case this process was launched from a submodule
431 if (submoduleCwd) {
432 localStorage = local_storage_1.localStorage.create(submoduleCwd);
433 }
434 else {
435 localStorage = local_storage_1.localStorage;
436 }
437 if (ensureStepMap(submoduleCwd, checkPending)) {
438 return JSON.parse(localStorage.getItem('STEP_MAP'));
439 }
440}
441// Provided argument will run an extra condition to check whether the pending flag
442// exists or not
443function ensureStepMap(submoduleCwd, checkPending) {
444 // Step map shouldn't be used in this process
445 if (checkPending && local_storage_1.localStorage.getItem('STEP_MAP_PENDING')) {
446 return false;
447 }
448 var paths;
449 // In case this process was launched from a submodule
450 if (submoduleCwd) {
451 paths = paths_1.Paths.resolveProject(submoduleCwd);
452 }
453 else {
454 paths = paths_1.Paths;
455 }
456 return utils_1.Utils.exists(Path.resolve(paths.storage, 'STEP_MAP'), 'file');
457}
458function disposeStepMap() {
459 local_storage_1.localStorage.deleteItem('STEP_MAP');
460 local_storage_1.localStorage.deleteItem('STEP_MAP_PENDING');
461}
462function updateStepMap(type, payload) {
463 var map = getStepMap();
464 switch (type) {
465 case 'remove':
466 delete map[payload.step];
467 break;
468 case 'reset':
469 map[payload.oldStep] = payload.newStep;
470 break;
471 }
472 local_storage_1.localStorage.setItem('STEP_MAP', JSON.stringify(map));
473}
474// Gets a list of all steps, from root to the most recent step
475function getAllSteps() {
476 var allSteps = git_1.Git(['log', '--grep=^Step [0-9]\\+.\\?[0-9]*:', '--format=%s'])
477 .split('\n')
478 .map(function (message) { return getStepDescriptor(message); })
479 .filter(Boolean)
480 .map(function (descriptor) { return descriptor.number; })
481 .reverse();
482 allSteps.unshift('root');
483 return allSteps;
484}
485/**
486 Contains step related utilities.
487 */
488(function () {
489 if (require.main !== module) {
490 return;
491 }
492 var argv = Minimist(process.argv.slice(2), {
493 string: ['_', 'message', 'm'],
494 boolean: ['root', 'udiff', 'allow-empty'],
495 });
496 var method = argv._[0];
497 var step = argv._[1];
498 var message = argv.message || argv.m;
499 var root = argv.root;
500 var allowEmpty = argv['allow-empty'];
501 var udiff = argv.udiff;
502 if (!step && root) {
503 step = 'root';
504 }
505 var options = {
506 allowEmpty: allowEmpty,
507 udiff: udiff,
508 };
509 switch (method) {
510 case 'push':
511 return pushStep(message, options);
512 case 'pop':
513 return popStep();
514 case 'tag':
515 return tagStep(message);
516 case 'edit':
517 return editStep(step, options);
518 case 'sort':
519 return sortStep(step);
520 case 'reword':
521 return rewordStep(step, message);
522 }
523})();
524exports.Step = {
525 push: pushStep,
526 pop: popStep,
527 tag: tagStep,
528 edit: editStep,
529 sort: sortStep,
530 reword: rewordStep,
531 show: showStep,
532 assert: assertStep,
533 commit: commitStep,
534 current: getCurrentStep,
535 currentSuper: getCurrentSuperStep,
536 next: getNextStep,
537 nextSuper: getNextSuperStep,
538 base: getStepBase,
539 recentCommit: getRecentStepCommit,
540 recentSuperCommit: getRecentSuperStepCommit,
541 recentSubCommit: getRecentSubStepCommit,
542 descriptor: getStepDescriptor,
543 superDescriptor: getSuperStepDescriptor,
544 subDescriptor: getSubStepDescriptor,
545 initializeStepMap: initializeStepMap,
546 getStepMap: getStepMap,
547 ensureStepMap: ensureStepMap,
548 disposeStepMap: disposeStepMap,
549 updateStepMap: updateStepMap,
550 all: getAllSteps,
551};
552//# sourceMappingURL=step.js.map
\No newline at end of file