forked from google-gemini/gemini-cli
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcheckpointing.test.ts
More file actions
155 lines (128 loc) · 4.8 KB
/
Copy pathcheckpointing.test.ts
File metadata and controls
155 lines (128 loc) · 4.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import * as os from 'node:os';
import { GitService, Storage } from '@google/gemini-cli-core';
describe('Checkpointing Integration', () => {
let tmpDir: string;
let projectRoot: string;
let fakeHome: string;
let originalEnv: NodeJS.ProcessEnv;
beforeEach(async () => {
tmpDir = await fs.mkdtemp(
path.join(os.tmpdir(), 'gemini-checkpoint-test-'),
);
projectRoot = path.join(tmpDir, 'project');
fakeHome = path.join(tmpDir, 'home');
await fs.mkdir(projectRoot, { recursive: true });
await fs.mkdir(fakeHome, { recursive: true });
// Save original env
originalEnv = { ...process.env };
// Simulate environment with NO global gitconfig
process.env['HOME'] = fakeHome;
delete process.env['GIT_CONFIG_GLOBAL'];
delete process.env['GIT_CONFIG_SYSTEM'];
});
afterEach(async () => {
// Restore env
process.env = originalEnv;
// Cleanup
try {
await fs.rm(tmpDir, { recursive: true, force: true });
} catch (e) {
console.error('Failed to cleanup temp dir', e);
}
});
it('should successfully create and restore snapshots without global git config', async () => {
const storage = new Storage(projectRoot);
const gitService = new GitService(projectRoot, storage);
// 1. Initialize
await gitService.initialize();
// Verify system config empty file creation
// We need to access getHistoryDir logic or replicate it.
// Since we don't have access to private getHistoryDir, we can infer it or just trust the functional test.
// 2. Create initial state
await fs.writeFile(path.join(projectRoot, 'file1.txt'), 'version 1');
await fs.writeFile(path.join(projectRoot, 'file2.txt'), 'permanent file');
// 3. Create Snapshot
const snapshotHash = await gitService.createFileSnapshot('Checkpoint 1');
expect(snapshotHash).toBeDefined();
// 4. Modify files
await fs.writeFile(
path.join(projectRoot, 'file1.txt'),
'version 2 (BAD CHANGE)',
);
await fs.writeFile(
path.join(projectRoot, 'file3.txt'),
'new file (SHOULD BE GONE)',
);
await fs.rm(path.join(projectRoot, 'file2.txt'));
// 5. Restore
await gitService.restoreProjectFromSnapshot(snapshotHash);
// 6. Verify state
const file1Content = await fs.readFile(
path.join(projectRoot, 'file1.txt'),
'utf-8',
);
expect(file1Content).toBe('version 1');
const file2Exists = await fs
.stat(path.join(projectRoot, 'file2.txt'))
.then(() => true)
.catch(() => false);
expect(file2Exists).toBe(true);
const file2Content = await fs.readFile(
path.join(projectRoot, 'file2.txt'),
'utf-8',
);
expect(file2Content).toBe('permanent file');
const file3Exists = await fs
.stat(path.join(projectRoot, 'file3.txt'))
.then(() => true)
.catch(() => false);
expect(file3Exists).toBe(false);
});
it('should ignore user global git config and use isolated identity', async () => {
// 1. Create a fake global gitconfig with a specific user
const globalConfigPath = path.join(fakeHome, '.gitconfig');
const globalConfigContent = `[user]
name = Global User
email = global@example.com
`;
await fs.writeFile(globalConfigPath, globalConfigContent);
// Point HOME to fakeHome so git picks up this global config (if we didn't isolate it)
process.env['HOME'] = fakeHome;
// Ensure GIT_CONFIG_GLOBAL is NOT set for the process initially,
// so it would default to HOME/.gitconfig if GitService didn't override it.
delete process.env['GIT_CONFIG_GLOBAL'];
const storage = new Storage(projectRoot);
const gitService = new GitService(projectRoot, storage);
await gitService.initialize();
// 2. Create a file and snapshot
await fs.writeFile(path.join(projectRoot, 'test.txt'), 'content');
await gitService.createFileSnapshot('Snapshot with global config present');
// 3. Verify the commit author in the shadow repo
const historyDir = storage.getHistoryDir();
const { execFileSync } = await import('node:child_process');
const logOutput = execFileSync(
'git',
['log', '-1', '--pretty=format:%an <%ae>'],
{
cwd: historyDir,
env: {
...process.env,
GIT_DIR: path.join(historyDir, '.git'),
GIT_CONFIG_GLOBAL: path.join(historyDir, '.gitconfig'),
GIT_CONFIG_SYSTEM: path.join(historyDir, '.gitconfig_system_empty'),
},
encoding: 'utf-8',
},
);
expect(logOutput).toBe('Gemini CLI <gemini-cli@google.com>');
expect(logOutput).not.toContain('Global User');
});
});