diff --git a/apps/desktop/electron/gitlock.test.ts b/apps/desktop/electron/gitlock.test.ts index bfaa067157..342a9cd4b1 100644 --- a/apps/desktop/electron/gitlock.test.ts +++ b/apps/desktop/electron/gitlock.test.ts @@ -10,6 +10,7 @@ import { clearStaleGitLocks, LOCK_NAMES, STALE_LOCK_MIN_AGE_MS } from './gitlock function makeRepo(): string { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gitlock-test-')) fs.mkdirSync(path.join(root, '.git')) + return root } @@ -18,6 +19,7 @@ function writeLock(root: string, name: string, ageMs: number): string { fs.writeFileSync(p, '') const t = new Date(Date.now() - ageMs) fs.utimesSync(p, t, t) + return p } diff --git a/apps/desktop/electron/gitlock.ts b/apps/desktop/electron/gitlock.ts index 4bc396f5ee..6beab799ea 100644 --- a/apps/desktop/electron/gitlock.ts +++ b/apps/desktop/electron/gitlock.ts @@ -25,19 +25,24 @@ function gitProcessRunning(): Promise { process.platform === 'win32' ? ['tasklist', ['/FI', 'IMAGENAME eq git.exe', '/FO', 'CSV']] : ['pgrep', ['-x', 'git']] + execFile(cmd, args, { timeout: 10_000 }, (error, stdout) => { if (process.platform === 'win32') { // tasklist exits 0 either way; presence is signaled in stdout. resolve(Boolean(stdout && stdout.toLowerCase().includes('git.exe'))) + return } + // pgrep: exit 0 = at least one match; 1 = none; other = probe failure. // On probe failure stay conservative: report "running" so no lock is // touched when we cannot tell. if (error && (error as any).code === 1) { resolve(false) + return } + resolve(true) }) }) @@ -47,13 +52,17 @@ function gitProcessRunning(): Promise { // Never throws: a lock we cannot stat or unlink is skipped. export async function clearStaleGitLocks( repoRoot: string, - { minAgeMs = STALE_LOCK_MIN_AGE_MS, isGitRunning = gitProcessRunning }: { + { + minAgeMs = STALE_LOCK_MIN_AGE_MS, + isGitRunning = gitProcessRunning + }: { minAgeMs?: number isGitRunning?: () => Promise } = {} ): Promise { const gitDir = path.join(repoRoot, '.git') const removed: string[] = [] + try { if (!fs.statSync(gitDir).isDirectory()) { return removed @@ -67,10 +76,13 @@ export async function clearStaleGitLocks( } const cutoff = Date.now() - minAgeMs + for (const name of LOCK_NAMES) { const lockPath = path.join(gitDir, name) + try { const st = fs.statSync(lockPath) + if (st.isFile() && st.mtimeMs < cutoff) { fs.unlinkSync(lockPath) removed.push(lockPath) @@ -79,5 +91,6 @@ export async function clearStaleGitLocks( // Missing or concurrently removed — skipping is always safe. } } + return removed }