git-coco 0.62.3 → 0.62.4
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 +132 -12
- package/dist/index.js +132 -12
- 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.4";
|
|
65
65
|
|
|
66
66
|
const isInteractive = (config) => {
|
|
67
67
|
return config?.mode === 'interactive' || !!config?.interactive;
|
|
@@ -26368,6 +26368,8 @@ function applyLogInkAction(state, action) {
|
|
|
26368
26368
|
pendingMutationConfirmation: action.value ? undefined : state.pendingMutationConfirmation,
|
|
26369
26369
|
pendingKey: undefined,
|
|
26370
26370
|
};
|
|
26371
|
+
case 'setWorktreeCheckoutConflict':
|
|
26372
|
+
return { ...state, worktreeCheckoutConflict: action.value, pendingKey: undefined };
|
|
26371
26373
|
case 'setPendingMutationConfirmation':
|
|
26372
26374
|
return {
|
|
26373
26375
|
...state,
|
|
@@ -27740,7 +27742,45 @@ function getLogInkInputEvents(state, inputValue, key = {}, context = {}) {
|
|
|
27740
27742
|
return [];
|
|
27741
27743
|
}
|
|
27742
27744
|
if (state.pendingConfirmationId) {
|
|
27745
|
+
// Worktree-conflict removal options (#1175): alongside the y-switch,
|
|
27746
|
+
// `r` removes the conflicting worktree and checks the branch out
|
|
27747
|
+
// here, `x` removes the worktree AND deletes the branch. Both defer
|
|
27748
|
+
// to the runtime (it owns the git ops + the conflict context); the
|
|
27749
|
+
// runtime clears the conflict state once it resolves.
|
|
27750
|
+
if (state.pendingConfirmationId === 'switch-to-conflicting-worktree' && state.worktreeCheckoutConflict) {
|
|
27751
|
+
if (inputValue === 'r') {
|
|
27752
|
+
return [
|
|
27753
|
+
{ type: 'runWorkflowAction', id: 'conflict-remove-worktree-checkout' },
|
|
27754
|
+
action({ type: 'setPendingConfirmation', value: undefined }),
|
|
27755
|
+
];
|
|
27756
|
+
}
|
|
27757
|
+
if (inputValue === 'x') {
|
|
27758
|
+
return [
|
|
27759
|
+
{ type: 'runWorkflowAction', id: 'conflict-remove-worktree-branch' },
|
|
27760
|
+
action({ type: 'setPendingConfirmation', value: undefined }),
|
|
27761
|
+
];
|
|
27762
|
+
}
|
|
27763
|
+
}
|
|
27743
27764
|
if (inputValue === 'y') {
|
|
27765
|
+
// Worktree-conflict switch (#1175): the branch is already checked
|
|
27766
|
+
// out elsewhere, so "switch" just opens that worktree as a nested
|
|
27767
|
+
// repo frame (same mechanism as drilling into a submodule) — no
|
|
27768
|
+
// git mutation, hence handled here rather than via the runtime.
|
|
27769
|
+
if (state.pendingConfirmationId === 'switch-to-conflicting-worktree') {
|
|
27770
|
+
const conflict = state.worktreeCheckoutConflict;
|
|
27771
|
+
if (conflict) {
|
|
27772
|
+
return [
|
|
27773
|
+
action({ type: 'pushRepoFrame', label: conflict.branch, workdir: conflict.worktreePath }),
|
|
27774
|
+
action({ type: 'setStatus', value: `Switched to worktree ${conflict.worktreePath} (${conflict.branch})` }),
|
|
27775
|
+
action({ type: 'setPendingConfirmation', value: undefined }),
|
|
27776
|
+
action({ type: 'setWorktreeCheckoutConflict', value: undefined }),
|
|
27777
|
+
];
|
|
27778
|
+
}
|
|
27779
|
+
return [
|
|
27780
|
+
action({ type: 'setPendingConfirmation', value: undefined }),
|
|
27781
|
+
action({ type: 'setWorktreeCheckoutConflict', value: undefined }),
|
|
27782
|
+
];
|
|
27783
|
+
}
|
|
27744
27784
|
const workflowAction = getLogInkWorkflowActionById(state.pendingConfirmationId);
|
|
27745
27785
|
if (workflowAction?.id === 'ai-commit-summary') {
|
|
27746
27786
|
return [
|
|
@@ -27766,6 +27806,11 @@ function getLogInkInputEvents(state, inputValue, key = {}, context = {}) {
|
|
|
27766
27806
|
if (inputValue === 'n' || key.escape) {
|
|
27767
27807
|
return [
|
|
27768
27808
|
action({ type: 'setPendingConfirmation', value: undefined }),
|
|
27809
|
+
// Drop any worktree-conflict context so the prompt doesn't
|
|
27810
|
+
// linger after the user declines to switch.
|
|
27811
|
+
...(state.worktreeCheckoutConflict
|
|
27812
|
+
? [action({ type: 'setWorktreeCheckoutConflict', value: undefined })]
|
|
27813
|
+
: []),
|
|
27769
27814
|
action({ type: 'setStatus', value: 'workflow action cancelled' }),
|
|
27770
27815
|
];
|
|
27771
27816
|
}
|
|
@@ -37640,25 +37685,36 @@ function renderConfirmationPanel(h, components, state, width, theme, focused) {
|
|
|
37640
37685
|
: state.pendingMutationConfirmation === 'discard-draft'
|
|
37641
37686
|
? 'Quit and discard the in-progress commit draft'
|
|
37642
37687
|
: undefined;
|
|
37643
|
-
|
|
37688
|
+
// Worktree-conflict switch (#1175): a checkout was rejected because
|
|
37689
|
+
// the branch is checked out elsewhere — name the branch + worktree so
|
|
37690
|
+
// the prompt explains what "y" does (jump into that worktree).
|
|
37691
|
+
const conflict = state.worktreeCheckoutConflict;
|
|
37692
|
+
const isWorktreeConflict = state.pendingConfirmationId === 'switch-to-conflicting-worktree';
|
|
37693
|
+
const label = isWorktreeConflict && conflict
|
|
37694
|
+
? `Switch to the worktree where '${conflict.branch}' is checked out?`
|
|
37695
|
+
: action?.label || mutationLabel || 'Workflow action';
|
|
37644
37696
|
const warning = state.pendingMutationConfirmation === 'discard-draft'
|
|
37645
37697
|
? 'You have an unsaved commit draft. Press y to discard it and quit.'
|
|
37646
37698
|
: state.pendingMutationConfirmation
|
|
37647
37699
|
? 'This discards local changes and cannot be undone by Coco.'
|
|
37648
|
-
|
|
37649
|
-
|
|
37650
|
-
|
|
37651
|
-
|
|
37652
|
-
:
|
|
37653
|
-
?
|
|
37654
|
-
:
|
|
37700
|
+
: isWorktreeConflict && conflict
|
|
37701
|
+
? `'${conflict.branch}' is checked out at ${conflict.worktreePath}.${conflict.dirty ? ' That worktree has uncommitted changes — removal will be refused until it is clean or stashed.' : ''}`
|
|
37702
|
+
// Second-stage confirm raised when a safe delete hit an unmerged
|
|
37703
|
+
// branch — name the reason so the force isn't a blind "y again".
|
|
37704
|
+
: state.pendingConfirmationId === 'force-delete-branch'
|
|
37705
|
+
? 'Not fully merged. Force-delete (git branch -D) is irreversible.'
|
|
37706
|
+
: action?.kind === 'ai'
|
|
37707
|
+
? `AI action requires confirmation. Estimated ${action.estimatedTokens || '<unknown>'} tokens.`
|
|
37708
|
+
: 'Destructive Git action requires confirmation.';
|
|
37655
37709
|
return h(Box, {
|
|
37656
37710
|
borderColor: focusBorderColor(theme, focused),
|
|
37657
37711
|
borderStyle: theme.borderStyle,
|
|
37658
37712
|
flexDirection: 'column',
|
|
37659
37713
|
width,
|
|
37660
37714
|
paddingX: 1,
|
|
37661
|
-
}, h(Text, { bold: true }, panelTitle('Confirm', focused)), h(Text, undefined, truncateCells(label, width - 4)), h(Text, { dimColor: true }, truncateCells(warning, width - 4)), h(Text, undefined, ''), h(Text, undefined,
|
|
37715
|
+
}, h(Text, { bold: true }, panelTitle('Confirm', focused)), h(Text, undefined, truncateCells(label, width - 4)), h(Text, { dimColor: true }, truncateCells(warning, width - 4)), h(Text, undefined, ''), h(Text, undefined, truncateCells(isWorktreeConflict
|
|
37716
|
+
? 'y switch · r remove worktree & check out here · x remove worktree & delete branch · n/Esc cancel'
|
|
37717
|
+
: 'Press y to confirm or n/Esc to cancel.', width - 4)));
|
|
37662
37718
|
}
|
|
37663
37719
|
/**
|
|
37664
37720
|
* First-launch onboarding overlay (P1.3). Shown once per machine, gated
|
|
@@ -43772,6 +43828,42 @@ function LogInkApp(deps) {
|
|
|
43772
43828
|
// path on the wrong target.
|
|
43773
43829
|
return removeWorktreeAndBranch(git, cursorTarget, context.branches?.localBranches || []);
|
|
43774
43830
|
},
|
|
43831
|
+
// Worktree-checkout-conflict resolutions (#1175). Unlike the
|
|
43832
|
+
// cursor-targeted handlers above, these act on the worktree
|
|
43833
|
+
// captured in `state.worktreeCheckoutConflict` (the one git named
|
|
43834
|
+
// when it refused the checkout), not the worktrees-view cursor.
|
|
43835
|
+
'conflict-remove-worktree-checkout': async () => {
|
|
43836
|
+
const conflict = state.worktreeCheckoutConflict;
|
|
43837
|
+
dispatch({ type: 'setWorktreeCheckoutConflict', value: undefined });
|
|
43838
|
+
if (!conflict)
|
|
43839
|
+
return { ok: false, message: 'No worktree conflict to resolve.' };
|
|
43840
|
+
const worktree = context.worktreeList?.worktrees?.find((w) => w.path === conflict.worktreePath);
|
|
43841
|
+
if (!worktree)
|
|
43842
|
+
return { ok: false, message: `Worktree ${conflict.worktreePath} not found.` };
|
|
43843
|
+
// removeWorktree refuses a dirty / current worktree and returns
|
|
43844
|
+
// a clear message — surface it rather than forcing.
|
|
43845
|
+
const removed = await removeWorktree(git, worktree);
|
|
43846
|
+
if (!removed.ok)
|
|
43847
|
+
return removed;
|
|
43848
|
+
const branch = (context.branches?.localBranches || []).find((b) => b.type === 'local' && b.shortName === conflict.branch);
|
|
43849
|
+
if (!branch) {
|
|
43850
|
+
return { ok: true, message: `Removed worktree ${worktree.path}; branch ${conflict.branch} not found to check out.` };
|
|
43851
|
+
}
|
|
43852
|
+
const checkout = await checkoutBranch(git, branch);
|
|
43853
|
+
return checkout.ok
|
|
43854
|
+
? { ok: true, message: `Removed worktree ${worktree.path} and checked out ${conflict.branch}` }
|
|
43855
|
+
: { ok: false, message: `Removed worktree ${worktree.path}, but checkout failed: ${checkout.message}` };
|
|
43856
|
+
},
|
|
43857
|
+
'conflict-remove-worktree-branch': async () => {
|
|
43858
|
+
const conflict = state.worktreeCheckoutConflict;
|
|
43859
|
+
dispatch({ type: 'setWorktreeCheckoutConflict', value: undefined });
|
|
43860
|
+
if (!conflict)
|
|
43861
|
+
return { ok: false, message: 'No worktree conflict to resolve.' };
|
|
43862
|
+
const worktree = context.worktreeList?.worktrees?.find((w) => w.path === conflict.worktreePath);
|
|
43863
|
+
if (!worktree)
|
|
43864
|
+
return { ok: false, message: `Worktree ${conflict.worktreePath} not found.` };
|
|
43865
|
+
return removeWorktreeAndBranch(git, worktree, context.branches?.localBranches || []);
|
|
43866
|
+
},
|
|
43775
43867
|
'abort-operation': async () => {
|
|
43776
43868
|
const operation = context.operation?.operation;
|
|
43777
43869
|
if (!operation) {
|
|
@@ -44234,6 +44326,30 @@ function LogInkApp(deps) {
|
|
|
44234
44326
|
kind: 'warning',
|
|
44235
44327
|
});
|
|
44236
44328
|
}
|
|
44329
|
+
// Checking out a branch that's already checked out in another
|
|
44330
|
+
// worktree is rejected by git ("already checked out at <path>").
|
|
44331
|
+
// Rather than dead-end on that, capture the conflict and raise a
|
|
44332
|
+
// y-confirm offering to switch into that worktree — the branch IS
|
|
44333
|
+
// checked out, just elsewhere (#1175).
|
|
44334
|
+
if (id === 'checkout-branch' && !result?.ok && isBranchCheckedOutElsewhereError(result?.message)) {
|
|
44335
|
+
const worktreePath = parseCheckedOutWorktreePath(result?.message);
|
|
44336
|
+
const branchName = pendingItemAction?.id;
|
|
44337
|
+
if (worktreePath && branchName) {
|
|
44338
|
+
const worktree = context.worktreeList?.worktrees?.find((w) => w.path === worktreePath);
|
|
44339
|
+
dispatch({
|
|
44340
|
+
type: 'setWorktreeCheckoutConflict',
|
|
44341
|
+
value: { branch: branchName, worktreePath, dirty: worktree?.dirty ?? false },
|
|
44342
|
+
});
|
|
44343
|
+
dispatch({ type: 'setPendingConfirmation', value: 'switch-to-conflicting-worktree' });
|
|
44344
|
+
}
|
|
44345
|
+
else {
|
|
44346
|
+
dispatch({
|
|
44347
|
+
type: 'setStatus',
|
|
44348
|
+
value: `'${branchName ?? 'branch'}' is already checked out in another worktree.`,
|
|
44349
|
+
kind: 'warning',
|
|
44350
|
+
});
|
|
44351
|
+
}
|
|
44352
|
+
}
|
|
44237
44353
|
// Refresh history rows AS WELL when the workflow could have
|
|
44238
44354
|
// changed the commits the user sees (#945 follow-up). The
|
|
44239
44355
|
// workflow IDs below all either create/rewrite local commits or
|
|
@@ -44243,6 +44359,10 @@ function LogInkApp(deps) {
|
|
|
44243
44359
|
// metadata-only mutations (delete-tag, set-upstream, etc.).
|
|
44244
44360
|
const historyMutatingIds = new Set([
|
|
44245
44361
|
'checkout-branch',
|
|
44362
|
+
// Resolving a checkout conflict changes HEAD (checkout) and/or the
|
|
44363
|
+
// ref set (branch delete), so the graph needs a refresh.
|
|
44364
|
+
'conflict-remove-worktree-checkout',
|
|
44365
|
+
'conflict-remove-worktree-branch',
|
|
44246
44366
|
'continue-operation',
|
|
44247
44367
|
'pull-current-branch',
|
|
44248
44368
|
// Fetch / pull / push bring in new commits and move
|
|
@@ -44278,7 +44398,7 @@ function LogInkApp(deps) {
|
|
|
44278
44398
|
// (resolvePendingItemAction → action 'checkout'), so a silent
|
|
44279
44399
|
// stale-while-revalidate swap keeps the list readable and just
|
|
44280
44400
|
// repaints the current-branch marker once the new context lands.
|
|
44281
|
-
if (id === 'checkout-branch' && result?.ok) {
|
|
44401
|
+
if ((id === 'checkout-branch' || id === 'conflict-remove-worktree-checkout') && result?.ok) {
|
|
44282
44402
|
dispatch({ type: 'resetBranchSelection' });
|
|
44283
44403
|
await refreshContext({ silent: true });
|
|
44284
44404
|
}
|
|
@@ -44345,7 +44465,7 @@ function LogInkApp(deps) {
|
|
|
44345
44465
|
}, [context, dispatch, git, refreshContext, refreshHistoryRows, refreshWorktreeContext,
|
|
44346
44466
|
state.branchSort, state.filter, state.selectedBranchIndex,
|
|
44347
44467
|
state.selectedStashIndex, state.selectedTagIndex, state.selectedWorktreeListIndex, state.stashDiffRef,
|
|
44348
|
-
state.statusFilterMask, state.tagSort]);
|
|
44468
|
+
state.statusFilterMask, state.tagSort, state.worktreeCheckoutConflict]);
|
|
44349
44469
|
// Resolve the active view's "yank target" (commit hash / branch /
|
|
44350
44470
|
// tag / stash ref / file path) against the live filtered+sorted list,
|
|
44351
44471
|
// copy it to the system clipboard, and surface the result on the
|
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.4";
|
|
82
82
|
|
|
83
83
|
const isInteractive = (config) => {
|
|
84
84
|
return config?.mode === 'interactive' || !!config?.interactive;
|
|
@@ -26385,6 +26385,8 @@ function applyLogInkAction(state, action) {
|
|
|
26385
26385
|
pendingMutationConfirmation: action.value ? undefined : state.pendingMutationConfirmation,
|
|
26386
26386
|
pendingKey: undefined,
|
|
26387
26387
|
};
|
|
26388
|
+
case 'setWorktreeCheckoutConflict':
|
|
26389
|
+
return { ...state, worktreeCheckoutConflict: action.value, pendingKey: undefined };
|
|
26388
26390
|
case 'setPendingMutationConfirmation':
|
|
26389
26391
|
return {
|
|
26390
26392
|
...state,
|
|
@@ -27757,7 +27759,45 @@ function getLogInkInputEvents(state, inputValue, key = {}, context = {}) {
|
|
|
27757
27759
|
return [];
|
|
27758
27760
|
}
|
|
27759
27761
|
if (state.pendingConfirmationId) {
|
|
27762
|
+
// Worktree-conflict removal options (#1175): alongside the y-switch,
|
|
27763
|
+
// `r` removes the conflicting worktree and checks the branch out
|
|
27764
|
+
// here, `x` removes the worktree AND deletes the branch. Both defer
|
|
27765
|
+
// to the runtime (it owns the git ops + the conflict context); the
|
|
27766
|
+
// runtime clears the conflict state once it resolves.
|
|
27767
|
+
if (state.pendingConfirmationId === 'switch-to-conflicting-worktree' && state.worktreeCheckoutConflict) {
|
|
27768
|
+
if (inputValue === 'r') {
|
|
27769
|
+
return [
|
|
27770
|
+
{ type: 'runWorkflowAction', id: 'conflict-remove-worktree-checkout' },
|
|
27771
|
+
action({ type: 'setPendingConfirmation', value: undefined }),
|
|
27772
|
+
];
|
|
27773
|
+
}
|
|
27774
|
+
if (inputValue === 'x') {
|
|
27775
|
+
return [
|
|
27776
|
+
{ type: 'runWorkflowAction', id: 'conflict-remove-worktree-branch' },
|
|
27777
|
+
action({ type: 'setPendingConfirmation', value: undefined }),
|
|
27778
|
+
];
|
|
27779
|
+
}
|
|
27780
|
+
}
|
|
27760
27781
|
if (inputValue === 'y') {
|
|
27782
|
+
// Worktree-conflict switch (#1175): the branch is already checked
|
|
27783
|
+
// out elsewhere, so "switch" just opens that worktree as a nested
|
|
27784
|
+
// repo frame (same mechanism as drilling into a submodule) — no
|
|
27785
|
+
// git mutation, hence handled here rather than via the runtime.
|
|
27786
|
+
if (state.pendingConfirmationId === 'switch-to-conflicting-worktree') {
|
|
27787
|
+
const conflict = state.worktreeCheckoutConflict;
|
|
27788
|
+
if (conflict) {
|
|
27789
|
+
return [
|
|
27790
|
+
action({ type: 'pushRepoFrame', label: conflict.branch, workdir: conflict.worktreePath }),
|
|
27791
|
+
action({ type: 'setStatus', value: `Switched to worktree ${conflict.worktreePath} (${conflict.branch})` }),
|
|
27792
|
+
action({ type: 'setPendingConfirmation', value: undefined }),
|
|
27793
|
+
action({ type: 'setWorktreeCheckoutConflict', value: undefined }),
|
|
27794
|
+
];
|
|
27795
|
+
}
|
|
27796
|
+
return [
|
|
27797
|
+
action({ type: 'setPendingConfirmation', value: undefined }),
|
|
27798
|
+
action({ type: 'setWorktreeCheckoutConflict', value: undefined }),
|
|
27799
|
+
];
|
|
27800
|
+
}
|
|
27761
27801
|
const workflowAction = getLogInkWorkflowActionById(state.pendingConfirmationId);
|
|
27762
27802
|
if (workflowAction?.id === 'ai-commit-summary') {
|
|
27763
27803
|
return [
|
|
@@ -27783,6 +27823,11 @@ function getLogInkInputEvents(state, inputValue, key = {}, context = {}) {
|
|
|
27783
27823
|
if (inputValue === 'n' || key.escape) {
|
|
27784
27824
|
return [
|
|
27785
27825
|
action({ type: 'setPendingConfirmation', value: undefined }),
|
|
27826
|
+
// Drop any worktree-conflict context so the prompt doesn't
|
|
27827
|
+
// linger after the user declines to switch.
|
|
27828
|
+
...(state.worktreeCheckoutConflict
|
|
27829
|
+
? [action({ type: 'setWorktreeCheckoutConflict', value: undefined })]
|
|
27830
|
+
: []),
|
|
27786
27831
|
action({ type: 'setStatus', value: 'workflow action cancelled' }),
|
|
27787
27832
|
];
|
|
27788
27833
|
}
|
|
@@ -37657,25 +37702,36 @@ function renderConfirmationPanel(h, components, state, width, theme, focused) {
|
|
|
37657
37702
|
: state.pendingMutationConfirmation === 'discard-draft'
|
|
37658
37703
|
? 'Quit and discard the in-progress commit draft'
|
|
37659
37704
|
: undefined;
|
|
37660
|
-
|
|
37705
|
+
// Worktree-conflict switch (#1175): a checkout was rejected because
|
|
37706
|
+
// the branch is checked out elsewhere — name the branch + worktree so
|
|
37707
|
+
// the prompt explains what "y" does (jump into that worktree).
|
|
37708
|
+
const conflict = state.worktreeCheckoutConflict;
|
|
37709
|
+
const isWorktreeConflict = state.pendingConfirmationId === 'switch-to-conflicting-worktree';
|
|
37710
|
+
const label = isWorktreeConflict && conflict
|
|
37711
|
+
? `Switch to the worktree where '${conflict.branch}' is checked out?`
|
|
37712
|
+
: action?.label || mutationLabel || 'Workflow action';
|
|
37661
37713
|
const warning = state.pendingMutationConfirmation === 'discard-draft'
|
|
37662
37714
|
? 'You have an unsaved commit draft. Press y to discard it and quit.'
|
|
37663
37715
|
: state.pendingMutationConfirmation
|
|
37664
37716
|
? 'This discards local changes and cannot be undone by Coco.'
|
|
37665
|
-
|
|
37666
|
-
|
|
37667
|
-
|
|
37668
|
-
|
|
37669
|
-
:
|
|
37670
|
-
?
|
|
37671
|
-
:
|
|
37717
|
+
: isWorktreeConflict && conflict
|
|
37718
|
+
? `'${conflict.branch}' is checked out at ${conflict.worktreePath}.${conflict.dirty ? ' That worktree has uncommitted changes — removal will be refused until it is clean or stashed.' : ''}`
|
|
37719
|
+
// Second-stage confirm raised when a safe delete hit an unmerged
|
|
37720
|
+
// branch — name the reason so the force isn't a blind "y again".
|
|
37721
|
+
: state.pendingConfirmationId === 'force-delete-branch'
|
|
37722
|
+
? 'Not fully merged. Force-delete (git branch -D) is irreversible.'
|
|
37723
|
+
: action?.kind === 'ai'
|
|
37724
|
+
? `AI action requires confirmation. Estimated ${action.estimatedTokens || '<unknown>'} tokens.`
|
|
37725
|
+
: 'Destructive Git action requires confirmation.';
|
|
37672
37726
|
return h(Box, {
|
|
37673
37727
|
borderColor: focusBorderColor(theme, focused),
|
|
37674
37728
|
borderStyle: theme.borderStyle,
|
|
37675
37729
|
flexDirection: 'column',
|
|
37676
37730
|
width,
|
|
37677
37731
|
paddingX: 1,
|
|
37678
|
-
}, h(Text, { bold: true }, panelTitle('Confirm', focused)), h(Text, undefined, truncateCells(label, width - 4)), h(Text, { dimColor: true }, truncateCells(warning, width - 4)), h(Text, undefined, ''), h(Text, undefined,
|
|
37732
|
+
}, h(Text, { bold: true }, panelTitle('Confirm', focused)), h(Text, undefined, truncateCells(label, width - 4)), h(Text, { dimColor: true }, truncateCells(warning, width - 4)), h(Text, undefined, ''), h(Text, undefined, truncateCells(isWorktreeConflict
|
|
37733
|
+
? 'y switch · r remove worktree & check out here · x remove worktree & delete branch · n/Esc cancel'
|
|
37734
|
+
: 'Press y to confirm or n/Esc to cancel.', width - 4)));
|
|
37679
37735
|
}
|
|
37680
37736
|
/**
|
|
37681
37737
|
* First-launch onboarding overlay (P1.3). Shown once per machine, gated
|
|
@@ -43789,6 +43845,42 @@ function LogInkApp(deps) {
|
|
|
43789
43845
|
// path on the wrong target.
|
|
43790
43846
|
return removeWorktreeAndBranch(git, cursorTarget, context.branches?.localBranches || []);
|
|
43791
43847
|
},
|
|
43848
|
+
// Worktree-checkout-conflict resolutions (#1175). Unlike the
|
|
43849
|
+
// cursor-targeted handlers above, these act on the worktree
|
|
43850
|
+
// captured in `state.worktreeCheckoutConflict` (the one git named
|
|
43851
|
+
// when it refused the checkout), not the worktrees-view cursor.
|
|
43852
|
+
'conflict-remove-worktree-checkout': async () => {
|
|
43853
|
+
const conflict = state.worktreeCheckoutConflict;
|
|
43854
|
+
dispatch({ type: 'setWorktreeCheckoutConflict', value: undefined });
|
|
43855
|
+
if (!conflict)
|
|
43856
|
+
return { ok: false, message: 'No worktree conflict to resolve.' };
|
|
43857
|
+
const worktree = context.worktreeList?.worktrees?.find((w) => w.path === conflict.worktreePath);
|
|
43858
|
+
if (!worktree)
|
|
43859
|
+
return { ok: false, message: `Worktree ${conflict.worktreePath} not found.` };
|
|
43860
|
+
// removeWorktree refuses a dirty / current worktree and returns
|
|
43861
|
+
// a clear message — surface it rather than forcing.
|
|
43862
|
+
const removed = await removeWorktree(git, worktree);
|
|
43863
|
+
if (!removed.ok)
|
|
43864
|
+
return removed;
|
|
43865
|
+
const branch = (context.branches?.localBranches || []).find((b) => b.type === 'local' && b.shortName === conflict.branch);
|
|
43866
|
+
if (!branch) {
|
|
43867
|
+
return { ok: true, message: `Removed worktree ${worktree.path}; branch ${conflict.branch} not found to check out.` };
|
|
43868
|
+
}
|
|
43869
|
+
const checkout = await checkoutBranch(git, branch);
|
|
43870
|
+
return checkout.ok
|
|
43871
|
+
? { ok: true, message: `Removed worktree ${worktree.path} and checked out ${conflict.branch}` }
|
|
43872
|
+
: { ok: false, message: `Removed worktree ${worktree.path}, but checkout failed: ${checkout.message}` };
|
|
43873
|
+
},
|
|
43874
|
+
'conflict-remove-worktree-branch': async () => {
|
|
43875
|
+
const conflict = state.worktreeCheckoutConflict;
|
|
43876
|
+
dispatch({ type: 'setWorktreeCheckoutConflict', value: undefined });
|
|
43877
|
+
if (!conflict)
|
|
43878
|
+
return { ok: false, message: 'No worktree conflict to resolve.' };
|
|
43879
|
+
const worktree = context.worktreeList?.worktrees?.find((w) => w.path === conflict.worktreePath);
|
|
43880
|
+
if (!worktree)
|
|
43881
|
+
return { ok: false, message: `Worktree ${conflict.worktreePath} not found.` };
|
|
43882
|
+
return removeWorktreeAndBranch(git, worktree, context.branches?.localBranches || []);
|
|
43883
|
+
},
|
|
43792
43884
|
'abort-operation': async () => {
|
|
43793
43885
|
const operation = context.operation?.operation;
|
|
43794
43886
|
if (!operation) {
|
|
@@ -44251,6 +44343,30 @@ function LogInkApp(deps) {
|
|
|
44251
44343
|
kind: 'warning',
|
|
44252
44344
|
});
|
|
44253
44345
|
}
|
|
44346
|
+
// Checking out a branch that's already checked out in another
|
|
44347
|
+
// worktree is rejected by git ("already checked out at <path>").
|
|
44348
|
+
// Rather than dead-end on that, capture the conflict and raise a
|
|
44349
|
+
// y-confirm offering to switch into that worktree — the branch IS
|
|
44350
|
+
// checked out, just elsewhere (#1175).
|
|
44351
|
+
if (id === 'checkout-branch' && !result?.ok && isBranchCheckedOutElsewhereError(result?.message)) {
|
|
44352
|
+
const worktreePath = parseCheckedOutWorktreePath(result?.message);
|
|
44353
|
+
const branchName = pendingItemAction?.id;
|
|
44354
|
+
if (worktreePath && branchName) {
|
|
44355
|
+
const worktree = context.worktreeList?.worktrees?.find((w) => w.path === worktreePath);
|
|
44356
|
+
dispatch({
|
|
44357
|
+
type: 'setWorktreeCheckoutConflict',
|
|
44358
|
+
value: { branch: branchName, worktreePath, dirty: worktree?.dirty ?? false },
|
|
44359
|
+
});
|
|
44360
|
+
dispatch({ type: 'setPendingConfirmation', value: 'switch-to-conflicting-worktree' });
|
|
44361
|
+
}
|
|
44362
|
+
else {
|
|
44363
|
+
dispatch({
|
|
44364
|
+
type: 'setStatus',
|
|
44365
|
+
value: `'${branchName ?? 'branch'}' is already checked out in another worktree.`,
|
|
44366
|
+
kind: 'warning',
|
|
44367
|
+
});
|
|
44368
|
+
}
|
|
44369
|
+
}
|
|
44254
44370
|
// Refresh history rows AS WELL when the workflow could have
|
|
44255
44371
|
// changed the commits the user sees (#945 follow-up). The
|
|
44256
44372
|
// workflow IDs below all either create/rewrite local commits or
|
|
@@ -44260,6 +44376,10 @@ function LogInkApp(deps) {
|
|
|
44260
44376
|
// metadata-only mutations (delete-tag, set-upstream, etc.).
|
|
44261
44377
|
const historyMutatingIds = new Set([
|
|
44262
44378
|
'checkout-branch',
|
|
44379
|
+
// Resolving a checkout conflict changes HEAD (checkout) and/or the
|
|
44380
|
+
// ref set (branch delete), so the graph needs a refresh.
|
|
44381
|
+
'conflict-remove-worktree-checkout',
|
|
44382
|
+
'conflict-remove-worktree-branch',
|
|
44263
44383
|
'continue-operation',
|
|
44264
44384
|
'pull-current-branch',
|
|
44265
44385
|
// Fetch / pull / push bring in new commits and move
|
|
@@ -44295,7 +44415,7 @@ function LogInkApp(deps) {
|
|
|
44295
44415
|
// (resolvePendingItemAction → action 'checkout'), so a silent
|
|
44296
44416
|
// stale-while-revalidate swap keeps the list readable and just
|
|
44297
44417
|
// repaints the current-branch marker once the new context lands.
|
|
44298
|
-
if (id === 'checkout-branch' && result?.ok) {
|
|
44418
|
+
if ((id === 'checkout-branch' || id === 'conflict-remove-worktree-checkout') && result?.ok) {
|
|
44299
44419
|
dispatch({ type: 'resetBranchSelection' });
|
|
44300
44420
|
await refreshContext({ silent: true });
|
|
44301
44421
|
}
|
|
@@ -44362,7 +44482,7 @@ function LogInkApp(deps) {
|
|
|
44362
44482
|
}, [context, dispatch, git, refreshContext, refreshHistoryRows, refreshWorktreeContext,
|
|
44363
44483
|
state.branchSort, state.filter, state.selectedBranchIndex,
|
|
44364
44484
|
state.selectedStashIndex, state.selectedTagIndex, state.selectedWorktreeListIndex, state.stashDiffRef,
|
|
44365
|
-
state.statusFilterMask, state.tagSort]);
|
|
44485
|
+
state.statusFilterMask, state.tagSort, state.worktreeCheckoutConflict]);
|
|
44366
44486
|
// Resolve the active view's "yank target" (commit hash / branch /
|
|
44367
44487
|
// tag / stash ref / file path) against the live filtered+sorted list,
|
|
44368
44488
|
// copy it to the system clipboard, and surface the result on the
|