Exclude dotfiles by default, add --include-dotfiles flag

Changed the default behavior to exclude dotfiles (files/dirs starting with .)
which is the more common use case. Added --include-dotfiles flag for when
hidden files need to be included in the manifest.
This commit is contained in:
Jeffrey Paul 2025-12-17 15:10:22 -08:00
parent 6dc496fa9e
commit 0e86562c09
9 changed files with 52 additions and 26 deletions

View File

@ -19,7 +19,7 @@ default: fmt test
run: ./mfer.cmd run: ./mfer.cmd
./$< ./$<
./$< gen --ignore-dotfiles ./$< gen
ci: test ci: test

View File

@ -101,7 +101,7 @@ Reading file contents and computing cryptographic hashes for manifest generation
### scanner.go ### scanner.go
- **Types** - **Types**
- `Options struct` - Options for scanner behavior - `Options struct` - Options for scanner behavior
- `IgnoreDotfiles bool` - `IncludeDotfiles bool` - Include dot (hidden) files (excluded by default)
- `FollowSymLinks bool` - `FollowSymLinks bool`
- `EnumerateStatus struct` - Progress information for enumeration phase - `EnumerateStatus struct` - Progress information for enumeration phase
- `FilesFound int64` - `FilesFound int64`
@ -171,7 +171,7 @@ Reading file contents and computing cryptographic hashes for manifest generation
### manifest.go ### manifest.go
- **Types** - **Types**
- `ManifestScanOptions struct` - Options for scanning directories - `ManifestScanOptions struct` - Options for scanning directories
- `IgnoreDotfiles bool` - `IncludeDotfiles bool` - Include dot (hidden) files (excluded by default)
- `FollowSymLinks bool` - `FollowSymLinks bool`
- **Functions** - **Functions**
- `New() *manifest` - Creates a new empty manifest - `New() *manifest` - Creates a new empty manifest

View File

@ -15,5 +15,5 @@ trap cleanup EXIT
echo "Building mfer..." echo "Building mfer..."
go build -o "$TMPDIR/mfer" ./cmd/mfer go build -o "$TMPDIR/mfer" ./cmd/mfer
"$TMPDIR/mfer" generate --ignore-dotfiles -o "$MANIFEST" . "$TMPDIR/mfer" generate -o "$MANIFEST" .
"$TMPDIR/mfer" check --base . "$MANIFEST" "$TMPDIR/mfer" check --base . "$MANIFEST"

View File

