Clear diff dedupe for worktree ref mutations

This commit is contained in:
Neil
2026-06-25 16:36:15 -07:00
parent 1ec62e443c
commit 49cdebf8a5
2 changed files with 88 additions and 61 deletions
+28 -16
View File
@@ -507,10 +507,13 @@ export class SshGitProvider implements IGitProvider {
worktreePath: string,
expectedUpstream: GitForkSyncExpectedUpstream
): Promise<GitForkSyncResult> {
return (await this.mux.request('git.forkSync', {
worktreePath,
...(expectedUpstream ? { expectedUpstream } : {})
})) as GitForkSyncResult
return this.runWithDiffDedupeClear(
async () =>
(await this.mux.request('git.forkSync', {
worktreePath,
...(expectedUpstream ? { expectedUpstream } : {})
})) as GitForkSyncResult
)
}
async fetchRemoteTrackingRef(
@@ -607,11 +610,13 @@ export class SshGitProvider implements IGitProvider {
targetDir: string,
options?: { base?: string; checkoutExistingBranch?: boolean; noCheckout?: boolean }
): Promise<void> {
await this.mux.request('git.addWorktree', {
repoPath,
branchName,
targetDir,
...options
await this.runWithDiffDedupeClear(async () => {
await this.mux.request('git.addWorktree', {
repoPath,
branchName,
targetDir,
...options
})
})
}
@@ -620,11 +625,14 @@ export class SshGitProvider implements IGitProvider {
force?: boolean,
options?: { deleteBranch?: boolean; forceBranchDelete?: boolean }
): Promise<RemoveWorktreeResult> {
return ((await this.mux.request('git.removeWorktree', {
worktreePath,
force,
...options
})) ?? {}) as RemoveWorktreeResult
return this.runWithDiffDedupeClear(
async () =>
((await this.mux.request('git.removeWorktree', {
worktreePath,
force,
...options
})) ?? {}) as RemoveWorktreeResult
)
}
async worktreeIsClean(
@@ -676,11 +684,15 @@ export class SshGitProvider implements IGitProvider {
ownerWorktreePath?: string
checkOnly?: boolean
}): Promise<void> {
await this.mux.request('git.refreshLocalBaseRefForWorktreeCreate', args)
await this.runWithDiffDedupeClear(async () => {
await this.mux.request('git.refreshLocalBaseRefForWorktreeCreate', args)
})
}
async renameCurrentBranch(worktreePath: string, newBranch: string): Promise<void> {
await this.mux.request('git.renameCurrentBranch', { worktreePath, newBranch })
await this.runWithDiffDedupeClear(async () => {
await this.mux.request('git.renameCurrentBranch', { worktreePath, newBranch })
})
}
async exec(
+60 -45
View File
@@ -143,6 +143,15 @@ export class GitHandler {
this.dispatcher.onRequest('git.isGitRepo', (p) => this.isGitRepo(p))
}
private async runWithDiffDedupeClear<T>(run: () => Promise<T>): Promise<T> {
this.gitDiffReadDedupe.clear()
try {
return await run()
} finally {
this.gitDiffReadDedupe.clear()
}
}
private async git(
args: string[],
cwd: string,
@@ -609,33 +618,35 @@ export class GitHandler {
}
private async forkSync(params: Record<string, unknown>, context?: RequestContext) {
const worktreePath = params.worktreePath as string
const expectedUpstream = validateGitForkSyncExpectedUpstream(params.expectedUpstream, {
required: true
return this.runWithDiffDedupeClear(async () => {
const worktreePath = params.worktreePath as string
const expectedUpstream = validateGitForkSyncExpectedUpstream(params.expectedUpstream, {
required: true
})
const controller = new AbortController()
const abortFromContext = () => controller.abort()
if (context?.signal?.aborted) {
controller.abort()
} else {
context?.signal?.addEventListener('abort', abortFromContext, { once: true })
}
const timeout = setTimeout(() => controller.abort(), 60_000)
try {
return await syncForkDefaultBranch(
(args) =>
this.git(args, worktreePath, {
nonInteractive: true,
signal: controller.signal
}),
{ expectedUpstream }
)
} catch (error) {
throw new Error(normalizeGitErrorMessage(error, 'push'))
} finally {
clearTimeout(timeout)
context?.signal?.removeEventListener('abort', abortFromContext)
}
})
const controller = new AbortController()
const abortFromContext = () => controller.abort()
if (context?.signal?.aborted) {
controller.abort()
} else {
context?.signal?.addEventListener('abort', abortFromContext, { once: true })
}
const timeout = setTimeout(() => controller.abort(), 60_000)
try {
return await syncForkDefaultBranch(
(args) =>
this.git(args, worktreePath, {
nonInteractive: true,
signal: controller.signal
}),
{ expectedUpstream }
)
} catch (error) {
throw new Error(normalizeGitErrorMessage(error, 'push'))
} finally {
clearTimeout(timeout)
context?.signal?.removeEventListener('abort', abortFromContext)
}
}
private async fetchRemoteTrackingRef(params: Record<string, unknown>) {
@@ -955,22 +966,24 @@ export class GitHandler {
}
private async renameCurrentBranch(params: Record<string, unknown>) {
const worktreePath = params.worktreePath
const newBranch = params.newBranch
if (typeof worktreePath !== 'string' || typeof newBranch !== 'string') {
throw new Error('Invalid branch rename request.')
}
if (newBranch.startsWith('-')) {
throw new Error('Branch name must not start with "-".')
}
try {
// Why: generic git.exec intentionally blocks destructive branch flags.
// This narrow RPC permits only the already-checked current-branch rename.
await this.git(['check-ref-format', '--branch', newBranch], worktreePath)
await this.git(['branch', '-m', newBranch], worktreePath)
} catch (error) {
throw new Error(normalizeGitErrorMessage(error))
}
return this.runWithDiffDedupeClear(async () => {
const worktreePath = params.worktreePath
const newBranch = params.newBranch
if (typeof worktreePath !== 'string' || typeof newBranch !== 'string') {
throw new Error('Invalid branch rename request.')
}
if (newBranch.startsWith('-')) {
throw new Error('Branch name must not start with "-".')
}
try {
// Why: generic git.exec intentionally blocks destructive branch flags.
// This narrow RPC permits only the already-checked current-branch rename.
await this.git(['check-ref-format', '--branch', newBranch], worktreePath)
await this.git(['branch', '-m', newBranch], worktreePath)
} catch (error) {
throw new Error(normalizeGitErrorMessage(error))
}
})
}
private async isGitRepo(params: Record<string, unknown>) {
@@ -1005,11 +1018,11 @@ export class GitHandler {
}
private async addWorktree(params: Record<string, unknown>) {
return addWorktreeOp(this.git.bind(this), params)
return this.runWithDiffDedupeClear(() => addWorktreeOp(this.git.bind(this), params))
}
private async removeWorktree(params: Record<string, unknown>) {
return removeWorktreeOp(this.git.bind(this), params)
return this.runWithDiffDedupeClear(() => removeWorktreeOp(this.git.bind(this), params))
}
private async worktreeIsClean(params: Record<string, unknown>) {
@@ -1017,6 +1030,8 @@ export class GitHandler {
}
private async refreshLocalBaseRefForWorktreeCreate(params: Record<string, unknown>) {
return refreshLocalBaseRefForWorktreeCreateOp(this.git.bind(this), params)
return this.runWithDiffDedupeClear(() =>
refreshLocalBaseRefForWorktreeCreateOp(this.git.bind(this), params)
)
}
}