mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-07-28 16:02:01 +03:00
work towards more interactive rebase options
This commit is contained in:
@ -5,6 +5,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/heroku/rollrus"
|
"github.com/heroku/rollrus"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||||
@ -120,10 +121,20 @@ func (app *App) Run() error {
|
|||||||
return app.Gui.RunWithSubprocesses()
|
return app.Gui.RunWithSubprocesses()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rebase contains logic for when we've been run in demon mode, meaning we've
|
||||||
|
// given lazygit as a command for git to call e.g. to edit a file
|
||||||
func (app *App) Rebase() error {
|
func (app *App) Rebase() error {
|
||||||
app.Log.Error("Lazygit invokved as interactive rebase demon")
|
app.Log.Info("Lazygit invoked as interactive rebase demon")
|
||||||
|
app.Log.Info("args: ", os.Args)
|
||||||
|
|
||||||
ioutil.WriteFile(".git/rebase-merge/git-rebase-todo", []byte(os.Getenv("LAZYGIT_REBASE_TODO")), 0644)
|
if strings.HasSuffix(os.Args[1], "git-rebase-todo") {
|
||||||
|
ioutil.WriteFile(os.Args[1], []byte(os.Getenv("LAZYGIT_REBASE_TODO")), 0644)
|
||||||
|
} else if strings.HasSuffix(os.Args[1], ".git/COMMIT_EDITMSG") {
|
||||||
|
// if we are rebasing and squashing, we'll see a COMMIT_EDITMSG
|
||||||
|
// but in this case we don't need to edit it, so we'll just return
|
||||||
|
} else {
|
||||||
|
app.Log.Info("Lazygit demon did not match on any use cases")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package commands
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Commit : A git commit
|
// Commit : A git commit
|
||||||
@ -10,6 +11,7 @@ type Commit struct {
|
|||||||
Name string
|
Name string
|
||||||
Status string // one of "unpushed", "pushed", "merged", or "rebasing"
|
Status string // one of "unpushed", "pushed", "merged", or "rebasing"
|
||||||
DisplayString string
|
DisplayString string
|
||||||
|
Action string // one of "", "pick", "edit", "squash", "reword", "drop", "fixup"
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDisplayStrings is a function.
|
// GetDisplayStrings is a function.
|
||||||
@ -19,6 +21,7 @@ func (c *Commit) GetDisplayStrings(isFocused bool) []string {
|
|||||||
green := color.New(color.FgGreen)
|
green := color.New(color.FgGreen)
|
||||||
white := color.New(color.FgWhite)
|
white := color.New(color.FgWhite)
|
||||||
blue := color.New(color.FgBlue)
|
blue := color.New(color.FgBlue)
|
||||||
|
cyan := color.New(color.FgCyan)
|
||||||
|
|
||||||
var shaColor *color.Color
|
var shaColor *color.Color
|
||||||
switch c.Status {
|
switch c.Status {
|
||||||
@ -34,5 +37,10 @@ func (c *Commit) GetDisplayStrings(isFocused bool) []string {
|
|||||||
shaColor = white
|
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)}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package commands
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
@ -360,46 +361,6 @@ func (c *GitCommand) Push(branchName string, force bool, ask func(string) string
|
|||||||
return c.OSCommand.DetectUnamePass(cmd, ask)
|
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
|
// CatFile obtains the content of a file
|
||||||
func (c *GitCommand) CatFile(fileName string) (string, error) {
|
func (c *GitCommand) CatFile(fileName string) (string, error) {
|
||||||
return c.OSCommand.RunCommandWithOutput(fmt.Sprintf("cat %s", c.OSCommand.Quote(fileName)))
|
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))
|
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
|
// By default we skip the editor in the case where a commit will be made
|
||||||
func (c *GitCommand) GenericMerge(commandType string, command string) error {
|
func (c *GitCommand) GenericMerge(commandType string, command string) error {
|
||||||
gitCommand := fmt.Sprintf("git %s %s --%s", c.OSCommand.Platform.skipEditorArg, commandType, command)
|
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 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 {
|
func (c *GitCommand) MoveCommitDown(commits []*Commit, index int) error {
|
||||||
@ -633,7 +594,7 @@ func (c *GitCommand) MoveCommitDown(commits []*Commit, index int) error {
|
|||||||
todo := ""
|
todo := ""
|
||||||
orderedCommits := append(commits[0:index], commits[index+1], commits[index])
|
orderedCommits := append(commits[0:index], commits[index+1], commits[index])
|
||||||
for _, commit := range orderedCommits {
|
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)
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: decide whether to autostash when action == editing
|
|
||||||
cmd, err := c.PrepareInteractiveRebaseCommand(commits[index+1].Sha, todo, true)
|
cmd, err := c.PrepareInteractiveRebaseCommand(commits[index+1].Sha, todo, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -659,7 +619,7 @@ func (c *GitCommand) InteractiveRebase(commits []*Commit, index int, action stri
|
|||||||
return c.OSCommand.RunPreparedCommand(cmd)
|
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
|
ex, err := os.Executable() // get the executable path for git to use
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ex = os.Args[0] // fallback to the first call argument if needed
|
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"
|
debug = "TRUE"
|
||||||
}
|
}
|
||||||
|
|
||||||
autoStashFlag := ""
|
splitCmd := str.ToArgv(fmt.Sprintf("git rebase --interactive --autostash %s", baseSha))
|
||||||
if autoStash {
|
|
||||||
autoStashFlag = "--autostash"
|
|
||||||
}
|
|
||||||
|
|
||||||
splitCmd := str.ToArgv(fmt.Sprintf("git rebase --interactive %s %s", autoStashFlag, baseSha))
|
|
||||||
|
|
||||||
cmd := exec.Command(splitCmd[0], splitCmd[1:]...)
|
cmd := exec.Command(splitCmd[0], splitCmd[1:]...)
|
||||||
|
|
||||||
@ -690,6 +645,10 @@ func (c *GitCommand) PrepareInteractiveRebaseCommand(baseSha string, todo string
|
|||||||
"GIT_SEQUENCE_EDITOR="+ex,
|
"GIT_SEQUENCE_EDITOR="+ex,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if overrideEditor {
|
||||||
|
cmd.Env = append(cmd.Env, "EDITOR="+ex)
|
||||||
|
}
|
||||||
|
|
||||||
return cmd, nil
|
return cmd, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -697,7 +656,11 @@ func (c *GitCommand) HardReset(baseSha string) error {
|
|||||||
return c.OSCommand.RunCommand("git reset --hard " + baseSha)
|
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 {
|
if len(commits) <= index+1 {
|
||||||
// assuming they aren't picking the bottom commit
|
// assuming they aren't picking the bottom commit
|
||||||
// TODO: support more than say 30 commits and ensure this logic is correct, and i18n
|
// 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 {
|
if i == index {
|
||||||
a = action
|
a = action
|
||||||
}
|
}
|
||||||
todo = a + " " + commit.Sha + "\n" + todo
|
todo = a + " " + commit.Sha + " " + commit.Name + "\n" + todo
|
||||||
}
|
}
|
||||||
return todo, nil
|
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))
|
||||||
|
}
|
||||||
|
@ -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.
|
// TestGitCommandCatFile is a function.
|
||||||
func TestGitCommandCatFile(t *testing.T) {
|
func TestGitCommandCatFile(t *testing.T) {
|
||||||
gitCmd := newDummyGitCommand()
|
gitCmd := newDummyGitCommand()
|
||||||
|
@ -42,13 +42,20 @@ func NewCommitListBuilder(log *logrus.Entry, gitCommand *commands.GitCommand, os
|
|||||||
// GetCommits obtains the commits of the current branch
|
// GetCommits obtains the commits of the current branch
|
||||||
func (c *CommitListBuilder) GetCommits() ([]*commands.Commit, error) {
|
func (c *CommitListBuilder) GetCommits() ([]*commands.Commit, error) {
|
||||||
commits := []*commands.Commit{}
|
commits := []*commands.Commit{}
|
||||||
// here we want to also prepend the commits that we're in the process of rebasing
|
var rebasingCommits []*commands.Commit
|
||||||
rebasingCommits, err := c.getRebasingCommits()
|
rebaseMode, err := c.GitCommand.RebaseMode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(rebasingCommits) > 0 {
|
if rebaseMode != "" {
|
||||||
commits = append(commits, rebasingCommits...)
|
// here we want to also prepend the commits that we're in the process of rebasing
|
||||||
|
rebasingCommits, err = c.getRebasingCommits(rebaseMode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(rebasingCommits) > 0 {
|
||||||
|
commits = append(commits, rebasingCommits...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unpushedCommits := c.getUnpushedCommits()
|
unpushedCommits := c.getUnpushedCommits()
|
||||||
@ -67,7 +74,7 @@ func (c *CommitListBuilder) GetCommits() ([]*commands.Commit, error) {
|
|||||||
DisplayString: strings.Join(splitLine, " "),
|
DisplayString: strings.Join(splitLine, " "),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if len(rebasingCommits) > 0 {
|
if rebaseMode != "" {
|
||||||
currentCommit := commits[len(rebasingCommits)]
|
currentCommit := commits[len(rebasingCommits)]
|
||||||
blue := color.New(color.FgYellow)
|
blue := color.New(color.FgYellow)
|
||||||
youAreHere := blue.Sprint("<-- YOU ARE HERE ---")
|
youAreHere := blue.Sprint("<-- YOU ARE HERE ---")
|
||||||
@ -77,11 +84,7 @@ func (c *CommitListBuilder) GetCommits() ([]*commands.Commit, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getRebasingCommits obtains the commits that we're in the process of rebasing
|
// getRebasingCommits obtains the commits that we're in the process of rebasing
|
||||||
func (c *CommitListBuilder) getRebasingCommits() ([]*commands.Commit, error) {
|
func (c *CommitListBuilder) getRebasingCommits(rebaseMode string) ([]*commands.Commit, error) {
|
||||||
rebaseMode, err := c.GitCommand.RebaseMode()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
switch rebaseMode {
|
switch rebaseMode {
|
||||||
case "normal":
|
case "normal":
|
||||||
return c.getNormalRebasingCommits()
|
return c.getNormalRebasingCommits()
|
||||||
@ -147,45 +150,28 @@ func (c *CommitListBuilder) getNormalRebasingCommits() ([]*commands.Commit, erro
|
|||||||
// in the rebase:
|
// in the rebase:
|
||||||
func (c *CommitListBuilder) getInteractiveRebasingCommits() ([]*commands.Commit, error) {
|
func (c *CommitListBuilder) getInteractiveRebasingCommits() ([]*commands.Commit, error) {
|
||||||
bytesContent, err := ioutil.ReadFile(".git/rebase-merge/git-rebase-todo")
|
bytesContent, err := ioutil.ReadFile(".git/rebase-merge/git-rebase-todo")
|
||||||
var content []string
|
if err != nil {
|
||||||
if err == nil {
|
c.Log.Info(fmt.Sprintf("error occured reading git-rebase-todo: %s", err.Error()))
|
||||||
content = strings.Split(string(bytesContent), "\n")
|
// we assume an error means the file doesn't exist so we just return
|
||||||
if len(content) > 0 && content[len(content)-1] == "" {
|
return nil, nil
|
||||||
content = content[0 : len(content)-1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// for each of them, grab the matching commit name in the backup
|
|
||||||
bytesContent, err = ioutil.ReadFile(".git/rebase-merge/git-rebase-todo.backup")
|
|
||||||
var backupContent []string
|
|
||||||
if err == nil {
|
|
||||||
backupContent = strings.Split(string(bytesContent), "\n")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
commits := []*commands.Commit{}
|
commits := []*commands.Commit{}
|
||||||
for _, todoLine := range content {
|
lines := strings.Split(string(bytesContent), "\n")
|
||||||
commit := c.extractCommit(todoLine, backupContent)
|
for _, line := range lines {
|
||||||
if commit != nil {
|
if line == "" {
|
||||||
commits = append([]*commands.Commit{commit}, commits...)
|
return commits, nil
|
||||||
}
|
}
|
||||||
|
splitLine := strings.Split(line, " ")
|
||||||
|
commits = append([]*commands.Commit{&commands.Commit{
|
||||||
|
Sha: splitLine[1][0:7],
|
||||||
|
Name: strings.Join(splitLine[2:], " "),
|
||||||
|
Status: "rebasing",
|
||||||
|
Action: splitLine[0],
|
||||||
|
}}, commits...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return commits, nil
|
return nil, nil
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CommitListBuilder) extractCommit(todoLine string, backupContent []string) *commands.Commit {
|
|
||||||
for _, backupLine := range backupContent {
|
|
||||||
split := strings.Split(todoLine, " ")
|
|
||||||
prefix := strings.Join(split[0:2], " ")
|
|
||||||
if strings.HasPrefix(backupLine, prefix) {
|
|
||||||
return &commands.Commit{
|
|
||||||
Sha: split[2],
|
|
||||||
Name: strings.TrimPrefix(backupLine, prefix+" "),
|
|
||||||
Status: "rebasing",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// assuming the file starts like this:
|
// assuming the file starts like this:
|
||||||
|
@ -115,24 +115,23 @@ func (gui *Gui) handleResetToCommit(g *gocui.Gui, commitView *gocui.View) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleCommitSquashDown(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleCommitSquashDown(g *gocui.Gui, v *gocui.View) error {
|
||||||
if gui.State.Panels.Commits.SelectedLine != 0 {
|
|
||||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("OnlySquashTopmostCommit"))
|
|
||||||
}
|
|
||||||
if len(gui.State.Commits) <= 1 {
|
if len(gui.State.Commits) <= 1 {
|
||||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("YouNoCommitsToSquash"))
|
return gui.createErrorPanel(g, gui.Tr.SLocalize("YouNoCommitsToSquash"))
|
||||||
}
|
}
|
||||||
commit := gui.getSelectedCommit(g)
|
|
||||||
if commit == nil {
|
applied, err := gui.handleMidRebaseCommand("squash")
|
||||||
return errors.New(gui.Tr.SLocalize("NoCommitsThisBranch"))
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
if err := gui.GitCommand.SquashPreviousTwoCommits(commit.Name); err != nil {
|
if applied {
|
||||||
return gui.createErrorPanel(g, err.Error())
|
return nil
|
||||||
}
|
}
|
||||||
if err := gui.refreshCommits(g); err != nil {
|
|
||||||
panic(err)
|
gui.createConfirmationPanel(g, v, gui.Tr.SLocalize("Squash"), gui.Tr.SLocalize("SureSquashThisCommit"), func(g *gocui.Gui, v *gocui.View) error {
|
||||||
}
|
err := gui.GitCommand.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLine, "squash")
|
||||||
gui.refreshStatus(g)
|
return gui.handleGenericMergeCommandResult(err)
|
||||||
return gui.handleCommitSelect(g, v)
|
}, nil)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: move to files panel
|
// TODO: move to files panel
|
||||||
@ -149,28 +148,31 @@ func (gui *Gui) handleCommitFixup(g *gocui.Gui, v *gocui.View) error {
|
|||||||
if len(gui.State.Commits) <= 1 {
|
if len(gui.State.Commits) <= 1 {
|
||||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("YouNoCommitsToSquash"))
|
return gui.createErrorPanel(g, gui.Tr.SLocalize("YouNoCommitsToSquash"))
|
||||||
}
|
}
|
||||||
if gui.anyUnStagedChanges(gui.State.Files) {
|
|
||||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("CantFixupWhileUnstagedChanges"))
|
applied, err := gui.handleMidRebaseCommand("fixup")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
branch := gui.State.Branches[0]
|
if applied {
|
||||||
commit := gui.getSelectedCommit(g)
|
return nil
|
||||||
if commit == nil {
|
|
||||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("NoCommitsThisBranch"))
|
|
||||||
}
|
}
|
||||||
message := gui.Tr.SLocalize("SureFixupThisCommit")
|
|
||||||
gui.createConfirmationPanel(g, v, gui.Tr.SLocalize("Fixup"), message, func(g *gocui.Gui, v *gocui.View) error {
|
gui.createConfirmationPanel(g, v, gui.Tr.SLocalize("Fixup"), gui.Tr.SLocalize("SureFixupThisCommit"), func(g *gocui.Gui, v *gocui.View) error {
|
||||||
if err := gui.GitCommand.SquashFixupCommit(branch.Name, commit.Sha); err != nil {
|
err := gui.GitCommand.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLine, "fixup")
|
||||||
return gui.createErrorPanel(g, err.Error())
|
return gui.handleGenericMergeCommandResult(err)
|
||||||
}
|
|
||||||
if err := gui.refreshCommits(g); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return gui.refreshStatus(g)
|
|
||||||
}, nil)
|
}, nil)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleRenameCommit(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleRenameCommit(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
applied, err := gui.handleMidRebaseCommand("reword")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if applied {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if gui.State.Panels.Commits.SelectedLine != 0 {
|
if gui.State.Panels.Commits.SelectedLine != 0 {
|
||||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("OnlyRenameTopCommit"))
|
return gui.createErrorPanel(g, gui.Tr.SLocalize("OnlyRenameTopCommit"))
|
||||||
}
|
}
|
||||||
@ -186,6 +188,14 @@ func (gui *Gui) handleRenameCommit(g *gocui.Gui, v *gocui.View) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleRenameCommitEditor(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleRenameCommitEditor(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
applied, err := gui.handleMidRebaseCommand("reword")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if applied {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
subProcess, err := gui.GitCommand.RewordCommit(gui.State.Commits, gui.State.Panels.Commits.SelectedLine)
|
subProcess, err := gui.GitCommand.RewordCommit(gui.State.Commits, gui.State.Panels.Commits.SelectedLine)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gui.createErrorPanel(gui.g, err.Error())
|
return gui.createErrorPanel(gui.g, err.Error())
|
||||||
@ -198,7 +208,29 @@ func (gui *Gui) handleRenameCommitEditor(g *gocui.Gui, v *gocui.View) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleMidRebaseCommand sees if the selected commit is in fact a rebasing
|
||||||
|
// commit meaning you are trying to edit the todo file rather than actually
|
||||||
|
// begin a rebase. It then updates the todo file with that action
|
||||||
|
func (gui *Gui) handleMidRebaseCommand(action string) (bool, error) {
|
||||||
|
selectedCommit := gui.State.Commits[gui.State.Panels.Commits.SelectedLine]
|
||||||
|
if selectedCommit.Status != "rebasing" {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if err := gui.GitCommand.EditRebaseTodo(gui.State.Panels.Commits.SelectedLine, action); err != nil {
|
||||||
|
return false, gui.createErrorPanel(gui.g, err.Error())
|
||||||
|
}
|
||||||
|
return true, gui.refreshCommits(gui.g)
|
||||||
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleCommitDelete(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleCommitDelete(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
applied, err := gui.handleMidRebaseCommand("drop")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if applied {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: i18n
|
// TODO: i18n
|
||||||
return gui.createConfirmationPanel(gui.g, v, "Delete Commit", "Are you sure you want to delete this commit?", func(*gocui.Gui, *gocui.View) error {
|
return gui.createConfirmationPanel(gui.g, v, "Delete Commit", "Are you sure you want to delete this commit?", func(*gocui.Gui, *gocui.View) error {
|
||||||
err := gui.GitCommand.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLine, "drop")
|
err := gui.GitCommand.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLine, "drop")
|
||||||
@ -225,6 +257,41 @@ func (gui *Gui) handleCommitMoveUp(g *gocui.Gui, v *gocui.View) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleCommitEdit(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleCommitEdit(g *gocui.Gui, v *gocui.View) error {
|
||||||
err := gui.GitCommand.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLine, "edit")
|
applied, err := gui.handleMidRebaseCommand("edit")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if applied {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = gui.GitCommand.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLine, "edit")
|
||||||
return gui.handleGenericMergeCommandResult(err)
|
return gui.handleGenericMergeCommandResult(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleCommitAmendTo(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
err := gui.GitCommand.AmendTo(gui.State.Commits[gui.State.Panels.Commits.SelectedLine].Sha)
|
||||||
|
return gui.handleGenericMergeCommandResult(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleCommitPick(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
applied, err := gui.handleMidRebaseCommand("pick")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if applied {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// at this point we aren't actually rebasing so we will interpret this as an
|
||||||
|
// attempt to pull. We might revoke this later after enabling configurable keybindings
|
||||||
|
return gui.pullFiles(g, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleCommitRevert(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
if err := gui.GitCommand.Revert(gui.State.Commits[gui.State.Panels.Commits.SelectedLine].Sha); err != nil {
|
||||||
|
return gui.createErrorPanel(gui.g, err.Error())
|
||||||
|
}
|
||||||
|
gui.State.Panels.Commits.SelectedLine++
|
||||||
|
return gui.refreshCommits(gui.g)
|
||||||
|
}
|
||||||
|
@ -480,3 +480,16 @@ func (gui *Gui) anyFilesWithMergeConflicts() bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleSoftReset(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
return gui.createConfirmationPanel(g, v, gui.Tr.SLocalize("SoftReset"), gui.Tr.SLocalize("ConfirmSoftReset"), func(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
if err := gui.GitCommand.SoftReset("HEAD^"); err != nil {
|
||||||
|
return gui.createErrorPanel(g, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gui.refreshCommits(gui.g); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return gui.refreshFiles()
|
||||||
|
}, nil)
|
||||||
|
}
|
||||||
|
@ -82,6 +82,12 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
|||||||
Key: gocui.KeyCtrlD,
|
Key: gocui.KeyCtrlD,
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.scrollDownMain,
|
Handler: gui.scrollDownMain,
|
||||||
|
}, {
|
||||||
|
ViewName: "",
|
||||||
|
Key: 'm',
|
||||||
|
Modifier: gocui.ModNone,
|
||||||
|
Handler: gui.handleCreateRebaseOptionsMenu,
|
||||||
|
Description: gui.Tr.SLocalize("ViewMergeRebaseOptions"),
|
||||||
}, {
|
}, {
|
||||||
ViewName: "",
|
ViewName: "",
|
||||||
Key: 'P',
|
Key: 'P',
|
||||||
@ -160,12 +166,6 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
|||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleFileRemove,
|
Handler: gui.handleFileRemove,
|
||||||
Description: gui.Tr.SLocalize("removeFile"),
|
Description: gui.Tr.SLocalize("removeFile"),
|
||||||
}, {
|
|
||||||
ViewName: "files", // TODO: might make this for more views as well
|
|
||||||
Key: 'm',
|
|
||||||
Modifier: gocui.ModNone,
|
|
||||||
Handler: gui.handleCreateRebaseOptionsMenu,
|
|
||||||
Description: gui.Tr.SLocalize("ViewMergeRebaseOptions"),
|
|
||||||
}, {
|
}, {
|
||||||
ViewName: "files",
|
ViewName: "files",
|
||||||
Key: 'e',
|
Key: 'e',
|
||||||
@ -192,10 +192,16 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
|||||||
Description: gui.Tr.SLocalize("refreshFiles"),
|
Description: gui.Tr.SLocalize("refreshFiles"),
|
||||||
}, {
|
}, {
|
||||||
ViewName: "files",
|
ViewName: "files",
|
||||||
Key: 'S',
|
Key: 's',
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleStashSave,
|
Handler: gui.handleStashSave,
|
||||||
Description: gui.Tr.SLocalize("stashFiles"),
|
Description: gui.Tr.SLocalize("stashFiles"),
|
||||||
|
}, {
|
||||||
|
ViewName: "files",
|
||||||
|
Key: 'S',
|
||||||
|
Modifier: gocui.ModNone,
|
||||||
|
Handler: gui.handleSoftReset,
|
||||||
|
Description: gui.Tr.SLocalize("softReset"),
|
||||||
}, {
|
}, {
|
||||||
ViewName: "files",
|
ViewName: "files",
|
||||||
Key: 'a',
|
Key: 'a',
|
||||||
@ -270,7 +276,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
|||||||
Description: gui.Tr.SLocalize("rebaseBranch"),
|
Description: gui.Tr.SLocalize("rebaseBranch"),
|
||||||
}, {
|
}, {
|
||||||
ViewName: "branches",
|
ViewName: "branches",
|
||||||
Key: 'm',
|
Key: 'M',
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleMerge,
|
Handler: gui.handleMerge,
|
||||||
Description: gui.Tr.SLocalize("mergeIntoCurrentBranch"),
|
Description: gui.Tr.SLocalize("mergeIntoCurrentBranch"),
|
||||||
@ -334,6 +340,24 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
|||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleCommitEdit,
|
Handler: gui.handleCommitEdit,
|
||||||
Description: gui.Tr.SLocalize("editCommit"),
|
Description: gui.Tr.SLocalize("editCommit"),
|
||||||
|
}, {
|
||||||
|
ViewName: "commits",
|
||||||
|
Key: 'A',
|
||||||
|
Modifier: gocui.ModNone,
|
||||||
|
Handler: gui.handleCommitAmendTo,
|
||||||
|
Description: gui.Tr.SLocalize("amendToCommit"),
|
||||||
|
}, {
|
||||||
|
ViewName: "commits",
|
||||||
|
Key: 'p',
|
||||||
|
Modifier: gocui.ModNone,
|
||||||
|
Handler: gui.handleCommitPick,
|
||||||
|
Description: gui.Tr.SLocalize("pickCommit"),
|
||||||
|
}, {
|
||||||
|
ViewName: "commits",
|
||||||
|
Key: 't',
|
||||||
|
Modifier: gocui.ModNone,
|
||||||
|
Handler: gui.handleCommitRevert,
|
||||||
|
Description: gui.Tr.SLocalize("revertCommit"),
|
||||||
}, {
|
}, {
|
||||||
ViewName: "stash",
|
ViewName: "stash",
|
||||||
Key: gocui.KeySpace,
|
Key: gocui.KeySpace,
|
||||||
|
@ -90,6 +90,9 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
|||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "stashFiles",
|
ID: "stashFiles",
|
||||||
Other: "stash files",
|
Other: "stash files",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "softReset",
|
||||||
|
Other: "soft reset to last commit",
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "open",
|
ID: "open",
|
||||||
Other: "open",
|
Other: "open",
|
||||||
@ -167,7 +170,13 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
|||||||
Other: "This file has no merge conflicts",
|
Other: "This file has no merge conflicts",
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "SureResetHardHead",
|
ID: "SureResetHardHead",
|
||||||
Other: "Are you sure you want `reset --hard HEAD` and `clean -fd`? You may lose changes",
|
Other: "Are you sure you want to `reset --hard HEAD` and `clean -fd`? You may lose changes",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "SoftReset",
|
||||||
|
Other: "Soft reset",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "ConfirmSoftReset",
|
||||||
|
Other: "Are you sure you want to `reset --soft HEAD^`? The changes in your topmost commit will be placed in your working tree",
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "SureTo",
|
ID: "SureTo",
|
||||||
Other: "Are you sure you want to {{.deleteVerb}} {{.fileName}} (you will lose your changes)?",
|
Other: "Are you sure you want to {{.deleteVerb}} {{.fileName}} (you will lose your changes)?",
|
||||||
@ -276,6 +285,18 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
|||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "SureFixupThisCommit",
|
ID: "SureFixupThisCommit",
|
||||||
Other: "Are you sure you want to fixup this commit? The commit beneath will be squashed up into this one",
|
Other: "Are you sure you want to fixup this commit? The commit beneath will be squashed up into this one",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "SureSquashThisCommit",
|
||||||
|
Other: "Are you sure you want to squash this commit into the commit below?", // TODO: i18n
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "Squash",
|
||||||
|
Other: "Squash", // TODO: i18n
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "pickCommit",
|
||||||
|
Other: "pick commit (when mid-rebase)", // TODO: i18n
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "revertCommit",
|
||||||
|
Other: "revert commit", // TODO: i18n
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "OnlyRenameTopCommit",
|
ID: "OnlyRenameTopCommit",
|
||||||
Other: "Can only rename topmost commit",
|
Other: "Can only rename topmost commit",
|
||||||
@ -295,6 +316,9 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
|||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "editCommit",
|
ID: "editCommit",
|
||||||
Other: "edit commit", // TODO: other languages
|
Other: "edit commit", // TODO: other languages
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "amendToCommit",
|
||||||
|
Other: "amend commit with staged changes", // TODO: other languages
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "renameCommitEditor",
|
ID: "renameCommitEditor",
|
||||||
Other: "rename commit with editor",
|
Other: "rename commit with editor",
|
||||||
|
Reference in New Issue
Block a user