Skip to content
quarry Artifact [e74fde5665]
Login

Artifact [e74fde5665]

Artifact e74fde56659c5181b5ee5c86d86647f862aa6431525259c75ecba4e660f2f795:


package index

// strata.go — RFC 0006: a stack of dependent landings, derived and never
// stored. Follow target → branch across landings: a landing whose target
// is another landing's branch is a segment stacked on it; the one whose
// target is a plain branch is the base. The chain is read fresh from the
// summaries every time, so a segment retargeted by zit#144 after its base
// lands simply stops being one.

// MaxDrawnRungs bounds what the pages DRAW, not what a strata may be.
// GitLab stops drawing its stack control past twenty; past that a
// folded list and a map stop helping and start scrolling. The chain is
// still read and the walk still lands it; the page says how tall it is
// and points at the base.
const MaxDrawnRungs = 20

// StackSegment is one rung of a strata as the record page draws it.
type StackSegment struct {
	ID       string `json:"id"`
	Title    string `json:"title"`
	Branch   string `json:"branch"`
	Target   string `json:"target"`
	State    string `json:"state"`
	Position int    `json:"position"` // 1 is the base
	// Landable marks the one open segment whose target is not itself an
	// open landing's branch — the only rung accept can act on.
	Landable bool `json:"landable"`
	// CI is this rung's own verdict, filled by the reader from the tag.
	CI string `json:"ci,omitempty"`
	// Preflight is this rung's answer when the whole stack is probed
	// onto the bottom rung's target with the rungs below it merged
	// first (zit#211): clean, up-to-date, conflict, unrelated, or
	// not-reached above the rung that stops. Empty when not probed.
	Preflight          string   `json:"preflight,omitempty"`
	PreflightConflicts []string `json:"preflightConflicts,omitempty"`
}

// Strata is a chain, bottom-up.
type Strata struct {
	Base string `json:"base"`
	// Top is the highest rung: landing "the strata" means landing up
	// to it.
	Top string `json:"top"`
	// CI is the strata's verdict: the top rung's. Its tip contains every
	// rung below, so a run there validates the stack as a unit — the
	// reading Graphite and Aviator settled on. A lower rung's own
	// verdict still says what it says; this is the one to land on.
	CI       string         `json:"ci,omitempty"`
	Segments []StackSegment `json:"segments"`
	// Broken names why the chain cannot be walked: a cycle, or a base
	// that was rejected so everything above it is stacked on work
	// nobody will land (RFC 0006 §An abandoned base is flagged).
	Broken string `json:"broken,omitempty"`
	// Preflight says what walking the strata would do, known before
	// anything is accepted (1f8c4148): "lands" when every rung merges
	// onto the target with the ones below it, "stops" when one does not.
	// Empty when the stack was not probed. StopsAt is the position of
	// the rung that would stop the walk; LandTo and LandCount name how
	// far a walk would get — the rung to land up to, and how many.
	Preflight string `json:"preflight,omitempty"`
	StopsAt   int    `json:"stopsAt,omitempty"`
	LandTo    string `json:"landTo,omitempty"`
	LandCount int    `json:"landCount,omitempty"`
}

