UNPKG

16.1 kBJavaScriptView Raw
1"use strict";
2var __assign = (this && this.__assign) || function () {
3 __assign = Object.assign || function(t) {
4 for (var s, i = 1, n = arguments.length; i < n; i++) {
5 s = arguments[i];
6 for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7 t[p] = s[p];
8 }
9 return t;
10 };
11 return __assign.apply(this, arguments);
12};
13Object.defineProperty(exports, "__esModule", { value: true });
14var Fs = require("fs-extra");
15var Minimist = require("minimist");
16var Path = require("path");
17var git_1 = require("./git");
18var local_storage_1 = require("./local-storage");
19var paths_1 = require("./paths");
20var step_1 = require("./step");
21var utils_1 = require("./utils");
22/**
23 This is the editor for interactive rebases and amended commits. Instead of opening
24 an editing software like 'nano' or 'vim', this module will edit the file by specified
25 methods we choose.
26 */
27function init() {
28 if (require.main !== module) {
29 return;
30 }
31 var argv = Minimist(process.argv.slice(2), {
32 string: ['_', 'message', 'm', 'udiff'],
33 });
34 // The first argument will be the rebase file path provided to us by git
35 var method = argv._[0];
36 var steps = argv._.slice(1, -1);
37 var rebaseFilePath = argv._[argv._.length - 1];
38 // This method doesn't require us to parse operations, it just resets todo from a string
39 if (method === 'reset-todo') {
40 var todo = new local_storage_1.localStorage.native(paths_1.Paths.rebaseStates).getItem('TODO');
41 Fs.writeFileSync(rebaseFilePath, todo);
42 return;
43 }
44 var message = argv.message || argv.m;
45 var udiff = argv.udiff;
46 // Rebase file path will always be appended at the end of the arguments vector,
47 // therefore udiff has to have a value, otherwise it will be matched with the wrong
48 // argument
49 var options = {
50 udiff: udiff === 'true' ? '' : udiff,
51 };
52 var rebaseFileContent = Fs.readFileSync(rebaseFilePath, 'utf8');
53 // Convert to array of jsons so it would be more comfortable to word with
54 var operations = disassemblyOperations(rebaseFileContent);
55 // Set flag just in case recent rebase was aborted
56 local_storage_1.localStorage.removeItem('REBASE_HOOKS_DISABLED');
57 // Automatically invoke a method by the provided arguments.
58 // The methods will manipulate the operations array.
59 switch (method) {
60 case 'edit':
61 editStep(operations, steps, options);
62 break;
63 case 'edit-head':
64 editHead(operations);
65 break;
66 case 'sort':
67 sortSteps(operations, options);
68 break;
69 case 'reword':
70 rewordStep(operations, message);
71 break;
72 case 'render':
73 renderManuals(operations);
74 break;
75 }
76 // Put everything back together and rewrite the rebase file
77 var newRebaseFileContent = assemblyOperations(operations);
78 Fs.writeFileSync(rebaseFilePath, newRebaseFileContent);
79}
80init();
81// Edit the last step in the rebase file
82function editStep(operations, steps, options) {
83 // Create initial step map
84 // Note that udiff is a string, since it may very well specify a module path
85 if (options.udiff != null) {
86 // Providing pending flag
87 step_1.Step.initializeStepMap(!!options.udiff);
88 }
89 if (!steps || steps.length === 0) {
90 var descriptor = step_1.Step.descriptor(operations[0].message);
91 var step = (descriptor && descriptor.number) || 'root';
92 steps = [step];
93 }
94 // This way we can store data on each step string
95 // We have to keep it as `new String` and not `String`!
96 /* tslint:disable-next-line */
97 steps = steps.map(function (step) { return new String(step); });
98 // Edit each commit which is relevant to the specified steps
99 steps.forEach(function (step) {
100 if (String(step) === 'root') {
101 var operation = operations[0];
102 operation.method = 'edit';
103 step.operation = operation;
104 }
105 else {
106 var operation = operations.find(function (_a) {
107 var message = _a.message;
108 if (!message) {
109 return;
110 }
111 var descriptor = step_1.Step.descriptor(message);
112 return descriptor && descriptor.number === String(step);
113 });
114 if (!operation) {
115 return;
116 }
117 operation.method = 'edit';
118 step.operation = operation;
119 }
120 });
121 // Probably editing the recent step in which case no sorts are needed
122 if (operations.length > 1) {
123 // Prepare meta-data for upcoming sorts
124 var descriptor = step_1.Step.descriptor(operations[0].message);
125 // Step exists
126 if (descriptor) {
127 local_storage_1.localStorage.setItem('REBASE_OLD_STEP', descriptor.number);
128 local_storage_1.localStorage.setItem('REBASE_NEW_STEP', descriptor.number);
129 }
130 else {
131 local_storage_1.localStorage.setItem('REBASE_OLD_STEP', 'root');
132 local_storage_1.localStorage.setItem('REBASE_NEW_STEP', 'root');
133 }
134 // Building sort command
135 var sort_1 = "node " + paths_1.Paths.tortilla.editor + " sort";
136 if (options.udiff) {
137 sort_1 = sort_1 + " --udiff=" + options.udiff;
138 }
139 sort_1 = "GIT_SEQUENCE_EDITOR=\"" + sort_1 + "\"";
140 var saveRebaseState_1 = "GIT_SEQUENCE_EDITOR=\"node " + paths_1.Paths.tortilla.rebase + " stash-rebase-state\"";
141 // Continue sorting the steps after step editing has been finished
142 steps.forEach(function (step) {
143 var operation = step.operation;
144 if (!operation) {
145 return;
146 }
147 var index = operations.indexOf(operation);
148 // Insert the following operation AFTER the step's operation
149 operations.splice(index + 1, 0, {
150 method: 'exec',
151 command: sort_1 + " git rebase --edit-todo",
152 });
153 // Insert the following operation BEFORE the step's operation
154 operations.splice(index, 0, {
155 method: 'exec',
156 command: saveRebaseState_1 + " git rebase --edit-todo",
157 });
158 });
159 }
160 // Whether we edit the most recent step or not, rebranching process should be initiated
161 var rebranchSuper = "GIT_SEQUENCE_EDITOR=\"node " + paths_1.Paths.tortilla.rebase + " rebranch-super\"";
162 // After rebase has finished, update the brancehs referencing the super steps
163 operations.push({
164 method: 'exec',
165 command: rebranchSuper + " git rebase --edit-todo",
166 });
167}
168// Adjusts upcoming step numbers in rebase
169function sortSteps(operations, options) {
170 // Grab meta-data
171 var oldStep = local_storage_1.localStorage.getItem('REBASE_OLD_STEP');
172 var newStep = local_storage_1.localStorage.getItem('REBASE_NEW_STEP');
173 var submoduleCwd = local_storage_1.localStorage.getItem('SUBMODULE_CWD');
174 // If delta is 0 no sorts are needed
175 if (oldStep === newStep) {
176 local_storage_1.localStorage.setItem('REBASE_HOOKS_DISABLED', 1);
177 // Escape unless we need to update stepDiffs for submodules
178 if (!submoduleCwd) {
179 return;
180 }
181 }
182 var stepLimit = getStepLimit(oldStep, newStep);
183 var editFlag = false;
184 var offset = 0;
185 operations.slice().some(function (operation, index) {
186 var currStepDescriptor = step_1.Step.descriptor(operation.message || '');
187 // Skip commits which are not step commits
188 if (!currStepDescriptor) {
189 return;
190 }
191 var currStepSplit = currStepDescriptor.number.split('.');
192 var currSuperStep = currStepSplit[0];
193 var currSubStep = currStepSplit[1];
194 if (submoduleCwd) {
195 // If this is a super step, replace pick operation with the super pick
196 if (!currSubStep) {
197 operations.splice(index + offset, 1, {
198 method: 'exec',
199 command: "node " + paths_1.Paths.tortilla.rebase + " super-pick " + operation.hash,
200 });
201 }
202 }
203 else if (currSuperStep > stepLimit) {
204 // Prepend local storage item setting operation, this would be a flag which will be
205 // used in git-hooks
206 operations.splice(index + offset++, 0, {
207 method: 'exec',
208 command: "node " + paths_1.Paths.tortilla.localStorage + " set REBASE_HOOKS_DISABLED 1",
209 });
210 // Abort operations loop
211 return true;
212 }
213 // If this is a super step, replace pick operation with the super pick
214 if (!currSubStep) {
215 operations.splice(index + offset, 1, {
216 method: 'exec',
217 command: "node " + paths_1.Paths.tortilla.rebase + " super-pick " + operation.hash,
218 });
219 }
220 // If another step edit is pending, we will first perform the reword and only then
221 // we will proceed to the editing itself, since we wanna ensure that all the previous
222 // step indexes are already sorted
223 if (operation.method === 'edit') {
224 // Pick BEFORE edit
225 operations.splice(index + offset++, 0, __assign({}, operation, { method: 'pick' }));
226 // Update commit's step number
227 operations.splice(index + offset++, 0, {
228 method: 'exec',
229 command: "GIT_EDITOR=true node " + paths_1.Paths.tortilla.rebase + " reword",
230 });
231 var editor = "GIT_SEQUENCE_EDITOR=\"node " + paths_1.Paths.tortilla.editor + " edit-head\"";
232 // Replace edited step with the reworded one
233 operations.splice(index + offset++, 0, {
234 method: 'exec',
235 command: editor + " git rebase --edit-todo",
236 });
237 operations.splice(index + offset, 1);
238 // The sorting process should continue after we've finished editing the step, for now
239 // we will need to abort the current sorting process
240 return editFlag = true;
241 }
242 // Update commit's step number
243 operations.splice(index + ++offset, 0, {
244 method: 'exec',
245 command: "GIT_EDITOR=true node " + paths_1.Paths.tortilla.rebase + " reword",
246 });
247 });
248 // Remove hooks storage items so it won't affect post-rebase operations, but only if
249 // there are no any further step edits pending
250 if (!editFlag) {
251 operations.push({
252 method: 'exec',
253 command: "node " + paths_1.Paths.tortilla.localStorage + " remove HOOK_STEP",
254 });
255 // If specified udiff is a path to another tortilla repo
256 if (options.udiff) {
257 var subCwd = utils_1.Utils.cwd();
258 var cwd = Path.resolve(utils_1.Utils.cwd(), options.udiff);
259 // Update the specified repo's manual files
260 // Note that TORTILLA_CHILD_PROCESS and TORTILLA_CWD flags are set to
261 // prevent external interventions, mostly because of tests
262 operations.push({
263 method: 'exec',
264 command: utils_1.Utils.shCmd("\n export GIT_DIR=" + cwd + "/.git\n export GIT_WORK_TREE=" + cwd + "\n export TORTILLA_CHILD_PROCESS=true\n export TORTILLA_CWD=" + cwd + "\n export TORTILLA_SUBMODULE_CWD=" + subCwd + "\n\n if node " + paths_1.Paths.cli.tortilla + " step edit --root ; then\n git rebase --continue\n else\n git rebase --abort\n fi\n "),
265 });
266 }
267 // Ensure step map is being disposed
268 operations.push({
269 method: 'exec',
270 command: "node " + paths_1.Paths.tortilla.localStorage + " remove STEP_MAP STEP_MAP_PENDING",
271 });
272 }
273}
274// Edit the commit which is presented as the current HEAD
275function editHead(operations) {
276 // Prepare meta-data for upcoming sorts
277 var descriptor = step_1.Step.descriptor(operations[0].message);
278 // Descriptor should always exist, but just in case
279 if (descriptor) {
280 local_storage_1.localStorage.setItem('REBASE_OLD_STEP', descriptor.number);
281 }
282 var head = git_1.Git.recentCommit(['--format=%h m']).split(' ');
283 var hash = head.shift();
284 var message = head.join(' ');
285 // Remove head commit so there won't be any conflicts
286 operations.push({
287 method: 'exec',
288 command: 'git reset --hard HEAD~1',
289 });
290 // Re-pick and edit head commit
291 operations.push({
292 method: 'edit',
293 hash: hash,
294 message: message,
295 });
296}
297// Reword the last step in the rebase file
298function rewordStep(operations, message) {
299 var argv = [paths_1.Paths.tortilla.rebase, 'reword'];
300 if (message) {
301 argv.push("\"" + message + "\"");
302 }
303 // Replace original message with the provided message
304 operations.splice(1, 0, {
305 method: 'exec',
306 command: "node " + argv.join(' '),
307 });
308}
309// Render all manuals since the beginning of history to the opposite format
310function renderManuals(operations) {
311 var offset = 2;
312 // Render README.md
313 operations.splice(1, 0, {
314 method: 'exec',
315 command: "node " + paths_1.Paths.tortilla.manual + " render --root",
316 });
317 operations.slice(offset).forEach(function (operation, index) {
318 var stepDescriptor = step_1.Step.superDescriptor(operation.message);
319 if (!stepDescriptor) {
320 return;
321 }
322 // Render step manual file
323 operations.splice(index + ++offset, 0, {
324 method: 'exec',
325 command: "node " + paths_1.Paths.tortilla.manual + " render " + stepDescriptor.number,
326 });
327 return offset;
328 });
329 // Re-adjust branch references since hash values are outdated at this point
330 operations.push({
331 method: 'exec',
332 command: "node " + paths_1.Paths.tortilla.rebase + " rebranch-super",
333 });
334}
335// The step limit of which sorts are needed would be determined by the step
336// which is greater
337function getStepLimit(oldStep, newStep) {
338 oldStep = oldStep === 'root' ? '0' : oldStep;
339 var newSuperStep = newStep === 'root' ? '0' : newStep;
340 // Grabbing step splits for easy access
341 var oldStepSplits = oldStep.split('.');
342 var newStepSplits = newStep.split('.');
343 var oldSuperStep = oldStepSplits[0];
344 newSuperStep = newStepSplits[0];
345 var oldSubStep = oldStepSplits[1];
346 var newSubStep = newStepSplits[1];
347 if (oldSuperStep === newSuperStep) {
348 // 1.1, 1.2 or 1.2, 1.1 or 1.1 or 1.1, 1
349 if (oldSubStep) {
350 return oldSuperStep;
351 }
352 // 1, 1.1
353 return Infinity;
354 }
355 // 1, 2.1
356 if (!oldSubStep && newSubStep && Number(newSuperStep) === Number(oldSuperStep) + 1) {
357 return newSuperStep;
358 }
359 // 2.1, 1
360 if (!newSubStep && oldSubStep && Number(oldSuperStep) === Number(newSuperStep) + 1) {
361 return oldSuperStep;
362 }
363 // 1, 2 or 1, 3.1 or 1.1, 2.1 or 1.1, 2
364 return Infinity;
365}
366// Convert rebase file content to operations array
367function disassemblyOperations(rebaseFileContent) {
368 var operations = rebaseFileContent.match(/^[a-z]+\s.{7}.*$/mg);
369 if (!operations) {
370 return [];
371 }
372 return operations.map(function (line) {
373 var split = line.split(' ');
374 return {
375 method: split[0],
376 hash: split[1],
377 message: split.slice(2).join(' '),
378 };
379 });
380}
381// Convert operations array to rebase file content
382function assemblyOperations(operations) {
383 return operations
384 // Compose lines
385 .map(function (operation) { return Object
386 .keys(operation)
387 .map(function (k) { return operation[k]; })
388 .join(' '); })
389 // Connect lines
390 .join('\n') + '\n';
391}
392//# sourceMappingURL=editor.js.map
\No newline at end of file