feat: add git diff utility module with staged/unstaged support

This commit is contained in:
2026-04-15 13:43:47 -04:00
parent aa85ed29e8
commit 5d336af6c6
+87
View File
@@ -0,0 +1,87 @@
import { exec } from "child_process"
import { promisify } from "util"
const execAsync = promisify(exec)
async function isGitInstalled(): Promise<boolean> {
try {
await execAsync("git --version")
return true
} catch {
return false
}
}
async function isGitRepo(cwd: string): Promise<boolean> {
try {
await execAsync("git rev-parse --git-dir", { cwd })
return true
} catch {
return false
}
}
async function hasCommits(cwd: string): Promise<boolean> {
try {
await execAsync("git rev-parse HEAD", { cwd })
return true
} catch {
return false
}
}
export async function getGitDiff(cwd: string, stagedOnly = false): Promise<string> {
if (!(await isGitInstalled())) {
throw new Error("Git is not installed")
}
if (!(await isGitRepo(cwd))) {
throw new Error("Not a git repository")
}
let diff = ""
if (await hasCommits(cwd)) {
const { stdout: staged } = await execAsync("git --no-pager diff --staged --diff-filter=d", { cwd })
diff = staged.trim()
}
if (!stagedOnly && !diff) {
const { stdout: unstaged } = await execAsync("git --no-pager diff HEAD --diff-filter=d", { cwd })
diff = unstaged.trim()
}
// New repo with no commits yet — show everything that's staged/tracked
if (!diff) {
const { stdout: newRepo } = await execAsync("git --no-pager diff --cached --diff-filter=d", { cwd })
diff = newRepo.trim()
}
// Include untracked (new) files when not limiting to staged only
if (!stagedOnly) {
const { stdout: untrackedList } = await execAsync("git ls-files --others --exclude-standard", { cwd })
const untrackedFiles = untrackedList.trim().split("\n").filter(Boolean)
for (const file of untrackedFiles) {
// git diff --no-index exits with code 1 when files differ — that's normal
const result = await execAsync(
`git --no-pager diff --no-index -- /dev/null ${JSON.stringify(file)}`,
{ cwd },
).catch((e: any) => ({ stdout: e.stdout as string | undefined }))
if (result.stdout) diff += (diff ? "\n" : "") + result.stdout
}
diff = diff.trim()
}
if (!diff) {
throw new Error("No changes found to generate a commit message from")
}
return diff
}
export async function getGitDiffStagedFirst(cwd: string): Promise<string> {
try {
return await getGitDiff(cwd, true)
} catch {
return await getGitDiff(cwd, false)
}
}