// AttachStrata computes, for every OPEN landing in ls, which stack it
// belongs to and where: Stack (the base's id), Position and Depth. The
// input is every landing the caller knows about, whatever its state,
// because a segment's target may be the branch of a landing that was
// rejected — and that is a fact the chain must report, not lose.
//
// Superseded landings take no part: the current landing on a branch is
// the one the chain runs through.
func AttachStrata(ls []LandingSummary) {
	byBranch := map[string]*LandingSummary{}
	for i := range ls {
		l := &ls[i]
		if l.SupersededBy != "" {
			continue
		}
		if cur, ok := byBranch[l.Branch]; !ok || l.State == "open" && cur.State != "open" {
			byBranch[l.Branch] = l
		}
	}
	for i := range ls {
		l := &ls[i]
		l.Stack, l.Position, l.Depth, l.Broken = "", 0, 0, ""
		if l.State != "open" || l.SupersededBy != "" {
			continue
		}
		// Walk down to the base, counting.
		seen := map[string]bool{l.ID: true}
		base, pos := l, 1
		for {
			below, ok := byBranch[base.Target]
			if !ok {
				break // a plain branch: this is the base
			}
			if seen[below.ID] {
				l.Broken = "cycle"
				break
			}
			if below.State != "open" {
				if below.State == "rejected" {
					l.Broken = "base rejected"
				}
				// Accepted and not yet retargeted: the rung below is
				// done and this one is next. It counts as the base of
				// what remains.
				break
			}
			seen[below.ID] = true
			base, pos = below, pos+1
		}
		// A landing whose base was REJECTED is not standalone, and this
		// is where it used to become one.
		//
		// The walk breaks at a rejected rung with pos still 1, and
		// nothing is stacked above a top segment — so this `continue`
		// fired, l.Stack stayed empty, and StrataOf then returned
		// Strata{} for it. The record page lost the whole chain:
		// {base:"", top:"", segments:null}, Mergeable flipped from
		// "stacked" to clean, and a live Accept appeared on work whose
		// base is gone (ticket b3aad27c). RFC 0006 says the opposite —
		// "an abandoned base is flagged: the record page says the base
		// was rejected and names it".
		//
		// Broken is already set by then. Keeping the membership is what
		// lets anything downstream read it: the segment becomes its own
		// base, carrying the reason it is alone.
		if pos == 1 && l.Broken == "" && !isStackedOn(byBranch, l) {
			continue // standalone: no stack at all
		}
		l.Stack, l.Position = base.ID, pos
	}
	// Depth is the tallest position sharing a base.
	depth := map[string]int{}
	for i := range ls {
		if ls[i].Stack != "" && ls[i].Position > depth[ls[i].Stack] {
			depth[ls[i].Stack] = ls[i].Position
		}
	}
	for i := range ls {
		if ls[i].Stack != "" {
			ls[i].Depth = depth[ls[i].Stack]
		}
	}
}

// isStackedOn reports whether some open landing targets l's branch.
func isStackedOn(byBranch map[string]*LandingSummary, l *LandingSummary) bool {
	for _, other := range byBranch {
		if other.State == "open" && other.Target == l.Branch && other.ID != l.ID {
			return true
		}
	}
	return false
}

// StrataOf assembles the chain a landing belongs to, bottom-up, for the
// record page's map. Empty for a standalone landing. ls must already
// carry AttachStrata's reading.
func StrataOf(ls []LandingSummary, id string) Strata {
	var me *LandingSummary
	for i := range ls {
		if ls[i].ID == id {
			me = &ls[i]
		}
	}
	if me == nil || me.Stack == "" {
		return Strata{}
	}
	out := Strata{Base: me.Stack, Broken: me.Broken}
	for _, l := range ls {
		if l.Stack != me.Stack {
			continue
		}
		out.Segments = append(out.Segments, StackSegment{
			ID: l.ID, Title: l.Title, Branch: l.Branch, Target: l.Target,
			State: l.State, Position: l.Position, Landable: l.Position == 1 && l.Broken == "",
		})
	}
	// Bottom-up, by position.
	for i := 1; i < len(out.Segments); i++ {
		for j := i; j > 0 && out.Segments[j].Position < out.Segments[j-1].Position; j-- {
			out.Segments[j], out.Segments[j-1] = out.Segments[j-1], out.Segments[j]
		}
	}
	if n := len(out.Segments); n > 0 {
		out.Top = out.Segments[n-1].ID
	}
	// A chain whose base was rejected is kept together under its lowest
	// OPEN rung (that is what keeps it unlandable), so me.Stack names a
	// landing of the chain itself. Reported as-is, Base then named the
	// segment as its own base while the refusal beside it named the
	// rejected one — two fields answering one question, disagreeing.
	// RFC 0006: "the record page says the base was rejected and names
	// it". So Base names the rejected landing: the one the bottom rung
	// targets.
	if out.Broken == "base rejected" && len(out.Segments) > 0 {
		bottom := out.Segments[0]
		for _, l := range ls {
			if l.Branch == bottom.Target && l.State == "rejected" {
				out.Base = l.ID
			}
		}
	}
	return out
}

// Decisions counts what awaits a decision: every open landing that is
// not stacked on another open one. A stack of five is one decision —
// the base's — because nothing above it can be acted on until it is.
func Decisions(ls []LandingSummary) int {
	n := 0
	for _, l := range ls {
		if l.State == "open" && l.SupersededBy == "" && (l.Stack == "" || l.Position == 1) {
			n++
		}
	}
	return n
}