← back to Letsbegin
lib/git.ts
123 lines
/**
* Git integration for auto-committing on successful steps
*/
import { exec } from 'child_process'
import { promisify } from 'util'
const execAsync = promisify(exec)
interface CommitResult {
success: boolean
message: string
sha?: string
}
export async function autoCommit(
projectPath: string,
message: string,
files?: string[]
): Promise<CommitResult> {
try {
// Check if it's a git repo
try {
await execAsync(`cd "${projectPath}" && git rev-parse --is-inside-work-tree`)
} catch {
return { success: false, message: 'Not a git repository' }
}
// Stage files
if (files && files.length > 0) {
for (const file of files) {
await execAsync(`cd "${projectPath}" && git add "${file}"`)
}
} else {
// Stage all changes
await execAsync(`cd "${projectPath}" && git add -A`)
}
// Check if there are changes to commit
const { stdout: status } = await execAsync(`cd "${projectPath}" && git status --porcelain`)
if (!status.trim()) {
return { success: true, message: 'No changes to commit' }
}
// Create commit
const commitMessage = `${message}\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>`
const { stdout } = await execAsync(
`cd "${projectPath}" && git commit -m "${commitMessage.replace(/"/g, '\\"')}"`,
{ maxBuffer: 10 * 1024 * 1024 }
)
// Get commit SHA
const { stdout: sha } = await execAsync(`cd "${projectPath}" && git rev-parse --short HEAD`)
return {
success: true,
message: 'Committed successfully',
sha: sha.trim()
}
} catch (error) {
console.error('Git commit error:', error)
return {
success: false,
message: error instanceof Error ? error.message : 'Unknown error'
}
}
}
export async function createBranch(projectPath: string, branchName: string): Promise<CommitResult> {
try {
// Check if branch exists
try {
await execAsync(`cd "${projectPath}" && git show-ref --verify --quiet refs/heads/${branchName}`)
// Branch exists, checkout
await execAsync(`cd "${projectPath}" && git checkout ${branchName}`)
return { success: true, message: `Switched to existing branch: ${branchName}` }
} catch {
// Branch doesn't exist, create it
await execAsync(`cd "${projectPath}" && git checkout -b ${branchName}`)
return { success: true, message: `Created and switched to branch: ${branchName}` }
}
} catch (error) {
console.error('Git branch error:', error)
return {
success: false,
message: error instanceof Error ? error.message : 'Unknown error'
}
}
}
export async function getGitStatus(projectPath: string): Promise<{
isRepo: boolean
branch: string | null
hasChanges: boolean
changedFiles: string[]
}> {
try {
// Check if it's a git repo
await execAsync(`cd "${projectPath}" && git rev-parse --is-inside-work-tree`)
// Get current branch
const { stdout: branch } = await execAsync(`cd "${projectPath}" && git branch --show-current`)
// Get changed files
const { stdout: status } = await execAsync(`cd "${projectPath}" && git status --porcelain`)
const changedFiles = status.trim().split('\n').filter(Boolean).map(line => line.slice(3))
return {
isRepo: true,
branch: branch.trim() || 'detached',
hasChanges: changedFiles.length > 0,
changedFiles
}
} catch {
return {
isRepo: false,
branch: null,
hasChanges: false,
changedFiles: []
}
}
}