-
Notifications
You must be signed in to change notification settings - Fork 254
Expand file tree
/
Copy pathgetDiffApi.ts
More file actions
100 lines (84 loc) · 2.99 KB
/
getDiffApi.ts
File metadata and controls
100 lines (84 loc) · 2.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { sew } from '@/actions';
import { invalidGitRef, notFound, ServiceError, unexpectedError } from '@/lib/serviceError';
import { withOptionalAuthV2 } from '@/withAuthV2';
import { getRepoPath } from '@sourcebot/shared';
import parseDiff from 'parse-diff';
import { simpleGit } from 'simple-git';
import { isGitRefValid } from './utils';
export interface HunkRange {
start: number;
lines: number;
}
export interface DiffHunk {
oldRange: HunkRange;
newRange: HunkRange;
heading?: string;
body: string;
}
export interface FileDiff {
oldPath: string;
newPath: string;
hunks: DiffHunk[];
}
export interface GetDiffResult {
files: FileDiff[];
}
type GetDiffRequest = {
repo: string;
base: string;
head: string;
}
export const getDiff = async ({
repo: repoName,
base,
head,
}: GetDiffRequest): Promise<GetDiffResult | ServiceError> => sew(() =>
withOptionalAuthV2(async ({ org, prisma }) => {
if (!isGitRefValid(base)) {
return invalidGitRef(base);
}
if (!isGitRefValid(head)) {
return invalidGitRef(head);
}
const repo = await prisma.repo.findFirst({
where: {
name: repoName,
orgId: org.id,
},
});
if (!repo) {
return notFound(`Repository "${repoName}" not found.`);
}
const { path: repoPath } = getRepoPath(repo);
const git = simpleGit().cwd(repoPath);
try {
const rawDiff = await git.raw(['diff', base, head]);
const files = parseDiff(rawDiff);
const nodes: FileDiff[] = files.map((file) => ({
oldPath: file.from ?? '/dev/null',
newPath: file.to ?? '/dev/null',
hunks: file.chunks.map((chunk) => {
// chunk.content is the full @@ header line, e.g.:
// "@@ -7,6 +7,8 @@ some heading text"
// The heading is the optional text after the second @@.
const headingMatch = chunk.content.match(/^@@ .+ @@ (.+)$/);
const heading = headingMatch ? headingMatch[1].trim() : undefined;
return {
oldRange: { start: chunk.oldStart, lines: chunk.oldLines },
newRange: { start: chunk.newStart, lines: chunk.newLines },
heading,
body: chunk.changes.map((change) => change.content).join('\n'),
};
}),
}));
return {
files: nodes,
};
} catch (error: unknown) {
const message = error instanceof Error ? error.message : String(error);
if (message.includes('unknown revision') || message.includes('bad revision')) {
return invalidGitRef(`${base}..${head}`);
}
return unexpectedError(`Failed to compute diff for ${repoName}: ${message}`);
}
}));