fix(backups): preserve relative include/exclude pattern behavior
This commit is contained in:
parent
80cf8dd778
commit
0323ff0031
2 changed files with 33 additions and 29 deletions
|
|
@ -21,15 +21,17 @@ afterEach(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("executeBackup - include / exclude patterns", () => {
|
describe("executeBackup - include / exclude patterns", () => {
|
||||||
test("should correctly build include and exclude patterns by joining with volume path", async () => {
|
test("should correctly build include and exclude patterns", async () => {
|
||||||
// arrange
|
// arrange
|
||||||
const volume = await createTestVolume();
|
const volume = await createTestVolume();
|
||||||
const repository = await createTestRepository();
|
const repository = await createTestRepository();
|
||||||
|
const volumePath = getVolumePath(volume);
|
||||||
|
|
||||||
const schedule = await createTestBackupSchedule({
|
const schedule = await createTestBackupSchedule({
|
||||||
volumeId: volume.id,
|
volumeId: volume.id,
|
||||||
repositoryId: repository.id,
|
repositoryId: repository.id,
|
||||||
includePatterns: ["include1", "subdir/include2"],
|
includePatterns: ["*.zip", "/Photos", "!/Temp", "!*.log"],
|
||||||
excludePatterns: ["exclude1", "subdir/exclude2"],
|
excludePatterns: [".DS_Store", "/Config", "!/Important", "!*.tmp"],
|
||||||
excludeIfPresent: [".nobackup"],
|
excludeIfPresent: [".nobackup"],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -37,14 +39,12 @@ describe("executeBackup - include / exclude patterns", () => {
|
||||||
await backupsService.executeBackup(schedule.id);
|
await backupsService.executeBackup(schedule.id);
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
const volumePath = getVolumePath(volume);
|
|
||||||
|
|
||||||
expect(backupMock).toHaveBeenCalledWith(
|
expect(backupMock).toHaveBeenCalledWith(
|
||||||
expect.anything(),
|
expect.anything(),
|
||||||
volumePath,
|
volumePath,
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
include: [path.join(volumePath, "include1"), path.join(volumePath, "subdir/include2")],
|
include: ["*.zip", path.join(volumePath, "Photos"), `!${path.join(volumePath, "Temp")}`, "!*.log"],
|
||||||
exclude: [path.join(volumePath, "exclude1"), path.join(volumePath, "subdir/exclude2")],
|
exclude: [".DS_Store", path.join(volumePath, "Config"), `!${path.join(volumePath, "Important")}`, "!*.tmp"],
|
||||||
excludeIfPresent: [".nobackup"],
|
excludeIfPresent: [".nobackup"],
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
@ -88,11 +88,12 @@ describe("executeBackup - include / exclude patterns", () => {
|
||||||
|
|
||||||
const alreadyJoinedInclude = path.join(volumePath, "already/joined");
|
const alreadyJoinedInclude = path.join(volumePath, "already/joined");
|
||||||
const relativeInclude = "relative/include";
|
const relativeInclude = "relative/include";
|
||||||
|
const anchoredInclude = "/anchored/include";
|
||||||
|
|
||||||
const schedule = await createTestBackupSchedule({
|
const schedule = await createTestBackupSchedule({
|
||||||
volumeId: volume.id,
|
volumeId: volume.id,
|
||||||
repositoryId: repository.id,
|
repositoryId: repository.id,
|
||||||
includePatterns: [alreadyJoinedInclude, relativeInclude],
|
includePatterns: [alreadyJoinedInclude, relativeInclude, anchoredInclude],
|
||||||
});
|
});
|
||||||
|
|
||||||
// act
|
// act
|
||||||
|
|
@ -103,7 +104,7 @@ describe("executeBackup - include / exclude patterns", () => {
|
||||||
expect.anything(),
|
expect.anything(),
|
||||||
volumePath,
|
volumePath,
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
include: [alreadyJoinedInclude, path.join(volumePath, relativeInclude)],
|
include: [alreadyJoinedInclude, relativeInclude, path.join(volumePath, "anchored/include")],
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,27 @@ const calculateNextRun = (cronExpression: string): number => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const processPattern = (pattern: string, volumePath: string): string => {
|
||||||
|
let isNegated = false;
|
||||||
|
let p = pattern;
|
||||||
|
|
||||||
|
if (p.startsWith("!")) {
|
||||||
|
isNegated = true;
|
||||||
|
p = p.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p.startsWith(volumePath)) {
|
||||||
|
return pattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p.startsWith("/")) {
|
||||||
|
const processed = path.join(volumePath, p.slice(1));
|
||||||
|
return isNegated ? `!${processed}` : processed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pattern;
|
||||||
|
};
|
||||||
|
|
||||||
const listSchedules = async () => {
|
const listSchedules = async () => {
|
||||||
const schedules = await db.query.backupSchedulesTable.findMany({
|
const schedules = await db.query.backupSchedulesTable.findMany({
|
||||||
with: {
|
with: {
|
||||||
|
|
@ -259,16 +280,7 @@ const executeBackup = async (scheduleId: number, manual = false) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (schedule.excludePatterns && schedule.excludePatterns.length > 0) {
|
if (schedule.excludePatterns && schedule.excludePatterns.length > 0) {
|
||||||
const excludePatterns = [];
|
backupOptions.exclude = schedule.excludePatterns.map((p) => processPattern(p, volumePath));
|
||||||
for (const pattern of schedule.excludePatterns) {
|
|
||||||
if (!pattern.startsWith(volumePath)) {
|
|
||||||
excludePatterns.push(path.join(volumePath, pattern));
|
|
||||||
} else {
|
|
||||||
excludePatterns.push(pattern);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
backupOptions.exclude = excludePatterns;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (schedule.excludeIfPresent && schedule.excludeIfPresent.length > 0) {
|
if (schedule.excludeIfPresent && schedule.excludeIfPresent.length > 0) {
|
||||||
|
|
@ -276,16 +288,7 @@ const executeBackup = async (scheduleId: number, manual = false) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (schedule.includePatterns && schedule.includePatterns.length > 0) {
|
if (schedule.includePatterns && schedule.includePatterns.length > 0) {
|
||||||
const includePatterns = [];
|
backupOptions.include = schedule.includePatterns.map((p) => processPattern(p, volumePath));
|
||||||
for (const pattern of schedule.includePatterns) {
|
|
||||||
if (!pattern.startsWith(volumePath)) {
|
|
||||||
includePatterns.push(path.join(volumePath, pattern));
|
|
||||||
} else {
|
|
||||||
includePatterns.push(pattern);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
backupOptions.include = includePatterns;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const releaseBackupLock = await repoMutex.acquireShared(repository.id, `backup:${volume.name}`);
|
const releaseBackupLock = await repoMutex.acquireShared(repository.id, `backup:${volume.name}`);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue