From 41c1c69f52d3c66f35d73579090bf3ac573a240b Mon Sep 17 00:00:00 2001 From: user Date: Tue, 10 Feb 2026 18:34:51 -0800 Subject: [PATCH] Fix FindExtraFiles reporting manifest file and dotfiles as extra (closes #16) FindExtraFiles now skips hidden files/directories (dotfiles) and the manifest file itself when walking the filesystem. The manifest's relative path is computed at Checker construction time. --- mfer/checker.go | 36 +++++++++++++++++++---------- mfer/checker_test.go | 55 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 12 deletions(-) diff --git a/mfer/checker.go b/mfer/checker.go index a213697..1bd8408 100644 --- a/mfer/checker.go +++ b/mfer/checker.go @@ -70,6 +70,8 @@ type Checker struct { fs afero.Fs // manifestPaths is a set of paths in the manifest for quick lookup manifestPaths map[RelFilePath]struct{} + // manifestRelPath is the relative path of the manifest file from basePath (for exclusion) + manifestRelPath RelFilePath // signature info from the manifest signature []byte signer []byte @@ -100,14 +102,25 @@ func NewChecker(manifestPath string, basePath string, fs afero.Fs) (*Checker, er manifestPaths[RelFilePath(f.Path)] = struct{}{} } + // Compute manifest's relative path from basePath for exclusion in FindExtraFiles + absManifest, err := filepath.Abs(manifestPath) + if err != nil { + return nil, err + } + manifestRel, err := filepath.Rel(abs, absManifest) + if err != nil { + manifestRel = "" + } + return &Checker{ - basePath: AbsFilePath(abs), - files: files, - fs: fs, - manifestPaths: manifestPaths, - signature: m.pbOuter.Signature, - signer: m.pbOuter.Signer, - signingPubKey: m.pbOuter.SigningPubKey, + basePath: AbsFilePath(abs), + files: files, + fs: fs, + manifestPaths: manifestPaths, + manifestRelPath: RelFilePath(manifestRel), + signature: m.pbOuter.Signature, + signer: m.pbOuter.Signer, + signingPubKey: m.pbOuter.SigningPubKey, }, nil } @@ -309,14 +322,13 @@ func (c *Checker) FindExtraFiles(ctx context.Context, results chan<- Result) err return nil } - // Skip manifest files - base := filepath.Base(rel) - if base == "index.mf" || base == ".index.mf" { + relPath := RelFilePath(rel) + + // Skip the manifest file itself + if relPath == c.manifestRelPath { return nil } - relPath := RelFilePath(rel) - // Check if path is in manifest if _, exists := c.manifestPaths[relPath]; !exists { if results != nil { diff --git a/mfer/checker_test.go b/mfer/checker_test.go index 5ba283e..9c7bf9a 100644 --- a/mfer/checker_test.go +++ b/mfer/checker_test.go @@ -452,6 +452,61 @@ func TestCheckMissingFileDetectedWithoutFallback(t *testing.T) { assert.Equal(t, 0, statusCounts[StatusError], "no files should be ERROR") } +func TestFindExtraFilesSkipsDotfiles(t *testing.T) { + // Regression test for #16: FindExtraFiles should not report dotfiles + // or the manifest file itself as extra files. + fs := afero.NewMemMapFs() + files := map[string][]byte{ + "file1.txt": []byte("in manifest"), + } + createTestManifest(t, fs, "/data/.index.mf", files) + createFilesOnDisk(t, fs, "/data", files) + + // Add dotfiles and manifest file on disk + require.NoError(t, afero.WriteFile(fs, "/data/.hidden", []byte("dotfile"), 0o644)) + require.NoError(t, fs.MkdirAll("/data/.git", 0o755)) + require.NoError(t, afero.WriteFile(fs, "/data/.git/config", []byte("git config"), 0o644)) + + chk, err := NewChecker("/data/.index.mf", "/data", fs) + require.NoError(t, err) + + results := make(chan Result, 10) + err = chk.FindExtraFiles(context.Background(), results) + require.NoError(t, err) + + var extras []Result + for r := range results { + extras = append(extras, r) + } + + // Should report NO extra files — dotfiles and manifest should be skipped + assert.Empty(t, extras, "FindExtraFiles should not report dotfiles or manifest file as extra; got: %v", extras) +} + +func TestFindExtraFilesSkipsManifestFile(t *testing.T) { + // The manifest file itself should never be reported as extra + fs := afero.NewMemMapFs() + files := map[string][]byte{ + "file1.txt": []byte("content"), + } + createTestManifest(t, fs, "/data/index.mf", files) + createFilesOnDisk(t, fs, "/data", files) + + chk, err := NewChecker("/data/index.mf", "/data", fs) + require.NoError(t, err) + + results := make(chan Result, 10) + err = chk.FindExtraFiles(context.Background(), results) + require.NoError(t, err) + + var extras []Result + for r := range results { + extras = append(extras, r) + } + + assert.Empty(t, extras, "manifest file should not be reported as extra; got: %v", extras) +} + func TestCheckEmptyManifest(t *testing.T) { fs := afero.NewMemMapFs() // Create manifest with no files