issue 1025: the merge gate judges a group's move of a module as a move (ADR 0420 §6)
This commit is contained in:
@@ -271,6 +271,25 @@ func judgeChange(ctx context.Context, in mergeCheckInput) (mergeVerdict, error)
|
||||
// The change, then each other head of its delivery group laid over it in turn (novox/hq ADR 0239): one
|
||||
// future state of the mesh, judged as the group would leave it.
|
||||
heads := append([]groupChange{{Repository: in.repository, Tree: in.tree, Changed: in.changed}}, in.group...)
|
||||
// **A module one head removes and another defines is a move** (novox/hq ADR 0420 §6, issue 1025): neither
|
||||
// the removal of a module machines run (ADR 0236) nor a second definition of one module name (issue 239).
|
||||
// The change is judged with the module built from the repository that now defines it.
|
||||
moves, err := movesInGroup(heads, sources)
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
changeSources := sources
|
||||
if len(moves) > 0 {
|
||||
changeSources = make(map[string]inventory.Source, len(sources))
|
||||
for k, o := range sources {
|
||||
changeSources[k] = o
|
||||
}
|
||||
for name, mv := range moves {
|
||||
o := changeSources[name]
|
||||
o.Repository, o.Path = mv.To, mv.Dir
|
||||
changeSources[name] = o
|
||||
}
|
||||
}
|
||||
for i, h := range heads {
|
||||
var r *mergeReach
|
||||
if h.Repository != "" && len(h.Changed) > 0 {
|
||||
@@ -289,13 +308,23 @@ func judgeChange(ctx context.Context, in mergeCheckInput) (mergeVerdict, error)
|
||||
continue
|
||||
}
|
||||
var failures []string
|
||||
change, failures, err = shelfWithChange(change, sources, in.facts, h.Repository, h.Tree, v.Modules,
|
||||
scopeOfReach(r, h.Repository))
|
||||
scope := scopeOfReach(r, h.Repository)
|
||||
if scope != nil {
|
||||
for _, mv := range moves {
|
||||
if mv.To == h.Repository {
|
||||
scope[mv.Dir] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
change, failures, err = shelfWithChange(change, changeSources, in.facts, h.Repository, h.Tree, v.Modules, scope)
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
v.Failures = append(v.Failures, failures...)
|
||||
}
|
||||
for name := range moves {
|
||||
v.Modules[name] = "moved"
|
||||
}
|
||||
if len(in.group) > 0 {
|
||||
say("a delivery group: %d head(s) composed together", len(heads))
|
||||
}
|
||||
@@ -332,7 +361,7 @@ func judgeChange(ctx context.Context, in mergeCheckInput) (mergeVerdict, error)
|
||||
}
|
||||
}
|
||||
sort.Strings(added)
|
||||
after, tried, notes, err := composeEveryMachine(ctx, in, change, sources, added)
|
||||
after, tried, notes, err := composeEveryMachine(ctx, in, change, changeSources, added)
|
||||
if err != nil {
|
||||
return v, fmt.Errorf("the mesh with the change could not be raised: %w", err)
|
||||
}
|
||||
@@ -531,6 +560,98 @@ func sourcesOfFacts(f snapshot.Facts) map[string]inventory.Source {
|
||||
return out
|
||||
}
|
||||
|
||||
// moveOfModule is a module a delivery group moves: the repository the mesh builds it from, the one whose
|
||||
// head now defines it, and its directory there.
|
||||
type moveOfModule struct{ From, To, Dir string }
|
||||
|
||||
// movesInGroup is the modules one head of a group removes and another defines (novox/hq ADR 0420 §6): a name
|
||||
// the mesh builds from a head's repository that the head's tree no longer defines, and some other head's tree
|
||||
// defines. A name removed by a head with no other head defining it, or defined by a head the mesh builds from
|
||||
// another repository with none removing it, is no move.
|
||||
func movesInGroup(heads []groupChange, sources map[string]inventory.Source) (map[string]moveOfModule, error) {
|
||||
if len(heads) < 2 {
|
||||
return nil, nil
|
||||
}
|
||||
defined := make([]map[string]string, len(heads))
|
||||
for i, h := range heads {
|
||||
if h.Tree == "" {
|
||||
continue
|
||||
}
|
||||
d, err := namesDefinedIn(h.Tree)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defined[i] = d
|
||||
}
|
||||
var moves map[string]moveOfModule
|
||||
for name, s := range sources {
|
||||
if s.Repository == "" {
|
||||
continue
|
||||
}
|
||||
for i, h := range heads {
|
||||
if h.Tree == "" || !sameRepository(s.Repository, link.SourceMoved{Owner: ownerOf(h.Repository), Repo: repoOf(h.Repository)}) {
|
||||
continue
|
||||
}
|
||||
if _, still := defined[i][name]; still {
|
||||
break
|
||||
}
|
||||
// Removed by head i: defined by another?
|
||||
for j, o := range heads {
|
||||
if dir, ok := defined[j][name]; ok && j != i && o.Repository != h.Repository {
|
||||
if moves == nil {
|
||||
moves = map[string]moveOfModule{}
|
||||
}
|
||||
moves[name] = moveOfModule{From: h.Repository, To: o.Repository, Dir: dir}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return moves, nil
|
||||
}
|
||||
|
||||
// namesDefinedIn is the module names a tree's module.json files define, each with its directory; a manifest
|
||||
// that does not read is left to shelfWithChange to refuse.
|
||||
func namesDefinedIn(tree string) (map[string]string, error) {
|
||||
found := map[string]string{}
|
||||
err := filepath.WalkDir(tree, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d.IsDir() {
|
||||
if path != tree && (ignoredInTree[d.Name()] || strings.HasPrefix(d.Name(), ".")) {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if d.Name() != "module.json" {
|
||||
return nil
|
||||
}
|
||||
raw, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var m struct {
|
||||
Module string `json:"module"`
|
||||
}
|
||||
if json.Unmarshal(raw, &m) != nil || m.Module == "" {
|
||||
return nil
|
||||
}
|
||||
dir, err := filepath.Rel(tree, filepath.Dir(path))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dir == "." {
|
||||
dir = ""
|
||||
}
|
||||
if _, twice := found[m.Module]; !twice {
|
||||
found[m.Module] = dir
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return found, err
|
||||
}
|
||||
|
||||
// ignoredInTree are directories a module.json in is not a module of the repository.
|
||||
var ignoredInTree = map[string]bool{".git": true, "vendor": true, "node_modules": true, "testdata": true,
|
||||
"examples": true, "fixtures": true}
|
||||
|
||||
Reference in New Issue
Block a user