reslop 0.2.0 → 0.2.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.
@@ -0,0 +1,270 @@
1
+ 'use strict';
2
+
3
+ const { TODO_FILE } = require('./files.js');
4
+
5
+ const flatText = (text) => `${text ?? ''}`.trim().replace(/\n/g, ' ');
6
+
7
+ const snapTodo = (todo) => ({
8
+ file: todo.file ?? '',
9
+ text: flatText(todo.text),
10
+ done: todo.done === true,
11
+ });
12
+
13
+ const snapList = (todos) => {
14
+ const list = [];
15
+ for (const todo of todos ?? []) {
16
+ const snap = snapTodo(todo);
17
+ if (!snap.text) continue;
18
+ list.push(snap);
19
+ }
20
+ return list;
21
+ };
22
+
23
+ const copyMap = (map) => {
24
+ const next = new Map();
25
+ if (!map) return next;
26
+ for (const [key, note] of map) next.set(key, { ...note });
27
+ return next;
28
+ };
29
+
30
+ const snapshotSide = (store) => ({
31
+ status: store.status,
32
+ todos: snapList(store.todos),
33
+ feedback: copyMap(store.feedback),
34
+ code: copyMap(store.code),
35
+ });
36
+
37
+ const captureBaseline = (store) => {
38
+ store.baseline = snapshotSide(store);
39
+ };
40
+
41
+ const claim = (list, used, text, read) => {
42
+ for (let i = 0; i < list.length; i++) {
43
+ if (used[i]) continue;
44
+ if (read(list[i]) !== text) continue;
45
+ used[i] = true;
46
+ return i;
47
+ }
48
+ return -1;
49
+ };
50
+
51
+ const pickDone = (baseDone, ourDone, theirDone) => {
52
+ if (ourDone === theirDone) return ourDone;
53
+ if (theirDone === baseDone) return ourDone;
54
+ if (ourDone === baseDone) return theirDone;
55
+ return ourDone;
56
+ };
57
+
58
+ const withDone = (todo, done) => {
59
+ if (todo.done === done) return todo;
60
+ return { id: todo.id, file: todo.file, text: todo.text, done };
61
+ };
62
+
63
+ const liveText = (todo) => flatText(todo.text);
64
+
65
+ const snapText = (snap) => snap.text;
66
+
67
+ const flags = (n) => new Array(n).fill(false);
68
+
69
+ const adoptTodo = (store, snap) => {
70
+ const id = store.nextTodoId;
71
+ store.nextTodoId += 1;
72
+ const file = snap.file || TODO_FILE;
73
+ return { id, file, text: snap.text, done: snap.done };
74
+ };
75
+
76
+ const findMerged = (merged, alias, text) => {
77
+ const linked = alias.get(text);
78
+ if (linked) {
79
+ const at = merged.indexOf(linked);
80
+ if (at >= 0) return at;
81
+ }
82
+ for (let i = 0; i < merged.length; i++) {
83
+ if (liveText(merged[i]) === text) return i;
84
+ }
85
+ return -1;
86
+ };
87
+
88
+ const insertTheir = (store, merged, theirs, theirKeep, alias) => {
89
+ for (let i = 0; i < theirs.length; i++) {
90
+ if (!theirKeep[i]) continue;
91
+ let at = merged.length;
92
+ let placed = false;
93
+ for (let j = i - 1; j >= 0 && !placed; j--) {
94
+ const prev = findMerged(merged, alias, theirs[j].text);
95
+ if (prev < 0) continue;
96
+ at = prev + 1;
97
+ placed = true;
98
+ }
99
+ for (let j = i + 1; j < theirs.length && !placed; j++) {
100
+ const next = findMerged(merged, alias, theirs[j].text);
101
+ if (next < 0) continue;
102
+ at = next;
103
+ placed = true;
104
+ }
105
+ const item = adoptTodo(store, theirs[i]);
106
+ merged.splice(at, 0, item);
107
+ alias.set(theirs[i].text, item);
108
+ }
109
+ return merged;
110
+ };
111
+
112
+ const keepOurs = (ours, kept, alias, index, text) => {
113
+ kept[index] = true;
114
+ alias.set(text, ours[index]);
115
+ };
116
+
117
+ const mergeBaseTodo = (
118
+ base,
119
+ ours,
120
+ theirs,
121
+ used,
122
+ kept,
123
+ dropped,
124
+ theirKeep,
125
+ alias,
126
+ ) => {
127
+ const text = base.text;
128
+ const oi = claim(ours, used.ours, text, liveText);
129
+ const ti = claim(theirs, used.theirs, text, snapText);
130
+ if (oi >= 0 && ti >= 0) {
131
+ const done = pickDone(base.done, ours[oi].done === true, theirs[ti].done);
132
+ ours[oi] = withDone(ours[oi], done);
133
+ keepOurs(ours, kept, alias, oi, text);
134
+ return;
135
+ }
136
+ if (oi >= 0) {
137
+ if (ours[oi].done === base.done) {
138
+ dropped[oi] = true;
139
+ return;
140
+ }
141
+ keepOurs(ours, kept, alias, oi, text);
142
+ return;
143
+ }
144
+ if (ti >= 0 && theirs[ti].done !== base.done) theirKeep[ti] = true;
145
+ };
146
+
147
+ const replaceTodo = (todo, snap) => ({
148
+ id: todo.id,
149
+ file: todo.file,
150
+ text: snap.text,
151
+ done: snap.done,
152
+ });
153
+
154
+ const fillGaps = (merged, gaps, theirs, theirKeep, alias) => {
155
+ let filled = 0;
156
+ for (let i = 0; i < theirs.length && filled < gaps.length; i++) {
157
+ if (!theirKeep[i]) continue;
158
+ const gap = gaps[filled];
159
+ const item = replaceTodo(gap.todo, theirs[i]);
160
+ merged.splice(gap.at, 0, item);
161
+ alias.set(theirs[i].text, item);
162
+ theirKeep[i] = false;
163
+ filled += 1;
164
+ }
165
+ };
166
+
167
+ const mergeFreshTodo = (ours, theirs, used, kept, alias, index) => {
168
+ const text = liveText(ours[index]);
169
+ const ti = claim(theirs, used.theirs, text, snapText);
170
+ if (ti < 0) {
171
+ kept[index] = true;
172
+ return;
173
+ }
174
+ const done = ours[index].done === true || theirs[ti].done === true;
175
+ ours[index] = withDone(ours[index], done);
176
+ keepOurs(ours, kept, alias, index, text);
177
+ };
178
+
179
+ const mergeTodos = (store, base, theirs) => {
180
+ const ours = [];
181
+ for (const todo of store.todos) {
182
+ if (!liveText(todo)) continue;
183
+ ours.push(todo);
184
+ }
185
+ const used = { ours: flags(ours.length), theirs: flags(theirs.length) };
186
+ const kept = flags(ours.length);
187
+ const dropped = flags(ours.length);
188
+ const theirKeep = flags(theirs.length);
189
+ const alias = new Map();
190
+ for (const item of base) {
191
+ mergeBaseTodo(item, ours, theirs, used, kept, dropped, theirKeep, alias);
192
+ }
193
+ for (let i = 0; i < ours.length; i++) {
194
+ if (used.ours[i]) continue;
195
+ mergeFreshTodo(ours, theirs, used, kept, alias, i);
196
+ }
197
+ for (let i = 0; i < theirs.length; i++) {
198
+ if (!used.theirs[i]) theirKeep[i] = true;
199
+ }
200
+ const merged = [];
201
+ const gaps = [];
202
+ let cursor = 0;
203
+ for (let i = 0; i < ours.length; i++) {
204
+ if (kept[i]) {
205
+ merged.push(ours[i]);
206
+ cursor += 1;
207
+ continue;
208
+ }
209
+ if (!dropped[i]) continue;
210
+ gaps.push({ at: cursor, todo: ours[i] });
211
+ cursor += 1;
212
+ }
213
+ fillGaps(merged, gaps, theirs, theirKeep, alias);
214
+ store.todos = insertTheir(store, merged, theirs, theirKeep, alias);
215
+ };
216
+
217
+ const sameNote = (left, right) =>
218
+ left.text === right.text && !!left.done === !!right.done;
219
+
220
+ const cloneNote = (note, done) => {
221
+ if (!!note.done === done) return note;
222
+ return { ...note, done };
223
+ };
224
+
225
+ const pickNote = (ours, base, theirs) => {
226
+ if (ours && theirs && ours.text === theirs.text) {
227
+ const baseDone = base ? base.done === true : ours.done === true;
228
+ const done = pickDone(baseDone, ours.done === true, theirs.done === true);
229
+ return cloneNote(ours, done);
230
+ }
231
+ if (!base) return ours || theirs || null;
232
+ if (!ours) {
233
+ if (!theirs || sameNote(theirs, base)) return null;
234
+ return theirs;
235
+ }
236
+ if (!theirs) {
237
+ if (sameNote(ours, base)) return null;
238
+ return ours;
239
+ }
240
+ if (sameNote(theirs, base)) return ours;
241
+ if (sameNote(ours, base)) return theirs;
242
+ return ours;
243
+ };
244
+
245
+ const mergeMap = (ours, base, theirs) => {
246
+ const keys = new Set();
247
+ for (const key of ours.keys()) keys.add(key);
248
+ for (const key of base.keys()) keys.add(key);
249
+ for (const key of theirs.keys()) keys.add(key);
250
+ for (const key of keys) {
251
+ const next = pickNote(ours.get(key), base.get(key), theirs.get(key));
252
+ if (!next) ours.delete(key);
253
+ else ours.set(key, next);
254
+ }
255
+ };
256
+
257
+ const mergeReview = (store, disk) => {
258
+ const base = store.baseline;
259
+ const baseTodos = base ? base.todos : [];
260
+ const baseStatus = base ? base.status : store.status;
261
+ mergeTodos(store, baseTodos, snapList(disk.todos));
262
+ const feedback = base ? base.feedback : new Map();
263
+ const code = base ? base.code : new Map();
264
+ mergeMap(store.feedback, feedback, disk.feedback);
265
+ mergeMap(store.code, code, disk.code);
266
+ if (store.status === baseStatus && disk.status) store.status = disk.status;
267
+ store.baseline = snapshotSide(disk);
268
+ };
269
+
270
+ module.exports = { captureBaseline, mergeReview };
package/lib/review.js CHANGED
@@ -4,6 +4,7 @@ const fs = require('node:fs');
4
4
  const path = require('node:path');
