Artifact 5412bc5987527d74a6c4db401ec0a40f49fa1d663070e2d333cd78835d2a5cf1:
- File
internal/manifest/event_comment_test.go
— part of check-in
[087e1c7ce3]
at
2026-10-18 21:49:37
on branch feat/submanifest-reader
— fix(manifest): derive the event rows fossil derives for tags and attachments
A control artifact's 'g' event comment now matches fossil byte for byte: each target introduced by an Edit link, changes sorted by target hash and full tag name, sym- tags named as tags, a branch move linked to its timeline with its own sym- changes left out, values HTML-escaped, no leading space. tag.InsertControlEvent owns the row, and AddTag writes it, as fossil's tag add does; it was missing until a rebuild or clone.
Attachment comments link the attached artifact and the ticket or tech note as fossil 2.28 does, and a deleted attachment's src is NULL. A check-in without a C card gets a NULL event comment. Hash prefixes follow hash-digits up to 64 digits (was capped at 40); the printf helpers move to fsltype so tag, ticket and attachment comments share them.
Fossil-binary oracles: a fossil-made history with tags, branch moves, wiki, tickets, tech notes, forum posts and attachments rebuilt by both, and a go-written history rebuilt by fossil, must give identical event and attachment tables. (user: [email protected], size: 3437) [annotate] [blame] [check-ins using] [more...]
package manifest import ( "crypto/md5" "fmt" "testing" "time" "github.com/danmestas/go-libfossil/internal/blob" ) // The expected comments are the ones fossil 2.28 wrote for the same cards. func TestAttachmentComment(t *testing.T) { const src = "e05badb474cffee2aa0d8a2c4c1e7e0f6a8b9c0d" const tkt = "f0fcd1568784da2c08318aecf16014d96408abe7" const note = "17a58f7773a1096ed9a2af9882293c4b95105c75" const long = "a-very-long-attachment-name.txt" cases := []struct { name string kind byte src, file, target string crosslink, refresh string }{ {"wiki add", 'w', src, "a.txt", "Page1", "Add attachment [/artifact/e05badb474cffee2|a.txt] to wiki page [Page1]", ""}, {"wiki delete", 'w', "", long, "Page1", `Delete attachment "a-very-long-attachment-name.txt" from wiki page [Page1]`, ""}, {"ticket add", 't', src, "t <x>.txt", tkt, "Add attachment [/artifact/e05badb474cffee2|t <x>.txt] to ticket [f0fcd1568784da2c|f0fcd15687]", "Add attachment [/artifact/e05badb474cffee2|t <x>.txt] to ticket [f0fcd1568784da2c|f0fcd15687]"}, {"ticket delete", 't', "", "t <x>.txt", tkt, `Delete attachment "t <x>.txt" from ticket [f0fcd1568784da2c|f0fcd15687]`, `Delete attachment "t <x>.txt" from ticket [f0fcd1568784da2c|f0fcd15687]`}, {"technote add", 'e', src, "a.txt", note, "Add attachment [/artifact/e05badb474cffee2|a.txt] to tech note [/technote/17a58f7773a1096e|17a58f7773]", "Add attachment [/artifact/e05badb474cffee2|a.txt] to tech note [/technote/17a58f7773a1096e|17a58f7773]"}, {"technote delete", 'e', "", long, note, `Delete attachment "/artifact/a-very-long-atta|a-very-long-attachment-name.txt" from tech note` + ` [/technote/17a58f7773a1096e|17a58f7773]`, `Delete attachment "a-very-long-attachment-name.txt" from tech note [/technote/17a58f7773a1096e|17a58f7773]`}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { if got := attachmentComment(c.kind, c.src, c.file, c.target, false, 10, 16); got != c.crosslink { t.Errorf("crosslink comment:\n got %q\nwant %q", got, c.crosslink) } if c.refresh == "" { return } if got := attachmentComment(c.kind, c.src, c.file, c.target, true, 10, 16); got != c.refresh { t.Errorf("refreshed comment:\n got %q\nwant %q", got, c.refresh) } }) } } // TestCheckinWithoutCCardHasNullComment checks a check-in with no C card // gets a NULL event comment, as fossil stores its absent zComment. func TestCheckinWithoutCCardHasNullComment(t *testing.T) { r := setupTestRepo(t) parent, parentUUID, err := Checkin(r, CheckinOpts{ Files: []File{{Name: "a.txt", Content: []byte("a")}}, Comment: "first", User: "u", Time: time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC), }) if err != nil { t.Fatalf("Checkin: %v", err) } if parent <= 0 { t.Fatalf("parent rid = %d", parent) } body := "D 2024-01-15T11:00:00.000\nP " + parentUUID + "\nU u\n" art := []byte(fmt.Sprintf("%sZ %x\n", body, md5.Sum([]byte(body)))) rid, _, err := blob.Store(r.DB(), art) if err != nil { t.Fatalf("Store: %v", err) } if _, err := Crosslink(r); err != nil { t.Fatalf("Crosslink: %v", err) } var got string if err := r.DB().QueryRow("SELECT quote(comment) FROM event WHERE objid=? AND type='ci'", rid). Scan(&got); err != nil { t.Fatalf("event query: %v", err) } if got != "NULL" { t.Errorf("event comment %s, want NULL", got) } }