Artifact 2b0f48313e4fb9443b8decf7bc95b912623cfe626b10c1cba0326562d23386d1:
- File
internal/checkout/setmtime.go
— part of check-in
[41951ffbc5]
at
2026-10-19 01:53:57
on branch feat/submanifest-reader
— fix(stash,undo): write working files through the checkout's storage
Stash and undo read, wrote and removed working files with the os package, so a MemStorage checkout and simulation tests never saw those writes, and no write recorded its mtime. They now take a *simio.Env and go through its Storage, recording each written file's mtime via vfile.WrittenMTime (moved with RecordableMTime from internal/checkout so these packages can share the same-second guard).
Stash save marks reverted rows unchanged with their written mtime, as fossil's revert does. Apply and pop add a stashed added file again, as fossil's stash_apply does, and give a restored edit's row its changed state and written mtime. Undo and redo record a written file's mtime only when the restored row's mtime matched the file when it was saved.
Tracked in #448 (user: [email protected], size: 3540) [annotate] [blame] [check-ins using] [more...]
package checkout import ( "fmt" "time" libfossil "github.com/danmestas/go-libfossil/internal/fsltype" "github.com/danmestas/go-libfossil/internal/vfile" ) // fileCheckinTimesQuery is fossil's mtime_of_manifest_file (src/descendants.c) // for every file at once: for each artifact, the time of the newest check-in // that recorded it among vid and vid's primary ancestors (compute_ancestors // in direct mode), in seconds since 1970. The seconds are computed and cast // as fossil computes and reads them, so a time is truncated, not rounded. const fileCheckinTimesQuery = ` WITH RECURSIVE ancestor(rid, mtime) AS ( SELECT ?, mtime FROM event WHERE objid=? UNION SELECT plink.pid, event.mtime FROM ancestor, plink, event WHERE plink.cid=ancestor.rid AND event.objid=plink.pid AND plink.isPrim ORDER BY mtime DESC LIMIT 100000000 ) SELECT mlink.fid, CAST((max(event.mtime)-2440587.5)*86400 AS INTEGER) FROM mlink, event WHERE mlink.mid=event.objid AND mlink.mid IN (SELECT rid FROM ancestor) GROUP BY mlink.fid` // fileCheckinTimes returns, by file artifact, the seconds fossil's --setmtime // gives a file of version vid (fileCheckinTimesQuery). func (c *Checkout) fileCheckinTimes(vid libfossil.FslID) (map[int64]int64, error) { rows, err := c.repo.DB().Query(fileCheckinTimesQuery, int64(vid), int64(vid)) if err != nil { return nil, fmt.Errorf("file check-in times: %w", err) } defer rows.Close() out := map[int64]int64{} for rows.Next() { var fid, secs int64 if err := rows.Scan(&fid, &secs); err != nil { return nil, fmt.Errorf("file check-in times: %w", err) } out[fid] = secs } if err := rows.Err(); err != nil { return nil, fmt.Errorf("file check-in times: %w", err) } return out, nil } // setFileMTimes is fossil's --setmtime, vfile_check_signature with // CKSIG_SETMTIME (src/vfile.c): after the files of vid are brought up to date // by hash, every one still unchanged (chnged 0, 2 or 4) and present on disk // takes the time fileCheckinTimes gives its artifact. As in fossil it is // best-effort: a file whose time cannot be set keeps the one it has. func (c *Checkout) setFileMTimes(vid libfossil.FslID) error { if vid <= 0 { panic("checkout.setFileMTimes: vid must be positive") } if err := c.refreshChanged(vid); err != nil { return err } times, err := c.fileCheckinTimes(vid) if err != nil { return err } rows, err := c.db.Query(`SELECT pathname, rid FROM vfile WHERE vid=? AND NOT deleted AND rid>0 AND chnged IN (0, 2, 4)`, int64(vid)) if err != nil { return fmt.Errorf("set mtimes: %w", err) } defer rows.Close() set := map[string]int64{} for rows.Next() { var name string var rid int64 if err := rows.Scan(&name, &rid); err != nil { return fmt.Errorf("set mtimes: %w", err) } // An artifact no check-in in the ancestry recorded is absent from // times, and gets 0: fossil's max() over no rows is NULL, which it // reads as 0, setting the file to 1970. secs := times[rid] fullPath, err := c.safePath(name) if err != nil { continue } info, err := c.env.Storage.Stat(fullPath) if err != nil || !info.Mode().IsRegular() { continue } when := time.Unix(secs, 0) if c.env.Storage.Chtimes(fullPath, when, when) == nil { set[name] = vfile.RecordableMTime(when, c.env.Clock.Now().Unix()) } } if err := rows.Err(); err != nil { return fmt.Errorf("set mtimes: %w", err) } // As fossil's scan, the time given a file is the one vfile records. return c.setRowMTimes(vid, set) }