Skip to content

macOS: The case insensitive filesystem prevents correct analysis #229

Description

@jrasell

On all occasions when running copywrite locally on macOS due to CI header plans failures, it reports that no files require modification. I worked on narrowing this down as it is very disruptive to our development flow and believe I have found the cause. macOS is a case insensitive filesystem which causes problems when used in conjunction with filepath.Rel which is case sensitive.

I hacked together a local patch, so I can get it working and hopefully this can provide reference for a fix in the main codebase:

$ git --no-pager diff
diff --git a/licensecheck/update.go b/licensecheck/update.go
index 6872ac1..17300d7 100644
--- a/licensecheck/update.go
+++ b/licensecheck/update.go
@@ -503,26 +503,35 @@ func getFileLastCommitYear(filePath string, repoRoot string) (int, error) {
                return 0, err
        }

-       // Resolve symlinks to get the canonical path. This is important on systems
-       // like macOS where /tmp is a symlink to /private/tmp. The git repo root
-       // (from 'git rev-parse --show-toplevel') uses the real path, so absPath
-       // must also be resolved before computing the relative path.
        if resolved, err := filepath.EvalSymlinks(absPath); err == nil {
                absPath = resolved
        }
+       if resolved, err := filepath.EvalSymlinks(repoRoot); err == nil {
+               repoRoot = resolved
+       }

-       // Calculate relative path from repo root to file
-       relPath, err := filepath.Rel(repoRoot, absPath)
-       if err != nil {
-               return 0, fmt.Errorf("failed to calculate relative path: %w", err)
+       // Build the cache key by stripping the repoRoot prefix. Use a
+       // case-insensitive comparison because macOS has a case-insensitive
+       // filesystem but filepath.Rel is case-sensitive. A mismatch between
+       // the casing of repoRoot and the corresponding prefix of absPath
+       // causes filepath.Rel to produce "../.."-prefixed paths that will
+       // never match a cache key derived from git log output.
+       prefix := filepath.Clean(repoRoot) + string(filepath.Separator)
+       var relPath string
+       if strings.HasPrefix(strings.ToLower(absPath), strings.ToLower(prefix)) {
+               relPath = absPath[len(prefix):]
+       } else {
+               relPath, err = filepath.Rel(repoRoot, absPath)
+               if err != nil {
+                       return 0, fmt.Errorf("failed to calculate relative path: %w", err)
+               }
        }

        if cachedYear, err := getCachedFileLastCommitYear(relPath, repoRoot); err == nil {
                return cachedYear, nil
-       } else {
-               return 0, nil
        }

+       return 0, nil
 }

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions