feat: better patch application

This commit is contained in:
Eli Bosley
2025-02-04 10:24:08 -05:00
parent a12181a5e0
commit 445f3b50b1
5 changed files with 28 additions and 26 deletions

View File

@@ -68,7 +68,7 @@ export abstract class FileModification {
}
}
private async applyPatch(patchContents: string): Promise<void> {
private async applyPatch(patchContents: string): Promise<string> {
if (!patchContents.trim()) {
throw new Error('Patch contents are empty');
}
@@ -82,22 +82,28 @@ export abstract class FileModification {
throw new Error(`Failed to apply patch to ${this.filePath}`);
}
await writeFile(this.filePath, results);
return results;
}
async apply(): Promise<void> {
/**
* Apply the patch for the target file
* @returns
*/
async apply(): Promise<string> {
try {
const savedPatch = await this.loadPatchedFilePatch(this.filePath);
if (savedPatch) {
// Rollback the saved patch before applying the new patch
await this.rollback();
}
// First attempt to rollback an existing patch saved on disk (if the file has already been modified by us, unclean shutdown)
await this.rollback(true).catch((err) => {
this.logger.debug(
`Failed to rollback patch for ${this.id}: ${err}, may not have been applied yet`
);
});
// First attempt to apply the patch that was generated
const staticPatch = await this.getPregeneratedPatch();
if (staticPatch) {
try {
await this.applyPatch(staticPatch);
await this.savePatch(staticPatch);
return;
return staticPatch;
} catch (error) {
this.logger.error(
`Failed to apply static patch to ${this.filePath}, continuing with dynamic patch`
@@ -107,25 +113,35 @@ export abstract class FileModification {
const patchContents = await this.generatePatch();
await this.applyPatch(patchContents);
await this.savePatch(patchContents);
return patchContents;
} catch (err) {
this.logger.error(`Failed to apply patch to ${this.filePath}: ${err}`);
throw err;
}
}
async rollback(): Promise<void> {
let patch: string;
/**
* Rollback the patch for the target file
* @param useSavedPatchOnly - If true, only use the saved patch file if it exists, otherwise use the file or generate a new patch
*/
async rollback(useSavedPatchOnly: boolean = false): Promise<void> {
let patch: string | null = null;
// Try to load saved patch first
const savedPatch = await this.loadPatchedFilePatch(this.filePath);
if (savedPatch) {
this.logger.debug(`Using saved patch file for ${this.id}`);
patch = savedPatch;
} else {
} else if (!useSavedPatchOnly) {
this.logger.debug(`No saved patch found for ${this.id}, generating new patch`);
const patchContents = await this.generatePatch();
patch = patchContents;
}
if (!patch) {
throw new Error(`No patch found to rollback for ${this.id}`);
}
const currentContent = await readFile(this.filePath, 'utf8');
const parsedPatch = parsePatch(patch)[0];

View File

@@ -1,9 +0,0 @@
Index: text-patch-file.txt
===================================================================
--- text-patch-file.txt
+++ text-patch-file.txt
@@ -1,1 +1,1 @@
-original
\ No newline at end of file
+modified
\ No newline at end of file

View File

@@ -132,12 +132,6 @@ describe.sequential('FileModificationService', () => {
afterEach(async () => {
await service.rollbackAll();
// Clean up the fixture file
try {
await fs.unlink(FIXTURE_PATH);
} catch (error) {
console.error('Failed to clean up fixture file', error);
}
vi.clearAllMocks();
});
});