blob: c22b8518c385881f7b36ca8fc923e6930a2d7093 [file] [log] [blame]
Philip Zeyliger272a90e2025-05-16 14:49:51 -07001// git-data-service.ts
2// Interface and implementation for fetching Git data
3
4import { DiffFile, GitLogEntry } from '../types';
5
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>;
48
49 /**
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 {
78 const url = initialCommit
79 ? `git/recentlog?initialCommit=${encodeURIComponent(initialCommit)}`
80 : 'git/recentlog';
81 const response = await fetch(url);
82
83 if (!response.ok) {
84 throw new Error(`Failed to fetch commit history: ${response.statusText}`);
85 }
86
87 return await response.json();
88 } catch (error) {
89 console.error('Error fetching commit history:', error);
90 throw error;
91 }
92 }
93
94 async getDiff(from: string, to: string): Promise<GitDiffFile[]> {
95 try {
96 const url = `git/rawdiff?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`;
97 const response = await fetch(url);
98
99 if (!response.ok) {
100 throw new Error(`Failed to fetch diff: ${response.statusText}`);
101 }
102
103 return await response.json();
104 } catch (error) {
105 console.error('Error fetching diff:', error);
106 throw error;
107 }
108 }
109
110 async getCommitDiff(commit: string): Promise<GitDiffFile[]> {
111 try {
112 const url = `git/rawdiff?commit=${encodeURIComponent(commit)}`;
113 const response = await fetch(url);
114
115 if (!response.ok) {
116 throw new Error(`Failed to fetch commit diff: ${response.statusText}`);
117 }
118
119 return await response.json();
120 } catch (error) {
121 console.error('Error fetching commit diff:', error);
122 throw error;
123 }
124 }
125
126 async getFileContent(fileHash: string): Promise<string> {
127 try {
128 // If the hash is marked as a working copy (special value '000000' or empty)
129 if (fileHash === '0000000000000000000000000000000000000000' || !fileHash) {
130 // This shouldn't happen, but if it does, return empty string
131 // Working copy content should be fetched through getWorkingCopyContent
132 console.warn('Invalid file hash for getFileContent, returning empty string');
133 return '';
134 }
135
136 const url = `git/show?hash=${encodeURIComponent(fileHash)}`;
137 const response = await fetch(url);
138
139 if (!response.ok) {
140 throw new Error(`Failed to fetch file content: ${response.statusText}`);
141 }
142
143 const data = await response.json();
144 return data.output || '';
145 } catch (error) {
146 console.error('Error fetching file content:', error);
147 throw error;
148 }
149 }
150
151 async getWorkingCopyContent(filePath: string): Promise<string> {
152 try {
153 const url = `git/cat?path=${encodeURIComponent(filePath)}`;
154 const response = await fetch(url);
155
156 if (!response.ok) {
157 throw new Error(`Failed to fetch working copy content: ${response.statusText}`);
158 }
159
160 const data = await response.json();
161 return data.output || '';
162 } catch (error) {
163 console.error('Error fetching working copy content:', error);
164 throw error;
165 }
166 }
167
168 async saveFileContent(filePath: string, content: string): Promise<void> {
169 try {
170 const url = `git/save`;
171 const response = await fetch(url, {
172 method: 'POST',
173 headers: {
174 'Content-Type': 'application/json',
175 },
176 body: JSON.stringify({
177 path: filePath,
178 content: content
179 }),
180 });
181
182 if (!response.ok) {
183 const errorText = await response.text();
184 throw new Error(`Failed to save file content: ${response.statusText} - ${errorText}`);
185 }
186
187 // Don't need to return the response, just ensure it was successful
188 } catch (error) {
189 console.error('Error saving file content:', error);
190 throw error;
191 }
192 }
193
194 async getUnstagedChanges(from: string = 'HEAD'): Promise<GitDiffFile[]> {
195 try {
196 // To get unstaged changes, we diff the specified commit (or HEAD) with an empty 'to'
197 return await this.getDiff(from, '');
198 } catch (error) {
199 console.error('Error fetching unstaged changes:', error);
200 throw error;
201 }
202 }
203
204 async getBaseCommitRef(): Promise<string> {
205 // Cache the base commit reference to avoid multiple requests
206 if (this.baseCommitRef) {
207 return this.baseCommitRef;
208 }
209
210 try {
211 // This could be replaced with a specific endpoint call if available
212 // For now, we'll use a fixed value or try to get it from the server
213 this.baseCommitRef = 'sketch-base';
214 return this.baseCommitRef;
215 } catch (error) {
216 console.error('Error fetching base commit reference:', error);
217 throw error;
218 }
219 }
220}