Skip to content

Commit 9020224

Browse files
committed
Isolate Git object reads from inherited environment
1 parent 39a6d70 commit 9020224

3 files changed

Lines changed: 21 additions & 2 deletions

File tree

‎docs/implementation/build-step-1.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ Copilot reported two findings. Fixed scope after rename: scope now travels with
3737

3838
Declined the claim that repository aliases can override `rev-parse`, `rev-list`, `diff`, or `cat-file`: these are built-in commands, and Git ignores aliases that shadow them. A scratch probe and a permanent adapter regression confirmed the shell alias never ran. See [Git's alias documentation](https://git-scm.com/docs/git-config/2.54.0).
3939

40+
## Review round 2
41+
42+
Fixed inherited Git environment redirection. A regression with `GIT_DIR` pointing at a second repository initially returned the foreign repository's content. The adapter now drops inherited `GIT_*` variables, ignores global/system Git configuration, and disables lazy fetch and transport access. The same regression now reads only the requested repository.
43+
4044
## Remaining gates
4145

4246
This is a working foundation, not a completed application or a claim that all implementation tasks are done. T18's pure validation/edit core is present; its agent adapters, import UI, and persistence are pending. Ledger storage, rebase mappings, and the read-only review screen remain next. The already-fixed GitHub check belongs to the later GitHub/runner integration.

‎git/history.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ import type { FileDelta, FileVersion, History } from '../core/linking.ts';
33

44
/** Read-only Git adapter. Never follows working-tree symlinks or runs diff helpers. */
55
export function readHistory(repo: string, baseRef: string, headRef = 'HEAD'): History {
6-
const run = (...args: string[]) => execFileSync('git', ['--no-pager', '--no-replace-objects', '-c', 'core.hooksPath=/dev/null', ...args], {
6+
// Inherited Git variables can redirect repository, index, config, and object lookup.
7+
const environment = Object.fromEntries(Object.entries(process.env).filter(([key]) => !/^GIT_/i.test(key)));
8+
const run = (...args: string[]) => execFileSync('git', ['--no-pager', '--no-replace-objects', '-c', 'core.hooksPath=/dev/null', '-c', 'protocol.allow=never', ...args], {
79
cwd: repo, maxBuffer: 32 * 1024 * 1024, timeout: 30_000,
8-
env: { ...process.env, GIT_OPTIONAL_LOCKS: '0', GIT_TERMINAL_PROMPT: '0' },
10+
env: { ...environment, GIT_OPTIONAL_LOCKS: '0', GIT_TERMINAL_PROMPT: '0',
11+
GIT_NO_LAZY_FETCH: '1', GIT_CONFIG_NOSYSTEM: '1', GIT_CONFIG_GLOBAL: '/dev/null' },
912
stdio: ['ignore', 'pipe', 'pipe'],
1013
});
1114
const resolve = (ref: string) => run('rev-parse', '--verify', '--end-of-options', `${ref}^{commit}`).toString().trim();

‎test/history.test.ts‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,15 @@ it('Git built-ins cannot be overridden by repository shell aliases', () => {
161161
expect(f.segments().length).toBeGreaterThan(0);
162162
expect(existsSync(join(f.dir, 'alias-executed'))).toBe(false);
163163
});
164+
it('isolates repository selection from inherited Git environment variables', () => {
165+
const expected = fixture(); expected.write('a.txt', 'expected repo\n'); expected.commit('P1');
166+
const foreign = fixture(); foreign.write('a.txt', 'foreign repo\n'); foreign.commit();
167+
const previous = process.env.GIT_DIR;
168+
try {
169+
process.env.GIT_DIR = join(foreign.dir, '.git');
170+
const history = readHistory(expected.dir, 'HEAD~1');
171+
expect(history.final[0]!.after!.text).toBe('expected repo\n');
172+
} finally {
173+
if (previous === undefined) delete process.env.GIT_DIR; else process.env.GIT_DIR = previous;
174+
}
175+
});

0 commit comments

Comments
 (0)