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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions