From bf5f2dd160c626e97a0f6dfe66a331a8dbc4f099 Mon Sep 17 00:00:00 2001 From: Harald Sitter Date: Tue, 26 Aug 2025 06:34:54 +0200 Subject: [PATCH] give dirs a stable time with file mtimes stabilized, we now have dirs lighting up like a christmas tree in my diff scripts. give them a stable mtime to get consistency between builds. the idea here is that if we set the mtime of all dirs to their latest content's mtime we'll implicitly stabilize the dirs through stabilizing the files somewhat unfortunately we need to do this in a single thread because otherwise we'd have to segment deep trees and I really don't want to venture there for such an otherwise simple program a future option might be to also put dirs in our json but realistically that only makes a difference for empty dirs (since they have no content from which to derive the mtime). so let's see where we get with this. we can always add dir records in the json later --- mtimer/main.go | 63 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/mtimer/main.go b/mtimer/main.go index 582006a..0643ec4 100644 --- a/mtimer/main.go +++ b/mtimer/main.go @@ -14,11 +14,18 @@ import ( "os" "path/filepath" "runtime" + "sort" + "strings" "time" "golang.org/x/sync/errgroup" ) +type DirInfo struct { + absPath string + info os.FileInfo +} + type FileInfo struct { SHA256 string `json:"sha256"` MTime int64 `json:"mtime"` @@ -89,6 +96,39 @@ func analyze(input Analysis) Analysis { return input } +func updateDir(dir DirInfo) { + entries, err := os.ReadDir(dir.absPath) + if err != nil { + log.Fatal(err) + } + + dir.info, err = os.Stat(dir.absPath) // Refresh in case it changed + if err != nil { + log.Fatal(err) + } + + latest := dir.info.ModTime() + for _, entry := range entries { + if entry.Type()&os.ModeSymlink != 0 { + continue + } + entryInfo, err := entry.Info() + if err != nil { + log.Fatal(err) + } + if entryInfo.ModTime().After(latest) { + latest = entryInfo.ModTime() + } + } + + if latest.After(dir.info.ModTime()) { + log.Println("Restoring mtime for directory", dir.absPath) + if err := os.Chtimes(dir.absPath, latest, latest); err != nil { + log.Fatal(err) + } + } +} + func main() { root := flag.String("root", "", "rootfs to operate on") jsonPath := flag.String("json", "", "json file to read and write") @@ -110,12 +150,19 @@ func main() { Files: map[string]FileInfo{}, } + // We also collect all directories so we might chmod them later. + dirs := []DirInfo{} + toAnalyze := []Analysis{} err = filepath.Walk(*root, func(path string, info os.FileInfo, err error) error { if err != nil { return err } - if info.IsDir() || info.Mode()&os.ModeSymlink != 0 { + if info.Mode()&os.ModeSymlink != 0 { + return nil + } + if info.IsDir() { + dirs = append(dirs, DirInfo{absPath: path, info: info}) return nil } @@ -162,6 +209,12 @@ func main() { return nil }) } + g.Go(func() error { + // Sort directories by depth, deepest first. + // Be mindful that we change dirs in-place. This is not thread safe and relies on our errgroup waiting! + sort.Slice(dirs, func(i, j int) bool { return strings.Count(dirs[i].absPath, "/") > strings.Count(dirs[j].absPath, "/") }) + return nil + }) if err := g.Wait(); err != nil { log.Fatal(err) } @@ -170,6 +223,14 @@ func main() { newBlob.Files[result.relPath] = result.info } + // Now let's chtimes the directories to the latest mtime of their contents. + // This could be more efficient but makes for somewhat complicated code. + // Instead we run the directories in a single thread. + // Unfortunate but it is what it is. + for _, dir := range dirs { + updateDir(dir) + } + data, err := json.Marshal(newBlob) if err != nil { log.Fatal(err)