mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 22:21:22 -07:00
fix(core): ensure headless git config is conditional and appends correctly
This commit is contained in:
@@ -1754,4 +1754,44 @@ describe('ShellExecutionService environment variables', () => {
|
||||
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
it('should NOT include headless git and gh environment variables in interactive fallback mode', async () => {
|
||||
vi.resetModules();
|
||||
vi.stubEnv('GIT_TERMINAL_PROMPT', undefined);
|
||||
vi.stubEnv('GIT_ASKPASS', undefined);
|
||||
vi.stubEnv('SSH_ASKPASS', undefined);
|
||||
vi.stubEnv('GH_PROMPT_DISABLED', undefined);
|
||||
vi.stubEnv('GCM_INTERACTIVE', undefined);
|
||||
vi.stubEnv('GIT_CONFIG_COUNT', undefined);
|
||||
|
||||
const { ShellExecutionService } = await import(
|
||||
'./shellExecutionService.js'
|
||||
);
|
||||
|
||||
mockGetPty.mockResolvedValue(null); // Force child_process fallback
|
||||
await ShellExecutionService.execute(
|
||||
'test-cp-interactive-fallback',
|
||||
'/',
|
||||
vi.fn(),
|
||||
new AbortController().signal,
|
||||
true, // isInteractive (shouldUseNodePty)
|
||||
shellExecutionConfig,
|
||||
);
|
||||
|
||||
expect(mockCpSpawn).toHaveBeenCalled();
|
||||
const cpEnv = mockCpSpawn.mock.calls[0][2].env;
|
||||
expect(cpEnv).not.toHaveProperty('GIT_TERMINAL_PROMPT');
|
||||
expect(cpEnv).not.toHaveProperty('GIT_ASKPASS');
|
||||
expect(cpEnv).not.toHaveProperty('SSH_ASKPASS');
|
||||
expect(cpEnv).not.toHaveProperty('GH_PROMPT_DISABLED');
|
||||
expect(cpEnv).not.toHaveProperty('GCM_INTERACTIVE');
|
||||
expect(cpEnv).not.toHaveProperty('GIT_CONFIG_COUNT');
|
||||
|
||||
// Ensure child_process exits
|
||||
mockChildProcess.emit('exit', 0, null);
|
||||
mockChildProcess.emit('close', 0, null);
|
||||
await new Promise(process.nextTick);
|
||||
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -252,6 +252,7 @@ export class ShellExecutionService {
|
||||
onOutputEvent,
|
||||
abortSignal,
|
||||
shellExecutionConfig.sanitizationConfig,
|
||||
shouldUseNodePty,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -298,6 +299,7 @@ export class ShellExecutionService {
|
||||
onOutputEvent: (event: ShellOutputEvent) => void,
|
||||
abortSignal: AbortSignal,
|
||||
sanitizationConfig: EnvironmentSanitizationConfig,
|
||||
isInteractive: boolean,
|
||||
): ShellExecutionHandle {
|
||||
try {
|
||||
const isWindows = os.platform() === 'win32';
|
||||
@@ -305,23 +307,34 @@ export class ShellExecutionService {
|
||||
const guardedCommand = ensurePromptvarsDisabled(commandToExecute, shell);
|
||||
const spawnArgs = [...argsPrefix, guardedCommand];
|
||||
|
||||
const sanitizedEnv = sanitizeEnvironment(process.env, sanitizationConfig);
|
||||
const gitConfigCount = parseInt(
|
||||
sanitizedEnv['GIT_CONFIG_COUNT'] || '0',
|
||||
10,
|
||||
);
|
||||
// Specifically allow GIT_CONFIG_* variables to pass through sanitization
|
||||
// in non-interactive mode so we can safely append our overrides.
|
||||
const gitConfigKeys = !isInteractive
|
||||
? Object.keys(process.env).filter((k) => k.startsWith('GIT_CONFIG_'))
|
||||
: [];
|
||||
const sanitizedEnv = sanitizeEnvironment(process.env, {
|
||||
...sanitizationConfig,
|
||||
allowedEnvironmentVariables: [
|
||||
...(sanitizationConfig.allowedEnvironmentVariables || []),
|
||||
...gitConfigKeys,
|
||||
],
|
||||
});
|
||||
|
||||
const child = cpSpawn(executable, spawnArgs, {
|
||||
cwd,
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
windowsVerbatimArguments: isWindows ? false : undefined,
|
||||
shell: false,
|
||||
detached: !isWindows,
|
||||
env: {
|
||||
...sanitizedEnv,
|
||||
[GEMINI_CLI_IDENTIFICATION_ENV_VAR]:
|
||||
GEMINI_CLI_IDENTIFICATION_ENV_VAR_VALUE,
|
||||
TERM: 'xterm-256color',
|
||||
const env: NodeJS.ProcessEnv = {
|
||||
...sanitizedEnv,
|
||||
[GEMINI_CLI_IDENTIFICATION_ENV_VAR]:
|
||||
GEMINI_CLI_IDENTIFICATION_ENV_VAR_VALUE,
|
||||
TERM: 'xterm-256color',
|
||||
PAGER: 'cat',
|
||||
GIT_PAGER: 'cat',
|
||||
};
|
||||
|
||||
if (!isInteractive) {
|
||||
const gitConfigCount = parseInt(
|
||||
sanitizedEnv['GIT_CONFIG_COUNT'] || '0',
|
||||
10,
|
||||
);
|
||||
Object.assign(env, {
|
||||
// Disable interactive prompts and session-linked credential helpers
|
||||
// in non-interactive mode to prevent hangs in detached process groups.
|
||||
GIT_TERMINAL_PROMPT: '0',
|
||||
@@ -334,9 +347,16 @@ export class ShellExecutionService {
|
||||
GIT_CONFIG_COUNT: (gitConfigCount + 1).toString(),
|
||||
[`GIT_CONFIG_KEY_${gitConfigCount}`]: 'credential.helper',
|
||||
[`GIT_CONFIG_VALUE_${gitConfigCount}`]: '',
|
||||
PAGER: 'cat',
|
||||
GIT_PAGER: 'cat',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const child = cpSpawn(executable, spawnArgs, {
|
||||
cwd,
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
windowsVerbatimArguments: isWindows ? false : undefined,
|
||||
shell: false,
|
||||
detached: !isWindows,
|
||||
env,
|
||||
});
|
||||
|
||||
const state = {
|
||||
|
||||
Reference in New Issue
Block a user