5
5
 
6
6
  const { TODO_FILE, REVIEW_DIR } = require('./files.js');
7
+ const { captureBaseline, mergeReview } = require('./review-merge.js');
7
8
 
8
9
  const createStore = (reviewPath, templates = []) => ({
9
10
  reviewPath,
@@ -14,6 +15,7 @@ const createStore = (reviewPath, templates = []) => ({
14
15
  todos: [],
15
16
  templates: [...templates],
16
17
  status: 'editing',
18
+ baseline: null,
17
19
  });
18
20
 
19
21
  const noteCounts = (store) => {
@@ -119,6 +121,15 @@ const setTodoText = (store, id, text) => {
119
121
  store.dirty = true;
120
122
  };
121
123
 
124
+ const setTodoDone = (store, id, done) => {
125
+ const i = store.todos.findIndex((todo) => todo.id === id);
126
+ if (i < 0) return;
127
+ const prev = store.todos[i];
128
+ if (prev.done === done) return;
129
+ store.todos[i] = { id: prev.id, file: prev.file, text: prev.text, done };
130
+ store.dirty = true;
131
+ };
132
+
122
133
  const checkLabel = (text, done) => {
123
134
  const mark = done ? 'x' : ' ';
124
135
  return `[${mark}] ${text}`;
@@ -222,12 +233,13 @@ const SECTION_LINE = {
222
233
  };
223
234
 
224
235
  const AGENT_INSTRUCTIONS = [
225
- 'Execute reviews with `status: ready` or `status: partial`.',
226
- 'Do not start `editing` or `done` review files.',
227
- 'Work through unchecked items. After finishing one, mark it `[x]`.',
228
- 'For `code` items, replace added lines at that location with the fence.',
229
- 'Set `status` to `partial` if some remain, or `done` if all are `[x]`.',
230
- 'Do not redo items already marked `[x]`.',
236
+ '- Execute reviews with `status` `ready, partial, editing`',
237
+ '- Do not start or change `done` review files',
238
+ '- Work through unchecked items. After finishing one, mark it `[x]`',
239
+ '- For `code` items, replace added lines at that location',
240
+ '- If not `editing`, set `status` to `partial` if some remain, ' +
241
+ 'or `done` if all are `[x]`',
242
+ '- Do not redo items already marked `[x]`',
231
243
  ].join('\n');
232
244
 
233
245
  const toInt = (text) => parseInt(text, 10);
@@ -622,6 +634,7 @@ const parseReview = (text, reviewPath, templates = []) => {
622
634
  flushPendingCode(builder, pendingCode);
623
635
  flushPendingFeedback(builder, pending);
624
636
  builder.dirty = false;
637
+ captureBaseline(builder);
625
638
  return builder;
626
639
  };
627
640
 
@@ -733,8 +746,25 @@ const loadTemplates = (dir) => {
733
746
  }
734
747
  };
735
748
 
749
+ const readReviewText = (reviewPath) => {
750
+ try {
751
+ return fs.readFileSync(reviewPath, 'utf8');
752
+ } catch (error) {
753
+ if (error.code === 'ENOENT') return '';
754
+ throw error;
755
+ }
756
+ };
757
+
758
+ const mergeDiskReview = (notes) => {
759
+ const text = readReviewText(notes.reviewPath);
760
+ if (!text.startsWith('---')) return;
761
+ const disk = parseReview(text, notes.reviewPath, notes.templates);
762
+ mergeReview(notes, disk);
763
+ };
764
+
736
765
  const flushReview = (notes, force = false) => {
737
766
  if (!notes || !notes.dirty) return false;
767
+ mergeDiskReview(notes);
738
768
  if (!hasNotes(notes) && !force) {
739
769
  notes.dirty = false;
740
770
  return false;
@@ -746,6 +776,7 @@ const flushReview = (notes, force = false) => {
746
776
  const templatesFile = path.join(top, REVIEW_DIR, TEMPLATES_FILE);
747
777
  const body = `${JSON.stringify(notes.templates, null, 2)}\n`;
748
778
  fs.writeFileSync(templatesFile, body, 'utf8');
779
+ captureBaseline(notes);
749
780
  notes.dirty = false;
750
781
  return true;
751
782
  };
@@ -770,6 +801,7 @@ module.exports = {
770
801
  addTodo,
771
802
  removeTodo,
772
803
  setTodoText,
804
+ setTodoDone,
773
805
  checkLabel,
774
806
  feedbackKey,
775
807
  applyImportedNotes,
@@ -7,6 +7,7 @@ const ACTIONS = {
7
7
  commit: ['c'],
8
8
  prev: ['left', 'p', 'k'],
9
9
  next: ['right', 'n', 'j'],
10
+ check: ['x', ' '],
10
11
  layout: ['m'],
11
12
  feedback: ['f'],
12
13
  todo: ['t'],
@@ -28,6 +29,8 @@ const BRANCH_ACTIONS = {
28
29
 
29
30
  const COMMIT_ACTIONS = {
30
31
  amend: ['a'],
32
+ apply: ['a'],
33
+ reword: ['r'],
31
34
  fixup: ['f'],
32
35
  drop: ['d'],
33
36
  pull: ['p'],
@@ -41,6 +44,7 @@ const ACTION_HOT = {
41
44
 
42
45
  const ACTION_LABEL = {
43
46
  revert: 'drop',
47
+ check: 'x',
44
48
  layout: 'mode',
45
49
  code: 'edit',
46
50
  newBranch: 'new',
@@ -49,11 +53,20 @@ const ACTION_LABEL = {
49
53
  next: '→',
50
54
  };
51
55
 
52
- const DIFF_DISABLED = ['commit', 'branch', 'pull', 'push', 'file', 'diff'];
56
+ const DIFF_DISABLED = [
57
+ 'check',
58
+ 'commit',
59
+ 'branch',
60
+ 'pull',
61
+ 'push',
62
+ 'file',
63
+ 'diff',
64
+ ];
53
65
  const UNIT_DISABLED = [...DIFF_DISABLED, 'layout'];
54
66
  const FILES_DISABLED = ['layout', 'feedback', 'code'];
55
67
  const FILES_HIDDEN = [
56
68
  ...FILES_DISABLED,
69
+ 'check',
57
70
  'prev',
58
71
  'next',
59
72
  'commit',
@@ -86,6 +99,7 @@ const BRANCHES_DISABLED = [
86
99
  'feedback',
87
100
  'todo',
88
101
  'code',
102
+ 'check',
89
103
  'branch',
90
104
  'file',
91
105
  'diff',
@@ -100,6 +114,7 @@ const COMMITS_DISABLED = [
100
114
  'feedback',
101
115
  'todo',
102
116
  'code',
117
+ 'check',
103
118
  'branch',
104
119
  'file',
105
120
  'diff',
@@ -124,13 +139,17 @@ const branchGitDisabled = (item) => {
124
139
  return off;
125
140
  };
126
141
 
142
+ const isFixupCommit = (item) =>
143
+ `${item && item.subject ? item.subject : ''}`.startsWith('fixup!');
144
+
127
145
  const commitWriteDisabled = (item) => {
128
- if (!item || !item.sha) return ['amend', 'fixup', 'drop'];
146
+ if (!item || !item.sha) return ['amend', 'apply', 'reword', 'fixup', 'drop'];
129
147
  return [];
130
148
  };
131
149
 
132
150
  const commitGitDisabled = (item) => {
133
- const off = commitWriteDisabled(item);
151
+ const off = [...commitWriteDisabled(item)];
152
+ if (!isFixupCommit(item)) off.push('apply');
134
153
  if (item && item.canCommit === true) return off;
135
154
  return [...off, 'commit', 'fixup'];
136
155
  };
@@ -149,6 +168,10 @@ const disabledActions = (pane, item) => {
149
168
  return [...DIFF_DISABLED, ...diffGitDisabled(item)];
150
169
  };
151
170
 
171
+ const HIDDEN_KEYS = {
172
+ l: 'theme',
173
+ };
174
+
152
175
  const SCROLL = {
153
176
  up: 'scrollUp',
154
177
  down: 'scrollDown',
@@ -194,6 +217,9 @@ for (const id of ACTION_IDS) {
194
217
  }
195
218
 
196
219
  for (const key of Object.keys(SCROLL)) KEY_TO_ACTION[key] = SCROLL[key];
220
+ for (const key of Object.keys(HIDDEN_KEYS)) {
221
+ KEY_TO_ACTION[key] = HIDDEN_KEYS[key];
222
+ }
197
223
 
198
224
  const actionFromKey = (key, pane, fileScope) => {
199
225
  const listPane =
@@ -318,6 +344,7 @@ module.exports = {
318
344
  diffGitDisabled,
319
345
  branchGitDisabled,
320
346
  commitGitDisabled,
347
+ isFixupCommit,
321
348
  actionFromKey,
322
349
  actionKeys,
323
350
  actionHot,
@@ -3,12 +3,22 @@
3
3
  const { stateView } = require('./accessors.js');
4
4
 
5
5
  const { isReadOnlyOrigin } = require('../files.js');
6
+ const { isFixupCommit } = require('./actions.js');
6
7
  const { pickRepoWrite } = require('./ops.js');
7
8
 
8
9
  const COMMIT_STATUS = {
9
10
  commit: 'committed',
10
11
  amend: 'amended',
11
12
  fixup: 'fixup',
13
+ reword: 'reworded',
14
+ };
15
+
16
+ const isInPlaceCommit = (kind) => kind === 'amend' || kind === 'reword';
17
+
18
+ const fixupMessage = (entry) => {
19
+ const subject = `${entry && entry.subject ? entry.subject : ''}`.trim();
20
+ if (!subject) return 'fixup!';
21
+ return `fixup! ${subject}`;
12
22
  };
13
23
 
14
24
  const canCommit = (ui) => {
@@ -100,21 +110,33 @@ const onCommitMove = (api, delta) => {
100
110
 
101
111
  const startGitCommit = (api, kind) => {
102
112
  const ui = api.ui;
103
- if (kind !== 'amend' && !hasStagedChanges(ui)) {
113
+ if (!isInPlaceCommit(kind) && !hasStagedChanges(ui)) {
104
114
  ui.mode = 'review';
105
115
  ui.composer.state.commitKind = null;
106
116
  ui.status = 'nothing to commit';
107
117
  return;
108
118
  }
109
- ui.composer.state.commitKind = kind;
110
119
  let text = '';
111
120
  if (kind === 'fixup') {
112
- const entry = selectedCommit(api);
113
- text = entry ? entry.shortSha || entry.sha : 'HEAD';
121
+ text = fixupMessage(selectedCommit(api));
122
+ }
123
+ if (kind === 'amend') {
124
+ const headAt = api.state.commits.findIndex((entry) => entry.head);
125
+ api.ui.nav.commitCursor = headAt < 0 ? 0 : headAt;
126
+ if (ui.repo.lastMessage) {
127
+ text = ui.repo.lastMessage(ui.top) ?? '';
128
+ }
114
129
  }
115
- if (kind === 'amend' && ui.repo.lastMessage) {
116
- text = ui.repo.lastMessage(ui.top) ?? '';
130
+ if (kind === 'reword') {
131
+ const entry = selectedCommit(api);
132
+ if (!entry || !entry.sha) return;
133
+ if (typeof ui.repo.commitMessage === 'function') {
134
+ text = ui.repo.commitMessage(ui.top, entry.sha) ?? '';
135
+ } else {
136
+ text = `${entry.subject ?? ''}`;
137
+ }
117
138
  }
139
+ ui.composer.state.commitKind = kind;
118
140
  ui.composer.openCompose('commit', text);
119
141
  };
120
142
 
@@ -130,6 +152,43 @@ const onAmend = (api) => {
130
152
  startGitCommit(api, 'amend');
131
153
  };
132
154
 
155
+ const onReword = (api) => {
156
+ if (api.ui.nav.pane !== 'commits') return;
157
+ if (!canCommit(api.ui)) return;
158
+ startGitCommit(api, 'reword');
159
+ };
160
+
161
+ const applyFixupNow = (api) => {
162
+ const entry = selectedCommit(api);
163
+ if (!entry || !entry.sha) return;
164
+ const done = 'applied';
165
+ const repo = api.ui.repo;
166
+ if (!repo.applyFixup) {
167
+ api.ui.status = 'read only';
168
+ return;
169
+ }
170
+ const picked = pickRepoWrite(repo, 'applyFixup', api.ui.uiOpen);
171
+ if (picked.async) {
172
+ const run = (top) => picked.write(top, entry.sha);
173
+ const after = () => refreshAfterWrite(api, { doneStatus: done });
174
+ return void api.ui.ops.runBusy('applying', done, run, after);
175
+ }
176
+ const ok = api.ui.ops.runRepo(done, (top) => {
177
+ repo.applyFixup(top, entry.sha);
178
+ });
179
+ if (!ok) return;
180
+ refreshAfterWrite(api);
181
+ if (!api.ui.status) api.ui.status = done;
182
+ };
183
+
184
+ const onApply = (api) => {
185
+ if (api.ui.nav.pane !== 'commits') return;
186
+ if (!canCommit(api.ui)) return;
187
+ const entry = selectedCommit(api);
188
+ if (isFixupCommit(entry)) return void applyFixupNow(api);
189
+ startGitCommit(api, 'amend');
190
+ };
191
+
133
192
  const onFixup = (api) => {
134
193
  if (api.ui.nav.pane !== 'commits') return;
135
194
  if (!canCommit(api.ui)) return;
@@ -166,20 +225,52 @@ const finishSyncCommit = (api, kind, message, done) => {
166
225
  }
167
226
  };
168
227
 
228
+ const finishReword = (api, message) => {
229
+ const ui = api.ui;
230
+ const entry = selectedCommit(api);
231
+ if (!entry || !entry.sha) {
232
+ ui.status = 'read only';
233
+ return;
234
+ }
235
+ const done = COMMIT_STATUS.reword;
236
+ const repo = ui.repo;
237
+ if (!repo.reword) {
238
+ ui.status = 'read only';
239
+ return;
240
+ }
241
+ const picked = pickRepoWrite(repo, 'reword', ui.uiOpen);
242
+ if (picked.async) {
243
+ ui.composer.closeCompose();
244
+ const run = (top) => picked.write(top, entry.sha, message);
245
+ const after = () => refreshAfterWrite(api, { doneStatus: done });
246
+ return void ui.ops.runBusy('rewording', done, run, after);
247
+ }
248
+ try {
249
+ repo.reword(ui.top, entry.sha, message);
250
+ } catch (error) {
251
+ ui.status = error.message;
252
+ return;
253
+ }
254
+ ui.composer.closeCompose();
255
+ refreshAfterWrite(api);
256
+ if (!ui.status) ui.status = done;
257
+ };
258
+
169
259
  const finishGitCommit = (api) => {
170
260
  const ui = api.ui;
171
261
  const kind = ui.composer.state.commitKind;
172
262
  const editor = ui.composer.state.editor;
173
263
  const message = editor ? editor.text : '';
174
- if (kind !== 'fixup' && !message.trim()) {
264
+ if (!message.trim()) {
175
265
  ui.status = 'empty commit message';
176
266
  return;
177
267
  }
178
- if (kind !== 'amend' && !hasStagedChanges(ui)) {
268
+ if (!isInPlaceCommit(kind) && !hasStagedChanges(ui)) {
179
269
  ui.composer.closeCompose();
180
270
  ui.status = 'nothing to commit';
181
271
  return;
182
272
  }
273
+ if (kind === 'reword') return void finishReword(api, message);
183
274
  const done = COMMIT_STATUS[kind] ?? 'committed';
184
275
  const uiOpen = ui.uiOpen;
185
276
  const picked = pickRepoWrite(ui.repo, 'commit', uiOpen);
@@ -259,6 +350,8 @@ const createCommitController = (ui) => {
259
350
  canCommit: () => canCommit(ui),
260
351
  onCommit: () => onCommit(api),
261
352
  onAmend: () => onAmend(api),
353
+ onReword: () => onReword(api),
354
+ onApply: () => onApply(api),
262
355
  onFixup: () => onFixup(api),
263
356
  onCommitMove: (delta) => onCommitMove(api, delta),
264
357
  cancelCommit: () => cancelCommit(api),