|
1 | 1 | import { spawnSync } from 'node:child_process';
|
2 |
| -import path from 'node:path'; |
3 |
| -import fs from 'fs-extra'; |
4 |
| -import { simpleGit } from 'simple-git'; |
5 |
| - |
6 |
| -function execGit(args: string[], options: { input?: string; cwd?: string } = {}): string { |
7 |
| - const { input, cwd } = options; |
8 |
| - |
9 |
| - const result = spawnSync('git', args, { |
10 |
| - input: input ? Buffer.from(input) : undefined, |
11 |
| - cwd, |
12 |
| - encoding: 'utf-8', |
13 |
| - stdio: ['pipe', 'pipe', 'pipe'], |
14 |
| - }); |
15 |
| - |
16 |
| - if (result.status !== 0) { |
17 |
| - throw new Error(`Git command failed: git ${args.join(' ')}\n${result.stderr}`); |
18 |
| - } |
19 |
| - |
20 |
| - return result.stdout ? result.stdout.trim() : ''; |
21 |
| -} |
| 2 | +import { type SimpleGit, simpleGit } from 'simple-git'; |
22 | 3 |
|
23 |
| -export const createGit = (cwd?: string) => simpleGit(cwd); |
| 4 | +class GitService { |
| 5 | + private static instance: GitService; |
| 6 | + private gitInstances: Map<string, SimpleGit> = new Map(); |
| 7 | + private defaultGit: SimpleGit; |
24 | 8 |
|
25 |
| -export async function findGitRoot(startDir: string = process.cwd()): Promise<string> { |
26 |
| - const dir = path.resolve(startDir); |
27 |
| - const gitDir = path.join(dir, '.git'); |
28 |
| - if (fs.existsSync(gitDir) && fs.statSync(gitDir).isDirectory()) { |
29 |
| - return dir; |
| 9 | + private constructor() { |
| 10 | + this.defaultGit = simpleGit(); |
| 11 | + this.gitInstances.set('default', this.defaultGit); |
30 | 12 | }
|
31 | 13 |
|
32 |
| - const parentDir = path.dirname(dir); |
33 |
| - if (parentDir === dir) { |
34 |
| - throw new Error('Not a git repository (or any of the parent directories)'); |
| 14 | + static getInstance(): GitService { |
| 15 | + if (!GitService.instance) { |
| 16 | + GitService.instance = new GitService(); |
| 17 | + } |
| 18 | + return GitService.instance; |
35 | 19 | }
|
36 | 20 |
|
37 |
| - return findGitRoot(parentDir); |
38 |
| -} |
| 21 | + getGit(cwd?: string): SimpleGit { |
| 22 | + if (!cwd) { |
| 23 | + return this.defaultGit; |
| 24 | + } |
39 | 25 |
|
40 |
| -export async function checkIsRepo(cwd?: string): Promise<string> { |
41 |
| - try { |
42 |
| - const git = createGit(cwd); |
43 |
| - await git.checkIsRepo(); |
44 |
| - |
45 |
| - return await findGitRoot(cwd); |
46 |
| - } catch { |
47 |
| - try { |
48 |
| - return await findGitRoot(cwd); |
49 |
| - } catch (error) { |
50 |
| - throw new Error('Not a git repository (or any of the parent directories)'); |
| 26 | + const cacheKey = cwd; |
| 27 | + if (!this.gitInstances.has(cacheKey)) { |
| 28 | + this.gitInstances.set(cacheKey, simpleGit(cwd)); |
51 | 29 | }
|
| 30 | + |
| 31 | + return this.gitInstances.get(cacheKey) as SimpleGit; |
52 | 32 | }
|
53 |
| -} |
54 | 33 |
|
55 |
| -export async function getCurrentBranch(cwd?: string): Promise<string> { |
56 |
| - const git = createGit(cwd); |
57 |
| - const branch = await git.revparse(['--abbrev-ref', 'HEAD']); |
58 |
| - return branch.trim(); |
59 |
| -} |
| 34 | + execGit(args: string[], options: { input?: string; cwd?: string } = {}): string { |
| 35 | + const { input, cwd } = options; |
60 | 36 |
|
61 |
| -export async function hasStagedFiles(cwd?: string): Promise<boolean> { |
62 |
| - const git = createGit(cwd); |
63 |
| - const status = await git.status(); |
64 |
| - return status.staged.length > 0; |
65 |
| -} |
| 37 | + const result = spawnSync('git', args, { |
| 38 | + input: input ? Buffer.from(input) : undefined, |
| 39 | + cwd, |
| 40 | + encoding: 'utf-8', |
| 41 | + stdio: ['pipe', 'pipe', 'pipe'], |
| 42 | + }); |
66 | 43 |
|
67 |
| -export async function createCommit(message: string, cwd?: string): Promise<string> { |
68 |
| - const git = createGit(cwd); |
69 |
| - const result = await git.commit(message); |
70 |
| - return result.commit; |
71 |
| -} |
| 44 | + if (result.status !== 0) { |
| 45 | + throw new Error(`Git command failed: git ${args.join(' ')}\n${result.stderr}`); |
| 46 | + } |
72 | 47 |
|
73 |
| -export async function getStatus(cwd?: string): Promise<string> { |
74 |
| - const git = createGit(cwd); |
75 |
| - const status = await git.status(); |
76 |
| - return JSON.stringify(status, null, 2); |
77 |
| -} |
| 48 | + return result.stdout ? result.stdout.trim() : ''; |
| 49 | + } |
78 | 50 |
|
79 |
| -export async function hashObject(content: string, cwd?: string): Promise<string> { |
80 |
| - return execGit(['hash-object', '-w', '--stdin'], { input: content, cwd }); |
81 |
| -} |
| 51 | + async checkIsRepo(cwd?: string): Promise<boolean> { |
| 52 | + const git = this.getGit(cwd); |
| 53 | + const isRepo = await git.checkIsRepo(); |
82 | 54 |
|
83 |
| -export async function createTree(treeContent: string, cwd?: string): Promise<string> { |
84 |
| - if (!treeContent || treeContent.trim() === '') { |
85 |
| - throw new Error('Invalid tree content: tree content cannot be empty'); |
| 55 | + return isRepo; |
86 | 56 | }
|
87 |
| - return execGit(['mktree'], { input: treeContent, cwd }); |
88 |
| -} |
89 | 57 |
|
90 |
| -export async function createCommitTree(treeHash: string, message: string, cwd?: string): Promise<string> { |
91 |
| - if (!treeHash || treeHash.trim() === '') { |
92 |
| - throw new Error('Invalid tree hash: tree hash cannot be empty'); |
| 58 | + async getRepoRoot(cwd?: string): Promise<string> { |
| 59 | + const git = this.getGit(cwd); |
| 60 | + const root = await git.revparse(['--show-toplevel']); |
| 61 | + return root.trim(); |
93 | 62 | }
|
94 | 63 |
|
95 |
| - try { |
96 |
| - const result = execGit(['commit-tree', treeHash, '-m', message], { cwd }); |
97 |
| - |
98 |
| - if (!result || result.trim() === '') { |
99 |
| - throw new Error(`Failed to create commit tree from hash: ${treeHash}`); |
100 |
| - } |
| 64 | + async getCurrentBranch(cwd?: string): Promise<string> { |
| 65 | + const git = this.getGit(cwd); |
| 66 | + const branch = await git.revparse(['--abbrev-ref', 'HEAD']); |
| 67 | + return branch.trim(); |
| 68 | + } |
101 | 69 |
|
102 |
| - return result; |
103 |
| - } catch (error) { |
104 |
| - console.error('Error creating commit tree:', error); |
105 |
| - throw error; |
| 70 | + async getStatus(cwd?: string): Promise<any> { |
| 71 | + const git = this.getGit(cwd); |
| 72 | + return await git.status(); |
106 | 73 | }
|
107 |
| -} |
108 | 74 |
|
109 |
| -export async function updateRef(refName: string, commitHash: string, cwd?: string): Promise<void> { |
110 |
| - if (!commitHash || commitHash.trim() === '') { |
111 |
| - throw new Error(`Invalid commit hash: commit hash cannot be empty for ref ${refName}`); |
| 75 | + async hasStagedFiles(cwd?: string): Promise<boolean> { |
| 76 | + const status = await this.getStatus(cwd); |
| 77 | + return status.staged.length > 0; |
112 | 78 | }
|
113 | 79 |
|
114 |
| - const git = createGit(cwd); |
115 |
| - await git.raw(['update-ref', refName, commitHash]); |
116 |
| -} |
| 80 | + async createCommit(message: string, cwd?: string): Promise<string> { |
| 81 | + const git = this.getGit(cwd); |
| 82 | + const result = await git.commit(message); |
| 83 | + return result.commit; |
| 84 | + } |
117 | 85 |
|
118 |
| -export async function deleteRef(refName: string, cwd?: string): Promise<void> { |
119 |
| - const git = createGit(cwd); |
120 |
| - await git.raw(['update-ref', '-d', refName]); |
121 |
| -} |
| 86 | + async hashObject(content: string, cwd?: string): Promise<string> { |
| 87 | + return this.execGit(['hash-object', '-w', '--stdin'], { input: content, cwd }); |
| 88 | + } |
122 | 89 |
|
123 |
| -export async function checkRefExists(refName: string, cwd?: string): Promise<boolean> { |
124 |
| - const git = createGit(cwd); |
125 |
| - try { |
126 |
| - await git.raw(['show-ref', '--verify', refName]); |
127 |
| - return true; |
128 |
| - } catch { |
129 |
| - return false; |
| 90 | + async raw(commands: string[], cwd?: string): Promise<string> { |
| 91 | + const git = this.getGit(cwd); |
| 92 | + return await git.raw(commands); |
130 | 93 | }
|
131 | 94 | }
|
132 | 95 |
|
133 |
| -export default createGit(); |
| 96 | +export const git = GitService.getInstance(); |
| 97 | + |
| 98 | +export default git.getGit(); |
0 commit comments