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 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` | Stage this diff (git add) |
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
- | `f` | Feedback on this diff |
59
- | `t` | New todo |
60
- | `e` | Edit added lines |
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 openRemoteSession = async (proc, deps, args, selected, env) => {
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 loaded = await loader(remote.target(selected), {
152
- cwd,
153
- paths: selected.paths,
154
- token,
155
- fetch: deps.fetch,
156
- });
157
- const change = loaded.change ?? null;
158
- const repository = change ? change.repository : '';
159
- const repo = deps.repo ?? createLoadedSource(loaded);
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 LANG = {
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 LANG[ext] || 'txt';
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') group.unstaged += 1;
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 child = spawn(cmd, args, spawnBase({ cwd, env }));
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
- spawnBase({
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
- spawnBase({
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 applyProposedUpdate = (top, item) => {
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
- return { top, parsed };
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
- return { top, parsed, rev: commit, revShort };
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 commitChanges = (top, kind, message) => {
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
- const result = runGit(['commit', `--fixup=${target}`], top);
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
- const result = runGit(args, top, body);
867
- requireOk(result);
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
  };