зеркало из https://github.com/golang/build.git
206 строки
5.6 KiB
Go
206 строки
5.6 KiB
Go
// Copyright 2017 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/google/go-github/github"
|
|
"golang.org/x/build/maintner"
|
|
"golang.org/x/build/maintner/godata"
|
|
"golang.org/x/oauth2"
|
|
)
|
|
|
|
const (
|
|
projectOwner = "golang"
|
|
projectRepo = "go"
|
|
)
|
|
|
|
var githubClient *github.Client
|
|
|
|
// GitHub personal access token, from https://github.com/settings/applications.
|
|
var githubAuthToken string
|
|
|
|
var goRepo *maintner.GitHubRepo
|
|
|
|
func loadMaintner() {
|
|
corpus, err := godata.Get(context.Background())
|
|
if err != nil {
|
|
log.Fatal("failed to load maintner data:", err)
|
|
}
|
|
goRepo = corpus.GitHub().Repo(projectOwner, projectRepo)
|
|
}
|
|
|
|
func loadGithubAuth() {
|
|
const short = ".github-issue-token"
|
|
filename := filepath.Clean(os.Getenv("HOME") + "/" + short)
|
|
shortFilename := filepath.Clean("$HOME/" + short)
|
|
data, err := ioutil.ReadFile(filename)
|
|
if err != nil {
|
|
log.Fatal("reading token: ", err, "\n\n"+
|
|
"Please create a personal access token at https://github.com/settings/tokens/new\n"+
|
|
"and write it to ", shortFilename, " to use this program.\n"+
|
|
"** The token only needs the public_repo scope. **\n"+
|
|
"The benefit of using a personal access token over using your GitHub\n"+
|
|
"password directly is that you can limit its use and revoke it at any time.\n\n")
|
|
}
|
|
fi, err := os.Stat(filename)
|
|
if err != nil {
|
|
log.Fatalln("reading token:", err)
|
|
}
|
|
if fi.Mode()&0077 != 0 {
|
|
log.Fatalf("reading token: %s mode is %#o, want %#o", shortFilename, fi.Mode()&0777, fi.Mode()&0700)
|
|
}
|
|
githubAuthToken = strings.TrimSpace(string(data))
|
|
t := &oauth2.Transport{
|
|
Source: &tokenSource{AccessToken: githubAuthToken},
|
|
}
|
|
githubClient = github.NewClient(&http.Client{Transport: t})
|
|
}
|
|
|
|
// releaseStatusTitle returns the title of the release status issue
|
|
// for the given milestone.
|
|
// If you change this function, releasebot will not be able to find an
|
|
// existing tracking issue using the old name and will create a new one.
|
|
func (w *Work) releaseStatusTitle() string {
|
|
return "all: " + strings.Replace(w.Version, "go", "Go ", -1) + " release status"
|
|
}
|
|
|
|
type tokenSource oauth2.Token
|
|
|
|
func (t *tokenSource) Token() (*oauth2.Token, error) {
|
|
return (*oauth2.Token)(t), nil
|
|
}
|
|
|
|
func (w *Work) findOrCreateReleaseIssue() {
|
|
if w.Security {
|
|
// There is no release status issue for security releases
|
|
// to avoid the risk of leaking sensitive test failures.
|
|
return
|
|
}
|
|
w.log.Printf("Release status issue title: %q", w.releaseStatusTitle())
|
|
if dryRun {
|
|
return
|
|
}
|
|
if w.ReleaseIssue == 0 {
|
|
title := w.releaseStatusTitle()
|
|
body := fmt.Sprintf("Issue tracking the %s release by releasebot.", w.Version)
|
|
num, err := w.createGitHubIssue(title, body)
|
|
if err != nil {
|
|
w.log.Panic(err)
|
|
}
|
|
w.ReleaseIssue = num
|
|
w.log.Printf("Release status issue: https://golang.org/issue/%d", num)
|
|
}
|
|
}
|
|
|
|
// createGitHubIssue creates an issue in the release milestone and returns its number.
|
|
func (w *Work) createGitHubIssue(title, msg string) (int, error) {
|
|
if dryRun {
|
|
return 0, errors.New("attempted write operation in dry-run mode")
|
|
}
|
|
var dup int
|
|
goRepo.ForeachIssue(func(gi *maintner.GitHubIssue) error {
|
|
if gi.Title == title {
|
|
dup = int(gi.Number)
|
|
return errors.New("stop iteration")
|
|
}
|
|
return nil
|
|
})
|
|
if dup != 0 {
|
|
return dup, nil
|
|
}
|
|
opts := &github.IssueListByRepoOptions{
|
|
State: "all",
|
|
ListOptions: github.ListOptions{PerPage: 100},
|
|
}
|
|
if !w.BetaRelease && !w.RCRelease {
|
|
opts.Milestone = strconv.Itoa(int(w.Milestone.Number))
|
|
}
|
|
is, _, err := githubClient.Issues.ListByRepo(context.TODO(), "golang", "go", opts)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
for _, i := range is {
|
|
if i.GetTitle() == title {
|
|
// Dup.
|
|
return i.GetNumber(), nil
|
|
}
|
|
}
|
|
copts := &github.IssueRequest{
|
|
Title: github.String(title),
|
|
Body: github.String(msg),
|
|
}
|
|
if !w.BetaRelease && !w.RCRelease {
|
|
copts.Milestone = github.Int(int(w.Milestone.Number))
|
|
}
|
|
i, _, err := githubClient.Issues.Create(context.TODO(), "golang", "go", copts)
|
|
return i.GetNumber(), err
|
|
}
|
|
|
|
// pushIssues moves open issues to the next release.
|
|
func (w *Work) pushIssues() {
|
|
if err := goRepo.ForeachIssue(func(gi *maintner.GitHubIssue) error {
|
|
if gi.Milestone == nil || gi.Milestone.Title != w.Milestone.Title {
|
|
return nil
|
|
}
|
|
if gi.Title == w.releaseStatusTitle() {
|
|
return nil
|
|
}
|
|
// All issues are unrelated if this is a security release.
|
|
if gi.Closed && !w.Security {
|
|
return nil
|
|
}
|
|
w.log.Printf("changing milestone of issue %d to %s", gi.Number, w.NextMilestone.Title)
|
|
if dryRun {
|
|
return nil
|
|
}
|
|
_, _, err := githubClient.Issues.Edit(context.TODO(), projectOwner, projectRepo, int(gi.Number), &github.IssueRequest{
|
|
Milestone: github.Int(int(w.NextMilestone.Number)),
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("#%d: %s", gi.Number, err)
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
w.logError("error moving issues to the next minor release: %v", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
func (w *Work) closeMilestone() {
|
|
w.log.Printf("closing milestone %s", w.Milestone.Title)
|
|
if dryRun {
|
|
return
|
|
}
|
|
closed := "closed"
|
|
_, _, err := githubClient.Issues.EditMilestone(context.TODO(), projectOwner, projectRepo, int(w.Milestone.Number), &github.Milestone{
|
|
State: &closed,
|
|
})
|
|
if err != nil {
|
|
w.logError("closing milestone: %v", err)
|
|
}
|
|
|
|
}
|
|
|
|
func postGithubComment(number int, body string) error {
|
|
if dryRun {
|
|
return errors.New("attemted write operation in dry-run mode")
|
|
}
|
|
_, _, err := githubClient.Issues.CreateComment(context.TODO(), projectOwner, projectRepo, number, &github.IssueComment{
|
|
Body: &body,
|
|
})
|
|
return err
|
|
}
|