Skip to content

Commit cb208b9

Browse files
committed
refactor: reorganize git operations into GitService and GitRefsService classes
1 parent 3121aab commit cb208b9

File tree

7 files changed

+187
-148
lines changed

7 files changed

+187
-148
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"scripts": {
2626
"dev": "tsup --watch",
2727
"build": "tsup",
28-
"start": "node dist/index.js",
28+
"start": "node dist/index.cjs",
2929
"format": "biome format",
3030
"format:fix": "biome format --write",
3131
"pkg:build:macos": "pkg . --no-bytecode -t node18-macos-arm64 -o build/git-intent-macos",

src/commands/cancel.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { type IntentionalCommit, storage } from '@/utils/storage.js';
1+
import type { IntentionalCommit } from '@/types/intent.js';
2+
import { storage } from '@/utils/storage.js';
23
import chalk from 'chalk';
34
import { Command } from 'commander';
45
import prompts from 'prompts';

src/commands/commit.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createCommit } from '@/utils/git.js';
1+
import { git } from '@/utils/git.js';
22
import { storage } from '@/utils/storage.js';
33
import chalk from 'chalk';
44
import { Command } from 'commander';
@@ -18,7 +18,7 @@ const commit = new Command()
1818

1919
const message = options.message ? `${currentCommit.message}\n\n${options.message}` : currentCommit.message;
2020

21-
await createCommit(message);
21+
await git.createCommit(message);
2222
await storage.deleteCommit(currentCommit.id);
2323

2424
console.log(chalk.green('✓ Intention completed and committed'));

src/commands/start.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getCurrentBranch } from '@/utils/git.js';
1+
import { git } from '@/utils/git.js';
22
import { storage } from '@/utils/storage.js';
33
import chalk from 'chalk';
44
import { Command } from 'commander';
@@ -49,7 +49,7 @@ const start = new Command()
4949
return;
5050
}
5151

52-
const currentBranch = await getCurrentBranch();
52+
const currentBranch = await git.getCurrentBranch();
5353
targetCommit.status = 'in_progress';
5454
targetCommit.metadata.startedAt = new Date().toISOString();
5555
targetCommit.metadata.branch = currentBranch;

src/utils/git-refs.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { git } from './git.js';
2+
3+
class GitRefsService {
4+
private static instance: GitRefsService;
5+
6+
private constructor() {}
7+
8+
static getInstance(): GitRefsService {
9+
if (!GitRefsService.instance) {
10+
GitRefsService.instance = new GitRefsService();
11+
}
12+
return GitRefsService.instance;
13+
}
14+
15+
async createTree(treeContent: string, cwd?: string): Promise<string> {
16+
if (!treeContent || treeContent.trim() === '') {
17+
throw new Error('Invalid tree content: tree content cannot be empty');
18+
}
19+
return git.execGit(['mktree'], { input: treeContent, cwd });
20+
}
21+
22+
async createCommitTree(treeHash: string, message: string, cwd?: string): Promise<string> {
23+
if (!treeHash || treeHash.trim() === '') {
24+
throw new Error('Invalid tree hash: tree hash cannot be empty');
25+
}
26+
27+
try {
28+
const result = git.execGit(['commit-tree', treeHash, '-m', message], { cwd });
29+
30+
if (!result || result.trim() === '') {
31+
throw new Error(`Failed to create commit tree from hash: ${treeHash}`);
32+
}
33+
34+
return result;
35+
} catch (error) {
36+
console.error('Error creating commit tree:', error);
37+
throw error;
38+
}
39+
}
40+
41+
async updateRef(refName: string, commitHash: string, cwd?: string): Promise<void> {
42+
if (!commitHash || commitHash.trim() === '') {
43+
throw new Error(`Invalid commit hash: commit hash cannot be empty for ref ${refName}`);
44+
}
45+
46+
await git.raw(['update-ref', refName, commitHash], cwd);
47+
}
48+
49+
async deleteRef(refName: string, cwd?: string): Promise<void> {
50+
await git.raw(['update-ref', '-d', refName], cwd);
51+
}
52+
53+
async checkRefExists(refName: string, cwd?: string): Promise<boolean> {
54+
try {
55+
await git.raw(['show-ref', '--verify', refName], cwd);
56+
return true;
57+
} catch {
58+
return false;
59+
}
60+
}
61+
62+
async showRef(refPath: string, cwd?: string): Promise<string> {
63+
try {
64+
return await git.raw(['show', refPath], cwd);
65+
} catch (error) {
66+
throw new Error(`Failed to show ref: ${refPath}`);
67+
}
68+
}
69+
}
70+
71+
export const gitRefs = GitRefsService.getInstance();

src/utils/git.ts

Lines changed: 68 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,133 +1,98 @@
11
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';
223

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;
248

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);
3012
}
3113

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;
3519
}
3620

37-
return findGitRoot(parentDir);
38-
}
21+
getGit(cwd?: string): SimpleGit {
22+
if (!cwd) {
23+
return this.defaultGit;
24+
}
3925

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));
5129
}
30+
31+
return this.gitInstances.get(cacheKey) as SimpleGit;
5232
}
53-
}
5433

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;
6036

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+
});
6643

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+
}
7247

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+
}
7850

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();
8254

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;
8656
}
87-
return execGit(['mktree'], { input: treeContent, cwd });
88-
}
8957

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();
9362
}
9463

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+
}
10169

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();
10673
}
107-
}
10874

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;
11278
}
11379

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+
}
11785

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+
}
12289

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);
13093
}
13194
}
13295

133-
export default createGit();
96+
export const git = GitService.getInstance();
97+
98+
export default git.getGit();

0 commit comments

Comments
 (0)