reslop 0.2.5 → 0.2.6

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.
Files changed (105) hide show
  1. package/CHANGELOG.md +12 -1
  2. package/LICENSE +1 -1
  3. package/README.md +11 -11
  4. package/bin/reslop.js +3 -8
  5. package/lib/ansi.js +32 -19
  6. package/lib/capabilities.js +8 -8
  7. package/lib/cli.js +40 -74
  8. package/lib/clipboard.js +39 -47
  9. package/lib/dashboard/git.js +128 -0
  10. package/lib/dashboard/model.js +332 -0
  11. package/lib/dashboard/npm.js +193 -0
  12. package/lib/dashboard/tree.js +278 -0
  13. package/lib/deps-diff.js +197 -402
  14. package/lib/deps.js +113 -240
  15. package/lib/detect.js +8 -18
  16. package/lib/diff/align.js +41 -113
  17. package/lib/diff/diff.js +12 -43
  18. package/lib/diff/display.js +25 -36
  19. package/lib/diff/edit.js +6 -20
  20. package/lib/diff/inline.js +76 -144
  21. package/lib/diff/items.js +33 -47
  22. package/lib/diff/parse.js +28 -56
  23. package/lib/diff/patch.js +12 -33
  24. package/lib/diff/unit.js +79 -178
  25. package/lib/editor.js +52 -69
  26. package/lib/files.js +71 -89
  27. package/lib/git-branches.js +254 -416
  28. package/lib/git-deps.js +147 -272
  29. package/lib/git-reword-todo.js +5 -13
  30. package/lib/git-worktree.js +141 -212
  31. package/lib/git.js +21 -49
  32. package/lib/github.js +67 -181
  33. package/lib/gitlab.js +40 -90
  34. package/lib/highlight/bash.js +55 -177
  35. package/lib/highlight/core.js +155 -141
  36. package/lib/highlight/css.js +25 -58
  37. package/lib/highlight/csv.js +19 -37
  38. package/lib/highlight/dart.js +72 -213
  39. package/lib/highlight/dot.js +14 -14
  40. package/lib/highlight/html.js +42 -63
  41. package/lib/highlight/js.js +177 -506
  42. package/lib/highlight/json.js +17 -37
  43. package/lib/highlight/log.js +19 -25
  44. package/lib/highlight/txt.js +7 -10
  45. package/lib/highlight.js +17 -46
  46. package/lib/keys.js +49 -82
  47. package/lib/manifest.js +67 -126
  48. package/lib/npm-commands.js +120 -169
  49. package/lib/remote.js +140 -190
  50. package/lib/render/branches.js +63 -61
  51. package/lib/render/chrome.js +220 -333
  52. package/lib/render/commits.js +160 -361
  53. package/lib/render/dash-activity.js +212 -0
  54. package/lib/render/dash-blocks.js +245 -0
  55. package/lib/render/dash-table.js +290 -0
  56. package/lib/render/dashboard.js +62 -0
  57. package/lib/render/diff.js +123 -258
  58. package/lib/render/files.js +40 -85
  59. package/lib/render/frame.js +78 -117
  60. package/lib/render/notes.js +139 -210
  61. package/lib/render/npm.js +213 -177
  62. package/lib/render/present.js +4 -11
  63. package/lib/render/primitives.js +72 -174
  64. package/lib/render/render.js +5 -14
  65. package/lib/render/report.js +43 -0
  66. package/lib/render/tiles.js +260 -0
  67. package/lib/render/unit.js +15 -31
  68. package/lib/report-model.js +110 -33
  69. package/lib/report-parse.js +56 -37
  70. package/lib/report-render.js +102 -59
  71. package/lib/report-run.js +87 -77
  72. package/lib/report-test.js +221 -99
  73. package/lib/report-tools.js +96 -154
  74. package/lib/review-merge.js +33 -41
  75. package/lib/review.js +68 -129
  76. package/lib/runs.js +225 -0
  77. package/lib/select.js +53 -91
  78. package/lib/session/accessors.js +14 -21
  79. package/lib/session/actions.js +61 -43
  80. package/lib/session/branches.js +166 -222
  81. package/lib/session/change-actions.js +49 -119
  82. package/lib/session/commit.js +274 -355
  83. package/lib/session/compose.js +556 -714
  84. package/lib/session/dashboard.js +385 -0
  85. package/lib/session/dispatch.js +162 -170
  86. package/lib/session/files-pane.js +76 -44
  87. package/lib/session/items.js +6 -17
  88. package/lib/session/lifecycle.js +393 -466
  89. package/lib/session/load.js +1 -14
  90. package/lib/session/navigation.js +2 -2
  91. package/lib/session/npm.js +403 -420
  92. package/lib/session/ops.js +5 -13
  93. package/lib/session/pointer.js +16 -14
  94. package/lib/session/progress.js +39 -30
  95. package/lib/session/review.js +1 -3
  96. package/lib/session/tasks.js +276 -235
  97. package/lib/session/templates.js +51 -59
  98. package/lib/session/terminal.js +85 -132
  99. package/lib/session/view.js +47 -81
  100. package/lib/session/watch.js +196 -212
  101. package/lib/session.js +81 -161
  102. package/lib/update.js +44 -111
  103. package/lib/utilities.js +46 -88
  104. package/lib/wrap.js +29 -55
  105. package/package.json +1 -1
