[A2A] Disable checkpointing if git is not installed (#16896)

This commit is contained in:
Coco Sheng
2026-01-20 11:38:38 -05:00
committed by GitHub
parent 166e04a8dd
commit 79076d1d52
5 changed files with 133 additions and 10 deletions
+4 -1
View File
@@ -575,7 +575,10 @@ export class Task {
EDIT_TOOL_NAMES.has(request.name), EDIT_TOOL_NAMES.has(request.name),
); );
if (restorableToolCalls.length > 0) { if (
restorableToolCalls.length > 0 &&
this.config.getCheckpointingEnabled()
) {
const gitService = await this.config.getGitService(); const gitService = await this.config.getGitService();
if (gitService) { if (gitService) {
const { checkpointsToWrite, toolCallToCheckpointMap, errors } = const { checkpointsToWrite, toolCallToCheckpointMap, errors } =
@@ -0,0 +1,110 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { loadConfig } from './config.js';
import type { ExtensionLoader } from '@google/gemini-cli-core';
import type { Settings } from './settings.js';
const {
mockLoadServerHierarchicalMemory,
mockConfigConstructor,
mockVerifyGitAvailability,
} = vi.hoisted(() => ({
mockLoadServerHierarchicalMemory: vi.fn().mockResolvedValue({
memoryContent: '',
fileCount: 0,
filePaths: [],
}),
mockConfigConstructor: vi.fn(),
mockVerifyGitAvailability: vi.fn(),
}));
vi.mock('@google/gemini-cli-core', async () => ({
Config: class MockConfig {
constructor(params: unknown) {
mockConfigConstructor(params);
}
initialize = vi.fn();
refreshAuth = vi.fn();
},
loadServerHierarchicalMemory: mockLoadServerHierarchicalMemory,
startupProfiler: {
flush: vi.fn(),
},
FileDiscoveryService: vi.fn(),
ApprovalMode: { DEFAULT: 'default', YOLO: 'yolo' },
AuthType: {
LOGIN_WITH_GOOGLE: 'login_with_google',
USE_GEMINI: 'use_gemini',
},
GEMINI_DIR: '.gemini',
DEFAULT_GEMINI_EMBEDDING_MODEL: 'models/embedding-001',
DEFAULT_GEMINI_MODEL: 'models/gemini-1.5-flash',
PREVIEW_GEMINI_MODEL: 'models/gemini-1.5-pro-latest',
homedir: () => '/tmp',
GitService: {
verifyGitAvailability: mockVerifyGitAvailability,
},
}));
describe('loadConfig', () => {
const mockSettings = {
checkpointing: { enabled: true },
};
const mockExtensionLoader = {
start: vi.fn(),
getExtensions: vi.fn().mockReturnValue([]),
} as unknown as ExtensionLoader;
beforeEach(() => {
vi.resetAllMocks();
process.env['GEMINI_API_KEY'] = 'test-key';
// Reset the mock return value just in case
mockLoadServerHierarchicalMemory.mockResolvedValue({
memoryContent: '',
fileCount: 0,
filePaths: [],
});
});
afterEach(() => {
delete process.env['GEMINI_API_KEY'];
delete process.env['CHECKPOINTING'];
});
it('should disable checkpointing if git is not installed', async () => {
mockVerifyGitAvailability.mockResolvedValue(false);
await loadConfig(
mockSettings as unknown as Settings,
mockExtensionLoader,
'test-task',
);
expect(mockConfigConstructor).toHaveBeenCalledWith(
expect.objectContaining({
checkpointing: false,
}),
);
});
it('should enable checkpointing if git is installed', async () => {
mockVerifyGitAvailability.mockResolvedValue(true);
await loadConfig(
mockSettings as unknown as Settings,
mockExtensionLoader,
'test-task',
);
expect(mockConfigConstructor).toHaveBeenCalledWith(
expect.objectContaining({
checkpointing: true,
}),
);
});
});
+15 -3
View File
@@ -23,6 +23,7 @@ import {
startupProfiler, startupProfiler,
PREVIEW_GEMINI_MODEL, PREVIEW_GEMINI_MODEL,
homedir, homedir,
GitService,
} from '@google/gemini-cli-core'; } from '@google/gemini-cli-core';
import { logger } from '../utils/logger.js'; import { logger } from '../utils/logger.js';
@@ -41,6 +42,19 @@ export async function loadConfig(
settings.folderTrust === true || settings.folderTrust === true ||
process.env['GEMINI_FOLDER_TRUST'] === 'true'; process.env['GEMINI_FOLDER_TRUST'] === 'true';
let checkpointing = process.env['CHECKPOINTING']
? process.env['CHECKPOINTING'] === 'true'
: settings.checkpointing?.enabled;
if (checkpointing) {
if (!(await GitService.verifyGitAvailability())) {
logger.warn(
'[Config] Checkpointing is enabled but git is not installed. Disabling checkpointing.',
);
checkpointing = false;
}
}
const configParams: ConfigParameters = { const configParams: ConfigParameters = {
sessionId: taskId, sessionId: taskId,
model: settings.general?.previewFeatures model: settings.general?.previewFeatures
@@ -79,9 +93,7 @@ export async function loadConfig(
folderTrust, folderTrust,
trustedFolder: true, trustedFolder: true,
extensionLoader, extensionLoader,
checkpointing: process.env['CHECKPOINTING'] checkpointing,
? process.env['CHECKPOINTING'] === 'true'
: settings.checkpointing?.enabled,
previewFeatures: settings.general?.previewFeatures, previewFeatures: settings.general?.previewFeatures,
interactive: true, interactive: true,
enableInteractiveShell: true, enableInteractiveShell: true,
@@ -147,15 +147,13 @@ describe('GitService', () => {
describe('verifyGitAvailability', () => { describe('verifyGitAvailability', () => {
it('should resolve true if git --version command succeeds', async () => { it('should resolve true if git --version command succeeds', async () => {
const service = new GitService(projectRoot, storage); await expect(GitService.verifyGitAvailability()).resolves.toBe(true);
await expect(service.verifyGitAvailability()).resolves.toBe(true);
expect(spawnAsync).toHaveBeenCalledWith('git', ['--version']); expect(spawnAsync).toHaveBeenCalledWith('git', ['--version']);
}); });
it('should resolve false if git --version command fails', async () => { it('should resolve false if git --version command fails', async () => {
(spawnAsync as Mock).mockRejectedValue(new Error('git not found')); (spawnAsync as Mock).mockRejectedValue(new Error('git not found'));
const service = new GitService(projectRoot, storage); await expect(GitService.verifyGitAvailability()).resolves.toBe(false);
await expect(service.verifyGitAvailability()).resolves.toBe(false);
}); });
}); });
+2 -2
View File
@@ -27,7 +27,7 @@ export class GitService {
} }
async initialize(): Promise<void> { async initialize(): Promise<void> {
const gitAvailable = await this.verifyGitAvailability(); const gitAvailable = await GitService.verifyGitAvailability();
if (!gitAvailable) { if (!gitAvailable) {
throw new Error( throw new Error(
'Checkpointing is enabled, but Git is not installed. Please install Git or disable checkpointing to continue.', 'Checkpointing is enabled, but Git is not installed. Please install Git or disable checkpointing to continue.',
@@ -42,7 +42,7 @@ export class GitService {
} }
} }
async verifyGitAvailability(): Promise<boolean> { static async verifyGitAvailability(): Promise<boolean> {
try { try {
await spawnAsync('git', ['--version']); await spawnAsync('git', ['--version']);
return true; return true;