reslop 0.2.3 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -1
- package/README.md +8 -3
- package/bin/reslop.js +4 -2
- package/lib/api.js +2 -1
- package/lib/cli.js +19 -10
- package/lib/clipboard.js +6 -3
- package/lib/deps-diff.js +61 -115
- package/lib/deps.js +24 -61
- package/lib/detect.js +4 -2
- package/lib/diff/align.js +2 -1
- package/lib/diff/diff.js +6 -3
- package/lib/diff/display.js +2 -1
- package/lib/diff/inline.js +2 -1
- package/lib/diff/patch.js +2 -1
- package/lib/diff/unit.js +4 -2
- package/lib/editor.js +22 -4
- package/lib/files.js +38 -12
- package/lib/git-branches.js +107 -154
- package/lib/git-deps.js +20 -60
- package/lib/git-worktree.js +508 -0
- package/lib/git.js +23 -580
- package/lib/github.js +2 -1
- package/lib/gitlab.js +2 -1
- package/lib/highlight/bash.js +2 -1
- package/lib/highlight/css.js +2 -1
- package/lib/highlight/csv.js +2 -1
- package/lib/highlight/dot.js +2 -1
- package/lib/highlight/html.js +2 -1
- package/lib/highlight/json.js +2 -1
- package/lib/highlight/log.js +2 -1
- package/lib/highlight/txt.js +2 -1
- package/lib/highlight.js +2 -1
- package/lib/keys.js +2 -2
- package/lib/manifest.js +4 -2
- package/lib/npm-commands.js +83 -4
- package/lib/remote.js +20 -13
- package/lib/render/branches.js +6 -15
- package/lib/render/chrome.js +27 -7
- package/lib/render/commits.js +158 -46
- package/lib/render/diff.js +130 -47
- package/lib/render/files.js +4 -12
- package/lib/render/frame.js +3 -9
- package/lib/render/notes.js +4 -11
- package/lib/render/npm.js +1 -13
- package/lib/render/present.js +2 -1
- package/lib/render/primitives.js +28 -15
- package/lib/render/render.js +2 -1
- package/lib/render/unit.js +14 -11
- package/lib/report-model.js +6 -4
- package/lib/report-parse.js +8 -13
- package/lib/report-render.js +38 -21
- package/lib/report-run.js +12 -10
- package/lib/report-test.js +5 -4
- package/lib/report-tools.js +44 -18
- package/lib/review-merge.js +2 -1
- package/lib/review.js +6 -2
- package/lib/session/actions.js +49 -65
- package/lib/session/branches.js +13 -49
- package/lib/session/change-actions.js +2 -1
- package/lib/session/commit.js +34 -49
- package/lib/session/compose.js +37 -11
- package/lib/session/dispatch.js +17 -25
- package/lib/session/files-pane.js +6 -27
- package/lib/session/items.js +6 -3
- package/lib/session/lifecycle.js +8 -4
- package/lib/session/load.js +4 -2
- package/lib/session/navigation.js +1 -0
- package/lib/session/npm.js +54 -1
- package/lib/session/ops.js +15 -1
- package/lib/session/pointer.js +22 -3
- package/lib/session/review.js +2 -1
- package/lib/session/terminal.js +4 -2
- package/lib/session/update.js +2 -1
- package/lib/session/view.js +29 -3
- package/lib/session.js +37 -23
- package/lib/source.js +6 -3
- package/lib/update.js +5 -9
- package/lib/utilities.js +4 -2
- package/lib/wrap.js +3 -1
- package/package.json +1 -1
package/lib/editor.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const {
|
|
3
|
+
const ansi = require('./ansi.js');
|
|
4
|
+
const { graphemes, visibleWidth } = ansi;
|
|
5
|
+
const wrap = require('./wrap.js');
|
|
6
|
+
const { wrapMove } = wrap;
|
|
5
7
|
|
|
6
8
|
const UNDO_CAP = 100;
|
|
7
9
|
|
|
@@ -59,6 +61,7 @@ class Editor {
|
|
|
59
61
|
this.undo = [];
|
|
60
62
|
this.redo = [];
|
|
61
63
|
this.wantCol = null;
|
|
64
|
+
this.scrollCol = 0;
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
snapshot() {
|
|
@@ -132,6 +135,19 @@ class Editor {
|
|
|
132
135
|
return { line, col };
|
|
133
136
|
}
|
|
134
137
|
|
|
138
|
+
visibleLineCol() {
|
|
139
|
+
const { col, line } = this.linePos();
|
|
140
|
+
const row = this.text.split('\n')[line] ?? '';
|
|
141
|
+
return visibleWidth(row.slice(0, col));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
reveal(width) {
|
|
145
|
+
const col = this.visibleLineCol();
|
|
146
|
+
const room = Math.max(1, width);
|
|
147
|
+
if (col < this.scrollCol) this.scrollCol = col;
|
|
148
|
+
if (col >= this.scrollCol + room) this.scrollCol = col - room + 1;
|
|
149
|
+
}
|
|
150
|
+
|
|
135
151
|
moveLine(delta, width) {
|
|
136
152
|
if (width !== undefined) {
|
|
137
153
|
const col = this.wantCol;
|
|
@@ -142,8 +158,10 @@ class Editor {
|
|
|
142
158
|
}
|
|
143
159
|
const rows = this.text.split('\n');
|
|
144
160
|
const { line, col } = this.linePos();
|
|
145
|
-
|
|
146
|
-
if (next < 0
|
|
161
|
+
let next = line + delta;
|
|
162
|
+
if (next < 0) next = 0;
|
|
163
|
+
if (next >= rows.length) next = rows.length - 1;
|
|
164
|
+
if (next === line) return;
|
|
147
165
|
const row = rows[next];
|
|
148
166
|
const c = Math.min(col, row.length);
|
|
149
167
|
let cursor = 0;
|
package/lib/files.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const manifest = require('./manifest.js');
|
|
4
|
+
const { listFilePath } = manifest;
|
|
4
5
|
|
|
5
6
|
const REVIEW_DIR = '.review';
|
|
6
7
|
const ORIGIN_ORDER = ['staged', 'unstaged', 'untracked', 'commit', 'pr'];
|
|
7
8
|
const TODO_FILE = 'TODOs';
|
|
8
|
-
const REPO_TODOS_LABEL = '
|
|
9
|
+
const REPO_TODOS_LABEL = 'Project Backlog and Issues';
|
|
9
10
|
const TOTAL_LABEL = 'total';
|
|
10
11
|
|
|
11
12
|
const isPathInScope = (rel, paths) => {
|
|
@@ -73,23 +74,47 @@ const lineDelta = ({ hunk, blockId }) => {
|
|
|
73
74
|
return delta;
|
|
74
75
|
};
|
|
75
76
|
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
const AGE_UNIT = {
|
|
78
|
+
second: 's',
|
|
79
|
+
minute: 'm',
|
|
80
|
+
hour: 'h',
|
|
81
|
+
day: 'd',
|
|
82
|
+
week: 'w',
|
|
83
|
+
month: 'mo',
|
|
84
|
+
year: 'y',
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const AGE_AGO = /^(\d+)\s+(second|minute|hour|day|week|month|year)s?\s+ago$/i;
|
|
88
|
+
const AGE_SHORT = /^(\d+)(mo|[smhdwy])(?: ago)?$/i;
|
|
89
|
+
|
|
90
|
+
const shortUnit = (n, unit) => `${n}${AGE_UNIT[unit]} ago`;
|
|
91
|
+
|
|
92
|
+
const shortAge = (text) => {
|
|
93
|
+
const raw = `${text ?? ''}`.trim();
|
|
94
|
+
if (!raw) return '';
|
|
95
|
+
if (/^yesterday$/i.test(raw)) return '1d ago';
|
|
96
|
+
const match = AGE_AGO.exec(raw);
|
|
97
|
+
if (match) {
|
|
98
|
+
const unit = AGE_UNIT[match[2].toLowerCase()];
|
|
99
|
+
if (unit) return `${match[1]}${unit} ago`;
|
|
100
|
+
}
|
|
101
|
+
const brief = AGE_SHORT.exec(raw);
|
|
102
|
+
if (brief) return `${brief[1]}${brief[2].toLowerCase()} ago`;
|
|
103
|
+
return raw;
|
|
79
104
|
};
|
|
80
105
|
|
|
81
106
|
const relativeAge = (thenMs, nowMs = Date.now()) => {
|
|
82
107
|
const diff = Math.max(0, Math.round((nowMs - thenMs) / 1000));
|
|
83
|
-
if (diff < 90) return
|
|
108
|
+
if (diff < 90) return shortUnit(diff, 'second');
|
|
84
109
|
const minutes = Math.round(diff / 60);
|
|
85
|
-
if (minutes < 90) return
|
|
110
|
+
if (minutes < 90) return shortUnit(minutes, 'minute');
|
|
86
111
|
const hours = Math.round(minutes / 60);
|
|
87
|
-
if (hours < 36) return
|
|
112
|
+
if (hours < 36) return shortUnit(hours, 'hour');
|
|
88
113
|
const days = Math.round(hours / 24);
|
|
89
|
-
if (days < 14) return
|
|
90
|
-
if (days < 70) return
|
|
91
|
-
if (days < 365) return
|
|
92
|
-
return
|
|
114
|
+
if (days < 14) return shortUnit(days, 'day');
|
|
115
|
+
if (days < 70) return shortUnit(Math.round(days / 7), 'week');
|
|
116
|
+
if (days < 365) return shortUnit(Math.round(days / 30), 'month');
|
|
117
|
+
return shortUnit(Math.round(days / 365), 'year');
|
|
93
118
|
};
|
|
94
119
|
|
|
95
120
|
const addLineDelta = (group, origin, delta) => {
|
|
@@ -231,4 +256,5 @@ module.exports = {
|
|
|
231
256
|
fileEntries,
|
|
232
257
|
fileTotals,
|
|
233
258
|
relativeAge,
|
|
259
|
+
shortAge,
|
|
234
260
|
};
|
package/lib/git-branches.js
CHANGED
|
@@ -4,7 +4,10 @@ const fs = require('node:fs');
|
|
|
4
4
|
const os = require('node:os');
|
|
5
5
|
const path = require('node:path');
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const files = require('./files.js');
|
|
8
|
+
const gitWorktree = require('./git-worktree.js');
|
|
9
|
+
const { shortAge } = files;
|
|
10
|
+
const { oneLine, requireOk, runGit, runGitAsync } = gitWorktree;
|
|
8
11
|
|
|
9
12
|
const BRANCH_FORMAT = [
|
|
10
13
|
'%(HEAD)',
|
|
@@ -72,27 +75,56 @@ const parseCommitRecord = (record, index) => {
|
|
|
72
75
|
shortSha: read('%h') || sha.slice(0, 7),
|
|
73
76
|
author: read('%an'),
|
|
74
77
|
email: read('%ae'),
|
|
75
|
-
date: read('%ar'),
|
|
78
|
+
date: shortAge(read('%ar')),
|
|
76
79
|
when: read('%ci'),
|
|
77
80
|
refs: formatCommitRefs(read('%D')),
|
|
78
81
|
subject,
|
|
79
82
|
body,
|
|
83
|
+
added: 0,
|
|
84
|
+
removed: 0,
|
|
80
85
|
head: index === 0,
|
|
81
86
|
};
|
|
82
87
|
};
|
|
83
88
|
|
|
89
|
+
const STAT_LEAD = /^\n* *\d+ files? changed[^\n]*\n?/;
|
|
90
|
+
|
|
91
|
+
const takeStat = (chunk) => {
|
|
92
|
+
const match = STAT_LEAD.exec(chunk);
|
|
93
|
+
if (!match) return { stat: '', rest: chunk };
|
|
94
|
+
return { stat: match[0], rest: chunk.slice(match[0].length) };
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const countIn = (line, word) => {
|
|
98
|
+
const match = new RegExp(`(\\d+) ${word}`).exec(line);
|
|
99
|
+
if (!match) return 0;
|
|
100
|
+
return parseInt(match[1], 10);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const parseShortstat = (line) => ({
|
|
104
|
+
added: countIn(line, 'insertion'),
|
|
105
|
+
removed: countIn(line, 'deletion'),
|
|
106
|
+
});
|
|
107
|
+
|
|
84
108
|
const listCommits = (top) => {
|
|
85
109
|
const args = [
|
|
86
110
|
'log',
|
|
87
111
|
'-z',
|
|
112
|
+
'--shortstat',
|
|
88
113
|
`--format=${COMMIT_FORMAT}`,
|
|
89
114
|
`--max-count=${COMMIT_LIMIT}`,
|
|
90
115
|
];
|
|
91
116
|
const result = requireOk(runGit(args, top), 'log failed');
|
|
92
117
|
const commits = [];
|
|
93
|
-
for (const
|
|
94
|
-
|
|
95
|
-
|
|
118
|
+
for (const chunk of result.stdout.split('\0')) {
|
|
119
|
+
const taken = takeStat(chunk);
|
|
120
|
+
if (taken.stat && commits.length) {
|
|
121
|
+
const stat = parseShortstat(taken.stat);
|
|
122
|
+
const last = commits[commits.length - 1];
|
|
123
|
+
last.added = stat.added;
|
|
124
|
+
last.removed = stat.removed;
|
|
125
|
+
}
|
|
126
|
+
if (!taken.rest) continue;
|
|
127
|
+
const entry = parseCommitRecord(taken.rest, commits.length);
|
|
96
128
|
if (entry) commits.push(entry);
|
|
97
129
|
}
|
|
98
130
|
return commits;
|
|
@@ -113,48 +145,40 @@ const dropHeadCommit = (top) => {
|
|
|
113
145
|
requireOk(runGit(['reset', '--soft', 'HEAD~1'], top), 'drop failed');
|
|
114
146
|
};
|
|
115
147
|
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
const dropOlderCommit = (top, sha) => {
|
|
122
|
-
const onto = `${sha}^`;
|
|
123
|
-
const result = runGit(['rebase', '--onto', onto, sha], top);
|
|
124
|
-
const failed = rebaseFailed(result);
|
|
125
|
-
if (!failed) return;
|
|
148
|
+
const runRebase = (top, args, extra = {}) => {
|
|
149
|
+
const result = runGit(args, top, undefined, extra);
|
|
150
|
+
if (result.status === 0) return;
|
|
126
151
|
runGit(['rebase', '--abort'], top);
|
|
127
|
-
|
|
152
|
+
requireOk(result, 'rebase failed');
|
|
128
153
|
};
|
|
129
154
|
|
|
130
|
-
const
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
const failed = rebaseFailed(result);
|
|
134
|
-
if (!failed) return;
|
|
155
|
+
const runRebaseAsync = async (top, args, extra = {}) => {
|
|
156
|
+
const result = await runGitAsync(args, top, extra);
|
|
157
|
+
if (result.status === 0) return;
|
|
135
158
|
await runGitAsync(['rebase', '--abort'], top);
|
|
136
|
-
|
|
159
|
+
requireOk(result, 'rebase failed');
|
|
137
160
|
};
|
|
138
161
|
|
|
139
|
-
const
|
|
140
|
-
const
|
|
141
|
-
const target = resolveCommit(top, spec);
|
|
162
|
+
const dropTarget = (top, sha) => {
|
|
163
|
+
const target = resolveCommit(top, namedCommit(sha));
|
|
142
164
|
const parent = runGit(['rev-parse', '--verify', `${target}^`], top);
|
|
143
165
|
if (parent.status !== 0) throw new Error('cannot drop root commit');
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
166
|
+
return target;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const dropCommit = (top, sha) => {
|
|
170
|
+
const target = dropTarget(top, sha);
|
|
171
|
+
if (target === resolveCommit(top, 'HEAD')) return void dropHeadCommit(top);
|
|
172
|
+
runRebase(top, ['rebase', '--onto', `${target}^`, target]);
|
|
147
173
|
};
|
|
148
174
|
|
|
149
175
|
const dropCommitAsync = async (top, sha) => {
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (target === head) return void dropHeadCommit(top);
|
|
157
|
-
await dropOlderCommitAsync(top, target);
|
|
176
|
+
const target = dropTarget(top, sha);
|
|
177
|
+
if (target === resolveCommit(top, 'HEAD')) {
|
|
178
|
+
requireOk(await runGitAsync(['reset', '--soft', 'HEAD~1'], top));
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
await runRebaseAsync(top, ['rebase', '--onto', `${target}^`, target]);
|
|
158
182
|
};
|
|
159
183
|
|
|
160
184
|
const FIXUP_MARK = 'fixup!';
|
|
@@ -197,61 +221,33 @@ const ancestorWithSubject = (top, sha, want) => {
|
|
|
197
221
|
return '';
|
|
198
222
|
};
|
|
199
223
|
|
|
200
|
-
const
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
if (!failed) return;
|
|
204
|
-
runGit(['rebase', '--abort'], top);
|
|
205
|
-
throw new Error(oneLine(failed));
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
const applyAutosquashAsync = async (top, ontoArgs) => {
|
|
209
|
-
const result = await runGitAsync([...AUTOSQUASH_ARGS, ...ontoArgs], top);
|
|
210
|
-
const failed = rebaseFailed(result);
|
|
211
|
-
if (!failed) return;
|
|
212
|
-
await runGitAsync(['rebase', '--abort'], top);
|
|
213
|
-
throw new Error(oneLine(failed));
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
const applyFixupOnto = (top, original, applyRange) => {
|
|
217
|
-
const parent = runGit(['rev-parse', '--verify', `${original}^`], top);
|
|
218
|
-
if (parent.status !== 0) return applyRange(top, ['--root']);
|
|
219
|
-
return applyRange(top, [(parent.stdout ?? '').trim()]);
|
|
224
|
+
const rebaseRange = (top, sha) => {
|
|
225
|
+
const parent = runGit(['rev-parse', '--verify', `${sha}^`], top);
|
|
226
|
+
return parent.status === 0 ? [parent.stdout.trim()] : ['--root'];
|
|
220
227
|
};
|
|
221
228
|
|
|
222
|
-
const
|
|
223
|
-
const
|
|
224
|
-
const commit = resolveCommit(top, spec);
|
|
229
|
+
const fixupArgs = (top, sha) => {
|
|
230
|
+
const commit = resolveCommit(top, namedCommit(sha));
|
|
225
231
|
const subject = commitSubject(top, commit);
|
|
226
232
|
if (!isFixupSubject(subject)) throw new Error('not a fixup commit');
|
|
227
233
|
const want = fixupRest(subject);
|
|
228
234
|
if (!want) throw new Error('no fixup target');
|
|
229
235
|
const original = ancestorWithSubject(top, commit, want);
|
|
230
236
|
if (!original) throw new Error('no fixup target');
|
|
231
|
-
|
|
237
|
+
return [...AUTOSQUASH_ARGS, ...rebaseRange(top, original)];
|
|
232
238
|
};
|
|
233
239
|
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
if (!isFixupSubject(subject)) throw new Error('not a fixup commit');
|
|
239
|
-
const want = fixupRest(subject);
|
|
240
|
-
if (!want) throw new Error('no fixup target');
|
|
241
|
-
const original = ancestorWithSubject(top, commit, want);
|
|
242
|
-
if (!original) throw new Error('no fixup target');
|
|
243
|
-
await applyFixupOnto(top, original, applyAutosquashAsync);
|
|
244
|
-
};
|
|
240
|
+
const applyFixup = (top, sha) => runRebase(top, fixupArgs(top, sha));
|
|
241
|
+
|
|
242
|
+
const applyFixupAsync = async (top, sha) =>
|
|
243
|
+
runRebaseAsync(top, fixupArgs(top, sha));
|
|
245
244
|
|
|
246
245
|
const messageBody = (message) => {
|
|
247
246
|
const text = `${message ?? ''}`;
|
|
248
247
|
return text.endsWith('\n') ? text : `${text}\n`;
|
|
249
248
|
};
|
|
250
249
|
|
|
251
|
-
const
|
|
252
|
-
const args = ['commit', '--amend', '--only', '-F', '-'];
|
|
253
|
-
requireOk(runGit(args, top, messageBody(message)));
|
|
254
|
-
};
|
|
250
|
+
const REWORD_HEAD_ARGS = ['commit', '--amend', '--only', '-F', '-'];
|
|
255
251
|
|
|
256
252
|
const REWORD_TODO = path.join(__dirname, 'git-reword-todo.js');
|
|
257
253
|
|
|
@@ -270,32 +266,11 @@ const prepareRewordEditor = (message) => {
|
|
|
270
266
|
};
|
|
271
267
|
};
|
|
272
268
|
|
|
273
|
-
const rewordRange = (top, sha, env) => {
|
|
274
|
-
const parent = runGit(['rev-parse', '--verify', `${sha}^`], top);
|
|
275
|
-
const onto =
|
|
276
|
-
parent.status !== 0 ? ['--root'] : [(parent.stdout ?? '').trim()];
|
|
277
|
-
return runGit(['rebase', '--interactive', ...onto], top, undefined, { env });
|
|
278
|
-
};
|
|
279
|
-
|
|
280
|
-
const rewordRangeAsync = async (top, sha, env) => {
|
|
281
|
-
const parent = runGit(['rev-parse', '--verify', `${sha}^`], top);
|
|
282
|
-
const onto =
|
|
283
|
-
parent.status !== 0 ? ['--root'] : [(parent.stdout ?? '').trim()];
|
|
284
|
-
return runGitAsync(['rebase', '--interactive', ...onto], top, { env });
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
const finishRewordRebase = (top, result) => {
|
|
288
|
-
const failed = rebaseFailed(result);
|
|
289
|
-
if (!failed) return;
|
|
290
|
-
runGit(['rebase', '--abort'], top);
|
|
291
|
-
throw new Error(oneLine(failed));
|
|
292
|
-
};
|
|
293
|
-
|
|
294
269
|
const rewordOlder = (top, sha, message) => {
|
|
295
270
|
const prepared = prepareRewordEditor(message);
|
|
296
271
|
try {
|
|
297
|
-
const
|
|
298
|
-
|
|
272
|
+
const args = ['rebase', '--interactive', ...rebaseRange(top, sha)];
|
|
273
|
+
runRebase(top, args, { env: prepared.env });
|
|
299
274
|
} finally {
|
|
300
275
|
fs.rmSync(prepared.dir, { recursive: true, force: true });
|
|
301
276
|
}
|
|
@@ -304,29 +279,29 @@ const rewordOlder = (top, sha, message) => {
|
|
|
304
279
|
const rewordOlderAsync = async (top, sha, message) => {
|
|
305
280
|
const prepared = prepareRewordEditor(message);
|
|
306
281
|
try {
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
if (!failed) return;
|
|
310
|
-
await runGitAsync(['rebase', '--abort'], top);
|
|
311
|
-
throw new Error(oneLine(failed));
|
|
282
|
+
const args = ['rebase', '--interactive', ...rebaseRange(top, sha)];
|
|
283
|
+
await runRebaseAsync(top, args, { env: prepared.env });
|
|
312
284
|
} finally {
|
|
313
285
|
fs.rmSync(prepared.dir, { recursive: true, force: true });
|
|
314
286
|
}
|
|
315
287
|
};
|
|
316
288
|
|
|
317
289
|
const rewordCommit = (top, sha, message) => {
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
290
|
+
const commit = resolveCommit(top, namedCommit(sha));
|
|
291
|
+
if (commit === resolveCommit(top, 'HEAD')) {
|
|
292
|
+
requireOk(runGit(REWORD_HEAD_ARGS, top, messageBody(message)));
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
322
295
|
rewordOlder(top, commit, message);
|
|
323
296
|
};
|
|
324
297
|
|
|
325
298
|
const rewordCommitAsync = async (top, sha, message) => {
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
299
|
+
const commit = resolveCommit(top, namedCommit(sha));
|
|
300
|
+
if (commit === resolveCommit(top, 'HEAD')) {
|
|
301
|
+
const input = messageBody(message);
|
|
302
|
+
requireOk(await runGitAsync(REWORD_HEAD_ARGS, top, { input }));
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
330
305
|
await rewordOlderAsync(top, commit, message);
|
|
331
306
|
};
|
|
332
307
|
|
|
@@ -348,7 +323,7 @@ const parseBranchLine = (line) => {
|
|
|
348
323
|
if (!name) return null;
|
|
349
324
|
const mark = parts[0] ?? '';
|
|
350
325
|
const sha = (parts[2] ?? '').trim();
|
|
351
|
-
const date = (parts[3] ?? '')
|
|
326
|
+
const date = shortAge(parts[3] ?? '');
|
|
352
327
|
const track = parseTrack(parts[4]);
|
|
353
328
|
const subject = parts.slice(5).join('\t').trim();
|
|
354
329
|
return {
|
|
@@ -393,13 +368,10 @@ const listBranches = (top) => {
|
|
|
393
368
|
const names = new Set();
|
|
394
369
|
for (const entry of branches) names.add(entry.name);
|
|
395
370
|
const primary = defaultBranch(top, names);
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
listed[i] = { ...entry, isDefault: entry.name === primary };
|
|
401
|
-
}
|
|
402
|
-
return listed;
|
|
371
|
+
return branches.map((entry) => ({
|
|
372
|
+
...entry,
|
|
373
|
+
isDefault: entry.name === primary,
|
|
374
|
+
}));
|
|
403
375
|
};
|
|
404
376
|
|
|
405
377
|
const namedBranch = (name) => {
|
|
@@ -430,23 +402,11 @@ const createBranchAsync = async (top, name) => {
|
|
|
430
402
|
requireOk(await runGitAsync(args, top), 'checkout failed');
|
|
431
403
|
};
|
|
432
404
|
|
|
433
|
-
const rebaseBranch = (top, onto) =>
|
|
434
|
-
|
|
435
|
-
const result = runGit(['rebase', branch], top);
|
|
436
|
-
const failed = rebaseFailed(result);
|
|
437
|
-
if (!failed) return;
|
|
438
|
-
runGit(['rebase', '--abort'], top);
|
|
439
|
-
throw new Error(oneLine(failed));
|
|
440
|
-
};
|
|
405
|
+
const rebaseBranch = (top, onto) =>
|
|
406
|
+
runRebase(top, ['rebase', namedBranch(onto)]);
|
|
441
407
|
|
|
442
|
-
const rebaseBranchAsync = async (top, onto) =>
|
|
443
|
-
|
|
444
|
-
const result = await runGitAsync(['rebase', branch], top);
|
|
445
|
-
const failed = rebaseFailed(result);
|
|
446
|
-
if (!failed) return;
|
|
447
|
-
await runGitAsync(['rebase', '--abort'], top);
|
|
448
|
-
throw new Error(oneLine(failed));
|
|
449
|
-
};
|
|
408
|
+
const rebaseBranchAsync = async (top, onto) =>
|
|
409
|
+
runRebaseAsync(top, ['rebase', namedBranch(onto)]);
|
|
450
410
|
|
|
451
411
|
const dropBranch = (top, name) => {
|
|
452
412
|
const branch = namedBranch(name);
|
|
@@ -510,36 +470,31 @@ const pushCmd = (force, branch) => {
|
|
|
510
470
|
const noUpstream = (msg) =>
|
|
511
471
|
`${msg ?? ''}`.toLowerCase().includes('no upstream');
|
|
512
472
|
|
|
513
|
-
const
|
|
473
|
+
const requirePushOk = (result) => {
|
|
514
474
|
if (result.status === 0) return;
|
|
515
|
-
|
|
516
|
-
if (!noUpstream(msg)) throw pushError(msg);
|
|
517
|
-
const next = run(pushCmd(force, branch));
|
|
518
|
-
if (next.status !== 0) {
|
|
519
|
-
throw pushError(next.stderr || next.stdout || 'push failed');
|
|
520
|
-
}
|
|
475
|
+
throw pushError(result.stderr || result.stdout || 'push failed');
|
|
521
476
|
};
|
|
522
477
|
|
|
523
478
|
const pushChanges = (top, force) => {
|
|
524
479
|
const useForce = force === true;
|
|
525
480
|
const branch = pushBranch(top);
|
|
526
481
|
const tracked = hasUpstream(top) ? '' : branch;
|
|
527
|
-
|
|
528
|
-
|
|
482
|
+
let result = runGit(pushCmd(useForce, tracked), top);
|
|
483
|
+
if (result.status !== 0 && noUpstream(result.stderr || result.stdout)) {
|
|
484
|
+
result = runGit(pushCmd(useForce, branch), top);
|
|
485
|
+
}
|
|
486
|
+
requirePushOk(result);
|
|
529
487
|
};
|
|
530
488
|
|
|
531
489
|
const pushChangesAsync = async (top, force) => {
|
|
532
490
|
const useForce = force === true;
|
|
533
491
|
const branch = pushBranch(top);
|
|
534
492
|
const tracked = hasUpstream(top) ? '' : branch;
|
|
535
|
-
|
|
536
|
-
if (result.status
|
|
537
|
-
|
|
538
|
-
if (!noUpstream(msg)) throw pushError(msg);
|
|
539
|
-
const next = await runGitAsync(pushCmd(useForce, branch), top);
|
|
540
|
-
if (next.status !== 0) {
|
|
541
|
-
throw pushError(next.stderr || next.stdout || 'push failed');
|
|
493
|
+
let result = await runGitAsync(pushCmd(useForce, tracked), top);
|
|
494
|
+
if (result.status !== 0 && noUpstream(result.stderr || result.stdout)) {
|
|
495
|
+
result = await runGitAsync(pushCmd(useForce, branch), top);
|
|
542
496
|
}
|
|
497
|
+
requirePushOk(result);
|
|
543
498
|
};
|
|
544
499
|
|
|
545
500
|
const hasStaged = (top) => {
|
|
@@ -551,9 +506,7 @@ const commitPlan = (kind, message) => {
|
|
|
551
506
|
const args = ['commit'];
|
|
552
507
|
if (kind === 'amend') args.push('--amend');
|
|
553
508
|
args.push('-F', '-');
|
|
554
|
-
|
|
555
|
-
const body = text.endsWith('\n') ? text : `${text}\n`;
|
|
556
|
-
return { args, body };
|
|
509
|
+
return { args, body: messageBody(message) };
|
|
557
510
|
};
|
|
558
511
|
|
|
559
512
|
const commitChanges = (top, kind, message) => {
|