package main
import (
"context"
"log"
"craftdesign.group/quarry/internal/index"
"craftdesign.group/quarry/internal/markup"
)
// closePage is how many new check-ins one read of the sweep takes; a
// larger push is walked a page at a time within the same pass.
const closePage = 50
// closeSweep closes the tickets new default-branch check-ins say they
// close (quarry#298), fossil-style: "closes #12" in a check-in comment
// does what it does in a landing's message. Runs after every fold, on
// the lease holder only.
//
// A durable watermark bounds what is new, so the history a repository
// arrives with — a mirror's, or anything folded before this sweep
// existed — is baselined at first sight, never read as a list of
// closes. A repository first seen EMPTY has no history: its watermark
// is recorded as "", the beginning, so its first push is all new. A per-check-in mark makes each close happen once across
// restarts and re-folds. A landing's own merge is left to the landing,
// whose accept closes what it named; a tracking mirror's tracker
// belongs to its upstream and is not written.
func (rg *registrar) closeSweep(name string, ix *index.Index) {
if rg.odb == nil || rg.deps == nil || ix == nil {
return
}
if rg.deps.Mirrors != nil {
if m, ok, err := rg.deps.Mirrors.MirrorFor(name); err != nil || (ok && m.Enabled) {
return
}
}
watch, seen := rg.odb.CloseWatch(name)
if !seen {
tip, err := ix.CheckinsAfter("", 1)
if err != nil {
return
}
if len(tip) == 1 {
_ = rg.odb.SetCloseWatch(name, tip[0].Hash)
} else if rg.storeEmpty(name) {
_ = rg.odb.SetCloseWatch(name, "")
}
return
}
// A watermark the index does not hold (a reindex part-way through)
// would read as "only the newest check-in is new", and that one may
// be history. Wait for the index to catch up instead.
if watch != "" {
if known, err := ix.BranchOf([]string{watch}); err != nil || known[watch] == "" {
return
}
}
branch := ix.DefaultBranch()
for {
var fresh []index.TimelineEntry
var err error
if watch == "" {
fresh, err = ix.OldestCheckins(closePage)
} else {
fresh, err = ix.CheckinsAfter(watch, closePage)
}
if err != nil || len(fresh) == 0 {
return
}
for _, c := range fresh {
if c.Branch == branch {
rg.closeFrom(name, ix, c)
}
}
watch = fresh[len(fresh)-1].Hash
if err := rg.odb.SetCloseWatch(name, watch); err != nil {
log.Printf("close: %s: watermark: %v", name, err)
return
}
if len(fresh) < closePage {
return
}
}
}
// storeEmpty asks the store, not the index, whether a repository holds
// no check-in. An index that has not folded yet is empty too, and
// reading that as "created empty" would turn its whole history into
// closes on the next fold.
func (rg *registrar) storeEmpty(name string) bool {
repo, ok := rg.deps.Repos.Get(name)
if !ok || repo.Zit == nil {
return false
}
leaves, err := repo.Zit.Leaves(context.Background())
return err == nil && len(leaves) == 0
}
// closeFrom closes what one default-branch check-in names, once.
func (rg *registrar) closeFrom(name string, ix *index.Index, c index.TimelineEntry) {
if len(markup.ClosingRefs(c.Comment)) == 0 {
return
}
if done, err := rg.odb.CheckinClosed(name, c.Hash); err != nil || done {
return
}
if merge, err := ix.IsLandingMerge(c.Hash); err != nil || merge {
return
}
rg.deps.CloseFromCheckin(name, c)
if err := rg.odb.MarkCheckinClosed(name, c.Hash); err != nil {
log.Printf("close: %s %.12s: mark: %v", name, c.Hash, err)
}
}