reslop 0.1.5 → 0.1.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.
- package/CHANGELOG.md +9 -0
- package/README.md +4 -6
- package/lib/cli.js +20 -16
- package/lib/detect.js +2 -40
- package/lib/files.js +5 -1
- package/lib/git.js +302 -18
- package/lib/highlight/bash.js +228 -0
- package/lib/highlight/core.js +194 -0
- package/lib/highlight/css.js +76 -0
- package/lib/highlight/csv.js +66 -0
- package/lib/highlight/dart.js +261 -0
- package/lib/highlight/dot.js +54 -0
- package/lib/highlight/html.js +90 -0
- package/lib/highlight/js.js +571 -0
- package/lib/highlight/json.js +54 -0
- package/lib/highlight/log.js +60 -0
- package/lib/highlight/txt.js +41 -0
- package/lib/highlight.js +32 -1299
- package/lib/keys.js +79 -28
- package/lib/render.js +443 -45
- package/lib/session.js +538 -43
- package/lib/sys.js +4 -0
- package/lib/update.js +302 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased][unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.1.6][] - 2026-09-15
|
|
6
|
+
|
|
7
|
+
- Auto updates for patch/minor, y/n per new major
|
|
8
|
+
- Lists branches, new branch, git pull, git push
|
|
9
|
+
- Show TUI progress for long background operations
|
|
10
|
+
- Fix Windows spawn EINVAL when running `npm.cmd`
|
|
11
|
+
- Highlight Dart diffs; refactor syntax highlighting
|
|
12
|
+
- Multiple TUI/TUX improvements and fixes
|
|
13
|
+
|
|
5
14
|
## [0.1.5][] - 2026-09-14
|
|
6
15
|
|
|
7
16
|
- Added `r` to reload diffs from disk
|
package/README.md
CHANGED
|
@@ -51,13 +51,11 @@ Reviews go in `.review/YYYY-MM-DD-NN.md` with frontmatter `status`:
|
|
|
51
51
|
|
|
52
52
|
| Key | Action |
|
|
53
53
|
| ------------------- | ----------------------------------------------- |
|
|
54
|
-
| `a`
|
|
55
|
-
| `u` | Unstage this diff |
|
|
56
|
-
| `d` | Drop this diff to the last commit |
|
|
54
|
+
| `a` / `u` / `d` | Stage / unstage / drop this diff |
|
|
57
55
|
| `c` | Commit: then `c` commit / `a` amend / `f` fixup |
|
|
58
|
-
| `
|
|
59
|
-
| `
|
|
60
|
-
| `e`
|
|
56
|
+
| `b` | List branches; `b, n` new branch |
|
|
57
|
+
| `p` / `s` | git pull/push |
|
|
58
|
+
| `f` / `t` / `e` | Add feedback / todo / edit diff text |
|
|
61
59
|
| `r` | Reload diffs from disk |
|
|
62
60
|
| `m` | Mode: unified / mixed / side-by-side |
|
|
63
61
|
| `⏎` | Open the selected file |
|
package/lib/cli.js
CHANGED
|
@@ -143,29 +143,33 @@ const openLocalSession = (proc, deps, args, selected, env) => {
|
|
|
143
143
|
return session;
|
|
144
144
|
};
|
|
145
145
|
|
|
146
|
-
const
|
|
146
|
+
const remoteRepo = (deps, selected, env, cwd) => {
|
|
147
|
+
if (deps.repo) return deps.repo;
|
|
147
148
|
const remote = REMOTE_CHANGE[selected.kind];
|
|
148
|
-
const cwd = deps.cwd ?? proc.cwd();
|
|
149
149
|
const loader = deps[remote.load] ?? remote.fallback;
|
|
150
150
|
const token = remote.token(env);
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
151
|
+
const loadRemote = async () => {
|
|
152
|
+
const loaded = await loader(remote.target(selected), {
|
|
153
|
+
cwd,
|
|
154
|
+
paths: selected.paths,
|
|
155
|
+
token,
|
|
156
|
+
fetch: deps.fetch,
|
|
157
|
+
});
|
|
158
|
+
const items = loaded.items ?? [];
|
|
159
|
+
return { ...loaded, items: [...items] };
|
|
160
|
+
};
|
|
161
|
+
const repo = createLoadedSource({ items: [] });
|
|
162
|
+
repo.loadAsync = loadRemote;
|
|
163
|
+
return repo;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const openRemoteSession = (proc, deps, args, selected, env) => {
|
|
167
|
+
const cwd = deps.cwd ?? proc.cwd();
|
|
160
168
|
return startSession(
|
|
161
169
|
sessionOptions(proc, deps, args, env, {
|
|
162
|
-
repo,
|
|
170
|
+
repo: remoteRepo(deps, selected, env, cwd),
|
|
163
171
|
paths: selected.paths,
|
|
164
|
-
repoName: repository,
|
|
165
|
-
sourceLabel: loaded.sourceLabel,
|
|
166
|
-
change,
|
|
167
172
|
}),
|
|
168
|
-
{ load: true },
|
|
169
173
|
);
|
|
170
174
|
};
|
|
171
175
|
|
package/lib/detect.js
CHANGED
|
@@ -2,40 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const path = require('node:path');
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
md: 'md',
|
|
7
|
-
markdown: 'md',
|
|
8
|
-
js: 'js',
|
|
9
|
-
javascript: 'js',
|
|
10
|
-
mjs: 'mjs',
|
|
11
|
-
cjs: 'js',
|
|
12
|
-
ts: 'ts',
|
|
13
|
-
typescript: 'ts',
|
|
14
|
-
jsx: 'jsx',
|
|
15
|
-
tsx: 'tsx',
|
|
16
|
-
json: 'json',
|
|
17
|
-
csv: 'csv',
|
|
18
|
-
html: 'html',
|
|
19
|
-
htm: 'html',
|
|
20
|
-
css: 'css',
|
|
21
|
-
sh: 'bash',
|
|
22
|
-
bash: 'bash',
|
|
23
|
-
shell: 'bash',
|
|
24
|
-
zsh: 'bash',
|
|
25
|
-
txt: 'txt',
|
|
26
|
-
text: 'txt',
|
|
27
|
-
plain: 'txt',
|
|
28
|
-
log: 'log',
|
|
29
|
-
env: 'dot',
|
|
30
|
-
ini: 'dot',
|
|
31
|
-
conf: 'dot',
|
|
32
|
-
cfg: 'dot',
|
|
33
|
-
yaml: 'dot',
|
|
34
|
-
yml: 'dot',
|
|
35
|
-
toml: 'dot',
|
|
36
|
-
py: 'py',
|
|
37
|
-
python: 'py',
|
|
38
|
-
};
|
|
5
|
+
const { mapLang } = require('./highlight.js');
|
|
39
6
|
|
|
40
7
|
const PROSE_BASENAMES = [
|
|
41
8
|
'license',
|
|
@@ -53,11 +20,6 @@ const fileExt = (filePath) => {
|
|
|
53
20
|
return base.slice(dot + 1).toLowerCase();
|
|
54
21
|
};
|
|
55
22
|
|
|
56
|
-
const mapLang = (name) => {
|
|
57
|
-
if (!name) return 'txt';
|
|
58
|
-
return LANG[name.toLowerCase()] || 'txt';
|
|
59
|
-
};
|
|
60
|
-
|
|
61
23
|
const isProseBasename = (base) => {
|
|
62
24
|
const name = (base || '').toLowerCase();
|
|
63
25
|
if (PROSE_BASENAMES.includes(name)) return true;
|
|
@@ -79,7 +41,7 @@ const detectLang = (filePath) => {
|
|
|
79
41
|
if (rule.test(base)) return rule.lang;
|
|
80
42
|
}
|
|
81
43
|
const ext = fileExt(base);
|
|
82
|
-
if (ext) return
|
|
44
|
+
if (ext) return mapLang(ext);
|
|
83
45
|
return 'dot';
|
|
84
46
|
};
|
|
85
47
|
|
package/lib/files.js
CHANGED
|
@@ -57,6 +57,7 @@ const fileEntries = (items) => {
|
|
|
57
57
|
path: rel,
|
|
58
58
|
origins: [],
|
|
59
59
|
firstIndex: i,
|
|
60
|
+
openIndex: i,
|
|
60
61
|
remaining: 0,
|
|
61
62
|
added: 0,
|
|
62
63
|
removed: 0,
|
|
@@ -72,7 +73,10 @@ const fileEntries = (items) => {
|
|
|
72
73
|
group.added += delta.added;
|
|
73
74
|
group.removed += delta.removed;
|
|
74
75
|
if (origin === 'staged') group.staged += 1;
|
|
75
|
-
if (origin === 'unstaged')
|
|
76
|
+
if (origin === 'unstaged') {
|
|
77
|
+
if (group.unstaged === 0) group.openIndex = i;
|
|
78
|
+
group.unstaged += 1;
|
|
79
|
+
}
|
|
76
80
|
}
|
|
77
81
|
const entries = [...groups.values()];
|
|
78
82
|
for (const group of entries) {
|
package/lib/git.js
CHANGED
|
@@ -7,7 +7,7 @@ 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
|
+
const { npmBin, spawnBase, npmOpts } = require('./sys.js');
|
|
11
11
|
const deps = require('./deps.js');
|
|
12
12
|
const { foldDepItems, mergeDepFile, depFileMeta } = deps;
|
|
13
13
|
const { collectUsedNames, parseAuditReport, parseOutdatedReport } = deps;
|
|
@@ -25,6 +25,23 @@ const GIT_MAX_BUFFER = 32 * 1024 * 1024;
|
|
|
25
25
|
const BINARY_SIZE = 1000000;
|
|
26
26
|
const AUDIT_MS = 15000;
|
|
27
27
|
const INSTALL_MS = 120000;
|
|
28
|
+
const BRANCH_FORMAT = [
|
|
29
|
+
'%(HEAD)',
|
|
30
|
+
'%(refname:short)',
|
|
31
|
+
'%(objectname:short)',
|
|
32
|
+
'%(committerdate:relative)',
|
|
33
|
+
'%(upstream:track)',
|
|
34
|
+
'%(subject)',
|
|
35
|
+
].join('%09');
|
|
36
|
+
const TRACK_AHEAD = /ahead (\d+)/;
|
|
37
|
+
const TRACK_BEHIND = /behind (\d+)/;
|
|
38
|
+
const ORIGIN_HEAD = 'refs/remotes/origin/HEAD';
|
|
39
|
+
const DEFAULT_BRANCHES = ['main', 'master'];
|
|
40
|
+
const PUSH_REJECTED = [
|
|
41
|
+
'non-fast-forward',
|
|
42
|
+
'failed to push some refs',
|
|
43
|
+
'updates were rejected',
|
|
44
|
+
];
|
|
28
45
|
|
|
29
46
|
const oneLine = (text) => {
|
|
30
47
|
const line = text.trim().split('\n')[0];
|
|
@@ -77,7 +94,15 @@ const runProc = (cmd, args, options = {}) =>
|
|
|
77
94
|
let stderr = '';
|
|
78
95
|
let settled = false;
|
|
79
96
|
let timer = null;
|
|
80
|
-
const
|
|
97
|
+
const spawnOpts = { cwd, env };
|
|
98
|
+
if (options.shell) spawnOpts.shell = true;
|
|
99
|
+
let child;
|
|
100
|
+
try {
|
|
101
|
+
child = spawn(cmd, args, spawnBase(spawnOpts));
|
|
102
|
+
} catch (error) {
|
|
103
|
+
resolve({ status: null, error, stdout: '', stderr: '' });
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
81
106
|
const finish = (result) => {
|
|
82
107
|
if (settled) return;
|
|
83
108
|
settled = true;
|
|
@@ -284,7 +309,7 @@ const runNpmJson = (top, args, timeout) => {
|
|
|
284
309
|
const result = spawnSync(
|
|
285
310
|
npmBin(),
|
|
286
311
|
args,
|
|
287
|
-
|
|
312
|
+
npmOpts({
|
|
288
313
|
cwd: top,
|
|
289
314
|
encoding: GIT_ENCODING,
|
|
290
315
|
maxBuffer: GIT_MAX_BUFFER,
|
|
@@ -302,6 +327,7 @@ const runNpmJsonAsync = async (top, args, timeout, signal) => {
|
|
|
302
327
|
maxBuffer: GIT_MAX_BUFFER,
|
|
303
328
|
timeout,
|
|
304
329
|
signal,
|
|
330
|
+
shell: npmOpts().shell,
|
|
305
331
|
});
|
|
306
332
|
if (result.error) return '';
|
|
307
333
|
return result.stdout || '';
|
|
@@ -456,7 +482,7 @@ const runNpm = (cwd, args) =>
|
|
|
456
482
|
spawnSync(
|
|
457
483
|
npmBin(),
|
|
458
484
|
args,
|
|
459
|
-
|
|
485
|
+
npmOpts({
|
|
460
486
|
cwd,
|
|
461
487
|
encoding: GIT_ENCODING,
|
|
462
488
|
maxBuffer: GIT_MAX_BUFFER,
|
|
@@ -464,6 +490,15 @@ const runNpm = (cwd, args) =>
|
|
|
464
490
|
}),
|
|
465
491
|
);
|
|
466
492
|
|
|
493
|
+
const runNpmAsync = (cwd, args) =>
|
|
494
|
+
runProc(npmBin(), args, {
|
|
495
|
+
cwd,
|
|
496
|
+
encoding: GIT_ENCODING,
|
|
497
|
+
maxBuffer: GIT_MAX_BUFFER,
|
|
498
|
+
timeout: INSTALL_MS,
|
|
499
|
+
shell: npmOpts().shell,
|
|
500
|
+
});
|
|
501
|
+
|
|
467
502
|
const firstDepRel = (files, kind) => {
|
|
468
503
|
for (const rel of files) {
|
|
469
504
|
const meta = depFileMeta(rel);
|
|
@@ -472,7 +507,7 @@ const firstDepRel = (files, kind) => {
|
|
|
472
507
|
return '';
|
|
473
508
|
};
|
|
474
509
|
|
|
475
|
-
const
|
|
510
|
+
const proposedNpmPlan = (top, item) => {
|
|
476
511
|
const change = depChange(item);
|
|
477
512
|
const origin = item.origin;
|
|
478
513
|
const files = depPaths(item);
|
|
@@ -483,7 +518,7 @@ const applyProposedUpdate = (top, item) => {
|
|
|
483
518
|
const picked = lockOnly ? lockRel : manifest;
|
|
484
519
|
const target = picked || files[0] || '';
|
|
485
520
|
const meta = depFileMeta(target);
|
|
486
|
-
if (!meta) return;
|
|
521
|
+
if (!meta) return null;
|
|
487
522
|
let saved = '';
|
|
488
523
|
if (!lockOnly && !removing && manifest) {
|
|
489
524
|
const worktree = worktreeText(top, manifest);
|
|
@@ -503,9 +538,23 @@ const applyProposedUpdate = (top, item) => {
|
|
|
503
538
|
let npmArgs = ['i'];
|
|
504
539
|
if (removing) npmArgs = ['uninstall', change.name];
|
|
505
540
|
else if (lockOnly) npmArgs = ['audit', 'fix'];
|
|
506
|
-
const install = item.dep.install ?? ((dir) => runNpm(dir, npmArgs));
|
|
507
|
-
const result = install(cwd);
|
|
508
541
|
const fail = removing ? 'npm uninstall failed' : 'npm i failed';
|
|
542
|
+
const dirLock = meta.dir === '.' ? LOCKFILE : `${meta.dir}/${LOCKFILE}`;
|
|
543
|
+
const lockPath = lockRel || dirLock;
|
|
544
|
+
return {
|
|
545
|
+
cwd,
|
|
546
|
+
npmArgs,
|
|
547
|
+
saved,
|
|
548
|
+
manifest,
|
|
549
|
+
lockOnly,
|
|
550
|
+
lockPath,
|
|
551
|
+
fail,
|
|
552
|
+
runner: item.dep.install,
|
|
553
|
+
};
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
const finishProposedNpm = (top, plan, result) => {
|
|
557
|
+
const { saved, manifest, lockOnly, lockPath, fail } = plan;
|
|
509
558
|
if (result && (result.error || result.status !== 0)) {
|
|
510
559
|
if (saved && manifest) writeWorktreeFile(top, manifest, saved);
|
|
511
560
|
if (result.error) {
|
|
@@ -517,12 +566,26 @@ const applyProposedUpdate = (top, item) => {
|
|
|
517
566
|
}
|
|
518
567
|
const toAdd = [];
|
|
519
568
|
if (!lockOnly && manifest) toAdd.push(manifest);
|
|
520
|
-
const dirLock = meta.dir === '.' ? LOCKFILE : `${meta.dir}/${LOCKFILE}`;
|
|
521
|
-
const lockPath = lockRel || dirLock;
|
|
522
569
|
if (fs.existsSync(path.join(top, lockPath))) toAdd.push(lockPath);
|
|
523
570
|
if (toAdd.length) requireOk(runGit(['add', '--', ...toAdd], top));
|
|
524
571
|
};
|
|
525
572
|
|
|
573
|
+
const applyProposedUpdate = (top, item) => {
|
|
574
|
+
const plan = proposedNpmPlan(top, item);
|
|
575
|
+
if (!plan) return;
|
|
576
|
+
const install = plan.runner ?? ((dir) => runNpm(dir, plan.npmArgs));
|
|
577
|
+
finishProposedNpm(top, plan, install(plan.cwd));
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
const applyProposedUpdateAsync = async (top, item) => {
|
|
581
|
+
const plan = proposedNpmPlan(top, item);
|
|
582
|
+
if (!plan) return;
|
|
583
|
+
const run = plan.runner
|
|
584
|
+
? (dir) => Promise.resolve(plan.runner(dir))
|
|
585
|
+
: (dir) => runNpmAsync(dir, plan.npmArgs);
|
|
586
|
+
finishProposedNpm(top, plan, await run(plan.cwd));
|
|
587
|
+
};
|
|
588
|
+
|
|
526
589
|
const applyDepChange = (top, item, mode) => {
|
|
527
590
|
const change = depChange(item);
|
|
528
591
|
if (change && change.propose) {
|
|
@@ -579,6 +642,15 @@ const commitDiffArgs = (commit, parent, pathArgs) => {
|
|
|
579
642
|
|
|
580
643
|
const pathArgsOf = (paths) => (paths.length ? ['--', ...paths] : []);
|
|
581
644
|
|
|
645
|
+
const currentBranch = (top) => {
|
|
646
|
+
const args = ['rev-parse', '--abbrev-ref', 'HEAD'];
|
|
647
|
+
const result = runGit(args, top);
|
|
648
|
+
if (result.status !== 0) return '';
|
|
649
|
+
const name = result.stdout.trim();
|
|
650
|
+
if (!name || name === 'HEAD') return '';
|
|
651
|
+
return name;
|
|
652
|
+
};
|
|
653
|
+
|
|
582
654
|
const snapshotWorktree = (top, stagedOut, unstagedOut, names) => {
|
|
583
655
|
const stagedDiff = parseDiff(stagedOut);
|
|
584
656
|
const unstagedDiff = parseDiff(unstagedOut);
|
|
@@ -587,14 +659,16 @@ const snapshotWorktree = (top, stagedOut, unstagedOut, names) => {
|
|
|
587
659
|
const untracked = untrackedItemsFrom(top, names);
|
|
588
660
|
const combined = mergeItems([staged, unstaged, untracked]);
|
|
589
661
|
const parsed = dropReviewItems(combined);
|
|
590
|
-
|
|
662
|
+
const branch = currentBranch(top);
|
|
663
|
+
return { top, parsed, branch };
|
|
591
664
|
};
|
|
592
665
|
|
|
593
666
|
const snapshotCommit = (top, commit, files) => {
|
|
594
667
|
const commitItems = itemsFromFiles(files, 'commit');
|
|
595
668
|
const parsed = dropReviewItems(commitItems);
|
|
596
669
|
const revShort = shortRev(top, commit);
|
|
597
|
-
|
|
670
|
+
const branch = currentBranch(top);
|
|
671
|
+
return { top, parsed, rev: commit, revShort, branch };
|
|
598
672
|
};
|
|
599
673
|
|
|
600
674
|
const loadCommit = (top, commit, extra) => {
|
|
@@ -746,6 +820,16 @@ const addItem = (top, item) => {
|
|
|
746
820
|
applyPatch(top, item.patchAdd, ['--cached']);
|
|
747
821
|
};
|
|
748
822
|
|
|
823
|
+
const addItemAsync = async (top, item) => {
|
|
824
|
+
if (item.origin === 'staged' || item.origin === 'commit') return;
|
|
825
|
+
const change = depChange(item);
|
|
826
|
+
if (change && change.propose) {
|
|
827
|
+
await applyProposedUpdateAsync(top, item);
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
addItem(top, item);
|
|
831
|
+
};
|
|
832
|
+
|
|
749
833
|
const unstageItem = (top, item) => {
|
|
750
834
|
if (item.origin !== 'staged') return;
|
|
751
835
|
if (depChange(item)) {
|
|
@@ -851,20 +935,195 @@ const lastMessage = (top) => {
|
|
|
851
935
|
return (result.stdout ?? '').replace(/\s+$/, '');
|
|
852
936
|
};
|
|
853
937
|
|
|
854
|
-
const
|
|
938
|
+
const parseTrack = (text) => {
|
|
939
|
+
const raw = `${text ?? ''}`.trim().replace(/^\[|\]$/g, '');
|
|
940
|
+
if (!raw) return { ahead: 0, behind: 0, gone: false };
|
|
941
|
+
if (raw === 'gone') return { ahead: 0, behind: 0, gone: true };
|
|
942
|
+
const aheadMatch = TRACK_AHEAD.exec(raw);
|
|
943
|
+
const behindMatch = TRACK_BEHIND.exec(raw);
|
|
944
|
+
const ahead = aheadMatch ? Number(aheadMatch[1]) : 0;
|
|
945
|
+
const behind = behindMatch ? Number(behindMatch[1]) : 0;
|
|
946
|
+
return { ahead, behind, gone: false };
|
|
947
|
+
};
|
|
948
|
+
|
|
949
|
+
const parseBranchLine = (line) => {
|
|
950
|
+
const parts = line.split('\t');
|
|
951
|
+
if (parts.length < 2) return null;
|
|
952
|
+
const name = (parts[1] ?? '').trim();
|
|
953
|
+
if (!name) return null;
|
|
954
|
+
const mark = parts[0] ?? '';
|
|
955
|
+
const sha = (parts[2] ?? '').trim();
|
|
956
|
+
const date = (parts[3] ?? '').trim();
|
|
957
|
+
const track = parseTrack(parts[4]);
|
|
958
|
+
const subject = parts.slice(5).join('\t').trim();
|
|
959
|
+
return {
|
|
960
|
+
name,
|
|
961
|
+
current: mark.includes('*'),
|
|
962
|
+
isDefault: false,
|
|
963
|
+
sha,
|
|
964
|
+
date,
|
|
965
|
+
subject,
|
|
966
|
+
...track,
|
|
967
|
+
};
|
|
968
|
+
};
|
|
969
|
+
|
|
970
|
+
const originHead = (top) => {
|
|
971
|
+
const args = ['symbolic-ref', '--short', ORIGIN_HEAD];
|
|
972
|
+
const result = runGit(args, top);
|
|
973
|
+
if (result.status !== 0) return '';
|
|
974
|
+
const ref = result.stdout.trim();
|
|
975
|
+
const prefix = 'origin/';
|
|
976
|
+
if (ref.startsWith(prefix)) return ref.slice(prefix.length);
|
|
977
|
+
return ref;
|
|
978
|
+
};
|
|
979
|
+
|
|
980
|
+
const defaultBranch = (top, names) => {
|
|
981
|
+
const remote = originHead(top);
|
|
982
|
+
if (names.has(remote)) return remote;
|
|
983
|
+
for (const name of DEFAULT_BRANCHES) {
|
|
984
|
+
if (names.has(name)) return name;
|
|
985
|
+
}
|
|
986
|
+
return '';
|
|
987
|
+
};
|
|
988
|
+
|
|
989
|
+
const listBranches = (top) => {
|
|
990
|
+
const args = ['for-each-ref', `--format=${BRANCH_FORMAT}`, 'refs/heads'];
|
|
991
|
+
const result = requireOk(runGit(args, top), 'branch failed');
|
|
992
|
+
const branches = [];
|
|
993
|
+
for (const line of result.stdout.split('\n')) {
|
|
994
|
+
if (!line) continue;
|
|
995
|
+
const entry = parseBranchLine(line);
|
|
996
|
+
if (entry) branches.push(entry);
|
|
997
|
+
}
|
|
998
|
+
const names = new Set();
|
|
999
|
+
for (const entry of branches) names.add(entry.name);
|
|
1000
|
+
const primary = defaultBranch(top, names);
|
|
1001
|
+
for (const entry of branches) {
|
|
1002
|
+
entry.isDefault = entry.name === primary;
|
|
1003
|
+
}
|
|
1004
|
+
return branches;
|
|
1005
|
+
};
|
|
1006
|
+
|
|
1007
|
+
const namedBranch = (name) => {
|
|
1008
|
+
const branch = `${name ?? ''}`.trim();
|
|
1009
|
+
if (!branch) throw new Error('empty branch name');
|
|
1010
|
+
return branch;
|
|
1011
|
+
};
|
|
1012
|
+
|
|
1013
|
+
const checkoutBranch = (top, name) => {
|
|
1014
|
+
const branch = namedBranch(name);
|
|
1015
|
+
requireOk(runGit(['checkout', branch], top), 'checkout failed');
|
|
1016
|
+
};
|
|
1017
|
+
|
|
1018
|
+
const checkoutBranchAsync = async (top, name) => {
|
|
1019
|
+
const branch = namedBranch(name);
|
|
1020
|
+
requireOk(await runGitAsync(['checkout', branch], top), 'checkout failed');
|
|
1021
|
+
};
|
|
1022
|
+
|
|
1023
|
+
const createBranch = (top, name) => {
|
|
1024
|
+
const branch = namedBranch(name);
|
|
1025
|
+
const args = ['checkout', '-b', branch];
|
|
1026
|
+
requireOk(runGit(args, top), 'checkout failed');
|
|
1027
|
+
};
|
|
1028
|
+
|
|
1029
|
+
const createBranchAsync = async (top, name) => {
|
|
1030
|
+
const branch = namedBranch(name);
|
|
1031
|
+
const args = ['checkout', '-b', branch];
|
|
1032
|
+
requireOk(await runGitAsync(args, top), 'checkout failed');
|
|
1033
|
+
};
|
|
1034
|
+
|
|
1035
|
+
const pullChanges = (top) => {
|
|
1036
|
+
requireOk(runGit(['pull'], top), 'pull failed');
|
|
1037
|
+
};
|
|
1038
|
+
|
|
1039
|
+
const pullChangesAsync = async (top) => {
|
|
1040
|
+
requireOk(await runGitAsync(['pull'], top), 'pull failed');
|
|
1041
|
+
};
|
|
1042
|
+
|
|
1043
|
+
const isRejectedPush = (msg) => {
|
|
1044
|
+
const text = `${msg ?? ''}`.toLowerCase();
|
|
1045
|
+
for (const token of PUSH_REJECTED) {
|
|
1046
|
+
if (text.includes(token)) return true;
|
|
1047
|
+
}
|
|
1048
|
+
return false;
|
|
1049
|
+
};
|
|
1050
|
+
|
|
1051
|
+
const pushError = (msg) => {
|
|
1052
|
+
const text = `${msg ?? ''}`;
|
|
1053
|
+
const rejected = isRejectedPush(text);
|
|
1054
|
+
const error = new Error(rejected ? 'push rejected' : oneLine(text));
|
|
1055
|
+
error.rejected = rejected;
|
|
1056
|
+
return error;
|
|
1057
|
+
};
|
|
1058
|
+
|
|
1059
|
+
const pushCmd = (force) => {
|
|
1060
|
+
if (force) return ['push', '--force-with-lease'];
|
|
1061
|
+
return ['push'];
|
|
1062
|
+
};
|
|
1063
|
+
|
|
1064
|
+
const pushUpstreamCmd = (force) => {
|
|
1065
|
+
if (force) return ['push', '--force-with-lease', '-u', 'origin', 'HEAD'];
|
|
1066
|
+
return ['push', '-u', 'origin', 'HEAD'];
|
|
1067
|
+
};
|
|
1068
|
+
|
|
1069
|
+
const finishPush = (result, force, run) => {
|
|
1070
|
+
if (result.status === 0) return;
|
|
1071
|
+
const msg = result.stderr || result.stdout || 'push failed';
|
|
1072
|
+
if (!msg.toLowerCase().includes('no upstream')) {
|
|
1073
|
+
throw pushError(msg);
|
|
1074
|
+
}
|
|
1075
|
+
const next = run(pushUpstreamCmd(force));
|
|
1076
|
+
if (next.status !== 0) {
|
|
1077
|
+
throw pushError(next.stderr || next.stdout || 'push failed');
|
|
1078
|
+
}
|
|
1079
|
+
};
|
|
1080
|
+
|
|
1081
|
+
const pushChanges = (top, force) => {
|
|
1082
|
+
const useForce = force === true;
|
|
1083
|
+
const result = runGit(pushCmd(useForce), top);
|
|
1084
|
+
finishPush(result, useForce, (args) => runGit(args, top));
|
|
1085
|
+
};
|
|
1086
|
+
|
|
1087
|
+
const pushChangesAsync = async (top, force) => {
|
|
1088
|
+
const useForce = force === true;
|
|
1089
|
+
const result = await runGitAsync(pushCmd(useForce), top);
|
|
1090
|
+
if (result.status === 0) return;
|
|
1091
|
+
const msg = result.stderr || result.stdout || 'push failed';
|
|
1092
|
+
if (!msg.toLowerCase().includes('no upstream')) {
|
|
1093
|
+
throw pushError(msg);
|
|
1094
|
+
}
|
|
1095
|
+
const next = await runGitAsync(pushUpstreamCmd(useForce), top);
|
|
1096
|
+
if (next.status !== 0) {
|
|
1097
|
+
throw pushError(next.stderr || next.stdout || 'push failed');
|
|
1098
|
+
}
|
|
1099
|
+
};
|
|
1100
|
+
|
|
1101
|
+
const hasStaged = (top) => {
|
|
1102
|
+
const result = runGit(['diff', '--cached', '--quiet'], top);
|
|
1103
|
+
return result.status === 1;
|
|
1104
|
+
};
|
|
1105
|
+
|
|
1106
|
+
const commitPlan = (kind, message) => {
|
|
855
1107
|
if (kind === 'fixup') {
|
|
856
1108
|
const target = (message ?? '').trim() || 'HEAD';
|
|
857
|
-
|
|
858
|
-
requireOk(result);
|
|
859
|
-
return;
|
|
1109
|
+
return { args: ['commit', `--fixup=${target}`], body: undefined };
|
|
860
1110
|
}
|
|
861
1111
|
const args = ['commit'];
|
|
862
1112
|
if (kind === 'amend') args.push('--amend');
|
|
863
1113
|
args.push('-F', '-');
|
|
864
1114
|
const text = message ?? '';
|
|
865
1115
|
const body = text.endsWith('\n') ? text : `${text}\n`;
|
|
866
|
-
|
|
867
|
-
|
|
1116
|
+
return { args, body };
|
|
1117
|
+
};
|
|
1118
|
+
|
|
1119
|
+
const commitChanges = (top, kind, message) => {
|
|
1120
|
+
const plan = commitPlan(kind, message);
|
|
1121
|
+
requireOk(runGit(plan.args, top, plan.body));
|
|
1122
|
+
};
|
|
1123
|
+
|
|
1124
|
+
const commitChangesAsync = async (top, kind, message) => {
|
|
1125
|
+
const plan = commitPlan(kind, message);
|
|
1126
|
+
requireOk(await runGitAsync(plan.args, top, { input: plan.body }));
|
|
868
1127
|
};
|
|
869
1128
|
|
|
870
1129
|
const revertFile = (top, rel, items) => {
|
|
@@ -886,11 +1145,24 @@ const createGitRepo = () => ({
|
|
|
886
1145
|
toplevel,
|
|
887
1146
|
resolveRev,
|
|
888
1147
|
add: addItem,
|
|
1148
|
+
addAsync: addItemAsync,
|
|
889
1149
|
unstage: unstageItem,
|
|
890
1150
|
revert: revertItem,
|
|
891
1151
|
revertFile,
|
|
892
1152
|
commit: commitChanges,
|
|
1153
|
+
commitAsync: commitChangesAsync,
|
|
1154
|
+
hasStaged,
|
|
893
1155
|
lastMessage,
|
|
1156
|
+
listBranches,
|
|
1157
|
+
currentBranch,
|
|
1158
|
+
checkout: checkoutBranch,
|
|
1159
|
+
checkoutAsync: checkoutBranchAsync,
|
|
1160
|
+
createBranch,
|
|
1161
|
+
createBranchAsync,
|
|
1162
|
+
pull: pullChanges,
|
|
1163
|
+
pullAsync: pullChangesAsync,
|
|
1164
|
+
push: pushChanges,
|
|
1165
|
+
pushAsync: pushChangesAsync,
|
|
894
1166
|
});
|
|
895
1167
|
|
|
896
1168
|
module.exports = {
|
|
@@ -901,6 +1173,18 @@ module.exports = {
|
|
|
901
1173
|
unstageItem,
|
|
902
1174
|
revertItem,
|
|
903
1175
|
commitChanges,
|
|
1176
|
+
commitChangesAsync,
|
|
1177
|
+
hasStaged,
|
|
1178
|
+
currentBranch,
|
|
904
1179
|
lastMessage,
|
|
1180
|
+
listBranches,
|
|
1181
|
+
checkoutBranch,
|
|
1182
|
+
checkoutBranchAsync,
|
|
1183
|
+
createBranch,
|
|
1184
|
+
createBranchAsync,
|
|
1185
|
+
pullChanges,
|
|
1186
|
+
pullChangesAsync,
|
|
1187
|
+
pushChanges,
|
|
1188
|
+
pushChangesAsync,
|
|
905
1189
|
createGitRepo,
|
|
906
1190
|
};
|