Polish and cleanup, move to worktree
ci/woodpecker/push/goreleaser Pipeline was successful
Details
ci/woodpecker/push/goreleaser Pipeline was successful
Details
Signed-off-by: jolheiser <john.olheiser@gmail.com>main
parent
86ce1bed45
commit
9c0fec9616
|
@ -0,0 +1,167 @@
|
||||||
|
# git-ea
|
||||||
|
|
||||||
|
git-ea is the base command
|
||||||
|
|
||||||
|
```
|
||||||
|
git-ea
|
||||||
|
├─ backport
|
||||||
|
├─ branch
|
||||||
|
├─ frontport
|
||||||
|
└─ init
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
[--help]
|
||||||
|
[--v]
|
||||||
|
[--version]
|
||||||
|
```
|
||||||
|
**Usage**:
|
||||||
|
|
||||||
|
```
|
||||||
|
git-ea <cmd>
|
||||||
|
```
|
||||||
|
|
||||||
|
**--help**: Show help
|
||||||
|
|
||||||
|
|
||||||
|
**--v**: --version
|
||||||
|
|
||||||
|
|
||||||
|
**--version**: Print git-ea version
|
||||||
|
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
## backport
|
||||||
|
|
||||||
|
backport cherry-picks a commit and applies it to a clean branch based on `release`
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
[--f]=[value]
|
||||||
|
[--from]=[value]
|
||||||
|
[--help]
|
||||||
|
[--l]
|
||||||
|
[--list]
|
||||||
|
[--t]=[value]
|
||||||
|
[--to]=[value]
|
||||||
|
```
|
||||||
|
**Usage**:
|
||||||
|
|
||||||
|
```
|
||||||
|
backport --from [release=main] --to [release=latest]
|
||||||
|
```
|
||||||
|
|
||||||
|
**--f**="": --from
|
||||||
|
|
||||||
|
|
||||||
|
**--from**="": Release to backport from (ex: `main`, default: main)
|
||||||
|
|
||||||
|
|
||||||
|
**--help**: Show help
|
||||||
|
|
||||||
|
|
||||||
|
**--l**: --list
|
||||||
|
|
||||||
|
|
||||||
|
**--list**: Open repository to see needed backports
|
||||||
|
|
||||||
|
|
||||||
|
**--t**="": --to
|
||||||
|
|
||||||
|
|
||||||
|
**--to**="": Release to backport to (ex: `1.17`, default: `latest`)
|
||||||
|
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
## branch
|
||||||
|
|
||||||
|
branch creates a new branch called `name` based on `base`
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
[--b]=[value]
|
||||||
|
[--base]=[value]
|
||||||
|
[--help]
|
||||||
|
[--nf]
|
||||||
|
[--no-fetch]
|
||||||
|
```
|
||||||
|
**Usage**:
|
||||||
|
|
||||||
|
```
|
||||||
|
branch --base [ref=main] <name>
|
||||||
|
```
|
||||||
|
|
||||||
|
**--b**="": --base (default: `main`)
|
||||||
|
|
||||||
|
|
||||||
|
**--base**="": Ref to base from (default: `main`)
|
||||||
|
|
||||||
|
|
||||||
|
**--help**: Show help
|
||||||
|
|
||||||
|
|
||||||
|
**--nf**: --no-fetch
|
||||||
|
|
||||||
|
|
||||||
|
**--no-fetch**: Skip fetching
|
||||||
|
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
## frontport
|
||||||
|
|
||||||
|
frontport cherry-picks a commit and applies it to a clean branch based on `release`
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
[--f]=[value]
|
||||||
|
[--from]=[value]
|
||||||
|
[--help]
|
||||||
|
[--t]=[value]
|
||||||
|
[--to]=[value]
|
||||||
|
```
|
||||||
|
**Usage**:
|
||||||
|
|
||||||
|
```
|
||||||
|
frontport --from [release=latest] --to [release=main]
|
||||||
|
```
|
||||||
|
|
||||||
|
**--f**="": --from
|
||||||
|
|
||||||
|
|
||||||
|
**--from**="": Release to frontport from (ex: `1.17`, default: <latest>)
|
||||||
|
|
||||||
|
|
||||||
|
**--help**: Show help
|
||||||
|
|
||||||
|
|
||||||
|
**--t**="": --to
|
||||||
|
|
||||||
|
|
||||||
|
**--to**="": Release to frontport to (ex: `main`, default: `main`)
|
||||||
|
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
## init
|
||||||
|
|
||||||
|
init initializes a workspace for Gitea
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
[--help]
|
||||||
|
```
|
||||||
|
**Usage**:
|
||||||
|
|
||||||
|
```
|
||||||
|
init
|
||||||
|
```
|
||||||
|
|
||||||
|
**--help**: Show help
|
||||||
|
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
|
@ -15,42 +15,49 @@ import (
|
||||||
"github.com/skratchdot/open-golang/open"
|
"github.com/skratchdot/open-golang/open"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
func (h *Handler) Backport() *ffcli.Command {
|
||||||
backportFS = flag.NewFlagSet("backport", flag.ContinueOnError)
|
fs := flag.NewFlagSet("backport", flag.ContinueOnError)
|
||||||
backportFromFlag = backportFS.String("from", "", "Release to backport from (ex: `main`, default: main)")
|
fromFlag := fs.String("from", "", "Release to backport from (ex: `main`, default: main)")
|
||||||
backportToFlag = backportFS.String("to", "", "Release to backport to (ex: `1.17`, default: <latest>)")
|
fs.StringVar(fromFlag, "f", *fromFlag, "--from")
|
||||||
backportListFlag = backportFS.Bool("list", false, "Open repository to see needed backports")
|
toFlag := fs.String("to", "", "Release to backport to (ex: `1.17`, default: `latest`)")
|
||||||
Backport = &ffcli.Command{
|
fs.StringVar(toFlag, "t", *toFlag, "--to")
|
||||||
|
listFlag := fs.Bool("list", false, "Open repository to see needed backports")
|
||||||
|
fs.BoolVar(listFlag, "l", *listFlag, "--list")
|
||||||
|
return &ffcli.Command{
|
||||||
Name: "backport",
|
Name: "backport",
|
||||||
FlagSet: backportFS,
|
FlagSet: fs,
|
||||||
ShortUsage: "backport --from [release=main] --to [release=latest]",
|
ShortUsage: "backport --from [release=main] --to [release=latest]",
|
||||||
ShortHelp: "backport cherry-picks a <commit> and applies it to a clean branch based on <release>",
|
ShortHelp: "backport cherry-picks a commit and applies it to a clean branch based on `release`",
|
||||||
Exec: func(ctx context.Context, _ []string) error {
|
Exec: func(ctx context.Context, _ []string) error {
|
||||||
to := *backportToFlag
|
if err := h.checkInit(); err != nil {
|
||||||
if to == "" {
|
return err
|
||||||
to = latestRelease()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if *backportListFlag {
|
to := *toFlag
|
||||||
|
if to == "" {
|
||||||
|
to = h.latestRelease()
|
||||||
|
}
|
||||||
|
|
||||||
|
if *listFlag {
|
||||||
if err := open.Run(fmt.Sprintf("https://github.com/go-gitea/gitea/pulls?q=is%%3Apr+-label%%3Abackport%%2Fdone+label%%3Abackport%%2Fv1.%s+is%%3Amerged", to)); err != nil {
|
if err := open.Run(fmt.Sprintf("https://github.com/go-gitea/gitea/pulls?q=is%%3Apr+-label%%3Abackport%%2Fdone+label%%3Abackport%%2Fv1.%s+is%%3Amerged", to)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(ctx)
|
h.fetch(ctx)
|
||||||
|
|
||||||
if !isClean() {
|
if isClean() {
|
||||||
log.Fatal().Msg("working tree is dirty")
|
log.Fatal().Msg("working tree is dirty")
|
||||||
}
|
}
|
||||||
|
|
||||||
from := *backportFromFlag
|
from := *fromFlag
|
||||||
if from == "" {
|
if from == "" {
|
||||||
from = "main"
|
from = "main"
|
||||||
}
|
}
|
||||||
|
|
||||||
commits, err := repo().Log(&git.LogOptions{
|
commits, err := h.repo().Log(&git.LogOptions{
|
||||||
From: head(from),
|
From: h.head(from),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -78,11 +85,11 @@ var (
|
||||||
hash := optMap[resp]
|
hash := optMap[resp]
|
||||||
branch := fmt.Sprintf("backport-%s", hash)
|
branch := fmt.Sprintf("backport-%s", hash)
|
||||||
base := fmt.Sprintf("upstream/release/v1.%s", to)
|
base := fmt.Sprintf("upstream/release/v1.%s", to)
|
||||||
if err := Branch.ParseAndRun(ctx, []string{"--base", base, branch}); err != nil {
|
if err := h.Branch().ParseAndRun(ctx, []string{"--base", base, branch}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return run(ctx, "git", "cherry-pick", hash.String())
|
return run(ctx, h.Config.WorkspaceBranch(branch), "git", "cherry-pick", hash.String())
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
|
@ -4,40 +4,44 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/peterbourgon/ff/v3/ffcli"
|
"github.com/peterbourgon/ff/v3/ffcli"
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
func (h *Handler) Branch() *ffcli.Command {
|
||||||
branchFS = flag.NewFlagSet("branch", flag.ContinueOnError)
|
fs := flag.NewFlagSet("branch", flag.ContinueOnError)
|
||||||
branchNoFetchFlag = branchFS.Bool("no-fetch", false, "Skip fetching")
|
noFetchFlag := fs.Bool("no-fetch", false, "Skip fetching")
|
||||||
branchBaseFlag = branchFS.String("base", "main", "Ref to base from")
|
fs.BoolVar(noFetchFlag, "nf", *noFetchFlag, "--no-fetch")
|
||||||
Branch = &ffcli.Command{
|
baseFlag := fs.String("base", "main", "Ref to base from")
|
||||||
|
fs.StringVar(baseFlag, "b", *baseFlag, "--base")
|
||||||
|
return &ffcli.Command{
|
||||||
Name: "branch",
|
Name: "branch",
|
||||||
FlagSet: branchFS,
|
FlagSet: fs,
|
||||||
ShortUsage: "branch --base [ref=main] <name>",
|
ShortUsage: "branch --base [ref=main] <name>",
|
||||||
ShortHelp: "branch creates a new branch called <name> based on <base>",
|
ShortHelp: "branch creates a new branch called `name` based on `base`",
|
||||||
Exec: func(ctx context.Context, args []string) error {
|
Exec: func(ctx context.Context, args []string) error {
|
||||||
|
if err := h.checkInit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
return errors.New("branch requires one argument")
|
return errors.New("branch requires a name")
|
||||||
}
|
}
|
||||||
|
|
||||||
name := args[0]
|
name := args[0]
|
||||||
base := *branchBaseFlag
|
base := *baseFlag
|
||||||
if base == "" {
|
if !strings.HasPrefix(base, "upstream") {
|
||||||
base = "upstream/main"
|
base = fmt.Sprintf("upstream/%s", base)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isClean() {
|
if !*noFetchFlag {
|
||||||
log.Fatal().Msg("working tree is dirty")
|
h.fetch(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !*branchNoFetchFlag {
|
return h.run(ctx, "git", "worktree", "add", "-B", name, filepath.Join(h.Config.Workspace(), name), base)
|
||||||
fetch(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
return run(ctx, "git", "checkout", "-b", name, base)
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/AlecAivazis/survey/v2"
|
||||||
|
"github.com/peterbourgon/ff/v3/ffcli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *Handler) Cleanup() *ffcli.Command {
|
||||||
|
fs := flag.NewFlagSet("cleanup", flag.ContinueOnError)
|
||||||
|
return &ffcli.Command{
|
||||||
|
Name: "cleanup",
|
||||||
|
FlagSet: fs,
|
||||||
|
ShortUsage: "cleanup [branches...]",
|
||||||
|
ShortHelp: "cleanup removes named branches, or interactive if no arguments",
|
||||||
|
Exec: func(ctx context.Context, args []string) error {
|
||||||
|
if err := h.checkInit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) > 0 {
|
||||||
|
for _, arg := range args {
|
||||||
|
if err := removeWorktree(h, ctx, arg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dirs, err := os.ReadDir(h.Config.Workspace())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := make([]string, 0, len(dirs))
|
||||||
|
for _, dir := range dirs {
|
||||||
|
if !dir.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
opts = append(opts, dir.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(opts) == 0 {
|
||||||
|
return errors.New("no worktrees currently exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
var remove []string
|
||||||
|
if err := survey.AskOne(&survey.MultiSelect{
|
||||||
|
Message: "Worktrees to remove",
|
||||||
|
Options: opts,
|
||||||
|
}, &remove); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rm := range remove {
|
||||||
|
if err := removeWorktree(h, ctx, rm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeWorktree(h *Handler, ctx context.Context, name string) error {
|
||||||
|
if err := h.run(ctx, "git", "worktree", "remove", name); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return h.run(ctx, "git", "branch", "-D", name)
|
||||||
|
}
|
90
cmd/cmd.go
90
cmd/cmd.go
|
@ -2,26 +2,93 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"go.jolheiser.com/git-ea/config"
|
||||||
|
|
||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
|
"github.com/peterbourgon/ff/v3/ffcli"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func run(ctx context.Context, cmd string, args ...string) error {
|
var Version = "x.y.z"
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
Config *config.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() (*ffcli.Command, error) {
|
||||||
|
cfg, err := config.Load()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := Handler{
|
||||||
|
Config: cfg,
|
||||||
|
}
|
||||||
|
|
||||||
|
fs := flag.NewFlagSet("git-ea", flag.ContinueOnError)
|
||||||
|
versionFlag := fs.Bool("version", false, "Print git-ea version")
|
||||||
|
fs.BoolVar(versionFlag, "v", *versionFlag, "--version")
|
||||||
|
|
||||||
|
return &ffcli.Command{
|
||||||
|
Name: "git-ea",
|
||||||
|
FlagSet: fs,
|
||||||
|
ShortUsage: "git-ea <cmd>",
|
||||||
|
ShortHelp: "git-ea is the base command",
|
||||||
|
Subcommands: []*ffcli.Command{
|
||||||
|
handler.Cleanup(),
|
||||||
|
handler.Backport(),
|
||||||
|
handler.Branch(),
|
||||||
|
handler.Frontport(),
|
||||||
|
handler.Init(),
|
||||||
|
},
|
||||||
|
Exec: func(_ context.Context, _ []string) error {
|
||||||
|
if *versionFlag {
|
||||||
|
log.Info().Msgf("git-ea v%s", Version)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dir := cfg.Base
|
||||||
|
if fs.NArg() > 0 {
|
||||||
|
dir = cfg.WorkspaceBranch(fs.Arg(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(dir)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) checkInit() error {
|
||||||
|
if !h.Config.IsInit() {
|
||||||
|
return errors.New("git-ea must be initialized first with `git ea init` in an appropriate directory")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) run(ctx context.Context, cmd string, args ...string) error {
|
||||||
|
return run(ctx, h.Config.Base, cmd, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(ctx context.Context, dir, cmd string, args ...string) error {
|
||||||
c := exec.CommandContext(ctx, cmd, args...)
|
c := exec.CommandContext(ctx, cmd, args...)
|
||||||
|
c.Dir = dir
|
||||||
c.Stdout = os.Stdout
|
c.Stdout = os.Stdout
|
||||||
c.Stderr = os.Stderr
|
c.Stderr = os.Stderr
|
||||||
c.Stdin = os.Stdin
|
c.Stdin = os.Stdin
|
||||||
return c.Run()
|
return c.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetch(ctx context.Context) {
|
func (h *Handler) fetch(ctx context.Context) {
|
||||||
if err := run(ctx, "git", "fetch", "upstream"); err != nil {
|
if err := run(ctx, h.Config.Base, "git", "fetch", "upstream"); err != nil {
|
||||||
log.Err(err).Msg("")
|
log.Err(err).Msg("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,26 +102,24 @@ func isClean() bool {
|
||||||
return len(o) == 0
|
return len(o) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func repo() *git.Repository {
|
func (h *Handler) repo() *git.Repository {
|
||||||
repo, err := git.PlainOpenWithOptions(".", &git.PlainOpenOptions{
|
repo, err := git.PlainOpen(h.Config.Base)
|
||||||
DetectDotGit: true,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("cannot open git repository")
|
log.Fatal().Err(err).Msg("cannot open git repository")
|
||||||
}
|
}
|
||||||
return repo
|
return repo
|
||||||
}
|
}
|
||||||
|
|
||||||
func worktree() *git.Worktree {
|
func (h *Handler) worktree() *git.Worktree {
|
||||||
tree, err := repo().Worktree()
|
tree, err := h.repo().Worktree()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("cannot get git worktree")
|
log.Fatal().Err(err).Msg("cannot get git worktree")
|
||||||
}
|
}
|
||||||
return tree
|
return tree
|
||||||
}
|
}
|
||||||
|
|
||||||
func head(name string) plumbing.Hash {
|
func (h *Handler) head(name string) plumbing.Hash {
|
||||||
ref, err := repo().Reference(plumbing.NewRemoteReferenceName("upstream", name), false)
|
ref, err := h.repo().Reference(plumbing.NewRemoteReferenceName("upstream", name), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msgf("cannot get remote upstream %s HEAD", name)
|
log.Fatal().Err(err).Msgf("cannot get remote upstream %s HEAD", name)
|
||||||
}
|
}
|
||||||
|
@ -63,8 +128,9 @@ func head(name string) plumbing.Hash {
|
||||||
|
|
||||||
var releaseRe = regexp.MustCompile(`release/v1\.(\d+)`)
|
var releaseRe = regexp.MustCompile(`release/v1\.(\d+)`)
|
||||||
|
|
||||||
func latestRelease() string {
|
func (h *Handler) latestRelease() string {
|
||||||
cmd := exec.Command("git", "ls-remote", "upstream", "release/*")
|
cmd := exec.Command("git", "ls-remote", "upstream", "release/*")
|
||||||
|
cmd.Dir = h.Config.Base
|
||||||
out, err := cmd.Output()
|
out, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("could not get latest release")
|
log.Fatal().Err(err).Msg("could not get latest release")
|
||||||
|
|
|
@ -14,32 +14,38 @@ import (
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
func (h *Handler) Frontport() *ffcli.Command {
|
||||||
frontportFS = flag.NewFlagSet("frontport", flag.ContinueOnError)
|
fs := flag.NewFlagSet("frontport", flag.ContinueOnError)
|
||||||
frontportFromFlag = frontportFS.String("from", "", "Release to frontport from (ex: `1.17`, default: <latest>)")
|
fromFlag := fs.String("from", "", "Release to frontport from (ex: `1.17`, default: <latest>)")
|
||||||
frontportToFlag = frontportFS.String("to", "", "Release to frontport to (ex: `main`, default: `main`)")
|
fs.StringVar(fromFlag, "f", *fromFlag, "--from")
|
||||||
Frontport = &ffcli.Command{
|
toFlag := fs.String("to", "", "Release to frontport to (ex: `main`, default: `main`)")
|
||||||
|
fs.StringVar(toFlag, "t", *toFlag, "--to")
|
||||||
|
return &ffcli.Command{
|
||||||
Name: "frontport",
|
Name: "frontport",
|
||||||
FlagSet: frontportFS,
|
FlagSet: fs,
|
||||||
ShortUsage: "frontport --from [release=latest] --to [release=main]",
|
ShortUsage: "frontport --from [release=latest] --to [release=main]",
|
||||||
ShortHelp: "frontport cherry-picks a <commit> and applies it to a clean branch based on <release>",
|
ShortHelp: "frontport cherry-picks a commit and applies it to a clean branch based on `release`",
|
||||||
Exec: func(ctx context.Context, _ []string) error {
|
Exec: func(ctx context.Context, _ []string) error {
|
||||||
from := *frontportFromFlag
|
if err := h.checkInit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
from := *fromFlag
|
||||||
if from == "" {
|
if from == "" {
|
||||||
from = latestRelease()
|
from = h.latestRelease()
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(from, "release") {
|
if !strings.HasPrefix(from, "release") {
|
||||||
from = fmt.Sprintf("release/v%s", from)
|
from = fmt.Sprintf("release/v%s", from)
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(ctx)
|
h.fetch(ctx)
|
||||||
|
|
||||||
if !isClean() {
|
if !isClean() {
|
||||||
log.Fatal().Msg("working tree is dirty")
|
log.Fatal().Msg("working tree is dirty")
|
||||||
}
|
}
|
||||||
|
|
||||||
commits, err := repo().Log(&git.LogOptions{
|
commits, err := h.repo().Log(&git.LogOptions{
|
||||||
From: head(from),
|
From: h.head(from),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -67,18 +73,18 @@ var (
|
||||||
hash := optMap[resp]
|
hash := optMap[resp]
|
||||||
branch := fmt.Sprintf("frontport-%s", hash)
|
branch := fmt.Sprintf("frontport-%s", hash)
|
||||||
|
|
||||||
base := *frontportToFlag
|
base := *toFlag
|
||||||
if base == "" {
|
if base == "" {
|
||||||
base = "upstream/main"
|
base = "upstream/main"
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(base, "upstream") {
|
if !strings.HasPrefix(base, "upstream") {
|
||||||
base = fmt.Sprintf("upstream/release/v1.%s", base)
|
base = fmt.Sprintf("upstream/release/v1.%s", base)
|
||||||
}
|
}
|
||||||
if err := Branch.ParseAndRun(ctx, []string{branch, base}); err != nil {
|
if err := h.Branch().ParseAndRun(ctx, []string{branch, base}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return run(ctx, "git", "cherry-pick", hash.String())
|
return run(ctx, h.Config.WorkspaceBranch(branch), "git", "cherry-pick", hash.String())
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"go.jolheiser.com/git-ea/config"
|
||||||
|
|
||||||
|
"github.com/peterbourgon/ff/v3/ffcli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *Handler) Init() *ffcli.Command {
|
||||||
|
fs := flag.NewFlagSet("init", flag.ContinueOnError)
|
||||||
|
return &ffcli.Command{
|
||||||
|
Name: "init",
|
||||||
|
FlagSet: fs,
|
||||||
|
ShortUsage: "init",
|
||||||
|
ShortHelp: "init initializes a workspace for Gitea",
|
||||||
|
Exec: func(ctx context.Context, args []string) error {
|
||||||
|
if h.Config.Base != "" {
|
||||||
|
return fmt.Errorf("a git-ea workspace already exists at %q", h.Config.Base)
|
||||||
|
}
|
||||||
|
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := run(ctx, ".", "git", "clone", "--bare", "git@github.com:jolheiser/gitea.git", "."); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := run(ctx, ".", "git", "remote", "add", "upstream", "https://github.com/go-gitea/gitea.git"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := config.Config{
|
||||||
|
Base: cwd,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(cfg.Workspace(), os.ModePerm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return config.Save(cfg)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Base string `json:"base"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) Workspace() string {
|
||||||
|
return filepath.Join(c.Base, ".workspace")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) WorkspaceBranch(name string) string {
|
||||||
|
return filepath.Join(c.Workspace(), name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) IsInit() bool {
|
||||||
|
return c.Base != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func path() (string, error) {
|
||||||
|
userCfgDir, err := os.UserConfigDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
cfgDir := filepath.Join(userCfgDir, "git-ea")
|
||||||
|
return filepath.Join(cfgDir, "config.json"), os.MkdirAll(cfgDir, os.ModePerm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Load() (*Config, error) {
|
||||||
|
cfgPath, err := path()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err := os.Open(cfgPath)
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fi, err = os.Create(cfgPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, err := fi.WriteString("{}"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, err := fi.Seek(0, 0); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer fi.Close()
|
||||||
|
|
||||||
|
var cfg Config
|
||||||
|
return &cfg, json.NewDecoder(fi).Decode(&cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Save(c Config) error {
|
||||||
|
cfgPath, err := path()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err := os.Create(cfgPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer fi.Close()
|
||||||
|
return json.NewEncoder(fi).Encode(c)
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
//go:build generate
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"go.jolheiser.com/git-ea/cmd"
|
||||||
|
|
||||||
|
"go.jolheiser.com/ffmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate go run ffmd.go
|
||||||
|
func main() {
|
||||||
|
c, err := cmd.New()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
docs, err := ffmd.Command(c)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err := os.Create("DOCS.md")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer fi.Close()
|
||||||
|
|
||||||
|
if _, err := fi.WriteString(docs); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
1
go.mod
1
go.mod
|
@ -8,6 +8,7 @@ require (
|
||||||
github.com/peterbourgon/ff/v3 v3.3.0
|
github.com/peterbourgon/ff/v3 v3.3.0
|
||||||
github.com/rs/zerolog v1.27.0
|
github.com/rs/zerolog v1.27.0
|
||||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
||||||
|
go.jolheiser.com/ffmd v0.0.2
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|
8
go.sum
8
go.sum
|
@ -1,5 +1,6 @@
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.5 h1:A8cYupsAZkjaUmhtTYv3sSqc7LO5mp1XDfqe5E/9wRQ=
|
github.com/AlecAivazis/survey/v2 v2.3.5 h1:A8cYupsAZkjaUmhtTYv3sSqc7LO5mp1XDfqe5E/9wRQ=
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.5/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI=
|
github.com/AlecAivazis/survey/v2 v2.3.5/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||||
github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk=
|
github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk=
|
||||||
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
|
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
|
||||||
|
@ -56,8 +57,9 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
|
|
||||||
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
||||||
|
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
||||||
|
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
||||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||||
|
@ -69,6 +71,8 @@ github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyex
|
||||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
|
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
|
||||||
|
github.com/peterbourgon/ff/v3 v3.1.2/go.mod h1:XNJLY8EIl6MjMVjBS4F0+G0LYoAqs0DTa4rmHHukKDE=
|
||||||
github.com/peterbourgon/ff/v3 v3.3.0 h1:PaKe7GW8orVFh8Unb5jNHS+JZBwWUMa2se0HM6/BI24=
|
github.com/peterbourgon/ff/v3 v3.3.0 h1:PaKe7GW8orVFh8Unb5jNHS+JZBwWUMa2se0HM6/BI24=
|
||||||
github.com/peterbourgon/ff/v3 v3.3.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ=
|
github.com/peterbourgon/ff/v3 v3.3.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
@ -93,6 +97,8 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI=
|
github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI=
|
||||||
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
|
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
|
||||||
|
go.jolheiser.com/ffmd v0.0.2 h1:rNOUq5wQKsNQU3pc51XNaks9+GK9hLlFTsPHrQhlGRU=
|
||||||
|
go.jolheiser.com/ffmd v0.0.2/go.mod h1:NLhcXZqO+dwvQ2/X9z3TUA3gORxUuRlgOjKC931vCZ4=
|
||||||
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg=
|
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg=
|
||||||
|
|
19
main.go
19
main.go
|
@ -6,29 +6,16 @@ import (
|
||||||
|
|
||||||
"go.jolheiser.com/git-ea/cmd"
|
"go.jolheiser.com/git-ea/cmd"
|
||||||
|
|
||||||
"github.com/peterbourgon/ff/v3/ffcli"
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Version = "x.y.z"
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
||||||
|
|
||||||
c := &ffcli.Command{
|
c, err := cmd.New()
|
||||||
Name: "ea",
|
if err != nil {
|
||||||
ShortUsage: "ea <cmd>",
|
log.Fatal().Err(err).Msg("could not initialize command")
|
||||||
ShortHelp: "ea is the base command",
|
|
||||||
Subcommands: []*ffcli.Command{
|
|
||||||
cmd.Backport,
|
|
||||||
cmd.Branch,
|
|
||||||
cmd.Frontport,
|
|
||||||
},
|
|
||||||
Exec: func(_ context.Context, _ []string) error {
|
|
||||||
log.Info().Msgf("git-ea v%s", Version)
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ParseAndRun(context.Background(), os.Args[1:]); err != nil {
|
if err := c.ParseAndRun(context.Background(), os.Args[1:]); err != nil {
|
||||||
|
|
Loading…
Reference in New Issue