Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Unreleased

- Allow managed sidecar trees to prune selected generated files while preserving unrelated files.
- Add non-mutating archive tag validation and rebase-based write synchronization for durable multi-machine backup retries.
- Add reusable Git snapshot history/tag/ref restoration, SQLite bundle snapshots, managed snapshot sidecars, mapped sync-state adapters, FTS5 helpers, and the shared contact-export contract; refresh stable dependencies.

Expand Down
14 changes: 12 additions & 2 deletions snapshot/sidecar.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type SidecarTreeOptions struct {
TargetDir string
Kind string
Include func(relativePath string) bool
Prune func(relativePath string) bool
}

// SyncSidecarTree atomically copies a managed directory tree into a snapshot,
Expand Down Expand Up @@ -94,7 +95,7 @@ func SyncSidecarTree(ctx context.Context, opts SidecarTreeOptions) ([]Sidecar, e
if err != nil {
return nil, err
}
if err := pruneSidecarTree(ctx, targetRoot, keep); err != nil {
if err := pruneSidecarTree(ctx, targetRoot, keep, opts.Prune); err != nil {
return nil, err
}
sort.Slice(sidecars, func(i, j int) bool { return sidecars[i].Path < sidecars[j].Path })
Expand Down Expand Up @@ -153,7 +154,7 @@ func copyFingerprintFile(source, target string) (int64, string, error) {
return size, hex.EncodeToString(hash.Sum(nil)), nil
}

func pruneSidecarTree(ctx context.Context, root string, keep map[string]struct{}) error {
func pruneSidecarTree(ctx context.Context, root string, keep map[string]struct{}, shouldPrune func(relativePath string) bool) error {
return filepath.WalkDir(root, func(path string, entry os.DirEntry, err error) error {
if err != nil {
return err
Expand All @@ -167,6 +168,15 @@ func pruneSidecarTree(ctx context.Context, root string, keep map[string]struct{}
if _, ok := keep[filepath.Clean(path)]; ok {
return nil
}
if shouldPrune != nil {
rel, err := filepath.Rel(root, path)
if err != nil {
return err
}
if !shouldPrune(filepath.ToSlash(rel)) {
return nil
}
}
if err := os.Remove(path); err != nil {
return fmt.Errorf("remove stale sidecar %s: %w", path, err)
}
Expand Down
8 changes: 8 additions & 0 deletions snapshot/snapshot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,17 @@ func TestSyncSidecarTreeCopiesFingerprintsAndPrunes(t *testing.T) {
if err := os.WriteFile(stale, []byte("stale"), 0o600); err != nil {
t.Fatal(err)
}
preserved := filepath.Join(root, "pages", "README.txt")
if err := os.WriteFile(preserved, []byte("keep"), 0o600); err != nil {
t.Fatal(err)
}
sidecars, err := SyncSidecarTree(ctx, SidecarTreeOptions{
SourceDir: source,
RootDir: root,
TargetDir: "pages",
Kind: "markdown",
Include: func(rel string) bool { return strings.HasSuffix(rel, ".md") },
Prune: func(rel string) bool { return strings.HasSuffix(rel, ".md") },
})
if err != nil {
t.Fatal(err)
Expand All @@ -169,6 +174,9 @@ func TestSyncSidecarTreeCopiesFingerprintsAndPrunes(t *testing.T) {
if _, err := os.Stat(stale); !os.IsNotExist(err) {
t.Fatalf("stale sidecar should be pruned: %v", err)
}
if _, err := os.Stat(preserved); err != nil {
t.Fatalf("excluded sidecar should be preserved: %v", err)
}
if _, err := SyncSidecarTree(ctx, SidecarTreeOptions{SourceDir: source, RootDir: root, TargetDir: "../escape"}); err == nil {
t.Fatal("escaping sidecar target should fail")
}
Expand Down