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
This commit is contained in:
Harald Sitter
2025-08-26 06:34:54 +02:00
parent 806f85a40a
commit bf5f2dd160

View File

@@ -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)