Skip to content

Commit

Permalink
Merge pull request #78 from garethjevans/rebase
Browse files Browse the repository at this point in the history
feat: added a command to rebase based on the default branch
  • Loading branch information
macox authored Nov 18, 2020
2 parents bda9602 + 6791b01 commit 5ba9522
Show file tree
Hide file tree
Showing 11 changed files with 525 additions and 0 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,12 @@ hiddenLabels:
- wip
- do not merge
```

### Rebase the local repository

This will rebase the local repository against the remote called 'upstream'.

```
dx rebase
```

3 changes: 3 additions & 0 deletions cmd/dx/dx.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"runtime/debug"
"strings"

"github.com/plumming/dx/pkg/cmd/rebasecmd"

"github.com/plumming/dx/pkg/cmd/contextcmd"

"github.com/plumming/dx/pkg/cmd/editcmd"
Expand Down Expand Up @@ -74,6 +76,7 @@ func init() {
RootCmd.AddCommand(editcmd.NewEditCmd())
RootCmd.AddCommand(upgradecmd.NewUpgradeCmd())
RootCmd.AddCommand(contextcmd.NewContextCmd())
RootCmd.AddCommand(rebasecmd.NewRebaseCmd())

c := completionCmd
c.Flags().StringP("shell", "s", "bash", "Shell type: {bash|zsh|fish|powershell}")
Expand Down
1 change: 1 addition & 0 deletions docs/dx.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ Have you got the chillies.
* [dx context](dx_context.md) - View or change the current Kubernetes context
* [dx edit](dx_edit.md) -
* [dx get](dx_get.md) -
* [dx rebase](dx_rebase.md) - Rebase the local clone
* [dx upgrade](dx_upgrade.md) -

22 changes: 22 additions & 0 deletions docs/dx_rebase.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
## dx rebase

Rebase the local clone

### Synopsis

Performs a 'git fetch upstream master && git rebase upstream/master && git push origin master'. Uses the default_branch name determined from the GitHub API.

```
dx rebase [flags]
```

### Options inherited from parent commands

```
--help Show help for command
```

### SEE ALSO

* [dx](dx.md) - Plumming dx

1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/mattn/go-colorable v0.1.6 // indirect
github.com/maxbrunsfeld/counterfeiter/v6 v6.3.0
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.6.0
github.com/spf13/cobra v1.1.1
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.6.1
Expand Down
49 changes: 49 additions & 0 deletions pkg/cmd/rebasecmd/rebase.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package rebasecmd

import (
"github.com/jenkins-x/jx-logging/pkg/log"
"github.com/pkg/errors"
"github.com/plumming/dx/pkg/domain"
"github.com/spf13/cobra"
)

type RebaseCmd struct {
Cmd *cobra.Command
Args []string
}

func NewRebaseCmd() *cobra.Command {
c := &RebaseCmd{}
cmd := &cobra.Command{
Use: "rebase",
Short: "Rebase the local clone",
Long: "Performs a 'git fetch upstream master && git rebase upstream/master && git push origin master'. " +
"Uses the default_branch name determined from the GitHub API.",
Example: "",
Aliases: []string{"rb"},
Run: func(cmd *cobra.Command, args []string) {
c.Cmd = cmd
c.Args = args
err := c.Run()
if err != nil {
log.Logger().Fatalf("unable to run command: %s", err)
}
},
Args: cobra.NoArgs,
}
return cmd
}

func (c *RebaseCmd) Run() error {
d := domain.NewRebase()

err := d.Validate()
if err != nil {
return errors.Wrap(err, "validate failed")
}
err = d.Run()
if err != nil {
return errors.Wrap(err, "validate failed")
}
return nil
}
21 changes: 21 additions & 0 deletions pkg/domain/gh.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package domain

import (
"fmt"

"github.com/plumming/dx/pkg/api"
)

func GetDefaultBranch(client *api.Client, org string, repo string) (string, error) {
repository := repository{}
err := client.REST("GET", fmt.Sprintf("repos/%s/%s", org, repo), nil, &repository)
if err != nil {
return "", err
}

return repository.DefaultBranch, nil
}

type repository struct {
DefaultBranch string `json:"default_branch"`
}
47 changes: 47 additions & 0 deletions pkg/domain/gh_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package domain_test

import (
"bytes"
"strings"
"testing"

"github.com/plumming/dx/pkg/api"
"github.com/plumming/dx/pkg/domain"
"github.com/stretchr/testify/assert"
)

func TestGetDefaultBranch_Main(t *testing.T) {
http := &api.FakeHTTP{}
client := api.NewClient(api.ReplaceTripper(http))

http.StubResponse(200, bytes.NewBufferString("{ \"default_branch\":\"main\" }"))

defaultBranch, err := domain.GetDefaultBranch(client, "orgname", "reponame")
assert.NoError(t, err)

assert.Equal(t, defaultBranch, "main")
}

func TestGetDefaultBranch_Master(t *testing.T) {
http := &api.FakeHTTP{}
client := api.NewClient(api.ReplaceTripper(http))

http.StubResponse(200, bytes.NewBufferString("{ \"default_branch\":\"master\"}"))

defaultBranch, err := domain.GetDefaultBranch(client, "orgname", "reponame")
assert.NoError(t, err)

assert.Equal(t, defaultBranch, "master")
}

func TestGetOrgAndRepo(t *testing.T) {
output := `origin https://github.com/clone/chilly (fetch)
origin https://github.com/clone/chilly (push)
upstream https://github.com/plumming/dx (fetch)
upstream https://github.com/plumming/dx (push)`

