Skip to content
quarry Artifact [002950906a]
Login

Artifact [002950906a]

Artifact 002950906af74399789d1fb5c80a14c5b470c2652d79e90d2d15a93c26549630:


package web

import "html/template"

// A stack landed in one press of Land (RFC 0006 strata) leaves a run of
// merges on the mainline, one per rung, each merging a branch that was
// forked from the branch merged just below it. The timeline draws that
// run as ONE card beside its merge nodes, tinted in the stack's bottom
// branch's colour: the press is one decision, and a card per merge said
// it several times over. The graph is not changed: drawing the stack's
// branches as one lane meant dropping the older merges' lines, and the
// graph stopped saying what merged where (2026-10-29).
//
// It is read from the DAG and the landing decorations alone. The
// landing records cannot say it after the fact: a stack's upper rungs
// are retargeted onto the mainline as the rungs below them land.

// tlStack is a stack card's content, on the newest of its merge rows.
type tlStack struct {
	Lines    []tlStackLine // the landings, newest first
	Approver string
	CI       template.HTML // the newest merge's verdict: the mainline after the press
}

// Shown is what the card lists at once; Rest opens in place.
func (s *tlStack) Shown() []tlStackLine { return s.Lines[:min(stackShown, len(s.Lines))] }
func (s *tlStack) Rest() []tlStackLine  { return s.Lines[min(stackShown, len(s.Lines)):] }

// stackShown is how many landings a stack card lists before "N more":
// a stack of ten would otherwise push the rest of the day off a phone.
const stackShown = 3

type tlStackLine struct {
	Landing string
	Number  int
	Title   string
	Stat    template.HTML
	Note    template.HTML // the merge's own note, such as which tip its checks tested
}

// stackLanes finds the landed stacks among entries (newest first): each
// one's merge hashes, newest first, and its bottom branch, whose colour
// tints the card.
func stackLanes(defaultBranch string, entries []timelineEntry) []landedStack {
	branchOf := map[string]string{}
	root := map[string]timelineEntry{} // a branch's oldest check-in on the page
	for _, e := range entries {
		if flatKind(e.Kind) {
			continue
		}
		branchOf[e.Hash] = e.Branch
		root[e.Branch] = e
	}
	// base is the branch a branch was forked from, when its fork is on
	// the page.
	base := func(b string) string {
		r, ok := root[b]
		if !ok || len(r.Parents) == 0 {
			return ""
		}
		return branchOf[r.Parents[0]]
	}
	type landed struct {
		at     int
		hash   string
		branch string
		by     string
	}
	var merges []landed
	for i, e := range entries {
		if flatKind(e.Kind) || e.Branch != defaultBranch || e.Landing == "" || len(e.Parents) < 2 {
			continue
		}
		b := branchOf[e.Parents[1]]
		if b == "" || b == defaultBranch {
			continue
		}
		merges = append(merges, landed{at: i, hash: e.Hash, branch: b, by: e.LandingApprover})
	}
	var stacks []landedStack
	for i := 0; i < len(merges); {
		j := i
		// One press: the next older row is the next merge, decided by
		// the same person, of the branch this one was stacked on.
		for j+1 < len(merges) && merges[j+1].at == merges[j].at+1 &&
			merges[j+1].by == merges[j].by && base(merges[j].branch) == merges[j+1].branch {
			j++
		}
		if j > i {
			var hashes []string
			bottom := merges[j].branch
			for k := i; k <= j; k++ {
				hashes = append(hashes, merges[k].hash)
			}
			stacks = append(stacks, landedStack{hashes: hashes, lane: bottom})
		}
		i = j + 1
	}
	return stacks
}

// landedStack is one press's merges, newest first, and its lane.
type landedStack struct {
	hashes []string
	lane   string
}

// foldStacks turns each stack's merge rows into one card: the newest
// row carries it and spans the rest, whose graph cells stay. A stack
// split by a day boundary keeps a card per day.
func foldStacks(pal *palette, stacks []landedStack, days []tlDay, refs []rowRef) {
	at := map[string]rowRef{}
	for _, r := range refs {
		if r.ci {
			at[r.hash] = r
		}
	}
	for _, stack := range stacks {
		var run []rowRef
		flush := func() {
			if len(run) < 2 {
				run = nil
				return
			}
			head := &days[run[0].d].Rows[run[0].r]
			st := &tlStack{Approver: head.LandingApprover, CI: head.CI}
			for _, r := range run {
				row := &days[r.d].Rows[r.r]
				var note template.HTML
				if len(row.Paras) > 0 {
					note = row.Paras[0].Text
				}
				st.Lines = append(st.Lines, tlStackLine{Landing: row.Landing, Number: row.LandingNumber,
					Title: row.LandingTitle, Stat: row.Stat, Note: note})
				row.Skip = true
			}
			head.Skip, head.Span, head.Stack = false, len(run), st
			head.StackTint = pal.tint(stack.lane)
			run = nil
		}
		for _, h := range stack.hashes {
			r, ok := at[h]
			if !ok {
				continue
			}
			if n := len(run); n > 0 && (run[n-1].d != r.d || run[n-1].r+1 != r.r) {
				flush()
			}
			run = append(run, r)
		}
		flush()
	}
}