Skip to content

Commit 47c2978

Browse files
authored
Merge pull request #26 from vitessio/enhance-code-freeze
2 parents a8ecf07 + 5ba4cbf commit 47c2978

File tree

7 files changed

+144
-23
lines changed

7 files changed

+144
-23
lines changed

go/cmd/interactive/cmd.go

+5
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,12 @@ func Command() *cobra.Command {
3232
Run: func(cmd *cobra.Command, args []string) {
3333
ctx := releaser.UnwrapCtx(cmd.Context())
3434
git.CorrectCleanRepo(ctx.VitessRepo)
35+
36+
// TODO: The assumption that the Release Manager won't be
37+
// modifying the release issue while using vitess-releaser
38+
// is made here, perhaps there is a better way of doing it
3539
ctx.LoadIssue()
40+
3641
interactive.MainScreen(ctx)
3742
},
3843
}

go/interactive/code_freeze.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package interactive
1818

1919
import (
2020
tea "github.com/charmbracelet/bubbletea"
21-
"vitess.io/vitess-releaser/go/interactive/state"
2221
"vitess.io/vitess-releaser/go/releaser"
2322
"vitess.io/vitess-releaser/go/releaser/steps"
2423

@@ -31,24 +30,25 @@ func codeFreezeMenuItem(ctx *releaser.Context) *menuItem {
3130
name: steps.CodeFreeze,
3231
act: codeFreezeAct,
3332
update: codeFreezeUpdate,
34-
isDone: state.ToDo, // TODO: read the initial state from the Release Issue on GitHub
33+
info: ctx.Issue.CodeFreeze.URL,
34+
isDone: ctx.Issue.CodeFreeze.Done,
3535
}
3636
}
3737

3838
type codeFreezeUrl string
3939

4040
func codeFreezeUpdate(mi *menuItem, msg tea.Msg) (*menuItem, tea.Cmd) {
41-
url, ok := msg.(codeFreezeUrl)
41+
_, ok := msg.(codeFreezeUrl)
4242
if !ok {
4343
return mi, nil
4444
}
45-
mi.info = string(url)
46-
mi.isDone = state.Done
45+
46+
mi.info = mi.ctx.Issue.CodeFreeze.URL
47+
mi.isDone = mi.ctx.Issue.CodeFreeze.Done
4748
return mi, nil
4849
}
4950

