git-coco 0.62.0 → 0.62.2
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/dist/index.esm.mjs +126 -48
- package/dist/index.js +126 -48
- package/package.json +1 -1
package/dist/index.esm.mjs
CHANGED
|
@@ -61,7 +61,7 @@ import { pathToFileURL } from 'url';
|
|
|
61
61
|
/**
|
|
62
62
|
* Current build version from package.json
|
|
63
63
|
*/
|
|
64
|
-
const BUILD_VERSION = "0.62.
|
|
64
|
+
const BUILD_VERSION = "0.62.2";
|
|
65
65
|
|
|
66
66
|
const isInteractive = (config) => {
|
|
67
67
|
return config?.mode === 'interactive' || !!config?.interactive;
|
|
@@ -25162,12 +25162,14 @@ function formatSortIndicator(mode, options = {}) {
|
|
|
25162
25162
|
}
|
|
25163
25163
|
|
|
25164
25164
|
/**
|
|
25165
|
-
* True when `pending` (a `state.
|
|
25166
|
-
*
|
|
25167
|
-
*
|
|
25168
|
-
*
|
|
25165
|
+
* True when `pending` (a `state.pendingItemAction`) targets this exact
|
|
25166
|
+
* row. Action-agnostic on purpose — every surface + the sidebar render
|
|
25167
|
+
* the same spinner whether the row is being deleted or checked out, so
|
|
25168
|
+
* the spinner-swap test stays identical everywhere. Takes the field
|
|
25169
|
+
* value (not the whole state) so it can live next to the type without a
|
|
25170
|
+
* forward reference.
|
|
25169
25171
|
*/
|
|
25170
|
-
function
|
|
25172
|
+
function isPendingItemAction(pending, kind, id) {
|
|
25171
25173
|
return pending?.kind === kind && pending.id === id;
|
|
25172
25174
|
}
|
|
25173
25175
|
const DEFAULT_CHANGELOG_VIEW_STATE = {
|
|
@@ -26375,10 +26377,11 @@ function applyLogInkAction(state, action) {
|
|
|
26375
26377
|
workflowActionId: action.value ? undefined : state.workflowActionId,
|
|
26376
26378
|
pendingKey: undefined,
|
|
26377
26379
|
};
|
|
26378
|
-
case '
|
|
26379
|
-
// Pure marker for the in-flight
|
|
26380
|
-
//
|
|
26381
|
-
|
|
26380
|
+
case 'setPendingItemAction':
|
|
26381
|
+
// Pure marker for the in-flight row action (delete / checkout);
|
|
26382
|
+
// touches nothing else so the list keeps rendering normally
|
|
26383
|
+
// underneath the one spinner'd row.
|
|
26384
|
+
return { ...state, pendingItemAction: action.value };
|
|
26382
26385
|
case 'toggleFilterMode':
|
|
26383
26386
|
return {
|
|
26384
26387
|
...state,
|
|
@@ -31001,6 +31004,29 @@ function deleteBranch(git, branch, force = false) {
|
|
|
31001
31004
|
function isBranchNotFullyMergedError(message) {
|
|
31002
31005
|
return /not fully merged/i.test(message || '');
|
|
31003
31006
|
}
|
|
31007
|
+
/**
|
|
31008
|
+
* True when a branch delete was rejected because the branch is checked
|
|
31009
|
+
* out in a worktree. Unlike "not fully merged" there's no force escape
|
|
31010
|
+
* hatch — git refuses `git branch -D` on a worktree-checked-out branch
|
|
31011
|
+
* too — so the UI should surface a clear "free up the worktree first"
|
|
31012
|
+
* message rather than offering a force-delete that would fail the same
|
|
31013
|
+
* way. Matches git's wording: `Cannot delete branch 'x' checked out at
|
|
31014
|
+
* '<path>'` / `used by worktree at '<path>'`.
|
|
31015
|
+
*/
|
|
31016
|
+
function isBranchCheckedOutElsewhereError(message) {
|
|
31017
|
+
return /checked out at|used by worktree/i.test(message || '');
|
|
31018
|
+
}
|
|
31019
|
+
/**
|
|
31020
|
+
* Pull the worktree path out of git's "checked out at '<path>'" /
|
|
31021
|
+
* "used by worktree at '<path>'" rejection so the UI can name where the
|
|
31022
|
+
* branch is still in use. Returns undefined when the message doesn't
|
|
31023
|
+
* carry a path (older git phrasings) so callers can fall back to a
|
|
31024
|
+
* generic message.
|
|
31025
|
+
*/
|
|
31026
|
+
function parseCheckedOutWorktreePath(message) {
|
|
31027
|
+
const match = /(?:checked out at|used by worktree at) '([^']+)'/i.exec(message || '');
|
|
31028
|
+
return match?.[1];
|
|
31029
|
+
}
|
|
31004
31030
|
function fetchRemotes(git) {
|
|
31005
31031
|
return runAction$5(() => git.raw(['fetch', '--all', '--prune']), 'Fetched all remotes');
|
|
31006
31032
|
}
|
|
@@ -34412,7 +34438,7 @@ function renderActiveSidebarContent(h, Text, tab, state, context, contextStatus,
|
|
|
34412
34438
|
// shows this spinner in place of its leading marker (branches /
|
|
34413
34439
|
// worktrees) or appended to the row (tags / stashes, which have no
|
|
34414
34440
|
// leading status icon). `pending` is the single in-flight target.
|
|
34415
|
-
const pending = state.
|
|
34441
|
+
const pending = state.pendingItemAction;
|
|
34416
34442
|
const spin = inlineSpinnerGlyph(spinnerFrame, theme.ascii);
|
|
34417
34443
|
// Available rows for the active tab's list. The sidebar chrome
|
|
34418
34444
|
// takes ~10 rows (panel title + spacer + 5 tab headers + 4 inter-tab
|
|
@@ -34451,7 +34477,7 @@ function renderActiveSidebarContent(h, Text, tab, state, context, contextStatus,
|
|
|
34451
34477
|
return [
|
|
34452
34478
|
...headerRows,
|
|
34453
34479
|
...renderSelectableSidebarRows(h, Text, sortedBranches, state.selectedBranchIndex, focused, width, theme, (branch) => {
|
|
34454
|
-
const glyph =
|
|
34480
|
+
const glyph = isPendingItemAction(pending, 'branch', branch.shortName)
|
|
34455
34481
|
? spin
|
|
34456
34482
|
: branchRowMarker(branch, { ascii: theme.ascii }).glyph;
|
|
34457
34483
|
return `${glyph} ${branch.shortName}`;
|
|
@@ -34470,7 +34496,7 @@ function renderActiveSidebarContent(h, Text, tab, state, context, contextStatus,
|
|
|
34470
34496
|
const base = `${truncateCells(tag.name, 16)} ${tag.subject}`;
|
|
34471
34497
|
// Tags have no leading status icon, so the pending spinner is
|
|
34472
34498
|
// appended to the row instead of replacing a glyph.
|
|
34473
|
-
return
|
|
34499
|
+
return isPendingItemAction(pending, 'tag', tag.name) ? `${base} ${spin}` : base;
|
|
34474
34500
|
}, 'tab-tags', visibleListCount);
|
|
34475
34501
|
}
|
|
34476
34502
|
if (tab === 'stashes') {
|
|
@@ -34485,7 +34511,7 @@ function renderActiveSidebarContent(h, Text, tab, state, context, contextStatus,
|
|
|
34485
34511
|
const base = `@{${index}} ${stash.message || '(no message)'}`;
|
|
34486
34512
|
// `@{N}` is the stash ref, not a status icon, so append the
|
|
34487
34513
|
// spinner rather than replacing it.
|
|
34488
|
-
return
|
|
34514
|
+
return isPendingItemAction(pending, 'stash', stash.ref) ? `${base} ${spin}` : base;
|
|
34489
34515
|
}, 'tab-stashes', visibleListCount);
|
|
34490
34516
|
}
|
|
34491
34517
|
// worktrees
|
|
@@ -34497,7 +34523,7 @@ function renderActiveSidebarContent(h, Text, tab, state, context, contextStatus,
|
|
|
34497
34523
|
return [h(Text, { key: 'tab-worktrees-empty', dimColor: true }, ' No linked worktrees')];
|
|
34498
34524
|
}
|
|
34499
34525
|
return renderSelectableSidebarRows(h, Text, worktrees, state.selectedWorktreeListIndex, focused, width, theme, (worktree) => {
|
|
34500
|
-
const marker =
|
|
34526
|
+
const marker = isPendingItemAction(pending, 'worktree', worktree.path)
|
|
34501
34527
|
? spin
|
|
34502
34528
|
: worktree.current ? '*' : ' ';
|
|
34503
34529
|
const wstate = worktree.dirty ? 'dirty' : 'clean';
|
|
@@ -34933,7 +34959,7 @@ function renderBranchesSurface(ctx, spinnerFrame = 0) {
|
|
|
34933
34959
|
// While this branch's delete is in flight, its sync-state marker
|
|
34934
34960
|
// is replaced by an inline spinner (accent-coloured) so the row
|
|
34935
34961
|
// reads as "deleting" until it vanishes on refresh.
|
|
34936
|
-
const deleting =
|
|
34962
|
+
const deleting = isPendingItemAction(state.pendingItemAction, 'branch', branch.shortName);
|
|
34937
34963
|
const glyph = deleting ? inlineSpinnerGlyph(spinnerFrame, theme.ascii) : marker.glyph;
|
|
34938
34964
|
const glyphColor = deleting
|
|
34939
34965
|
? (theme.noColor ? undefined : theme.colors.accent)
|
|
@@ -38823,7 +38849,7 @@ function renderStashSurface(ctx, spinnerFrame = 0) {
|
|
|
38823
38849
|
// The `stash@{N}` ref is an identifier, not a status icon, so a
|
|
38824
38850
|
// delete-in-flight appends an accent spinner at the row's end
|
|
38825
38851
|
// (2 cells reserved from the width budget).
|
|
38826
|
-
const deleting =
|
|
38852
|
+
const deleting = isPendingItemAction(state.pendingItemAction, 'stash', stash.ref);
|
|
38827
38853
|
const spinnerSpan = deleting
|
|
38828
38854
|
? h(Text, { color: theme.noColor ? undefined : theme.colors.accent, dimColor: false }, ` ${inlineSpinnerGlyph(spinnerFrame, theme.ascii)}`)
|
|
38829
38855
|
: null;
|
|
@@ -39223,7 +39249,7 @@ function renderTagsSurface(ctx, spinnerFrame = 0) {
|
|
|
39223
39249
|
// Tags have no leading status icon, so a delete-in-flight appends
|
|
39224
39250
|
// an accent spinner at the row's end. Reserve its 2 cells from the
|
|
39225
39251
|
// truncation budget so it never pushes the row past the panel.
|
|
39226
|
-
const deleting =
|
|
39252
|
+
const deleting = isPendingItemAction(state.pendingItemAction, 'tag', tag.name);
|
|
39227
39253
|
const spinnerSpan = deleting
|
|
39228
39254
|
? h(Text, { color: theme.noColor ? undefined : theme.colors.accent, dimColor: false }, ` ${inlineSpinnerGlyph(spinnerFrame, theme.ascii)}`)
|
|
39229
39255
|
: null;
|
|
@@ -39305,7 +39331,7 @@ function renderWorktreesSurface(ctx, spinnerFrame = 0) {
|
|
|
39305
39331
|
const index = startIndex + offset;
|
|
39306
39332
|
const isSelected = index === selected;
|
|
39307
39333
|
const cursor = isSelected ? '>' : ' ';
|
|
39308
|
-
const marker =
|
|
39334
|
+
const marker = isPendingItemAction(state.pendingItemAction, 'worktree', entry.path)
|
|
39309
39335
|
? inlineSpinnerGlyph(spinnerFrame, theme.ascii)
|
|
39310
39336
|
: entry.current ? '*' : ' ';
|
|
39311
39337
|
const branchLabel = entry.branch ? entry.branch : entry.head || '<detached>';
|
|
@@ -40774,15 +40800,28 @@ const REMOTE_OP_LOADERS = {
|
|
|
40774
40800
|
* Returns `undefined` for non-delete workflows (and when nothing is
|
|
40775
40801
|
* selected), which the runner treats as "no pending marker".
|
|
40776
40802
|
*/
|
|
40777
|
-
function
|
|
40803
|
+
function resolvePendingItemAction(id, state, context) {
|
|
40778
40804
|
const { filter } = state;
|
|
40805
|
+
// Checking out a branch gets the same inline spinner on its row as a
|
|
40806
|
+
// delete does — the action just runs `git checkout` instead of
|
|
40807
|
+
// `git branch -d`. Resolved the same way as the delete branch case
|
|
40808
|
+
// (and identically to the checkout-branch handler) so the spinner
|
|
40809
|
+
// lands on exactly the row the user pressed enter on.
|
|
40810
|
+
if (id === 'checkout-branch') {
|
|
40811
|
+
const all = sortBranches(context.branches?.localBranches || [], state.branchSort);
|
|
40812
|
+
const visible = filter
|
|
40813
|
+
? all.filter((b) => matchesPromotedFilter([b.shortName, b.upstream || ''], filter))
|
|
40814
|
+
: all;
|
|
40815
|
+
const branch = visible[Math.min(state.selectedBranchIndex, visible.length - 1)];
|
|
40816
|
+
return branch ? { kind: 'branch', id: branch.shortName, action: 'checkout' } : undefined;
|
|
40817
|
+
}
|
|
40779
40818
|
if (id === 'delete-branch' || id === 'force-delete-branch') {
|
|
40780
40819
|
const all = sortBranches(context.branches?.localBranches || [], state.branchSort);
|
|
40781
40820
|
const visible = filter
|
|
40782
40821
|
? all.filter((b) => matchesPromotedFilter([b.shortName, b.upstream || ''], filter))
|
|
40783
40822
|
: all;
|
|
40784
40823
|
const branch = visible[Math.min(state.selectedBranchIndex, visible.length - 1)];
|
|
40785
|
-
return branch ? { kind: 'branch', id: branch.shortName } : undefined;
|
|
40824
|
+
return branch ? { kind: 'branch', id: branch.shortName, action: 'delete' } : undefined;
|
|
40786
40825
|
}
|
|
40787
40826
|
if (id === 'delete-tag') {
|
|
40788
40827
|
const all = sortTags(context.tags?.tags || [], state.tagSort);
|
|
@@ -40790,7 +40829,7 @@ function resolvePendingDeletion(id, state, context) {
|
|
|
40790
40829
|
? all.filter((t) => matchesPromotedFilter([t.name, t.subject], filter))
|
|
40791
40830
|
: all;
|
|
40792
40831
|
const tag = visible[Math.min(state.selectedTagIndex, visible.length - 1)];
|
|
40793
|
-
return tag ? { kind: 'tag', id: tag.name } : undefined;
|
|
40832
|
+
return tag ? { kind: 'tag', id: tag.name, action: 'delete' } : undefined;
|
|
40794
40833
|
}
|
|
40795
40834
|
if (id === 'drop-stash') {
|
|
40796
40835
|
const all = context.stashes?.stashes || [];
|
|
@@ -40798,7 +40837,7 @@ function resolvePendingDeletion(id, state, context) {
|
|
|
40798
40837
|
? all.filter((s) => matchesPromotedFilter([s.ref, s.message], filter))
|
|
40799
40838
|
: all;
|
|
40800
40839
|
const stash = visible[Math.min(state.selectedStashIndex, visible.length - 1)];
|
|
40801
|
-
return stash ? { kind: 'stash', id: stash.ref } : undefined;
|
|
40840
|
+
return stash ? { kind: 'stash', id: stash.ref, action: 'delete' } : undefined;
|
|
40802
40841
|
}
|
|
40803
40842
|
if (id === 'remove-worktree') {
|
|
40804
40843
|
const all = context.worktreeList?.worktrees || [];
|
|
@@ -40808,7 +40847,7 @@ function resolvePendingDeletion(id, state, context) {
|
|
|
40808
40847
|
const wt = visible.length
|
|
40809
40848
|
? visible[Math.min(state.selectedWorktreeListIndex, visible.length - 1)]
|
|
40810
40849
|
: all[Math.min(state.selectedWorktreeListIndex, Math.max(0, all.length - 1))];
|
|
40811
|
-
return wt ? { kind: 'worktree', id: wt.path } : undefined;
|
|
40850
|
+
return wt ? { kind: 'worktree', id: wt.path, action: 'delete' } : undefined;
|
|
40812
40851
|
}
|
|
40813
40852
|
return undefined;
|
|
40814
40853
|
}
|
|
@@ -41129,9 +41168,10 @@ function LogInkApp(deps) {
|
|
|
41129
41168
|
state.commitCompose.loading ||
|
|
41130
41169
|
Boolean(state.remoteOp) ||
|
|
41131
41170
|
Boolean(state.statusLoading) ||
|
|
41132
|
-
// Keep the shared spinner ticking while a list-item delete
|
|
41133
|
-
// flight so its inline pending glyph animates
|
|
41134
|
-
|
|
41171
|
+
// Keep the shared spinner ticking while a list-item action (delete
|
|
41172
|
+
// or checkout) is in flight so its inline pending glyph animates
|
|
41173
|
+
// instead of freezing.
|
|
41174
|
+
Boolean(state.pendingItemAction);
|
|
41135
41175
|
React.useEffect(() => {
|
|
41136
41176
|
if (!anyLoading) {
|
|
41137
41177
|
// Reset to 0 so the next loading state starts from a known
|
|
@@ -44071,14 +44111,15 @@ function LogInkApp(deps) {
|
|
|
44071
44111
|
if (remoteOp) {
|
|
44072
44112
|
dispatch({ type: 'setRemoteOp', value: remoteOp });
|
|
44073
44113
|
}
|
|
44074
|
-
// Mark the cursored row as
|
|
44075
|
-
// spinner while the git call runs. Cleared in
|
|
44076
|
-
// refresh, so a successful delete hands straight
|
|
44077
|
-
// vanishing,
|
|
44078
|
-
// the
|
|
44079
|
-
|
|
44080
|
-
|
|
44081
|
-
|
|
44114
|
+
// Mark the cursored row as busy so it shows an inline pending
|
|
44115
|
+
// spinner while the git call runs (delete or checkout). Cleared in
|
|
44116
|
+
// `finally` after the refresh, so a successful delete hands straight
|
|
44117
|
+
// off to the row vanishing, a checkout to the sidebar repainting
|
|
44118
|
+
// with the new current branch, and a failure (e.g. an unmerged
|
|
44119
|
+
// branch) restores the row's normal icon alongside the error status.
|
|
44120
|
+
const pendingItemAction = resolvePendingItemAction(id, state, context);
|
|
44121
|
+
if (pendingItemAction) {
|
|
44122
|
+
dispatch({ type: 'setPendingItemAction', value: pendingItemAction });
|
|
44082
44123
|
}
|
|
44083
44124
|
try {
|
|
44084
44125
|
const result = await handler();
|
|
@@ -44091,6 +44132,24 @@ function LogInkApp(deps) {
|
|
|
44091
44132
|
if (id === 'delete-branch' && !result?.ok && isBranchNotFullyMergedError(result?.message)) {
|
|
44092
44133
|
dispatch({ type: 'setPendingConfirmation', value: 'force-delete-branch' });
|
|
44093
44134
|
}
|
|
44135
|
+
// A branch checked out in a worktree can't be deleted — and unlike
|
|
44136
|
+
// the unmerged case, `git branch -D` won't force it either, so we
|
|
44137
|
+
// don't offer a confirmation. Replace git's raw rejection with a
|
|
44138
|
+
// clear "free up the worktree first" message that names where the
|
|
44139
|
+
// branch is still in use.
|
|
44140
|
+
if ((id === 'delete-branch' || id === 'force-delete-branch') &&
|
|
44141
|
+
!result?.ok &&
|
|
44142
|
+
isBranchCheckedOutElsewhereError(result?.message)) {
|
|
44143
|
+
const worktreePath = parseCheckedOutWorktreePath(result?.message);
|
|
44144
|
+
const branchName = pendingItemAction?.id;
|
|
44145
|
+
dispatch({
|
|
44146
|
+
type: 'setStatus',
|
|
44147
|
+
value: worktreePath
|
|
44148
|
+
? `Can't delete ${branchName ? `'${branchName}'` : 'branch'} — checked out in worktree ${worktreePath}. Switch that worktree off the branch or remove it first.`
|
|
44149
|
+
: `Can't delete ${branchName ? `'${branchName}'` : 'branch'} — it's checked out in another worktree. Switch that worktree off the branch or remove it first.`,
|
|
44150
|
+
kind: 'warning',
|
|
44151
|
+
});
|
|
44152
|
+
}
|
|
44094
44153
|
// Refresh history rows AS WELL when the workflow could have
|
|
44095
44154
|
// changed the commits the user sees (#945 follow-up). The
|
|
44096
44155
|
// workflow IDs below all either create/rewrite local commits or
|
|
@@ -44126,15 +44185,18 @@ function LogInkApp(deps) {
|
|
|
44126
44185
|
if (result?.ok && historyMutatingIds.has(id)) {
|
|
44127
44186
|
await refreshHistoryRows();
|
|
44128
44187
|
}
|
|
44129
|
-
// Checkout-branch
|
|
44130
|
-
// refresh
|
|
44131
|
-
//
|
|
44132
|
-
//
|
|
44133
|
-
//
|
|
44134
|
-
// the
|
|
44188
|
+
// Checkout-branch snaps the cursor to position 0 first so when the
|
|
44189
|
+
// refresh completes and the new current branch lands at the top
|
|
44190
|
+
// (per #809's pin-current rule), the cursor is already there
|
|
44191
|
+
// waiting. The refresh is *silent*: the loud refresh used to blank
|
|
44192
|
+
// every branch name behind a "loading branches…" placeholder (#806),
|
|
44193
|
+
// but the in-flight row now carries its own inline pending spinner
|
|
44194
|
+
// (resolvePendingItemAction → action 'checkout'), so a silent
|
|
44195
|
+
// stale-while-revalidate swap keeps the list readable and just
|
|
44196
|
+
// repaints the current-branch marker once the new context lands.
|
|
44135
44197
|
if (id === 'checkout-branch' && result?.ok) {
|
|
44136
44198
|
dispatch({ type: 'resetBranchSelection' });
|
|
44137
|
-
await refreshContext();
|
|
44199
|
+
await refreshContext({ silent: true });
|
|
44138
44200
|
}
|
|
44139
44201
|
else {
|
|
44140
44202
|
// Silent refresh so the deleted item disappears from the list
|
|
@@ -44189,11 +44251,11 @@ function LogInkApp(deps) {
|
|
|
44189
44251
|
if (remoteOp) {
|
|
44190
44252
|
dispatch({ type: 'setRemoteOp', value: undefined });
|
|
44191
44253
|
}
|
|
44192
|
-
// Same guarantee for the per-row
|
|
44193
|
-
// the
|
|
44194
|
-
// left spinning forever.
|
|
44195
|
-
if (
|
|
44196
|
-
dispatch({ type: '
|
|
44254
|
+
// Same guarantee for the per-row pending spinner (delete or
|
|
44255
|
+
// checkout): clear it whether the action succeeded, failed, or the
|
|
44256
|
+
// refresh threw, so no row is left spinning forever.
|
|
44257
|
+
if (pendingItemAction) {
|
|
44258
|
+
dispatch({ type: 'setPendingItemAction', value: undefined });
|
|
44197
44259
|
}
|
|
44198
44260
|
}
|
|
44199
44261
|
}, [context, dispatch, git, refreshContext, refreshHistoryRows, refreshWorktreeContext,
|
|
@@ -45365,12 +45427,23 @@ function createLogArgvFromUiArgv(argv) {
|
|
|
45365
45427
|
// starting state. `coco ui --no-all` opts back to
|
|
45366
45428
|
// current-branch-only.
|
|
45367
45429
|
//
|
|
45430
|
+
// `?? true` re-asserts that default for the synthetic-argv path:
|
|
45431
|
+
// bare `coco` routes through defaultRouteHandler → buildSyntheticArgv,
|
|
45432
|
+
// which bypasses yargs and so never applies the `default: true` from
|
|
45433
|
+
// ui/config.ts — leaving `argv.all` undefined (#1169). Without the
|
|
45434
|
+
// fallback the workstation booted in compact (`--first-parent
|
|
45435
|
+
// --no-merges`) mode: fewer commits, branches "ahead" of HEAD hidden,
|
|
45436
|
+
// and cursoring/checking-out a branch whose tip wasn't in the compact
|
|
45437
|
+
// window triggered an anchored-context append that looped the graph
|
|
45438
|
+
// back to the initial commit. Explicit `--no-all` (argv.all === false)
|
|
45439
|
+
// is preserved because `false ?? true === false`.
|
|
45440
|
+
//
|
|
45368
45441
|
// Note: passing `--branch foo` does NOT automatically scope away
|
|
45369
45442
|
// from --all. If the user wants strictly that branch, they pass
|
|
45370
45443
|
// `coco ui --branch foo --no-all`. We considered the implicit
|
|
45371
45444
|
// scope-narrowing but it surprises users who pass `--branch` as
|
|
45372
45445
|
// a "highlight this branch in the all-refs view" hint.
|
|
45373
|
-
all: argv.all,
|
|
45446
|
+
all: argv.all ?? true,
|
|
45374
45447
|
branch: argv.branch,
|
|
45375
45448
|
format: 'table',
|
|
45376
45449
|
interactive: true,
|
|
@@ -50384,6 +50457,11 @@ async function probeIsGitRepo() {
|
|
|
50384
50457
|
* `InitArgv`, `UiArgv`, `WorkspaceArgv`) so we can't just spread the
|
|
50385
50458
|
* raw default argv — we have to project the shared fields and let
|
|
50386
50459
|
* the handler fill in command-specific defaults.
|
|
50460
|
+
*
|
|
50461
|
+
* Exported for testing: this is the path bare `coco` takes to the
|
|
50462
|
+
* workstation, and it intentionally does NOT set `all` — yargs's
|
|
50463
|
+
* `default: true` for `coco ui` never runs here, so the ui→log argv
|
|
50464
|
+
* mapping has to re-assert that default (#1169).
|
|
50387
50465
|
*/
|
|
50388
50466
|
function buildSyntheticArgv(argv) {
|
|
50389
50467
|
return {
|
package/dist/index.js
CHANGED
|
@@ -78,7 +78,7 @@ var readline__namespace = /*#__PURE__*/_interopNamespaceDefault(readline);
|
|
|
78
78
|
/**
|
|
79
79
|
* Current build version from package.json
|
|
80
80
|
*/
|
|
81
|
-
const BUILD_VERSION = "0.62.
|
|
81
|
+
const BUILD_VERSION = "0.62.2";
|
|
82
82
|
|
|
83
83
|
const isInteractive = (config) => {
|
|
84
84
|
return config?.mode === 'interactive' || !!config?.interactive;
|
|
@@ -25179,12 +25179,14 @@ function formatSortIndicator(mode, options = {}) {
|
|
|
25179
25179
|
}
|
|
25180
25180
|
|
|
25181
25181
|
/**
|
|
25182
|
-
* True when `pending` (a `state.
|
|
25183
|
-
*
|
|
25184
|
-
*
|
|
25185
|
-
*
|
|
25182
|
+
* True when `pending` (a `state.pendingItemAction`) targets this exact
|
|
25183
|
+
* row. Action-agnostic on purpose — every surface + the sidebar render
|
|
25184
|
+
* the same spinner whether the row is being deleted or checked out, so
|
|
25185
|
+
* the spinner-swap test stays identical everywhere. Takes the field
|
|
25186
|
+
* value (not the whole state) so it can live next to the type without a
|
|
25187
|
+
* forward reference.
|
|
25186
25188
|
*/
|
|
25187
|
-
function
|
|
25189
|
+
function isPendingItemAction(pending, kind, id) {
|
|
25188
25190
|
return pending?.kind === kind && pending.id === id;
|
|
25189
25191
|
}
|
|
25190
25192
|
const DEFAULT_CHANGELOG_VIEW_STATE = {
|
|
@@ -26392,10 +26394,11 @@ function applyLogInkAction(state, action) {
|
|
|
26392
26394
|
workflowActionId: action.value ? undefined : state.workflowActionId,
|
|
26393
26395
|
pendingKey: undefined,
|
|
26394
26396
|
};
|
|
26395
|
-
case '
|
|
26396
|
-
// Pure marker for the in-flight
|
|
26397
|
-
//
|
|
26398
|
-
|
|
26397
|
+
case 'setPendingItemAction':
|
|
26398
|
+
// Pure marker for the in-flight row action (delete / checkout);
|
|
26399
|
+
// touches nothing else so the list keeps rendering normally
|
|
26400
|
+
// underneath the one spinner'd row.
|
|
26401
|
+
return { ...state, pendingItemAction: action.value };
|
|
26399
26402
|
case 'toggleFilterMode':
|
|
26400
26403
|
return {
|
|
26401
26404
|
...state,
|
|
@@ -31018,6 +31021,29 @@ function deleteBranch(git, branch, force = false) {
|
|
|
31018
31021
|
function isBranchNotFullyMergedError(message) {
|
|
31019
31022
|
return /not fully merged/i.test(message || '');
|
|
31020
31023
|
}
|
|
31024
|
+
/**
|
|
31025
|
+
* True when a branch delete was rejected because the branch is checked
|
|
31026
|
+
* out in a worktree. Unlike "not fully merged" there's no force escape
|
|
31027
|
+
* hatch — git refuses `git branch -D` on a worktree-checked-out branch
|
|
31028
|
+
* too — so the UI should surface a clear "free up the worktree first"
|
|
31029
|
+
* message rather than offering a force-delete that would fail the same
|
|
31030
|
+
* way. Matches git's wording: `Cannot delete branch 'x' checked out at
|
|
31031
|
+
* '<path>'` / `used by worktree at '<path>'`.
|
|
31032
|
+
*/
|
|
31033
|
+
function isBranchCheckedOutElsewhereError(message) {
|
|
31034
|
+
return /checked out at|used by worktree/i.test(message || '');
|
|
31035
|
+
}
|
|
31036
|
+
/**
|
|
31037
|
+
* Pull the worktree path out of git's "checked out at '<path>'" /
|
|
31038
|
+
* "used by worktree at '<path>'" rejection so the UI can name where the
|
|
31039
|
+
* branch is still in use. Returns undefined when the message doesn't
|
|
31040
|
+
* carry a path (older git phrasings) so callers can fall back to a
|
|
31041
|
+
* generic message.
|
|
31042
|
+
*/
|
|
31043
|
+
function parseCheckedOutWorktreePath(message) {
|
|
31044
|
+
const match = /(?:checked out at|used by worktree at) '([^']+)'/i.exec(message || '');
|
|
31045
|
+
return match?.[1];
|
|
31046
|
+
}
|
|
31021
31047
|
function fetchRemotes(git) {
|
|
31022
31048
|
return runAction$5(() => git.raw(['fetch', '--all', '--prune']), 'Fetched all remotes');
|
|
31023
31049
|
}
|
|
@@ -34429,7 +34455,7 @@ function renderActiveSidebarContent(h, Text, tab, state, context, contextStatus,
|
|
|
34429
34455
|
// shows this spinner in place of its leading marker (branches /
|
|
34430
34456
|
// worktrees) or appended to the row (tags / stashes, which have no
|
|
34431
34457
|
// leading status icon). `pending` is the single in-flight target.
|
|
34432
|
-
const pending = state.
|
|
34458
|
+
const pending = state.pendingItemAction;
|
|
34433
34459
|
const spin = inlineSpinnerGlyph(spinnerFrame, theme.ascii);
|
|
34434
34460
|
// Available rows for the active tab's list. The sidebar chrome
|
|
34435
34461
|
// takes ~10 rows (panel title + spacer + 5 tab headers + 4 inter-tab
|
|
@@ -34468,7 +34494,7 @@ function renderActiveSidebarContent(h, Text, tab, state, context, contextStatus,
|
|
|
34468
34494
|
return [
|
|
34469
34495
|
...headerRows,
|
|
34470
34496
|
...renderSelectableSidebarRows(h, Text, sortedBranches, state.selectedBranchIndex, focused, width, theme, (branch) => {
|
|
34471
|
-
const glyph =
|
|
34497
|
+
const glyph = isPendingItemAction(pending, 'branch', branch.shortName)
|
|
34472
34498
|
? spin
|
|
34473
34499
|
: branchRowMarker(branch, { ascii: theme.ascii }).glyph;
|
|
34474
34500
|
return `${glyph} ${branch.shortName}`;
|
|
@@ -34487,7 +34513,7 @@ function renderActiveSidebarContent(h, Text, tab, state, context, contextStatus,
|
|
|
34487
34513
|
const base = `${truncateCells(tag.name, 16)} ${tag.subject}`;
|
|
34488
34514
|
// Tags have no leading status icon, so the pending spinner is
|
|
34489
34515
|
// appended to the row instead of replacing a glyph.
|
|
34490
|
-
return
|
|
34516
|
+
return isPendingItemAction(pending, 'tag', tag.name) ? `${base} ${spin}` : base;
|
|
34491
34517
|
}, 'tab-tags', visibleListCount);
|
|
34492
34518
|
}
|
|
34493
34519
|
if (tab === 'stashes') {
|
|
@@ -34502,7 +34528,7 @@ function renderActiveSidebarContent(h, Text, tab, state, context, contextStatus,
|
|
|
34502
34528
|
const base = `@{${index}} ${stash.message || '(no message)'}`;
|
|
34503
34529
|
// `@{N}` is the stash ref, not a status icon, so append the
|
|
34504
34530
|
// spinner rather than replacing it.
|
|
34505
|
-
return
|
|
34531
|
+
return isPendingItemAction(pending, 'stash', stash.ref) ? `${base} ${spin}` : base;
|
|
34506
34532
|
}, 'tab-stashes', visibleListCount);
|
|
34507
34533
|
}
|
|
34508
34534
|
// worktrees
|
|
@@ -34514,7 +34540,7 @@ function renderActiveSidebarContent(h, Text, tab, state, context, contextStatus,
|
|
|
34514
34540
|
return [h(Text, { key: 'tab-worktrees-empty', dimColor: true }, ' No linked worktrees')];
|
|
34515
34541
|
}
|
|
34516
34542
|
return renderSelectableSidebarRows(h, Text, worktrees, state.selectedWorktreeListIndex, focused, width, theme, (worktree) => {
|
|
34517
|
-
const marker =
|
|
34543
|
+
const marker = isPendingItemAction(pending, 'worktree', worktree.path)
|
|
34518
34544
|
? spin
|
|
34519
34545
|
: worktree.current ? '*' : ' ';
|
|
34520
34546
|
const wstate = worktree.dirty ? 'dirty' : 'clean';
|
|
@@ -34950,7 +34976,7 @@ function renderBranchesSurface(ctx, spinnerFrame = 0) {
|
|
|
34950
34976
|
// While this branch's delete is in flight, its sync-state marker
|
|
34951
34977
|
// is replaced by an inline spinner (accent-coloured) so the row
|
|
34952
34978
|
// reads as "deleting" until it vanishes on refresh.
|
|
34953
|
-
const deleting =
|
|
34979
|
+
const deleting = isPendingItemAction(state.pendingItemAction, 'branch', branch.shortName);
|
|
34954
34980
|
const glyph = deleting ? inlineSpinnerGlyph(spinnerFrame, theme.ascii) : marker.glyph;
|
|
34955
34981
|
const glyphColor = deleting
|
|
34956
34982
|
? (theme.noColor ? undefined : theme.colors.accent)
|
|
@@ -38840,7 +38866,7 @@ function renderStashSurface(ctx, spinnerFrame = 0) {
|
|
|
38840
38866
|
// The `stash@{N}` ref is an identifier, not a status icon, so a
|
|
38841
38867
|
// delete-in-flight appends an accent spinner at the row's end
|
|
38842
38868
|
// (2 cells reserved from the width budget).
|
|
38843
|
-
const deleting =
|
|
38869
|
+
const deleting = isPendingItemAction(state.pendingItemAction, 'stash', stash.ref);
|
|
38844
38870
|
const spinnerSpan = deleting
|
|
38845
38871
|
? h(Text, { color: theme.noColor ? undefined : theme.colors.accent, dimColor: false }, ` ${inlineSpinnerGlyph(spinnerFrame, theme.ascii)}`)
|
|
38846
38872
|
: null;
|
|
@@ -39240,7 +39266,7 @@ function renderTagsSurface(ctx, spinnerFrame = 0) {
|
|
|
39240
39266
|
// Tags have no leading status icon, so a delete-in-flight appends
|
|
39241
39267
|
// an accent spinner at the row's end. Reserve its 2 cells from the
|
|
39242
39268
|
// truncation budget so it never pushes the row past the panel.
|
|
39243
|
-
const deleting =
|
|
39269
|
+
const deleting = isPendingItemAction(state.pendingItemAction, 'tag', tag.name);
|
|
39244
39270
|
const spinnerSpan = deleting
|
|
39245
39271
|
? h(Text, { color: theme.noColor ? undefined : theme.colors.accent, dimColor: false }, ` ${inlineSpinnerGlyph(spinnerFrame, theme.ascii)}`)
|
|
39246
39272
|
: null;
|
|
@@ -39322,7 +39348,7 @@ function renderWorktreesSurface(ctx, spinnerFrame = 0) {
|
|
|
39322
39348
|
const index = startIndex + offset;
|
|
39323
39349
|
const isSelected = index === selected;
|
|
39324
39350
|
const cursor = isSelected ? '>' : ' ';
|
|
39325
|
-
const marker =
|
|
39351
|
+
const marker = isPendingItemAction(state.pendingItemAction, 'worktree', entry.path)
|
|
39326
39352
|
? inlineSpinnerGlyph(spinnerFrame, theme.ascii)
|
|
39327
39353
|
: entry.current ? '*' : ' ';
|
|
39328
39354
|
const branchLabel = entry.branch ? entry.branch : entry.head || '<detached>';
|
|
@@ -40791,15 +40817,28 @@ const REMOTE_OP_LOADERS = {
|
|
|
40791
40817
|
* Returns `undefined` for non-delete workflows (and when nothing is
|
|
40792
40818
|
* selected), which the runner treats as "no pending marker".
|
|
40793
40819
|
*/
|
|
40794
|
-
function
|
|
40820
|
+
function resolvePendingItemAction(id, state, context) {
|
|
40795
40821
|
const { filter } = state;
|
|
40822
|
+
// Checking out a branch gets the same inline spinner on its row as a
|
|
40823
|
+
// delete does — the action just runs `git checkout` instead of
|
|
40824
|
+
// `git branch -d`. Resolved the same way as the delete branch case
|
|
40825
|
+
// (and identically to the checkout-branch handler) so the spinner
|
|
40826
|
+
// lands on exactly the row the user pressed enter on.
|
|
40827
|
+
if (id === 'checkout-branch') {
|
|
40828
|
+
const all = sortBranches(context.branches?.localBranches || [], state.branchSort);
|
|
40829
|
+
const visible = filter
|
|
40830
|
+
? all.filter((b) => matchesPromotedFilter([b.shortName, b.upstream || ''], filter))
|
|
40831
|
+
: all;
|
|
40832
|
+
const branch = visible[Math.min(state.selectedBranchIndex, visible.length - 1)];
|
|
40833
|
+
return branch ? { kind: 'branch', id: branch.shortName, action: 'checkout' } : undefined;
|
|
40834
|
+
}
|
|
40796
40835
|
if (id === 'delete-branch' || id === 'force-delete-branch') {
|
|
40797
40836
|
const all = sortBranches(context.branches?.localBranches || [], state.branchSort);
|
|
40798
40837
|
const visible = filter
|
|
40799
40838
|
? all.filter((b) => matchesPromotedFilter([b.shortName, b.upstream || ''], filter))
|
|
40800
40839
|
: all;
|
|
40801
40840
|
const branch = visible[Math.min(state.selectedBranchIndex, visible.length - 1)];
|
|
40802
|
-
return branch ? { kind: 'branch', id: branch.shortName } : undefined;
|
|
40841
|
+
return branch ? { kind: 'branch', id: branch.shortName, action: 'delete' } : undefined;
|
|
40803
40842
|
}
|
|
40804
40843
|
if (id === 'delete-tag') {
|
|
40805
40844
|
const all = sortTags(context.tags?.tags || [], state.tagSort);
|
|
@@ -40807,7 +40846,7 @@ function resolvePendingDeletion(id, state, context) {
|
|
|
40807
40846
|
? all.filter((t) => matchesPromotedFilter([t.name, t.subject], filter))
|
|
40808
40847
|
: all;
|
|
40809
40848
|
const tag = visible[Math.min(state.selectedTagIndex, visible.length - 1)];
|
|
40810
|
-
return tag ? { kind: 'tag', id: tag.name } : undefined;
|
|
40849
|
+
return tag ? { kind: 'tag', id: tag.name, action: 'delete' } : undefined;
|
|
40811
40850
|
}
|
|
40812
40851
|
if (id === 'drop-stash') {
|
|
40813
40852
|
const all = context.stashes?.stashes || [];
|
|
@@ -40815,7 +40854,7 @@ function resolvePendingDeletion(id, state, context) {
|
|
|
40815
40854
|
? all.filter((s) => matchesPromotedFilter([s.ref, s.message], filter))
|
|
40816
40855
|
: all;
|
|
40817
40856
|
const stash = visible[Math.min(state.selectedStashIndex, visible.length - 1)];
|
|
40818
|
-
return stash ? { kind: 'stash', id: stash.ref } : undefined;
|
|
40857
|
+
return stash ? { kind: 'stash', id: stash.ref, action: 'delete' } : undefined;
|
|
40819
40858
|
}
|
|
40820
40859
|
if (id === 'remove-worktree') {
|
|
40821
40860
|
const all = context.worktreeList?.worktrees || [];
|
|
@@ -40825,7 +40864,7 @@ function resolvePendingDeletion(id, state, context) {
|
|
|
40825
40864
|
const wt = visible.length
|
|
40826
40865
|
? visible[Math.min(state.selectedWorktreeListIndex, visible.length - 1)]
|
|
40827
40866
|
: all[Math.min(state.selectedWorktreeListIndex, Math.max(0, all.length - 1))];
|
|
40828
|
-
return wt ? { kind: 'worktree', id: wt.path } : undefined;
|
|
40867
|
+
return wt ? { kind: 'worktree', id: wt.path, action: 'delete' } : undefined;
|
|
40829
40868
|
}
|
|
40830
40869
|
return undefined;
|
|
40831
40870
|
}
|
|
@@ -41146,9 +41185,10 @@ function LogInkApp(deps) {
|
|
|
41146
41185
|
state.commitCompose.loading ||
|
|
41147
41186
|
Boolean(state.remoteOp) ||
|
|
41148
41187
|
Boolean(state.statusLoading) ||
|
|
41149
|
-
// Keep the shared spinner ticking while a list-item delete
|
|
41150
|
-
// flight so its inline pending glyph animates
|
|
41151
|
-
|
|
41188
|
+
// Keep the shared spinner ticking while a list-item action (delete
|
|
41189
|
+
// or checkout) is in flight so its inline pending glyph animates
|
|
41190
|
+
// instead of freezing.
|
|
41191
|
+
Boolean(state.pendingItemAction);
|
|
41152
41192
|
React.useEffect(() => {
|
|
41153
41193
|
if (!anyLoading) {
|
|
41154
41194
|
// Reset to 0 so the next loading state starts from a known
|
|
@@ -44088,14 +44128,15 @@ function LogInkApp(deps) {
|
|
|
44088
44128
|
if (remoteOp) {
|
|
44089
44129
|
dispatch({ type: 'setRemoteOp', value: remoteOp });
|
|
44090
44130
|
}
|
|
44091
|
-
// Mark the cursored row as
|
|
44092
|
-
// spinner while the git call runs. Cleared in
|
|
44093
|
-
// refresh, so a successful delete hands straight
|
|
44094
|
-
// vanishing,
|
|
44095
|
-
// the
|
|
44096
|
-
|
|
44097
|
-
|
|
44098
|
-
|
|
44131
|
+
// Mark the cursored row as busy so it shows an inline pending
|
|
44132
|
+
// spinner while the git call runs (delete or checkout). Cleared in
|
|
44133
|
+
// `finally` after the refresh, so a successful delete hands straight
|
|
44134
|
+
// off to the row vanishing, a checkout to the sidebar repainting
|
|
44135
|
+
// with the new current branch, and a failure (e.g. an unmerged
|
|
44136
|
+
// branch) restores the row's normal icon alongside the error status.
|
|
44137
|
+
const pendingItemAction = resolvePendingItemAction(id, state, context);
|
|
44138
|
+
if (pendingItemAction) {
|
|
44139
|
+
dispatch({ type: 'setPendingItemAction', value: pendingItemAction });
|
|
44099
44140
|
}
|
|
44100
44141
|
try {
|
|
44101
44142
|
const result = await handler();
|
|
@@ -44108,6 +44149,24 @@ function LogInkApp(deps) {
|
|
|
44108
44149
|
if (id === 'delete-branch' && !result?.ok && isBranchNotFullyMergedError(result?.message)) {
|
|
44109
44150
|
dispatch({ type: 'setPendingConfirmation', value: 'force-delete-branch' });
|
|
44110
44151
|
}
|
|
44152
|
+
// A branch checked out in a worktree can't be deleted — and unlike
|
|
44153
|
+
// the unmerged case, `git branch -D` won't force it either, so we
|
|
44154
|
+
// don't offer a confirmation. Replace git's raw rejection with a
|
|
44155
|
+
// clear "free up the worktree first" message that names where the
|
|
44156
|
+
// branch is still in use.
|
|
44157
|
+
if ((id === 'delete-branch' || id === 'force-delete-branch') &&
|
|
44158
|
+
!result?.ok &&
|
|
44159
|
+
isBranchCheckedOutElsewhereError(result?.message)) {
|
|
44160
|
+
const worktreePath = parseCheckedOutWorktreePath(result?.message);
|
|
44161
|
+
const branchName = pendingItemAction?.id;
|
|
44162
|
+
dispatch({
|
|
44163
|
+
type: 'setStatus',
|
|
44164
|
+
value: worktreePath
|
|
44165
|
+
? `Can't delete ${branchName ? `'${branchName}'` : 'branch'} — checked out in worktree ${worktreePath}. Switch that worktree off the branch or remove it first.`
|
|
44166
|
+
: `Can't delete ${branchName ? `'${branchName}'` : 'branch'} — it's checked out in another worktree. Switch that worktree off the branch or remove it first.`,
|
|
44167
|
+
kind: 'warning',
|
|
44168
|
+
});
|
|
44169
|
+
}
|
|
44111
44170
|
// Refresh history rows AS WELL when the workflow could have
|
|
44112
44171
|
// changed the commits the user sees (#945 follow-up). The
|
|
44113
44172
|
// workflow IDs below all either create/rewrite local commits or
|
|
@@ -44143,15 +44202,18 @@ function LogInkApp(deps) {
|
|
|
44143
44202
|
if (result?.ok && historyMutatingIds.has(id)) {
|
|
44144
44203
|
await refreshHistoryRows();
|
|
44145
44204
|
}
|
|
44146
|
-
// Checkout-branch
|
|
44147
|
-
// refresh
|
|
44148
|
-
//
|
|
44149
|
-
//
|
|
44150
|
-
//
|
|
44151
|
-
// the
|
|
44205
|
+
// Checkout-branch snaps the cursor to position 0 first so when the
|
|
44206
|
+
// refresh completes and the new current branch lands at the top
|
|
44207
|
+
// (per #809's pin-current rule), the cursor is already there
|
|
44208
|
+
// waiting. The refresh is *silent*: the loud refresh used to blank
|
|
44209
|
+
// every branch name behind a "loading branches…" placeholder (#806),
|
|
44210
|
+
// but the in-flight row now carries its own inline pending spinner
|
|
44211
|
+
// (resolvePendingItemAction → action 'checkout'), so a silent
|
|
44212
|
+
// stale-while-revalidate swap keeps the list readable and just
|
|
44213
|
+
// repaints the current-branch marker once the new context lands.
|
|
44152
44214
|
if (id === 'checkout-branch' && result?.ok) {
|
|
44153
44215
|
dispatch({ type: 'resetBranchSelection' });
|
|
44154
|
-
await refreshContext();
|
|
44216
|
+
await refreshContext({ silent: true });
|
|
44155
44217
|
}
|
|
44156
44218
|
else {
|
|
44157
44219
|
// Silent refresh so the deleted item disappears from the list
|
|
@@ -44206,11 +44268,11 @@ function LogInkApp(deps) {
|
|
|
44206
44268
|
if (remoteOp) {
|
|
44207
44269
|
dispatch({ type: 'setRemoteOp', value: undefined });
|
|
44208
44270
|
}
|
|
44209
|
-
// Same guarantee for the per-row
|
|
44210
|
-
// the
|
|
44211
|
-
// left spinning forever.
|
|
44212
|
-
if (
|
|
44213
|
-
dispatch({ type: '
|
|
44271
|
+
// Same guarantee for the per-row pending spinner (delete or
|
|
44272
|
+
// checkout): clear it whether the action succeeded, failed, or the
|
|
44273
|
+
// refresh threw, so no row is left spinning forever.
|
|
44274
|
+
if (pendingItemAction) {
|
|
44275
|
+
dispatch({ type: 'setPendingItemAction', value: undefined });
|
|
44214
44276
|
}
|
|
44215
44277
|
}
|
|
44216
44278
|
}, [context, dispatch, git, refreshContext, refreshHistoryRows, refreshWorktreeContext,
|
|
@@ -45382,12 +45444,23 @@ function createLogArgvFromUiArgv(argv) {
|
|
|
45382
45444
|
// starting state. `coco ui --no-all` opts back to
|
|
45383
45445
|
// current-branch-only.
|
|
45384
45446
|
//
|
|
45447
|
+
// `?? true` re-asserts that default for the synthetic-argv path:
|
|
45448
|
+
// bare `coco` routes through defaultRouteHandler → buildSyntheticArgv,
|
|
45449
|
+
// which bypasses yargs and so never applies the `default: true` from
|
|
45450
|
+
// ui/config.ts — leaving `argv.all` undefined (#1169). Without the
|
|
45451
|
+
// fallback the workstation booted in compact (`--first-parent
|
|
45452
|
+
// --no-merges`) mode: fewer commits, branches "ahead" of HEAD hidden,
|
|
45453
|
+
// and cursoring/checking-out a branch whose tip wasn't in the compact
|
|
45454
|
+
// window triggered an anchored-context append that looped the graph
|
|
45455
|
+
// back to the initial commit. Explicit `--no-all` (argv.all === false)
|
|
45456
|
+
// is preserved because `false ?? true === false`.
|
|
45457
|
+
//
|
|
45385
45458
|
// Note: passing `--branch foo` does NOT automatically scope away
|
|
45386
45459
|
// from --all. If the user wants strictly that branch, they pass
|
|
45387
45460
|
// `coco ui --branch foo --no-all`. We considered the implicit
|
|
45388
45461
|
// scope-narrowing but it surprises users who pass `--branch` as
|
|
45389
45462
|
// a "highlight this branch in the all-refs view" hint.
|
|
45390
|
-
all: argv.all,
|
|
45463
|
+
all: argv.all ?? true,
|
|
45391
45464
|
branch: argv.branch,
|
|
45392
45465
|
format: 'table',
|
|
45393
45466
|
interactive: true,
|
|
@@ -50401,6 +50474,11 @@ async function probeIsGitRepo() {
|
|
|
50401
50474
|
* `InitArgv`, `UiArgv`, `WorkspaceArgv`) so we can't just spread the
|
|
50402
50475
|
* raw default argv — we have to project the shared fields and let
|
|
50403
50476
|
* the handler fill in command-specific defaults.
|
|
50477
|
+
*
|
|
50478
|
+
* Exported for testing: this is the path bare `coco` takes to the
|
|
50479
|
+
* workstation, and it intentionally does NOT set `all` — yargs's
|
|
50480
|
+
* `default: true` for `coco ui` never runs here, so the ui→log argv
|
|
50481
|
+
* mapping has to re-assert that default (#1169).
|
|
50404
50482
|
*/
|
|
50405
50483
|
function buildSyntheticArgv(argv) {
|
|
50406
50484
|
return {
|