mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-13 05:12:55 -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();
|
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,
|
onOutputEvent,
|
||||||
abortSignal,
|
abortSignal,
|
||||||
shellExecutionConfig.sanitizationConfig,
|
shellExecutionConfig.sanitizationConfig,
|
||||||
|
shouldUseNodePty,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,6 +299,7 @@ export class ShellExecutionService {
|
|||||||
onOutputEvent: (event: ShellOutputEvent) => void,
|
onOutputEvent: (event: ShellOutputEvent) => void,
|
||||||
abortSignal: AbortSignal,
|
abortSignal: AbortSignal,
|
||||||
sanitizationConfig: EnvironmentSanitizationConfig,
|
sanitizationConfig: EnvironmentSanitizationConfig,
|
||||||
|
isInteractive: boolean,
|
||||||
): ShellExecutionHandle {
|
): ShellExecutionHandle {
|
||||||
try {
|
try {
|
||||||
const isWindows = os.platform() === 'win32';
|
const isWindows = os.platform() === 'win32';
|
||||||
@@ -305,23 +307,34 @@ export class ShellExecutionService {
|
|||||||
const guardedCommand = ensurePromptvarsDisabled(commandToExecute, shell);
|
const guardedCommand = ensurePromptvarsDisabled(commandToExecute, shell);
|
||||||
const spawnArgs = [...argsPrefix, guardedCommand];
|
const spawnArgs = [...argsPrefix, guardedCommand];
|
||||||
|
|
||||||
const sanitizedEnv = sanitizeEnvironment(process.env, sanitizationConfig);
|
// Specifically allow GIT_CONFIG_* variables to pass through sanitization
|
||||||
const gitConfigCount = parseInt(
|
// in non-interactive mode so we can safely append our overrides.
|
||||||
sanitizedEnv['GIT_CONFIG_COUNT'] || '0',
|
const gitConfigKeys = !isInteractive
|
||||||
10,
|
? 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, {
|
const env: NodeJS.ProcessEnv = {
|
||||||
cwd,
|
...sanitizedEnv,
|
||||||
stdio: ['ignore', 'pipe', 'pipe'],
|
[GEMINI_CLI_IDENTIFICATION_ENV_VAR]:
|
||||||
windowsVerbatimArguments: isWindows ? false : undefined,
|
GEMINI_CLI_IDENTIFICATION_ENV_VAR_VALUE,
|
||||||
shell: false,
|
TERM: 'xterm-256color',
|
||||||
detached: !isWindows,
|
PAGER: 'cat',
|
||||||
env: {
|
GIT_PAGER: 'cat',
|
||||||
...sanitizedEnv,
|
};
|
||||||
[GEMINI_CLI_IDENTIFICATION_ENV_VAR]:
|
|
||||||
GEMINI_CLI_IDENTIFICATION_ENV_VAR_VALUE,
|
if (!isInteractive) {
|
||||||
TERM: 'xterm-256color',
|
const gitConfigCount = parseInt(
|
||||||
|
sanitizedEnv['GIT_CONFIG_COUNT'] || '0',
|
||||||
|
10,
|
||||||
|
);
|
||||||
|
Object.assign(env, {
|
||||||
// Disable interactive prompts and session-linked credential helpers
|
// Disable interactive prompts and session-linked credential helpers
|
||||||
// in non-interactive mode to prevent hangs in detached process groups.
|
// in non-interactive mode to prevent hangs in detached process groups.
|
||||||
GIT_TERMINAL_PROMPT: '0',
|
GIT_TERMINAL_PROMPT: '0',
|
||||||
@@ -334,9 +347,16 @@ export class ShellExecutionService {
|
|||||||
GIT_CONFIG_COUNT: (gitConfigCount + 1).toString(),
|
GIT_CONFIG_COUNT: (gitConfigCount + 1).toString(),
|
||||||
[`GIT_CONFIG_KEY_${gitConfigCount}`]: 'credential.helper',
|
[`GIT_CONFIG_KEY_${gitConfigCount}`]: 'credential.helper',
|
||||||
[`GIT_CONFIG_VALUE_${gitConfigCount}`]: '',
|
[`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 = {
|
const state = {
|
||||||
|
|||||||
Reference in New Issue
Block a user