diff --git a/apps/x/apps/main/src/ipc.ts b/apps/x/apps/main/src/ipc.ts index c7192143..5ed785bf 100644 --- a/apps/x/apps/main/src/ipc.ts +++ b/apps/x/apps/main/src/ipc.ts @@ -1006,12 +1006,22 @@ export function setupIpcHandlers() { if (!info.isGitRepo) { return { isRepo: false, branch: null, hasCommits: false, files: [] }; } - const files = await codeGit.status(session.cwd); + let files = await codeGit.status(session.cwd); + if (session.worktree && !session.worktree.removedAt && session.worktree.baseBranch) { + const branchFiles = await codeGit.changedSinceBase(session.cwd, session.worktree.baseBranch); + const byPath = new Map(branchFiles.map((file) => [file.path, file])); + for (const file of files) { + if (!byPath.has(file.path)) byPath.set(file.path, file); + } + files = [...byPath.values()]; + } return { isRepo: true, branch: info.branch, hasCommits: info.hasCommits, files }; }, 'codeSession:fileDiff': async (_event, args) => { const session = await requireCodeSession(args.sessionId); - return codeGit.fileDiff(session.cwd, args.path); + return codeGit.fileDiff(session.cwd, args.path, { + baseRef: session.worktree && !session.worktree.removedAt ? session.worktree.baseBranch : null, + }); }, 'codeSession:readdir': async (_event, args) => { const session = await requireCodeSession(args.sessionId); diff --git a/apps/x/packages/core/src/code-mode/git/service.ts b/apps/x/packages/core/src/code-mode/git/service.ts index 7729961b..c49ba259 100644 --- a/apps/x/packages/core/src/code-mode/git/service.ts +++ b/apps/x/packages/core/src/code-mode/git/service.ts @@ -78,6 +78,14 @@ async function repoToplevel(cwd: string): Promise { } } +async function mergeBase(cwd: string, baseRef: string): Promise { + try { + return (await git(cwd, ['merge-base', baseRef, 'HEAD'])).trim() || baseRef; + } catch { + return baseRef; + } +} + function stateFromPorcelain(xy: string): GitFileState { if (xy === '??') return 'untracked'; if (xy.includes('R')) return 'renamed'; @@ -166,12 +174,7 @@ export async function status(cwd: string): Promise { // so it misses work an agent committed; this is what you want for a session // summary. Counts come from numstat, states from name-status, merged by path. export async function changedSinceBase(cwd: string, baseRef: string): Promise { - let forkPoint = baseRef; - try { - forkPoint = (await git(cwd, ['merge-base', baseRef, 'HEAD'])).trim() || baseRef; - } catch { - forkPoint = baseRef; - } + const forkPoint = await mergeBase(cwd, baseRef); const stateByPath = new Map(); try { @@ -229,7 +232,7 @@ export interface FileDiff { tooLarge: boolean; } -export async function fileDiff(cwd: string, relPath: string): Promise { +export async function fileDiff(cwd: string, relPath: string, opts: { baseRef?: string | null } = {}): Promise { // Paths from `status` are repo-root-relative; paths clicked in the chat // timeline are cwd-relative. Resolve whichever interpretation points at a // real file (deleted files fall back to the root interpretation, which is @@ -250,7 +253,8 @@ export async function fileDiff(cwd: string, relPath: string): Promise } let oldText = ''; try { - oldText = await git(cwd, ['show', `HEAD:${gitPath}`]); + const oldRef = opts.baseRef ? await mergeBase(cwd, opts.baseRef) : 'HEAD'; + oldText = await git(cwd, ['show', `${oldRef}:${gitPath}`]); } catch { // untracked / newly added / no commits — diff against empty oldText = '';