Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions apps/code/src/main/services/file-watcher/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const FileWatcherEvent = {
FileChanged: "file-changed",
FileDeleted: "file-deleted",
GitStateChanged: "git-state-changed",
WorkingTreeChanged: "working-tree-changed",
} as const;

export type DirectoryChangedPayload = {
Expand All @@ -46,9 +47,14 @@ export type GitStateChangedPayload = {
repoPath: string;
};

export type WorkingTreeChangedPayload = {
repoPath: string;
};

export interface FileWatcherEvents {
[FileWatcherEvent.DirectoryChanged]: DirectoryChangedPayload;
[FileWatcherEvent.FileChanged]: FileChangedPayload;
[FileWatcherEvent.FileDeleted]: FileDeletedPayload;
[FileWatcherEvent.GitStateChanged]: GitStateChangedPayload;
[FileWatcherEvent.WorkingTreeChanged]: WorkingTreeChangedPayload;
}
6 changes: 4 additions & 2 deletions apps/code/src/main/services/file-watcher/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,11 @@ export class FileWatcherService extends TypedEventEmitter<FileWatcherEvents> {

const totalChanges = pending.files.size + pending.deletes.size;

// For bulk changes, emit a single event instead of per-file events
if (totalChanges > 0) {
this.emit(FileWatcherEvent.WorkingTreeChanged, { repoPath });
}

if (totalChanges > BULK_THRESHOLD) {
this.emit(FileWatcherEvent.GitStateChanged, { repoPath });
pending.dirs.clear();
pending.files.clear();
pending.deletes.clear();
Expand Down
55 changes: 43 additions & 12 deletions apps/code/src/main/services/git/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,20 +297,29 @@ export class GitService extends TypedEventEmitter<GitServiceEvents> {
return getRemoteUrl(directoryPath);
}

public async getCurrentBranch(directoryPath: string): Promise<string | null> {
return getCurrentBranch(directoryPath);
public async getCurrentBranch(
directoryPath: string,
signal?: AbortSignal,
): Promise<string | null> {
return getCurrentBranch(directoryPath, { abortSignal: signal });
}

public async getDefaultBranch(directoryPath: string): Promise<string> {
return getDefaultBranch(directoryPath);
}

public async getAllBranches(directoryPath: string): Promise<string[]> {
return getAllBranches(directoryPath);
public async getAllBranches(
directoryPath: string,
signal?: AbortSignal,
): Promise<string[]> {
return getAllBranches(directoryPath, { abortSignal: signal });
}

public async getGitBusyState(directoryPath: string): Promise<GitBusyState> {
return getGitBusyState(directoryPath);
public async getGitBusyState(
directoryPath: string,
signal?: AbortSignal,
): Promise<GitBusyState> {
return getGitBusyState(directoryPath, { abortSignal: signal });
}

public async createBranch(
Expand All @@ -334,9 +343,11 @@ export class GitService extends TypedEventEmitter<GitServiceEvents> {

public async getChangedFilesHead(
directoryPath: string,
signal?: AbortSignal,
): Promise<ChangedFile[]> {
const files = await getChangedFilesDetailed(directoryPath, {
excludePatterns: [".claude", "CLAUDE.local.md"],
abortSignal: signal,
});
type HeadChangedFile = Omit<ChangedFile, "patch">;
const filteredFiles: Array<HeadChangedFile | null> = await Promise.all(
Expand Down Expand Up @@ -371,29 +382,42 @@ export class GitService extends TypedEventEmitter<GitServiceEvents> {
public async getFileAtHead(
directoryPath: string,
filePath: string,
signal?: AbortSignal,
): Promise<string | null> {
return getFileAtHead(directoryPath, filePath);
return getFileAtHead(directoryPath, filePath, { abortSignal: signal });
}

public async getDiffHead(
directoryPath: string,
ignoreWhitespace?: boolean,
signal?: AbortSignal,
): Promise<string> {
return getDiffHead(directoryPath, { ignoreWhitespace });
return getDiffHead(directoryPath, {
ignoreWhitespace,
abortSignal: signal,
});
}

public async getDiffCached(
directoryPath: string,
ignoreWhitespace?: boolean,
signal?: AbortSignal,
): Promise<string> {
return getStagedDiff(directoryPath, { ignoreWhitespace });
return getStagedDiff(directoryPath, {
ignoreWhitespace,
abortSignal: signal,
});
}

public async getDiffUnstaged(
directoryPath: string,
ignoreWhitespace?: boolean,
signal?: AbortSignal,
): Promise<string> {
return getUnstagedDiff(directoryPath, { ignoreWhitespace });
return getUnstagedDiff(directoryPath, {
ignoreWhitespace,
abortSignal: signal,
});
}

public async stageFiles(
Expand All @@ -412,9 +436,13 @@ export class GitService extends TypedEventEmitter<GitServiceEvents> {
return this.getStateSnapshot(directoryPath);
}

public async getDiffStats(directoryPath: string): Promise<DiffStats> {
public async getDiffStats(
directoryPath: string,
signal?: AbortSignal,
): Promise<DiffStats> {
const stats = await getDiffStats(directoryPath, {
excludePatterns: [".claude", "CLAUDE.local.md"],
abortSignal: signal,
});
return {
filesChanged: stats.filesChanged,
Expand Down Expand Up @@ -455,8 +483,11 @@ export class GitService extends TypedEventEmitter<GitServiceEvents> {

public async getLatestCommit(
directoryPath: string,
signal?: AbortSignal,
): Promise<GitCommitInfo | null> {
const commit = await getLatestCommit(directoryPath);
const commit = await getLatestCommit(directoryPath, {
abortSignal: signal,
});
if (!commit) return null;
return {
sha: commit.sha,
Expand Down
1 change: 1 addition & 0 deletions apps/code/src/main/trpc/routers/file-watcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,5 @@ export const fileWatcherRouter = router({
onFileChanged: subscribe(FileWatcherEvent.FileChanged),
onFileDeleted: subscribe(FileWatcherEvent.FileDeleted),
onGitStateChanged: subscribe(FileWatcherEvent.GitStateChanged),
onWorkingTreeChanged: subscribe(FileWatcherEvent.WorkingTreeChanged),
});
52 changes: 37 additions & 15 deletions apps/code/src/main/trpc/routers/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,17 +125,23 @@ export const gitRouter = router({
getCurrentBranch: publicProcedure
.input(getCurrentBranchInput)
.output(getCurrentBranchOutput)
.query(({ input }) => getService().getCurrentBranch(input.directoryPath)),
.query(({ input, signal }) =>
getService().getCurrentBranch(input.directoryPath, signal),
),

getAllBranches: publicProcedure
.input(getAllBranchesInput)
.output(getAllBranchesOutput)
.query(({ input }) => getService().getAllBranches(input.directoryPath)),
.query(({ input, signal }) =>
getService().getAllBranches(input.directoryPath, signal),
),

getGitBusyState: publicProcedure
.input(getGitBusyStateInput)
.output(getGitBusyStateOutput)
.query(({ input }) => getService().getGitBusyState(input.directoryPath)),
.query(({ input, signal }) =>
getService().getGitBusyState(input.directoryPath, signal),
),

createBranch: publicProcedure
.input(createBranchInput)
Expand All @@ -154,42 +160,56 @@ export const gitRouter = router({
getChangedFilesHead: publicProcedure
.input(getChangedFilesHeadInput)
.output(getChangedFilesHeadOutput)
.query(({ input }) =>
getService().getChangedFilesHead(input.directoryPath),
.query(({ input, signal }) =>
getService().getChangedFilesHead(input.directoryPath, signal),
),

getFileAtHead: publicProcedure
.input(getFileAtHeadInput)
.output(getFileAtHeadOutput)
.query(({ input }) =>
getService().getFileAtHead(input.directoryPath, input.filePath),
.query(({ input, signal }) =>
getService().getFileAtHead(input.directoryPath, input.filePath, signal),
),

getDiffHead: publicProcedure
.input(diffInput)
.output(diffOutput)
.query(({ input }) =>
getService().getDiffHead(input.directoryPath, input.ignoreWhitespace),
.query(({ input, signal }) =>
getService().getDiffHead(
input.directoryPath,
input.ignoreWhitespace,
signal,
),
),

getDiffCached: publicProcedure
.input(diffInput)
.output(diffOutput)
.query(({ input }) =>
getService().getDiffCached(input.directoryPath, input.ignoreWhitespace),
.query(({ input, signal }) =>
getService().getDiffCached(
input.directoryPath,
input.ignoreWhitespace,
signal,
),
),

getDiffUnstaged: publicProcedure
.input(diffInput)
.output(diffOutput)
.query(({ input }) =>
getService().getDiffUnstaged(input.directoryPath, input.ignoreWhitespace),
.query(({ input, signal }) =>
getService().getDiffUnstaged(
input.directoryPath,
input.ignoreWhitespace,
signal,
),
),

getDiffStats: publicProcedure
.input(getDiffStatsInput)
.output(getDiffStatsOutput)
.query(({ input }) => getService().getDiffStats(input.directoryPath)),
.query(({ input, signal }) =>
getService().getDiffStats(input.directoryPath, signal),
),

stageFiles: publicProcedure
.input(stageFilesInput)
Expand Down Expand Up @@ -233,7 +253,9 @@ export const gitRouter = router({
getLatestCommit: publicProcedure
.input(getLatestCommitInput)
.output(getLatestCommitOutput)
.query(({ input }) => getService().getLatestCommit(input.directoryPath)),
.query(({ input, signal }) =>
getService().getLatestCommit(input.directoryPath, signal),
),

getGitRepoInfo: publicProcedure
.input(getGitRepoInfoInput)
Expand Down
11 changes: 9 additions & 2 deletions apps/code/src/renderer/hooks/useFileWatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ export function useFileWatcher(repoPath: string | null, taskId?: string) {
filePath: relativePath,
}),
);
invalidateGitWorkingTreeQueries(repoPath);
},
}),
);
Expand All @@ -57,7 +56,6 @@ export function useFileWatcher(repoPath: string | null, taskId?: string) {
enabled: !!repoPath,
onData: ({ repoPath: rp, filePath }) => {
if (rp !== repoPath) return;
invalidateGitWorkingTreeQueries(repoPath);
if (!taskId) return;
const relativePath = toRelativePath(filePath, repoPath);
closeTabsForFile(taskId, relativePath);
Expand All @@ -71,6 +69,15 @@ export function useFileWatcher(repoPath: string | null, taskId?: string) {
onData: ({ repoPath: rp }) => {
if (rp !== repoPath) return;
invalidateGitBranchQueries(repoPath);
},
}),
);

useSubscription(
trpc.fileWatcher.onWorkingTreeChanged.subscriptionOptions(undefined, {
enabled: !!repoPath,
onData: ({ repoPath: rp }) => {
if (rp !== repoPath) return;
invalidateGitWorkingTreeQueries(repoPath);
},
}),
Expand Down
Loading