Move the shell history our of the project .gemini to the home dir (#1195)

This commit is contained in:
Louis Jimenez 2025-06-19 23:53:24 -04:00 committed by GitHub
parent 7a419282c8
commit ea63a8401e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 53 additions and 26 deletions

View File

@ -7,16 +7,30 @@
import { renderHook, act, waitFor } from '@testing-library/react'; import { renderHook, act, waitFor } from '@testing-library/react';
import { useShellHistory } from './useShellHistory.js'; import { useShellHistory } from './useShellHistory.js';
import * as fs from 'fs/promises'; import * as fs from 'fs/promises';
import path from 'path'; import * as path from 'path';
import * as os from 'os';
import * as crypto from 'crypto';
vi.mock('fs/promises'); vi.mock('fs/promises');
vi.mock('os');
vi.mock('crypto');
const MOCKED_PROJECT_ROOT = '/test/project'; const MOCKED_PROJECT_ROOT = '/test/project';
const MOCKED_HISTORY_DIR = path.join(MOCKED_PROJECT_ROOT, '.gemini'); const MOCKED_HOME_DIR = '/test/home';
const MOCKED_PROJECT_HASH = 'mocked_hash';
const MOCKED_HISTORY_DIR = path.join(
MOCKED_HOME_DIR,
'.gemini',
'tmp',
MOCKED_PROJECT_HASH,
);
const MOCKED_HISTORY_FILE = path.join(MOCKED_HISTORY_DIR, 'shell_history'); const MOCKED_HISTORY_FILE = path.join(MOCKED_HISTORY_DIR, 'shell_history');
describe('useShellHistory', () => { describe('useShellHistory', () => {
const mockedFs = vi.mocked(fs); const mockedFs = vi.mocked(fs);
const mockedOs = vi.mocked(os);
const mockedCrypto = vi.mocked(crypto);
beforeEach(() => { beforeEach(() => {
vi.resetAllMocks(); vi.resetAllMocks();
@ -24,6 +38,13 @@ describe('useShellHistory', () => {
mockedFs.readFile.mockResolvedValue(''); mockedFs.readFile.mockResolvedValue('');
mockedFs.writeFile.mockResolvedValue(undefined); mockedFs.writeFile.mockResolvedValue(undefined);
mockedFs.mkdir.mockResolvedValue(undefined); mockedFs.mkdir.mockResolvedValue(undefined);
mockedOs.homedir.mockReturnValue(MOCKED_HOME_DIR);
const hashMock = {
update: vi.fn().mockReturnThis(),
digest: vi.fn().mockReturnValue(MOCKED_PROJECT_HASH),
};
mockedCrypto.createHash.mockReturnValue(hashMock as never);
}); });
it('should initialize and read the history file from the correct path', async () => { it('should initialize and read the history file from the correct path', async () => {

View File

@ -7,14 +7,13 @@
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import * as fs from 'fs/promises'; import * as fs from 'fs/promises';
import * as path from 'path'; import * as path from 'path';
import { isNodeError } from '@gemini-cli/core'; import { isNodeError, getProjectTempDir } from '@gemini-cli/core';
const HISTORY_DIR = '.gemini';
const HISTORY_FILE = 'shell_history'; const HISTORY_FILE = 'shell_history';
const MAX_HISTORY_LENGTH = 100; const MAX_HISTORY_LENGTH = 100;
async function getHistoryFilePath(projectRoot: string): Promise<string> { async function getHistoryFilePath(projectRoot: string): Promise<string> {
const historyDir = path.join(projectRoot, HISTORY_DIR); const historyDir = getProjectTempDir(projectRoot);
return path.join(historyDir, HISTORY_FILE); return path.join(historyDir, HISTORY_FILE);
} }

View File

@ -5,13 +5,10 @@
*/ */
import path from 'node:path'; import path from 'node:path';
import os from 'node:os';
import crypto from 'node:crypto';
import { promises as fs } from 'node:fs'; import { promises as fs } from 'node:fs';
import { Content } from '@google/genai'; import { Content } from '@google/genai';
import { getProjectTempDir } from '../utils/paths.js';
const GEMINI_DIR = '.gemini';
const TMP_DIR_NAME = 'tmp';
const LOG_FILE_NAME = 'logs.json'; const LOG_FILE_NAME = 'logs.json';
const CHECKPOINT_FILE_NAME = 'checkpoint.json'; const CHECKPOINT_FILE_NAME = 'checkpoint.json';
@ -99,17 +96,7 @@ export class Logger {
return; return;
} }
const projectHash = crypto this.geminiDir = getProjectTempDir(process.cwd());
.createHash('sha256')
.update(process.cwd())
.digest('hex');
this.geminiDir = path.join(
os.homedir(),
GEMINI_DIR,
TMP_DIR_NAME,
projectHash,
);
this.logFilePath = path.join(this.geminiDir, LOG_FILE_NAME); this.logFilePath = path.join(this.geminiDir, LOG_FILE_NAME);
this.checkpointFilePath = path.join(this.geminiDir, CHECKPOINT_FILE_NAME); this.checkpointFilePath = path.join(this.geminiDir, CHECKPOINT_FILE_NAME);

View File

@ -7,11 +7,11 @@
import * as fs from 'fs/promises'; import * as fs from 'fs/promises';
import * as path from 'path'; import * as path from 'path';
import * as os from 'os'; import * as os from 'os';
import * as crypto from 'crypto';
import { isNodeError } from '../utils/errors.js'; import { isNodeError } from '../utils/errors.js';
import { isGitRepository } from '../utils/gitUtils.js'; import { isGitRepository } from '../utils/gitUtils.js';
import { exec } from 'node:child_process'; import { exec } from 'node:child_process';
import { simpleGit, SimpleGit, CheckRepoActions } from 'simple-git'; import { simpleGit, SimpleGit, CheckRepoActions } from 'simple-git';
import { getProjectHash, GEMINI_DIR } from '../utils/paths.js';
export class GitService { export class GitService {
private projectRoot: string; private projectRoot: string;
@ -21,11 +21,8 @@ export class GitService {
} }
private getHistoryDir(): string { private getHistoryDir(): string {
const hash = crypto const hash = getProjectHash(this.projectRoot);
.createHash('sha256') return path.join(os.homedir(), GEMINI_DIR, 'history', hash);
.update(this.projectRoot)
.digest('hex');
return path.join(os.homedir(), '.gemini', 'history', hash);
} }
async initialize(): Promise<void> { async initialize(): Promise<void> {

View File

@ -6,6 +6,10 @@
import path from 'node:path'; import path from 'node:path';
import os from 'os'; import os from 'os';
import * as crypto from 'crypto';
export const GEMINI_DIR = '.gemini';
const TMP_DIR_NAME = 'tmp';
/** /**
* Replaces the home directory with a tilde. * Replaces the home directory with a tilde.
@ -134,3 +138,22 @@ export function escapePath(filePath: string): string {
export function unescapePath(filePath: string): string { export function unescapePath(filePath: string): string {
return filePath.replace(/\\ /g, ' '); return filePath.replace(/\\ /g, ' ');
} }
/**
* Generates a unique hash for a project based on its root path.
* @param projectRoot The absolute path to the project's root directory.
* @returns A SHA256 hash of the project root path.
*/
export function getProjectHash(projectRoot: string): string {
return crypto.createHash('sha256').update(projectRoot).digest('hex');
}
/**
* Generates a unique temporary directory path for a project.
* @param projectRoot The absolute path to the project's root directory.
* @returns The path to the project's temporary directory.
*/
export function getProjectTempDir(projectRoot: string): string {
const hash = getProjectHash(projectRoot);
return path.join(os.homedir(), GEMINI_DIR, TMP_DIR_NAME, hash);
}