Skip to content

Commit 101901d

Browse files
committed
fix: resolve 3 additional critical bugs
- Fix diff computation race condition: add request ID tracking to prevent stale diff results from overwriting current branch selection when user switches branches quickly - Fix token counting error handling: log errors instead of throwing from useEffect to prevent unhandled promise rejections and app crashes - Fix rename detection in git_diff: call find_similar() to enable proper rename and copy detection instead of showing as delete+add pairs
1 parent bb9bdde commit 101901d

3 files changed

Lines changed: 35 additions & 5 deletions

File tree

apps/desktop/src-tauri/src/git.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,10 +110,14 @@ pub fn git_diff(path: &str, base: &str, compare: &str) -> Result<DiffResult, Str
110110
.map_err(|e| format!("Failed to get compare tree: {}", e))?;
111111

112112
// Compute diff
113-
let diff = repo
113+
let mut diff = repo
114114
.diff_tree_to_tree(Some(&base_tree), Some(&compare_tree), None)
115115
.map_err(|e| format!("Failed to compute diff: {}", e))?;
116116

117+
// Enable rename and copy detection
118+
diff.find_similar(None)
119+
.map_err(|e| format!("Failed to find similar files: {}", e))?;
120+
117121
let mut files = Vec::new();
118122

119123
diff.foreach(

apps/web/src/hooks/useFileTree.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useCallback, useState } from 'react'
1+
import { useCallback, useState, useRef } from 'react'
22
import type { GitWorkerClient } from '../utils/gitWorkerClient'
33
import type { AppStatus } from '../types/appStatus'
44
import { isBinaryPath, LARGE_REPO_FILE_THRESHOLD, type FileDiffStatus, type FileTreeNode } from '@gitcontext/core'
@@ -18,6 +18,9 @@ export function useFileTree(setAppStatus?: (s: AppStatus) => void) {
1818
const [selectedPaths, setSelectedPaths] = useState<Set<string>>(new Set())
1919
const [isComputing, setIsComputing] = useState<boolean>(false)
2020

21+
// Track diff computation request IDs to prevent race conditions
22+
const diffRequestIdRef = useRef(0)
23+
2124
const buildTreeFromPaths = useCallback((allPaths: string[], diffMap: Map<string, FileDiffStatus>): { tree: FileTreeNode; statusByPath: Map<string, FileDiffStatus> } => {
2225
const root: FileTreeNode = { name: '', path: '', type: 'dir', children: [] }
2326
const dirMap = new Map<string, FileTreeNode>()
@@ -85,21 +88,35 @@ export function useFileTree(setAppStatus?: (s: AppStatus) => void) {
8588
setExpandedPaths(new Set())
8689
return
8790
}
91+
92+
// Increment request ID to track this specific diff computation
93+
const requestId = ++diffRequestIdRef.current
8894
setIsComputing(true)
89-
95+
9096
try {
9197
setAppStatus?.({ state: 'LOADING', task: 'diff', message: 'Computing file differences…', progress: 25 })
9298
try { console.info('[app-status]', { state: 'LOADING', task: 'diff', message: 'Computing file differences…', progress: 25 }) } catch {}
9399
setProgress?.({ message: 'Computing file differences…', percent: 25 })
94-
100+
95101
const res = await gitClient.diff(baseBranch, compareBranch)
102+
103+
// Check if this request is still the latest one (prevent race condition)
104+
if (requestId !== diffRequestIdRef.current) {
105+
return // A newer diff request has been made, ignore this result
106+
}
107+
96108
setDiffFiles(res.files)
97109

98110
setProgress?.({ message: 'Fetching file list…', percent: 50 })
99111
setAppStatus?.({ state: 'LOADING', task: 'diff', message: 'Fetching file list…', progress: 50 })
100112
try { console.info('[app-status]', { state: 'LOADING', task: 'diff', message: 'Fetching file list…', progress: 50 }) } catch {}
101113
const baseList = await gitClient.listFiles(baseBranch)
102114
const compareList = await gitClient.listFiles(compareBranch)
115+
116+
// Check again after async operations
117+
if (requestId !== diffRequestIdRef.current) {
118+
return
119+
}
103120
const diffMap = new Map<string, FileDiffStatus>()
104121
for (const f of res.files) diffMap.set(f.path, f.type as FileDiffStatus)
105122
// Build union from both sides to keep unchanged files present on either side
@@ -108,6 +125,12 @@ export function useFileTree(setAppStatus?: (s: AppStatus) => void) {
108125
setAppStatus?.({ state: 'LOADING', task: 'diff', message: 'Building file tree…', progress: 75 })
109126
try { console.info('[app-status]', { state: 'LOADING', task: 'diff', message: 'Building file tree…', progress: 75 }) } catch {}
110127
const { tree, statusByPath: statusMap } = buildTreeFromPaths(Array.from(union), diffMap)
128+
129+
// Final check before committing all state updates
130+
if (requestId !== diffRequestIdRef.current) {
131+
return
132+
}
133+
111134
setFileTree(tree)
112135
setStatusByPath(statusMap)
113136

apps/web/src/hooks/useTokenCounts.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { FileDiffStatus } from './useFileTree'
55
import { buildUnifiedDiffForStatus } from '../utils/diff'
66
import { isBinaryPath, MAX_CONCURRENT_READS } from '@gitcontext/core'
77
import { mapWithConcurrency } from '../utils/concurrency'
8+
import { logError } from '../utils/logger'
89

910
// Helper to infer language from file extension for syntax highlighting
1011
function inferLangFromPath(p: string): string {
@@ -152,8 +153,10 @@ export function useTokenCounts({
152153
}
153154
setCounts(next)
154155
} catch (err: any) {
156+
// Don't throw from effect to avoid unhandled promise rejection
155157
if (err?.message !== 'Operation cancelled') {
156-
throw err
158+
logError('tokenCounts', err)
159+
setCounts(new Map()) // Reset to empty on error
157160
}
158161
} finally {
159162
setBusy(false)

0 commit comments

Comments
 (0)