95 lines
2.9 KiB
TypeScript
95 lines
2.9 KiB
TypeScript
// Git helpers for branch validation and commit operations
|
|
import { execSync } from "child_process"
|
|
|
|
export function getCurrentBranch(cwd = process.cwd()): string {
|
|
return execSync("git rev-parse --abbrev-ref HEAD", { cwd, encoding: "utf-8" }).trim()
|
|
}
|
|
|
|
export function isClean(cwd = process.cwd()): boolean {
|
|
const status = execSync("git status --porcelain", { cwd, encoding: "utf-8" }).trim()
|
|
return status === ""
|
|
}
|
|
|
|
export function commitFile(path: string, message: string, cwd = process.cwd()): void {
|
|
execSync(`git add ${path}`, { cwd })
|
|
execSync(`git commit -m ${JSON.stringify(message)}`, { cwd })
|
|
}
|
|
|
|
export function commitFileWithBody(path: string, header: string, body: string, cwd = process.cwd()): void {
|
|
execSync(`git add ${path}`, { cwd })
|
|
const msg = `${header}\n\n${body}`
|
|
execSync(`git commit -m ${JSON.stringify(msg)}`, { cwd })
|
|
}
|
|
|
|
export function branchExists(branch: string, cwd = process.cwd()): boolean {
|
|
try {
|
|
execSync(`git rev-parse --verify ${branch}`, { cwd, stdio: "pipe" })
|
|
return true
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Parse the issue branch name into type and ID
|
|
export function parseIssueBranch(branch: string): { type: string; id: number } | null {
|
|
const match = branch.match(/^(feature|bugfix|hotfix)\/(\d+)$/)
|
|
if (!match) return null
|
|
return { type: match[1], id: Number(match[2]) }
|
|
}
|
|
|
|
export interface GitCommit {
|
|
hash: string
|
|
subject: string
|
|
body: string
|
|
}
|
|
|
|
// Get commits on current branch since it diverged from a base branch
|
|
export function getCommitsSinceDiverge(base = "develop", cwd = process.cwd()): GitCommit[] {
|
|
return parseGitLog(`${base}..HEAD`, cwd)
|
|
}
|
|
|
|
// Get commits from a ref spec (e.g. HEAD~3..HEAD)
|
|
export function getCommitsFromRef(refSpec: string, cwd = process.cwd()): GitCommit[] {
|
|
return parseGitLog(refSpec, cwd)
|
|
}
|
|
|
|
// Get all commits on a branch
|
|
export function getAllCommits(branch: string, cwd = process.cwd()): GitCommit[] {
|
|
return parseGitLog(branch, cwd)
|
|
}
|
|
|
|
function parseGitLog(range: string, cwd: string): GitCommit[] {
|
|
const SEP = "---COMMIT-SEP---"
|
|
const format = `%H%n%s%n%b%n${SEP}`
|
|
let output: string
|
|
try {
|
|
output = execSync(
|
|
`git log ${range} --format=${JSON.stringify(format)}`,
|
|
{ cwd, encoding: "utf-8" }
|
|
)
|
|
} catch {
|
|
return []
|
|
}
|
|
|
|
const commits: GitCommit[] = []
|
|
const entries = output.split(SEP).filter(e => e.trim())
|
|
|
|
for (const entry of entries) {
|
|
const lines = entry.trim().split("\n")
|
|
if (lines.length < 2) continue
|
|
commits.push({
|
|
hash: lines[0],
|
|
subject: lines[1],
|
|
body: lines.slice(2).join("\n").trim(),
|
|
})
|
|
}
|
|
|
|
return commits
|
|
}
|
|
|
|
// Check if a commit subject is a todo transition
|
|
export function parseTodoTransition(subject: string): { issueId: number; status: string } | null {
|
|
const match = subject.match(/^todo\((\d+)\):\s*(\S+)/)
|
|
if (!match) return null
|
|
return { issueId: Number(match[1]), status: match[2] }
|
|
}
|