Skip to content
go-libfossil Artifact [30677f8415]
Login

Artifact [30677f8415]

Artifact 30677f84157add15d8aea2a14f868a7bc9335474cec193d2f643b20bb61d8332:


package libfossil

import (
	"fmt"
	"sort"
	"strings"

	"github.com/danmestas/go-libfossil/db"
	"github.com/danmestas/go-libfossil/internal/content"
)

// Explicit widening is lasting store intent, including when the subsequent
// fetch fails. Rebase only our policy field, never accept a fresh stale snapshot.
func (c *Checkout) requiredStorePolicy(next *Cone, state string) (string, error) {
	if err := c.checkRequiredState(state); err != nil {
		return "", err
	}
	lines := strings.Split(state, "\n")
	if len(lines) < 2 {
		panic("libfossil.requiredStorePolicy: incomplete state")
	}
	err := c.inner.Repo().WithTx(func(tx *db.Tx) error {
		stored, err := content.StoreCone(tx)
		if err != nil {
			return err
		}
		digest := ""
		if stored != nil {
			digest = stored.Digest()
		}
		if digest != lines[1] {
			return fmt.Errorf("libfossil: store policy changed before widening")
		}
		widened, err := requiredStoreUnion(wrapCone(stored), next)
		if err != nil {
			return err
		}
		if err := content.SetStoreCone(tx, unwrapCone(widened)); err != nil {
			return err
		}
		lines[1] = ""
		if widened != nil {
			lines[1] = widened.Digest()
		}
		return nil
	})
	if err != nil {
		return "", err
	}
	state = strings.Join(lines, "\n")
	return state, c.checkRequiredState(state)
}

// Coverage changes only at member boundaries. Taking OR there preserves both
// cones, including reinclusions, without letting one cone's cuts narrow another.
func requiredStoreUnion(left, right *Cone) (*Cone, error) {
	if left == nil || right == nil {
		return nil, nil
	}
	members := make(map[string]bool)
	for _, c := range []*Cone{left, right} {
		for _, name := range append(c.Include(), c.Exclude()...) {
			members[name] = true
		}
	}
	paths := make([]string, 0, len(members))
	for name := range members {
		paths = append(paths, name)
	}
	sort.Strings(paths)
	var include, exclude []string
	decisions := make(map[string]bool)
	for _, name := range paths {
		covered := left.Covers(name) || right.Covers(name)
		parentCovered, parentLength := false, 0
		for parent, decision := range decisions {
			if len(parent) > parentLength && strings.HasPrefix(name, parent+"/") {
				parentCovered, parentLength = decision, len(parent)
			}
		}
		if covered == parentCovered {
			continue
		}
		decisions[name] = covered
		if covered {
			include = append(include, name)
		} else {
			exclude = append(exclude, name)
		}
	}
	return NewCone(include, exclude)
}