From c8c0c2985399bbcc08d1052350bbc8328b42c5da Mon Sep 17 00:00:00 2001 From: Richard Morrill Date: Mon, 4 May 2026 15:31:37 +0000 Subject: [PATCH] fix(fuse): align ctime/atime with mtime in inode attrs Mirror `mtime` onto `atime` and `ctime` in FUSE inode attrs. Leaving them as `time.Time{}` produces a year-1 zero that overflows when serialized into the FUSE attr struct, and a `ctime` that disagrees with what git stored makes git's stat-cache treat every base file as potentially modified. This addresses one of two causes of the "all files modified on fresh blobless-clone mount" symptom. The size=0 cause and the slowness of git's resulting content-check pass are addressed in follow-up PRs. --- internal/fusefs/fuse_unix.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/internal/fusefs/fuse_unix.go b/internal/fusefs/fuse_unix.go index 3a1108e..73d23b6 100644 --- a/internal/fusefs/fuse_unix.go +++ b/internal/fusefs/fuse_unix.go @@ -585,13 +585,20 @@ func inodeAttrs(mode uint32, size uint64, typ string, mtime time.Time) fuseops.I case "symlink": m |= os.ModeSymlink } + // Mirror mtime onto atime/ctime. Leaving them as zero time.Time produces + // nonsensical timestamps (Go's year-1 zero overflows when serialized into + // the FUSE attr struct), and a ctime that disagrees with the index makes + // git's stat-cache treat every base file as "potentially modified" even + // when content is intact. return fuseops.InodeAttributes{ Size: size, Nlink: 1, Mode: m, Uid: uint32(os.Getuid()), Gid: uint32(os.Getgid()), + Atime: mtime, Mtime: mtime, + Ctime: mtime, } } @@ -610,12 +617,15 @@ func setChildEntryExpiry(entry *fuseops.ChildInodeEntry, ttl time.Duration) { } func (fs *ArtifactFuse) gitFileAttrs() fuseops.InodeAttributes { + now := time.Now() return fuseops.InodeAttributes{ Size: uint64(len(fs.gitfileContent)), Mode: 0o644, Nlink: 1, Uid: uint32(os.Getuid()), Gid: uint32(os.Getgid()), - Mtime: time.Now(), + Atime: now, + Mtime: now, + Ctime: now, } }