Skip to content

Commit 426280e

Browse files
committed
Fix git staging refresh and status counts
1 parent 969d035 commit 426280e

File tree

3 files changed

+63
-8
lines changed

3 files changed

+63
-8
lines changed

packages/desktop/src/features/git/FileWatcher.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -245,13 +245,14 @@ export class GitFileWatcher extends EventEmitter {
245245
}
246246
}
247247

248-
// Also watch the gitdir itself for ref changes (best-effort, non-recursive).
248+
// Also watch the gitdir itself (best-effort). On some platforms Git updates files
249+
// via atomic replace, which can invalidate per-file watches; watching the directory
250+
// makes staging/commit changes reliably detectable (Zed-style).
249251
try {
250252
const w = watch(gitdir, { persistent: true }, (eventType, filename) => {
251253
const name = filename ? String(filename) : '';
252-
if (name.startsWith('refs') || name === 'refs' || name === 'packed-refs') {
253-
this.handleGitMetadataChange(sessionId, join(gitdir, name), eventType);
254-
}
254+
// Always treat gitdir changes as refresh-worthy; debounce will batch them.
255+
this.handleGitMetadataChange(sessionId, name ? join(gitdir, name) : gitdir, eventType);
255256
});
256257
watchers.push(w);
257258
} catch {

packages/desktop/src/features/git/StatusManager.ts

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ export class GitStatusManager extends EventEmitter {
7878
hasStaged: boolean;
7979
hasUntracked: boolean;
8080
hasConflicts: boolean;
81+
modified: number;
82+
staged: number;
83+
untracked: number;
84+
conflicted: number;
8185
}> {
8286
const { stdout } = await this.gitExecutor.run({
8387
sessionId,
@@ -92,25 +96,38 @@ export class GitStatusManager extends EventEmitter {
9296
let hasUntracked = false;
9397
let hasConflicts = false;
9498

99+
let modified = 0;
100+
let staged = 0;
101+
let untracked = 0;
102+
let conflicted = 0;
103+
95104
for (const line of stdout.split('\n')) {
96105
if (!line) continue;
97106
if (line.startsWith('??')) {
98107
hasUntracked = true;
108+
untracked++;
99109
continue;
100110
}
101111
if (line.length < 2) continue;
102112
const x = line[0];
103113
const y = line[1];
104114

105-
if (x !== ' ' && x !== '?') hasStaged = true;
106-
if (y !== ' ' && y !== '?') hasModified = true;
115+
if (x !== ' ' && x !== '?') {
116+
hasStaged = true;
117+
staged++;
118+
}
119+
if (y !== ' ' && y !== '?') {
120+
hasModified = true;
121+
modified++;
122+
}
107123

108124
if (x === 'U' || y === 'U' || (x === 'A' && y === 'A') || (x === 'D' && y === 'D')) {
109125
hasConflicts = true;
126+
conflicted++;
110127
}
111128
}
112129

113-
return { hasModified, hasStaged, hasUntracked, hasConflicts };
130+
return { hasModified, hasStaged, hasUntracked, hasConflicts, modified, staged, untracked, conflicted };
114131
}
115132

116133
private async getAheadBehind(worktreePath: string, sessionId: string, mainBranch: string): Promise<{ ahead: number; behind: number }> {
@@ -385,6 +402,12 @@ export class GitStatusManager extends EventEmitter {
385402
const summary = await this.getPorcelainSummary(session.worktreePath, sessionId);
386403
updatedStatus.hasUncommittedChanges = summary.hasModified || summary.hasStaged;
387404
updatedStatus.hasUntrackedFiles = summary.hasUntracked;
405+
updatedStatus.staged = summary.staged > 0 ? summary.staged : undefined;
406+
updatedStatus.modified = summary.modified > 0 ? summary.modified : undefined;
407+
updatedStatus.untracked = summary.untracked > 0 ? summary.untracked : undefined;
408+
updatedStatus.conflicted = summary.conflicted > 0 ? summary.conflicted : undefined;
409+
updatedStatus.clean =
410+
!updatedStatus.hasUncommittedChanges && !updatedStatus.hasUntrackedFiles && !summary.hasConflicts ? true : undefined;
388411
if (summary.hasConflicts) updatedStatus.state = 'conflict';
389412

390413
if (updatedStatus.hasUncommittedChanges) {
@@ -648,6 +671,16 @@ export class GitStatusManager extends EventEmitter {
648671
if (cachedHasChanges !== currentHasChanges) {
649672
return true;
650673
}
674+
675+
// Detect state transitions that keep "has changes" true (e.g. stage/unstage operations).
676+
if (
677+
(cached.status.staged ?? 0) !== quickStatus.staged ||
678+
(cached.status.modified ?? 0) !== quickStatus.modified ||
679+
(cached.status.untracked ?? 0) !== quickStatus.untracked ||
680+
(cached.status.conflicted ?? 0) !== quickStatus.conflicted
681+
) {
682+
return true;
683+
}
651684

652685
// If both have no changes, check if ahead/behind changed
653686
if (!currentHasChanges) {
@@ -804,6 +837,11 @@ export class GitStatusManager extends EventEmitter {
804837

805838
const result = {
806839
state,
840+
staged: quickStatus.staged > 0 ? quickStatus.staged : undefined,
841+
modified: quickStatus.modified > 0 ? quickStatus.modified : undefined,
842+
untracked: quickStatus.untracked > 0 ? quickStatus.untracked : undefined,
843+
conflicted: quickStatus.conflicted > 0 ? quickStatus.conflicted : undefined,
844+
clean: !hasUncommittedChanges && !hasUntrackedFiles && !hasMergeConflicts ? true : undefined,
807845
ahead: ahead > 0 ? ahead : undefined,
808846
behind: behind > 0 ? behind : undefined,
809847
additions: uncommittedDiff.stats.additions > 0 ? uncommittedDiff.stats.additions : undefined,

packages/desktop/src/infrastructure/ipc/git.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { promises as fs } from 'fs';
44
import { join } from 'path';
55

66
export function registerGitHandlers(ipcMain: IpcMain, services: AppServices): void {
7-
const { sessionManager, gitDiffManager, gitStagingManager, gitExecutor } = services;
7+
const { sessionManager, gitDiffManager, gitStagingManager, gitStatusManager, gitExecutor } = services;
88

99
ipcMain.handle('sessions:get-executions', async (_event, sessionId: string) => {
1010
try {
@@ -214,6 +214,10 @@ export function registerGitHandlers(ipcMain: IpcMain, services: AppServices): vo
214214
hunkHeader: options.hunkHeader,
215215
});
216216

217+
if (result.success) {
218+
void gitStatusManager.refreshSessionGitStatus(sessionId, false);
219+
}
220+
217221
return result;
218222
} catch (error) {
219223
return {
@@ -242,6 +246,10 @@ export function registerGitHandlers(ipcMain: IpcMain, services: AppServices): vo
242246
hunkHeader: options.hunkHeader,
243247
});
244248

249+
if (result.success) {
250+
void gitStatusManager.refreshSessionGitStatus(sessionId, false);
251+
}
252+
245253
return result;
246254
} catch (error) {
247255
return {
@@ -266,6 +274,10 @@ export function registerGitHandlers(ipcMain: IpcMain, services: AppServices): vo
266274
stage: options.stage,
267275
});
268276

277+
if (result.success) {
278+
void gitStatusManager.refreshSessionGitStatus(sessionId, false);
279+
}
280+
269281
return result;
270282
} catch (error) {
271283
return {
@@ -296,6 +308,10 @@ export function registerGitHandlers(ipcMain: IpcMain, services: AppServices): vo
296308
stage,
297309
});
298310

311+
if (result.success) {
312+
void gitStatusManager.refreshSessionGitStatus(sessionId, false);
313+
}
314+
299315
return result;
300316
} catch (error) {
301317
return {

0 commit comments

Comments
 (0)