|
1 | | -import { execSync } from "child_process" |
2 | | -import fs from "fs" |
3 | | -import os from "os" |
4 | | -import path from "path" |
5 | | -import { type NextRequest, NextResponse } from "next/server" |
| 1 | +import { execSync } from "child_process"; |
| 2 | +import fs from "fs"; |
| 3 | +import os from "os"; |
| 4 | +import path from "path"; |
| 5 | +import { type NextRequest, NextResponse } from "next/server"; |
6 | 6 |
|
7 | 7 | export async function POST(request: NextRequest) { |
8 | 8 | try { |
9 | | - const { repoPath, commitHash } = await request.json() |
| 9 | + const { repoPath, commitHash } = await request.json(); |
10 | 10 |
|
11 | 11 | if (!repoPath || !commitHash) { |
12 | | - return NextResponse.json({ error: "Repository path and commit hash are required" }, { status: 400 }) |
| 12 | + return NextResponse.json( |
| 13 | + { error: "Repository path and commit hash are required" }, |
| 14 | + { status: 400 }, |
| 15 | + ); |
13 | 16 | } |
14 | 17 |
|
15 | | - // Helper to run git commands that may exit with non-zero when differences exist (git diff returns 1) |
16 | | - const runGit = (cmd: string): string => { |
17 | | - try { |
18 | | - return execSync(cmd, { encoding: "utf-8", env: { ...process.env, GIT_PAGER: "" } }) |
19 | | - } catch (e: any) { |
20 | | - const out: Buffer | string | undefined = e?.stdout |
21 | | - if (out) { |
22 | | - return Buffer.isBuffer(out) ? out.toString("utf-8") : String(out) |
| 18 | + // Helper to run git commands in a specific repository directory |
| 19 | + const runGitInRepo = (repoPath: string, cmd: string): string => { |
| 20 | + try { |
| 21 | + return execSync(cmd, { |
| 22 | + encoding: "utf-8", |
| 23 | + env: { ...process.env, GIT_PAGER: "" }, |
| 24 | + cwd: repoPath, |
| 25 | + }); |
| 26 | + } catch (e: any) { |
| 27 | + const out: Buffer | string | undefined = e?.stdout; |
| 28 | + if (out) { |
| 29 | + return Buffer.isBuffer(out) ? out.toString("utf-8") : String(out); |
| 30 | + } |
| 31 | + throw e; |
23 | 32 | } |
24 | | - throw e |
25 | | - } |
26 | | - } |
| 33 | + }; |
27 | 34 |
|
28 | | - // If the special working directory node is requested, return the working tree diff vs HEAD |
29 | | - let diffText = "" |
30 | | - if (commitHash === "WORKING_DIR") { |
31 | | - // Base diff: tracked changes vs HEAD (staged + unstaged) |
32 | | - const baseCmd = `cd "${repoPath}" && git diff HEAD --no-color --no-ext-diff` |
33 | | - diffText = runGit(baseCmd) |
| 35 | + // If the special working directory node is requested, return the working tree diff vs HEAD |
| 36 | + let diffText = ""; |
| 37 | + if (commitHash === "WORKING_DIR") { |
| 38 | + // Base diff: tracked changes vs HEAD (staged + unstaged) |
| 39 | + diffText = runGitInRepo( |
| 40 | + repoPath, |
| 41 | + `git diff HEAD --no-color --no-ext-diff`, |
| 42 | + ); |
34 | 43 |
|
35 | | - // Append diffs for untracked files using no-index against an empty temp file |
36 | | - const listCmd = `cd "${repoPath}" && git ls-files --others --exclude-standard -z` |
37 | | - let untrackedRaw = "" |
38 | | - try { |
39 | | - // Capture as Buffer to safely handle NULs |
40 | | - const buf: Buffer = execSync(listCmd, { env: { ...process.env, GIT_PAGER: "" } }) as unknown as Buffer |
41 | | - untrackedRaw = buf.toString("utf-8") |
42 | | - } catch { |
43 | | - untrackedRaw = "" |
44 | | - } |
45 | | - const files = untrackedRaw |
46 | | - .split("\u0000") |
47 | | - .map((s) => s.trim()) |
48 | | - .filter((s) => s.length > 0) |
49 | | - |
50 | | - if (files.length > 0) { |
51 | | - // Create a temporary empty file |
52 | | - const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vcv-")) |
53 | | - const emptyPath = path.join(tmpDir, "empty") |
54 | | - fs.writeFileSync(emptyPath, "") |
| 44 | + // Append diffs for untracked files using no-index against an empty temp file |
| 45 | + const listCmd = `git ls-files --others --exclude-standard -z`; |
| 46 | + let untrackedRaw = ""; |
55 | 47 | try { |
56 | | - for (const f of files) { |
57 | | - const perFileCmd = `cd "${repoPath}" && git diff --no-index --no-color --no-ext-diff -- "${emptyPath}" "${f}"` |
58 | | - const perFile = runGit(perFileCmd) |
59 | | - if (perFile && perFile.trim().length > 0) { |
60 | | - if (diffText && !diffText.endsWith("\n")) diffText += "\n" |
61 | | - diffText += perFile |
| 48 | + // Capture as Buffer to safely handle NULs |
| 49 | + const buf: Buffer = execSync(listCmd, { |
| 50 | + env: { ...process.env, GIT_PAGER: "" }, |
| 51 | + cwd: repoPath, |
| 52 | + }) as unknown as Buffer; |
| 53 | + untrackedRaw = buf.toString("utf-8"); |
| 54 | + } catch { |
| 55 | + untrackedRaw = ""; |
| 56 | + } |
| 57 | + const files = untrackedRaw |
| 58 | + .split("\u0000") |
| 59 | + .map((s) => s.trim()) |
| 60 | + .filter((s) => s.length > 0); |
| 61 | + |
| 62 | + if (files.length > 0) { |
| 63 | + // Create a temporary empty file |
| 64 | + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vcv-")); |
| 65 | + const emptyPath = path.join(tmpDir, "empty"); |
| 66 | + fs.writeFileSync(emptyPath, ""); |
| 67 | + try { |
| 68 | + for (const f of files) { |
| 69 | + const perFileCmd = `git diff --no-index --no-color --no-ext-diff -- "${emptyPath}" "${f}"`; |
| 70 | + const perFile = runGitInRepo(repoPath, perFileCmd); |
| 71 | + if (perFile && perFile.trim().length > 0) { |
| 72 | + if (diffText && !diffText.endsWith("\n")) diffText += "\n"; |
| 73 | + diffText += perFile; |
| 74 | + } |
62 | 75 | } |
| 76 | + } finally { |
| 77 | + // Cleanup temp file and dir |
| 78 | + try { |
| 79 | + fs.unlinkSync(emptyPath); |
| 80 | + } catch {} |
| 81 | + try { |
| 82 | + fs.rmdirSync(tmpDir); |
| 83 | + } catch {} |
63 | 84 | } |
64 | | - } finally { |
65 | | - // Cleanup temp file and dir |
66 | | - try { fs.unlinkSync(emptyPath) } catch {} |
67 | | - try { fs.rmdirSync(tmpDir) } catch {} |
68 | 85 | } |
| 86 | + } else { |
| 87 | + // Use git show to get the patch for a single commit. Disable color and pager for clean parsing. |
| 88 | + diffText = runGitInRepo( |
| 89 | + repoPath, |
| 90 | + `git show ${commitHash} --no-color --no-ext-diff`, |
| 91 | + ); |
69 | 92 | } |
70 | | - } else { |
71 | | - // Use git show to get the patch for a single commit. Disable color and pager for clean parsing. |
72 | | - const cmd = `cd "${repoPath}" && git show ${commitHash} --no-color --no-ext-diff` |
73 | | - diffText = runGit(cmd) |
74 | | - } |
75 | 93 |
|
76 | | - return NextResponse.json({ diff: diffText }) |
| 94 | + return NextResponse.json({ diff: diffText }); |
77 | 95 | } catch (error: any) { |
78 | | - return NextResponse.json({ error: error.message || "Failed to get diff" }, { status: 500 }) |
| 96 | + return NextResponse.json( |
| 97 | + { error: error.message || "Failed to get diff" }, |
| 98 | + { status: 500 }, |
| 99 | + ); |
79 | 100 | } |
80 | 101 | } |
0 commit comments