Artifact c02ecbea8698a7df2d0a509dfbe14451fca83f387bd7e7d7b64a59756805110b:
- File
internal/merge/fork.go
— part of check-in
[f5744359d3]
at
2026-04-19 16:30:54
on branch trunk
— Initial open source release
libfossil is a pure-Go implementation of the Fossil SCM, reading and writing the same .fossil SQLite repository format as upstream Fossil.
This is the first public release of code that previously lived in a private repository. The module path is github.com/danmestas/libfossil and the binary is named libfossil to avoid $PATH collisions with upstream fossil.
Included: - Repo lifecycle (create, clone), checkout/checkin, timeline, merge, diff, annotate, shun, tag, branch, bisect, stash, undo/redo - Sync-over-HTTP protocol (client and handler) - Two SQLite drivers: modernc (pure Go, default) and ncruces (wasm-capable) - Observer interfaces (SyncObserver, CheckoutObserver) with an optional OpenTelemetry implementation in observer/otel (separate submodule) - Deterministic simulation testing harness with BUGGIFY fault injection - Kong-based embeddable CLI under cli/ and a standalone binary under cmd/libfossil/ (user: [email protected], size: 2902) [annotate] [blame] [check-ins using]
package merge import "github.com/danmestas/libfossil/internal/repo" func init() { Register(&ConflictFork{}) } // ConflictFork preserves all divergent versions without merging. // Used for offline-first scenarios where conflicts accumulate. // Stores references in a conflict table (Fossil-idiomatic schema). type ConflictFork struct{} func (c *ConflictFork) Name() string { return "conflict-fork" } func (c *ConflictFork) Merge(base, local, remote []byte) (*Result, error) { // Don't merge — preserve all versions. The CLI writes to the conflict table. return &Result{ Content: local, Clean: false, Conflicts: []Conflict{{ Local: local, Remote: remote, Base: base, }}, }, nil } // EnsureConflictTable creates the conflict table if it doesn't exist. // Follows Fossil conventions: REFERENCES blob for rids, julianday REAL for timestamps. func EnsureConflictTable(r *repo.Repo) error { if r == nil { panic("merge.EnsureConflictTable: r must not be nil") } _, err := r.DB().Exec(`CREATE TABLE IF NOT EXISTS conflict( cid INTEGER PRIMARY KEY, filename TEXT NOT NULL, base_rid INTEGER REFERENCES blob, local_rid INTEGER REFERENCES blob, remote_rid INTEGER REFERENCES blob, mtime REAL NOT NULL )`) return err } // RecordConflictFork inserts a conflict-fork entry. func RecordConflictFork(r *repo.Repo, filename string, baseRID, localRID, remoteRID int64) error { if r == nil { panic("merge.RecordConflictFork: r must not be nil") } if filename == "" { panic("merge.RecordConflictFork: filename must not be empty") } _, err := r.DB().Exec( "INSERT INTO conflict(filename, base_rid, local_rid, remote_rid, mtime) VALUES(?, ?, ?, ?, julianday('now'))", filename, baseRID, localRID, remoteRID, ) return err } // ResolveConflictFork deletes a resolved entry (Fossil convention: delete, don't flag). func ResolveConflictFork(r *repo.Repo, filename string) error { if r == nil { panic("merge.ResolveConflictFork: r must not be nil") } if filename == "" { panic("merge.ResolveConflictFork: filename must not be empty") } _, err := r.DB().Exec("DELETE FROM conflict WHERE filename=?", filename) return err } // ListConflictForks returns all unresolved conflict-fork entries. func ListConflictForks(r *repo.Repo) ([]string, error) { if r == nil { panic("merge.ListConflictForks: r must not be nil") } // Check if conflict table exists. var count int err := r.DB().QueryRow("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='conflict'").Scan(&count) if err != nil || count == 0 { return nil, nil } rows, err := r.DB().Query("SELECT filename FROM conflict ORDER BY mtime DESC") if err != nil { return nil, err } defer rows.Close() var names []string for rows.Next() { var name string if err := rows.Scan(&name); err != nil { continue } names = append(names, name) } return names, rows.Err() }