@@ -3,17 +3,15 @@
3
3
  const fs = require('node:fs');
4
4
  const path = require('node:path');
5
5
 
6
- const proc = require('./utilities.js');
7
- const { runProc, runProcSync, isAbort } = proc;
6
+ const utilities = require('./utilities.js');
7
+ const { runProc, runProcSync, isAbort, oneLine } = utilities;
8
8
  const diff = require('./diff/diff.js');
9
9
  const { parseDiff, synthesizeNewFile, itemsFromFiles } = diff;
10
10
  const files = require('./files.js');
11
- const { itemPath, REVIEW_DIR } = files;
11
+ const { itemPath, isReadOnlyOrigin, REVIEW_DIR } = files;
12
12
  const edit = require('./diff/edit.js');
13
13
  const { replaceBlockAdds } = edit;
14
14
 
15
- const oneLine = (text, fallback = 'git failed') => proc.oneLine(text, fallback);
16
-
17
15
  const GIT_CONFIG = [
18
16
  '-c',
19
17
  'core.quotepath=false',
@@ -21,14 +19,11 @@ const GIT_CONFIG = [
21
19
  'i18n.logOutputEncoding=utf-8',
22
20
  ];
23
21
  const DIFF_OPTS = ['--no-color', '--no-ext-diff', '--no-renames', '-U3'];
24
- const GIT_ENCODING = proc.DEFAULT_ENCODING;
25
- const GIT_MAX_BUFFER = proc.DEFAULT_MAX_BUFFER;
22
+ const BINARY_SIZE = 1000000;
26
23
 
27
- const gitProcOptions = (cwd, extra = {}) => ({
24
+ const gitOptions = (cwd, extra) => ({
28
25
  cwd,
29
26
  env: { ...process.env, GIT_OPTIONAL_LOCKS: '0', ...extra.env },
30
- encoding: GIT_ENCODING,
31
- maxBuffer: GIT_MAX_BUFFER,
32
27
  input: extra.input,
33
28
  signal: extra.signal,
34
29
  });
@@ -46,37 +41,61 @@ const requireOk = (result, fallback = 'git failed') => {
46
41
  throw new Error(oneLine(msg, fallback));
47
42
  };
48
43
 
49
- const runGit = (args, cwd, input, extra = {}) => {
50
- const options = gitProcOptions(cwd, { input, env: extra.env });
51
- return gitResult(runProcSync('git', args, options));
44
+ const runGit = (args, cwd, extra = {}) =>
45
+ gitResult(runProcSync('git', args, gitOptions(cwd, extra)));
46
+
47
+ const runGitAsync = (args, cwd, extra = {}) =>
48
+ runProc('git', args, gitOptions(cwd, extra)).then(gitResult);
49
+
50
+ const command = (args, extra = {}) => ({
51
+ args,
52
+ input: extra.input,
53
+ env: extra.env,
54
+ });
55
+
56
+ const runSteps = (steps, top) => {
57
+ try {
58
+ let step = steps.next();
59
+ while (!step.done) {
60
+ step = steps.next(runGit(step.value.args, top, step.value));
61
+ }
62
+ return step.value;
63
+ } finally {
64
+ steps.return();
65
+ }
52
66
  };
53
67
 
54
- const runGitAsync = (args, cwd, extra = {}) => {
55
- const options = gitProcOptions(cwd, extra);
56
- return runProc('git', args, options).then(gitResult);
68
+ const runStepsAsync = async (steps, top, signal) => {
69
+ try {
70
+ let step = steps.next();
71
+ while (!step.done) {
72
+ const extra = { ...step.value, signal };
73
+ step = steps.next(await runGitAsync(step.value.args, top, extra));
74
+ }
75
+ return step.value;
76
+ } finally {
77
+ steps.return();
78
+ }
57
79
  };
58
80
 
59
- const pathArgsOf = (paths) => (paths.length ? ['--', ...paths] : []);
81
+ const gitOp = (op) => ({
82
+ sync: (top, ...args) => runSteps(op(...args), top),
83
+ async: (top, ...args) => runStepsAsync(op(...args), top),
84
+ });
60
85
 
61
- const cachedDiffArgs = (pathArgs) => [
62
- ...GIT_CONFIG,
63
- 'diff',
64
- '--cached',
65
- ...DIFF_OPTS,
66
- ...pathArgs,
67
- ];
86
+ const pathArgs = (paths) => (paths.length ? ['--', ...paths] : []);
68
87
 
69
- const worktreeDiffArgs = (pathArgs) => [
88
+ const diffArgs = (revs, paths) => [
70
89
  ...GIT_CONFIG,
71
90
  'diff',
72
91
  ...DIFF_OPTS,
73
- ...pathArgs,
92
+ ...revs,
93
+ ...pathArgs(paths),
74
94
  ];
75
95
 
76
- const commitDiffArgs = (commit, parent, pathArgs) => {
96
+ const commitDiffArgs = (commit, parent, paths) => {
77
97
  if (parent.status === 0) {
78
- const from = parent.stdout.trim();
79
- return [...GIT_CONFIG, 'diff', ...DIFF_OPTS, from, commit, ...pathArgs];
98
+ return diffArgs([parent.stdout.trim(), commit], paths);
80
99
  }
81
100
  return [
82
101
  ...GIT_CONFIG,
@@ -86,62 +105,34 @@ const commitDiffArgs = (commit, parent, pathArgs) => {
86
105
  '-p',
87
106
  ...DIFF_OPTS,
88
107
  commit,
89
- ...pathArgs,
108
+ ...pathArgs(paths),
90
109
  ];
91
110
  };
92
111
 
93
- const BINARY_SIZE = 1000000;
112
+ const untrackedArgs = (paths) => [
113
+ 'ls-files',
114
+ '-o',
115
+ '--exclude-standard',
116
+ '-z',
117
+ ...pathArgs(paths),
118
+ ];
94
119
 
95
- const isReviewFile = (rel) => {
96
- if (!rel) return false;
97
- const first = rel.split(/[/\\]/)[0];
98
- return first === REVIEW_DIR;
99
- };
120
+ const isReviewFile = (rel) => rel.split(/[/\\]/)[0] === REVIEW_DIR;
100
121
 
101
122
  const dropReviewItems = (items) =>
102
123
  items.filter((item) => !isReviewFile(itemPath(item)));
103
124
 
104
- const isBinaryBuffer = (buf) => {
105
- if (buf.length > BINARY_SIZE) return true;
106
- return buf.includes(0);
107
- };
125
+ const isBinaryBuffer = (buf) => buf.length > BINARY_SIZE || buf.includes(0);
108
126
 
109
- const namesFromLs = (stdout) => {
110
- if (!stdout) return [];
111
- return stdout.split('\0').filter(Boolean);
112
- };
113
-
114
- const untrackedLsArgs = (paths) => {
115
- const args = ['ls-files', '-o', '--exclude-standard', '-z'];
116
- if (paths.length) args.push('--', ...paths);
117
- return args;
118
- };
119
-
120
- const listUntracked = (top, paths) => {
121
- const result = requireOk(runGit(untrackedLsArgs(paths), top));
122
- return namesFromLs(result.stdout);
123
- };
124
-
125
- const filterListed = (names) =>
126
- names.filter((rel) => rel && !isReviewFile(rel));
127
-
128
- const listTreeArgs = (rev, paths) => {
129
- const args = ['ls-tree', '-r', '--name-only', '-z', rev];
130
- if (paths.length) args.push('--', ...paths);
131
- return args;
132
- };
133
-
134
- const listWorktreeArgs = (paths) => {
135
- const args = ['ls-files', '-co', '--exclude-standard', '-z'];
136
- if (paths.length) args.push('--', ...paths);
137
- return args;
138
- };
127
+ const namesFromLs = (stdout) => stdout.split('\0').filter(Boolean);
139
128
 
140
129
  const listScopeFiles = (top, paths = [], options = {}) => {
141
130
  const rev = options.commit;
142
- const args = rev ? listTreeArgs(rev, paths) : listWorktreeArgs(paths);
131
+ const args = rev
132
+ ? ['ls-tree', '-r', '--name-only', '-z', rev, ...pathArgs(paths)]
133
+ : ['ls-files', '-co', '--exclude-standard', '-z', ...pathArgs(paths)];
143
134
  const result = requireOk(runGit(args, top), 'ls-files failed');
144
- const names = filterListed(namesFromLs(result.stdout));
135
+ const names = namesFromLs(result.stdout).filter((rel) => !isReviewFile(rel));
145
136
  names.sort((left, right) => left.localeCompare(right, 'en'));
146
137
  return names;
147
138
  };
@@ -160,43 +151,33 @@ const untrackedItemsFrom = (top, names) => {
160
151
  const files = [];
161
152
  for (const rel of names) {
162
153
  if (isReviewFile(rel)) continue;
163
- const full = path.join(top, rel);
164
154
  let buf;
165
155
  try {
166
- buf = fs.readFileSync(full);
156
+ buf = fs.readFileSync(path.join(top, rel));
167
157
  } catch (error) {
168
158
  if (error.code === 'ENOENT') continue;
169
159
  throw error;
170
160
  }
171
- if (isBinaryBuffer(buf)) {
172
- files.push(binaryNewFile(rel));
173
- continue;
174
- }
175
- files.push(synthesizeNewFile(rel, buf.toString('utf8')));
161
+ if (isBinaryBuffer(buf)) files.push(binaryNewFile(rel));
162
+ else files.push(synthesizeNewFile(rel, buf.toString('utf8')));
176
163
  }
177
164
  return itemsFromFiles(files, 'untracked');
178
165
  };
179
166
 
180
- const mergeItems = (groups) => {
167
+ const groupByPath = (items) => {
181
168
  const byPath = new Map();
182
- for (const group of groups) {
183
- for (const item of group) {
184
- const rel = itemPath(item);
185
- const list = byPath.get(rel);
186
- if (list) {
187
- list.push(item);
188
- continue;
189
- }
190
- byPath.set(rel, [item]);
191
- }
169
+ for (const item of items) {
170
+ const rel = itemPath(item);
171
+ const list = byPath.get(rel);
172
+ if (list) list.push(item);
173
+ else byPath.set(rel, [item]);
192
174
  }
193
- return [...byPath.values()].flat();
175
+ return byPath;
194
176
  };
195
177
 
196
178
  const gitText = (top, spec) => {
197
179
  const result = runGit(['show', spec], top);
198
- if (result.status !== 0) return '';
199
- return result.stdout ?? '';
180
+ return result.status === 0 ? result.stdout : '';
200
181
  };
201
182
 
202
183
  const worktreeText = (top, rel) => {
@@ -215,24 +196,19 @@ const fileText = (top, rel, rev) => {
215
196
 
216
197
  const readDepSides = (top, rev, origin, rel) => {
217
198
  if (origin === 'untracked') {
218
- const newText = worktreeText(top, rel);
219
- return { oldText: '', newText };
199
+ return { oldText: '', newText: worktreeText(top, rel) };
220
200
  }
221
201
  if (origin === 'staged') {
222
202
  const oldText = gitText(top, `HEAD:${rel}`);
223
- const newText = gitText(top, `:${rel}`);
224
- return { oldText, newText };
203
+ return { oldText, newText: gitText(top, `:${rel}`) };
225
204
  }
226
205
  if (origin === 'unstaged') {
227
- const indexed = gitText(top, `:${rel}`);
228
- const oldText = indexed || gitText(top, `HEAD:${rel}`);
229
- const newText = worktreeText(top, rel);
230
- return { oldText, newText };
206
+ const oldText = gitText(top, `:${rel}`) || gitText(top, `HEAD:${rel}`);
207
+ return { oldText, newText: worktreeText(top, rel) };
231
208
  }
232
209
  if (origin === 'commit' && rev) {
233
210
  const oldText = gitText(top, `${rev}^:${rel}`);
234
- const newText = gitText(top, `${rev}:${rel}`);
235
- return { oldText, newText };
211
+ return { oldText, newText: gitText(top, `${rev}:${rel}`) };
236
212
  }
237
213
  return { oldText: '', newText: '' };
238
214
  };
@@ -245,106 +221,77 @@ const toplevel = (cwd) => {
245
221
 
246
222
  const resolveRev = (cwd, spec) => {
247
223
  if (!spec) return null;
248
- const top = toplevel(cwd);
249
224
  const args = ['rev-parse', '--verify', `${spec}^{commit}`];
250
- const result = runGit(args, top);
251
- if (result.status !== 0) return null;
252
- return result.stdout.trim();
225
+ const result = runGit(args, toplevel(cwd));
226
+ return result.status === 0 ? result.stdout.trim() : null;
253
227
  };
254
228
 
255
229
  const shortRev = (top, commit) => {
256
230
  const result = runGit(['rev-parse', '--short', commit], top);
257
- if (result.status !== 0) return commit.slice(0, 7);
258
- return result.stdout.trim();
231
+ return result.status === 0 ? result.stdout.trim() : commit.slice(0, 7);
259
232
  };
260
233
 
261
234
  const currentBranch = (top) => {
262
- const args = ['rev-parse', '--abbrev-ref', 'HEAD'];
263
- const result = runGit(args, top);
235
+ const result = runGit(['rev-parse', '--abbrev-ref', 'HEAD'], top);
264
236
  if (result.status !== 0) return '';
265
237
  const name = result.stdout.trim();
266
- if (!name || name === 'HEAD') return '';
267
- return name;
238
+ return name === 'HEAD' ? '' : name;
268
239
  };
269
240
 
270
- const snapshotWorktree = (top, stagedOut, unstagedOut, names) => {
271
- const stagedDiff = parseDiff(stagedOut);
272
- const unstagedDiff = parseDiff(unstagedOut);
273
- const staged = itemsFromFiles(stagedDiff, 'staged');
274
- const unstaged = itemsFromFiles(unstagedDiff, 'unstaged');
275
- const untracked = untrackedItemsFrom(top, names);
276
- const combined = mergeItems([staged, unstaged, untracked]);
277
- const parsed = dropReviewItems(combined);
278
- const branch = currentBranch(top);
279
- return { top, parsed, branch };
241
+ const snapshotWorktree = (top, results) => {
242
+ const outputs = results.map((result) => requireOk(result).stdout);
243
+ const staged = itemsFromFiles(parseDiff(outputs[0]), 'staged');
244
+ const unstaged = itemsFromFiles(parseDiff(outputs[1]), 'unstaged');
245
+ const untracked = untrackedItemsFrom(top, namesFromLs(outputs[2]));
246
+ const grouped = groupByPath([...staged, ...unstaged, ...untracked]);
247
+ const parsed = dropReviewItems([...grouped.values()].flat());
248
+ return { top, parsed, branch: currentBranch(top) };
280
249
  };
281
250
 
282
251
  const snapshotCommit = (top, commit, files) => {
283
- const commitItems = itemsFromFiles(files, 'commit');
284
- const parsed = dropReviewItems(commitItems);
252
+ const parsed = dropReviewItems(itemsFromFiles(files, 'commit'));
285
253
  const revShort = shortRev(top, commit);
286
- const branch = currentBranch(top);
287
- return { top, parsed, rev: commit, revShort, branch };
254
+ return { top, parsed, rev: commit, revShort, branch: currentBranch(top) };
288
255
  };
289
256
 
290
- const loadCommit = (top, commit, extra) => {
291
- const parent = runGit(['rev-parse', '--verify', `${commit}^`], top);
292
- const args = commitDiffArgs(commit, parent, extra);
293
- const result = requireOk(runGit(args, top));
294
- return parseDiff(result.stdout);
295
- };
257
+ function* commitDiff(commit, paths) {
258
+ const parent = yield command(['rev-parse', '--verify', `${commit}^`]);
259
+ const result = yield command(commitDiffArgs(commit, parent, paths));
260
+ return parseDiff(requireOk(result).stdout);
261
+ }
296
262
 
297
- const loadCommitAsync = async (top, commit, extra, signal) => {
298
- const parent = await runGitAsync(
299
- ['rev-parse', '--verify', `${commit}^`],
300
- top,
301
- { signal },
302
- );
303
- const args = commitDiffArgs(commit, parent, extra);
304
- const result = requireOk(await runGitAsync(args, top, { signal }));
305
- return parseDiff(result.stdout);
306
- };
263
+ const worktreeCommands = (paths) => [
264
+ diffArgs(['--cached'], paths),
265
+ diffArgs([], paths),
266
+ untrackedArgs(paths),
267
+ ];
307
268
 
308
269
  const readSnapshot = (cwd, paths, options) => {
309
270
  const top = toplevel(cwd);
310
- const extra = pathArgsOf(paths);
311
271
  const rev = options.commit;
312
- if (rev) return snapshotCommit(top, rev, loadCommit(top, rev, extra));
313
- const stagedRes = requireOk(runGit(cachedDiffArgs(extra), top));
314
- const unstagedRes = requireOk(runGit(worktreeDiffArgs(extra), top));
315
- const names = listUntracked(top, paths);
316
- return snapshotWorktree(top, stagedRes.stdout, unstagedRes.stdout, names);
272
+ if (rev) {
273
+ return snapshotCommit(top, rev, runSteps(commitDiff(rev, paths), top));
274
+ }
275
+ const results = worktreeCommands(paths).map((args) => runGit(args, top));
276
+ return snapshotWorktree(top, results);
317
277
  };
318
278
 
319
279
  const readSnapshotAsync = async (cwd, paths, options) => {
320
280
  const top = toplevel(cwd);
321
- const extra = pathArgsOf(paths);
322
- const rev = options.commit;
323
- const signal = options.signal;
281
+ const { commit: rev, signal } = options;
324
282
  if (rev) {
325
- const files = await loadCommitAsync(top, rev, extra, signal);
283
+ const files = await runStepsAsync(commitDiff(rev, paths), top, signal);
326
284
  return snapshotCommit(top, rev, files);
327
285
  }
328
- const stagedArgs = cachedDiffArgs(extra);
329
- const unstagedArgs = worktreeDiffArgs(extra);
330
- const lsArgs = untrackedLsArgs(paths);
331
- const stagedP = runGitAsync(stagedArgs, top, { signal });
332
- const unstagedP = runGitAsync(unstagedArgs, top, { signal });
333
- const lsP = runGitAsync(lsArgs, top, { signal });
334
- const extras = await Promise.all([stagedP, unstagedP, lsP]);
335
- const stagedRes = extras[0];
336
- const unstagedRes = extras[1];
337
- const lsRes = extras[2];
338
- requireOk(stagedRes);
339
- requireOk(unstagedRes);
340
- const names = namesFromLs(requireOk(lsRes).stdout);
341
- return snapshotWorktree(top, stagedRes.stdout, unstagedRes.stdout, names);
286
+ const running = worktreeCommands(paths).map((args) =>
287
+ runGitAsync(args, top, { signal }),
288
+ );
289
+ return snapshotWorktree(top, await Promise.all(running));
342
290
  };
343
291
 
344
292
  const applyPatch = (cwd, patch, flags) => {
345
293
  const args = ['apply', ...flags, '--whitespace=nowarn', '-'];
346
- const result = runGit(args, cwd, patch);
347
- requireOk(result, 'apply failed');
294
+ requireOk(runGit(args, cwd, { input: patch }), 'apply failed');
348
295
  };
349
296
 
350
297
  const addItem = (top, item) => {
@@ -357,35 +304,26 @@ const addItem = (top, item) => {
357
304
 
358
305
  const unstageItem = (top, item) => {
359
306
  if (item.origin !== 'staged') return;
360
- const rel = itemPath(item);
361
307
  if (item.file.isBinary) {
362
- return void requireOk(runGit(['restore', '--staged', '--', rel], top));
308
+ const args = ['restore', '--staged', '--', itemPath(item)];
309
+ return void requireOk(runGit(args, top));
363
310
  }
364
311
  applyPatch(top, item.patchAdd, ['--reverse', '--cached']);
365
312
  };
366
313
 
367
314
  const revertOnePath = (top, rel, items) => {
368
- const origins = new Set();
369
- let isNew = false;
370
- for (const item of items) {
371
- origins.add(item.origin);
372
- if (item.file && item.file.isNew) isNew = true;
373
- }
374
- if (isNew) {
375
- if (origins.has('staged')) {
376
- return void requireOk(runGit(['rm', '-f', '--', rel], top));
377
- }
378
- return void fs.unlinkSync(path.join(top, rel));
379
- }
380
- if (origins.has('untracked') && origins.size === 1) {
381
- return void fs.unlinkSync(path.join(top, rel));
315
+ const origins = new Set(items.map((item) => item.origin));
316
+ const isNew = items.some((item) => item.file?.isNew);
317
+ if (isNew && origins.has('staged')) {
318
+ return void requireOk(runGit(['rm', '-f', '--', rel], top));
382
319
  }
320
+ const onlyUntracked = origins.size === 1 && origins.has('untracked');
321
+ if (isNew || onlyUntracked) return void fs.unlinkSync(path.join(top, rel));
383
322
  const args = ['restore', '-s', 'HEAD', '--worktree', '--staged', '--', rel];
384
323
  requireOk(runGit(args, top));
385
324
  };
386
325
 
387
326
  const revertItem = (top, item) => {
388
- if (item.origin === 'commit') return;
389
327
  const rel = itemPath(item);
390
328
  if (item.origin === 'untracked') {
391
329
  return void fs.unlinkSync(path.join(top, rel));
@@ -393,8 +331,7 @@ const revertItem = (top, item) => {
393
331
  if (item.file.isBinary) {
394
332
  const args = ['restore', '-s', 'HEAD', '--worktree'];
395
333
  if (item.origin === 'staged') args.push('--staged');
396
- args.push('--', rel);
397
- return void requireOk(runGit(args, top));
334
+ return void requireOk(runGit([...args, '--', rel], top));
398
335
  }
399
336
  if (item.origin === 'unstaged') {
400
337
  return void applyPatch(top, item.patchRevert, ['--reverse']);
@@ -407,12 +344,6 @@ const revertItem = (top, item) => {
407
344
  }
408
345
  };
409
346
 
410
- const indexText = (top, rel) => {
411
- const result = runGit(['show', `:${rel}`], top);
412
- requireOk(result, 'show failed');
413
- return result.stdout ?? '';
414
- };
415
-
416
347
  const indexMode = (top, rel) => {
417
348
  const result = requireOk(runGit(['ls-files', '--stage', '--', rel], top));
418
349
  const line = result.stdout.trim();
@@ -428,17 +359,17 @@ const indexMode = (top, rel) => {
428
359
 
429
360
  const writeIndex = (top, rel, text) => {
430
361
  const hashArgs = ['hash-object', '-w', `--path=${rel}`, '--stdin'];
431
- const hashed = runGit(hashArgs, top, text);
432
- requireOk(hashed, 'hash-object failed');
433
- const sha = (hashed.stdout ?? '').trim();
362
+ const hashed = runGit(hashArgs, top, { input: text });
363
+ const sha = requireOk(hashed, 'hash-object failed').stdout.trim();
434
364
  const mode = indexMode(top, rel);
435
365
  const args = ['update-index', '--add', '--cacheinfo', mode, sha, rel];
436
366
  requireOk(runGit(args, top), 'update-index failed');
437
367
  };
438
368
 
439
- const editStaged = (top, rel, abs, hunk, blockId, text) => {
440
- const source = indexText(top, rel);
441
- const next = replaceBlockAdds(source, hunk, blockId, text);
369
+ const editStaged = (top, rel, abs, item, text) => {
370
+ const shown = requireOk(runGit(['show', `:${rel}`], top), 'show failed');
371
+ const source = shown.stdout;
372
+ const next = replaceBlockAdds(source, item.hunk, item.blockId, text);
442
373
  writeIndex(top, rel, next);
443
374
  let work = null;
444
375
  try {
@@ -446,28 +377,24 @@ const editStaged = (top, rel, abs, hunk, blockId, text) => {
446
377
  } catch (error) {
447
378
  if (error.code !== 'ENOENT') throw error;
448
379
  }
449
- if (work === null) {
450
- return void fs.writeFileSync(abs, next);
451
- }
452
- if (work === source) fs.writeFileSync(abs, next);
380
+ if (work === null || work === source) fs.writeFileSync(abs, next);
453
381
  };
454
382
 
455
- const editWorktree = (abs, hunk, blockId, text) => {
383
+ const editWorktree = (abs, item, text) => {
456
384
  const source = fs.readFileSync(abs, 'utf8');
457
- const next = replaceBlockAdds(source, hunk, blockId, text);
458
- if (next === source) return;
459
- fs.writeFileSync(abs, next);
385
+ const next = replaceBlockAdds(source, item.hunk, item.blockId, text);
386
+ if (next !== source) fs.writeFileSync(abs, next);
460
387
  };
461
388
 
462
389
  const editItem = (top, item, text) => {
463
- if (item.origin === 'commit' || item.origin === 'pr') return;
390
+ if (isReadOnlyOrigin(item.origin)) return;
464
391
  const rel = itemPath(item);
465
392
  if (!rel || !item.hunk) return;
466
393
  const abs = path.join(top, rel);
467
394
  if (item.origin === 'staged') {
468
- return void editStaged(top, rel, abs, item.hunk, item.blockId, text);
395
+ return void editStaged(top, rel, abs, item, text);
469
396
  }
470
- editWorktree(abs, item.hunk, item.blockId, text);
397
+ editWorktree(abs, item, text);
471
398
  };
472
399
 
473
400
  const writeFile = (top, rel, text) => {
@@ -483,10 +410,12 @@ const stagePath = (top, rel) => {
483
410
  };
484
411
 
485
412
  module.exports = {
486
- oneLine,
487
413
  requireOk,
488
414
  runGit,
489
415
  runGitAsync,
416
+ command,
417
+ gitOp,
418
+ groupByPath,
490
419
  gitText,
491
420
  worktreeText,
492
421
  readDepSides,
package/lib/git.js CHANGED
@@ -3,47 +3,18 @@
3
3
  const capabilities = require('./capabilities.js');
4
4
  const { capabilitiesFor, attachCapabilities } = capabilities;
5
5
  const worktree = require('./git-worktree.js');
6
- const { readSnapshot, readSnapshotAsync, toplevel, resolveRev } = worktree;
7
- const { listScopeFiles, fileText, editItem, writeFile, stagePath } = worktree;
8
- const { addItem, unstageItem, revertItem, currentBranch } = worktree;
9
6
  const branches = require('./git-branches.js');
10
7
  const gitDeps = require('./git-deps.js');
11
8
 
12
9
  const load = (cwd, paths = [], options = {}) => {
13
- const snapshot = readSnapshot(cwd, paths, options);
10
+ const snapshot = worktree.readSnapshot(cwd, paths, options);
14
11
  return gitDeps.finishLoad(snapshot, { ...options, paths });
15
12
  };
16
13
 
17
14
  const loadAsync = async (cwd, paths = [], options = {}) => {
18
- const snapshot = await readSnapshotAsync(cwd, paths, options);
19
- return gitDeps.finishLoad(snapshot, {
20
- ...options,
21
- paths,
22
- deferExtras: options.deferExtras ?? true,
23
- });
24
- };
25
-
26
- const addGitItem = (top, item) => {
27
- if (item.dep) return gitDeps.addItem(top, item);
28
- return addItem(top, item);
29
- };
30
-
31
- const addGitItemAsync = async (top, item) => {
32
- if (item.dep) return gitDeps.addItemAsync(top, item);
33
- return addItem(top, item);
34
- };
35
-
36
- const unstageGitItem = (top, item) => {
37
- if (item.dep) return gitDeps.unstageItem(top, item);
38
- return unstageItem(top, item);
39
- };
40
-
41
- const revertGitItem = (top, item) => {
42
- if (item.origin === 'commit') return;
43
- if (item.dep) {
44
- return void gitDeps.revertDepItem(top, item);
45
- }
46
- revertItem(top, item);
15
+ const snapshot = await worktree.readSnapshotAsync(cwd, paths, options);
16
+ const deferExtras = options.deferExtras ?? true;
17
+ return gitDeps.finishLoad(snapshot, { ...options, paths, deferExtras });
47
18
  };
48
19
 
49
20
  const createGitRepo = () =>
@@ -52,18 +23,18 @@ const createGitRepo = () =>
52
23
  load,
53
24
  loadAsync,
54
25
  loadExtras: gitDeps.loadExtras,
55
- toplevel,
56
- resolveRev,
57
- listFiles: listScopeFiles,
58
- fileText,
59
- add: addGitItem,
60
- addAsync: addGitItemAsync,
61
- unstage: unstageGitItem,
62
- revert: revertGitItem,
26
+ toplevel: worktree.toplevel,
27
+ resolveRev: worktree.resolveRev,
28
+ listFiles: worktree.listScopeFiles,
29
+ fileText: worktree.fileText,
30
+ add: gitDeps.addItem,
31
+ addAsync: gitDeps.addItemAsync,
32
+ unstage: gitDeps.unstageItem,
33
+ revert: gitDeps.revertItem,
63
34
  revertFile: gitDeps.revertFile,
64
- edit: editItem,
65
- writeFile,
66
- stagePath,
35
+ edit: worktree.editItem,
36
+ writeFile: worktree.writeFile,
37
+ stagePath: worktree.stagePath,
67
38
  commit: branches.commitChanges,
68
39
  commitAsync: branches.commitChangesAsync,
69
40
  hasStaged: branches.hasStaged,
@@ -71,7 +42,7 @@ const createGitRepo = () =>
71
42
  commitMessage: branches.commitMessage,
72
43
  listBranches: branches.listBranches,
73
44
  listCommits: branches.listCommits,
74
- currentBranch,
45
+ currentBranch: worktree.currentBranch,
75
46
  checkout: branches.checkoutBranch,
76
47
  checkoutAsync: branches.checkoutBranchAsync,
77
48
  createBranch: branches.createBranch,
@@ -95,13 +66,14 @@ const createGitRepo = () =>
95
66
  );
96
67
 
97
68
  module.exports = {
98
- ...worktree,
99
69
  ...branches,
70
+ currentBranch: worktree.currentBranch,
71
+ editItem: worktree.editItem,
100
72
  load,
101
73
  loadAsync,
102
74
  loadExtras: gitDeps.loadExtras,
103
- addItem: addGitItem,
104
- unstageItem: unstageGitItem,
105
- revertItem: revertGitItem,
75
+ addItem: gitDeps.addItem,
76
+ unstageItem: gitDeps.unstageItem,
77
+ revertItem: gitDeps.revertItem,
106
78
  createGitRepo,
107
79
  };