зеркало из https://github.com/golang/build.git
cmd/releasebot: create GitHub milestones for next release
The current release process relies on humans to create GitHub milestones for future Go releases. releasebot includes automated checks that detect when that step is forgotten. CL 294249 included one of those checks, and its commit message mentioned: (Future release process improvements may include automatically making the milestone. That is better suited to be in scope of golang/go#40279.) This is the release process improvement that automates making milestones. For golang/go#40279. Change-Id: I8e02aff6714a5cf2de4ee4bcfe98aaf68abb0cd4 Reviewed-on: https://go-review.googlesource.com/c/build/+/354758 Run-TryBot: Dmitri Shuralyov <dmitshur@golang.org> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Heschi Kreinick <heschi@google.com> Reviewed-by: Alexander Rakoczy <alex@golang.org> Trust: Dmitri Shuralyov <dmitshur@golang.org>
This commit is contained in:
Родитель
70d1a997b4
Коммит
5747e451b3
|
@ -149,16 +149,42 @@ func (w *Work) createGitHubIssue(title, msg string) (int, error) {
|
|||
return i.GetNumber(), err
|
||||
}
|
||||
|
||||
// pushIssues moves open issues to the milestone of the next release of the same kind.
|
||||
// pushIssues moves open issues to the milestone of the next release of the same kind,
|
||||
// creating the milestone if it doesn't already exist.
|
||||
// For major releases, it's the milestone of the next major release (e.g., 1.14 → 1.15).
|
||||
// For minor releases, it's the milestone of the next minor release (e.g., 1.14.1 → 1.14.2).
|
||||
// For other release types, it does nothing.
|
||||
//
|
||||
// For major releases, it also creates the first minor release milestone if it doesn't already exist.
|
||||
func (w *Work) pushIssues() {
|
||||
if w.BetaRelease || w.RCRelease {
|
||||
// Nothing to do.
|
||||
return
|
||||
}
|
||||
|
||||
// Get the milestone for the next release.
|
||||
var nextMilestone *github.Milestone
|
||||
nextV, err := nextVersion(w.Version)
|
||||
if err != nil {
|
||||
w.logError("error determining next version: %v", err)
|
||||
return
|
||||
}
|
||||
nextMilestone, err = w.findOrCreateMilestone(nextV)
|
||||
if err != nil {
|
||||
w.logError("error finding or creating %s, the next GitHub milestone after release %s: %v", nextV, w.Version, err)
|
||||
return
|
||||
}
|
||||
|
||||
// For major releases (go1.X), also create the first minor release milestone (go1.X.1). See issue 44404.
|
||||
if strings.Count(w.Version, ".") == 1 {
|
||||
firstMinor := w.Version + ".1"
|
||||
_, err := w.findOrCreateMilestone(firstMinor)
|
||||
if err != nil {
|
||||
// Log this error, but continue executing the rest of the task.
|
||||
w.logError("error finding or creating %s, the first minor release GitHub milestone after major release %s: %v", firstMinor, w.Version, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := goRepo.ForeachIssue(func(gi *maintner.GitHubIssue) error {
|
||||
if gi.Milestone == nil || gi.Milestone.ID != w.Milestone.ID {
|
||||
return nil
|
||||
|
@ -170,12 +196,12 @@ func (w *Work) pushIssues() {
|
|||
if gi.Closed && !w.Security {
|
||||
return nil
|
||||
}
|
||||
w.log.Printf("changing milestone of issue %d to %s", gi.Number, w.NextMilestone.Title)
|
||||
w.log.Printf("changing milestone of issue %d to %s", gi.Number, nextMilestone.GetTitle())
|
||||
if dryRun {
|
||||
return nil
|
||||
}
|
||||
_, _, err := githubClient.Issues.Edit(context.TODO(), projectOwner, projectRepo, int(gi.Number), &github.IssueRequest{
|
||||
Milestone: github.Int(int(w.NextMilestone.Number)),
|
||||
Milestone: github.Int(nextMilestone.GetNumber()),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("#%d: %s", gi.Number, err)
|
||||
|
@ -187,6 +213,52 @@ func (w *Work) pushIssues() {
|
|||
}
|
||||
}
|
||||
|
||||
// findOrCreateMilestone finds or creates a GitHub milestone corresponding
|
||||
// to the specified Go version. This is done via the GitHub API, using githubClient.
|
||||
// If the milestone exists but isn't open, an error is returned.
|
||||
func (w *Work) findOrCreateMilestone(version string) (*github.Milestone, error) {
|
||||
// Look for an existing open milestone corresponding to version,
|
||||
// and return it if found.
|
||||
for opt := (&github.MilestoneListOptions{ListOptions: github.ListOptions{PerPage: 100}}); ; {
|
||||
ms, resp, err := githubClient.Issues.ListMilestones(context.Background(), projectOwner, projectRepo, opt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, m := range ms {
|
||||
if strings.ToLower(m.GetTitle()) == version {
|
||||
// Found an existing milestone.
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
if resp.NextPage == 0 {
|
||||
break
|
||||
}
|
||||
opt.Page = resp.NextPage
|
||||
}
|
||||
|
||||
// Create a new milestone.
|
||||
// For historical reasons, Go milestone titles use a capital "Go1.n" format,
|
||||
// in contrast to go versions which are like "go1.n". Do the same here.
|
||||
title := strings.Replace(version, "go", "Go", 1)
|
||||
w.log.Printf("creating milestone titled %q", title)
|
||||
if dryRun {
|
||||
return &github.Milestone{Title: github.String(title)}, nil
|
||||
}
|
||||
m, _, err := githubClient.Issues.CreateMilestone(context.Background(), projectOwner, projectRepo, &github.Milestone{
|
||||
Title: github.String(title),
|
||||
})
|
||||
if e := (*github.ErrorResponse)(nil); errors.As(err, &e) && e.Response != nil && e.Response.StatusCode == http.StatusUnprocessableEntity && len(e.Errors) == 1 && e.Errors[0].Code == "already_exists" {
|
||||
// We'll run into an already_exists error here if the milestone exists,
|
||||
// but it wasn't found in the loop above because the milestone isn't open.
|
||||
// That shouldn't happen under normal circumstances, so if it does,
|
||||
// let humans figure out how to best deal with it.
|
||||
return nil, errors.New("a closed milestone with the same title already exists")
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// closeMilestone closes the milestone for the current release.
|
||||
func (w *Work) closeMilestone() {
|
||||
w.log.Printf("closing milestone %s", w.Milestone.Title)
|
||||
|
|
|
@ -166,30 +166,12 @@ func main() {
|
|||
// Select release targets for this Go version.
|
||||
w.ReleaseTargets = matchTargets(w.Version)
|
||||
|
||||
// Find milestones.
|
||||
// Find milestone.
|
||||
var err error
|
||||
w.Milestone, err = getMilestone(w.Version)
|
||||
if err != nil {
|
||||
log.Fatalf("cannot find the GitHub milestone for release %s: %v", w.Version, err)
|
||||
}
|
||||
if !w.BetaRelease && !w.RCRelease {
|
||||
nextV, err := nextVersion(w.Version)
|
||||
if err != nil {
|
||||
log.Fatalln("nextVersion:", err)
|
||||
}
|
||||
w.NextMilestone, err = getMilestone(nextV)
|
||||
if err != nil {
|
||||
log.Fatalf("cannot find %s, the next GitHub milestone after release %s: %v", nextV, w.Version, err)
|
||||
}
|
||||
}
|
||||
// For major releases (go1.X), also check the "create first minor release milestone"
|
||||
// step in the release process wasn't accidentally missed. See issue 44404.
|
||||
if !w.BetaRelease && !w.RCRelease && strings.Count(w.Version, ".") == 1 {
|
||||
firstMinor := w.Version + ".1"
|
||||
if _, err := getMilestone(firstMinor); err != nil {
|
||||
log.Fatalf("cannot find %s, the first minor release GitHub milestone after major release %s: %v", firstMinor, w.Version, err)
|
||||
}
|
||||
}
|
||||
|
||||
w.doRelease()
|
||||
}
|
||||
|
@ -325,11 +307,6 @@ type Work struct {
|
|||
ReleaseInfo map[string]*ReleaseInfo // map and info protected by releaseMu
|
||||
|
||||
Milestone *maintner.GitHubMilestone // Milestone for the current release.
|
||||
// NextMilestone is the milestone of the next release of the same kind.
|
||||
// For major releases, it's the milestone of the next major release (e.g., 1.14 → 1.15).
|
||||
// For minor releases, it's the milestone of the next minor release (e.g., 1.14.1 → 1.14.2).
|
||||
// For other release types, it's unset.
|
||||
NextMilestone *maintner.GitHubMilestone
|
||||
}
|
||||
|
||||
// ReleaseInfo describes a release build for a specific target.
|
||||
|
|
Загрузка…
Ссылка в новой задаче