Skip to content

Commit e046393

Browse files
committed
Add utility to restore back up files
1 parent 220211e commit e046393

File tree

3 files changed

+68
-7
lines changed

3 files changed

+68
-7
lines changed

lib/.merge-backups/tmp-test/clean.txt

Lines changed: 0 additions & 1 deletion
This file was deleted.

lib/src/utils.test.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { describe, it, expect, beforeAll, afterAll } from "vitest";
22
import fs from "node:fs/promises";
33
import path from "node:path";
4-
import { hasConflict, createSkipDirectoryMatcher, listMatchingFiles, backupFile } from "./utils"; // adjust import
4+
import { hasConflict, listMatchingFiles, backupFile, restoreBackups } from "./utils"; // adjust import
55
import { basicMatcher } from "./matcher";
66

77
const matcher = basicMatcher;
@@ -26,6 +26,7 @@ beforeAll(async () => {
2626

2727
afterAll(async () => {
2828
await fs.rm(TMP, { recursive: true, force: true });
29+
await fs.rm(".merge-backups", { recursive: true, force: true });
2930
});
3031

3132
describe("hasConflict", () => {
@@ -83,3 +84,33 @@ describe("backupFile", () => {
8384
expect(backup).toMatch(/\.merge-backups/);
8485
});
8586
});
87+
88+
describe("restoreBackups", () => {
89+
it("restores files from backup directory", async () => {
90+
process.chdir(TMP);
91+
const backupDir = "test-backup";
92+
const testFile = "restore-test.txt";
93+
94+
await fs.mkdir(backupDir, { recursive: true });
95+
await fs.writeFile(path.join(backupDir, "restore-test.txt"), "restored");
96+
97+
await restoreBackups(backupDir);
98+
99+
const content = await fs.readFile(testFile, "utf8");
100+
expect(content).toBe("restored");
101+
});
102+
103+
it("restores nested directory structure", async () => {
104+
const backupDir = "nested-backup";
105+
const nestedDir = path.join(backupDir, "sub", "deep");
106+
107+
await fs.mkdir(nestedDir, { recursive: true });
108+
await fs.writeFile(path.join(nestedDir, "nested.txt"), "deep file");
109+
110+
await restoreBackups(backupDir);
111+
112+
const content = await fs.readFile(path.join("sub", "deep", "nested.txt"), "utf8");
113+
expect(content).toBe("deep file");
114+
process.chdir("..");
115+
});
116+
});

lib/src/utils.ts

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export const hasConflict = (content: string): boolean => {
1919

2020
export type CollectFilesOptions = Pick<
2121
NormalizedConfig,
22-
"include" | "exclude" | "matcher" | "includeNonConflicted" | "debug"
22+
"include" | "exclude" | "matcher" | "includeNonConflicted" | "debug" | "backupDir"
2323
> & {
2424
/** Root directory to start traversal (defaults to `process.cwd()`). */
2525
root?: string;
@@ -34,9 +34,15 @@ export type CollectFilesOptions = Pick<
3434
* @param options - Collection options, including `fileFilter` and traversal root.
3535
* @returns A promise that resolves with an array of `{ filePath, content }`.
3636
*/
37-
export const listMatchingFiles = async (options: CollectFilesOptions): Promise<FileEntry[]> => {
38-
const { root = process.cwd(), include, exclude, matcher, includeNonConflicted, debug } = options;
39-
37+
export const listMatchingFiles = async ({
38+
root = process.cwd(),
39+
include,
40+
exclude,
41+
matcher,
42+
includeNonConflicted,
43+
debug,
44+
backupDir,
45+
}: CollectFilesOptions): Promise<FileEntry[]> => {
4046
for (const p of [...include, ...exclude]) {
4147
if (p.startsWith("!")) throw new Error(`Negation not allowed in include/exclude: ${p}`);
4248
if (p.includes("\\")) console.warn(`Use '/' as path separator: ${p}`);
@@ -47,7 +53,11 @@ export const listMatchingFiles = async (options: CollectFilesOptions): Promise<F
4753
return matcher.isMatch(posixPath, include) && !matcher.isMatch(posixPath, exclude);
4854
};
4955

50-
const skipDirMatcher = createSkipDirectoryMatcher(include, exclude, matcher);
56+
const skipDirMatcher = createSkipDirectoryMatcher(
57+
include,
58+
backupDir ? [...exclude, backupDir] : exclude,
59+
matcher,
60+
);
5161

5262
const fileEntries: FileEntry[] = [];
5363

@@ -147,3 +157,24 @@ export const backupFile = async (filePath: string, backupDir = ".merge-backups")
147157

148158
return backupPath;
149159
};
160+
161+
export const restoreBackups = async (backupDir = ".merge-backups") => {
162+
const walk = async (dir: string, relativeDir = "") => {
163+
const entries = await fs.readdir(dir, { withFileTypes: true });
164+
165+
for (const entry of entries) {
166+
const srcPath = path.join(dir, entry.name);
167+
const relativePath = path.join(relativeDir, entry.name);
168+
const destPath = path.join(process.cwd(), relativePath);
169+
170+
if (entry.isDirectory()) {
171+
await walk(srcPath, relativePath);
172+
} else {
173+
await fs.mkdir(path.dirname(destPath), { recursive: true });
174+
await fs.copyFile(srcPath, destPath);
175+
}
176+
}
177+
};
178+
179+
await walk(backupDir);
180+
};

0 commit comments

Comments
 (0)