Read a module whole while a plan has overtaken its build source, and plan every view alike (hq ADR 0267, review)
A failed build, or a plan closed before reaching a module, left the closure its last good build said, and a fix-forward to a newly imported package would have moved nothing. A missed merge moving only a module that packages the repository was never caught up, and an older merge read as history for it through a look that was not its own. The gate, a pull request's check, the what-if and a delivery's order now read the same view the merge handler does.
This commit is contained in:
@@ -314,7 +314,7 @@ func (f following) SourceMoved(ctx context.Context, m link.SourceMoved) error {
|
||||
if err != nil {
|
||||
return notNow(err)
|
||||
}
|
||||
read, err := readForAMerge(ctx, inv)
|
||||
read, err := readForPlanning(ctx, inv)
|
||||
if err != nil {
|
||||
return notNow(err)
|
||||
}
|
||||
@@ -347,12 +347,6 @@ func (f following) SourceMoved(ctx context.Context, m link.SourceMoved) error {
|
||||
m.Owner, m.Repo, m.Base, m.Commit)
|
||||
return nil
|
||||
}
|
||||
// The same judgement for the packaging kind, against the newest look at that repository by
|
||||
// anything built from it: they keep no record of it themselves, and a replayed old merge should
|
||||
// not rebuild them either.
|
||||
if isHistory(m.MergedAt, lastLookAt(entries, m)) {
|
||||
packaging = nil
|
||||
}
|
||||
// Said, never silent (novox/hq 04-ISSUES/215): a module built from this repository that follows
|
||||
// another branch is not part of this merge, and whoever is waiting for its change should read why.
|
||||
for _, e := range entries {
|
||||
@@ -571,25 +565,43 @@ func mergeCandidates(m link.SourceMoved, entries []inventory.Entry,
|
||||
}
|
||||
from = append(from, e)
|
||||
case readsFrom(read[e.Manifest.Module], m):
|
||||
// **A merge older than the module's last look is history for it** (novox/hq ADR 0267): a
|
||||
// build or plan of it after the merge already read the repository with the merge in it. Per
|
||||
// module, since a merge that moved only the module built from the repository says nothing
|
||||
// about the ones packaging it.
|
||||
if isHistory(m.MergedAt, lookedOf(read[e.Manifest.Module])) {
|
||||
continue
|
||||
}
|
||||
packaging = append(packaging, e)
|
||||
}
|
||||
}
|
||||
return from, packaging, already
|
||||
}
|
||||
|
||||
// wouldMove is the modules built from the merged repository that acting on this merge would mark as
|
||||
// moved and rebuild — SourceMoved's judgement, made without acting (novox/hq issue 266). Empty for a
|
||||
// merge already acted on: acting marks each of them as looked at, so the merge then reads as history.
|
||||
// lookedOf is when a module packaging another repository was last looked at, as readForPlanning says.
|
||||
func lookedOf(read []inventory.ReadRepository) time.Time {
|
||||
var at time.Time
|
||||
for _, r := range read {
|
||||
if r.Looked.After(at) {
|
||||
at = r.Looked
|
||||
}
|
||||
}
|
||||
return at
|
||||
}
|
||||
|
||||
// wouldMove is the modules acting on this merge would move and rebuild — SourceMoved's judgement, made
|
||||
// without acting (novox/hq issue 266). Empty for a merge already acted on: acting marks each module built
|
||||
// from the repository as looked at, so the merge then reads as history for it.
|
||||
//
|
||||
// **Only the modules built from it, never the ones that merely package source from it.** Acting
|
||||
// records nothing about those, so a merge acted on would go on reading as unacted for them, and be
|
||||
// acted on again on every look. A merge that moves both is caught by the first kind, and acting on it
|
||||
// rebuilds the second as well.
|
||||
// **The ones packaging source from it too** (novox/hq ADR 0267): with a module moved only by the files of
|
||||
// its build source, a merge can move a packaging module and nothing built from the repository, and a missed
|
||||
// one of those was never acted on. A packaging module's look is its newest build or plan (lookedAt), so a
|
||||
// merge acted on for it reads as history once its plan is made.
|
||||
func wouldMove(m link.SourceMoved, entries []inventory.Entry,
|
||||
read map[string][]inventory.ReadRepository) []inventory.Entry {
|
||||
from, _, _ := mergeCandidates(m, entries, read)
|
||||
from, packaging, _ := mergeCandidates(m, entries, read)
|
||||
touched, _ := splitDeleted(whatTheMergeTouched(from, entries, m, read), m)
|
||||
return touched
|
||||
return append(touched, packaging...)
|
||||
}
|
||||
|
||||
// splitDeleted parts the modules a merge touched into those it changed and those whose manifest it
|
||||
@@ -721,75 +733,92 @@ func readsFile(e inventory.Entry, read []inventory.ReadRepository, p string) boo
|
||||
return strings.Trim(e.Source.Path, "/") == "" || inside(p, e.Source.Path)
|
||||
}
|
||||
|
||||
// pendingIn is the modules an open plan has yet to build (novox/hq ADR 0267): what such a module was built
|
||||
// from is about to change, so the build source its last build said is not the one a merge now meets — an
|
||||
// earlier merge that added an import to it would otherwise hide a change to what that import names. Each
|
||||
// is read whole, as before, and planned again with the merge; the newer plan supersedes the older.
|
||||
func pendingIn(plans []inventory.Plan) map[string]bool {
|
||||
pending := map[string]bool{}
|
||||
for _, p := range plans {
|
||||
if !p.Open() {
|
||||
continue
|
||||
// staleIn is the modules whose recorded build source a plan has overtaken (novox/hq ADR 0267): a plan still
|
||||
// working that has yet to build one, or a plan made after that build which never built it — failed, stopped
|
||||
// or superseded. What such a module is built from is changing, or changed without a build to say so: a merge
|
||||
// that added an import to it, and a later one changing only what that import names, would otherwise move
|
||||
// nothing. Each is read whole, as before, until a build of it works again.
|
||||
func staleIn(read map[string][]inventory.ReadRepository, plans []inventory.Plan) map[string]bool {
|
||||
stale := map[string]bool{}
|
||||
for name, rs := range read {
|
||||
var since time.Time
|
||||
for _, r := range rs {
|
||||
if r.Built.After(since) {
|
||||
since = r.Built
|
||||
}
|
||||
}
|
||||
for name, s := range p.Modules {
|
||||
if s == nil || (s.State != "built" && s.State != planDeleted) {
|
||||
pending[name] = true
|
||||
for _, p := range plans {
|
||||
s, in := p.Modules[name]
|
||||
if !in || (s != nil && (s.State == "built" || s.State == planDeleted)) {
|
||||
continue
|
||||
}
|
||||
if p.Open() || p.Created.After(since) {
|
||||
stale[name] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return pending
|
||||
return stale
|
||||
}
|
||||
|
||||
// readForAMerge is what each module's build read, as a merge acting now maps its files onto: the build
|
||||
// sources the newest trunk builds said, but for the modules an open plan has yet to build (pendingIn).
|
||||
func readForAMerge(ctx context.Context, inv *inventory.Inventory) (map[string][]inventory.ReadRepository, error) {
|
||||
// lookedAt is when a merge was last acted on for a module that packages another repository's source: its
|
||||
// newest build, or the newest plan that held it, whichever is later. A build asked after a merge clones that
|
||||
// repository with the merge in it, so an older merge is history for it.
|
||||
func lookedAt(name string, read []inventory.ReadRepository, plans []inventory.Plan) time.Time {
|
||||
var at time.Time
|
||||
for _, r := range read {
|
||||
if r.Looked.After(at) {
|
||||
at = r.Looked
|
||||
}
|
||||
}
|
||||
for _, p := range plans {
|
||||
if _, in := p.Modules[name]; in && p.Created.After(at) {
|
||||
at = p.Created
|
||||
}
|
||||
}
|
||||
return at
|
||||
}
|
||||
|
||||
// readForPlanning is what each module's build read, as the planner maps a change onto it — for a merge
|
||||
// acting now, the merge gate, a pull request's check, a delivery's order and the what-if alike, so planning
|
||||
// and gating cannot disagree (novox/hq ADR 0238): the build sources the newest trunk builds said, but for
|
||||
// the modules a plan has overtaken (staleIn), and with when each was last looked at (lookedAt).
|
||||
func readForPlanning(ctx context.Context, inv *inventory.Inventory) (map[string][]inventory.ReadRepository, error) {
|
||||
read, err := inv.ReadRepositories(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
open, err := inv.OpenPlans(ctx)
|
||||
plans, err := inv.RecentPlans(ctx, planLookBack)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return unnarrowed(read, pendingIn(open)), nil
|
||||
return planningView(read, plans), nil
|
||||
}
|
||||
|
||||
// unnarrowed is what modules read, with the build sources of the pending ones set aside: each of them is
|
||||
// read whole.
|
||||
func unnarrowed(read map[string][]inventory.ReadRepository, pending map[string]bool) map[string][]inventory.ReadRepository {
|
||||
if len(pending) == 0 {
|
||||
return read
|
||||
}
|
||||
// planLookBack is how many recent plans judge whether a recorded build source is overtaken.
|
||||
const planLookBack = 500
|
||||
|
||||
// planningView is readForPlanning over what was read, so a test can hand it records.
|
||||
func planningView(read map[string][]inventory.ReadRepository, plans []inventory.Plan) map[string][]inventory.ReadRepository {
|
||||
stale := staleIn(read, plans)
|
||||
out := make(map[string][]inventory.ReadRepository, len(read))
|
||||
for name, rs := range read {
|
||||
if !pending[name] {
|
||||
out[name] = rs
|
||||
continue
|
||||
}
|
||||
var whole []inventory.ReadRepository
|
||||
looked := lookedAt(name, rs, plans)
|
||||
var kept []inventory.ReadRepository
|
||||
for _, r := range rs {
|
||||
if r.Own {
|
||||
continue
|
||||
if stale[name] {
|
||||
if r.Own {
|
||||
continue
|
||||
}
|
||||
r.Paths = nil
|
||||
}
|
||||
r.Paths = nil
|
||||
whole = append(whole, r)
|
||||
r.Looked = looked
|
||||
kept = append(kept, r)
|
||||
}
|
||||
out[name] = whole
|
||||
out[name] = kept
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// lastLookAt is the most recent look at this repository by anything built from it.
|
||||
func lastLookAt(entries []inventory.Entry, m link.SourceMoved) time.Time {
|
||||
var newest time.Time
|
||||
for _, e := range entries {
|
||||
if sameRepository(e.Source.Repository, m) && e.Source.Seen.After(newest) {
|
||||
newest = e.Source.Seen
|
||||
}
|
||||
}
|
||||
return newest
|
||||
}
|
||||
|
||||
// whatTheMergeTouched narrows the modules built from a repository to the ones the merge changed: **a
|
||||
// changed file touches exactly the modules whose build reads it** (novox/hq issue 280, ADR 0238). It is
|
||||
// touchedBy's first answer; touchedBy is the one place the mesh maps a changed file onto its modules.
|
||||
|
||||
Reference in New Issue
Block a user