reslop 0.2.7 → 0.3.1

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 (158) hide show
  1. package/CHANGELOG.md +30 -1
  2. package/README.md +16 -14
  3. package/bin/reslop.js +8 -5
  4. package/lib/agents/agents.js +573 -0
  5. package/lib/agents/allow.js +71 -0
  6. package/lib/agents/allow.json +17 -0
  7. package/lib/agents/models.js +467 -0
  8. package/lib/api.js +15 -12
  9. package/lib/cli.js +43 -68
  10. package/lib/{files.js → common/files.js} +48 -55
  11. package/lib/{find.js → common/find.js} +1 -1
  12. package/lib/common/format.js +101 -0
  13. package/lib/{utilities.js → common/process.js} +7 -95
  14. package/lib/common/tasks.json +27 -0
  15. package/lib/common/utilities.js +112 -0
  16. package/lib/dashboard/git.js +9 -16
  17. package/lib/dashboard/model.js +90 -49
  18. package/lib/dashboard/npm.js +17 -13
  19. package/lib/dashboard/tree.js +27 -30
  20. package/lib/{session → dashboard}/watch.js +36 -31
  21. package/lib/{workspace.js → dashboard/workspace.js} +23 -38
  22. package/lib/{deps.js → deps/deps.js} +31 -45
  23. package/lib/{deps-diff.js → deps/diff.js} +35 -25
  24. package/lib/{manifest.js → deps/manifest.js} +47 -30
  25. package/lib/diff/align.js +1 -2
  26. package/lib/diff/diff.js +2 -2
  27. package/lib/diff/display.js +29 -5
  28. package/lib/diff/inline.js +10 -14
  29. package/lib/diff/items.js +6 -4
  30. package/lib/diff/patch.js +39 -3
  31. package/lib/diff/unit.js +45 -16
  32. package/lib/{git-branches.js → git/branches.js} +53 -48
  33. package/lib/{git-deps.js → git/deps.js} +151 -95
  34. package/lib/git/git.js +71 -0
  35. package/lib/git/worktree.js +504 -0
  36. package/lib/highlight/bash.js +1 -2
  37. package/lib/highlight/csv.js +1 -2
  38. package/lib/highlight/dot.js +1 -2
  39. package/lib/{highlight.js → highlight/highlight.js} +42 -11
  40. package/lib/highlight/html.js +20 -18
  41. package/lib/highlight/json.js +1 -2
  42. package/lib/highlight/log.js +1 -2
  43. package/lib/highlight/txt.js +1 -2
  44. package/lib/{session → input}/actions.js +114 -48
  45. package/lib/{editor.js → input/editor.js} +16 -8
  46. package/lib/{keys.js → input/keys.js} +24 -45
  47. package/lib/input/line-edit.js +78 -0
  48. package/lib/input/menu-pick.js +70 -0
  49. package/lib/render/agents.js +642 -0
  50. package/lib/render/branches.js +4 -5
  51. package/lib/render/commits.js +54 -64
  52. package/lib/render/{dash-activity.js → dashboard/activity.js} +25 -40
  53. package/lib/render/{dash-blocks.js → dashboard/blocks.js} +131 -137
  54. package/lib/render/{dashboard.js → dashboard/dashboard.js} +25 -22
  55. package/lib/render/diff.js +166 -45
  56. package/lib/render/field.js +131 -0
  57. package/lib/render/files.js +9 -14
  58. package/lib/render/footer.js +385 -0
  59. package/lib/render/frame.js +123 -25
  60. package/lib/render/header.js +176 -0
  61. package/lib/render/log.js +320 -0
  62. package/lib/render/menu.js +60 -0
  63. package/lib/render/notes.js +16 -11
  64. package/lib/render/npm.js +322 -196
  65. package/lib/render/packages.js +9 -13
  66. package/lib/render/primitives.js +101 -15
  67. package/lib/render/render.js +7 -11
  68. package/lib/render/repos.js +13 -25
  69. package/lib/render/status.js +370 -0
  70. package/lib/render/status.json +85 -0
  71. package/lib/render/{dash-table.js → table.js} +13 -60
  72. package/lib/render/tiles.js +109 -48
  73. package/lib/render/unit.js +32 -13
  74. package/lib/{report-model.js → report/model.js} +4 -6
  75. package/lib/{report-parse.js → report/parse.js} +9 -14
  76. package/lib/{report-render.js → report/render.js} +48 -19
  77. package/lib/{report-run.js → report/run.js} +42 -30
  78. package/lib/{report-test.js → report/test.js} +56 -55
  79. package/lib/{report-tools.js → report/tools.js} +24 -33
  80. package/lib/review/agent-instructions.md +14 -0
  81. package/lib/review/format.js +420 -0
  82. package/lib/{review-merge.js → review/merge.js} +9 -8
  83. package/lib/review/review.js +190 -0
  84. package/lib/review/store.js +281 -0
  85. package/lib/review/templates.js +83 -0
  86. package/lib/runs/commands.js +14 -0
  87. package/lib/runs/logs.js +299 -0
  88. package/lib/runs/output.js +174 -0
  89. package/lib/runs/process.js +113 -0
  90. package/lib/{runs.js → runs/runs.js} +84 -36
  91. package/lib/runs/scripts.js +150 -0
  92. package/lib/session/agents/agents.js +976 -0
  93. package/lib/session/agents/job.js +320 -0
  94. package/lib/session/agents/jobs.js +355 -0
  95. package/lib/session/agents/options.js +153 -0
  96. package/lib/session/agents/plans.js +83 -0
  97. package/lib/session/branches.js +33 -40
  98. package/lib/session/change-actions.js +34 -100
  99. package/lib/session/commit.js +91 -52
  100. package/lib/session/compose.js +189 -147
  101. package/lib/session/dashboard.js +23 -22
  102. package/lib/session/dispatch.js +86 -21
  103. package/lib/session/files-pane.js +47 -26
  104. package/lib/session/find.js +16 -29
  105. package/lib/session/import.js +93 -0
  106. package/lib/session/items.js +8 -17
  107. package/lib/session/lifecycle.js +78 -72
  108. package/lib/session/load.js +2 -4
  109. package/lib/session/navigation.js +6 -3
  110. package/lib/session/npm/history.js +250 -0
  111. package/lib/session/npm/npm.js +836 -0
  112. package/lib/session/{packages.js → npm/packages.js} +21 -14
  113. package/lib/session/ops.js +125 -34
  114. package/lib/session/pointer.js +202 -10
  115. package/lib/session/prefs.js +112 -0
  116. package/lib/session/reads.js +109 -0
  117. package/lib/session/review.js +28 -10
  118. package/lib/session/run-panel.js +40 -0
  119. package/lib/{session.js → session/session.js} +158 -63
  120. package/lib/session/tasks.js +321 -66
  121. package/lib/session/templates.js +1 -1
  122. package/lib/session/terminal.js +94 -12
  123. package/lib/session/unit.js +35 -30
  124. package/lib/session/update.js +194 -6
  125. package/lib/session/view.js +65 -65
  126. package/lib/session/workspace.js +39 -78
  127. package/lib/{capabilities.js → source/capabilities.js} +3 -12
  128. package/lib/{github.js → source/github.js} +102 -32
  129. package/lib/{gitlab.js → source/gitlab.js} +87 -10
  130. package/lib/{remote.js → source/remote.js} +144 -40
  131. package/lib/source/review-threads.graphql +20 -0
  132. package/lib/source/source.js +74 -0
  133. package/lib/term/ansi.js +396 -0
  134. package/lib/{clipboard.js → term/clipboard.js} +3 -6
  135. package/lib/{select.js → term/select.js} +4 -3
  136. package/lib/term/themes/dark.json +122 -0
  137. package/lib/term/themes/light.json +122 -0
  138. package/lib/term/wide.txt +21 -0
  139. package/lib/{wrap.js → term/wrap.js} +4 -4
  140. package/package.json +2 -2
  141. package/lib/ansi.js +0 -545
  142. package/lib/detect.js +0 -34
  143. package/lib/diff/edit.js +0 -33
  144. package/lib/git-worktree.js +0 -455
  145. package/lib/git.js +0 -79
  146. package/lib/npm-commands.js +0 -522
  147. package/lib/render/chrome.js +0 -806
  148. package/lib/render/present.js +0 -19
  149. package/lib/render/report.js +0 -43
  150. package/lib/render/search.js +0 -25
  151. package/lib/review.js +0 -745
  152. package/lib/session/accessors.js +0 -26
  153. package/lib/session/edit-click.js +0 -57
  154. package/lib/session/npm.js +0 -452
  155. package/lib/session/progress.js +0 -41
  156. package/lib/source.js +0 -36
  157. package/lib/update.js +0 -192
  158. /package/lib/{git-reword-todo.js → git/reword-todo.js} +0 -0
