blob: 6ff89aff940470c9664059cb4fe6fa13e5e2d8e5 [file] [log] [blame]
Philip Zeyliger272a90e2025-05-16 14:49:51 -07001// git-data-service.ts
2// Interface and implementation for fetching Git data
3
Autoformatter8c463622025-05-16 21:54:17 +00004import { DiffFile, GitLogEntry } from "../types";
Philip Zeyliger272a90e2025-05-16 14:49:51 -07005
6// Re-export DiffFile as GitDiffFile
7export type GitDiffFile = DiffFile;
8
9/**
10 * Interface for Git data services
11 */
12export interface GitDataService {
13 /**
14 * Fetches recent commit history
15 * @param initialCommit The initial commit hash to start from
16 * @returns List of commits
17 */
18 getCommitHistory(initialCommit?: string): Promise<GitLogEntry[]>;
19
20 /**
21 * Fetches diff between two commits
22 * @param from Starting commit hash
23 * @param to Ending commit hash (can be empty string for unstaged changes)
24 * @returns List of changed files
25 */
26 getDiff(from: string, to: string): Promise<GitDiffFile[]>;
27
28 /**
29 * Fetches diff for a single commit
30 * @param commit Commit hash
31 * @returns List of changed files
32 */
33 getCommitDiff(commit: string): Promise<GitDiffFile[]>;
34
35 /**
36 * Fetches file content from git using a file hash
37 * @param fileHash Git blob hash of the file to fetch
38 * @returns File content as string
39 */
40 getFileContent(fileHash: string): Promise<string>;
41
42 /**
43 * Gets file content from the current working directory
44 * @param filePath Path to the file within the repository
45 * @returns File content as string
46 */
47 getWorkingCopyContent(filePath: string): Promise<string>;
Autoformatter8c463622025-05-16 21:54:17 +000048
Philip Zeyliger272a90e2025-05-16 14:49:51 -070049 /**
50 * Saves file content to the working directory
51 * @param filePath Path to the file within the repository
52 * @param content New content to save to the file
53 */
54 saveFileContent(filePath: string, content: string): Promise<void>;
55
56 /**
57 * Gets the base commit reference (often "sketch-base")
58 * @returns Base commit reference
59 */
60 getBaseCommitRef(): Promise<string>;
61
62 /**
63 * Fetches unstaged changes (diff between a commit and working directory)
64 * @param from Starting commit hash (defaults to HEAD if not specified)
65 * @returns List of changed files
66 */
67 getUnstagedChanges(from?: string): Promise<GitDiffFile[]>;
68}
69
70/**
71 * Default implementation of GitDataService for the real application
72 */
73export class DefaultGitDataService implements GitDataService {
74 private baseCommitRef: string | null = null;
75
76 async getCommitHistory(initialCommit?: string): Promise<GitLogEntry[]> {
77 try {
Autoformatter8c463622025-05-16 21:54:17 +000078 const url = initialCommit
79 ? `git/recentlog?initialCommit=${encodeURIComponent(initialCommit)}`
80 : "git/recentlog";
Philip Zeyliger272a90e2025-05-16 14:49:51 -070081 const response = await fetch(url);
Autoformatter8c463622025-05-16 21:54:17 +000082
Philip Zeyliger272a90e2025-05-16 14:49:51 -070083 if (!response.ok) {
Autoformatter8c463622025-05-16 21:54:17 +000084 throw new Error(
85 `Failed to fetch commit history: ${response.statusText}`,
86 );
Philip Zeyliger272a90e2025-05-16 14:49:51 -070087 }
Autoformatter8c463622025-05-16 21:54:17 +000088
Philip Zeyliger272a90e2025-05-16 14:49:51 -070089 return await response.json();
90 } catch (error) {
Autoformatter8c463622025-05-16 21:54:17 +000091 console.error("Error fetching commit history:", error);
Philip Zeyliger272a90e2025-05-16 14:49:51 -070092 throw error;
93 }
94 }
95
96 async getDiff(from: string, to: string): Promise<GitDiffFile[]> {
97 try {
98 const url = `git/rawdiff?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`;
99 const response = await fetch(url);
Autoformatter8c463622025-05-16 21:54:17 +0000100
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700101 if (!response.ok) {
102 throw new Error(`Failed to fetch diff: ${response.statusText}`);
103 }
Autoformatter8c463622025-05-16 21:54:17 +0000104
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700105 return await response.json();
106 } catch (error) {
Autoformatter8c463622025-05-16 21:54:17 +0000107 console.error("Error fetching diff:", error);
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700108 throw error;
109 }
110 }
111
112 async getCommitDiff(commit: string): Promise<GitDiffFile[]> {
113 try {
114 const url = `git/rawdiff?commit=${encodeURIComponent(commit)}`;
115 const response = await fetch(url);
Autoformatter8c463622025-05-16 21:54:17 +0000116
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700117 if (!response.ok) {
118 throw new Error(`Failed to fetch commit diff: ${response.statusText}`);
119 }
Autoformatter8c463622025-05-16 21:54:17 +0000120
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700121 return await response.json();
122 } catch (error) {
Autoformatter8c463622025-05-16 21:54:17 +0000123 console.error("Error fetching commit diff:", error);
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700124 throw error;
125 }
126 }
127
128 async getFileContent(fileHash: string): Promise<string> {
129 try {
130 // If the hash is marked as a working copy (special value '000000' or empty)
Autoformatter8c463622025-05-16 21:54:17 +0000131 if (
132 fileHash === "0000000000000000000000000000000000000000" ||
133 !fileHash
134 ) {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700135 // This shouldn't happen, but if it does, return empty string
136 // Working copy content should be fetched through getWorkingCopyContent
Autoformatter8c463622025-05-16 21:54:17 +0000137 console.warn(
138 "Invalid file hash for getFileContent, returning empty string",
139 );
140 return "";
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700141 }
Autoformatter8c463622025-05-16 21:54:17 +0000142
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700143 const url = `git/show?hash=${encodeURIComponent(fileHash)}`;
144 const response = await fetch(url);
Autoformatter8c463622025-05-16 21:54:17 +0000145
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700146 if (!response.ok) {
147 throw new Error(`Failed to fetch file content: ${response.statusText}`);
148 }
Autoformatter8c463622025-05-16 21:54:17 +0000149
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700150 const data = await response.json();
Autoformatter8c463622025-05-16 21:54:17 +0000151 return data.output || "";
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700152 } catch (error) {
Autoformatter8c463622025-05-16 21:54:17 +0000153 console.error("Error fetching file content:", error);
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700154 throw error;
155 }
156 }
Autoformatter8c463622025-05-16 21:54:17 +0000157
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700158 async getWorkingCopyContent(filePath: string): Promise<string> {
159 try {
160 const url = `git/cat?path=${encodeURIComponent(filePath)}`;
161 const response = await fetch(url);
Autoformatter8c463622025-05-16 21:54:17 +0000162
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700163 if (!response.ok) {
Josh Bleecher Snyder77bac8c2025-05-28 11:04:09 -0700164 const errorText = await response.text();
Autoformatter8c463622025-05-16 21:54:17 +0000165 throw new Error(
Josh Bleecher Snyder77bac8c2025-05-28 11:04:09 -0700166 `Failed to fetch working copy content (${response.status}): ${errorText}`,
Autoformatter8c463622025-05-16 21:54:17 +0000167 );
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700168 }
Josh Bleecher Snyderfadffe32025-07-10 00:08:38 +0000169 if (response.status === 204) {
170 return ""; // file doesn't exist
171 }
Autoformatter8c463622025-05-16 21:54:17 +0000172
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700173 const data = await response.json();
Autoformatter8c463622025-05-16 21:54:17 +0000174 return data.output || "";
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700175 } catch (error) {
Autoformatter8c463622025-05-16 21:54:17 +0000176 console.error("Error fetching working copy content:", error);
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700177 throw error;
178 }
179 }
Autoformatter8c463622025-05-16 21:54:17 +0000180
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700181 async saveFileContent(filePath: string, content: string): Promise<void> {
182 try {
183 const url = `git/save`;
184 const response = await fetch(url, {
Autoformatter8c463622025-05-16 21:54:17 +0000185 method: "POST",
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700186 headers: {
Autoformatter8c463622025-05-16 21:54:17 +0000187 "Content-Type": "application/json",
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700188 },
189 body: JSON.stringify({
190 path: filePath,
Autoformatter8c463622025-05-16 21:54:17 +0000191 content: content,
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700192 }),
193 });
Autoformatter8c463622025-05-16 21:54:17 +0000194
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700195 if (!response.ok) {
196 const errorText = await response.text();
Autoformatter8c463622025-05-16 21:54:17 +0000197 throw new Error(
198 `Failed to save file content: ${response.statusText} - ${errorText}`,
199 );
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700200 }
Autoformatter8c463622025-05-16 21:54:17 +0000201
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700202 // Don't need to return the response, just ensure it was successful
203 } catch (error) {
Autoformatter8c463622025-05-16 21:54:17 +0000204 console.error("Error saving file content:", error);
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700205 throw error;
206 }
207 }
Autoformatter8c463622025-05-16 21:54:17 +0000208
209 async getUnstagedChanges(from: string = "HEAD"): Promise<GitDiffFile[]> {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700210 try {
211 // To get unstaged changes, we diff the specified commit (or HEAD) with an empty 'to'
Autoformatter8c463622025-05-16 21:54:17 +0000212 return await this.getDiff(from, "");
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700213 } catch (error) {
Autoformatter8c463622025-05-16 21:54:17 +0000214 console.error("Error fetching unstaged changes:", error);
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700215 throw error;
216 }
217 }
218
219 async getBaseCommitRef(): Promise<string> {
220 // Cache the base commit reference to avoid multiple requests
221 if (this.baseCommitRef) {
222 return this.baseCommitRef;
223 }
224
225 try {
226 // This could be replaced with a specific endpoint call if available
227 // For now, we'll use a fixed value or try to get it from the server
Autoformatter8c463622025-05-16 21:54:17 +0000228 this.baseCommitRef = "sketch-base";
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700229 return this.baseCommitRef;
230 } catch (error) {
Autoformatter8c463622025-05-16 21:54:17 +0000231 console.error("Error fetching base commit reference:", error);
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700232 throw error;
233 }
234 }
Autoformatter8c463622025-05-16 21:54:17 +0000235}