mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-23 03:24:42 -07:00
feat(worktree): add Git worktree support for isolated parallel sessions (#22973)
This commit is contained in:
@@ -8,10 +8,12 @@ import { renderWithProviders } from '../../test-utils/render.js';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { SessionSummaryDisplay } from './SessionSummaryDisplay.js';
|
||||
import * as SessionContext from '../contexts/SessionContext.js';
|
||||
import { useConfig } from '../contexts/ConfigContext.js';
|
||||
import { type SessionMetrics } from '../contexts/SessionContext.js';
|
||||
import {
|
||||
ToolCallDecision,
|
||||
getShellConfiguration,
|
||||
type WorktreeSettings,
|
||||
} from '@google/gemini-cli-core';
|
||||
|
||||
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
@@ -24,19 +26,30 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
});
|
||||
|
||||
vi.mock('../contexts/SessionContext.js', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof SessionContext>();
|
||||
const actual =
|
||||
await importOriginal<typeof import('../contexts/SessionContext.js')>();
|
||||
return {
|
||||
...actual,
|
||||
useSessionStats: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('../contexts/ConfigContext.js', async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import('../contexts/ConfigContext.js')>();
|
||||
return {
|
||||
...actual,
|
||||
useConfig: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const getShellConfigurationMock = vi.mocked(getShellConfiguration);
|
||||
const useSessionStatsMock = vi.mocked(SessionContext.useSessionStats);
|
||||
|
||||
const renderWithMockedStats = async (
|
||||
metrics: SessionMetrics,
|
||||
sessionId = 'test-session',
|
||||
worktreeSettings?: WorktreeSettings,
|
||||
) => {
|
||||
useSessionStatsMock.mockReturnValue({
|
||||
stats: {
|
||||
@@ -49,7 +62,11 @@ const renderWithMockedStats = async (
|
||||
|
||||
getPromptCount: () => 5,
|
||||
startNewPrompt: vi.fn(),
|
||||
});
|
||||
} as unknown as ReturnType<typeof SessionContext.useSessionStats>);
|
||||
|
||||
vi.mocked(useConfig).mockReturnValue({
|
||||
getWorktreeSettings: () => worktreeSettings,
|
||||
} as never);
|
||||
|
||||
const result = await renderWithProviders(
|
||||
<SessionSummaryDisplay duration="1h 23m 45s" />,
|
||||
@@ -188,4 +205,30 @@ describe('<SessionSummaryDisplay />', () => {
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Worktree status', () => {
|
||||
it('renders worktree instructions when worktreeSettings are present', async () => {
|
||||
const worktreeSettings: WorktreeSettings = {
|
||||
name: 'foo-bar',
|
||||
path: '/path/to/foo-bar',
|
||||
baseSha: 'base-sha',
|
||||
};
|
||||
|
||||
const { lastFrame, unmount } = await renderWithMockedStats(
|
||||
emptyMetrics,
|
||||
'test-session',
|
||||
worktreeSettings,
|
||||
);
|
||||
const output = lastFrame();
|
||||
|
||||
expect(output).toContain('To resume work in this worktree:');
|
||||
expect(output).toContain(
|
||||
'cd /path/to/foo-bar && gemini --resume test-session',
|
||||
);
|
||||
expect(output).toContain(
|
||||
'To remove manually: git worktree remove /path/to/foo-bar',
|
||||
);
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import type React from 'react';
|
||||
import { StatsDisplay } from './StatsDisplay.js';
|
||||
import { useSessionStats } from '../contexts/SessionContext.js';
|
||||
import { useConfig } from '../contexts/ConfigContext.js';
|
||||
import { escapeShellArg, getShellConfiguration } from '@google/gemini-cli-core';
|
||||
|
||||
interface SessionSummaryDisplayProps {
|
||||
@@ -17,8 +18,19 @@ export const SessionSummaryDisplay: React.FC<SessionSummaryDisplayProps> = ({
|
||||
duration,
|
||||
}) => {
|
||||
const { stats } = useSessionStats();
|
||||
const config = useConfig();
|
||||
const { shell } = getShellConfiguration();
|
||||
const footer = `To resume this session: gemini --resume ${escapeShellArg(stats.sessionId, shell)}`;
|
||||
|
||||
const worktreeSettings = config.getWorktreeSettings();
|
||||
|
||||
const escapedSessionId = escapeShellArg(stats.sessionId, shell);
|
||||
let footer = `To resume this session: gemini --resume ${escapedSessionId}`;
|
||||
|
||||
if (worktreeSettings) {
|
||||
footer =
|
||||
`To resume work in this worktree: cd ${escapeShellArg(worktreeSettings.path, shell)} && gemini --resume ${escapedSessionId}\n` +
|
||||
`To remove manually: git worktree remove ${escapeShellArg(worktreeSettings.path, shell)}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<StatsDisplay
|
||||
|
||||
Reference in New Issue
Block a user