package backup import ( "fmt" "os" "path/filepath" "sort" ) // ListGenerations liefert alle Generation-IDs in backupDir, aufsteigend // sortiert (die GenerationID selbst ist chronologisch sortierbar, siehe // NewGenerationID — kein Blick auf Dateisystem-Zeitstempel nötig, die bei // einem Restore/Kopiervorgang verändert werden könnten). func ListGenerations(backupDir string) ([]string, error) { entries, err := os.ReadDir(backupDir) if err != nil { if os.IsNotExist(err) { return nil, nil } return nil, fmt.Errorf("backup: sicherungsverzeichnis lesen: %w", err) } var generations []string for _, e := range entries { if e.IsDir() { generations = append(generations, e.Name()) } } sort.Strings(generations) return generations, nil } // Rotate entfernt alle bis auf die `keep` NEUESTEN Generationen // (Akzeptanzkriterium 3) — jede Generation umfasst ihre Vollsicherung UND // alle davon abhängigen Inkremente, ein Löschen der gesamten // Generationsverzeichnisses entfernt beides konsistent zusammen. func Rotate(backupDir string, keep int) (removed []string, err error) { if keep < 0 { keep = 0 } generations, err := ListGenerations(backupDir) if err != nil { return nil, err } if len(generations) <= keep { return nil, nil } toRemove := generations[:len(generations)-keep] for _, gen := range toRemove { if err := os.RemoveAll(filepath.Join(backupDir, gen)); err != nil { return removed, fmt.Errorf("backup: generation %q entfernen: %w", gen, err) } removed = append(removed, gen) } return removed, nil }