Forge.Paths, PreviewModule.Paths, KeptInputs2.Paths and buildstep.ModuleTree.Paths, each optional and read as absent by older readers; a kept or previewed tree that its named paths do not compute to is refused, so a recompute gives the merge's value.
188 lines
6.8 KiB
Go
188 lines
6.8 KiB
Go
package delivery
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
)
|
|
|
|
// The checker's other buckets (ADR 0484 clauses 2 and 3): owed, the acts it owes the forge, which the forge bridge
|
|
// performs once per revision of a key, and previews, what the fetch step answered for a head merged onto a trunk commit.
|
|
const (
|
|
// CheckerOwedBucket is the checker's bucket of owed acts, in the gate's owed shape: a status with the context
|
|
// StatusContextCheck for each head it judged. One part writes each key: the checker.
|
|
CheckerOwedBucket = CheckerModule + "_owed"
|
|
// CheckerOwedHistory is the revisions the checker's owed bucket keeps per key.
|
|
CheckerOwedHistory = 5
|
|
// PreviewsBucket is the checker's bucket of merge previews; the indexer reads it for the candidates' fingerprints.
|
|
PreviewsBucket = CheckerModule + "_previews"
|
|
// PreviewsHistory is the revisions the previews bucket keeps per key.
|
|
PreviewsHistory = 5
|
|
)
|
|
|
|
// CheckerOwedKey is the key, in CheckerOwedBucket, of the status the checker owes on a pull request at a head: the
|
|
// gate's OwedKey with the act `status=mesh/check`, the only act the checker owes.
|
|
func CheckerOwedKey(repository string, number int, head string) (string, error) {
|
|
return OwedKey(repository, number, head, OwedStatus+"="+StatusContextCheck)
|
|
}
|
|
|
|
// ParseCheckerOwedKey is the inverse of CheckerOwedKey; it refuses an owed key of any other act.
|
|
func ParseCheckerOwedKey(key string) (repository string, number int, head string, err error) {
|
|
repository, number, head, act, err := ParseOwedKey(key)
|
|
if err != nil {
|
|
return "", 0, "", err
|
|
}
|
|
if act != OwedStatus+"="+StatusContextCheck {
|
|
return "", 0, "", fmt.Errorf("delivery: %q is not a checker owed key: the checker owes only status=%s, not %q", key, StatusContextCheck, act)
|
|
}
|
|
return repository, number, head, nil
|
|
}
|
|
|
|
// EncodeCheckerOwed is o as JSON at Version; it refuses what EncodeOwed refuses and any act but a status with the
|
|
// context StatusContextCheck.
|
|
func EncodeCheckerOwed(o Owed) ([]byte, error) {
|
|
if err := checkCheckerOwed(o); err != nil {
|
|
return nil, err
|
|
}
|
|
return EncodeOwed(o)
|
|
}
|
|
|
|
func checkCheckerOwed(o Owed) error {
|
|
if o.Act != OwedStatus || o.Context != StatusContextCheck {
|
|
return fmt.Errorf("delivery: the checker owes only a status with the context %s, not the act %q with the context %q", StatusContextCheck, o.Act, o.Context)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DecodeCheckerOwed reads an owed act of the checker's bucket.
|
|
func DecodeCheckerOwed(data []byte) (Owed, error) {
|
|
o, err := DecodeOwed(data)
|
|
if err != nil {
|
|
return Owed{}, err
|
|
}
|
|
if err := checkCheckerOwed(o); err != nil {
|
|
return Owed{}, err
|
|
}
|
|
return o, nil
|
|
}
|
|
|
|
// PreviewKey is the key, in PreviewsBucket, of a head merged onto a trunk commit: `<repository>.<head>.<trunk>`.
|
|
func PreviewKey(repository, head, trunk string) (string, error) {
|
|
repo, err := escapeRepository(repository)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if err := checkName("head", head); err != nil {
|
|
return "", err
|
|
}
|
|
if err := checkName("trunk commit", trunk); err != nil {
|
|
return "", err
|
|
}
|
|
return repo + "." + head + "." + trunk, nil
|
|
}
|
|
|
|
// ParsePreviewKey is the inverse of PreviewKey.
|
|
func ParsePreviewKey(key string) (repository, head, trunk string, err error) {
|
|
parts := strings.Split(key, ".")
|
|
if len(parts) != 3 {
|
|
return "", "", "", fmt.Errorf("delivery: %q is not a preview key, <repository>.<head>.<trunk>", key)
|
|
}
|
|
repository, err = unescapeRepository(parts[0])
|
|
if err != nil {
|
|
return "", "", "", fmt.Errorf("delivery: %q is not a preview key: %w", key, err)
|
|
}
|
|
if _, err := PreviewKey(repository, parts[1], parts[2]); err != nil {
|
|
return "", "", "", fmt.Errorf("delivery: %q is not a preview key: %w", key, err)
|
|
}
|
|
return repository, parts[1], parts[2], nil
|
|
}
|
|
|
|
// PreviewModule is what the preview holds of one module directory: the tree sha of the directory (or its named tree), the digest of its
|
|
// manifest (the key in the manifests bucket) and the hex sha256 of its lock files by path relative to it.
|
|
type PreviewModule struct {
|
|
Tree string `json:"tree"`
|
|
Manifest string `json:"manifest"`
|
|
Locks map[string]string `json:"locks,omitempty"`
|
|
// Paths are, of a module whose every artifact names its paths, each named path's git object id at the merge; Tree
|
|
// is then their NamedTree (novox/hq ADR 0499 clause 2). Absent otherwise.
|
|
Paths map[string]string `json:"paths,omitempty"`
|
|
}
|
|
|
|
// Preview is the value of a preview key: the repository, the head and the trunk commit it was merged onto, the merge
|
|
// commit and, for each module directory of the merge, a PreviewModule. A head that does not merge is a preview too: it
|
|
// names the files in Conflicts and has no merge commit and no modules (issue 1445).
|
|
type Preview struct {
|
|
V int `json:"v"`
|
|
Repository string `json:"repository"`
|
|
Head string `json:"head"`
|
|
Trunk string `json:"trunk"`
|
|
Merge string `json:"merge,omitempty"`
|
|
Conflicts []string `json:"conflicts,omitempty"`
|
|
Modules map[string]PreviewModule `json:"modules,omitempty"`
|
|
}
|
|
|
|
func (p Preview) check() error {
|
|
if _, err := PreviewKey(p.Repository, p.Head, p.Trunk); err != nil {
|
|
return err
|
|
}
|
|
for _, f := range p.Conflicts {
|
|
if f == "" {
|
|
return fmt.Errorf("delivery: a preview names a conflicting file with an empty path")
|
|
}
|
|
}
|
|
if len(p.Conflicts) > 0 {
|
|
if p.Merge != "" || len(p.Modules) > 0 {
|
|
return fmt.Errorf("delivery: a preview of a head in conflict has no merge commit and no modules")
|
|
}
|
|
return nil
|
|
}
|
|
if p.Merge == "" {
|
|
return fmt.Errorf("delivery: a preview names no merge commit")
|
|
}
|
|
for dir, m := range p.Modules {
|
|
if dir == "" {
|
|
return fmt.Errorf("delivery: a preview names a module with an empty directory")
|
|
}
|
|
if m.Tree == "" {
|
|
return fmt.Errorf("delivery: the preview's module %q has no tree", dir)
|
|
}
|
|
if _, err := ManifestsKey(m.Manifest); err != nil {
|
|
return fmt.Errorf("delivery: the preview's module %q: %w", dir, err)
|
|
}
|
|
for path, hash := range m.Locks {
|
|
if path == "" || hash == "" {
|
|
return fmt.Errorf("delivery: the lock %q of the preview's module %q has an empty path or hash", path, dir)
|
|
}
|
|
}
|
|
if err := checkNamed(m.Tree, m.Manifest, m.Paths, fmt.Sprintf("the preview's module %q", dir)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// EncodePreview is p as JSON at Version.
|
|
func EncodePreview(p Preview) ([]byte, error) {
|
|
if err := p.check(); err != nil {
|
|
return nil, err
|
|
}
|
|
p.V = Version
|
|
return json.Marshal(p)
|
|
}
|
|
|
|
// DecodePreview reads a preview.
|
|
func DecodePreview(data []byte) (Preview, error) {
|
|
var p Preview
|
|
if err := decode(data, &p); err != nil {
|
|
return Preview{}, err
|
|
}
|
|
if err := p.check(); err != nil {
|
|
return Preview{}, err
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
// SamePreview says whether two previews are the same value.
|
|
func SamePreview(a, b Preview) bool { return reflect.DeepEqual(a, b) }
|