Artifact d4527cd980e6be247b44dd6817abccbe434d7f90fd88441575d685675e85eb35:
- File
internal/tag/event.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: 4218) [annotate] [blame] [check-ins using] [more...]
package tag import ( "fmt" "sort" "strings" "github.com/danmestas/go-libfossil/db" "github.com/danmestas/go-libfossil/internal/deck" "github.com/danmestas/go-libfossil/internal/fsltype" ) // InsertControlEvent writes the event row fossil's manifest_crosslink // writes for control artifact d stored at rid: type 'g', at the D-card // time, with a comment describing every tag change d makes. func InsertControlEvent(tx *db.Tx, rid fsltype.FslID, d *deck.Deck) error { if tx == nil { panic("tag.InsertControlEvent: tx must not be nil") } if rid <= 0 { panic("tag.InsertControlEvent: rid must be positive") } human, url := fsltype.HashDigits(tx) if _, err := tx.Exec( "REPLACE INTO event(type, mtime, objid, user, comment) VALUES('g', ?, ?, ?, ?)", fsltype.TimeToTextJulian(d.D), rid, d.U, controlComment(d, human, url), ); err != nil { return fmt.Errorf("tag.InsertControlEvent: %w", err) } return nil } // controlComment builds the comment of a control artifact's event row // as manifest_crosslink does: its tag changes ordered by target hash, then by // full tag name sign included, each target introduced by an "Edit" link. func controlComment(d *deck.Deck, human, url int) string { tags := make([]deck.TagCard, 0, len(d.T)) for _, tc := range d.T { if tc.UUID != "*" { tags = append(tags, tc) } } sort.Slice(tags, func(i, j int) bool { if tags[i].UUID != tags[j].UUID { return tags[i].UUID < tags[j].UUID } return string(tags[i].Type)+tags[i].Name < string(tags[j].Type)+tags[j].Name }) var b strings.Builder branchMove := false for i, tc := range tags { if i == 0 || tc.UUID != tags[i-1].UUID { fmt.Fprintf(&b, " Edit [%s|%s]:", fsltype.HashPrefix(tc.UUID, url), fsltype.HashPrefix(tc.UUID, human)) branchMove = false } branchMove = appendTagChange(&b, tc, url, branchMove) } // fossil drops the first byte, the space before the first phrase. return strings.TrimPrefix(b.String(), " ") } // tagChangeVerb names a change to a tag a control comment has no phrase for. var tagChangeVerb = map[deck.TagType]string{ deck.TagCancel: "Cancel", deck.TagSingleton: "Add", deck.TagPropagating: "Add propagating", } // appendTagChange appends one tag change's phrase to a control comment and // reports whether the target has moved to a new branch, which silences the // sym- tag changes that accompany the move. func appendTagChange(b *strings.Builder, tc deck.TagCard, url int, branchMove bool) bool { name := string(tc.Type) + tc.Name value := fsltype.HTMLize(tc.Value) switch { case name == "*branch": fmt.Fprintf(b, " Move to branch [/timeline?r=%s&nd&dp=%s&unhide | %s].", fsltype.HTTPize(tc.Value), fsltype.HashPrefix(tc.UUID, url), value) return true case name == "*bgcolor": fmt.Fprintf(b, " Change branch background color to \"%s\".", value) return branchMove case name == "+bgcolor": fmt.Fprintf(b, " Change background color to \"%s\".", value) return branchMove case name == "+comment": b.WriteString(" Edit check-in comment.") return branchMove case name == "+user": fmt.Fprintf(b, " Change user to \"%s\".", value) return branchMove case name == "+date": fmt.Fprintf(b, " Timestamp %s.", value) return branchMove case name == "-bgcolor": b.WriteString(" Cancel background color") case strings.HasPrefix(name, "-sym-") && branchMove, strings.HasPrefix(name, "*sym-") && branchMove: return branchMove case strings.HasPrefix(name, "-sym-"): fmt.Fprintf(b, " Cancel tag \"%s\"", fsltype.HTMLize(name[5:])) case strings.HasPrefix(name, "*sym-"): fmt.Fprintf(b, " Add propagating tag \"%s\"", fsltype.HTMLize(name[5:])) case strings.HasPrefix(name, "+sym-"): fmt.Fprintf(b, " Add tag \"%s\"", fsltype.HTMLize(name[5:])) case name == "+closed": b.WriteString(` Mark "Closed"`) case name == "-closed": b.WriteString(` Remove the "Closed" mark`) default: fmt.Fprintf(b, " %s \"%s\"", tagChangeVerb[tc.Type], fsltype.HTMLize(tc.Name)) if tc.Value != "" { fmt.Fprintf(b, " with value \"%s\".", value) } else { b.WriteString(".") } return branchMove } if tc.Value != "" { fmt.Fprintf(b, " with note \"%s\".", value) } else { b.WriteString(".") } return branchMove }