@@ -1,9 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const align = require('./align.js');
4
- const { isCtxType, attachInline, LAYOUT_ALIGN } = align;
5
- const patch = require('./patch.js');
6
- const { flattenBlock } = patch;
3
+ const { isCtxType, attachInline, LAYOUT_ALIGN } = require('./align.js');
4
+ const { flattenBlock } = require('./patch.js');
7
5
 
8
6
  const DISPLAY_CONTEXT = 3;
9
7
 
@@ -80,11 +78,37 @@ const blockAddText = (hunk, blockId) => {
80
78
  return parts.join('\n');
81
79
  };
82
80
 
81
+ const countsOld = (line) => {
82
+ if (line.type === 'add' || line.synthetic === true) return false;
83
+ return line.type !== 'note' && line.type !== 'noteSep';
84
+ };
85
+
86
+ const countsNew = (line) => line.type !== 'del';
87
+
88
+ const stampLineNumbers = (lines, hunk) => {
89
+ let oldNo = hunk && hunk.oldStart > 0 ? hunk.oldStart : 0;
90
+ let newNo = hunk && hunk.newStart > 0 ? hunk.newStart : 1;
91
+ return lines.map((line) => {
92
+ const next = { ...line };
93
+ if (line.type === 'note' || line.type === 'noteSep') return next;
94
+ if (countsOld(line)) {
95
+ if (oldNo > 0) next.oldNo = oldNo;
96
+ oldNo += 1;
97
+ }
98
+ if (countsNew(line)) {
99
+ next.newNo = newNo;
100
+ newNo += 1;
101
+ }
102
+ return next;
103
+ });
104
+ };
105
+
83
106
  const displayLines = (hunk, blockId, layout = 'unified', radius, overlay) => {
84
107
  const flat = flattenBlock(hunk, blockId, 'new');
85
108
  const source = overlay ? replaceAdds(flat, overlay, blockId) : flat;
109
+ const numbered = stampLineNumbers(source, hunk);
86
110
  const around = radius ?? DISPLAY_CONTEXT;
87
- const painted = attachInline(windowAroundBlock(source, blockId, around));
111
+ const painted = attachInline(windowAroundBlock(numbered, blockId, around));
88
112
  const alignRows = LAYOUT_ALIGN[layout];
89
113
  return alignRows ? alignRows(painted) : painted;
90
114
  };
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const ansi = require('../ansi.js');
4
- const { graphemes } = ansi;
3
+ const { graphemes } = require('../term/ansi.js');
5
4
 
