Files
orca/src/preload/api/worktrees-bridge.ts
T
Jinwoo HongandNeil 29aa2a4f0f Refresh Source Control only for the worktree whose git index changed (#26946)
* Refresh Source Control only for the worktree whose git index changed

Index and HEAD-log writes under <common>/worktrees/<entry>/ now carry the
checkout they belong to (resolved through the head-identity memo), and the
renderer skips the status nudge when that checkout is a known sibling of the
active one. Unattributable changes (shared config, bound upstream ref,
overflow, SSH watches, unknown admin dirs) still refresh as before.

* Pass the stable worktree-path getter as an effect dependency instead of a render-time ref

* Fan out sibling head moves to every checkout's Source Control

A commit to a shared branch (e.g. main) from a sibling worktree can move the
active checkout's local upstream, whose ref leaf is not polled. Its logs/HEAD
append now stays unattributed so ahead/behind refreshes immediately; index-only
churn keeps its per-worktree attribution.

---------

Co-authored-by: Neil <neil@stably.ai>
2026-10-14 04:40:22 +00:00

119 lines
4.5 KiB
TypeScript

import { ipcRenderer } from 'electron'
import type {
WorktreeBaseStatusEvent,
WorktreeRemoteBranchConflictEvent
} from '../../shared/worktree/base-ref-drift-types'
import type {
GitStatusMetadataChangedEvent,
WorktreeHeadIdentity
} from '../../shared/worktree/types'
import type { PreloadApi } from '../api-types'
export const worktreesApi = {
list: (args) => ipcRenderer.invoke('worktrees:list', args),
listRetiredNames: (args) => ipcRenderer.invoke('worktrees:listRetiredNames', args),
listDetected: (args) => ipcRenderer.invoke('worktrees:listDetected', args),
listKnownForExecutionHost: (args) =>
ipcRenderer.invoke('worktrees:listKnownForExecutionHost', args),
forgetRemovedForExecutionHost: (args) =>
ipcRenderer.invoke('worktrees:forgetRemovedForExecutionHost', args),
cancelListDetected: (args) => ipcRenderer.invoke('worktrees:cancelListDetected', args),
listAll: () => ipcRenderer.invoke('worktrees:listAll'),
create: (args) => ipcRenderer.invoke('worktrees:create', args),
adoptProvisionedRoot: (args) => ipcRenderer.invoke('worktrees:adoptProvisionedRoot', args),
onCreateProgress: (
callback: (data: { creationId?: string; phase: 'fetching' | 'creating' }) => void
): (() => void) => {
const listener = (
_event: Electron.IpcRendererEvent,
data: { creationId?: string; phase: 'fetching' | 'creating' }
) => callback(data)
ipcRenderer.on('createWorktree:progress', listener)
return () => ipcRenderer.removeListener('createWorktree:progress', listener)
},
prefetchCreateBase: (args) => ipcRenderer.invoke('worktrees:prefetchCreateBase', args),
resolvePrBase: (args) => ipcRenderer.invoke('worktrees:resolvePrBase', args),
resolveMrBase: (args) => ipcRenderer.invoke('worktrees:resolveMrBase', args),
previewNestedRemoval: (args) => ipcRenderer.invoke('worktrees:previewNestedRemoval', args),
remove: (args) => ipcRenderer.invoke('worktrees:remove', args),
forgetLocal: (args) => ipcRenderer.invoke('worktrees:forgetLocal', args),
forceDeletePreservedBranch: (args) =>
ipcRenderer.invoke('worktrees:forceDeletePreservedBranch', args),
updateMeta: (args) => ipcRenderer.invoke('worktrees:updateMeta', args),
listLineage: () => ipcRenderer.invoke('worktrees:listLineage'),
updateLineage: (args) => ipcRenderer.invoke('worktrees:updateLineage', args),
persistSortOrder: (args) => ipcRenderer.invoke('worktrees:persistSortOrder', args),
getBranchRenameFailureOutput: (args) =>
ipcRenderer.invoke('worktrees:getBranchRenameFailureOutput', args),
onChanged: (
callback: (data: {
repoId: string
renamed?: { oldWorktreeId: string; newWorktreeId: string }
}) => void
): (() => void) => {
const listener = (
_event: Electron.IpcRendererEvent,
data: { repoId: string; renamed?: { oldWorktreeId: string; newWorktreeId: string } }
) => callback(data)
ipcRenderer.on('worktrees:changed', listener)
return () => ipcRenderer.removeListener('worktrees:changed', listener)
},
onGitStatusMetadataChanged: (
callback: (data: GitStatusMetadataChangedEvent) => void
): (() => void) => {
const listener = (_event: Electron.IpcRendererEvent, data: GitStatusMetadataChangedEvent) =>
callback(data)
ipcRenderer.on('worktrees:gitStatusMetadataChanged', listener)
return () => ipcRenderer.removeListener('worktrees:gitStatusMetadataChanged', listener)
},
onHeadIdentitiesChanged: (
callback: (data: { repoId: string; identities: WorktreeHeadIdentity[] }) => void
): (() => void) => {
const listener = (
_event: Electron.IpcRendererEvent,
data: { repoId: string; identities: WorktreeHeadIdentity[] }
) => callback(data)
ipcRenderer.on('worktrees:headIdentitiesChanged', listener)
return () => ipcRenderer.removeListener('worktrees:headIdentitiesChanged', listener)
},
onBaseStatus: (callback: (data: WorktreeBaseStatusEvent) => void): (() => void) => {
const listener = (_event: Electron.IpcRendererEvent, data: WorktreeBaseStatusEvent) =>
callback(data)
ipcRenderer.on('worktree:baseStatus', listener)
return () => ipcRenderer.removeListener('worktree:baseStatus', listener)
},
onRemoteBranchConflict: (
callback: (data: WorktreeRemoteBranchConflictEvent) => void
): (() => void) => {
const listener = (_event: Electron.IpcRendererEvent, data: WorktreeRemoteBranchConflictEvent) =>
callback(data)
ipcRenderer.on('worktree:remoteBranchConflict', listener)
return () => ipcRenderer.removeListener('worktree:remoteBranchConflict', listener)
}
} satisfies PreloadApi['worktrees']