reslop 0.1.3 → 0.1.5
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 +19 -1
- package/README.md +40 -38
- package/lib/api.js +3 -0
- package/lib/cli.js +31 -8
- package/lib/clipboard.js +11 -11
- package/lib/diff.js +71 -2
- package/lib/git.js +56 -16
- package/lib/gitlab.js +520 -0
- package/lib/keys.js +27 -6
- package/lib/render.js +154 -59
- package/lib/review.js +145 -3
- package/lib/select.js +7 -12
- package/lib/session.js +256 -44
- package/lib/source.js +5 -2
- package/lib/sys.js +59 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased][unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.1.5][] - 2026-09-14
|
|
6
|
+
|
|
7
|
+
- Added `r` to reload diffs from disk
|
|
8
|
+
- Added `e` to edit added lines in place
|
|
9
|
+
- Added `d` on quit to discard a new review file
|
|
10
|
+
- Added `c` to commit, amend, or fixup staged changes
|
|
11
|
+
- Reassign hotkeys to free needed keys for new operations
|
|
12
|
+
|
|
13
|
+
## [0.1.4][] - 2026-09-13
|
|
14
|
+
|
|
15
|
+
- Support a GitLab MR by URL and import review comments
|
|
16
|
+
- Support Windows: npm.cmd, clip, terminal resize, installer
|
|
17
|
+
- Added `-r` read-only mode that blocks stage, unstage, revert, npm
|
|
18
|
+
- Fixed `npm test` so it finds tests on Windows, macOS, and Node 18
|
|
19
|
+
- Added Testing CI on Linux, macOS, and Windows
|
|
20
|
+
|
|
5
21
|
## [0.1.3][] - 2026-09-12
|
|
6
22
|
|
|
7
23
|
- Showed added/removed and staged/total counts in the file list
|
|
@@ -33,7 +49,9 @@
|
|
|
33
49
|
- Feedback and todos that produce a repair plan for an agent
|
|
34
50
|
- Intra-line highlighting in unified, mixed, and side-by-side layouts
|
|
35
51
|
|
|
36
|
-
[unreleased]: https://github.com/tshemsedinov/reslop/compare/v0.1.
|
|
52
|
+
[unreleased]: https://github.com/tshemsedinov/reslop/compare/v0.1.5...HEAD
|
|
53
|
+
[0.1.5]: https://github.com/tshemsedinov/reslop/compare/v0.1.4...v0.1.5
|
|
54
|
+
[0.1.4]: https://github.com/tshemsedinov/reslop/compare/v0.1.3...v0.1.4
|
|
37
55
|
[0.1.3]: https://github.com/tshemsedinov/reslop/compare/v0.1.2...v0.1.3
|
|
38
56
|
[0.1.2]: https://github.com/tshemsedinov/reslop/compare/v0.1.1...v0.1.2
|
|
39
57
|
[0.1.1]: https://github.com/tshemsedinov/reslop/compare/v0.1.0...v0.1.1
|
package/README.md
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
# reslop — Review diff and plan changes
|
|
1
|
+
# 👁️ reslop — Review diff and plan changes
|
|
2
|
+
|
|
3
|
+
[](https://github.com/tshemsedinov/reslop/actions?query=workflow%3A%22Testing+CI%22+branch%3Amain)
|
|
4
|
+
[](https://snyk.io/test/github/tshemsedinov/reslop)
|
|
5
|
+
[](https://badge.fury.io/js/reslop)
|
|
6
|
+
[](https://www.npmjs.com/package/reslop)
|
|
7
|
+
[](https://www.npmjs.com/package/reslop)
|
|
8
|
+
[](https://github.com/tshemsedinov/reslop/blob/main/LICENSE)
|
|
2
9
|
|
|
3
10
|
> Turn generated changes into owned changes
|
|
4
11
|
|
|
@@ -9,35 +16,29 @@ for the agent.
|
|
|
9
16
|
Review → Plan → Repair → Verify
|
|
10
17
|
```
|
|
11
18
|
|
|
12
|
-
- Review uncommitted git diffs, a given commit, or a
|
|
19
|
+
- Review uncommitted git diffs, a given commit, a GitHub PR, or a GitLab MR.
|
|
13
20
|
- Stage, unstage, or revert each contiguous block of diff lines.
|
|
14
|
-
- Leave feedback and
|
|
15
|
-
- Import GitHub
|
|
21
|
+
- Leave feedback, todos, and in-place code proposals that become a repair plan.
|
|
22
|
+
- Import GitHub PR and GitLab MR review comments into the local plan for AI.
|
|
16
23
|
- Review each npm dependency once across `package.json` and the lockfile.
|
|
17
24
|
- Propose unused removals, npm audit fixes, and outdated updates as diffs.
|
|
18
25
|
|
|
19
|
-
Commit and pull request review is read-only. Applying a dependency
|
|
20
|
-
proposal stages the files and runs `npm i` or `npm uninstall`.
|
|
21
|
-
|
|
22
|
-
Intra-line highlighting paints the words that actually changed in a
|
|
23
|
-
stronger red/green. A close edit inside one word still marks only
|
|
24
|
-
those characters.
|
|
25
|
-
|
|
26
26
|
## Install
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
29
|
npm i -g reslop
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
Works on Linux, macOS, and Windows. On Windows use Windows Terminal.
|
|
33
|
+
|
|
32
34
|
## Usage
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
reslop
|
|
36
|
-
reslop -
|
|
37
|
-
reslop
|
|
38
|
-
reslop
|
|
39
|
-
reslop
|
|
40
|
-
```
|
|
36
|
+
- `reslop` uncommitted diffs in this repository
|
|
37
|
+
- `reslop path/file` show only that path or file
|
|
38
|
+
- `reslop 7ac260c` show that commit (read-only)
|
|
39
|
+
- `reslop https://github.com/metarhia/metacom/pull/555` GitHub/GitLab PR/MR
|
|
40
|
+
- `reslop -n` start a new review even if the latest is still editing
|
|
41
|
+
- `reslop -r` read-only mode
|
|
41
42
|
|
|
42
43
|
Reviews go in `.review/YYYY-MM-DD-NN.md` with frontmatter `status`:
|
|
43
44
|
|
|
@@ -46,26 +47,27 @@ Reviews go in `.review/YYYY-MM-DD-NN.md` with frontmatter `status`:
|
|
|
46
47
|
- `partial`: AI started; some items remain
|
|
47
48
|
- `done`: all items marked `[x]`
|
|
48
49
|
|
|
49
|
-
##
|
|
50
|
-
|
|
51
|
-
| Key | Action
|
|
52
|
-
| ------------------- |
|
|
53
|
-
| `a` | Stage this
|
|
54
|
-
| `u` | Unstage this
|
|
55
|
-
| `
|
|
56
|
-
| `
|
|
57
|
-
| `
|
|
58
|
-
| `
|
|
59
|
-
| `
|
|
60
|
-
|
|
|
61
|
-
| `
|
|
62
|
-
|
|
|
63
|
-
|
|
|
64
|
-
|
|
|
65
|
-
| `Ctrl-
|
|
66
|
-
| `Ctrl-
|
|
67
|
-
|
|
|
68
|
-
| `
|
|
50
|
+
## Hotkeys
|
|
51
|
+
|
|
52
|
+
| Key | Action |
|
|
53
|
+
| ------------------- | ----------------------------------------------- |
|
|
54
|
+
| `a` | Stage this diff (git add) |
|
|
55
|
+
| `u` | Unstage this diff |
|
|
56
|
+
| `d` | Drop this diff to the last commit |
|
|
57
|
+
| `c` | Commit: then `c` commit / `a` amend / `f` fixup |
|
|
58
|
+
| `f` | Feedback on this diff |
|
|
59
|
+
| `t` | New todo |
|
|
60
|
+
| `e` | Edit added lines |
|
|
61
|
+
| `r` | Reload diffs from disk |
|
|
62
|
+
| `m` | Mode: unified / mixed / side-by-side |
|
|
63
|
+
| `⏎` | Open the selected file |
|
|
64
|
+
| `→` / `j` | Next remaining diff / next file |
|
|
65
|
+
| `←` / `k` | Previous remaining diff / previous file |
|
|
66
|
+
| `Ctrl-e` / `Ctrl-y` | Scroll one line down / up |
|
|
67
|
+
| `Ctrl-f` / `Ctrl-b` | Scroll page down / up |
|
|
68
|
+
| `Ctrl-d` / `Ctrl-u` | Scroll half-page down / up |
|
|
69
|
+
| `⌫` / `Del` | Remove the selected todo |
|
|
70
|
+
| `Esc` / `q` | Quit |
|
|
69
71
|
|
|
70
72
|
## License
|
|
71
73
|
|
package/lib/api.js
CHANGED
|
@@ -8,6 +8,7 @@ const render = require('./render.js');
|
|
|
8
8
|
const highlight = require('./highlight.js');
|
|
9
9
|
const detect = require('./detect.js');
|
|
10
10
|
const github = require('./github.js');
|
|
11
|
+
const gitlab = require('./gitlab.js');
|
|
11
12
|
const source = require('./source.js');
|
|
12
13
|
const { Session } = require('./session.js');
|
|
13
14
|
const cli = require('./cli.js');
|
|
@@ -25,6 +26,8 @@ module.exports = {
|
|
|
25
26
|
createGitRepo: git.createGitRepo,
|
|
26
27
|
parseGithubPrUrl: github.parseGithubPrUrl,
|
|
27
28
|
loadPullRequest: github.loadPullRequest,
|
|
29
|
+
parseGitlabMrUrl: gitlab.parseGitlabMrUrl,
|
|
30
|
+
loadMergeRequest: gitlab.loadMergeRequest,
|
|
28
31
|
selectChangeSource: source.selectChangeSource,
|
|
29
32
|
decodeChunk: keys.decodeChunk,
|
|
30
33
|
hitAction: keys.hitAction,
|
package/lib/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ const path = require('node:path');
|
|
|
5
5
|
|
|
6
6
|
const { createGitRepo } = require('./git.js');
|
|
7
7
|
const { githubToken, loadPullRequest } = require('./github.js');
|
|
8
|
+
const { gitlabToken, loadMergeRequest } = require('./gitlab.js');
|
|
8
9
|
const { selectChangeSource, createLoadedSource } = require('./source.js');
|
|
9
10
|
const { Session } = require('./session.js');
|
|
10
11
|
|
|
@@ -12,7 +13,22 @@ const HEX_REV = /^[0-9a-f]{4,40}$/i;
|
|
|
12
13
|
const HEAD_REV = /^HEAD([~^].*)?$/i;
|
|
13
14
|
const RANGE_REV = /[~^]/;
|
|
14
15
|
|
|
15
|
-
const USAGE = 'Usage: reslop [-n] [path | commit | pr-url]';
|
|
16
|
+
const USAGE = 'Usage: reslop [-n] [-r] [path | commit | pr-url | mr-url]';
|
|
17
|
+
|
|
18
|
+
const REMOTE_CHANGE = {
|
|
19
|
+
'github-pr': {
|
|
20
|
+
load: 'loadPullRequest',
|
|
21
|
+
fallback: loadPullRequest,
|
|
22
|
+
token: githubToken,
|
|
23
|
+
target: (selected) => selected.pr,
|
|
24
|
+
},
|
|
25
|
+
'gitlab-mr': {
|
|
26
|
+
load: 'loadMergeRequest',
|
|
27
|
+
fallback: loadMergeRequest,
|
|
28
|
+
token: gitlabToken,
|
|
29
|
+
target: (selected) => selected.mr,
|
|
30
|
+
},
|
|
31
|
+
};
|
|
16
32
|
|
|
17
33
|
const errorMessage = (reason) => {
|
|
18
34
|
if (reason instanceof Error) return reason.message;
|
|
@@ -37,17 +53,22 @@ const failUsage = (stderr, reason) => {
|
|
|
37
53
|
const parseArgv = (argv) => {
|
|
38
54
|
const paths = [];
|
|
39
55
|
let newReview = false;
|
|
56
|
+
let readOnly = false;
|
|
40
57
|
for (const arg of argv) {
|
|
41
58
|
if (arg === '-n') {
|
|
42
59
|
newReview = true;
|
|
43
60
|
continue;
|
|
44
61
|
}
|
|
62
|
+
if (arg === '-r') {
|
|
63
|
+
readOnly = true;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
45
66
|
if (arg.startsWith('-')) {
|
|
46
67
|
throw new Error(`unknown option ${arg}`);
|
|
47
68
|
}
|
|
48
69
|
paths.push(arg);
|
|
49
70
|
}
|
|
50
|
-
return { paths, newReview };
|
|
71
|
+
return { paths, newReview, readOnly };
|
|
51
72
|
};
|
|
52
73
|
|
|
53
74
|
const looksLikeRev = (spec) => {
|
|
@@ -95,6 +116,7 @@ const sessionOptions = (proc, deps, args, env, extra) => ({
|
|
|
95
116
|
getSize: deps.getSize,
|
|
96
117
|
startPane: 'files',
|
|
97
118
|
newReview: args.newReview,
|
|
119
|
+
readOnly: args.readOnly,
|
|
98
120
|
audit: true,
|
|
99
121
|
...extra,
|
|
100
122
|
});
|
|
@@ -121,11 +143,12 @@ const openLocalSession = (proc, deps, args, selected, env) => {
|
|
|
121
143
|
return session;
|
|
122
144
|
};
|
|
123
145
|
|
|
124
|
-
const
|
|
146
|
+
const openRemoteSession = async (proc, deps, args, selected, env) => {
|
|
147
|
+
const remote = REMOTE_CHANGE[selected.kind];
|
|
125
148
|
const cwd = deps.cwd ?? proc.cwd();
|
|
126
|
-
const loader = deps.
|
|
127
|
-
const token =
|
|
128
|
-
const loaded = await loader(selected
|
|
149
|
+
const loader = deps[remote.load] ?? remote.fallback;
|
|
150
|
+
const token = remote.token(env);
|
|
151
|
+
const loaded = await loader(remote.target(selected), {
|
|
129
152
|
cwd,
|
|
130
153
|
paths: selected.paths,
|
|
131
154
|
token,
|
|
@@ -149,8 +172,8 @@ const openPullRequestSession = async (proc, deps, args, selected, env) => {
|
|
|
149
172
|
const loadSession = async (proc, deps, args) => {
|
|
150
173
|
const env = proc.env ?? {};
|
|
151
174
|
const selected = selectChangeSource(args.paths);
|
|
152
|
-
if (selected.kind
|
|
153
|
-
return
|
|
175
|
+
if (REMOTE_CHANGE[selected.kind]) {
|
|
176
|
+
return openRemoteSession(proc, deps, args, selected, env);
|
|
154
177
|
}
|
|
155
178
|
return openLocalSession(proc, deps, args, selected, env);
|
|
156
179
|
};
|
package/lib/clipboard.js
CHANGED
|
@@ -3,13 +3,9 @@
|
|
|
3
3
|
const { spawnSync } = require('node:child_process');
|
|
4
4
|
|
|
5
5
|
const { ESC } = require('./ansi.js');
|
|
6
|
+
const { clipTools, spawnBase } = require('./sys.js');
|
|
6
7
|
|
|
7
8
|
const CLIP_TIMEOUT_MS = 1000;
|
|
8
|
-
const CLIP_TOOLS = [
|
|
9
|
-
{ cmd: 'wl-copy', args: [] },
|
|
10
|
-
{ cmd: 'xclip', args: ['-selection', 'clipboard'] },
|
|
11
|
-
{ cmd: 'xsel', args: ['--clipboard', '--input'] },
|
|
12
|
-
];
|
|
13
9
|
|
|
14
10
|
const osc52 = (text) => {
|
|
15
11
|
const b64 = Buffer.from(text, 'utf8').toString('base64');
|
|
@@ -17,11 +13,15 @@ const osc52 = (text) => {
|
|
|
17
13
|
};
|
|
18
14
|
|
|
19
15
|
const runClip = (cmd, args, text) => {
|
|
20
|
-
const result = spawnSync(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
16
|
+
const result = spawnSync(
|
|
17
|
+
cmd,
|
|
18
|
+
args,
|
|
19
|
+
spawnBase({
|
|
20
|
+
input: text,
|
|
21
|
+
encoding: 'utf8',
|
|
22
|
+
timeout: CLIP_TIMEOUT_MS,
|
|
23
|
+
}),
|
|
24
|
+
);
|
|
25
25
|
if (result.error) return false;
|
|
26
26
|
return result.status === 0;
|
|
27
27
|
};
|
|
@@ -33,7 +33,7 @@ const copyText = (text, stdout) => {
|
|
|
33
33
|
stdout.write(osc52(text));
|
|
34
34
|
ok = true;
|
|
35
35
|
}
|
|
36
|
-
for (const tool of
|
|
36
|
+
for (const tool of clipTools()) {
|
|
37
37
|
if (runClip(tool.cmd, tool.args, text)) return true;
|
|
38
38
|
}
|
|
39
39
|
return ok;
|
package/lib/diff.js
CHANGED
|
@@ -773,10 +773,78 @@ const windowAroundBlock = (lines, blockId, radius) => {
|
|
|
773
773
|
return lines.slice(from, to);
|
|
774
774
|
};
|
|
775
775
|
|
|
776
|
-
const
|
|
776
|
+
const insertAddIndex = (lines) => {
|
|
777
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
778
|
+
if (isCtxType(lines[i].type)) continue;
|
|
779
|
+
return i + 1;
|
|
780
|
+
}
|
|
781
|
+
return lines.length;
|
|
782
|
+
};
|
|
783
|
+
|
|
784
|
+
const taggedAdd = (text, blockId, start, end) => ({
|
|
785
|
+
type: 'add',
|
|
786
|
+
text,
|
|
787
|
+
noNl: false,
|
|
788
|
+
blockId,
|
|
789
|
+
wrap: true,
|
|
790
|
+
editStart: start,
|
|
791
|
+
editEnd: end,
|
|
792
|
+
editLast: true,
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
const replaceAdds = (lines, overlay, blockId) => {
|
|
796
|
+
const text = overlay.text ?? '';
|
|
797
|
+
const keepEmpty = overlay.keepEmpty === true;
|
|
798
|
+
if (text === '' && !keepEmpty) {
|
|
799
|
+
return lines.filter((line) => line.type !== 'add');
|
|
800
|
+
}
|
|
801
|
+
const texts = text.split('\n');
|
|
802
|
+
const tagged = [];
|
|
803
|
+
let offset = 0;
|
|
804
|
+
for (let i = 0; i < texts.length; i++) {
|
|
805
|
+
const row = texts[i];
|
|
806
|
+
const start = offset;
|
|
807
|
+
const end = start + row.length;
|
|
808
|
+
const isLast = i === texts.length - 1;
|
|
809
|
+
tagged.push(taggedAdd(row, blockId, start, end));
|
|
810
|
+
offset = isLast ? end : end + 1;
|
|
811
|
+
}
|
|
812
|
+
const out = [];
|
|
813
|
+
let placed = 0;
|
|
814
|
+
let lastAdd = -1;
|
|
815
|
+
for (const line of lines) {
|
|
816
|
+
if (line.type !== 'add') {
|
|
817
|
+
out.push(line);
|
|
818
|
+
continue;
|
|
819
|
+
}
|
|
820
|
+
if (placed >= tagged.length) continue;
|
|
821
|
+
out.push(tagged[placed]);
|
|
822
|
+
lastAdd = out.length - 1;
|
|
823
|
+
placed += 1;
|
|
824
|
+
}
|
|
825
|
+
const extras = tagged.slice(placed);
|
|
826
|
+
if (!extras.length) return out;
|
|
827
|
+
const at = lastAdd >= 0 ? lastAdd + 1 : insertAddIndex(out);
|
|
828
|
+
out.splice(at, 0, ...extras);
|
|
829
|
+
return out;
|
|
830
|
+
};
|
|
831
|
+
|
|
832
|
+
const blockAddText = (hunk, blockId) => {
|
|
833
|
+
if (!hunk) return '';
|
|
834
|
+
const parts = [];
|
|
835
|
+
for (const line of hunk.lines) {
|
|
836
|
+
if (line.type !== 'add') continue;
|
|
837
|
+
if (line.blockId !== blockId) continue;
|
|
838
|
+
parts.push(line.text);
|
|
839
|
+
}
|
|
840
|
+
return parts.join('\n');
|
|
841
|
+
};
|
|
842
|
+
|
|
843
|
+
const displayLines = (hunk, blockId, layout = 'unified', radius, overlay) => {
|
|
777
844
|
const flat = flattenBlock(hunk, blockId, 'new');
|
|
845
|
+
const source = overlay ? replaceAdds(flat, overlay, blockId) : flat;
|
|
778
846
|
const around = radius ?? DISPLAY_CONTEXT;
|
|
779
|
-
const windowed = windowAroundBlock(
|
|
847
|
+
const windowed = windowAroundBlock(source, blockId, around);
|
|
780
848
|
const lines = windowed.map((line) => ({ ...line }));
|
|
781
849
|
const painted = attachInline(lines);
|
|
782
850
|
const align = LAYOUT_ALIGN[layout];
|
|
@@ -877,6 +945,7 @@ module.exports = {
|
|
|
877
945
|
countSides,
|
|
878
946
|
formatPatch,
|
|
879
947
|
displayLines,
|
|
948
|
+
blockAddText,
|
|
880
949
|
synthesizeNewFile,
|
|
881
950
|
itemsFromFiles,
|
|
882
951
|
refreshIndexPatches,
|
package/lib/git.js
CHANGED
|
@@ -7,12 +7,18 @@ const path = require('node:path');
|
|
|
7
7
|
const diff = require('./diff.js');
|
|
8
8
|
const { parseDiff, synthesizeNewFile, itemsFromFiles } = diff;
|
|
9
9
|
const { itemPath, isPathInScope, REVIEW_DIR } = require('./files.js');
|
|
10
|
+
const { npmBin, spawnBase } = require('./sys.js');
|
|
10
11
|
const deps = require('./deps.js');
|
|
11
12
|
const { foldDepItems, mergeDepFile, depFileMeta } = deps;
|
|
12
13
|
const { collectUsedNames, parseAuditReport, parseOutdatedReport } = deps;
|
|
13
14
|
const { mergeProposedItems, MANIFEST, LOCKFILE } = deps;
|
|
14
15
|
|
|
15
|
-
const GIT_CONFIG = [
|
|
16
|
+
const GIT_CONFIG = [
|
|
17
|
+
'-c',
|
|
18
|
+
'core.quotepath=false',
|
|
19
|
+
'-c',
|
|
20
|
+
'i18n.logOutputEncoding=utf-8',
|
|
21
|
+
];
|
|
16
22
|
const DIFF_OPTS = ['--no-color', '--no-ext-diff', '--no-renames', '-U3'];
|
|
17
23
|
const GIT_ENCODING = 'utf8';
|
|
18
24
|
const GIT_MAX_BUFFER = 32 * 1024 * 1024;
|
|
@@ -71,7 +77,7 @@ const runProc = (cmd, args, options = {}) =>
|
|
|
71
77
|
let stderr = '';
|
|
72
78
|
let settled = false;
|
|
73
79
|
let timer = null;
|
|
74
|
-
const child = spawn(cmd, args, { cwd, env });
|
|
80
|
+
const child = spawn(cmd, args, spawnBase({ cwd, env }));
|
|
75
81
|
const finish = (result) => {
|
|
76
82
|
if (settled) return;
|
|
77
83
|
settled = true;
|
|
@@ -135,7 +141,7 @@ const runGit = (args, cwd, input) => {
|
|
|
135
141
|
const env = { ...process.env, GIT_OPTIONAL_LOCKS: '0' };
|
|
136
142
|
const encoding = GIT_ENCODING;
|
|
137
143
|
const maxBuffer = GIT_MAX_BUFFER;
|
|
138
|
-
const options = { cwd, encoding, input, maxBuffer, env };
|
|
144
|
+
const options = spawnBase({ cwd, encoding, input, maxBuffer, env });
|
|
139
145
|
const result = spawnSync('git', args, options);
|
|
140
146
|
return gitResult(result);
|
|
141
147
|
};
|
|
@@ -275,18 +281,22 @@ const readDepSides = (top, rev, origin, rel) => {
|
|
|
275
281
|
};
|
|
276
282
|
|
|
277
283
|
const runNpmJson = (top, args, timeout) => {
|
|
278
|
-
const result = spawnSync(
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
+
const result = spawnSync(
|
|
285
|
+
npmBin(),
|
|
286
|
+
args,
|
|
287
|
+
spawnBase({
|
|
288
|
+
cwd: top,
|
|
289
|
+
encoding: GIT_ENCODING,
|
|
290
|
+
maxBuffer: GIT_MAX_BUFFER,
|
|
291
|
+
timeout,
|
|
292
|
+
}),
|
|
293
|
+
);
|
|
284
294
|
if (result.error) return '';
|
|
285
295
|
return result.stdout || '';
|
|
286
296
|
};
|
|
287
297
|
|
|
288
298
|
const runNpmJsonAsync = async (top, args, timeout, signal) => {
|
|
289
|
-
const result = await runProc(
|
|
299
|
+
const result = await runProc(npmBin(), args, {
|
|
290
300
|
cwd: top,
|
|
291
301
|
encoding: GIT_ENCODING,
|
|
292
302
|
maxBuffer: GIT_MAX_BUFFER,
|
|
@@ -443,12 +453,16 @@ const applyDepWorktree = (top, rel, change, origin) => {
|
|
|
443
453
|
};
|
|
444
454
|
|
|
445
455
|
const runNpm = (cwd, args) =>
|
|
446
|
-
spawnSync(
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
456
|
+
spawnSync(
|
|
457
|
+
npmBin(),
|
|
458
|
+
args,
|
|
459
|
+
spawnBase({
|
|
460
|
+
cwd,
|
|
461
|
+
encoding: GIT_ENCODING,
|
|
462
|
+
maxBuffer: GIT_MAX_BUFFER,
|
|
463
|
+
timeout: INSTALL_MS,
|
|
464
|
+
}),
|
|
465
|
+
);
|
|
452
466
|
|
|
453
467
|
const firstDepRel = (files, kind) => {
|
|
454
468
|
for (const rel of files) {
|
|
@@ -831,6 +845,28 @@ const revertItem = (top, item) => {
|
|
|
831
845
|
}
|
|
832
846
|
};
|
|
833
847
|
|
|
848
|
+
const lastMessage = (top) => {
|
|
849
|
+
const result = runGit(['log', '-1', '--format=%B'], top);
|
|
850
|
+
if (result.status !== 0) return '';
|
|
851
|
+
return (result.stdout ?? '').replace(/\s+$/, '');
|
|
852
|
+
};
|
|
853
|
+
|
|
854
|
+
const commitChanges = (top, kind, message) => {
|
|
855
|
+
if (kind === 'fixup') {
|
|
856
|
+
const target = (message ?? '').trim() || 'HEAD';
|
|
857
|
+
const result = runGit(['commit', `--fixup=${target}`], top);
|
|
858
|
+
requireOk(result);
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
const args = ['commit'];
|
|
862
|
+
if (kind === 'amend') args.push('--amend');
|
|
863
|
+
args.push('-F', '-');
|
|
864
|
+
const text = message ?? '';
|
|
865
|
+
const body = text.endsWith('\n') ? text : `${text}\n`;
|
|
866
|
+
const result = runGit(args, top, body);
|
|
867
|
+
requireOk(result);
|
|
868
|
+
};
|
|
869
|
+
|
|
834
870
|
const revertFile = (top, rel, items) => {
|
|
835
871
|
const paths = new Set([rel]);
|
|
836
872
|
for (const item of items) {
|
|
@@ -853,6 +889,8 @@ const createGitRepo = () => ({
|
|
|
853
889
|
unstage: unstageItem,
|
|
854
890
|
revert: revertItem,
|
|
855
891
|
revertFile,
|
|
892
|
+
commit: commitChanges,
|
|
893
|
+
lastMessage,
|
|
856
894
|
});
|
|
857
895
|
|
|
858
896
|
module.exports = {
|
|
@@ -862,5 +900,7 @@ module.exports = {
|
|
|
862
900
|
addItem,
|
|
863
901
|
unstageItem,
|
|
864
902
|
revertItem,
|
|
903
|
+
commitChanges,
|
|
904
|
+
lastMessage,
|
|
865
905
|
createGitRepo,
|
|
866
906
|
};
|