A changed file touches exactly the modules whose build reads it (hq issue 280, ADR 0237)
The builder reads a module's own directory (the repository for one built from its root) and a repository its recipe packages, nothing else. A file in no module's directory was read as shared code and rebuilt everything built from the repository: 103 modules for a merge-check.sh added at the catalogue's root. It now touches nothing, in the merge handler, the release planner and the pull request's check alike, and the gate says so.
This commit is contained in:
@@ -5,7 +5,6 @@ import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"path"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -616,65 +615,33 @@ func lastLookAt(entries []inventory.Entry, m link.SourceMoved) time.Time {
|
||||
return newest
|
||||
}
|
||||
|
||||
// whatTheMergeTouched narrows the modules built from a repository to the ones the merge changed.
|
||||
// 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 0237 as amended).
|
||||
//
|
||||
// **A change inside no module's own directory is a change to what they share.** The forge lists the
|
||||
// files a merge changed; a module is affected when one of them is inside its own directory, when it
|
||||
// is built from the repository's root — everything there is its source — or when some changed file
|
||||
// belongs to no module's directory at all, which is how a shared file, a build recipe or a
|
||||
// dependency at the root rebuilds everything built from that repository.
|
||||
// What a build reads is the module's own directory — the builder clones the repository and builds within
|
||||
// that directory alone: the manifest, the recipes, the bundles' sources, the Docker context — or the whole
|
||||
// repository for a module built from its root. A second repository a recipe packages (an artifact's
|
||||
// `context`) is read too, and that is the build record's `read`, answered by readsFrom beside this. So a
|
||||
// changed file inside a module's directory is that module's; a file in no module's directory — a script
|
||||
// at the root, a README, CI configuration, a directory of notes — is read by no build and touches nothing.
|
||||
//
|
||||
// A change inside *another* module's directory is that module's business and not this one's, even
|
||||
// when the mesh does not hold that module: `known` is every module this repository is known to hold,
|
||||
// whatever branch it was registered from.
|
||||
// **It used to be read as shared code**, rebuilding everything built from the repository, on the theory
|
||||
// that a root file might be a build input (04-ISSUES/131). No build reads one: the theory cost a rebuild
|
||||
// of 103 modules for a merge-check.sh added at the catalogue's root (issue 280), as it had for a module
|
||||
// held by no machine (278) and a new module's directory (252), each patched as an exception to a rule that
|
||||
// was wrong. Rebuilding too much is not the safe direction when every rebuild is a rollout.
|
||||
//
|
||||
// **And a module the mesh has never seen is still a module** (novox/hq issue 252). A merge adding a
|
||||
// new module to the catalogue repository — `modules/newmod/module.json` and its files — read as a
|
||||
// change to shared code, because `modules/newmod` was nobody's known directory, and every module
|
||||
// built from the repository was rebuilt and rolled out for a module none of them is. So the
|
||||
// directories that hold modules are known too: the parents of the known modules' directories
|
||||
// (`modules`, never the root). A changed path `<parent>/<name>/…` belongs to the module at
|
||||
// `<parent>/<name>` — held or not — and rebuilds nothing else, **provided it is shown to be a
|
||||
// module**: its `module.json` is among the changed files (added, changed, or removed with it). A
|
||||
// directory under the same parent whose manifest the merge did not touch may as well be a shared
|
||||
// library (`modules/lib`), and that is still read as shared. Rebuilding too much remains the safe
|
||||
// direction: the fault this whole path exists for is a mesh that believes it is current and is not
|
||||
// (novox/hq 04-ISSUES/131). A file at the root, or directly in a parent, is shared as it always was.
|
||||
//
|
||||
// **Whether a directory is a module is a fact of the repository at the merge commit** (novox/hq issue
|
||||
// 278), and the announcer now says it: a changed file inside a directory holding a `module.json` there
|
||||
// is that module's, whatever the merge did to the manifest; only a file in no such directory is shared.
|
||||
// From an announcer that does not say, the rule above stands.
|
||||
// Nothing said about the files, or not all of them said, is still everything: what is not known cannot be
|
||||
// narrowed. `known` is kept for the callers; which directories hold a module is no longer needed to read a
|
||||
// change, because a file is a module's only by being inside it.
|
||||
func whatTheMergeTouched(candidates, known []inventory.Entry, m link.SourceMoved) []inventory.Entry {
|
||||
// Nothing said about the files, or not all of them said: everything built from it is affected.
|
||||
_ = known
|
||||
if len(m.Paths) == 0 || m.PathsTruncated {
|
||||
return candidates
|
||||
}
|
||||
var dirs []string
|
||||
parents := map[string]bool{}
|
||||
for _, e := range known {
|
||||
if e.Source.Path != "" && sameRepository(e.Source.Repository, m) {
|
||||
dir := strings.Trim(e.Source.Path, "/")
|
||||
dirs = append(dirs, dir)
|
||||
if parent := path.Dir(dir); parent != "." && parent != "/" {
|
||||
parents[parent] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
changed := map[string]bool{}
|
||||
for _, p := range m.Paths {
|
||||
changed[strings.TrimPrefix(p, "/")] = true
|
||||
}
|
||||
modules := saidModuleDirs(m)
|
||||
for _, p := range m.Paths {
|
||||
if insideAny(p, dirs) || inAModuleOfItsOwn(p, parents, changed) || insideAny(p, modules) {
|
||||
continue
|
||||
}
|
||||
return candidates
|
||||
}
|
||||
var out []inventory.Entry
|
||||
for _, e := range candidates {
|
||||
if e.Source.Path == "" || anyInside(m.Paths, e.Source.Path) {
|
||||
if strings.Trim(e.Source.Path, "/") == "" || anyInside(m.Paths, e.Source.Path) {
|
||||
out = append(out, e)
|
||||
}
|
||||
}
|
||||
@@ -700,28 +667,6 @@ func saidModuleDirs(m link.SourceMoved) []string {
|
||||
return out
|
||||
}
|
||||
|
||||
// inAModuleOfItsOwn is whether a changed file is inside a module directory the mesh does not know —
|
||||
// `<parent>/<name>/…` under a directory known to hold modules, whose `module.json` the same merge
|
||||
// changed (novox/hq issue 252). Such a file is that module's business and nobody else's.
|
||||
func inAModuleOfItsOwn(p string, parents, changed map[string]bool) bool {
|
||||
p = strings.TrimPrefix(p, "/")
|
||||
for parent := range parents {
|
||||
rest, under := strings.CutPrefix(p, parent+"/")
|
||||
if !under {
|
||||
continue
|
||||
}
|
||||
name, _, inADirectory := strings.Cut(rest, "/")
|
||||
if !inADirectory || name == "" {
|
||||
// A file directly in the parent — `modules/README.md` — is about all of them.
|
||||
continue
|
||||
}
|
||||
if changed[parent+"/"+name+"/module.json"] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// inside is whether a changed file is in a directory: that directory itself, or under it.
|
||||
func inside(path, dir string) bool {
|
||||
dir = strings.Trim(dir, "/")
|
||||
|
||||
Reference in New Issue
Block a user