mirror of
https://github.com/Awuqing/BackupX.git
synced 2026-09-06 07:56:41 +08:00
feat(backup): 新增差异备份(differential)模式 (#88)
文件备份新增差异模式:仅打包自上次全量以来的变更并记录删除,恢复自动按全量+差异链还原。含基线解析、链式恢复、保留链保护与本机文件任务校验;清单/比对/删除/往返/保留保护单测全覆盖。
This commit is contained in:
@@ -64,6 +64,8 @@ func (s *Service) Cleanup(ctx context.Context, task *model.BackupTask, provider
|
||||
} else {
|
||||
candidates = selectRecordsToDelete(records, task.RetentionDays, task.MaxBackups, s.now())
|
||||
}
|
||||
// 差异链保护:保留仍被存活差异依赖的全量,避免删除基线后差异无法恢复。
|
||||
candidates = protectDifferentialBases(records, candidates)
|
||||
result := &CleanupResult{}
|
||||
for _, record := range candidates {
|
||||
if strings.TrimSpace(record.StoragePath) != "" {
|
||||
@@ -97,6 +99,38 @@ func (s *Service) Cleanup(ctx context.Context, task *model.BackupTask, provider
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// protectDifferentialBases 从删除候选中剔除「仍被存活差异依赖的全量」,
|
||||
// 避免删除基线后其差异备份失去依据、无法恢复。全量仅当其全部差异都已过期/删除时才会被清理。
|
||||
func protectDifferentialBases(all []model.BackupRecord, candidates []model.BackupRecord) []model.BackupRecord {
|
||||
deleting := make(map[uint]struct{}, len(candidates))
|
||||
for _, r := range candidates {
|
||||
deleting[r.ID] = struct{}{}
|
||||
}
|
||||
protected := make(map[uint]struct{})
|
||||
for _, r := range all {
|
||||
if r.BackupKind != model.BackupKindDifferential || r.BaseRecordID == 0 {
|
||||
continue
|
||||
}
|
||||
if _, beingDeleted := deleting[r.ID]; beingDeleted {
|
||||
continue // 该差异本身也将被删除,无需保护其基线
|
||||
}
|
||||
protected[r.BaseRecordID] = struct{}{}
|
||||
}
|
||||
if len(protected) == 0 {
|
||||
return candidates
|
||||
}
|
||||
filtered := make([]model.BackupRecord, 0, len(candidates))
|
||||
for _, r := range candidates {
|
||||
if r.BackupKind == model.BackupKindFull {
|
||||
if _, keep := protected[r.ID]; keep {
|
||||
continue
|
||||
}
|
||||
}
|
||||
filtered = append(filtered, r)
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
func selectRecordsToDelete(records []model.BackupRecord, retentionDays int, maxBackups int, now time.Time) []model.BackupRecord {
|
||||
// 保留锁定(法律保留)的记录永不参与清理:先从候选集中剔除,
|
||||
// 锁定备份既不被删除,也不占用 maxBackups 轮转名额。
|
||||
|
||||
Reference in New Issue
Block a user