Skip to content
Open
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
55 changes: 55 additions & 0 deletions main/src/ipc/daemonRegistryBindings.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe, expect, it, vi } from 'vitest';
import { PaneCommandRegistry } from '../daemon/commandRegistry';
import { registerFileHandlers } from './file';
import { registerGitHandlers } from './git';
import { registerPanelHandlers } from './panels';
import { registerProjectHandlers } from './project';
import { registerPromptHandlers } from './prompt';
Expand Down Expand Up @@ -160,6 +161,43 @@ const SESSION_CHANNELS = [
'panels:continue',
] as const;

const GIT_STATUS_CHANNELS = [
'sessions:get-executions',
'sessions:get-execution-diff',
'sessions:get-git-graph',
'git:file-status',
'sessions:git-diff',
'sessions:get-commit-diff-by-hash',
'sessions:get-combined-diff',
'sessions:check-rebase-conflicts',
'sessions:has-stash',
'sessions:get-upstream',
'sessions:get-remote-branches',
'sessions:get-last-commits',
'sessions:has-changes-to-rebase',
'sessions:get-git-commands',
'sessions:get-git-status',
'git:cancel-status-for-project',
'git:get-github-remote',
] as const;

const GIT_DIRECT_MUTATION_CHANNELS = [
'sessions:git-commit',
'sessions:rebase-main-into-worktree',
'sessions:abort-rebase-and-use-claude',
'sessions:squash-and-rebase-to-main',
'sessions:rebase-to-main',
'sessions:git-pull',
'sessions:git-push',
'sessions:git-soft-reset',
'sessions:git-fetch',
'sessions:git-stash',
'sessions:git-stash-pop',
'sessions:set-upstream',
'sessions:git-stage-and-commit',
'git:clone-repo',
] as const;

interface IpcMainStub {
boundChannels: string[];
handle(channel: string, listener: (_event: unknown, ...args: unknown[]) => unknown): void;
Expand All @@ -183,6 +221,7 @@ function createServicesStub(): AppServices {
configManager: {},
databaseService: {},
worktreeManager: {},
gitDiffManager: {},
analyticsManager: {},
taskQueue: {},
cliManagerFactory: {},
Expand Down Expand Up @@ -280,4 +319,20 @@ describe('daemon registry IPC bindings', () => {
).toEqual([...SESSION_CHANNELS].sort());
expect(registry.has('sessions:set-active-session')).toBe(false);
});

it('keeps git mutators direct while routing git status and history through the registry', () => {
const registry = new PaneCommandRegistry();
const ipcMain = createIpcMainStub();

registerGitHandlers(ipcMain, createServicesStub(), registry);

expect(registry.listChannels()).toEqual([...GIT_STATUS_CHANNELS].sort());
expect(
ipcMain.boundChannels.filter(channel => !GIT_DIRECT_MUTATION_CHANNELS.includes(channel as (typeof GIT_DIRECT_MUTATION_CHANNELS)[number])).sort(),
).toEqual([...GIT_STATUS_CHANNELS].sort());
expect(ipcMain.boundChannels.filter(channel => GIT_DIRECT_MUTATION_CHANNELS.includes(channel as (typeof GIT_DIRECT_MUTATION_CHANNELS)[number])).sort()).toEqual(
[...GIT_DIRECT_MUTATION_CHANNELS].sort(),
);
expect(registry.has('git:clone-repo')).toBe(false);
});
});
65 changes: 46 additions & 19 deletions main/src/ipc/git.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { IpcMain } from 'electron';
import type { IpcMain } from 'electron';
import { existsSync } from 'fs';
import { join } from 'path';
import type { AppServices } from './types';
import type { PaneCommandRegistry } from '../daemon/commandRegistry';
import { buildGitCommitCommand } from '../utils/shellEscape';
import { getPaneEventSink } from '../core/runtime';
import { panelEventBus } from '../services/panelEventBus';
Expand Down Expand Up @@ -64,7 +65,31 @@ function extractRepoName(url: string): string {
return lastSegment;
}

