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: `..`. 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, ..", 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) }