Skip to content

Commit 4378f9d

Browse files
authored
Revert "Simplify contrib/backport (go-gitea#27520)" (go-gitea#27566)
This reverts go-gitea#27520 commit 79e8865 which breaks `--continue` functionality.
1 parent 248b7ee commit 4378f9d

File tree

1 file changed

+225
-12
lines changed

1 file changed

+225
-12
lines changed

contrib/backport/backport.go

+225-12
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package main
77
import (
88
"context"
99
"fmt"
10+
"log"
1011
"net/http"
1112
"os"
1213
"os/exec"
@@ -18,8 +19,11 @@ import (
1819

1920
"github.com/google/go-github/v53/github"
2021
"github.com/urfave/cli/v2"
22+
"gopkg.in/yaml.v3"
2123
)
2224

25+
const defaultVersion = "v1.18" // to backport to
26+
2327
func main() {
2428
app := cli.NewApp()
2529
app.Name = "backport"
@@ -50,6 +54,16 @@ func main() {
5054
Name: "backport-branch",
5155
Usage: "Backport branch to backport on to (default: backport-<pr>-<version>",
5256
},
57+
&cli.StringFlag{
58+
Name: "remote",
59+
Value: "",
60+
Usage: "Remote for your fork of the Gitea upstream",
61+
},
62+
&cli.StringFlag{
63+
Name: "fork-user",
64+
Value: "",
65+
Usage: "Forked user name on Github",
66+
},
5367
&cli.BoolFlag{
5468
Name: "no-fetch",
5569
Usage: "Set this flag to prevent fetch of remote branches",
@@ -58,6 +72,18 @@ func main() {
5872
Name: "no-amend-message",
5973
Usage: "Set this flag to prevent automatic amendment of the commit message",
6074
},
75+
&cli.BoolFlag{
76+
Name: "no-push",
77+
Usage: "Set this flag to prevent pushing the backport up to your fork",
78+
},
79+
&cli.BoolFlag{
80+
Name: "no-xdg-open",
81+
Usage: "Set this flag to not use xdg-open to open the PR URL",
82+
},
83+
&cli.BoolFlag{
84+
Name: "continue",
85+
Usage: "Set this flag to continue from a git cherry-pick that has broken",
86+
},
6187
}
6288
cli.AppHelpTemplate = `NAME:
6389
{{.Name}} - {{.Usage}}
@@ -75,24 +101,49 @@ OPTIONS:
75101
app.Action = runBackport
76102

77103
if err := app.Run(os.Args); err != nil {
78-
fmt.Fprintf(os.Stderr, "%v\n", err)
104+
fmt.Fprintf(os.Stderr, "Unable to backport: %v\n", err)
79105
}
80106
}
81107

82108
func runBackport(c *cli.Context) error {
83109
ctx, cancel := installSignals()
84110
defer cancel()
85111

112+
continuing := c.Bool("continue")
113+
114+
var pr string
115+
86116
version := c.String("version")
117+
if version == "" && continuing {
118+
// determine version from current branch name
119+
var err error
120+
pr, version, err = readCurrentBranch(ctx)
121+
if err != nil {
122+
return err
123+
}
124+
}
87125
if version == "" {
88-
return fmt.Errorf("Provide a version to backport to")
126+
version = readVersion()
127+
}
128+
if version == "" {
129+
version = defaultVersion
89130
}
90131

91132
upstream := c.String("upstream")
92133
if upstream == "" {
93134
upstream = "origin"
94135
}
95136

137+
forkUser := c.String("fork-user")
138+
remote := c.String("remote")
139+
if remote == "" && !c.Bool("--no-push") {
140+
var err error
141+
remote, forkUser, err = determineRemote(ctx, forkUser)
142+
if err != nil {
143+
return err
144+
}
145+
}
146+
96147
upstreamReleaseBranch := c.String("release-branch")
97148
if upstreamReleaseBranch == "" {
98149
upstreamReleaseBranch = path.Join("release", version)
@@ -101,12 +152,14 @@ func runBackport(c *cli.Context) error {
101152
localReleaseBranch := path.Join(upstream, upstreamReleaseBranch)
102153

103154
args := c.Args().Slice()
104-
if len(args) == 0 {
105-
return fmt.Errorf("Provide a PR number to backport")
106-
} else if len(args) != 1 {
107-
return fmt.Errorf("Only a single PR can be backported at a time")
155+
if len(args) == 0 && pr == "" {
156+
return fmt.Errorf("no PR number provided\nProvide a PR number to backport")
157+
} else if len(args) != 1 && pr == "" {
158+
return fmt.Errorf("multiple PRs provided %v\nOnly a single PR can be backported at a time", args)
159+
}
160+
if pr == "" {
161+
pr = args[0]
108162
}
109-
pr := args[0]
110163

111164
backportBranch := c.String("backport-branch")
112165
if backportBranch == "" {
@@ -133,8 +186,10 @@ func runBackport(c *cli.Context) error {
133186
}
134187
}
135188

136-
if err := checkoutBackportBranch(ctx, backportBranch, localReleaseBranch); err != nil {
137-
return err
189+
if !continuing {
190+
if err := checkoutBackportBranch(ctx, backportBranch, localReleaseBranch); err != nil {
191+
return err
192+
}
138193
}
139194

140195
if err := cherrypick(ctx, sha); err != nil {
@@ -147,8 +202,41 @@ func runBackport(c *cli.Context) error {
147202
}
148203
}
149204

150-
fmt.Printf("Backport done! You can now push it with `git push <your remote> %s`\n", backportBranch)
205+
if !c.Bool("no-push") {
206+
url := "https://github.com/go-gitea/gitea/compare/" + upstreamReleaseBranch + "..." + forkUser + ":" + backportBranch
207+
208+
if err := gitPushUp(ctx, remote, backportBranch); err != nil {
209+
return err
210+
}
211+
212+
if !c.Bool("no-xdg-open") {
213+
if err := xdgOpen(ctx, url); err != nil {
214+
return err
215+
}
216+
} else {
217+
fmt.Printf("* Navigate to %s to open PR\n", url)
218+
}
219+
}
220+
return nil
221+
}
222+
223+
func xdgOpen(ctx context.Context, url string) error {
224+
fmt.Printf("* `xdg-open %s`\n", url)
225+
out, err := exec.CommandContext(ctx, "xdg-open", url).Output()
226+
if err != nil {
227+
fmt.Fprintf(os.Stderr, "%s", string(out))
228+
return fmt.Errorf("unable to xdg-open to %s: %w", url, err)
229+
}
230+
return nil
231+
}
151232

233+
func gitPushUp(ctx context.Context, remote, backportBranch string) error {
234+
fmt.Printf("* `git push -u %s %s`\n", remote, backportBranch)
235+
out, err := exec.CommandContext(ctx, "git", "push", "-u", remote, backportBranch).Output()
236+
if err != nil {
237+
fmt.Fprintf(os.Stderr, "%s", string(out))
238+
return fmt.Errorf("unable to push up to %s: %w", remote, err)
239+
}
152240
return nil
153241
}
154242

@@ -179,6 +267,18 @@ func amendCommit(ctx context.Context, pr string) error {
179267
}
180268

181269
func cherrypick(ctx context.Context, sha string) error {
270+
// Check if a CHERRY_PICK_HEAD exists
271+
if _, err := os.Stat(".git/CHERRY_PICK_HEAD"); err == nil {
272+
// Assume that we are in the middle of cherry-pick - continue it
273+
fmt.Println("* Attempting git cherry-pick --continue")
274+
out, err := exec.CommandContext(ctx, "git", "cherry-pick", "--continue").Output()
275+
if err != nil {
276+
fmt.Fprintf(os.Stderr, "git cherry-pick --continue failed:\n%s\n", string(out))
277+
return fmt.Errorf("unable to continue cherry-pick: %w", err)
278+
}
279+
return nil
280+
}
281+
182282
fmt.Printf("* Attempting git cherry-pick %s\n", sha)
183283
out, err := exec.CommandContext(ctx, "git", "cherry-pick", sha).Output()
184284
if err != nil {
@@ -189,8 +289,22 @@ func cherrypick(ctx context.Context, sha string) error {
189289
}
190290

191291
func checkoutBackportBranch(ctx context.Context, backportBranch, releaseBranch string) error {
192-
fmt.Printf("* `git branch -D %s`\n", backportBranch)
193-
_ = exec.CommandContext(ctx, "git", "branch", "-D", backportBranch).Run()
292+
out, err := exec.CommandContext(ctx, "git", "branch", "--show-current").Output()
293+
if err != nil {
294+
return fmt.Errorf("unable to check current branch %w", err)
295+
}
296+
297+
currentBranch := strings.TrimSpace(string(out))
298+
fmt.Printf("* Current branch is %s\n", currentBranch)
299+
if currentBranch == backportBranch {
300+
fmt.Printf("* Current branch is %s - not checking out\n", currentBranch)
301+
return nil
302+
}
303+
304+
if _, err := exec.CommandContext(ctx, "git", "rev-list", "-1", backportBranch).Output(); err == nil {
305+
fmt.Printf("* Branch %s already exists. Checking it out...\n", backportBranch)
306+
return exec.CommandContext(ctx, "git", "checkout", "-f", backportBranch).Run()
307+
}
194308

195309
fmt.Printf("* `git checkout -b %s %s`\n", backportBranch, releaseBranch)
196310
return exec.CommandContext(ctx, "git", "checkout", "-b", backportBranch, releaseBranch).Run()
@@ -203,17 +317,116 @@ func fetchRemoteAndMain(ctx context.Context, remote, releaseBranch string) error
203317
fmt.Println(string(out))
204318
return fmt.Errorf("unable to fetch %s from %s: %w", "main", remote, err)
205319
}
320+
fmt.Println(string(out))
206321

207322
fmt.Printf("* `git fetch %s %s`\n", remote, releaseBranch)
208323
out, err = exec.CommandContext(ctx, "git", "fetch", remote, releaseBranch).Output()
209324
if err != nil {
210325
fmt.Println(string(out))
211326
return fmt.Errorf("unable to fetch %s from %s: %w", releaseBranch, remote, err)
212327
}
328+
fmt.Println(string(out))
213329

214330
return nil
215331
}
216332

333+
func determineRemote(ctx context.Context, forkUser string) (string, string, error) {
334+
out, err := exec.CommandContext(ctx, "git", "remote", "-v").Output()
335+
if err != nil {
336+
fmt.Fprintf(os.Stderr, "Unable to list git remotes:\n%s\n", string(out))
337+
return "", "", fmt.Errorf("unable to determine forked remote: %w", err)
338+
}
339+
lines := strings.Split(string(out), "\n")
340+
for _, line := range lines {
341+
fields := strings.Split(line, "\t")
342+
name, remote := fields[0], fields[1]
343+
// only look at pushers
344+
if !strings.HasSuffix(remote, " (push)") {
345+
continue
346+
}
347+
// only look at github.com pushes
348+
if !strings.Contains(remote, "github.com") {
349+
continue
350+
}
351+
// ignore go-gitea/gitea
352+
if strings.Contains(remote, "go-gitea/gitea") {
353+
continue
354+
}
355+
if !strings.Contains(remote, forkUser) {
356+
continue
357+
}
358+
if strings.HasPrefix(remote, "[email protected]:") {
359+
forkUser = strings.TrimPrefix(remote, "[email protected]:")
360+
} else if strings.HasPrefix(remote, "https://github.com/") {
361+
forkUser = strings.TrimPrefix(remote, "https://github.com/")
362+
} else if strings.HasPrefix(remote, "https://www.github.com/") {
363+
forkUser = strings.TrimPrefix(remote, "https://www.github.com/")
364+
} else if forkUser == "" {
365+
return "", "", fmt.Errorf("unable to extract forkUser from remote %s: %s", name, remote)
366+
}
367+
idx := strings.Index(forkUser, "/")
368+
if idx >= 0 {
369+
forkUser = forkUser[:idx]
370+
}
371+
return name, forkUser, nil
372+
}
373+
return "", "", fmt.Errorf("unable to find appropriate remote in:\n%s", string(out))
374+
}
375+
376+
func readCurrentBranch(ctx context.Context) (pr, version string, err error) {
377+
out, err := exec.CommandContext(ctx, "git", "branch", "--show-current").Output()
378+
if err != nil {
379+
fmt.Fprintf(os.Stderr, "Unable to read current git branch:\n%s\n", string(out))
380+
return "", "", fmt.Errorf("unable to read current git branch: %w", err)
381+
}
382+
parts := strings.Split(strings.TrimSpace(string(out)), "-")
383+
384+
if len(parts) != 3 || parts[0] != "backport" {
385+
fmt.Fprintf(os.Stderr, "Unable to continue from git branch:\n%s\n", string(out))
386+
return "", "", fmt.Errorf("unable to continue from git branch:\n%s", string(out))
387+
}
388+
389+
return parts[1], parts[2], nil
390+
}
391+
392+
func readVersion() string {
393+
bs, err := os.ReadFile("docs/config.yaml")
394+
if err != nil {
395+
if err == os.ErrNotExist {
396+
log.Println("`docs/config.yaml` not present")
397+
return ""
398+
}
399+
fmt.Fprintf(os.Stderr, "Unable to read `docs/config.yaml`: %v\n", err)
400+
return ""
401+
}
402+
403+
type params struct {
404+
Version string
405+
}
406+
type docConfig struct {
407+
Params params
408+
}
409+
dc := &docConfig{}
410+
if err := yaml.Unmarshal(bs, dc); err != nil {
411+
fmt.Fprintf(os.Stderr, "Unable to read `docs/config.yaml`: %v\n", err)
412+
return ""
413+
}
414+
415+
if dc.Params.Version == "" {
416+
fmt.Fprintf(os.Stderr, "No version in `docs/config.yaml`")
417+
return ""
418+
}
419+
420+
version := dc.Params.Version
421+
if version[0] != 'v' {
422+
version = "v" + version
423+
}
424+
425+
split := strings.SplitN(version, ".", 3)
426+
427+
return strings.Join(split[:2], ".")
428+
}
429+
217430
func determineSHAforPR(ctx context.Context, prStr string) (string, error) {
218431
prNum, err := strconv.Atoi(prStr)
219432
if err != nil {

0 commit comments

Comments
 (0)