@ -8,6 +8,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
urfcli "github.com/urfave/cli/v2" urfcli "github.com/urfave/cli/v2"
"sneak.berlin/go/mfer/mfer"
) )
func init() { func init() {
@ -186,7 +187,7 @@ func TestUnknownCommand(t *testing.T) {
assert.Equal(t, 1, exitCode) assert.Equal(t, 1, exitCode)
} }
func TestGenerateWithIgnoreDotfiles(t *testing.T) { func TestGenerateExcludesDotfilesByDefault(t *testing.T) {
fs := afero.NewMemMapFs() fs := afero.NewMemMapFs()
// Create test files including dotfiles // Create test files including dotfiles
@ -194,14 +195,39 @@ func TestGenerateWithIgnoreDotfiles(t *testing.T) {
require.NoError(t, afero.WriteFile(fs, "/testdir/file1.txt", []byte("hello"), 0644)) require.NoError(t, afero.WriteFile(fs, "/testdir/file1.txt", []byte("hello"), 0644))
require.NoError(t, afero.WriteFile(fs, "/testdir/.hidden", []byte("secret"), 0644)) require.NoError(t, afero.WriteFile(fs, "/testdir/.hidden", []byte("secret"), 0644))
// Generate manifest with --ignore-dotfiles // Generate manifest without --include-dotfiles (default excludes dotfiles)
opts := testOpts([]string{"mfer", "-q", "generate", "--ignore-dotfiles", "-o", "/testdir/test.mf", "/testdir"}, fs) opts := testOpts([]string{"mfer", "-q", "generate", "-o", "/testdir/test.mf", "/testdir"}, fs)
exitCode := RunWithOptions(opts) exitCode := RunWithOptions(opts)
require.Equal(t, 0, exitCode) require.Equal(t, 0, exitCode)
// Check that manifest exists and we can verify (hidden file won't cause failure even if missing) // Check that manifest exists
exists, _ := afero.Exists(fs, "/testdir/test.mf") exists, _ := afero.Exists(fs, "/testdir/test.mf")
assert.True(t, exists) assert.True(t, exists)
// Verify manifest only has 1 file (the non-dotfile)
manifest, err := mfer.NewManifestFromFile(fs, "/testdir/test.mf")
require.NoError(t, err)
assert.Len(t, manifest.Files(), 1)
assert.Equal(t, "file1.txt", manifest.Files()[0].Path)
}
func TestGenerateWithIncludeDotfiles(t *testing.T) {
fs := afero.NewMemMapFs()
// Create test files including dotfiles
require.NoError(t, fs.MkdirAll("/testdir", 0755))
require.NoError(t, afero.WriteFile(fs, "/testdir/file1.txt", []byte("hello"), 0644))
require.NoError(t, afero.WriteFile(fs, "/testdir/.hidden", []byte("secret"), 0644))
// Generate manifest with --include-dotfiles
opts := testOpts([]string{"mfer", "-q", "generate", "--include-dotfiles", "-o", "/testdir/test.mf", "/testdir"}, fs)
exitCode := RunWithOptions(opts)
require.Equal(t, 0, exitCode)
// Verify manifest has 2 files (including dotfile)
manifest, err := mfer.NewManifestFromFile(fs, "/testdir/test.mf")
require.NoError(t, err)
assert.Len(t, manifest.Files(), 2)
} }
func TestMultipleInputPaths(t *testing.T) { func TestMultipleInputPaths(t *testing.T) {

View File

@ -40,7 +40,7 @@ func (mfa *CLIApp) freshenManifestOperation(ctx *cli.Context) error {
basePath := ctx.String("base") basePath := ctx.String("base")
showProgress := ctx.Bool("progress") showProgress := ctx.Bool("progress")
ignoreDotfiles := ctx.Bool("IgnoreDotfiles") includeDotfiles := ctx.Bool("IncludeDotfiles")
followSymlinks := ctx.Bool("FollowSymLinks") followSymlinks := ctx.Bool("FollowSymLinks")
// Find manifest file // Find manifest file
@ -112,7 +112,7 @@ func (mfa *CLIApp) freshenManifestOperation(ctx *cli.Context) error {
} }
// Handle dotfiles // Handle dotfiles
if ignoreDotfiles && pathIsHidden(relPath) { if !includeDotfiles && pathIsHidden(relPath) {
if info.IsDir() { if info.IsDir() {
return filepath.SkipDir return filepath.SkipDir
} }

View File

@ -16,7 +16,7 @@ func (mfa *CLIApp) generateManifestOperation(ctx *cli.Context) error {
log.Debug("generateManifestOperation()") log.Debug("generateManifestOperation()")
opts := &scanner.Options{ opts := &scanner.Options{
IgnoreDotfiles: ctx.Bool("IgnoreDotfiles"), IncludeDotfiles: ctx.Bool("IncludeDotfiles"),
FollowSymLinks: ctx.Bool("FollowSymLinks"), FollowSymLinks: ctx.Bool("FollowSymLinks"),
Fs: mfa.Fs, Fs: mfa.Fs,
} }

View File

@ -111,9 +111,9 @@ func (mfa *CLIApp) run(args []string) {
Usage: "Resolve encountered symlinks", Usage: "Resolve encountered symlinks",
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "IgnoreDotfiles", Name: "IncludeDotfiles",
Aliases: []string{"ignore-dotfiles"}, Aliases: []string{"include-dotfiles"},
Usage: "Ignore any dot (hidden) files encountered", Usage: "Include dot (hidden) files (excluded by default)",
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "output", Name: "output",
@ -186,9 +186,9 @@ func (mfa *CLIApp) run(args []string) {
Usage: "Resolve encountered symlinks", Usage: "Resolve encountered symlinks",
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "IgnoreDotfiles", Name: "IncludeDotfiles",
Aliases: []string{"ignore-dotfiles"}, Aliases: []string{"include-dotfiles"},
Usage: "Ignore any dot (hidden) files encountered", Usage: "Include dot (hidden) files (excluded by default)",
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "progress", Name: "progress",

View File

@ -42,7 +42,7 @@ type ScanStatus struct {
// Options configures scanner behavior. // Options configures scanner behavior.
type Options struct { type Options struct {
IgnoreDotfiles bool // Skip files and directories starting with a dot IncludeDotfiles bool // Include files and directories starting with a dot (default: exclude)
FollowSymLinks bool // Resolve symlinks instead of skipping them FollowSymLinks bool // Resolve symlinks instead of skipping them
Fs afero.Fs // Filesystem to use, defaults to OsFs if nil Fs afero.Fs // Filesystem to use, defaults to OsFs if nil
} }
@ -152,7 +152,7 @@ func (s *Scanner) enumerateFS(afs afero.Fs, basePath string, progress chan<- Enu
if err != nil { if err != nil {
return err return err
} }
if s.options.IgnoreDotfiles && pathIsHidden(p) { if !s.options.IncludeDotfiles && pathIsHidden(p) {
if info.IsDir() { if info.IsDir() {
return filepath.SkipDir return filepath.SkipDir
} }

View File

@ -41,7 +41,7 @@ func (m *manifest) String() string {
// ManifestScanOptions configures behavior when scanning directories for manifest generation. // ManifestScanOptions configures behavior when scanning directories for manifest generation.
type ManifestScanOptions struct { type ManifestScanOptions struct {
IgnoreDotfiles bool // Skip files and directories starting with a dot IncludeDotfiles bool // Include files and directories starting with a dot (default: exclude)
FollowSymLinks bool // Resolve symlinks instead of skipping them FollowSymLinks bool // Resolve symlinks instead of skipping them
} }
@ -153,7 +153,7 @@ func pathIsHidden(p string) bool {
} }
func (m *manifest) addFile(p string, fi fs.FileInfo, sfsIndex int) error { func (m *manifest) addFile(p string, fi fs.FileInfo, sfsIndex int) error {
if m.scanOptions.IgnoreDotfiles && pathIsHidden(p) { if !m.scanOptions.IncludeDotfiles && pathIsHidden(p) {
return nil return nil
} }
if fi != nil && fi.IsDir() { if fi != nil && fi.IsDir() {