6
5
  const LCS_CELL_LIMIT = 250000;
7
6
  const NOISY_TINY_SPANS = 6;
@@ -131,10 +130,9 @@ const opsSpans = (ops) => {
131
130
  newParts.push({ text: op.text, changed: op.type === 'add' });
132
131
  }
133
132
  }
134
- return {
135
- oldSpans: collapseSpans(oldParts),
136
- newSpans: collapseSpans(newParts),
137
- };
133
+ const oldSpans = collapseSpans(oldParts);
134
+ const newSpans = collapseSpans(newParts);
135
+ return { oldSpans, newSpans };
138
136
  };
139
137
 
140
138
  const joinAffix = (prefix, mid, suffix) => {
@@ -154,10 +152,9 @@ const diffTrimmed = (a, b, spansOfOps) => {
154
152
  const mid = whole ? wholeChanged(midA, midB) : spansOfOps(lcsOps(midA, midB));
155
153
  const prefix = a.slice(0, lo);
156
154
  const suffix = a.slice(hiA);
157
- return {
158
- oldSpans: joinAffix(prefix, mid.oldSpans, suffix),
159
- newSpans: joinAffix(prefix, mid.newSpans, suffix),
160
- };
155
+ const oldSpans = joinAffix(prefix, mid.oldSpans, suffix);
156
+ const newSpans = joinAffix(prefix, mid.newSpans, suffix);
157
+ return { oldSpans, newSpans };
161
158
  };
162
159
 
163
160
  const diffTokens = (a, b) => {
@@ -221,10 +218,9 @@ const refineWordOps = (ops) => {
221
218
  if (dels.length) oldParts.push({ text: dels.join(''), changed: true });
222
219
  if (adds.length) newParts.push({ text: adds.join(''), changed: true });
223
220
  }
224
- return {
225
- oldSpans: collapseSpans(oldParts),
226
- newSpans: collapseSpans(newParts),
227
- };
221
+ const oldSpans = collapseSpans(oldParts);
222
+ const newSpans = collapseSpans(newParts);
223
+ return { oldSpans, newSpans };
228
224
  };
229
225
 
230
226
  const opRuns = (ops) => {
package/lib/diff/items.js CHANGED
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const patch = require('./patch.js');
4
- const { splitHunk, formatHunkHeader, formatPatch } = patch;
3
+ const { splitHunk, formatHunkHeader, formatPatch } = require('./patch.js');
5
4
 
6
5
  const BINARY_ITEM = {
7
6
  hunk: null,
@@ -70,8 +69,11 @@ const itemsFromFiles = (files, origin) => {
70
69
 
71
70
  const refreshIndexPatches = (items, item) => {
72
71
  if (!item.hunk) return items;
73
- const isPeer = (other) =>
74
- other.hunk === item.hunk && other.file === item.file;
72
+ const isPeer = (other) => {
73
+ const sameHunk = other.hunk === item.hunk;
74
+ const sameFile = other.file === item.file;
75
+ return sameHunk && sameFile;
76
+ };
75
77
  const peers = items.filter(isPeer);
76
78
  const sides = {};
77
79
  for (const peer of peers) sides[peer.blockId] = indexSide(peer.origin);
package/lib/diff/patch.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const align = require('./align.js');
4
- const { isCtxType } = align;
3
+ const { clamp } = require('../common/utilities.js');
4
+ const { isCtxType } = require('./align.js');
5
5
 
6
6
  const LINE_MARK = { add: '+', del: '-' };
7
7
 
@@ -40,7 +40,11 @@ const flattenBlock = (hunk, blockId, contextSide) => {
40
40
  }
41
41
  const kept = sideOf(contextSide, line.blockId) === 'old' ? 'del' : 'add';
42
42
  if (line.type === kept) {
43
- out.push({ type: 'ctx', text: line.text, noNl: line.noNl });
43
+ const type = 'ctx';
44
+ const { text, noNl } = line;
45
+ const synthetic = true;
46
+ const row = { type, text, noNl, synthetic };
47
+ out.push(row);
44
48
  }
45
49
  }
46
50
  return out;
@@ -72,10 +76,42 @@ const formatPatch = (file, hunk, blockId, contextSide) => {
72
76
  return `${parts.join('\n')}\n`;
73
77
  };
74
78
 
79
+ const addBlockRange = (hunk, blockId) => {
80
+ let newLine = hunk.newStart;
81
+ let start = 0;
82
+ let count = 0;
83
+ for (const line of hunk.lines) {
84
+ if (line.type === 'del') {
85
+ if (line.blockId === blockId && start === 0) start = newLine;
86
+ continue;
87
+ }
88
+ if (line.type === 'add' && line.blockId === blockId) {
89
+ if (count === 0) start = newLine;
90
+ count += 1;
91
+ }
92
+ newLine += 1;
93
+ }
94
+ if (start === 0) start = hunk.newStart;
95
+ return { start, count };
96
+ };
97
+
98
+ const replaceBlockAdds = (content, hunk, blockId, text) => {
99
+ const range = addBlockRange(hunk, blockId);
100
+ const ended = content.endsWith('\n');
101
+ const body = ended ? content.slice(0, -1) : content;
102
+ const lines = content === '' ? [] : body.split('\n');
103
+ const from = clamp(range.start - 1, 0, lines.length);
104
+ const added = text === '' ? [] : text.split('\n');
105
+ lines.splice(from, range.count, ...added);
106
+ return lines.join('\n') + (ended ? '\n' : '');
107
+ };
108
+
75
109
  module.exports = {
76
110
  splitHunk,
77
111
  flattenBlock,
78
112
  countSides,
79
113
  formatHunkHeader,
80
114
  formatPatch,
115
+ addBlockRange,
116
+ replaceBlockAdds,
81
117
  };
package/lib/diff/unit.js CHANGED
@@ -1,9 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const align = require('./align.js');
4
- const { attachInline } = align;
5
- const files = require('../files.js');
6
- const { listPath } = files;
3
+ const { attachInline } = require('./align.js');
4
+ const { listPath } = require('../common/files.js');
7
5
 
8
6
  const LCS_CELLS = 4000000;
9
7
 
@@ -75,6 +73,16 @@ const rowsFromAdds = (items) => {
75
73
  return rows;
76
74
  };
77
75
 
76
+ const stampFileNumbers = (lines) => {
77
+ let newNo = 1;
78
+ for (const line of lines) {
79
+ if (line.type === 'del') continue;
80
+ line.newNo = newNo;
81
+ newNo += 1;
82
+ }
83
+ return lines;
84
+ };
85
+
78
86
  const unitLines = (text, items) => {
79
87
  let rows = splitFile(text);
80
88
  if (!rows.length) rows = rowsFromAdds(items);
@@ -95,11 +103,14 @@ const unitLines = (text, items) => {
95
103
  out.push(tagged('del', del.text, del));
96
104
  }
97
105
  if (!out.length) out.push(tagged('ctx', '', NO_EXTRA));
98
- return attachInline(out);
106
+ return stampFileNumbers(attachInline(out));
99
107
  };
100
108
 
101
- const sameBlock = (line, item) =>
102
- line.item === item && line.blockId === item.blockId;
109
+ const sameBlock = (line, item) => {
110
+ const sameItem = line.item === item;
111
+ const sameId = line.blockId === item.blockId;
112
+ return sameItem && sameId;
113
+ };
103
114
 
104
115
  const changedLineRange = (lines, item, belongs) => {
105
116
  let start = -1;
@@ -187,25 +198,33 @@ const zipOps = (prev, next) => {
187
198
  };
188
199
 
189
200
  const lcsOps = (prev, next) => {
190
- const dp = lcsTable(prev, next);
191
201
  const ops = [];
202
+ const limit = Math.min(prev.length, next.length);
203
+ let head = 0;
204
+ while (head < limit && prev[head] === next[head]) {
205
+ ops.push({ kind: 'eq', text: next[head] });
206
+ head += 1;
207
+ }
208
+ const left = head ? prev.slice(head) : prev;
209
+ const right = head ? next.slice(head) : next;
210
+ const dp = lcsTable(left, right);
192
211
  let i = 0;
193
212
  let j = 0;
194
- while (i < prev.length && j < next.length) {
195
- if (prev[i] === next[j]) {
196
- ops.push({ kind: 'eq', text: next[j] });
213
+ while (i < left.length && j < right.length) {
214
+ if (left[i] === right[j]) {
215
+ ops.push({ kind: 'eq', text: right[j] });
197
216
  i += 1;
198
217
  j += 1;
199
218
  } else if (dp[i + 1][j] >= dp[i][j + 1]) {
200
219
  ops.push({ kind: 'del' });
201
220
  i += 1;
202
221
  } else {
203
- ops.push({ kind: 'ins', text: next[j] });
222
+ ops.push({ kind: 'ins', text: right[j] });
204
223
  j += 1;
205
224
  }
206
225
  }
207
- for (; i < prev.length; i++) ops.push({ kind: 'del' });
208
- for (; j < next.length; j++) ops.push({ kind: 'ins', text: next[j] });
226
+ for (; i < left.length; i++) ops.push({ kind: 'del' });
227
+ for (; j < right.length; j++) ops.push({ kind: 'ins', text: right[j] });
209
228
  return ops;
210
229
  };
211
230
 
@@ -241,7 +260,7 @@ const pushDels = (out, base, at) => {
241
260
  return i;
242
261
  };
243
262
 
244
- const mergeEditRows = (base, nextTexts) => {
263
+ const mergeRows = (base, nextTexts) => {
245
264
  const texts = nextTexts.length ? nextTexts : [''];
246
265
  const prev = [];
247
266
  for (const row of base) if (row.type !== 'del') prev.push(row.text);
@@ -259,7 +278,17 @@ const mergeEditRows = (base, nextTexts) => {
259
278
  }
260
279
  pushDels(out, base, at);
261
280
  if (!out.length) out.push(tagged('ctx', '', NO_EXTRA));
262
- return attachEditSpans(attachInline(out));
281
+ return stampFileNumbers(attachEditSpans(attachInline(out)));
282
+ };
283
+
284
+ let merged = { base: null, text: '', rows: [] };
285
+
286
+ const mergeEditRows = (base, nextTexts) => {
287
+ const text = nextTexts.join('\n');
288
+ if (merged.base === base && merged.text === text) return merged.rows;
289
+ const rows = mergeRows(base, nextTexts);
290
+ merged = { base, text, rows };
291
+ return rows;
263
292
  };
264
293
 
265
294
  module.exports = {
@@ -4,12 +4,9 @@ const fs = require('node:fs');
4
4
  const os = require('node:os');
5
5
  const path = require('node:path');
6
6
 
7
- const utilities = require('./utilities.js');
8
- const { oneLine } = utilities;
9
- const files = require('./files.js');
10
- const { shortAge } = files;
11
- const worktree = require('./git-worktree.js');
12
- const { requireOk, runGit, command, gitOp } = worktree;
7
+ const { oneLine, trimText } = require('../common/utilities.js');
8
+ const { shortAge } = require('../common/format.js');
9
+ const { requireOk, runGit, command, gitOp } = require('./worktree.js');
13
10
 
14
11
  const BRANCH_FORMAT = [
15
12
  '%(HEAD)',
@@ -43,9 +40,7 @@ const AUTOSQUASH_ARGS = [
43
40
  '--interactive',
44
41
  '--autosquash',
45
42
  ];
46
- const REWORD_TODO = path.join(__dirname, 'git-reword-todo.js');
47
-
48
- const trimText = (value) => `${value ?? ''}`.trim();
43
+ const REWORD_TODO = path.join(__dirname, 'reword-todo.js');
49
44
 
50
45
  const required = (value, message) => {
51
46
  const text = trimText(value);
@@ -60,9 +55,9 @@ const messageBody = (message) => {
60
55
  return text.endsWith('\n') ? text : `${text}\n`;
61
56
  };
62
57
 
63
- const commitMessage = (top, sha) => {
58
+ const commitMessage = async (top, sha) => {
64
59
  const spec = trimText(sha) || 'HEAD';
65
- const result = runGit(['log', '-1', '--format=%B', spec], top);
60
+ const result = await runGit(['log', '-1', '--format=%B', spec], top);
66
61
  return result.status === 0 ? result.stdout.trimEnd() : '';
67
62
  };
68
63
 
@@ -112,7 +107,7 @@ const statCount = (stat, word) => {
112
107
  return match ? parseInt(match[1], 10) : 0;
113
108
  };
114
109
 
115
- const listCommits = (top) => {
110
+ const listCommits = async (top) => {
116
111
  const args = [
117
112
  'log',
118
113
  '-z',
@@ -120,7 +115,7 @@ const listCommits = (top) => {
120
115
  `--format=${COMMIT_FORMAT}`,
121
116
  `--max-count=${COMMIT_LIMIT}`,
122
117
  ];
123
- const result = requireOk(runGit(args, top), 'log failed');
118
+ const result = requireOk(await runGit(args, top), 'log failed');
124
119
  const commits = [];
125
120
  for (const chunk of result.stdout.split('\0')) {
126
121
  const stat = STAT_LEAD.exec(chunk);
@@ -141,11 +136,10 @@ const parseTrack = (text) => {
141
136
  const raw = trimText(text).replace(/^\[|\]$/g, '');
142
137
  const ahead = /ahead (\d+)/.exec(raw);
143
138
  const behind = /behind (\d+)/.exec(raw);
144
- return {
145
- ahead: ahead ? Number(ahead[1]) : 0,
146
- behind: behind ? Number(behind[1]) : 0,
147
- gone: raw === 'gone',
148
- };
139
+ const aheadCount = ahead ? Number(ahead[1]) : 0;
140
+ const behindCount = behind ? Number(behind[1]) : 0;
141
+ const gone = raw === 'gone';
142
+ return { ahead: aheadCount, behind: behindCount, gone };
149
143
  };
150
144
 
151
145
  const parseBranchLine = (line) => {
@@ -163,31 +157,33 @@ const parseBranchLine = (line) => {
163
157
  };
164
158
  };
165
159
 
166
- const defaultBranch = (top, names) => {
167
- const result = runGit(['symbolic-ref', '--short', ORIGIN_HEAD], top);
160
+ const defaultBranch = async (top, names) => {
161
+ const args = ['symbolic-ref', '--short', ORIGIN_HEAD];
162
+ const result = await runGit(args, top);
168
163
  const ref = result.status === 0 ? result.stdout.trim() : '';
169
- const remote = ref.startsWith(ORIGIN_PREFIX)
170
- ? ref.slice(ORIGIN_PREFIX.length)
171
- : ref;
164
+ let remote = ref;
165
+ if (ref.startsWith(ORIGIN_PREFIX)) {
166
+ remote = ref.slice(ORIGIN_PREFIX.length);
167
+ }
172
168
  if (names.has(remote)) return remote;
173
169
  return DEFAULT_BRANCHES.find((name) => names.has(name)) ?? '';
174
170
  };
175
171
 
176
- const listBranches = (top) => {
172
+ const listBranches = async (top) => {
177
173
  const args = ['for-each-ref', `--format=${BRANCH_FORMAT}`, 'refs/heads'];
178
- const result = requireOk(runGit(args, top), 'branch failed');
174
+ const result = requireOk(await runGit(args, top), 'branch failed');
179
175
  const lines = result.stdout.split('\n').filter(Boolean);
180
176
  const branches = lines.map((line) => parseBranchLine(line)).filter(Boolean);
181
177
  const names = new Set(branches.map((entry) => entry.name));
182
- const primary = defaultBranch(top, names);
178
+ const primary = await defaultBranch(top, names);
183
179
  return branches.map((entry) => ({
184
180
  ...entry,
185
181
  isDefault: entry.name === primary,
186
182
  }));
187
183
  };
188
184
 
189
- const hasStaged = (top) => {
190
- const result = runGit(['diff', '--cached', '--quiet'], top);
185
+ const hasStaged = async (top) => {
186
+ const result = await runGit(['diff', '--cached', '--quiet'], top);
191
187
  return result.status === 1;
192
188
  };
193
189
 
@@ -243,6 +239,24 @@ const commitOp = gitOp(function* (kind, message) {
243
239
  requireOk(result);
244
240
  });
245
241
 
242
+ const updateOp = gitOp(function* (sha) {
243
+ const commit = yield* resolveCommit(required(sha, 'empty commit'));
244
+ const head = yield* resolveCommit('HEAD');
245
+ if (commit === head) {
246
+ const result = yield command(['commit', '--amend', '--no-edit']);
247
+ return void requireOk(result);
248
+ }
249
+ const fixed = yield command(['commit', '--fixup', commit]);
250
+ requireOk(fixed);
251
+ const range = yield* rebaseRange(commit);
252
+ try {
253
+ yield* rebase([...AUTOSQUASH_ARGS, ...range]);
254
+ } catch (error) {
255
+ yield command(['reset', '--soft', 'HEAD~1']);
256
+ throw error;
257
+ }
258
+ });
259
+
246
260
  const dropCommitOp = gitOp(function* (sha) {
247
261
  const target = yield* resolveCommit(required(sha, 'empty commit'));
248
262
  const parent = yield command(['rev-parse', '--verify', `${target}^`]);
@@ -363,24 +377,15 @@ module.exports = {
363
377
  listCommits,
364
378
  listBranches,
365
379
  hasStaged,
366
- checkoutBranch: checkoutOp.sync,
367
- checkoutBranchAsync: checkoutOp.async,
368
- createBranch: createOp.sync,
369
- createBranchAsync: createOp.async,
370
- rebaseBranch: rebaseOp.sync,
371
- rebaseBranchAsync: rebaseOp.async,
372
- dropBranch: dropBranchOp.sync,
373
- dropBranchAsync: dropBranchOp.async,
374
- dropCommit: dropCommitOp.sync,
375
- dropCommitAsync: dropCommitOp.async,
376
- applyFixup: fixupOp.sync,
377
- applyFixupAsync: fixupOp.async,
378
- rewordCommit: rewordOp.sync,
379
- rewordCommitAsync: rewordOp.async,
380
- pullChanges: pullOp.sync,
381
- pullChangesAsync: pullOp.async,
382
- pushChanges: pushOp.sync,
383
- pushChangesAsync: pushOp.async,
384
- commitChanges: commitOp.sync,
385
- commitChangesAsync: commitOp.async,
380
+ checkoutBranch: checkoutOp,
381
+ createBranch: createOp,
382
+ rebaseBranch: rebaseOp,
383
+ dropBranch: dropBranchOp,
384
+ dropCommit: dropCommitOp,
385
+ applyFixup: fixupOp,
386
+ rewordCommit: rewordOp,
387
+ pullChanges: pullOp,
388
+ pushChanges: pushOp,
389
+ commitChanges: commitOp,
390
+ updateCommit: updateOp,
386
391
  };