UNPKG

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