blob: 69d108df9e3aa96ae3301c8b5a47cd60be80cead [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) {
Autoformatter8c463622025-05-16 21:54:17 +0000164 throw new Error(
165 `Failed to fetch working copy content: ${response.statusText}`,
166 );
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700167 }
Autoformatter8c463622025-05-16 21:54:17 +0000168
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700169 const data = await response.json();
Autoformatter8c463622025-05-16 21:54:17 +0000170 return data.output || "";
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700171 } catch (error) {
Autoformatter8c463622025-05-16 21:54:17 +0000172 console.error("Error fetching working copy content:", error);
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700173 throw error;
174 }
175 }
Autoformatter8c463622025-05-16 21:54:17 +0000176
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700177 async saveFileContent(filePath: string, content: string): Promise<void> {
178 try {
179 const url = `git/save`;
180 const response = await fetch(url, {
Autoformatter8c463622025-05-16 21:54:17 +0000181 method: "POST",
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700182 headers: {
Autoformatter8c463622025-05-16 21:54:17 +0000183 "Content-Type": "application/json",
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700184 },
185 body: JSON.stringify({
186 path: filePath,
Autoformatter8c463622025-05-16 21:54:17 +0000187 content: content,
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700188 }),
189 });
Autoformatter8c463622025-05-16 21:54:17 +0000190
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700191 if (!response.ok) {
192 const errorText = await response.text();
Autoformatter8c463622025-05-16 21:54:17 +0000193 throw new Error(
194 `Failed to save file content: ${response.statusText} - ${errorText}`,
195 );
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700196 }
Autoformatter8c463622025-05-16 21:54:17 +0000197
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700198 // Don't need to return the response, just ensure it was successful
199 } catch (error) {
Autoformatter8c463622025-05-16 21:54:17 +0000200 console.error("Error saving file content:", error);
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700201 throw error;
202 }
203 }
Autoformatter8c463622025-05-16 21:54:17 +0000204
205 async getUnstagedChanges(from: string = "HEAD"): Promise<GitDiffFile[]> {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700206 try {
207 // To get unstaged changes, we diff the specified commit (or HEAD) with an empty 'to'
Autoformatter8c463622025-05-16 21:54:17 +0000208 return await this.getDiff(from, "");
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700209 } catch (error) {
Autoformatter8c463622025-05-16 21:54:17 +0000210 console.error("Error fetching unstaged changes:", error);
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700211 throw error;
212 }
213 }
214
215 async getBaseCommitRef(): Promise<string> {
216 // Cache the base commit reference to avoid multiple requests
217 if (this.baseCommitRef) {
218 return this.baseCommitRef;
219 }
220
221 try {
222 // This could be replaced with a specific endpoint call if available
223 // For now, we'll use a fixed value or try to get it from the server
Autoformatter8c463622025-05-16 21:54:17 +0000224 this.baseCommitRef = "sketch-base";
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700225 return this.baseCommitRef;
226 } catch (error) {
Autoformatter8c463622025-05-16 21:54:17 +0000227 console.error("Error fetching base commit reference:", error);
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700228 throw error;
229 }
230 }
Autoformatter8c463622025-05-16 21:54:17 +0000231}