@@ -13,25 +13,33 @@ import (
1313type FileStatus string
1414
1515const (
16- StatusInSync FileStatus = "IN_SYNC" // File content matches
17- StatusModified FileStatus = "MODIFIED" // File exists in both, content differs
18- StatusAdded FileStatus = "ADDED" // File in source, not in conf copy
19- StatusDeleted FileStatus = "DELETED" // File in conf copy, not in source
16+ StatusInSync FileStatus = "IN_SYNC" // File content matches
17+ StatusModified FileStatus = "MODIFIED" // File exists in both, content differs
18+ StatusAdded FileStatus = "ADDED" // File in source, not in conf copy
19+ StatusDeleted FileStatus = "DELETED" // File in conf copy, not in source
2020)
2121
2222// FileDrift represents a difference between source and conf copy
2323type FileDrift struct {
24- RelPath string // Relative path from folder root
25- Status FileStatus
26- SourceHash string // SHA256 hash of source file (if exists)
27- ConfHash string // SHA256 hash of conf file (if exists)
28- SourceMtime int64 // Modification time of source file
29- ConfMtime int64 // Modification time of conf file
30- IsDir bool // Whether this is a directory
24+ RelPath string // Relative path from folder root
25+ Status FileStatus
26+ SourceHash string // SHA256 hash of source file (if exists)
27+ ConfHash string // SHA256 hash of conf file (if exists)
28+ SourceMtime int64 // Modification time of source file
29+ ConfMtime int64 // Modification time of conf file
30+ IsDir bool // Whether this is a directory
3131}
3232
33- // DetectDrift compares source folder with conf copy and returns all differences
33+ // DetectDrift compares source folder with conf copy and returns all differences.
34+ // This is a convenience wrapper that calls DetectDriftWithExcludes with no excludes.
3435func DetectDrift (sourcePath , confPath string ) ([]FileDrift , error ) {
36+ return DetectDriftWithExcludes (sourcePath , confPath , nil )
37+ }
38+
39+ // DetectDriftWithExcludes compares source folder with conf copy and returns all differences,
40+ // excluding files that match any of the provided patterns.
41+ // Patterns support shell-style wildcards (*, ?) via filepath.Match.
42+ func DetectDriftWithExcludes (sourcePath , confPath string , excludePatterns []string ) ([]FileDrift , error ) {
3543 var drifts []FileDrift
3644
3745 // Build maps of all files in source and conf
@@ -51,6 +59,13 @@ func DetectDrift(sourcePath, confPath string) ([]FileDrift, error) {
5159 if relPath == "." {
5260 return nil
5361 }
62+ // Skip excluded files
63+ if shouldExclude (relPath , info .Name (), excludePatterns ) {
64+ if info .IsDir () {
65+ return filepath .SkipDir
66+ }
67+ return nil
68+ }
5469 sourceFiles [relPath ] = info
5570 return nil
5671 }); err != nil {
@@ -70,6 +85,13 @@ func DetectDrift(sourcePath, confPath string) ([]FileDrift, error) {
7085 if relPath == "." {
7186 return nil
7287 }
88+ // Skip excluded files
89+ if shouldExclude (relPath , info .Name (), excludePatterns ) {
90+ if info .IsDir () {
91+ return filepath .SkipDir
92+ }
93+ return nil
94+ }
7395 confFiles [relPath ] = info
7496 return nil
7597 }); err != nil {
@@ -196,5 +218,28 @@ func FormatDriftSummary(drifts []FileDrift) string {
196218 parts = append (parts , fmt .Sprintf ("%d deleted" , count ))
197219 }
198220
199- return fmt .Sprintf ("%d files with drift (%s)" , len (drifts ), strings .Join (parts , ", " ))
221+ fileWord := "files"
222+ if len (drifts ) == 1 {
223+ fileWord = "file"
224+ }
225+ return fmt .Sprintf ("%d %s with drift (%s)" , len (drifts ), fileWord , strings .Join (parts , ", " ))
226+ }
227+
228+ // shouldExclude returns true if a file path matches any of the exclude patterns.
229+ // Patterns are matched against both the full relative path and the base filename.
230+ // Supports shell-style wildcards (* and ?) via filepath.Match.
231+ // Invalid patterns are silently ignored (treated as non-matching).
232+ func shouldExclude (relPath , baseName string , excludePatterns []string ) bool {
233+ for _ , pattern := range excludePatterns {
234+ // Match against base filename (e.g., "*.tmp" matches "foo.tmp")
235+ // Error from filepath.Match indicates invalid pattern syntax - we skip such patterns
236+ if matched , err := filepath .Match (pattern , baseName ); err == nil && matched {
237+ return true
238+ }
239+ // Match against full relative path (e.g., "subdir/*.log")
240+ if matched , err := filepath .Match (pattern , relPath ); err == nil && matched {
241+ return true
242+ }
243+ }
244+ return false
200245}
0 commit comments