diff options
author | Jesse Duffield <jessedduffield@gmail.com> | 2019-02-19 23:36:29 +1100 |
---|---|---|
committer | Jesse Duffield <jessedduffield@gmail.com> | 2019-02-19 23:36:36 +1100 |
commit | 0228e250847d042730038281f435b0b21d992c42 (patch) | |
tree | 9f15ef6444c6215852a301a3286dcddf8a2b0cb3 /pkg/commands | |
parent | 935f77483443a12ab159e19a958dfdf61a947b36 (diff) |
work towards more interactive rebase options
Diffstat (limited to 'pkg/commands')
-rw-r--r-- | pkg/commands/commit.go | 10 | ||||
-rw-r--r-- | pkg/commands/git.go | 99 | ||||
-rw-r--r-- | pkg/commands/git_test.go | 64 |
3 files changed, 55 insertions, 118 deletions
diff --git a/pkg/commands/commit.go b/pkg/commands/commit.go index 6ae6acd02..1df6afbd0 100644 --- a/pkg/commands/commit.go +++ b/pkg/commands/commit.go @@ -2,6 +2,7 @@ package commands import ( "github.com/fatih/color" + "github.com/jesseduffield/lazygit/pkg/utils" ) // Commit : A git commit @@ -10,6 +11,7 @@ type Commit struct { Name string Status string // one of "unpushed", "pushed", "merged", or "rebasing" DisplayString string + Action string // one of "", "pick", "edit", "squash", "reword", "drop", "fixup" } // GetDisplayStrings is a function. @@ -19,6 +21,7 @@ func (c *Commit) GetDisplayStrings(isFocused bool) []string { green := color.New(color.FgGreen) white := color.New(color.FgWhite) blue := color.New(color.FgBlue) + cyan := color.New(color.FgCyan) var shaColor *color.Color switch c.Status { @@ -34,5 +37,10 @@ func (c *Commit) GetDisplayStrings(isFocused bool) []string { shaColor = white } - return []string{shaColor.Sprint(c.Sha), white.Sprint(c.Name)} + actionString := "" + if c.Action != "" { + actionString = cyan.Sprint(utils.WithPadding(c.Action, 7)) + " " + } + + return []string{shaColor.Sprint(c.Sha), actionString + white.Sprint(c.Name)} } diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 845b81ff2..fa6e4c5fe 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -2,6 +2,7 @@ package commands import ( "fmt" + "io/ioutil" "os" "os/exec" "strings" @@ -360,46 +361,6 @@ func (c *GitCommand) Push(branchName string, force bool, ask func(string) string return c.OSCommand.DetectUnamePass(cmd, ask) } -// SquashPreviousTwoCommits squashes a commit down to the one below it -// retaining the message of the higher commit -func (c *GitCommand) SquashPreviousTwoCommits(message string) error { - // TODO: test this - if err := c.OSCommand.RunCommand("git reset --soft HEAD^"); err != nil { - return err - } - // TODO: if password is required, we need to return a subprocess - return c.OSCommand.RunCommand(fmt.Sprintf("git commit --amend -m %s", c.OSCommand.Quote(message))) -} - -// SquashFixupCommit squashes a 'FIXUP' commit into the commit beneath it, -// retaining the commit message of the lower commit -func (c *GitCommand) SquashFixupCommit(branchName string, shaValue string) error { - commands := []string{ - fmt.Sprintf("git checkout -q %s", shaValue), - fmt.Sprintf("git reset --soft %s^", shaValue), - fmt.Sprintf("git commit --amend -C %s^", shaValue), - fmt.Sprintf("git rebase --onto HEAD %s %s", shaValue, branchName), - } - for _, command := range commands { - c.Log.Info(command) - - if output, err := c.OSCommand.RunCommandWithOutput(command); err != nil { - ret := output - // We are already in an error state here so we're just going to append - // the output of these commands - output, _ := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git branch -d %s", shaValue)) - ret += output - output, _ = c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git checkout %s", branchName)) - ret += output - - c.Log.Info(ret) - return errors.New(ret) - } - } - - return nil -} - // CatFile obtains the content of a file func (c *GitCommand) CatFile(fileName string) (string, error) { return c.OSCommand.RunCommandWithOutput(fmt.Sprintf("cat %s", c.OSCommand.Quote(fileName))) @@ -606,7 +567,7 @@ func (c *GitCommand) FastForward(branchName string) error { return c.OSCommand.RunCommand(fmt.Sprintf("git fetch %s %s:%s", upstream, branchName, branchName)) } -// GenericMerge takes a commandType of "merging" or "rebasing" and a command of "abort", "skip" or "continue" +// GenericMerge takes a commandType of "merge" or "rebase" and a command of "abort", "skip" or "continue" // By default we skip the editor in the case where a commit will be made func (c *GitCommand) GenericMerge(commandType string, command string) error { gitCommand := fmt.Sprintf("git %s %s --%s", c.OSCommand.Platform.skipEditorArg, commandType, command) @@ -619,7 +580,7 @@ func (c *GitCommand) RewordCommit(commits []*Commit, index int) (*exec.Cmd, erro return nil, err } - return c.PrepareInteractiveRebaseCommand(commits[index+1].Sha, todo, true) + return c.PrepareInteractiveRebaseCommand(commits[index+1].Sha, todo, false) } func (c *GitCommand) MoveCommitDown(commits []*Commit, index int) error { @@ -633,7 +594,7 @@ func (c *GitCommand) MoveCommitDown(commits []*Commit, index int) error { todo := "" orderedCommits := append(commits[0:index], commits[index+1], commits[index]) for _, commit := range orderedCommits { - todo = "pick " + commit.Sha + "\n" + todo + todo = "pick " + commit.Sha + " " + commit.Name + "\n" + todo } cmd, err := c.PrepareInteractiveRebaseCommand(commits[index+2].Sha, todo, true) @@ -650,7 +611,6 @@ func (c *GitCommand) InteractiveRebase(commits []*Commit, index int, action stri return err } - // TODO: decide whether to autostash when action == editing cmd, err := c.PrepareInteractiveRebaseCommand(commits[index+1].Sha, todo, true) if err != nil { return err @@ -659,7 +619,7 @@ func (c *GitCommand) InteractiveRebase(commits []*Commit, index int, action stri return c.OSCommand.RunPreparedCommand(cmd) } -func (c *GitCommand) PrepareInteractiveRebaseCommand(baseSha string, todo string, autoStash bool) (*exec.Cmd, error) { +func (c *GitCommand) PrepareInteractiveRebaseCommand(baseSha string, todo string, overrideEditor bool) (*exec.Cmd, error) { ex, err := os.Executable() // get the executable path for git to use if err != nil { ex = os.Args[0] // fallback to the first call argument if needed @@ -670,12 +630,7 @@ func (c *GitCommand) PrepareInteractiveRebaseCommand(baseSha string, todo string debug = "TRUE" } - autoStashFlag := "" - if autoStash { - autoStashFlag = "--autostash" - } - - splitCmd := str.ToArgv(fmt.Sprintf("git rebase --interactive %s %s", autoStashFlag, baseSha)) + splitCmd := str.ToArgv(fmt.Sprintf("git rebase --interactive --autostash %s", baseSha)) cmd := exec.Command(splitCmd[0], splitCmd[1:]...) @@ -690,6 +645,10 @@ func (c *GitCommand) PrepareInteractiveRebaseCommand(baseSha string, todo string "GIT_SEQUENCE_EDITOR="+ex, ) + if overrideEditor { + cmd.Env = append(cmd.Env, "EDITOR="+ex) + } + return cmd, nil } @@ -697,7 +656,11 @@ func (c *GitCommand) HardReset(baseSha string) error { return c.OSCommand.RunCommand("git reset --hard " + baseSha) } -func (v *GitCommand) GenerateGenericRebaseTodo(commits []*Commit, index int, action string) (string, error) { +func (c *GitCommand) SoftReset(baseSha string) error { + return c.OSCommand.RunCommand("git reset --soft " + baseSha) +} + +func (c *GitCommand) GenerateGenericRebaseTodo(commits []*Commit, index int, action string) (string, error) { if len(commits) <= index+1 { // assuming they aren't picking the bottom commit // TODO: support more than say 30 commits and ensure this logic is correct, and i18n @@ -710,7 +673,37 @@ func (v *GitCommand) GenerateGenericRebaseTodo(commits []*Commit, index int, act if i == index { a = action } - todo = a + " " + commit.Sha + "\n" + todo + todo = a + " " + commit.Sha + " " + commit.Name + "\n" + todo } return todo, nil } + +// AmendTo amends the given commit with whatever files are staged +func (c *GitCommand) AmendTo(sha string) error { + if err := c.OSCommand.RunCommand(fmt.Sprintf("git commit --fixup=%s", sha)); err != nil { + return err + } + return c.OSCommand.RunCommand(fmt.Sprintf("git %s rebase --interactive --autostash --autosquash %s^", c.OSCommand.Platform.skipEditorArg, sha)) +} + +func (c *GitCommand) EditRebaseTodo(index int, action string) error { + fileName := ".git/rebase-merge/git-rebase-todo" + bytes, err := ioutil.ReadFile(fileName) + if err != nil { + return err + } + + content := strings.Split(string(bytes), "\n") + + contentIndex := len(content) - 2 - index + splitLine := strings.Split(content[contentIndex], " ") + content[contentIndex] = action + " " + strings.Join(splitLine[1:], " ") + result := strings.Join(content, "\n") + + return ioutil.WriteFile(fileName, []byte(result), 0644) +} + +// Revert reverts the selected commit by sha +func (c *GitCommand) Revert(sha string) error { + return c.OSCommand.RunCommand(fmt.Sprintf("git revert %s", sha)) +} diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index 4159683ca..5af2bfe51 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -1152,70 +1152,6 @@ func TestGitCommandSquashPreviousTwoCommits(t *testing.T) { } } -// TestGitCommandSquashFixupCommit is a function. -func TestGitCommandSquashFixupCommit(t *testing.T) { - type scenario struct { - testName string - command func() (func(string, ...string) *exec.Cmd, *[][]string) - test func(*[][]string, error) - } - - scenarios := []scenario{ - { - "An error occurred with one of the sub git command", - func() (func(string, ...string) *exec.Cmd, *[][]string) { - cmdsCalled := [][]string{} - return func(cmd string, args ...string) *exec.Cmd { - cmdsCalled = append(cmdsCalled, args) - if len(args) > 0 && args[0] == "checkout" { - return exec.Command("test") - } - - return exec.Command("echo") - }, &cmdsCalled - }, - func(cmdsCalled *[][]string, err error) { - assert.NotNil(t, err) - assert.Len(t, *cmdsCalled, 3) - assert.EqualValues(t, *cmdsCalled, [][]string{ - {"checkout", "-q", "6789abcd"}, - {"branch", "-d", "6789abcd"}, - {"checkout", "test"}, - }) - }, - }, - { - "Squash fixup succeeded", - func() (func(string, ...string) *exec.Cmd, *[][]string) { - cmdsCalled := [][]string{} - return func(cmd string, args ...string) *exec.Cmd { - cmdsCalled = append(cmdsCalled, args) - return exec.Command("echo") - }, &cmdsCalled - }, - func(cmdsCalled *[][]string, err error) { - assert.Nil(t, err) - assert.Len(t, *cmdsCalled, 4) - assert.EqualValues(t, *cmdsCalled, [][]string{ - {"checkout", "-q", "6789abcd"}, - {"reset", "--soft", "6789abcd^"}, - {"commit", "--amend", "-C", "6789abcd^"}, - {"rebase", "--onto", "HEAD", "6789abcd", "test"}, - }) - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - var cmdsCalled *[][]string - gitCmd := newDummyGitCommand() - gitCmd.OSCommand.command, cmdsCalled = s.command() - s.test(cmdsCalled, gitCmd.SquashFixupCommit("test", "6789abcd")) - }) - } -} - // TestGitCommandCatFile is a function. func TestGitCommandCatFile(t *testing.T) { gitCmd := newDummyGitCommand() |