A late merge is named however its modules read since the cut; a walk builds the commits it carries (hq ADR 0276 review)
This commit is contained in:
@@ -137,7 +137,23 @@ func (f following) hearMerge(ctx context.Context, m link.SourceMoved, now time.T
|
||||
if err != nil {
|
||||
return notNow(err)
|
||||
}
|
||||
if from, packaging, already := mergeCandidates(m, entries, read); len(from) == 0 && len(packaging) == 0 {
|
||||
merged, err := time.Parse(time.RFC3339Nano, m.MergedAt)
|
||||
if err != nil {
|
||||
merged = now
|
||||
}
|
||||
// **What it touches, history or not** (review of this change): a merge heard after a later merge of its
|
||||
// branch was cut reads as history for every module that cut marked seen, and was dropped from the record
|
||||
// here, never named by the walk that carried it.
|
||||
raw := m
|
||||
raw.MergedAt = ""
|
||||
touches := wouldMove(raw, entries, read)
|
||||
var later []inventory.BatchedMerge
|
||||
if len(touches) > 0 {
|
||||
if later, err = inv.LaterMergesOf(ctx, repository, m.Base, merged.UTC()); err != nil {
|
||||
return notNow(err)
|
||||
}
|
||||
}
|
||||
if from, packaging, already := mergeCandidates(m, entries, read); len(from) == 0 && len(packaging) == 0 && len(later) == 0 {
|
||||
// "Already built from it" and "nothing reads it" are different facts, and reading the first
|
||||
// as the second sends somebody looking for a broken trigger when the mesh is up to date.
|
||||
if already > 0 {
|
||||
@@ -153,10 +169,9 @@ func (f following) hearMerge(ctx context.Context, m link.SourceMoved, now time.T
|
||||
return notNow(err)
|
||||
}
|
||||
defer release()
|
||||
|
||||
merged, err := time.Parse(time.RFC3339Nano, m.MergedAt)
|
||||
if err != nil {
|
||||
merged = now
|
||||
// Again under the hold: a controller handing over may have kept it since.
|
||||
if _, known, err := inv.MergeOf(ctx, repository, m.Commit); err != nil || known {
|
||||
return notNow(err)
|
||||
}
|
||||
event, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
@@ -166,7 +181,7 @@ func (f following) hearMerge(ctx context.Context, m link.SourceMoved, now time.T
|
||||
Heard: now, Event: event}
|
||||
|
||||
// **A merge heard after a later merge of its branch** is answered by the walk holding that one.
|
||||
if p, ok, err := answeredByALaterMerge(ctx, inv, heard, m, entries, read); err != nil {
|
||||
if p, ok, err := answeredByALaterMerge(ctx, inv, heard, touches); err != nil {
|
||||
return notNow(err)
|
||||
} else if ok {
|
||||
heard.Plan = p.ID
|
||||
@@ -190,7 +205,7 @@ func (f following) hearMerge(ctx context.Context, m link.SourceMoved, now time.T
|
||||
return notNow(err)
|
||||
}
|
||||
heard.Plan = batch.ID
|
||||
if _, err := inv.AddMerge(ctx, heard); err != nil {
|
||||
if added, err := inv.AddMerge(ctx, heard); err != nil || !added {
|
||||
return notNow(err)
|
||||
}
|
||||
if err := keepBatch(ctx, inv, &batch, now, ""); err != nil {
|
||||
@@ -207,12 +222,11 @@ func (f following) hearMerge(ctx context.Context, m link.SourceMoved, now time.T
|
||||
// later merge, which contains it. A failed or stopped walk answers nothing more, and a walk folded into
|
||||
// another is followed to that one. False when none does: the merge joins the next batch.
|
||||
func answeredByALaterMerge(ctx context.Context, inv *inventory.Inventory, heard inventory.BatchedMerge,
|
||||
m link.SourceMoved, entries []inventory.Entry, read map[string][]inventory.ReadRepository) (inventory.Plan, bool, error) {
|
||||
moves []inventory.Entry) (inventory.Plan, bool, error) {
|
||||
later, err := inv.LaterMergesOf(ctx, heard.Repository, heard.Branch, heard.Merged)
|
||||
if err != nil || len(later) == 0 {
|
||||
return inventory.Plan{}, false, err
|
||||
}
|
||||
moves := wouldMove(m, entries, read)
|
||||
for _, l := range later {
|
||||
p, err := followTakeOver(ctx, inv, l.Plan)
|
||||
if err != nil {
|
||||
@@ -240,8 +254,12 @@ func followTakeOver(ctx context.Context, inv *inventory.Inventory, id string) (i
|
||||
return p, err
|
||||
}
|
||||
|
||||
// buildsEvery says a walk builds every one of the modules.
|
||||
// buildsEvery says a walk builds every one of the modules, and that there is one: a merge that moves nothing
|
||||
// is no walk's to name.
|
||||
func buildsEvery(p inventory.Plan, modules []inventory.Entry) bool {
|
||||
if len(modules) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, e := range modules {
|
||||
if _, in := p.Modules[e.Manifest.Module]; !in {
|
||||
return false
|
||||
@@ -381,6 +399,15 @@ func carriedOf(merges []inventory.BatchedMerge) ([]inventory.PlanCommit, []inven
|
||||
return commits, named
|
||||
}
|
||||
|
||||
// spelledOf is a kept merge's repository as the forge spelled it; empty when its announcement does not say.
|
||||
func spelledOf(m inventory.BatchedMerge) string {
|
||||
var e link.SourceMoved
|
||||
if json.Unmarshal(m.Event, &e) == nil && e.Owner != "" {
|
||||
return e.Owner + "/" + e.Repo
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// laterMerge says a was merged after b: by the forge's merge time, then by when each was heard.
|
||||
func laterMerge(a, b inventory.BatchedMerge) bool {
|
||||
if !a.Merged.Equal(b.Merged) {
|
||||
@@ -406,15 +433,21 @@ func nameMerges(ctx context.Context, inv *inventory.Inventory, p *inventory.Plan
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, named := carriedOf(merges)
|
||||
// The walk's own commits stand: a merge heard late is carried by the one of its repository.
|
||||
for i := range named {
|
||||
if c := p.CommitOf(named[i].Repository); c != "" && c != named[i].Commit {
|
||||
named[i].Carried = c
|
||||
} else if c == named[i].Commit {
|
||||
named[i].Carried = ""
|
||||
// The walk's own commits stand: a merge heard late is carried by the one of its repository's branch.
|
||||
var named []inventory.PlanMerge
|
||||
for _, m := range merges {
|
||||
n := inventory.PlanMerge{Repository: m.Repository, Commit: m.Commit}
|
||||
if e := spelledOf(m); e != "" {
|
||||
n.Repository = e
|
||||
}
|
||||
if c := p.CommitOn(m.Repository, m.Branch); c != "" && c != m.Commit {
|
||||
n.Carried = c
|
||||
}
|
||||
named = append(named, n)
|
||||
}
|
||||
sort.SliceStable(named, func(i, j int) bool {
|
||||
return strings.ToLower(named[i].Repository) < strings.ToLower(named[j].Repository)
|
||||
})
|
||||
if p.Delivery == nil {
|
||||
p.Delivery = &inventory.PlanDelivery{}
|
||||
}
|
||||
@@ -493,7 +526,11 @@ func cutBatchesHeld(ctx context.Context, open *stores, now time.Time) error {
|
||||
}
|
||||
if len(alone) > 0 {
|
||||
if len(waiting) > 0 {
|
||||
return nil // the waiting walk is the one open; the search walks after it
|
||||
// The waiting walk is the one open; the search walks after it, and the batch waits behind both.
|
||||
if batch != nil {
|
||||
return keepBatch(ctx, inv, batch, now, waiting[0].ID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return walkAlone(ctx, open, alone[0], now)
|
||||
}
|
||||
@@ -600,6 +637,10 @@ func planBatch(ctx context.Context, open *stores, batch *inventory.Plan, carry [
|
||||
var moved []string
|
||||
var members []batchMember
|
||||
for _, m := range combinedMerges(merges) {
|
||||
// **News, whatever was seen since** (review of this change): each merge was judged when it was heard, and
|
||||
// a cut, a fold or a failed walk since marks its modules seen — so a merge walked alone after a failed
|
||||
// walk, or folded, would read as history and move nothing. When it was made is not asked again here.
|
||||
m.MergedAt = ""
|
||||
names, err := movesOfMerge(ctx, inv, m, entries, read)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -686,12 +727,17 @@ func planBatch(ctx context.Context, open *stores, batch *inventory.Plan, carry [
|
||||
}
|
||||
|
||||
// combinedMerges is a batch's merges as one merge per repository's branch: the latest, with every file the
|
||||
// merges of it changed, removed and said to be a module's — on a linear trunk the latest contains the others.
|
||||
// merges of it changed and said to be a module's — on a linear trunk the latest contains the others. A file is
|
||||
// removed when the last merge of the batch that changed it removed it. merges are in the order they were made.
|
||||
func combinedMerges(merges []inventory.BatchedMerge) []link.SourceMoved {
|
||||
type combined struct {
|
||||
latest inventory.BatchedMerge
|
||||
m link.SourceMoved
|
||||
said bool
|
||||
latest inventory.BatchedMerge
|
||||
m link.SourceMoved
|
||||
said bool
|
||||
removed map[string]bool
|
||||
paths []string
|
||||
dirs []string
|
||||
cut bool
|
||||
}
|
||||
by := map[string]*combined{}
|
||||
var keys []string
|
||||
@@ -703,49 +749,39 @@ func combinedMerges(merges []inventory.BatchedMerge) []link.SourceMoved {
|
||||
k := strings.ToLower(b.Repository) + "\x00" + b.Branch
|
||||
c, ok := by[k]
|
||||
if !ok {
|
||||
c = &combined{latest: b, m: e, said: e.ModuleDirsSaid}
|
||||
c = &combined{latest: b, m: e, said: true, removed: map[string]bool{}}
|
||||
by[k] = c
|
||||
keys = append(keys, k)
|
||||
} else {
|
||||
paths, removed, dirs, truncated := c.m.Paths, c.m.Removed, c.m.ModuleDirs, c.m.PathsTruncated
|
||||
said := c.said && e.ModuleDirsSaid
|
||||
if laterMerge(b, c.latest) {
|
||||
c.latest, c.m = b, e
|
||||
}
|
||||
c.m.Paths = union(paths, e.Paths)
|
||||
c.m.Removed = union(removed, e.Removed)
|
||||
c.m.ModuleDirs = union(dirs, e.ModuleDirs)
|
||||
c.m.PathsTruncated = truncated || e.PathsTruncated
|
||||
c.said = said
|
||||
} else if laterMerge(b, c.latest) {
|
||||
c.latest, c.m = b, e
|
||||
}
|
||||
c.said = c.said && e.ModuleDirsSaid
|
||||
c.cut = c.cut || e.PathsTruncated
|
||||
c.paths, c.dirs = union(c.paths, e.Paths), union(c.dirs, e.ModuleDirs)
|
||||
for _, path := range e.Paths {
|
||||
c.removed[path] = slices.Contains(e.Removed, path)
|
||||
}
|
||||
for _, path := range e.Removed {
|
||||
c.removed[path] = true
|
||||
}
|
||||
}
|
||||
sort.Strings(keys)
|
||||
var out []link.SourceMoved
|
||||
for _, k := range keys {
|
||||
c := by[k]
|
||||
c.m.ModuleDirsSaid = c.said
|
||||
// A file a later merge of the batch brought back is no longer removed.
|
||||
var removed []string
|
||||
for _, r := range c.m.Removed {
|
||||
if !restoredLater(r, c.latest, merges) {
|
||||
removed = append(removed, r)
|
||||
m := c.m
|
||||
m.Paths, m.ModuleDirs, m.PathsTruncated, m.ModuleDirsSaid = c.paths, c.dirs, c.cut, c.said
|
||||
m.Removed = nil
|
||||
for _, path := range c.paths {
|
||||
if c.removed[path] {
|
||||
m.Removed = append(m.Removed, path)
|
||||
}
|
||||
}
|
||||
c.m.Removed = removed
|
||||
out = append(out, c.m)
|
||||
out = append(out, m)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// restoredLater says a file removed by one merge of a batch was changed, not removed, by the latest.
|
||||
func restoredLater(path string, latest inventory.BatchedMerge, _ []inventory.BatchedMerge) bool {
|
||||
var e link.SourceMoved
|
||||
if json.Unmarshal(latest.Event, &e) != nil {
|
||||
return false
|
||||
}
|
||||
return slices.Contains(e.Paths, path) && !slices.Contains(e.Removed, path)
|
||||
}
|
||||
|
||||
// union is a and b without repeats, in order.
|
||||
func union(a, b []string) []string {
|
||||
out := append([]string{}, a...)
|
||||
|
||||
Reference in New Issue
Block a user