Artifact ae14364e79e190f9d502bcaa47a4f160d6dc3b9b9cc6182498ee4ca3b3667bf5:
- File
internal/tag/tag_date_test.go
— part of check-in
[30b9d65021]
at
2026-10-13 03:36:37
on branch main
— fix(tag): derive the check-in event time from direct date tags only (#372) (#380)
* fix(tag): derive the check-in event time from direct date tags only (#372)
Section 4.2, step 6 of repo-state-01 makes a date tag applied directly to a check-in set its event time, whatever order the tag and the check-in arrive in. Crosslinking read the date tagxref row whatever its source, so re-crosslinking after a propagating date tag retimed the descendants that inherited it, and a cancel, an unparsable value or 0 left no time or a time of 0, as fossil 2.28 does.
tag.RefreshEventTime now holds the rule, and both tag application and crosslinking call it: a direct, non-cancel date row whose value parses to 0 < r < 5373484.5 sets mtime; anything else restores the D-card time kept in omtime. The date-tag cancel, unparsable and zero cases are no longer deviations, and new before-checkin cases cover the other arrival order.
Tracked in #372.
* fix(tag): word the date-tag parse rule as step 6 does (#372)
* docs(changelog): tidy the entry spacing (#372) (user: [email protected], size: 3642) [annotate] [blame] [check-ins using]
package tag_test import ( "database/sql" "testing" libfossil "github.com/danmestas/go-libfossil/internal/fsltype" "github.com/danmestas/go-libfossil/internal/manifest" "github.com/danmestas/go-libfossil/internal/repo" "github.com/danmestas/go-libfossil/internal/tag" ) // The date tag replaces a check-in's event time (draft-fossil-repo-state-01 // Section 4.2, step 6). These tests pin the cases where the draft departs // from fossil(1) 2.28: a cancel, an unparsable value and a time that is not // positive all leave the D-card time, and an inherited row sets nothing. // eventTime reads a check-in's event.mtime and event.omtime as Julian days. // The casts keep a driver that maps DATETIME columns to time.Time from // converting them. func eventTime(t *testing.T, r *repo.Repo, rid int64) (mtime, omtime sql.NullFloat64) { t.Helper() err := r.DB().QueryRow( "SELECT CAST(mtime AS REAL), CAST(omtime AS REAL) FROM event WHERE objid=?", rid). Scan(&mtime, &omtime) if err != nil { t.Fatalf("event row for %d: %v", rid, err) } return mtime, omtime } // addDateTag applies a date tag of the given type and value to rid, n hours // from now so successive tags are each newer than the last. func addDateTag(t *testing.T, r *repo.Repo, rid int64, tagType int, value string, n int) { t.Helper() if _, err := tag.AddTag(r, tag.TagOpts{ TargetRID: libfossil.FslID(rid), TagName: "date", TagType: tagType, Value: value, User: "testuser", Time: tagTime(n), }); err != nil { t.Fatalf("tag.AddTag date %q: %v", value, err) } } // wantDCardTime fails unless rid's event time is its D-card time. func wantDCardTime(t *testing.T, r *repo.Repo, rid int64) { t.Helper() mtime, omtime := eventTime(t, r, rid) if !omtime.Valid || !mtime.Valid || mtime.Float64 != omtime.Float64 { t.Errorf("rid %d: mtime %v, want the D-card time %v", rid, mtime, omtime) } } func TestDateTagCancelRestoresDCardTime(t *testing.T) { r := setupTestRepo(t) rid := makeCheckin(t, r, 0, "a.txt", "a", "A") addDateTag(t, r, rid, tag.TagSingleton, "2001-02-03T04:05:06", 1) if mtime, omtime := eventTime(t, r, rid); mtime.Float64 == omtime.Float64 { t.Fatalf("date tag did not move the time: mtime %v", mtime) } addDateTag(t, r, rid, tag.TagCancel, "", 2) wantDCardTime(t, r, rid) } func TestDateTagIgnoredValuesKeepDCardTime(t *testing.T) { for _, value := range []string{"not-a-date", "0", "-1", "5373484.5"} { t.Run(value, func(t *testing.T) { r := setupTestRepo(t) rid := makeCheckin(t, r, 0, "a.txt", "a", "A") addDateTag(t, r, rid, tag.TagSingleton, "2001-02-03T04:05:06", 1) addDateTag(t, r, rid, tag.TagSingleton, value, 2) wantDCardTime(t, r, rid) }) } } // TestDateTagInheritedRowSetsNoTime re-crosslinks after a propagating date // tag reached B, keeping the inherited row as a second sweep does: only a // tag applied directly to a check-in sets its time (#372). func TestDateTagInheritedRowSetsNoTime(t *testing.T) { r := setupTestRepo(t) ridA := makeCheckin(t, r, 0, "a.txt", "a", "A") ridB := makeCheckin(t, r, ridA, "b.txt", "b", "B") addDateTag(t, r, ridA, tag.TagPropagating, "2001-02-03T04:05:06", 1) for _, clear := range []string{"DELETE FROM event", "DELETE FROM tagxref WHERE srcid > 0"} { if _, err := r.DB().Exec(clear); err != nil { t.Fatalf("%s: %v", clear, err) } } if _, err := manifest.Crosslink(r); err != nil { t.Fatalf("Crosslink: %v", err) } wantDCardTime(t, r, ridB) if mtime, omtime := eventTime(t, r, ridA); mtime.Float64 == omtime.Float64 { t.Errorf("A: propagating date tag did not move its own time: mtime %v", mtime) } }