Download packages/core/src/services/gitService.ts from Jimmi42/openCLI: direct link, hf CLI and curl.
- Browser
- Download file 3.79 kB
-
https://huggingface.co/Jimmi42/openCLI/resolve/main/packages/core/src/services/gitService.ts
- Command line
-
hf download hf://Jimmi42/openCLI/packages/core/src/services/gitService.ts
-
curl -L -o gitService.ts https://huggingface.co/Jimmi42/openCLI/resolve/main/packages/core/src/services/gitService.ts
3.79 kB
| /** | |
| * @license | |
| * Copyright 2025 Google LLC | |
| * SPDX-License-Identifier: Apache-2.0 | |
| */ | |
| import * as fs from 'fs/promises'; | |
| import * as path from 'path'; | |
| import * as os from 'os'; | |
| import { isNodeError } from '../utils/errors.js'; | |
| import { isGitRepository } from '../utils/gitUtils.js'; | |
| import { exec } from 'node:child_process'; | |
| import { simpleGit, SimpleGit, CheckRepoActions } from 'simple-git'; | |
| import { getProjectHash, GEMINI_DIR } from '../utils/paths.js'; | |
| export class GitService { | |
| private projectRoot: string; | |
| constructor(projectRoot: string) { | |
| this.projectRoot = path.resolve(projectRoot); | |
| } | |
| private getHistoryDir(): string { | |
| const hash = getProjectHash(this.projectRoot); | |
| return path.join(os.homedir(), GEMINI_DIR, 'history', hash); | |
| } | |
| async initialize(): Promise<void> { | |
| if (!isGitRepository(this.projectRoot)) { | |
| throw new Error('GitService requires a Git repository'); | |
| } | |
| const gitAvailable = await this.verifyGitAvailability(); | |
| if (!gitAvailable) { | |
| throw new Error('GitService requires Git to be installed'); | |
| } | |
| this.setupShadowGitRepository(); | |
| } | |
| verifyGitAvailability(): Promise<boolean> { | |
| return new Promise((resolve) => { | |
| exec('git --version', (error) => { | |
| if (error) { | |
| resolve(false); | |
| } else { | |
| resolve(true); | |
| } | |
| }); | |
| }); | |
| } | |
| /** | |
| * Creates a hidden git repository in the project root. | |
| * The Git repository is used to support checkpointing. | |
| */ | |
| async setupShadowGitRepository() { | |
| const repoDir = this.getHistoryDir(); | |
| const gitConfigPath = path.join(repoDir, '.gitconfig'); | |
| await fs.mkdir(repoDir, { recursive: true }); | |
| // We don't want to inherit the user's name, email, or gpg signing | |
| // preferences for the shadow repository, so we create a dedicated gitconfig. | |
| const gitConfigContent = | |
| '[user]\n name = Gemini CLI\n email = gemini-cli@google.com\n[commit]\n gpgsign = false\n'; | |
| await fs.writeFile(gitConfigPath, gitConfigContent); | |
| const repo = simpleGit(repoDir); | |
| const isRepoDefined = await repo.checkIsRepo(CheckRepoActions.IS_REPO_ROOT); | |
| if (!isRepoDefined) { | |
| await repo.init(false, { | |
| '--initial-branch': 'main', | |
| }); | |
| await repo.commit('Initial commit', { '--allow-empty': null }); | |
| } | |
| const userGitIgnorePath = path.join(this.projectRoot, '.gitignore'); | |
| const shadowGitIgnorePath = path.join(repoDir, '.gitignore'); | |
| let userGitIgnoreContent = ''; | |
| try { | |
| userGitIgnoreContent = await fs.readFile(userGitIgnorePath, 'utf-8'); | |
| } catch (error) { | |
| if (isNodeError(error) && error.code !== 'ENOENT') { | |
| throw error; | |
| } | |
| } | |
| await fs.writeFile(shadowGitIgnorePath, userGitIgnoreContent); | |
| } | |
| private get shadowGitRepository(): SimpleGit { | |
| const repoDir = this.getHistoryDir(); | |
| return simpleGit(this.projectRoot).env({ | |
| GIT_DIR: path.join(repoDir, '.git'), | |
| GIT_WORK_TREE: this.projectRoot, | |
| // Prevent git from using the user's global git config. | |
| HOME: repoDir, | |
| XDG_CONFIG_HOME: repoDir, | |
| }); | |
| } | |
| async getCurrentCommitHash(): Promise<string> { | |
| const hash = await this.shadowGitRepository.raw('rev-parse', 'HEAD'); | |
| return hash.trim(); | |
| } | |
| async createFileSnapshot(message: string): Promise<string> { | |
| const repo = this.shadowGitRepository; | |
| await repo.add('.'); | |
| const commitResult = await repo.commit(message); | |
| return commitResult.commit; | |
| } | |
| async restoreProjectFromSnapshot(commitHash: string): Promise<void> { | |
| const repo = this.shadowGitRepository; | |
| await repo.raw(['restore', '--source', commitHash, '.']); | |
| // Removes any untracked files that were introduced post snapshot. | |
| await repo.clean('f', ['-d']); | |
| } | |
| } | |