5051
func codeFreezeAct(mi *menuItem) (*menuItem, tea.Cmd) {
51-
mi.info = "running..."
5252
pl, freeze := pre_release.CodeFreeze(mi.ctx)
5353
return mi, tea.Batch(func() tea.Msg {
5454
return codeFreezeUrl(freeze())

go/interactive/create_github_milestones.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func createMilestoneMenuItem(ctx *releaser.Context) *menuItem {
4242

4343
func createMilestoneUpdate(mi *menuItem, msg tea.Msg) (*menuItem, tea.Cmd) {
4444
milestoneLink, ok := msg.(createMilestone)
45-
if !ok || milestoneLink == "" {
45+
if !ok || len(milestoneLink) == 0 {
4646
return mi, nil
4747
}
4848

go/releaser/git/git.go

+11-2
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,12 @@ func Pull(remote, branch string) {
5959
}
6060

6161
func ResetHard(remote, branch string) {
62-
out, err := exec.Command("git", "reset", "--hard", remote+"/"+branch).CombinedOutput()
62+
out, err := exec.Command("git", "fetch", remote).CombinedOutput()
63+
if err != nil {
64+
log.Fatalf("%s: %s", err, out)
65+
}
66+
67+
out, err = exec.Command("git", "reset", "--hard", remote+"/"+branch).CombinedOutput()
6368
if err != nil {
6469
log.Fatalf("%s: %s", err, out)
6570
}
@@ -83,16 +88,20 @@ func Push(remote, branch string) {
8388
}
8489
}
8590

86-
func CommitAll(msg string) {
91+
func CommitAll(msg string) (empty bool) {
8792
out, err := exec.Command("git", "add", "--all").CombinedOutput()
8893
if err != nil {
8994
log.Fatalf("%s: %s", err, out)
9095
}
9196

9297
out, err = exec.Command("git", "commit", "-n", "-s", "-m", msg).CombinedOutput()
9398
if err != nil {
99+
if strings.Contains(err.Error(), "nothing to commit, working tree clean") {
100+
return true
101+
}
94102
log.Fatalf("%s: %s", err, out)
95103
}
104+
return false
96105
}
97106

98107
// FindRemoteName takes the output of `git remote -v` and a repository name,

go/releaser/github/pr.go

+44-7
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"encoding/json"
2121
"fmt"
2222
"log"
23+
"strconv"
2324
"strings"
2425

2526
gh "github.com/cli/go-gh"
@@ -39,7 +40,7 @@ type PR struct {
3940
Labels []Label `json:"labels"`
4041
}
4142

42-
func (p *PR) Create(repo string) string {
43+
func (p *PR) Create(repo string) (nb int, url string) {
4344
var labels []string
4445
for _, label := range p.Labels {
4546
labels = append(labels, label.Name)
@@ -56,15 +57,28 @@ func (p *PR) Create(repo string) string {
5657
if err != nil {
5758
log.Fatal(err)
5859
}
59-
return strings.ReplaceAll(stdOut.String(), "\n", "")
60+
url = strings.ReplaceAll(stdOut.String(), "\n", "")
61+
nb = URLToNb(url)
62+
return nb, url
6063
}
6164

62-
func FormatPRs(prs []PR) []string {
63-
var prFmt []string
64-
for _, pr := range prs {
65-
prFmt = append(prFmt, fmt.Sprintf(" -> %s %s", pr.URL, pr.Title))
65+
func IsPRMerged(repo string, nb int) bool {
66+
stdOut, _, err := gh.Exec(
67+
"pr", "view", strconv.Itoa(nb),
68+
"--repo", repo,
69+
"--json", "mergedAt",
70+
)
71+
if err != nil {
72+
log.Fatal(err)
6673
}
67-
return prFmt
74+
75+
// If the PR is not merged, the output of the gh command will be:
76+
// {
77+
// "mergedAt": null
78+
// }
79+
//
80+
// We can then grep for "null", if present, the PR has not been merged yet.
81+
return !strings.Contains(stdOut.String(), "null")
6882
}
6983

7084
func CheckBackportToPRs(repo, majorRelease string) []PR {
@@ -95,3 +109,26 @@ func CheckBackportToPRs(repo, majorRelease string) []PR {
95109
}
96110
return mustClose
97111
}
112+
113+
func FindCodeFreezePR(repo, prTitle string) (nb int, url string) {
114+
byteRes, _, err := gh.Exec(
115+
"pr", "list",
116+
"--json", "url",
117+
"--repo", repo,
118+
"--search", prTitle,
119+
"--state", "open",
120+
)
121+
if err != nil {
122+
log.Fatalf(err.Error())
123+
}
124+
var prs []PR
125+
err = json.Unmarshal(byteRes.Bytes(), &prs)
126+
if err != nil {
127+
log.Fatalf(err.Error())
128+
}
129+
if len(prs) != 1 {
130+
return 0, ""
131+
}
132+
url = prs[0].URL
133+
return URLToNb(url), url
134+
}

go/releaser/issue.go

+6-3
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@ type (
5858
ItemWithLink struct {
5959
Done bool
6060

61-
// URL always uses the following format: "#111"
62-
// Expect for GH milestones, where the HTTP
63-
// version is used, such as "https://github.com...."
61+
// URL can use two formats:
62+
// - GH links: "#111"
63+
// - HTTP links: "https://github.com...."
6464
URL string
6565
}
6666

@@ -178,6 +178,9 @@ func (ctx *Context) LoadIssue() {
178178
s = stateReadingNewMilestoneItem
179179
}
180180
}
181+
if strings.Contains(line, postSlackAnnouncementItem) {
182+
newIssue.SlackPostRelease = strings.HasPrefix(line, markdownItemDone)
183+
}
181184
case stateReadingBackport:
182185
newIssue.CheckBackport.Items = append(newIssue.CheckBackport.Items, handleNewListItem(lines, i, &s))
183186
case stateReadingReleaseBlockerIssue:

go/releaser/pre_release/code_freeze.go

+71-4
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ import (
2020
"errors"
2121
"fmt"
2222
"log"
23+
"os"
2324
"os/exec"
25+
"strings"
26+
"time"
2427

2528
"vitess.io/vitess-releaser/go/releaser"
2629
"vitess.io/vitess-releaser/go/releaser/git"
@@ -42,37 +45,92 @@ const (
4245
// Request must be forced-merged by a Vitess maintainer, this step cannot be automated.
4346
func CodeFreeze(ctx *releaser.Context) (*logging.ProgressLogging, func() string) {
4447
pl := &logging.ProgressLogging{
45-
TotalSteps: 6,
48+
TotalSteps: 12,
4649
}
4750

51+
waitForPRToBeMerged := func(nb int) {
52+
pl.NewStepf("Waiting for the PR to be merged. You must enable bypassing the branch protection rules in: https://github.com/vitessio/vitess/settings/branches")
53+
outer:
54+
for {
55+
select {
56+
case <-time.After(5 * time.Second):
57+
if github.IsPRMerged(ctx.VitessRepo, nb) {
58+
break outer
59+
}
60+
}
61+
}
62+
pl.NewStepf("PR has been merged")
63+
}
64+
65+
var done bool
66+
var url string
67+
var nb int
4868
return pl, func() string {
69+
defer func() {
70+
ctx.Issue.CodeFreeze.Done = done
71+
ctx.Issue.CodeFreeze.URL = url
72+
pl.NewStepf("Update Issue %s on GitHub", ctx.IssueLink)
73+
_, fn := ctx.UploadIssue()
74+
issueLink := fn()
75+
76+
pl.NewStepf("Issue updated, see: %s", issueLink)
77+
}()
78+
4979
git.CorrectCleanRepo(ctx.VitessRepo)
5080
nextRelease, branchName := releaser.FindNextRelease(ctx.MajorRelease)
5181

5282
pl.NewStepf("Fetch from git remote")
5383
remote := git.FindRemoteName(ctx.VitessRepo)
5484
git.ResetHard(remote, branchName)
5585

86+
codeFreezePRName := fmt.Sprintf("[%s] Code Freeze for `v%s`", branchName, nextRelease)
87+
88+
// look for existing code freeze PRs
89+
pl.NewStepf("Look for existing an Code Freeze Pull Request")
90+
if nb, url = github.FindCodeFreezePR(ctx.VitessRepo, codeFreezePRName); url != "" {
91+
pl.TotalSteps = 7 // only 7 total steps in this situation
92+
pl.NewStepf("An opened Code Freeze Pull Request was found: %s", url)
93+
waitForPRToBeMerged(nb)
94+
done = true
95+
return url
96+
}
97+
98+
// check if the branch is already frozen or not
99+
pl.NewStepf("Check if branch %s is already frozen", branchName)
100+
if isCurrentBranchFrozen() {
101+
pl.TotalSteps = 6 // only 6 total steps in this situation
102+
pl.NewStepf("Branch %s is already frozen, no action needed.", branchName)
103+
done = true
104+
return ""
105+
}
106+
56107
pl.NewStepf("Create new branch based on %s/%s", remote, branchName)
57108
newBranchName := findNewBranchForCodeFreeze(remote, branchName)
58109

59110
pl.NewStepf("Turn on code freeze on branch %s", newBranchName)
60111
activateCodeFreeze()
61112

62113
pl.NewStepf("Commit and push to branch %s", newBranchName)
63-
git.CommitAll(fmt.Sprintf("Code Freeze of %s", branchName))
114+
if git.CommitAll(fmt.Sprintf("Code Freeze of %s", branchName)) {
115+
pl.TotalSteps = 9 // only 9 total steps in this situation
116+
pl.NewStepf("Nothing to commit, seems like code freeze is already done.", newBranchName)
117+
done = true
118+
return ""
119+
}
64120
git.Push(remote, newBranchName)
65121

66122
pl.NewStepf("Create Pull Request")
67123
pr := github.PR{
68-
Title: fmt.Sprintf("[%s] Code Freeze for `v%s`", branchName, nextRelease),
124+
Title: codeFreezePRName,
69125
Body: fmt.Sprintf("This Pull Request freezes the branch `%s` for `v%s`", branchName, nextRelease),
70126
Branch: newBranchName,
71127
Base: branchName,
72128
Labels: []github.Label{{Name: "Component: General"}, {Name: "Type: Release"}},
73129
}
74-
url := pr.Create(ctx.VitessRepo)
130+
nb, url = pr.Create(ctx.VitessRepo)
75131
pl.NewStepf("PR created %s", url)
132+
waitForPRToBeMerged(nb)
133+
done = true
76134
return url
77135
}
78136
}
@@ -95,6 +153,15 @@ func findNewBranchForCodeFreeze(remote, baseBranch string) string {
95153
return newBranch
96154
}
97155

156+
func isCurrentBranchFrozen() bool {
157+
b, err := os.ReadFile(codeFreezeWorkflowFile)
158+
if err != nil {
159+
log.Fatal(err)
160+
}
161+
str := string(b)
162+
return strings.Contains(str, "exit 1")
163+
}
164+
98165
func activateCodeFreeze() {
99166
changeCodeFreezeWorkflow(codeFreezeActivated)
100167
}

0 commit comments

Comments
 (0)