org, repo, err := domain.ExtractOrgAndRepoFromGitRemotes(strings.NewReader(output))
assert.NoError(t, err)
assert.Equal(t, org, "clone")
assert.Equal(t, repo, "chilly")
}
174 changes: 174 additions & 0 deletions pkg/domain/git.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package domain

import (
"errors"
"io"
url2 "net/url"
"strings"

"github.com/plumming/dx/pkg/util"
)

func GetOrgAndRepoFromCurrentDir() (string, string, error) {
c := util.Command{
Name: "git",
Args: []string{"remote", "-v"},
}
output, err := c.RunWithoutRetry()
if err != nil {
return "", "", err
}

return ExtractOrgAndRepoFromGitRemotes(strings.NewReader(output))
}

func CurrentBranchName(dir string) (string, error) {
c := util.Command{
Name: "git",
Args: []string{"branch", "--show-current"},
Dir: dir,
}
output, err := c.RunWithoutRetry()
if err != nil {
return "", err
}
return output, nil
}

func SwitchBranch(dir string, name string) (string, error) {
c := util.Command{
Name: "git",
Args: []string{"checkout", name},
Dir: dir,
}
output, err := c.RunWithoutRetry()
if err != nil {
return "", err
}
return output, nil
}

func Stash(dir string) (string, error) {
c := util.Command{
Name: "git",
Args: []string{"stash"},
Dir: dir,
}
output, err := c.RunWithoutRetry()
if err != nil {
return "", err
}
return output, nil
}

func StashPop(dir string) (string, error) {
c := util.Command{
Name: "git",
Args: []string{"stash", "pop"},
Dir: dir,
}
output, err := c.RunWithoutRetry()
if err != nil {
return "", err
}
return output, nil
}

func Add(dir string, name string) (string, error) {
c := util.Command{
Name: "git",
Args: []string{"add", name},
Dir: dir,
}
output, err := c.RunWithoutRetry()
if err != nil {
return "", err
}
return output, nil
}

func Commit(dir string, message string) (string, error) {
c := util.Command{
Name: "git",
Args: []string{"commit", "-m", message},
Dir: dir,
}
output, err := c.RunWithoutRetry()
if err != nil {
return "", err
}
return output, nil
}

func Status(dir string) (string, error) {
c := util.Command{
Name: "git",
Args: []string{"status"},
Dir: dir,
}
output, err := c.RunWithoutRetry()
if err != nil {
return "", err
}
return output, nil
}

func LocalChanges(dir string) (bool, error) {
c := util.Command{
Name: "git",
Args: []string{"status", "--porcelain"},
Dir: dir,
}
output, err := c.RunWithoutRetry()
if err != nil {
return false, err
}
return output != "", nil
}

func ConfigCommitterInformation(dir string, email string, name string) error {
c := util.Command{
Name: "git",
Args: []string{"config", "user.email", email},
Dir: dir,
}
_, err := c.RunWithoutRetry()
if err != nil {
return err
}

c = util.Command{
Name: "git",
Args: []string{"config", "user.name", name},
Dir: dir,
}
_, err = c.RunWithoutRetry()
if err != nil {
return err
}
return nil
}

func ExtractOrgAndRepoFromGitRemotes(reader io.Reader) (string, string, error) {
buf := new(strings.Builder)
_, err := io.Copy(buf, reader)
if err != nil {
return "", "", err
}
lines := strings.Split(buf.String(), "\n")
for _, line := range lines {
if strings.HasPrefix(line, "origin") && strings.HasSuffix(line, "(push)") {
urlString := strings.TrimSpace(strings.ReplaceAll(strings.ReplaceAll(line, "origin\t", ""), "(push)", ""))
url, err := url2.Parse(urlString)
if err != nil {
return "", "", err
}
fragments := strings.Split(url.Path, "/")
if len(fragments) != 3 {
return "", "", errors.New("invalid url path '" + url.Path + "'")
}
return fragments[1], fragments[2], nil
}
}
return "", "", errors.New("unable to find remote named 'origin'")
}
Loading

0 comments on commit 5ba9522

Please sign in to comment.