export function registerGitHandlers(ipcMain: IpcMain, services: AppServices): void {
const DAEMON_GIT_STATUS_CHANNELS = [
'sessions:get-executions',
'sessions:get-execution-diff',
'sessions:get-git-graph',
'git:file-status',
'sessions:git-diff',
'sessions:get-commit-diff-by-hash',
'sessions:get-combined-diff',
'sessions:check-rebase-conflicts',
'sessions:has-stash',
'sessions:get-upstream',
'sessions:get-remote-branches',
'sessions:get-last-commits',
'sessions:has-changes-to-rebase',
'sessions:get-git-commands',
'sessions:get-git-status',
'git:cancel-status-for-project',
'git:get-github-remote',
] as const;

export function registerGitHandlers(
ipcMain: IpcMain,
services: AppServices,
commandRegistry: PaneCommandRegistry,
): void {
const { sessionManager, gitDiffManager, worktreeManager, claudeCodeManager, gitStatusManager, databaseService } = services;

// Helper function to emit git operation events to all sessions in a project
Expand Down Expand Up @@ -201,7 +226,7 @@ export function registerGitHandlers(ipcMain: IpcMain, services: AppServices): vo
};
};

ipcMain.handle('sessions:get-executions', async (_event, sessionId: string) => {
commandRegistry.register('sessions:get-executions', async (sessionId: string) => {
try {
const session = await sessionManager.getSession(sessionId);
if (!session || !session.worktreePath) {
Expand Down Expand Up @@ -262,7 +287,7 @@ export function registerGitHandlers(ipcMain: IpcMain, services: AppServices): vo
}
});

ipcMain.handle('sessions:get-execution-diff', async (_event, sessionId: string, executionId: string) => {
commandRegistry.register('sessions:get-execution-diff', async (sessionId: string, executionId: string) => {
try {
const session = await sessionManager.getSession(sessionId);
if (!session || !session.worktreePath) {
Expand Down Expand Up @@ -290,7 +315,7 @@ export function registerGitHandlers(ipcMain: IpcMain, services: AppServices): vo
}
});

ipcMain.handle('sessions:get-git-graph', async (_event, sessionId: string) => {
commandRegistry.register('sessions:get-git-graph', async (sessionId: string) => {
try {
const session = await sessionManager.getSession(sessionId);
if (!session || !session.worktreePath) {
Expand Down Expand Up @@ -427,7 +452,7 @@ export function registerGitHandlers(ipcMain: IpcMain, services: AppServices): vo
}
});

ipcMain.handle('git:file-status', async (_event, sessionId: string, filePath: string) => {
commandRegistry.register('git:file-status', async (sessionId: string, filePath: string) => {
try {
const session = await sessionManager.getSession(sessionId);
if (!session || !session.worktreePath) {
Expand Down Expand Up @@ -455,7 +480,7 @@ export function registerGitHandlers(ipcMain: IpcMain, services: AppServices): vo
}
});

ipcMain.handle('sessions:git-diff', async (_event, sessionId: string) => {
commandRegistry.register('sessions:git-diff', async (sessionId: string) => {
try {
const session = await sessionManager.getSession(sessionId);
if (!session || !session.worktreePath) {
Expand All @@ -482,7 +507,7 @@ export function registerGitHandlers(ipcMain: IpcMain, services: AppServices): vo
}
});

ipcMain.handle('sessions:get-commit-diff-by-hash', async (_event, sessionId: string, commitHash: string) => {
commandRegistry.register('sessions:get-commit-diff-by-hash', async (sessionId: string, commitHash: string) => {
try {
const session = await sessionManager.getSession(sessionId);
if (!session || !session.worktreePath) {
Expand Down Expand Up @@ -510,7 +535,7 @@ export function registerGitHandlers(ipcMain: IpcMain, services: AppServices): vo
}
});

ipcMain.handle('sessions:get-combined-diff', async (_event, sessionId: string, executionIds?: number[]) => {
commandRegistry.register('sessions:get-combined-diff', async (sessionId: string, executionIds?: number[]) => {
try {
// Get session to find worktree path
const session = await sessionManager.getSession(sessionId);
Expand Down Expand Up @@ -777,7 +802,7 @@ export function registerGitHandlers(ipcMain: IpcMain, services: AppServices): vo
});

// Git rebase operations
ipcMain.handle('sessions:check-rebase-conflicts', async (_event, sessionId: string) => {
commandRegistry.register('sessions:check-rebase-conflicts', async (sessionId: string) => {
try {
const session = await sessionManager.getSession(sessionId);
if (!session) {
Expand Down Expand Up @@ -1634,7 +1659,7 @@ export function registerGitHandlers(ipcMain: IpcMain, services: AppServices): vo
}
});

ipcMain.handle('sessions:has-stash', async (_event, sessionId: string) => {
commandRegistry.register('sessions:has-stash', async (sessionId: string) => {
try {
const session = await sessionManager.getSession(sessionId);
if (!session) {
Expand Down Expand Up @@ -1693,7 +1718,7 @@ export function registerGitHandlers(ipcMain: IpcMain, services: AppServices): vo
}
});

ipcMain.handle('sessions:get-upstream', async (_event, sessionId: string) => {
commandRegistry.register('sessions:get-upstream', async (sessionId: string) => {
try {
const session = await sessionManager.getSession(sessionId);
if (!session) {
Expand All @@ -1718,7 +1743,7 @@ export function registerGitHandlers(ipcMain: IpcMain, services: AppServices): vo
}
});

ipcMain.handle('sessions:get-remote-branches', async (_event, sessionId: string) => {
commandRegistry.register('sessions:get-remote-branches', async (sessionId: string) => {
try {
const session = await sessionManager.getSession(sessionId);
if (!session) {
Expand Down Expand Up @@ -1804,7 +1829,7 @@ export function registerGitHandlers(ipcMain: IpcMain, services: AppServices): vo
}
});

ipcMain.handle('sessions:get-last-commits', async (_event, sessionId: string, count: number = 50) => {
commandRegistry.register('sessions:get-last-commits', async (sessionId: string, count: number = 50) => {
try {
const session = await sessionManager.getSession(sessionId);
if (!session) {
Expand Down Expand Up @@ -1848,7 +1873,7 @@ export function registerGitHandlers(ipcMain: IpcMain, services: AppServices): vo
});

// Git operation helpers
ipcMain.handle('sessions:has-changes-to-rebase', async (_event, sessionId: string) => {
commandRegistry.register('sessions:has-changes-to-rebase', async (sessionId: string) => {
try {
const session = await sessionManager.getSession(sessionId);
if (!session || !session.worktreePath) {
Expand All @@ -1873,7 +1898,7 @@ export function registerGitHandlers(ipcMain: IpcMain, services: AppServices): vo
}
});

ipcMain.handle('sessions:get-git-commands', async (_event, sessionId: string) => {
commandRegistry.register('sessions:get-git-commands', async (sessionId: string) => {
try {
const session = await sessionManager.getSession(sessionId);
if (!session || !session.worktreePath) {
Expand Down Expand Up @@ -1930,7 +1955,7 @@ export function registerGitHandlers(ipcMain: IpcMain, services: AppServices): vo
}
});

ipcMain.handle('sessions:get-git-status', async (_event, sessionId: string, nonBlocking?: boolean, isInitialLoad?: boolean) => {
commandRegistry.register('sessions:get-git-status', async (sessionId: string, nonBlocking?: boolean, isInitialLoad?: boolean) => {
try {
const session = await sessionManager.getSession(sessionId);
if (!session || !session.worktreePath) {
Expand Down Expand Up @@ -1979,7 +2004,7 @@ export function registerGitHandlers(ipcMain: IpcMain, services: AppServices): vo
}
});

ipcMain.handle('git:cancel-status-for-project', async (_event, projectId: number) => {
commandRegistry.register('git:cancel-status-for-project', async (projectId: number) => {
try {
// Get all sessions for the project
const sessions = await sessionManager.getAllSessions();
Expand All @@ -1996,7 +2021,7 @@ export function registerGitHandlers(ipcMain: IpcMain, services: AppServices): vo
}
});

ipcMain.handle('git:get-github-remote', async (_event, sessionId: string) => {
commandRegistry.register('git:get-github-remote', async (sessionId: string) => {
try {
const session = sessionManager.getSession(sessionId);
if (!session?.worktreePath) {
Expand Down Expand Up @@ -2095,4 +2120,6 @@ export function registerGitHandlers(ipcMain: IpcMain, services: AppServices): vo
return { success: false, error: errorMsg };
}
});

commandRegistry.bindChannels(ipcMain, DAEMON_GIT_STATUS_CHANNELS);
}
2 changes: 1 addition & 1 deletion main/src/ipc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function registerIpcHandlers(services: AppServices): PaneCommandRegistry
registerProjectHandlers(ipcMain, services, commandRegistry);
registerConfigHandlers(ipcMain, services);
registerDialogHandlers(ipcMain, services);
registerGitHandlers(ipcMain, services);
registerGitHandlers(ipcMain, services, commandRegistry);
registerScriptHandlers(ipcMain, services, commandRegistry);
registerPromptHandlers(ipcMain, services, commandRegistry);
registerFileHandlers(ipcMain, services, commandRegistry);
Expand Down