B1g-5: the catalog's revision verb answers a candidate's group alone and a branch's source revision

Builds: design 47 G A-205, A-222
This commit is contained in:
2026-11-12 06:19:07 +01:00
parent ef28ac17f4
commit bff7b402e7
2 changed files with 101 additions and 0 deletions
@@ -63,4 +63,36 @@ func TestACatalogRevisionIsKeptPerWatchedBranch(t *testing.T) {
if a := answer("beta", 0); !a.NoneYet {
t.Errorf("a branch with no revision answered %+v, want none-yet", a)
}
// A-205: `source` answers the branch's revision taken in from that source revision; a candidate's group alone answers its
// newest revision, whatever its heads.
bySource := func(branch string, source int64) (meshcatalog.RevisionAnswer, error) {
return Revision(ctx, ix.Revisions, "indexer", "reader", meshcatalog.RevisionInput{Branch: branch, Source: source})
}
if a, err := bySource("main", 5); err != nil || a.Revision.Revision != 1 || a.Entries[0].Commit != "c1" {
t.Errorf("main's source 5 answered %+v (%v)", a.Revision, err)
}
if a, err := bySource("feature/x=2ey", 7); err != nil || a.Revision.Revision != 1 || a.Entries[0].Commit != "c2" {
t.Errorf("the branch's source 7 answered %+v (%v), want its revision 1 at c2", a.Revision, err)
}
if a, err := bySource("feature/x=2ey", 8); err != nil || a.Revision.Revision != 2 {
t.Errorf("the branch's source 8 answered %+v (%v)", a.Revision, err)
}
if _, err := bySource("feature/x=2ey", 9); err == nil {
t.Error("a source no revision was taken in from was answered")
}
for i, heads := range []string{"h1", "h2"} {
rev, objects, err := CatalogRevisionOf(meshcatalog.CatalogRevision{Revision: int64(3 + i), Kind: "candidate", Group: "run-ab", Heads: heads}, tip("c9"))
if err != nil {
t.Fatal(err)
}
if err := KeepRevision(ctx, ix.Revisions, rev, objects); err != nil {
t.Fatal(err)
}
}
if a, err := Revision(ctx, ix.Revisions, "indexer", "reader", meshcatalog.RevisionInput{Group: "run-ab"}); err != nil || a.Revision.Revision != 4 || a.Revision.Heads != "h2" || a.Entries[0].Commit != "c9" {
t.Errorf("a group alone answered %+v (%v), want its newest revision 4", a.Revision, err)
}
if _, err := Revision(ctx, ix.Revisions, "indexer", "reader", meshcatalog.RevisionInput{Group: "run-none"}); err == nil {
t.Error("a candidate never kept was answered")
}
}
@@ -71,11 +71,21 @@ func Revision(ctx context.Context, o Objects, holder, caller string, in meshcata
}
name := RevisionObject(newest)
switch {
case in.Group != "" && in.Heads == "":
// A candidate's group alone answers its newest revision, whatever its heads (A-205).
if name, err = newestCandidate(ctx, o, in.Group); err != nil {
return out, err
}
case in.Group != "" || in.Heads != "":
if in.Group == "" || in.Heads == "" {
return out, fmt.Errorf("a candidate's revision is named by its group and its heads, both")
}
name = RevisionObject(meshcatalog.CatalogRevision{Kind: "candidate", Group: in.Group, Heads: in.Heads})
case in.Source != 0:
// The branch's revision taken in from that source revision (A-205).
if name, err = revisionOfSource(ctx, o, branch, in.Source); err != nil {
return out, err
}
case in.Revision != 0 && in.Revision != newest.Revision:
name = RevisionObject(meshcatalog.CatalogRevision{Branch: branch, Revision: in.Revision})
}
@@ -121,3 +131,62 @@ func RevisionTool(o Objects, holder string) stdio.Tool {
return Revision(context.Background(), o, holder, stdio.CallerOf(args), in)
}}
}
// newestCandidate is the object of the newest revision a candidate group keeps, over whatever heads it was kept at (A-205).
func newestCandidate(ctx context.Context, o Objects, group string) (string, error) {
names, err := o.Names(ctx)
if err != nil {
return "", fmt.Errorf("the candidate revisions cannot be listed: %w", err)
}
best, newest := "", int64(-1)
for _, n := range names {
if !strings.HasPrefix(n, "candidate."+group+".") {
continue
}
rev, _, err := ReadRevision(ctx, o, n)
if err != nil {
return "", err
}
if rev.Group == group && rev.Revision > newest {
best, newest = n, rev.Revision
}
}
if best == "" {
return "", fmt.Errorf("the catalog keeps no candidate of the group %s", group)
}
return best, nil
}
// revisionOfSource is the object of the newest revision of branch taken in from the source revision source (A-205); a source
// none was taken in from is an error.
func revisionOfSource(ctx context.Context, o Objects, branch string, source int64) (string, error) {
names, err := o.Names(ctx)
if err != nil {
return "", fmt.Errorf("the catalog graph revisions cannot be listed: %w", err)
}
number := TrunkRevisionOf
if !isTrunkBranch(branch) {
number = func(n string) (int64, bool) { return BranchRevisionOf(branch, n) }
}
var found []int64
for _, n := range names {
if r, ok := number(n); ok {
found = append(found, r)
}
}
slices.SortFunc(found, func(a, b int64) int { return int(b - a) })
for _, r := range found {
name := RevisionObject(meshcatalog.CatalogRevision{Branch: branch, Revision: r})
rev, _, err := ReadRevision(ctx, o, name)
if err != nil {
return "", err
}
if rev.Source == source {
return name, nil
}
if rev.Source < source {
break
}
}
return "", fmt.Errorf("no revision of %q was taken in from the source revision %d", branch, source)
}