From 37665bec9014a69e82e2c19e59fc40ce06feea77 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Sun, 22 Feb 2026 13:53:59 +0200 Subject: fix(showcase): exclude backup-suffixed repositories --- internal/showcase/showcase.go | 46 ++++++++++++----- internal/showcase/showcase_test.go | 100 +++++++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+), 12 deletions(-) create mode 100644 internal/showcase/showcase_test.go (limited to 'internal/showcase') diff --git a/internal/showcase/showcase.go b/internal/showcase/showcase.go index a8770a2..19a9a89 100644 --- a/internal/showcase/showcase.go +++ b/internal/showcase/showcase.go @@ -815,23 +815,13 @@ func (g *Generator) verifyImages(summary *ProjectSummary) error { // filterExcludedRepos filters out repositories that are in the exclusion list func (g *Generator) filterExcludedRepos(repos []string) []string { - if len(g.config.ExcludeFromShowcase) == 0 { - return repos - } - - // Create a map for quick lookup - excludeMap := make(map[string]bool) - for _, excluded := range g.config.ExcludeFromShowcase { - excludeMap[excluded] = true - } - // Filter repositories var filtered []string for _, repo := range repos { - if !excludeMap[repo] { + if !g.isExcluded(repo) { filtered = append(filtered, repo) } else { - fmt.Printf("Excluding repository from showcase: %s\n", repo) + fmt.Printf("Excluding repository from showcase (%s): %s\n", g.exclusionReason(repo), repo) } } @@ -840,6 +830,10 @@ func (g *Generator) filterExcludedRepos(repos []string) []string { // isExcluded checks if a repository is in the exclusion list func (g *Generator) isExcluded(repo string) bool { + if isBackupRepo(repo) { + return true + } + for _, excluded := range g.config.ExcludeFromShowcase { if excluded == repo { return true @@ -848,6 +842,34 @@ func (g *Generator) isExcluded(repo string) bool { return false } +// exclusionReason returns why a repository is excluded from showcase generation. +func (g *Generator) exclusionReason(repo string) string { + var reasons []string + + if isBackupRepo(repo) { + reasons = append(reasons, "backup suffix") + } + + for _, excluded := range g.config.ExcludeFromShowcase { + if excluded == repo { + reasons = append(reasons, "config") + break + } + } + + if len(reasons) == 0 { + return "unknown reason" + } + + return strings.Join(reasons, ", ") +} + +// isBackupRepo checks whether a repository name has a backup suffix. +// Excluded patterns: *.bak and *.bak.* +func isBackupRepo(repo string) bool { + return strings.HasSuffix(repo, ".bak") || strings.Contains(repo, ".bak.") +} + // formatNumber formats a number with thousands separators func formatNumber(n int) string { str := fmt.Sprintf("%d", n) diff --git a/internal/showcase/showcase_test.go b/internal/showcase/showcase_test.go new file mode 100644 index 0000000..284a6bc --- /dev/null +++ b/internal/showcase/showcase_test.go @@ -0,0 +1,100 @@ +package showcase + +import ( + "reflect" + "testing" + + "codeberg.org/snonux/gitsyncer/internal/config" +) + +func TestIsBackupRepo(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + repo string + want bool + }{ + {name: "exact bak suffix", repo: "foo.bak", want: true}, + {name: "bak dot suffix", repo: "foo.bak.20260222", want: true}, + {name: "bak dot with multiple segments", repo: "foo.bak.tmp.snapshot", want: true}, + {name: "backup word", repo: "foo.backup", want: false}, + {name: "bak as prefix", repo: "bak.foo", want: false}, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + got := isBackupRepo(tc.repo) + if got != tc.want { + t.Fatalf("isBackupRepo(%q) = %v, want %v", tc.repo, got, tc.want) + } + }) + } +} + +func TestIsExcluded_AdditiveRules(t *testing.T) { + t.Parallel() + + g := &Generator{ + config: &config.Config{ + ExcludeFromShowcase: []string{"manual-exclude"}, + }, + } + + tests := []struct { + name string + repo string + want bool + }{ + {name: "excluded by config", repo: "manual-exclude", want: true}, + {name: "excluded by backup suffix", repo: "repo.bak", want: true}, + {name: "not excluded", repo: "normal-repo", want: false}, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + got := g.isExcluded(tc.repo) + if got != tc.want { + t.Fatalf("isExcluded(%q) = %v, want %v", tc.repo, got, tc.want) + } + }) + } +} + +func TestFilterExcludedRepos_RemovesBackupAndConfigRepos(t *testing.T) { + t.Parallel() + + g := &Generator{ + config: &config.Config{ + ExcludeFromShowcase: []string{"manual-exclude"}, + }, + } + + repos := []string{"normal", "manual-exclude", "mirror.bak", "mirror.bak.20260222", "keep"} + want := []string{"normal", "keep"} + + got := g.filterExcludedRepos(repos) + if !reflect.DeepEqual(got, want) { + t.Fatalf("filterExcludedRepos() = %#v, want %#v", got, want) + } +} + +func TestFilterExcludedRepos_EmptyConfigStillRemovesBackupRepos(t *testing.T) { + t.Parallel() + + g := &Generator{ + config: &config.Config{}, + } + + repos := []string{"normal", "archive.bak", "archive.bak.old", "keep"} + want := []string{"normal", "keep"} + + got := g.filterExcludedRepos(repos) + if !reflect.DeepEqual(got, want) { + t.Fatalf("filterExcludedRepos() = %#v, want %#v", got, want) + } +} -- cgit v1.2.3