reslop 0.1.8 → 0.2.0
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 +25 -1
- package/README.md +27 -38
- package/bin/reslop.js +2 -2
- package/lib/ansi.js +6 -2
- package/lib/api.js +2 -2
- package/lib/capabilities.js +10 -14
- package/lib/cli.js +28 -47
- package/lib/clipboard.js +1 -1
- package/lib/deps-diff.js +712 -0
- package/lib/deps.js +556 -32
- package/lib/detect.js +1 -7
- package/lib/diff/align.js +7 -10
- package/lib/diff/diff.js +53 -0
- package/lib/diff/display.js +5 -4
- package/lib/diff/inline.js +26 -21
- package/lib/diff/items.js +6 -4
- package/lib/diff/patch.js +1 -3
- package/lib/diff/unit.js +392 -0
- package/lib/files.js +34 -8
- package/lib/{git/branches.js → git-branches.js} +103 -10
- package/lib/{git/dependencies.js → git-deps.js} +111 -38
- package/lib/git.js +592 -65
- package/lib/github.js +28 -197
- package/lib/gitlab.js +24 -195
- package/lib/highlight/bash.js +1 -3
- package/lib/highlight/css.js +1 -3
- package/lib/highlight/js.js +11 -14
- package/lib/highlight.js +6 -9
- package/lib/keys.js +22 -9
- package/lib/manifest.js +330 -0
- package/lib/remote.js +374 -0
- package/lib/render/branches.js +34 -66
- package/lib/render/chrome.js +57 -22
- package/lib/render/commits.js +85 -0
- package/lib/render/diff.js +16 -16
- package/lib/render/files.js +29 -37
- package/lib/render/frame.js +29 -13
- package/lib/render/notes.js +4 -3
- package/lib/render/primitives.js +46 -1
- package/lib/{render.js → render/render.js} +6 -7
- package/lib/render/unit.js +41 -0
- package/lib/review.js +750 -14
- package/lib/select.js +5 -10
- package/lib/session/accessors.js +9 -1
- package/lib/session/actions.js +93 -36
- package/lib/session/branches.js +130 -144
- package/lib/session/change-actions.js +206 -124
- package/lib/session/commit.js +217 -62
- package/lib/session/compose.js +158 -13
- package/lib/session/dispatch.js +109 -55
- package/lib/session/files-pane.js +145 -142
- package/lib/session/items.js +131 -34
- package/lib/session/lifecycle.js +82 -42
- package/lib/session/load.js +19 -33
- package/lib/session/navigation.js +15 -3
- package/lib/session/ops.js +28 -41
- package/lib/session/pointer.js +58 -54
- package/lib/session/review.js +23 -37
- package/lib/session/terminal.js +6 -12
- package/lib/session/todos.js +8 -12
- package/lib/session/unit.js +208 -0
- package/lib/session/update.js +33 -46
- package/lib/session/view.js +82 -11
- package/lib/session/watch.js +11 -19
- package/lib/session.js +29 -226
- package/lib/source.js +3 -3
- package/lib/update.js +40 -75
- package/lib/{git/proc.js → utilities.js} +75 -11
- package/lib/wrap.js +1 -3
- package/package.json +2 -2
- package/lib/deps/changes.js +0 -331
- package/lib/deps/items.js +0 -131
- package/lib/deps/manifest.js +0 -145
- package/lib/deps/merge.js +0 -120
- package/lib/deps/proposals.js +0 -164
- package/lib/deps/reports.js +0 -180
- package/lib/deps/rows.js +0 -177
- package/lib/deps/scan.js +0 -138
- package/lib/deps/usage.js +0 -68
- package/lib/deps/versions.js +0 -119
- package/lib/diff.js +0 -33
- package/lib/git/changes.js +0 -164
- package/lib/git/read.js +0 -253
- package/lib/git/run.js +0 -100
- package/lib/npm/commands.js +0 -100
- package/lib/remote/changes.js +0 -37
- package/lib/remote/headers.js +0 -36
- package/lib/remote/http.js +0 -122
- package/lib/remote/notes.js +0 -94
- package/lib/review/format.js +0 -434
- package/lib/review/storage.js +0 -150
- package/lib/review/store.js +0 -178
- package/lib/review/templates.js +0 -49
- package/lib/session/origins.js +0 -7
- package/lib/sys.js +0 -84
package/lib/git.js
CHANGED
|
@@ -1,57 +1,576 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const proc = require('./utilities.js');
|
|
7
|
+
const { runProc, runProcSync, isAbort } = proc;
|
|
3
8
|
const { capabilitiesFor, attachCapabilities } = require('./capabilities.js');
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
9
|
+
const diff = require('./diff/diff.js');
|
|
10
|
+
const { parseDiff, synthesizeNewFile, itemsFromFiles } = diff;
|
|
11
|
+
const { itemPath, REVIEW_DIR } = require('./files.js');
|
|
12
|
+
const { replaceBlockAdds } = require('./diff/edit.js');
|
|
13
|
+
|
|
14
|
+
const oneLine = (text, fallback = 'git failed') => proc.oneLine(text, fallback);
|
|
15
|
+
|
|
16
|
+
const GIT_CONFIG = [
|
|
17
|
+
'-c',
|
|
18
|
+
'core.quotepath=false',
|
|
19
|
+
'-c',
|
|
20
|
+
'i18n.logOutputEncoding=utf-8',
|
|
21
|
+
];
|
|
22
|
+
const DIFF_OPTS = ['--no-color', '--no-ext-diff', '--no-renames', '-U3'];
|
|
23
|
+
const GIT_ENCODING = proc.DEFAULT_ENCODING;
|
|
24
|
+
const GIT_MAX_BUFFER = proc.DEFAULT_MAX_BUFFER;
|
|
25
|
+
|
|
26
|
+
const gitProcOptions = (cwd, extra = {}) => ({
|
|
27
|
+
cwd,
|
|
28
|
+
env: { ...process.env, GIT_OPTIONAL_LOCKS: '0' },
|
|
29
|
+
encoding: GIT_ENCODING,
|
|
30
|
+
maxBuffer: GIT_MAX_BUFFER,
|
|
31
|
+
input: extra.input,
|
|
32
|
+
signal: extra.signal,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const gitResult = (result) => {
|
|
36
|
+
if (!result.error) return result;
|
|
37
|
+
if (isAbort(result.error)) throw result.error;
|
|
38
|
+
if (result.error.code === 'ENOENT') throw new Error('git not found');
|
|
39
|
+
throw result.error;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const requireOk = (result, fallback = 'git failed') => {
|
|
43
|
+
if (result.status === 0) return result;
|
|
44
|
+
const msg = result.stderr || result.stdout || fallback;
|
|
45
|
+
throw new Error(oneLine(msg, fallback));
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const runGit = (args, cwd, input) => {
|
|
49
|
+
const options = gitProcOptions(cwd, { input });
|
|
50
|
+
return gitResult(runProcSync('git', args, options));
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const runGitAsync = (args, cwd, extra = {}) => {
|
|
54
|
+
const options = gitProcOptions(cwd, extra);
|
|
55
|
+
return runProc('git', args, options).then(gitResult);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const pathArgsOf = (paths) => (paths.length ? ['--', ...paths] : []);
|
|
59
|
+
|
|
60
|
+
const cachedDiffArgs = (pathArgs) => [
|
|
61
|
+
...GIT_CONFIG,
|
|
62
|
+
'diff',
|
|
63
|
+
'--cached',
|
|
64
|
+
...DIFF_OPTS,
|
|
65
|
+
...pathArgs,
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
const worktreeDiffArgs = (pathArgs) => [
|
|
69
|
+
...GIT_CONFIG,
|
|
70
|
+
'diff',
|
|
71
|
+
...DIFF_OPTS,
|
|
72
|
+
...pathArgs,
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
const commitDiffArgs = (commit, parent, pathArgs) => {
|
|
76
|
+
if (parent.status === 0) {
|
|
77
|
+
const from = parent.stdout.trim();
|
|
78
|
+
return [...GIT_CONFIG, 'diff', ...DIFF_OPTS, from, commit, ...pathArgs];
|
|
79
|
+
}
|
|
80
|
+
return [
|
|
81
|
+
...GIT_CONFIG,
|
|
82
|
+
'diff-tree',
|
|
83
|
+
'--no-commit-id',
|
|
84
|
+
'--root',
|
|
85
|
+
'-p',
|
|
86
|
+
...DIFF_OPTS,
|
|
87
|
+
commit,
|
|
88
|
+
...pathArgs,
|
|
89
|
+
];
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const BINARY_SIZE = 1000000;
|
|
93
|
+
|
|
94
|
+
const isReviewFile = (rel) => {
|
|
95
|
+
if (!rel) return false;
|
|
96
|
+
const first = rel.split(/[/\\]/)[0];
|
|
97
|
+
return first === REVIEW_DIR;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const dropReviewItems = (items) => {
|
|
101
|
+
const kept = [];
|
|
102
|
+
for (const item of items) {
|
|
103
|
+
if (isReviewFile(itemPath(item))) continue;
|
|
104
|
+
kept.push(item);
|
|
105
|
+
}
|
|
106
|
+
return kept;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const isBinaryBuffer = (buf) => {
|
|
110
|
+
if (buf.length > BINARY_SIZE) return true;
|
|
111
|
+
return buf.includes(0);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const namesFromLs = (stdout) => {
|
|
115
|
+
if (!stdout) return [];
|
|
116
|
+
return stdout.split('\0').filter(Boolean);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const untrackedLsArgs = (paths) => {
|
|
120
|
+
const args = ['ls-files', '-o', '--exclude-standard', '-z'];
|
|
121
|
+
if (paths.length) args.push('--', ...paths);
|
|
122
|
+
return args;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const listUntracked = (top, paths) => {
|
|
126
|
+
const result = requireOk(runGit(untrackedLsArgs(paths), top));
|
|
127
|
+
return namesFromLs(result.stdout);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const filterListed = (names) => {
|
|
131
|
+
const out = [];
|
|
132
|
+
for (const rel of names) {
|
|
133
|
+
if (!rel || isReviewFile(rel)) continue;
|
|
134
|
+
out.push(rel);
|
|
135
|
+
}
|
|
136
|
+
return out;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const listTreeArgs = (rev, paths) => {
|
|
140
|
+
const args = ['ls-tree', '-r', '--name-only', '-z', rev];
|
|
141
|
+
if (paths.length) args.push('--', ...paths);
|
|
142
|
+
return args;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const listWorktreeArgs = (paths) => {
|
|
146
|
+
const args = ['ls-files', '-co', '--exclude-standard', '-z'];
|
|
147
|
+
if (paths.length) args.push('--', ...paths);
|
|
148
|
+
return args;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const listScopeFiles = (top, paths = [], options = {}) => {
|
|
152
|
+
const rev = options.commit;
|
|
153
|
+
const args = rev ? listTreeArgs(rev, paths) : listWorktreeArgs(paths);
|
|
154
|
+
const result = requireOk(runGit(args, top), 'ls-files failed');
|
|
155
|
+
const names = filterListed(namesFromLs(result.stdout));
|
|
156
|
+
names.sort((left, right) => left.localeCompare(right, 'en'));
|
|
157
|
+
return names;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const binaryNewFile = (rel) => ({
|
|
161
|
+
oldPath: rel,
|
|
162
|
+
newPath: rel,
|
|
163
|
+
isNew: true,
|
|
164
|
+
isDeleted: false,
|
|
165
|
+
isBinary: true,
|
|
166
|
+
preamble: [`diff --git a/${rel} b/${rel}`, 'new file mode 100644'],
|
|
167
|
+
hunks: [],
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const untrackedItemsFrom = (top, names) => {
|
|
171
|
+
const files = [];
|
|
172
|
+
for (const rel of names) {
|
|
173
|
+
if (isReviewFile(rel)) continue;
|
|
174
|
+
const full = path.join(top, rel);
|
|
175
|
+
let buf;
|
|
176
|
+
try {
|
|
177
|
+
buf = fs.readFileSync(full);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
if (error.code === 'ENOENT') continue;
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
if (isBinaryBuffer(buf)) {
|
|
183
|
+
files.push(binaryNewFile(rel));
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
files.push(synthesizeNewFile(rel, buf.toString('utf8')));
|
|
187
|
+
}
|
|
188
|
+
return itemsFromFiles(files, 'untracked');
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const mergeItems = (groups) => {
|
|
192
|
+
const byPath = new Map();
|
|
193
|
+
for (const group of groups) {
|
|
194
|
+
for (const item of group) {
|
|
195
|
+
const rel = itemPath(item);
|
|
196
|
+
const list = byPath.get(rel);
|
|
197
|
+
if (list) {
|
|
198
|
+
list.push(item);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
byPath.set(rel, [item]);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
let size = 0;
|
|
205
|
+
for (const list of byPath.values()) size += list.length;
|
|
206
|
+
const items = new Array(size);
|
|
207
|
+
let i = 0;
|
|
208
|
+
for (const list of byPath.values()) {
|
|
209
|
+
for (const item of list) items[i++] = item;
|
|
210
|
+
}
|
|
211
|
+
return items;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const gitText = (top, spec) => {
|
|
215
|
+
const result = runGit(['show', spec], top);
|
|
216
|
+
if (result.status !== 0) return '';
|
|
217
|
+
return result.stdout ?? '';
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const worktreeText = (top, rel) => {
|
|
221
|
+
try {
|
|
222
|
+
return fs.readFileSync(path.join(top, rel), 'utf8');
|
|
223
|
+
} catch {
|
|
224
|
+
return '';
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const fileText = (top, rel, rev) => {
|
|
229
|
+
if (!rel) return '';
|
|
230
|
+
if (rev) return gitText(top, `${rev}:${rel}`);
|
|
231
|
+
return worktreeText(top, rel);
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const readDepSides = (top, rev, origin, rel) => {
|
|
235
|
+
if (origin === 'untracked') {
|
|
236
|
+
const newText = worktreeText(top, rel);
|
|
237
|
+
return { oldText: '', newText };
|
|
238
|
+
}
|
|
239
|
+
if (origin === 'staged') {
|
|
240
|
+
const oldText = gitText(top, `HEAD:${rel}`);
|
|
241
|
+
const newText = gitText(top, `:${rel}`);
|
|
242
|
+
return { oldText, newText };
|
|
243
|
+
}
|
|
244
|
+
if (origin === 'unstaged') {
|
|
245
|
+
const indexed = gitText(top, `:${rel}`);
|
|
246
|
+
const oldText = indexed || gitText(top, `HEAD:${rel}`);
|
|
247
|
+
const newText = worktreeText(top, rel);
|
|
248
|
+
return { oldText, newText };
|
|
249
|
+
}
|
|
250
|
+
if (origin === 'commit' && rev) {
|
|
251
|
+
const oldText = gitText(top, `${rev}^:${rel}`);
|
|
252
|
+
const newText = gitText(top, `${rev}:${rel}`);
|
|
253
|
+
return { oldText, newText };
|
|
254
|
+
}
|
|
255
|
+
return { oldText: '', newText: '' };
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const toplevel = (cwd) => {
|
|
259
|
+
const result = runGit(['rev-parse', '--show-toplevel'], cwd);
|
|
260
|
+
if (result.status !== 0) throw new Error('not a git repository');
|
|
261
|
+
return result.stdout.trim();
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const resolveRev = (cwd, spec) => {
|
|
265
|
+
if (!spec) return null;
|
|
266
|
+
const top = toplevel(cwd);
|
|
267
|
+
const args = ['rev-parse', '--verify', `${spec}^{commit}`];
|
|
268
|
+
const result = runGit(args, top);
|
|
269
|
+
if (result.status !== 0) return null;
|
|
270
|
+
return result.stdout.trim();
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const shortRev = (top, commit) => {
|
|
274
|
+
const result = runGit(['rev-parse', '--short', commit], top);
|
|
275
|
+
if (result.status !== 0) return commit.slice(0, 7);
|
|
276
|
+
return result.stdout.trim();
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const currentBranch = (top) => {
|
|
280
|
+
const args = ['rev-parse', '--abbrev-ref', 'HEAD'];
|
|
281
|
+
const result = runGit(args, top);
|
|
282
|
+
if (result.status !== 0) return '';
|
|
283
|
+
const name = result.stdout.trim();
|
|
284
|
+
if (!name || name === 'HEAD') return '';
|
|
285
|
+
return name;
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const snapshotWorktree = (top, stagedOut, unstagedOut, names) => {
|
|
289
|
+
const stagedDiff = parseDiff(stagedOut);
|
|
290
|
+
const unstagedDiff = parseDiff(unstagedOut);
|
|
291
|
+
const staged = itemsFromFiles(stagedDiff, 'staged');
|
|
292
|
+
const unstaged = itemsFromFiles(unstagedDiff, 'unstaged');
|
|
293
|
+
const untracked = untrackedItemsFrom(top, names);
|
|
294
|
+
const combined = mergeItems([staged, unstaged, untracked]);
|
|
295
|
+
const parsed = dropReviewItems(combined);
|
|
296
|
+
const branch = currentBranch(top);
|
|
297
|
+
return { top, parsed, branch };
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
const snapshotCommit = (top, commit, files) => {
|
|
301
|
+
const commitItems = itemsFromFiles(files, 'commit');
|
|
302
|
+
const parsed = dropReviewItems(commitItems);
|
|
303
|
+
const revShort = shortRev(top, commit);
|
|
304
|
+
const branch = currentBranch(top);
|
|
305
|
+
return { top, parsed, rev: commit, revShort, branch };
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const loadCommit = (top, commit, extra) => {
|
|
309
|
+
const parent = runGit(['rev-parse', '--verify', `${commit}^`], top);
|
|
310
|
+
const args = commitDiffArgs(commit, parent, extra);
|
|
311
|
+
const result = requireOk(runGit(args, top));
|
|
312
|
+
return parseDiff(result.stdout);
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const loadCommitAsync = async (top, commit, extra, signal) => {
|
|
316
|
+
const parent = await runGitAsync(
|
|
317
|
+
['rev-parse', '--verify', `${commit}^`],
|
|
318
|
+
top,
|
|
319
|
+
{ signal },
|
|
320
|
+
);
|
|
321
|
+
const args = commitDiffArgs(commit, parent, extra);
|
|
322
|
+
const result = requireOk(await runGitAsync(args, top, { signal }));
|
|
323
|
+
return parseDiff(result.stdout);
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
const readSnapshot = (cwd, paths, options) => {
|
|
327
|
+
const top = toplevel(cwd);
|
|
328
|
+
const extra = pathArgsOf(paths);
|
|
329
|
+
const rev = options.commit;
|
|
330
|
+
if (rev) return snapshotCommit(top, rev, loadCommit(top, rev, extra));
|
|
331
|
+
const stagedRes = requireOk(runGit(cachedDiffArgs(extra), top));
|
|
332
|
+
const unstagedRes = requireOk(runGit(worktreeDiffArgs(extra), top));
|
|
333
|
+
const names = listUntracked(top, paths);
|
|
334
|
+
return snapshotWorktree(top, stagedRes.stdout, unstagedRes.stdout, names);
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
const readSnapshotAsync = async (cwd, paths, options) => {
|
|
338
|
+
const top = toplevel(cwd);
|
|
339
|
+
const extra = pathArgsOf(paths);
|
|
340
|
+
const rev = options.commit;
|
|
341
|
+
const signal = options.signal;
|
|
342
|
+
if (rev) {
|
|
343
|
+
const files = await loadCommitAsync(top, rev, extra, signal);
|
|
344
|
+
return snapshotCommit(top, rev, files);
|
|
345
|
+
}
|
|
346
|
+
const stagedArgs = cachedDiffArgs(extra);
|
|
347
|
+
const unstagedArgs = worktreeDiffArgs(extra);
|
|
348
|
+
const lsArgs = untrackedLsArgs(paths);
|
|
349
|
+
const stagedP = runGitAsync(stagedArgs, top, { signal });
|
|
350
|
+
const unstagedP = runGitAsync(unstagedArgs, top, { signal });
|
|
351
|
+
const lsP = runGitAsync(lsArgs, top, { signal });
|
|
352
|
+
const extras = await Promise.all([stagedP, unstagedP, lsP]);
|
|
353
|
+
const stagedRes = extras[0];
|
|
354
|
+
const unstagedRes = extras[1];
|
|
355
|
+
const lsRes = extras[2];
|
|
356
|
+
requireOk(stagedRes);
|
|
357
|
+
requireOk(unstagedRes);
|
|
358
|
+
const names = namesFromLs(requireOk(lsRes).stdout);
|
|
359
|
+
return snapshotWorktree(top, stagedRes.stdout, unstagedRes.stdout, names);
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
const applyPatch = (cwd, patch, flags) => {
|
|
363
|
+
const args = ['apply', ...flags, '--whitespace=nowarn', '-'];
|
|
364
|
+
const result = runGit(args, cwd, patch);
|
|
365
|
+
requireOk(result, 'apply failed');
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
const addItem = (top, item) => {
|
|
369
|
+
if (item.origin === 'staged' || item.origin === 'commit') return;
|
|
370
|
+
if (item.origin === 'untracked' || item.file.isBinary) {
|
|
371
|
+
return void requireOk(runGit(['add', '--', item.file.newPath], top));
|
|
372
|
+
}
|
|
373
|
+
applyPatch(top, item.patchAdd, ['--cached']);
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
const unstageItem = (top, item) => {
|
|
377
|
+
if (item.origin !== 'staged') return;
|
|
378
|
+
const rel = itemPath(item);
|
|
379
|
+
if (item.file.isBinary) {
|
|
380
|
+
return void requireOk(runGit(['restore', '--staged', '--', rel], top));
|
|
381
|
+
}
|
|
382
|
+
applyPatch(top, item.patchAdd, ['--reverse', '--cached']);
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
const revertOnePath = (top, rel, items) => {
|
|
386
|
+
const origins = new Set();
|
|
387
|
+
let isNew = false;
|
|
388
|
+
for (const item of items) {
|
|
389
|
+
origins.add(item.origin);
|
|
390
|
+
if (item.file && item.file.isNew) isNew = true;
|
|
391
|
+
}
|
|
392
|
+
if (isNew) {
|
|
393
|
+
if (origins.has('staged')) {
|
|
394
|
+
return void requireOk(runGit(['rm', '-f', '--', rel], top));
|
|
395
|
+
}
|
|
396
|
+
return void fs.unlinkSync(path.join(top, rel));
|
|
397
|
+
}
|
|
398
|
+
if (origins.has('untracked') && origins.size === 1) {
|
|
399
|
+
return void fs.unlinkSync(path.join(top, rel));
|
|
400
|
+
}
|
|
401
|
+
const args = ['restore', '-s', 'HEAD', '--worktree', '--staged', '--', rel];
|
|
402
|
+
requireOk(runGit(args, top));
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
const revertItem = (top, item) => {
|
|
406
|
+
if (item.origin === 'commit') return;
|
|
407
|
+
const rel = itemPath(item);
|
|
408
|
+
if (item.origin === 'untracked') {
|
|
409
|
+
return void fs.unlinkSync(path.join(top, rel));
|
|
410
|
+
}
|
|
411
|
+
if (item.file.isBinary) {
|
|
412
|
+
const args = ['restore', '-s', 'HEAD', '--worktree'];
|
|
413
|
+
if (item.origin === 'staged') args.push('--staged');
|
|
414
|
+
args.push('--', rel);
|
|
415
|
+
return void requireOk(runGit(args, top));
|
|
416
|
+
}
|
|
417
|
+
if (item.origin === 'unstaged') {
|
|
418
|
+
return void applyPatch(top, item.patchRevert, ['--reverse']);
|
|
419
|
+
}
|
|
420
|
+
applyPatch(top, item.patchRevert, ['--reverse', '--cached']);
|
|
421
|
+
try {
|
|
422
|
+
applyPatch(top, item.patchRevert, ['--reverse']);
|
|
423
|
+
} catch (error) {
|
|
424
|
+
if (!error.message.includes('does not apply')) throw error;
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
const revertFile = (top, rel, items) => {
|
|
429
|
+
const group = [];
|
|
430
|
+
for (const item of items) {
|
|
431
|
+
if (itemPath(item) === rel) group.push(item);
|
|
432
|
+
}
|
|
433
|
+
const targets = group.length ? group : items;
|
|
434
|
+
revertOnePath(top, rel, targets);
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
const indexText = (top, rel) => {
|
|
438
|
+
const result = runGit(['show', `:${rel}`], top);
|
|
439
|
+
requireOk(result, 'show failed');
|
|
440
|
+
return result.stdout ?? '';
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
const indexMode = (top, rel) => {
|
|
444
|
+
const result = runGit(['ls-files', '--stage', '--', rel], top);
|
|
445
|
+
if (result.status !== 0) return '100644';
|
|
446
|
+
const line = (result.stdout ?? '').trim();
|
|
447
|
+
if (!line) return '100644';
|
|
448
|
+
const mode = line.split(' ')[0];
|
|
449
|
+
return mode || '100644';
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
const writeIndex = (top, rel, text) => {
|
|
453
|
+
const hashed = runGit(['hash-object', '-w', '--stdin'], top, text);
|
|
454
|
+
requireOk(hashed, 'hash-object failed');
|
|
455
|
+
const sha = (hashed.stdout ?? '').trim();
|
|
456
|
+
const mode = indexMode(top, rel);
|
|
457
|
+
const args = ['update-index', '--cacheinfo', mode, sha, rel];
|
|
458
|
+
requireOk(runGit(args, top), 'update-index failed');
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
const writeWorktree = (abs, text) => {
|
|
462
|
+
fs.writeFileSync(abs, text);
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
const editStaged = (top, rel, abs, hunk, blockId, text) => {
|
|
466
|
+
const source = indexText(top, rel);
|
|
467
|
+
const next = replaceBlockAdds(source, hunk, blockId, text);
|
|
468
|
+
writeIndex(top, rel, next);
|
|
469
|
+
let work = null;
|
|
470
|
+
try {
|
|
471
|
+
work = fs.readFileSync(abs, 'utf8');
|
|
472
|
+
} catch (error) {
|
|
473
|
+
if (error.code !== 'ENOENT') throw error;
|
|
474
|
+
}
|
|
475
|
+
if (work === null) {
|
|
476
|
+
return void writeWorktree(abs, next);
|
|
477
|
+
}
|
|
478
|
+
if (work === source) writeWorktree(abs, next);
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
const editWorktree = (abs, hunk, blockId, text) => {
|
|
482
|
+
const source = fs.readFileSync(abs, 'utf8');
|
|
483
|
+
const next = replaceBlockAdds(source, hunk, blockId, text);
|
|
484
|
+
if (next === source) return;
|
|
485
|
+
writeWorktree(abs, next);
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
const editItem = (top, item, text) => {
|
|
489
|
+
if (item.origin === 'commit' || item.origin === 'pr') return;
|
|
490
|
+
const rel = itemPath(item);
|
|
491
|
+
if (!rel || !item.hunk) return;
|
|
492
|
+
const abs = path.join(top, rel);
|
|
493
|
+
if (item.origin === 'staged') {
|
|
494
|
+
return void editStaged(top, rel, abs, item.hunk, item.blockId, text);
|
|
495
|
+
}
|
|
496
|
+
editWorktree(abs, item.hunk, item.blockId, text);
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
const writeFile = (top, rel, text) => {
|
|
500
|
+
if (!rel) return;
|
|
501
|
+
const abs = path.join(top, rel);
|
|
502
|
+
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
503
|
+
writeWorktree(abs, text);
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
module.exports = {
|
|
507
|
+
oneLine,
|
|
508
|
+
requireOk,
|
|
509
|
+
runGit,
|
|
510
|
+
runGitAsync,
|
|
511
|
+
gitText,
|
|
512
|
+
worktreeText,
|
|
513
|
+
readDepSides,
|
|
514
|
+
revertOnePath,
|
|
515
|
+
toplevel,
|
|
516
|
+
resolveRev,
|
|
517
|
+
currentBranch,
|
|
518
|
+
fileText,
|
|
519
|
+
listScopeFiles,
|
|
520
|
+
readSnapshot,
|
|
521
|
+
readSnapshotAsync,
|
|
522
|
+
editItem,
|
|
523
|
+
writeFile,
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
const branches = require('./git-branches.js');
|
|
527
|
+
const gitDeps = require('./git-deps.js');
|
|
8
528
|
|
|
9
529
|
const load = (cwd, paths = [], options = {}) => {
|
|
10
|
-
const snapshot =
|
|
11
|
-
return
|
|
530
|
+
const snapshot = readSnapshot(cwd, paths, options);
|
|
531
|
+
return gitDeps.finishLoad(snapshot, { ...options, paths });
|
|
12
532
|
};
|
|
13
533
|
|
|
14
534
|
const loadAsync = async (cwd, paths = [], options = {}) => {
|
|
15
|
-
const snapshot = await
|
|
16
|
-
return
|
|
535
|
+
const snapshot = await readSnapshotAsync(cwd, paths, options);
|
|
536
|
+
return gitDeps.finishLoad(snapshot, {
|
|
17
537
|
...options,
|
|
18
538
|
paths,
|
|
19
539
|
deferExtras: options.deferExtras ?? true,
|
|
20
540
|
});
|
|
21
541
|
};
|
|
22
542
|
|
|
23
|
-
const
|
|
24
|
-
if (
|
|
25
|
-
return
|
|
543
|
+
const addGitItem = (top, item) => {
|
|
544
|
+
if (gitDeps.handles(item)) return gitDeps.addItem(top, item);
|
|
545
|
+
return addItem(top, item);
|
|
26
546
|
};
|
|
27
547
|
|
|
28
|
-
const
|
|
29
|
-
if (
|
|
30
|
-
await
|
|
548
|
+
const addGitItemAsync = async (top, item) => {
|
|
549
|
+
if (gitDeps.handles(item)) {
|
|
550
|
+
await gitDeps.addItemAsync(top, item);
|
|
31
551
|
return;
|
|
32
552
|
}
|
|
33
|
-
|
|
553
|
+
addGitItem(top, item);
|
|
34
554
|
};
|
|
35
555
|
|
|
36
|
-
const
|
|
37
|
-
if (
|
|
38
|
-
return
|
|
556
|
+
const unstageGitItem = (top, item) => {
|
|
557
|
+
if (gitDeps.handles(item)) return gitDeps.unstageItem(top, item);
|
|
558
|
+
return unstageItem(top, item);
|
|
39
559
|
};
|
|
40
560
|
|
|
41
|
-
const
|
|
561
|
+
const revertGitItem = (top, item) => {
|
|
42
562
|
if (item.origin === 'commit') return;
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
return;
|
|
563
|
+
if (gitDeps.handles(item)) {
|
|
564
|
+
return void gitDeps.revertDepItem(top, item);
|
|
46
565
|
}
|
|
47
|
-
|
|
566
|
+
revertItem(top, item);
|
|
48
567
|
};
|
|
49
568
|
|
|
50
|
-
const
|
|
51
|
-
if (
|
|
52
|
-
return
|
|
569
|
+
const revertGitFile = (top, rel, items) => {
|
|
570
|
+
if (gitDeps.relatedFiles(items).length) {
|
|
571
|
+
return gitDeps.revertFile(top, rel, items);
|
|
53
572
|
}
|
|
54
|
-
return
|
|
573
|
+
return revertFile(top, rel, items);
|
|
55
574
|
};
|
|
56
575
|
|
|
57
576
|
const createGitRepo = () =>
|
|
@@ -59,21 +578,25 @@ const createGitRepo = () =>
|
|
|
59
578
|
{
|
|
60
579
|
load,
|
|
61
580
|
loadAsync,
|
|
62
|
-
loadExtras:
|
|
63
|
-
toplevel
|
|
64
|
-
resolveRev
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
581
|
+
loadExtras: gitDeps.loadExtras,
|
|
582
|
+
toplevel,
|
|
583
|
+
resolveRev,
|
|
584
|
+
listFiles: listScopeFiles,
|
|
585
|
+
fileText,
|
|
586
|
+
add: addGitItem,
|
|
587
|
+
addAsync: addGitItemAsync,
|
|
588
|
+
unstage: unstageGitItem,
|
|
589
|
+
revert: revertGitItem,
|
|
590
|
+
revertFile: revertGitFile,
|
|
591
|
+
edit: editItem,
|
|
592
|
+
writeFile,
|
|
71
593
|
commit: branches.commitChanges,
|
|
72
594
|
commitAsync: branches.commitChangesAsync,
|
|
73
595
|
hasStaged: branches.hasStaged,
|
|
74
596
|
lastMessage: branches.lastMessage,
|
|
75
597
|
listBranches: branches.listBranches,
|
|
76
|
-
|
|
598
|
+
listCommits: branches.listCommits,
|
|
599
|
+
currentBranch,
|
|
77
600
|
checkout: branches.checkoutBranch,
|
|
78
601
|
checkoutAsync: branches.checkoutBranchAsync,
|
|
79
602
|
createBranch: branches.createBranch,
|
|
@@ -82,6 +605,8 @@ const createGitRepo = () =>
|
|
|
82
605
|
rebaseAsync: branches.rebaseBranchAsync,
|
|
83
606
|
drop: branches.dropBranch,
|
|
84
607
|
dropAsync: branches.dropBranchAsync,
|
|
608
|
+
dropCommit: branches.dropCommit,
|
|
609
|
+
dropCommitAsync: branches.dropCommitAsync,
|
|
85
610
|
pull: branches.pullChanges,
|
|
86
611
|
pullAsync: branches.pullChangesAsync,
|
|
87
612
|
push: branches.pushChanges,
|
|
@@ -90,31 +615,33 @@ const createGitRepo = () =>
|
|
|
90
615
|
capabilitiesFor('local'),
|
|
91
616
|
);
|
|
92
617
|
|
|
93
|
-
module.exports =
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
618
|
+
module.exports.load = load;
|
|
619
|
+
module.exports.loadAsync = loadAsync;
|
|
620
|
+
module.exports.loadExtras = gitDeps.loadExtras;
|
|
621
|
+
module.exports.addItem = addGitItem;
|
|
622
|
+
module.exports.unstageItem = unstageGitItem;
|
|
623
|
+
module.exports.revertItem = revertGitItem;
|
|
624
|
+
module.exports.editItem = editItem;
|
|
625
|
+
module.exports.writeFile = writeFile;
|
|
626
|
+
module.exports.commitChanges = branches.commitChanges;
|
|
627
|
+
module.exports.commitChangesAsync = branches.commitChangesAsync;
|
|
628
|
+
module.exports.hasStaged = branches.hasStaged;
|
|
629
|
+
module.exports.currentBranch = currentBranch;
|
|
630
|
+
module.exports.lastMessage = branches.lastMessage;
|
|
631
|
+
module.exports.listBranches = branches.listBranches;
|
|
632
|
+
module.exports.listCommits = branches.listCommits;
|
|
633
|
+
module.exports.checkoutBranch = branches.checkoutBranch;
|
|
634
|
+
module.exports.checkoutBranchAsync = branches.checkoutBranchAsync;
|
|
635
|
+
module.exports.createBranch = branches.createBranch;
|
|
636
|
+
module.exports.createBranchAsync = branches.createBranchAsync;
|
|
637
|
+
module.exports.rebaseBranch = branches.rebaseBranch;
|
|
638
|
+
module.exports.rebaseBranchAsync = branches.rebaseBranchAsync;
|
|
639
|
+
module.exports.dropBranch = branches.dropBranch;
|
|
640
|
+
module.exports.dropBranchAsync = branches.dropBranchAsync;
|
|
641
|
+
module.exports.dropCommit = branches.dropCommit;
|
|
642
|
+
module.exports.dropCommitAsync = branches.dropCommitAsync;
|
|
643
|
+
module.exports.pullChanges = branches.pullChanges;
|
|
644
|
+
module.exports.pullChangesAsync = branches.pullChangesAsync;
|
|
645
|
+
module.exports.pushChanges = branches.pushChanges;
|
|
646
|
+
module.exports.pushChangesAsync = branches.pushChangesAsync;
|
|
647
|
+
module.exports.createGitRepo = createGitRepo;
|