UNPKG

14.6 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 Tmp = require("tmp");
16var local_storage_1 = require("./local-storage");
17var paths_1 = require("./paths");
18var utils_1 = require("./utils");
19/**
20 Contains general git utilities.
21 */
22var exec = utils_1.Utils.exec;
23// This RegExp will help us pluck the versions in a conflict and solve it
24var conflict = /\n\s*<<<<<<< [^\n]+(\n(?:.|\n)+?)\n\s*=======(\n(?:.|\n)+?)\n\s*>>>>>>> [^\n]+/;
25function git(argv, options) {
26 return gitBody(utils_1.Utils.git, argv, options);
27}
28var gitPrint = git.print = function (argv, options) {
29 if (options === void 0) { options = {}; }
30 return gitBody(utils_1.Utils.git.print, argv, options);
31};
32// Push a tutorial based on the provided branch.
33// e.g. given 'master' then 'master-history', 'master-root', 'master@0.1.0', etc, will be pushed.
34// Note that everything will be pushed by FORCE and will override existing refs within the remote
35function pushTutorial(remote, baseBranch) {
36 var relatedBranches = git(['branch', '-l', '-a']).split('\n').map(function (branch) {
37 if (!branch) {
38 return null;
39 }
40 branch = branch.split(/\*?\s+/)[1];
41 var pathNodes = branch.split('/');
42 if (pathNodes[0] === 'remotes') {
43 if (pathNodes[1] !== remote) {
44 return;
45 }
46 }
47 var branchName = pathNodes.pop();
48 if (branchName === baseBranch) {
49 return branch;
50 }
51 if (branchName === baseBranch + "-history") {
52 return branch;
53 }
54 if (branchName === baseBranch + "-root") {
55 return branch;
56 }
57 if (new RegExp("^" + baseBranch + "-step\\d+$").test(branchName)) {
58 return branch;
59 }
60 }).filter(Boolean);
61 var relatedTags = git(['tag', '-l']).split('\n').map(function (tag) {
62 if (!tag) {
63 return null;
64 }
65 if (new RegExp("^" + baseBranch + "@(\\d+\\.\\d+\\.\\d+|next)$").test(tag)) {
66 return tag;
67 }
68 if (new RegExp("^" + baseBranch + "@root@(\\d+\\.\\d+\\.\\d+|next)$").test(tag)) {
69 return tag;
70 }
71 if (new RegExp("^" + baseBranch + "@step\\d+@(\\d+\\.\\d+\\.\\d+|next)$").test(tag)) {
72 return tag;
73 }
74 }).filter(Boolean);
75 var relatedBranchesNames = relatedBranches.map(function (b) { return b.split('/').pop(); });
76 var deletedBranches = git(['ls-remote', '--heads', remote])
77 .split('\n')
78 .filter(Boolean)
79 .map(function (line) { return line.split(/\s+/).pop(); })
80 .filter(function (ref) { return !relatedBranchesNames.includes(ref.split('/').pop()); })
81 .filter(function (ref) {
82 var branch = ref.split('/').pop();
83 return (branch === baseBranch ||
84 branch === baseBranch + "-history" ||
85 branch === baseBranch + "-root" ||
86 new RegExp("^" + baseBranch + "-step\\d+$").test(branch));
87 })
88 // https://stackoverflow.com/questions/5480258/how-to-delete-a-remote-tag
89 .map(function (ref) { return ":" + ref; });
90 var deletedTags = git(['ls-remote', '--tags', remote])
91 .split('\n')
92 .filter(Boolean)
93 .map(function (line) { return line.split(/\s+/).pop(); })
94 .filter(function (ref) { return !relatedTags.includes(ref.split('/').pop()); })
95 .filter(function (ref) {
96 return new RegExp("^refs/tags/" + baseBranch + "@(\\d+\\.\\d+\\.\\d+|next)$").test(ref) ||
97 new RegExp("^refs/tags/" + baseBranch + "@root@(\\d+\\.\\d+\\.\\d+|next)$").test(ref) ||
98 new RegExp("^refs/tags/" + baseBranch + "@step\\d+@(\\d+\\.\\d+\\.\\d+|next)$").test(ref);
99 })
100 // https://stackoverflow.com/questions/5480258/how-to-delete-a-remote-tag
101 .map(function (ref) { return ":" + ref; });
102 var refs = relatedBranches.concat(relatedTags, deletedBranches, deletedTags);
103 return gitPrint(['push', '-f', remote].concat(refs));
104}
105// Pull a tutorial based on the provided branch. e.g. given 'master' then 'master-history',
106// 'master-root', 'master@0.1.0', etc, will be pulled.
107function pullTutorial(remote, baseBranch) {
108 var relatedBranches = [];
109 var relatedTags = [];
110 git(['ls-remote', '--tags', '--heads', remote]).split('\n').forEach(function (line) {
111 if (!line) {
112 return;
113 }
114 var _a = line.split(/\s+/), ref = _a[1];
115 if (new RegExp("^refs/tags/" + baseBranch + "@(root|step-\\d+@)?(\\d+\\.\\d+\\.\\d+|next)$").test(ref)) {
116 relatedTags.push(ref.split('/').slice(2).join('/'));
117 }
118 if (new RegExp("^refs/heads/" + baseBranch + "(-root|-history|-step\\d+)?$").test(ref)) {
119 relatedBranches.push(ref.split('/').slice(2).join('/'));
120 }
121 });
122 var refs = relatedBranches.concat(relatedTags);
123 var activeBranchName = exports.Git.activeBranchName();
124 try {
125 var sha1 = exports.Git(['rev-parse', activeBranchName]);
126 // Detach HEAD so we can change the reference of the branch
127 exports.Git(['checkout', sha1]);
128 // --tags flag will overwrite tags
129 gitPrint(['fetch', '--tags', '-f', remote].concat(refs));
130 // Make sure that all local branches track the right remote branches
131 relatedBranches.forEach(function (branch) {
132 try {
133 exports.Git(['branch', '-D', branch]);
134 }
135 catch (e) {
136 // Branch doesn't exist
137 }
138 exports.Git.print(['branch', '--track', branch, "remotes/" + remote + "/" + branch]);
139 });
140 }
141 finally {
142 // Get back to where we were, regardless of the outcome
143 exports.Git(['checkout', activeBranchName]);
144 }
145}
146// Used internally by tutorialStatus() to get the right step message
147function getRefStep(ref) {
148 if (ref === void 0) { ref = 'HEAD'; }
149 if (getRootHash() === exports.Git(['rev-parse', ref])) {
150 return 'root';
151 }
152 var match = exports.Git(['log', ref, '-1', '--format=%s']).match(/^Step (\d+(?:\.\d+)?)/);
153 if (match) {
154 return match[1];
155 }
156 return exports.Git(['rev-parse', '--short', ref]);
157}
158// Print edit status followed by git-status
159function tutorialStatus(options) {
160 if (options === void 0) { options = {}; }
161 exports.Git.print(['status']);
162 var instructions;
163 position: if (isRebasing()) {
164 var head = exports.Git(['rev-parse', 'HEAD']);
165 var rebaseHead = exports.Git(['rev-parse', 'REBASE_HEAD']);
166 var headStep = getRefStep('HEAD');
167 if (head === rebaseHead) {
168 console.log("\nEditing " + headStep);
169 instructions = 'edit';
170 break position;
171 }
172 var rebaseHeadStep = getRefStep('REBASE_HEAD');
173 var isConflict = local_storage_1.localStorage.getItem('REBASE_NEW_STEP') !== headStep;
174 if (isConflict) {
175 console.log("\nSolving conflict between " + headStep + " and " + rebaseHeadStep);
176 instructions = 'conflict';
177 break position;
178 }
179 console.log("\nBranched out from " + rebaseHeadStep + " to " + headStep);
180 instructions = 'edit';
181 }
182 switch (options.instruct && instructions) {
183 case 'edit':
184 console.log('\n' + utils_1.freeText("\n To edit the current step, stage your changes and amend them:\n\n $ git add xxx\n $ git commit --amend\n\n Feel free to push or pop steps:\n\n $ tortilla step push/pop\n\n Once you finish, continue the rebase and Tortilla will take care of the rest:\n\n $ git rebase --continue\n\n You can go back to re-edit previous steps at any point, but be noted that this will discard all your changes thus far:\n\n $ tortilla step back\n\n If for some reason, at any point you decide to quit, use the comand:\n\n $ git rebase --abort\n "));
185 case 'conflict':
186 console.log('\n' + utils_1.freeText("\n Once you solved the conflict, stage your changes and continue the rebase.\n DO NOT amend your changes, push or pop steps:\n\n $ git add xxx\n $ git rebase --continue\n\n You can go back to re-edit previous steps at any point, but be noted that this will discard all your changes thus far:\n\n $ tortilla step back\n\n If for some reason, at any point you decide to quit, use the comand:\n\n $ git rebase --abort\n "));
187 }
188}
189// The body of the git execution function, useful since we use the same logic both for
190// exec and spawn
191function gitBody(handler, argv, options) {
192 options = __assign({ env: {} }, options);
193 // Zeroing environment vars which might affect other executions
194 options.env = __assign({ GIT_DIR: null, GIT_WORK_TREE: null }, options.env);
195 return handler(argv, options);
196}
197// Tells if rebasing or not
198function isRebasing(path) {
199 if (path === void 0) { path = null; }
200 var paths = path ? paths_1.resolveProject(path).git : paths_1.Paths.git;
201 return utils_1.Utils.exists(paths.rebaseMerge) || utils_1.Utils.exists(paths.rebaseApply);
202}
203// Tells if cherry-picking or not
204function isCherryPicking() {
205 return utils_1.Utils.exists(paths_1.Paths.git.heads.cherryPick) || utils_1.Utils.exists(paths_1.Paths.git.heads.revert);
206}
207// Tells if going to amend or not
208function gonnaAmend() {
209 return utils_1.Utils.childProcessOf('git', ['commit', '--amend']);
210}
211// Tells if a tag exists or not
212function tagExists(tag) {
213 try {
214 git(['rev-parse', tag]);
215 return true;
216 }
217 catch (err) {
218 return false;
219 }
220}
221// Get the recent commit by the provided arguments. An offset can be specified which
222// means that the recent commit from several times back can be fetched as well
223function getRecentCommit(offset, argv, options, path) {
224 if (path === void 0) { path = null; }
225 if (offset instanceof Array) {
226 options = argv;
227 argv = offset;
228 offset = 0;
229 }
230 else {
231 argv = argv || [];
232 offset = offset || 0;
233 }
234 var hash = typeof offset === 'string' ? offset : ("HEAD~" + offset);
235 argv = ['log', hash, '-1'].concat(argv);
236 return git(argv, path ? __assign({}, options, { cwd: path }) : options);
237}
238// Gets a list of the modified files reported by git matching the provided pattern.
239// This includes untracked files, changed files and deleted files
240function getStagedFiles(pattern) {
241 var stagedFiles = git(['diff', '--name-only', '--cached'])
242 .split('\n')
243 .filter(Boolean);
244 return utils_1.Utils.filterMatches(stagedFiles, pattern);
245}
246// Gets active branch name
247function getActiveBranchName(path) {
248 if (path === void 0) { path = null; }
249 if (!isRebasing(path)) {
250 return git(['rev-parse', '--abbrev-ref', 'HEAD'], path ? { cwd: path } : null);
251 }
252 // Getting a reference for the hash of which the rebase have started
253 var branchHash = git(['reflog', '--format=%gd %gs'], path ? { cwd: path } : null)
254 .split('\n')
255 .filter(Boolean)
256 .map(function (line) { return line.split(' '); })
257 .map(function (split) { return [split.shift(), split.join(' ')]; })
258 .find(function (_a) {
259 var ref = _a[0], msg = _a[1];
260 return msg.match(/^rebase -i \(start\)/);
261 })
262 .shift()
263 .match(/^HEAD@\{(\d+)\}$/)
264 .slice(1)
265 .map(function (i) { return "HEAD@{" + ++i + "}"; })
266 .map(function (ref) { return git(['rev-parse', ref]); })
267 .pop();
268 // Comparing the found hash to each of the branches' hashes
269 return Fs.readdirSync(paths_1.Paths.git.refs.heads).find(function (branchName) {
270 return git(['rev-parse', branchName], path ? { cwd: path } : null) === branchHash;
271 });
272}
273// Gets the root hash of HEAD
274function getRootHash(head, options) {
275 if (head === void 0) { head = 'HEAD'; }
276 if (options === void 0) { options = {}; }
277 return git(['rev-list', '--max-parents=0', head], options);
278}
279function getRoot() {
280 try {
281 return git(['rev-parse', '--show-toplevel']);
282 // Not a git project
283 }
284 catch (e) {
285 return '';
286 }
287}
288function edit(initialContent) {
289 var editor = getEditor();
290 var file = Tmp.fileSync();
291 Fs.writeFileSync(file.name, initialContent);
292 exec.print('sh', ['-c', editor + " " + file.name]);
293 var content = Fs.readFileSync(file.name).toString();
294 file.removeCallback();
295 return content;
296}
297// https://github.com/git/git/blob/master/git-rebase--interactive.sh#L257
298function getEditor() {
299 var editor = process.env.GIT_EDITOR;
300 if (!editor) {
301 try {
302 editor = git(['config', 'core.editor']);
303 }
304 catch (e) {
305 // Ignore
306 }
307 }
308 if (!editor) {
309 try {
310 editor = git(['var', 'GIT_EDITOR']);
311 }
312 catch (e) {
313 // Ignore
314 }
315 }
316 if (!editor) {
317 throw Error('Git editor could not be found');
318 }
319 return editor;
320}
321// Commander will split equal signs e.g. `--format=%H` which is is not the desired
322// behavior for git. This puts the everything back together when necessary
323function normalizeArgv(argv) {
324 argv = argv.slice();
325 {
326 var i = argv.indexOf('--format');
327 if (i !== -1) {
328 argv.splice(i, 2, "--format=" + argv[i + 1]);
329 }
330 }
331 return argv;
332}
333function getRevisionIdFromObject(object) {
334 return git(['rev-list', '-n', '1', object]);
335}
336exports.Git = utils_1.Utils.extend(git.bind(null), git, {
337 pushTutorial: pushTutorial,
338 pullTutorial: pullTutorial,
339 tutorialStatus: tutorialStatus,
340 conflict: conflict,
341 rebasing: isRebasing,
342 cherryPicking: isCherryPicking,
343 gonnaAmend: gonnaAmend,
344 tagExists: tagExists,
345 recentCommit: getRecentCommit,
346 stagedFiles: getStagedFiles,
347 activeBranchName: getActiveBranchName,
348 rootHash: getRootHash,
349 root: getRoot,
350 edit: edit,
351 editor: getEditor,
352 normalizeArgv: normalizeArgv,
353 getRevisionIdFromObject: getRevisionIdFromObject,
354});
355//# sourceMappingURL=git.js.map
\No newline at end of file