Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify contrib/backport #27520

Merged
merged 4 commits into from
Oct 9, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
237 changes: 12 additions & 225 deletions contrib/backport/backport.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/exec"
Expand All @@ -19,11 +18,8 @@ import (

"github.com/google/go-github/v53/github"
"github.com/urfave/cli/v2"
"gopkg.in/yaml.v3"
)

const defaultVersion = "v1.18" // to backport to

func main() {
app := cli.NewApp()
app.Name = "backport"
Expand Down Expand Up @@ -54,16 +50,6 @@ func main() {
Name: "backport-branch",
Usage: "Backport branch to backport on to (default: backport-<pr>-<version>",
},
&cli.StringFlag{
Name: "remote",
Value: "",
Usage: "Remote for your fork of the Gitea upstream",
},
&cli.StringFlag{
Name: "fork-user",
Value: "",
Usage: "Forked user name on Github",
},
&cli.BoolFlag{
Name: "no-fetch",
Usage: "Set this flag to prevent fetch of remote branches",
Expand All @@ -72,18 +58,6 @@ func main() {
Name: "no-amend-message",
Usage: "Set this flag to prevent automatic amendment of the commit message",
},
&cli.BoolFlag{
Name: "no-push",
Usage: "Set this flag to prevent pushing the backport up to your fork",
},
&cli.BoolFlag{
Name: "no-xdg-open",
Usage: "Set this flag to not use xdg-open to open the PR URL",
},
&cli.BoolFlag{
Name: "continue",
Usage: "Set this flag to continue from a git cherry-pick that has broken",
},
}
cli.AppHelpTemplate = `NAME:
{{.Name}} - {{.Usage}}
Expand All @@ -101,49 +75,24 @@ OPTIONS:
app.Action = runBackport

if err := app.Run(os.Args); err != nil {
fmt.Fprintf(os.Stderr, "Unable to backport: %v\n", err)
fmt.Fprintf(os.Stderr, "%v\n", err)
}
}

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

continuing := c.Bool("continue")

var pr string

version := c.String("version")
if version == "" && continuing {
// determine version from current branch name
var err error
pr, version, err = readCurrentBranch(ctx)
if err != nil {
return err
}
}
if version == "" {
version = readVersion()
}
if version == "" {
version = defaultVersion
return fmt.Errorf("Provide a version to backport to")
}

upstream := c.String("upstream")
if upstream == "" {
upstream = "origin"
}

forkUser := c.String("fork-user")
remote := c.String("remote")
if remote == "" && !c.Bool("--no-push") {
var err error
remote, forkUser, err = determineRemote(ctx, forkUser)
if err != nil {
return err
}
}

upstreamReleaseBranch := c.String("release-branch")
if upstreamReleaseBranch == "" {
upstreamReleaseBranch = path.Join("release", version)
Expand All @@ -152,14 +101,12 @@ func runBackport(c *cli.Context) error {
localReleaseBranch := path.Join(upstream, upstreamReleaseBranch)

args := c.Args().Slice()
if len(args) == 0 && pr == "" {
return fmt.Errorf("no PR number provided\nProvide a PR number to backport")
} else if len(args) != 1 && pr == "" {
return fmt.Errorf("multiple PRs provided %v\nOnly a single PR can be backported at a time", args)
}
if pr == "" {
pr = args[0]
if len(args) == 0 {
return fmt.Errorf("Provide a PR number to backport")
} else if len(args) != 1 {
return fmt.Errorf("Only a single PR can be backported at a time")
}
pr := args[0]

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

if !continuing {
if err := checkoutBackportBranch(ctx, backportBranch, localReleaseBranch); err != nil {
return err
}
if err := checkoutBackportBranch(ctx, backportBranch, localReleaseBranch); err != nil {
return err
}

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

if !c.Bool("no-push") {
url := "https://github.com/go-gitea/gitea/compare/" + upstreamReleaseBranch + "..." + forkUser + ":" + backportBranch

if err := gitPushUp(ctx, remote, backportBranch); err != nil {
return err
}

if !c.Bool("no-xdg-open") {
if err := xdgOpen(ctx, url); err != nil {
return err
}
} else {
fmt.Printf("* Navigate to %s to open PR\n", url)
}
}
return nil
}

func xdgOpen(ctx context.Context, url string) error {
fmt.Printf("* `xdg-open %s`\n", url)
out, err := exec.CommandContext(ctx, "xdg-open", url).Output()
if err != nil {
fmt.Fprintf(os.Stderr, "%s", string(out))
return fmt.Errorf("unable to xdg-open to %s: %w", url, err)
}
return nil
}
fmt.Printf("Backport done! You can now push it with `git push <your remote> %s`\n", backportBranch)

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

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

func cherrypick(ctx context.Context, sha string) error {
// Check if a CHERRY_PICK_HEAD exists
if _, err := os.Stat(".git/CHERRY_PICK_HEAD"); err == nil {
// Assume that we are in the middle of cherry-pick - continue it
fmt.Println("* Attempting git cherry-pick --continue")
out, err := exec.CommandContext(ctx, "git", "cherry-pick", "--continue").Output()
if err != nil {
fmt.Fprintf(os.Stderr, "git cherry-pick --continue failed:\n%s\n", string(out))
return fmt.Errorf("unable to continue cherry-pick: %w", err)
}
return nil
}

fmt.Printf("* Attempting git cherry-pick %s\n", sha)
out, err := exec.CommandContext(ctx, "git", "cherry-pick", sha).Output()
if err != nil {
Expand All @@ -289,22 +189,8 @@ func cherrypick(ctx context.Context, sha string) error {
}

func checkoutBackportBranch(ctx context.Context, backportBranch, releaseBranch string) error {
out, err := exec.CommandContext(ctx, "git", "branch", "--show-current").Output()
if err != nil {
return fmt.Errorf("unable to check current branch %w", err)
}

currentBranch := strings.TrimSpace(string(out))
fmt.Printf("* Current branch is %s\n", currentBranch)
if currentBranch == backportBranch {
fmt.Printf("* Current branch is %s - not checking out\n", currentBranch)
return nil
}

if _, err := exec.CommandContext(ctx, "git", "rev-list", "-1", backportBranch).Output(); err == nil {
fmt.Printf("* Branch %s already exists. Checking it out...\n", backportBranch)
return exec.CommandContext(ctx, "git", "checkout", "-f", backportBranch).Run()
}
fmt.Printf("* `git branch -D %s`\n", backportBranch)
_ = exec.CommandContext(ctx, "git", "branch", "-D", backportBranch).Run()

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

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

return nil
}

func determineRemote(ctx context.Context, forkUser string) (string, string, error) {
out, err := exec.CommandContext(ctx, "git", "remote", "-v").Output()
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to list git remotes:\n%s\n", string(out))
return "", "", fmt.Errorf("unable to determine forked remote: %w", err)
}
lines := strings.Split(string(out), "\n")
for _, line := range lines {
fields := strings.Split(line, "\t")
name, remote := fields[0], fields[1]
// only look at pushers
if !strings.HasSuffix(remote, " (push)") {
continue
}
// only look at github.com pushes
if !strings.Contains(remote, "github.com") {
continue
}
// ignore go-gitea/gitea
if strings.Contains(remote, "go-gitea/gitea") {
continue
}
if !strings.Contains(remote, forkUser) {
continue
}
if strings.HasPrefix(remote, "[email protected]:") {
forkUser = strings.TrimPrefix(remote, "[email protected]:")
} else if strings.HasPrefix(remote, "https://github.com/") {
forkUser = strings.TrimPrefix(remote, "https://github.com/")
} else if strings.HasPrefix(remote, "https://www.github.com/") {
forkUser = strings.TrimPrefix(remote, "https://www.github.com/")
} else if forkUser == "" {
return "", "", fmt.Errorf("unable to extract forkUser from remote %s: %s", name, remote)
}
idx := strings.Index(forkUser, "/")
if idx >= 0 {
forkUser = forkUser[:idx]
}
return name, forkUser, nil
}
return "", "", fmt.Errorf("unable to find appropriate remote in:\n%s", string(out))
}

func readCurrentBranch(ctx context.Context) (pr, version string, err error) {
out, err := exec.CommandContext(ctx, "git", "branch", "--show-current").Output()
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to read current git branch:\n%s\n", string(out))
return "", "", fmt.Errorf("unable to read current git branch: %w", err)
}
parts := strings.Split(strings.TrimSpace(string(out)), "-")

if len(parts) != 3 || parts[0] != "backport" {
fmt.Fprintf(os.Stderr, "Unable to continue from git branch:\n%s\n", string(out))
return "", "", fmt.Errorf("unable to continue from git branch:\n%s", string(out))
}

return parts[1], parts[2], nil
}

func readVersion() string {
bs, err := os.ReadFile("docs/config.yaml")
if err != nil {
if err == os.ErrNotExist {
log.Println("`docs/config.yaml` not present")
return ""
}
fmt.Fprintf(os.Stderr, "Unable to read `docs/config.yaml`: %v\n", err)
return ""
}

type params struct {
Version string
}
type docConfig struct {
Params params
}
dc := &docConfig{}
if err := yaml.Unmarshal(bs, dc); err != nil {
fmt.Fprintf(os.Stderr, "Unable to read `docs/config.yaml`: %v\n", err)
return ""
}

if dc.Params.Version == "" {
fmt.Fprintf(os.Stderr, "No version in `docs/config.yaml`")
return ""
}

version := dc.Params.Version
if version[0] != 'v' {
version = "v" + version
}

split := strings.SplitN(version, ".", 3)

return strings.Join(split[:2], ".")
}

func determineSHAforPR(ctx context.Context, prStr string) (string, error) {
prNum, err := strconv.Atoi(prStr)
if err != nil {
Expand Down