diff --git a/go/delivery/candidate_merged_test.go b/go/delivery/candidate_merged_test.go new file mode 100644 index 0000000..e8308bc --- /dev/null +++ b/go/delivery/candidate_merged_test.go @@ -0,0 +1,27 @@ +package delivery + +import ( + "strings" + "testing" +) + +// 1791/1921 S9: a member of a group that merged stays in the candidate, marked merged, with its head, so the heads hash +// the Approve covers holds until every member merged; a candidate whose every member merged is refused. +func TestACandidateKeepsAMergedMemberAndRefusesOneWhoseEveryMemberMerged(t *testing.T) { + a := Head{Repository: "novox/a", Number: 1, Head: strings.Repeat("1", 40)} + b := Head{Repository: "novox/b", Number: 2, Head: strings.Repeat("2", 40)} + hash, _ := HeadsHash([]Head{a, b}) + c := Candidate{Group: "feat/x", HeadsHash: hash, Pulls: []CandidatePull{{Head: a, Modules: []string{}, Merged: true}, {Head: b, Modules: []string{}}}} + raw, err := EncodeCandidate(c) + if err != nil { + t.Fatal(err) + } + back, err := DecodeCandidate(raw) + if err != nil || !back.Pulls[0].Merged || back.Pulls[1].Merged || back.HeadsHash != hash { + t.Fatalf("read back %+v, %v", back, err) + } + c.Pulls[1].Merged = true + if _, err := EncodeCandidate(c); err == nil { + t.Error("a candidate whose every member merged was written") + } +} diff --git a/go/delivery/source.go b/go/delivery/source.go index 535f771..19eb740 100644 --- a/go/delivery/source.go +++ b/go/delivery/source.go @@ -269,6 +269,10 @@ type CandidatePull struct { Head Modules []string `json:"modules"` Fps map[string]CandidateModule `json:"fps,omitempty"` + // Merged says the pull request merged while another member of its group is still open: it stays in the candidate + // with the head it merged at, so the heads hash an Approve covers holds until every member merged, and the checker + // judges the members still open by the verdict the group had (issue 1791). A reader of the older shape ignores it. + Merged bool `json:"merged,omitempty"` } // CandidateNoneNoModule is the None2 of a candidate's module whose directory the merge preview names no manifest for: @@ -346,6 +350,13 @@ func (c Candidate) check() error { if len(c.Pulls) == 0 { return fmt.Errorf("delivery: a candidate names no pull request") } + open := false + for _, p := range c.Pulls { + open = open || !p.Merged + } + if !open { + return fmt.Errorf("delivery: every pull request of the candidate merged: the group is no candidate any more") + } heads := make([]Head, len(c.Pulls)) for i, p := range c.Pulls { heads[i] = p.Head