Compare commits

...
29 Commits
Author SHA1 Message Date
Jesse Duffield 66e6369c28 allow fastforwarding the current branch 2020-02-18 23:07:38 +11:00
Jesse Duffield 0f0da9c32a fix wording 2020-02-16 09:57:49 +11:00
Jesse Duffield 0a69c1a02d add reset to reflog commit menu 2020-02-16 09:57:49 +11:00
Jesse Duffield feaf98bd01 add reset to upstream option on files panel 2020-02-16 09:57:49 +11:00
Jesse Duffield 0fe9c15ce8 add mixed option to HEAD resetting, remove @{upstream} 2020-02-16 09:57:49 +11:00
Jesse Duffield f528e12c83 allow resetting to tag 2020-02-16 09:57:49 +11:00
Jesse Duffield 8ca9f93ccf allow resetting to remote branch 2020-02-16 09:57:49 +11:00
Jesse Duffield 73d8064837 allow resetting to branch 2020-02-16 09:57:49 +11:00
Jesse Duffield 5b1f60b7eb refactor create reset menu logic 2020-02-16 09:57:49 +11:00
Jesse Duffield 2e1344f611 fix specs 2020-02-15 08:47:36 +11:00
Jesse Duffield 5b9996b16f remove old createMenu function 2020-02-15 08:47:36 +11:00
Jesse Duffield 6fdc1791e4 refactor stash options menu 2020-02-15 08:47:36 +11:00
Jesse Duffield fd4f37b5c3 refactor git flow menu 2020-02-15 08:47:36 +11:00
Jesse Duffield d76e8887e5 refactor patch options menu panel 2020-02-15 08:47:36 +11:00
Jesse Duffield eb9134685a refactor rebase menu panel 2020-02-15 08:47:36 +11:00
Jesse Duffield d929b84786 refactor recent repos menu panel 2020-02-15 08:47:36 +11:00
Jesse Duffield 8ef3297b11 refactor reflog reset options panel 2020-02-15 08:47:36 +11:00
Jesse Duffield 27c7aeb117 refactor workspace reset options panel 2020-02-15 08:47:36 +11:00
Jesse Duffield c9714600e8 refactor commit reset menu 2020-02-15 08:47:36 +11:00
Jesse Duffield 665fdded14 continue refactor of menu panel 2020-02-15 08:47:36 +11:00
Jesse Duffield 814a0ea36f begin refactor of menu panel 2020-02-15 08:47:36 +11:00
Jesse Duffield 71e018a3dd get whole commit SHA from rebase commits 2020-02-13 18:10:14 +11:00
Jesse Duffield efb26f8b60 refresh current branch graph when side panels refresh 2020-02-10 19:05:55 +11:00
Glenn Vriesman d9eb6e2682 Fixed tests
Signed-off-by: Glenn Vriesman <glenn.vriesman@gmail.com>
2020-02-09 23:47:22 +11:00
Glenn Vriesman b74107f2ba Use 8 instead of 7 digit long sha
Signed-off-by: Glenn Vriesman <glenn.vriesman@gmail.com>
2020-02-09 23:47:22 +11:00
Glenn Vriesman 0cd91a10c6 Increase internal sha size
This does not change the sha size that is displayed to the user

Signed-off-by: Glenn Vriesman <glenn.vriesman@gmail.com>
2020-02-09 23:47:22 +11:00
Jesse Duffield f062e1dcda ignore carriage returns 2020-02-09 16:43:02 +11:00
Glenn Vriesman 9f5397a2d4 Moved function to git.go
Signed-off-by: Glenn Vriesman <glenn.vriesman@gmail.com>
2020-02-06 23:19:29 +11:00
Glenn Vriesman 0164abbd4a Added feature to ignore tracked files
Signed-off-by: Glenn Vriesman <glenn.vriesman@gmail.com>
2020-02-06 23:19:29 +11:00
27 changed files with 412 additions and 405 deletions
+1 -1
View File
@@ -64,5 +64,5 @@ func (c *Commit) GetDisplayStrings(isFocused bool) []string {
tagString = utils.ColoredString(strings.Join(c.Tags, " "), color.FgMagenta) + " "
}
return []string{shaColor.Sprint(c.Sha), actionString + tagString + defaultColor.Sprint(c.Name)}
return []string{shaColor.Sprint(c.Sha[:8]), actionString + tagString + defaultColor.Sprint(c.Name)}
}
+3 -4
View File
@@ -190,7 +190,7 @@ func (c *CommitListBuilder) getInteractiveRebasingCommits() ([]*Commit, error) {
}
splitLine := strings.Split(line, " ")
commits = append([]*Commit{{
Sha: splitLine[1][0:7],
Sha: splitLine[1],
Name: strings.Join(splitLine[2:], " "),
Status: "rebasing",
Action: splitLine[0],
@@ -207,7 +207,7 @@ func (c *CommitListBuilder) getInteractiveRebasingCommits() ([]*Commit, error) {
// Subject: second commit on master
func (c *CommitListBuilder) commitFromPatch(content string) (*Commit, error) {
lines := strings.Split(content, "\n")
sha := strings.Split(lines[0], " ")[1][0:7]
sha := strings.Split(lines[0], " ")[1]
name := strings.TrimPrefix(lines[3], "Subject: ")
return &Commit{
Sha: sha,
@@ -288,8 +288,7 @@ func (c *CommitListBuilder) getLog(limit bool) string {
limitFlag = "-30"
}
c.Log.Warn(fmt.Sprintf("git log --oneline %s", limitFlag))
result, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git log --oneline %s", limitFlag))
result, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git log --oneline %s --abbrev=%d", limitFlag, 20))
if err != nil {
// assume if there is an error there are no commits yet for this branch
return ""
+5 -5
View File
@@ -163,7 +163,7 @@ func TestCommitListBuilderGetLog(t *testing.T) {
"Retrieves logs",
func(cmd string, args ...string) *exec.Cmd {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"log", "--oneline", "-30"}, args)
assert.EqualValues(t, []string{"log", "--oneline", "-30", "--abbrev=20"}, args)
return exec.Command("echo", "6f0b32f commands/git : add GetCommits tests refactor\n9d9d775 circle : remove new line")
},
@@ -175,7 +175,7 @@ func TestCommitListBuilderGetLog(t *testing.T) {
"An error occurred when retrieving logs",
func(cmd string, args ...string) *exec.Cmd {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"log", "--oneline", "-30"}, args)
assert.EqualValues(t, []string{"log", "--oneline", "-30", "--abbrev=20"}, args)
return exec.Command("test")
},
func(output string) {
@@ -212,7 +212,7 @@ func TestCommitListBuilderGetCommits(t *testing.T) {
assert.EqualValues(t, []string{"rev-list", "@{u}..HEAD", "--abbrev-commit"}, args)
return exec.Command("echo")
case "log":
assert.EqualValues(t, []string{"log", "--oneline", "-30"}, args)
assert.EqualValues(t, []string{"log", "--oneline", "-30", "--abbrev=20"}, args)
return exec.Command("echo")
case "merge-base":
assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args)
@@ -239,7 +239,7 @@ func TestCommitListBuilderGetCommits(t *testing.T) {
assert.EqualValues(t, []string{"rev-list", "@{u}..HEAD", "--abbrev-commit"}, args)
return exec.Command("echo", "8a2bb0e")
case "log":
assert.EqualValues(t, []string{"log", "--oneline", "-30"}, args)
assert.EqualValues(t, []string{"log", "--oneline", "-30", "--abbrev=20"}, args)
return exec.Command("echo", "8a2bb0e commit 1\n78976bc commit 2")
case "merge-base":
assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args)
@@ -280,7 +280,7 @@ func TestCommitListBuilderGetCommits(t *testing.T) {
assert.EqualValues(t, []string{"rev-list", "@{u}..HEAD", "--abbrev-commit"}, args)
return exec.Command("echo", "8a2bb0e")
case "log":
assert.EqualValues(t, []string{"log", "--oneline", "-30"}, args)
assert.EqualValues(t, []string{"log", "--oneline", "-30", "--abbrev=20"}, args)
return exec.Command("echo", "8a2bb0e commit 1\n78976bc commit 2")
case "merge-base":
assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args)
+10
View File
@@ -408,6 +408,11 @@ func (c *GitCommand) Pull(args string, ask func(string) string) error {
return c.OSCommand.DetectUnamePass("git pull --no-edit "+args, ask)
}
// PullWithoutPasswordCheck assumes that the pull will not prompt the user for a password
func (c *GitCommand) PullWithoutPasswordCheck(args string) error {
return c.OSCommand.RunCommand("git pull --no-edit " + args)
}
// Push pushes to a branch
func (c *GitCommand) Push(branchName string, force bool, upstream string, args string, ask func(string) string) error {
forceFlag := ""
@@ -940,6 +945,11 @@ func (c *GitCommand) DiscardAnyUnstagedFileChanges() error {
return c.OSCommand.RunCommand("git checkout -- .")
}
// RemoveTrackedFiles will delete the given file(s) even if they are currently tracked
func (c *GitCommand) RemoveTrackedFiles(name string) error {
return c.OSCommand.RunCommand("git rm -r --cached %s", name)
}
// RemoveUntrackedFiles runs `git clean -fd`
func (c *GitCommand) RemoveUntrackedFiles() error {
return c.OSCommand.RunCommand("git clean -fd")
+22 -7
View File
@@ -97,8 +97,7 @@ func (gui *Gui) refreshBranches(g *gocui.Gui) error {
// TODO: if we're in the remotes view and we've just deleted a remote we need to refresh accordingly
if gui.getBranchesView().Context == "local-branches" {
gui.refreshSelectedLine(&gui.State.Panels.Branches.SelectedLine, len(gui.State.Branches))
if err := gui.RenderSelectedBranchUpstreamDifferences(); err != nil {
if err := gui.renderLocalBranchesWithSelection(); err != nil {
return err
}
}
@@ -112,10 +111,10 @@ func (gui *Gui) renderLocalBranchesWithSelection() error {
branchesView := gui.getBranchesView()
gui.refreshSelectedLine(&gui.State.Panels.Branches.SelectedLine, len(gui.State.Branches))
if err := gui.renderListPanel(branchesView, gui.State.Branches); err != nil {
if err := gui.RenderSelectedBranchUpstreamDifferences(); err != nil {
return err
}
if gui.g.CurrentView() == branchesView && branchesView.Context == "local-branches" {
if gui.g.CurrentView() == branchesView {
if err := gui.handleBranchSelect(gui.g, branchesView); err != nil {
return err
}
@@ -373,12 +372,19 @@ func (gui *Gui) handleFastForward(g *gocui.Gui, v *gocui.View) error {
go func() {
_ = gui.createLoaderPanel(gui.g, v, message)
if err := gui.GitCommand.FastForward(branch.Name, remoteName, remoteBranchName); err != nil {
_ = gui.createErrorPanel(gui.g, err.Error())
if gui.State.Panels.Branches.SelectedLine == 0 {
if err := gui.GitCommand.PullWithoutPasswordCheck("--ff-only"); err != nil {
_ = gui.createErrorPanel(gui.g, err.Error())
}
_ = gui.refreshSidePanels(gui.g)
} else {
_ = gui.closeConfirmationPrompt(gui.g, true)
if err := gui.GitCommand.FastForward(branch.Name, remoteName, remoteBranchName); err != nil {
_ = gui.createErrorPanel(gui.g, err.Error())
}
_ = gui.RenderSelectedBranchUpstreamDifferences()
}
_ = gui.closeConfirmationPrompt(gui.g, true)
}()
return nil
}
@@ -429,3 +435,12 @@ func (gui *Gui) handlePrevBranchesTab(g *gocui.Gui, v *gocui.View) error {
utils.ModuloWithWrap(v.TabIndex-1, len(v.Tabs)),
)
}
func (gui *Gui) handleCreateResetToBranchMenu(g *gocui.Gui, v *gocui.View) error {
branch := gui.getSelectedBranch()
if branch == nil {
return nil
}
return gui.createResetMenu(branch.Name)
}
+9 -49
View File
@@ -1,10 +1,8 @@
package gui
import (
"fmt"
"strconv"
"github.com/fatih/color"
"github.com/go-errors/errors"
"github.com/jesseduffield/gocui"
@@ -560,53 +558,6 @@ func (gui *Gui) handleSquashAllAboveFixupCommits(g *gocui.Gui, v *gocui.View) er
}, nil)
}
type resetOption struct {
description string
command string
}
// GetDisplayStrings is a function.
func (r *resetOption) GetDisplayStrings(isFocused bool) []string {
return []string{r.description, color.New(color.FgRed).Sprint(r.command)}
}
func (gui *Gui) handleCreateCommitResetMenu(g *gocui.Gui, v *gocui.View) error {
commit := gui.getSelectedCommit(g)
if commit == nil {
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("NoCommitsThisBranch"))
}
strengths := []string{"soft", "mixed", "hard"}
options := make([]*resetOption, len(strengths))
for i, strength := range strengths {
options[i] = &resetOption{
description: fmt.Sprintf("%s reset", strength),
command: fmt.Sprintf("reset --%s %s", strength, commit.Sha),
}
}
handleMenuPress := func(index int) error {
if err := gui.GitCommand.ResetToCommit(commit.Sha, strengths[index]); err != nil {
return err
}
if err := gui.refreshCommits(g); err != nil {
return err
}
if err := gui.refreshFiles(); err != nil {
return err
}
if err := gui.resetOrigin(gui.getCommitsView()); err != nil {
return err
}
gui.State.Panels.Commits.SelectedLine = 0
return gui.handleCommitSelect(g, gui.getCommitsView())
}
return gui.createMenu(fmt.Sprintf("%s %s", gui.Tr.SLocalize("resetTo"), commit.Sha), options, len(options), handleMenuPress)
}
func (gui *Gui) handleTagCommit(g *gocui.Gui, v *gocui.View) error {
// TODO: bring up menu asking if you want to make a lightweight or annotated tag
// if annotated, switch to a subprocess to create the message
@@ -701,3 +652,12 @@ func (gui *Gui) handlePrevCommitsTab(g *gocui.Gui, v *gocui.View) error {
utils.ModuloWithWrap(v.TabIndex-1, len(v.Tabs)),
)
}
func (gui *Gui) handleCreateCommitResetMenu(g *gocui.Gui, v *gocui.View) error {
commit := gui.getSelectedCommit(g)
if commit == nil {
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("NoCommitsThisBranch"))
}
return gui.createResetMenu(commit.Sha)
}
+42
View File
@@ -0,0 +1,42 @@
package gui
import (
"github.com/jesseduffield/gocui"
)
func (gui *Gui) handleCreateDiscardMenu(g *gocui.Gui, v *gocui.View) error {
file, err := gui.getSelectedFile(g)
if err != nil {
if err != gui.Errors.ErrNoFiles {
return err
}
return nil
}
menuItems := []*menuItem{
{
displayString: gui.Tr.SLocalize("discardAllChanges"),
onPress: func() error {
if err := gui.GitCommand.DiscardAllFileChanges(file); err != nil {
return err
}
return gui.refreshFiles()
},
},
}
if file.HasStagedChanges && file.HasUnstagedChanges {
menuItems = append(menuItems, &menuItem{
displayString: gui.Tr.SLocalize("discardUnstagedChanges"),
onPress: func() error {
if err := gui.GitCommand.DiscardUnstagedFileChanges(file); err != nil {
return err
}
return gui.refreshFiles()
},
})
}
return gui.createMenu(file.Name, menuItems, createMenuOptions{showCancel: true})
}
+27 -91
View File
@@ -242,16 +242,29 @@ func (gui *Gui) handleStageAll(g *gocui.Gui, v *gocui.View) error {
}
func (gui *Gui) handleIgnoreFile(g *gocui.Gui, v *gocui.View) error {
file, err := gui.getSelectedFile(g)
file, err := gui.getSelectedFile(gui.g)
if err != nil {
return gui.createErrorPanel(g, err.Error())
return gui.createErrorPanel(gui.g, err.Error())
}
if file.Tracked {
return gui.createErrorPanel(g, gui.Tr.SLocalize("CantIgnoreTrackFiles"))
return gui.createConfirmationPanel(gui.g, gui.g.CurrentView(), true, gui.Tr.SLocalize("IgnoreTracked"), gui.Tr.SLocalize("IgnoreTrackedPrompt"),
// On confirmation
func(_ *gocui.Gui, _ *gocui.View) error {
if err := gui.GitCommand.Ignore(file.Name); err != nil {
return err
}
if err := gui.GitCommand.RemoveTrackedFiles(file.Name); err != nil {
return err
}
return gui.refreshFiles()
}, nil)
}
if err := gui.GitCommand.Ignore(file.Name); err != nil {
return gui.createErrorPanel(g, err.Error())
return gui.createErrorPanel(gui.g, err.Error())
}
return gui.refreshFiles()
}
@@ -527,67 +540,6 @@ func (gui *Gui) anyFilesWithMergeConflicts() bool {
return false
}
type discardOption struct {
handler func(fileName *commands.File) error
description string
}
// GetDisplayStrings is a function.
func (r *discardOption) GetDisplayStrings(isFocused bool) []string {
return []string{r.description}
}
func (gui *Gui) handleCreateDiscardMenu(g *gocui.Gui, v *gocui.View) error {
file, err := gui.getSelectedFile(g)
if err != nil {
if err != gui.Errors.ErrNoFiles {
return err
}
return nil
}
options := []*discardOption{
{
description: gui.Tr.SLocalize("discardAllChanges"),
handler: func(file *commands.File) error {
return gui.GitCommand.DiscardAllFileChanges(file)
},
},
{
description: gui.Tr.SLocalize("cancel"),
handler: func(file *commands.File) error {
return nil
},
},
}
if file.HasStagedChanges && file.HasUnstagedChanges {
discardUnstagedChanges := &discardOption{
description: gui.Tr.SLocalize("discardUnstagedChanges"),
handler: func(file *commands.File) error {
return gui.GitCommand.DiscardUnstagedFileChanges(file)
},
}
options = append(options[:1], append([]*discardOption{discardUnstagedChanges}, options[1:]...)...)
}
handleMenuPress := func(index int) error {
file, err := gui.getSelectedFile(g)
if err != nil {
return err
}
if err := options[index].handler(file); err != nil {
return err
}
return gui.refreshFiles()
}
return gui.createMenu(file.Name, options, len(options), handleMenuPress)
}
func (gui *Gui) handleCustomCommand(g *gocui.Gui, v *gocui.View) error {
return gui.createPromptPanel(g, v, gui.Tr.SLocalize("CustomCommand"), "", func(g *gocui.Gui, v *gocui.View) error {
command := gui.trimmedContent(v)
@@ -596,45 +548,29 @@ func (gui *Gui) handleCustomCommand(g *gocui.Gui, v *gocui.View) error {
})
}
type stashOption struct {
description string
handler func() error
}
// GetDisplayStrings is a function.
func (o *stashOption) GetDisplayStrings(isFocused bool) []string {
return []string{o.description}
}
func (gui *Gui) handleCreateStashMenu(g *gocui.Gui, v *gocui.View) error {
options := []*stashOption{
menuItems := []*menuItem{
{
description: gui.Tr.SLocalize("stashAllChanges"),
handler: func() error {
displayString: gui.Tr.SLocalize("stashAllChanges"),
onPress: func() error {
return gui.handleStashSave(gui.GitCommand.StashSave)
},
},
{
description: gui.Tr.SLocalize("stashStagedChanges"),
handler: func() error {
displayString: gui.Tr.SLocalize("stashStagedChanges"),
onPress: func() error {
return gui.handleStashSave(gui.GitCommand.StashSaveStagedChanges)
},
},
{
description: gui.Tr.SLocalize("cancel"),
handler: func() error {
return nil
},
},
}
handleMenuPress := func(index int) error {
return options[index].handler()
}
return gui.createMenu(gui.Tr.SLocalize("stashOptions"), options, len(options), handleMenuPress)
return gui.createMenu(gui.Tr.SLocalize("stashOptions"), menuItems, createMenuOptions{showCancel: true})
}
func (gui *Gui) handleStashChanges(g *gocui.Gui, v *gocui.View) error {
return gui.handleStashSave(gui.GitCommand.StashSave)
}
func (gui *Gui) handleCreateResetToUpstreamMenu(g *gocui.Gui, v *gocui.View) error {
return gui.createResetMenu("@{upstream}")
}
+10 -30
View File
@@ -8,11 +8,6 @@ import (
"github.com/jesseduffield/gocui"
)
type gitFlowOption struct {
handler func() error
description string
}
func (gui *Gui) gitFlowFinishBranch(gitFlowConfig string, branchName string) error {
// need to find out what kind of branch this is
prefix := strings.SplitAfterN(branchName, "/", 2)[0]
@@ -41,11 +36,6 @@ func (gui *Gui) gitFlowFinishBranch(gitFlowConfig string, branchName string) err
return gui.Errors.ErrSubProcess
}
// GetDisplayStrings is a function.
func (r *gitFlowOption) GetDisplayStrings(isFocused bool) []string {
return []string{r.description}
}
func (gui *Gui) handleCreateGitFlowMenu(g *gocui.Gui, v *gocui.View) error {
branch := gui.getSelectedBranch()
if branch == nil {
@@ -70,37 +60,27 @@ func (gui *Gui) handleCreateGitFlowMenu(g *gocui.Gui, v *gocui.View) error {
}
}
options := []*gitFlowOption{
menuItems := []*menuItem{
{
// not localising here because it's one to one with the actual git flow commands
description: fmt.Sprintf("finish branch '%s'", branch.Name),
handler: func() error {
displayString: fmt.Sprintf("finish branch '%s'", branch.Name),
onPress: func() error {
return gui.gitFlowFinishBranch(gitFlowConfig, branch.Name)
},
},
{
description: "start feature",
handler: startHandler("feature"),
displayString: "start feature",
onPress: startHandler("feature"),
},
{
description: "start hotfix",
handler: startHandler("hotfix"),
displayString: "start hotfix",
onPress: startHandler("hotfix"),
},
{
description: "start release",
handler: startHandler("release"),
},
{
description: gui.Tr.SLocalize("cancel"),
handler: func() error {
return nil
},
displayString: "start release",
onPress: startHandler("release"),
},
}
handleMenuPress := func(index int) error {
return options[index].handler()
}
return gui.createMenu("git flow", options, len(options), handleMenuPress)
return gui.createMenu("git flow", menuItems, createMenuOptions{})
}
+2
View File
@@ -508,6 +508,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
v.Title = gui.Tr.SLocalize("DiffTitle")
v.Wrap = true
v.FgColor = textColor
v.IgnoreCarriageReturns = true
}
hiddenViewOffset := 0
@@ -522,6 +523,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
secondaryView.Title = gui.Tr.SLocalize("DiffTitle")
secondaryView.Wrap = true
secondaryView.FgColor = gocui.ColorWhite
secondaryView.IgnoreCarriageReturns = true
}
if v, err := g.SetView("status", 0, 0, leftSideWidth, vHeights["status"]-1, gocui.BOTTOM|gocui.RIGHT); err != nil {
+31
View File
@@ -457,6 +457,13 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Handler: gui.handleCustomCommand,
Description: gui.Tr.SLocalize("executeCustomCommand"),
},
{
ViewName: "files",
Key: gui.getKey("commits.viewResetOptions"),
Modifier: gocui.ModNone,
Handler: gui.handleCreateResetToUpstreamMenu,
Description: gui.Tr.SLocalize("viewResetToUpstreamOptions"),
},
{
ViewName: "branches",
Contexts: []string{"local-branches"},
@@ -537,6 +544,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Handler: gui.handleFastForward,
Description: gui.Tr.SLocalize("FastForward"),
},
{
ViewName: "branches",
Contexts: []string{"local-branches"},
Key: gui.getKey("commits.viewResetOptions"),
Modifier: gocui.ModNone,
Handler: gui.handleCreateResetToBranchMenu,
Description: gui.Tr.SLocalize("viewResetOptions"),
},
{
ViewName: "branches",
Contexts: []string{"tags"},
@@ -569,6 +584,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Handler: gui.handleCreateTag,
Description: gui.Tr.SLocalize("createTag"),
},
{
ViewName: "branches",
Contexts: []string{"tags"},
Key: gui.getKey("commits.viewResetOptions"),
Modifier: gocui.ModNone,
Handler: gui.handleCreateResetToTagMenu,
Description: gui.Tr.SLocalize("viewResetOptions"),
},
{
ViewName: "branches",
Key: gui.getKey("universal.nextTab"),
@@ -589,6 +612,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Handler: gui.handleRemoteBranchesEscape,
Description: gui.Tr.SLocalize("ReturnToRemotesList"),
},
{
ViewName: "branches",
Contexts: []string{"remote-branches"},
Key: gui.getKey("commits.viewResetOptions"),
Modifier: gocui.ModNone,
Handler: gui.handleCreateResetToRemoteBranchMenu,
Description: gui.Tr.SLocalize("viewResetOptions"),
},
{
ViewName: "branches",
Contexts: []string{"remotes"},
+34 -7
View File
@@ -8,6 +8,12 @@ import (
"github.com/jesseduffield/lazygit/pkg/utils"
)
type menuItem struct {
displayString string
displayStrings []string
onPress func() error
}
// list panel functions
func (gui *Gui) handleMenuSelect(g *gocui.Gui, v *gocui.View) error {
@@ -38,14 +44,34 @@ func (gui *Gui) handleMenuClose(g *gocui.Gui, v *gocui.View) error {
return gui.returnFocus(g, v)
}
func (gui *Gui) createMenu(title string, items interface{}, itemCount int, handlePress func(int) error) error {
isFocused := gui.g.CurrentView().Name() == "menu"
gui.State.MenuItemCount = itemCount
list, err := utils.RenderList(items, isFocused)
if err != nil {
return err
type createMenuOptions struct {
showCancel bool
}
func (gui *Gui) createMenu(title string, items []*menuItem, createMenuOptions createMenuOptions) error {
if createMenuOptions.showCancel {
// this is mutative but I'm okay with that for now
items = append(items, &menuItem{
displayStrings: []string{gui.Tr.SLocalize("cancel")},
onPress: func() error {
return nil
},
})
}
gui.State.MenuItemCount = len(items)
stringArrays := make([][]string, len(items))
for i, item := range items {
if item.displayStrings == nil {
stringArrays[i] = []string{item.displayString}
} else {
stringArrays[i] = item.displayStrings
}
}
list := utils.RenderDisplayStrings(stringArrays)
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(gui.g, false, list)
menuView, _ := gui.g.SetView("menu", x0, y0, x1, y1, 0)
menuView.Title = title
@@ -56,9 +82,10 @@ func (gui *Gui) createMenu(title string, items interface{}, itemCount int, handl
wrappedHandlePress := func(g *gocui.Gui, v *gocui.View) error {
selectedLine := gui.State.Panels.Menu.SelectedLine
if err := handlePress(selectedLine); err != nil {
if err := items[selectedLine].onPress(); err != nil {
return err
}
if _, err := gui.g.View("menu"); err == nil {
if _, err := gui.g.SetViewOnBottom("menu"); err != nil {
return err
+16 -14
View File
@@ -3,8 +3,6 @@ package gui
import (
"strings"
"github.com/go-errors/errors"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@@ -38,19 +36,23 @@ func (gui *Gui) getBindings(v *gocui.View) []*Binding {
func (gui *Gui) handleCreateOptionsMenu(g *gocui.Gui, v *gocui.View) error {
bindings := gui.getBindings(v)
handleMenuPress := func(index int) error {
if bindings[index].Key == nil {
return nil
menuItems := make([]*menuItem, len(bindings))
for i, binding := range bindings {
innerBinding := binding // note to self, never close over loop variables
menuItems[i] = &menuItem{
displayStrings: []string{GetKeyDisplay(innerBinding.Key), innerBinding.Description},
onPress: func() error {
if innerBinding.Key == nil {
return nil
}
if err := gui.handleMenuClose(g, v); err != nil {
return err
}
return innerBinding.Handler(g, v)
},
}
if index >= len(bindings) {
return errors.New("Index is greater than size of bindings")
}
err := gui.handleMenuClose(g, v)
if err != nil {
return err
}
return bindings[index].Handler(g, v)
}
return gui.createMenu(strings.Title(gui.Tr.SLocalize("menu")), bindings, len(bindings), handleMenuPress)
return gui.createMenu(strings.Title(gui.Tr.SLocalize("menu")), menuItems, createMenuOptions{})
}
+20 -25
View File
@@ -6,48 +6,43 @@ import (
"github.com/jesseduffield/gocui"
)
type patchMenuOption struct {
displayName string
function func() error
}
// GetDisplayStrings is a function.
func (o *patchMenuOption) GetDisplayStrings(isFocused bool) []string {
return []string{o.displayName}
}
func (gui *Gui) handleCreatePatchOptionsMenu(g *gocui.Gui, v *gocui.View) error {
if !gui.GitCommand.PatchManager.CommitSelected() {
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("NoPatchError"))
}
options := []*patchMenuOption{
{displayName: fmt.Sprintf("remove patch from original commit (%s)", gui.GitCommand.PatchManager.CommitSha), function: gui.handleDeletePatchFromCommit},
{displayName: "pull patch out into index", function: gui.handlePullPatchIntoWorkingTree},
{displayName: "reset patch", function: gui.handleResetPatch},
menuItems := []*menuItem{
{
displayString: fmt.Sprintf("remove patch from original commit (%s)", gui.GitCommand.PatchManager.CommitSha),
onPress: gui.handleDeletePatchFromCommit,
},
{
displayString: "pull patch out into index",
onPress: gui.handlePullPatchIntoWorkingTree,
},
{
displayString: "reset patch",
onPress: gui.handleResetPatch,
},
}
selectedCommit := gui.getSelectedCommit(gui.g)
if selectedCommit != nil && gui.GitCommand.PatchManager.CommitSha != selectedCommit.Sha {
// adding this option to index 1
options = append(
options[:1],
menuItems = append(
menuItems[:1],
append(
[]*patchMenuOption{
[]*menuItem{
{
displayName: fmt.Sprintf("move patch to selected commit (%s)", selectedCommit.Sha),
function: gui.handleMovePatchToSelectedCommit,
displayString: fmt.Sprintf("move patch to selected commit (%s)", selectedCommit.Sha),
onPress: gui.handleMovePatchToSelectedCommit,
},
}, options[1:]...,
}, menuItems[1:]...,
)...,
)
}
handleMenuPress := func(index int) error {
return options[index].function()
}
return gui.createMenu(gui.Tr.SLocalize("PatchOptionsTitle"), options, len(options), handleMenuPress)
return gui.createMenu(gui.Tr.SLocalize("PatchOptionsTitle"), menuItems, createMenuOptions{showCancel: true})
}
func (gui *Gui) getPatchCommitIndex() int {
+10 -22
View File
@@ -7,33 +7,21 @@ import (
"github.com/jesseduffield/gocui"
)
type option struct {
value string
}
// GetDisplayStrings is a function.
func (r *option) GetDisplayStrings(isFocused bool) []string {
return []string{r.value}
}
func (gui *Gui) handleCreateRebaseOptionsMenu(g *gocui.Gui, v *gocui.View) error {
options := []*option{
{value: "continue"},
{value: "abort"},
}
options := []string{"continue", "abort"}
if gui.State.WorkingTreeState == "rebasing" {
options = append(options, &option{value: "skip"})
options = append(options, "skip")
}
options = append(options, &option{value: "cancel"})
handleMenuPress := func(index int) error {
command := options[index].value
if command == "cancel" {
return nil
menuItems := make([]*menuItem, len(options))
for i, option := range options {
menuItems[i] = &menuItem{
displayString: option,
onPress: func() error {
return gui.genericMergeCommand(option)
},
}
return gui.genericMergeCommand(command)
}
var title string
@@ -43,7 +31,7 @@ func (gui *Gui) handleCreateRebaseOptionsMenu(g *gocui.Gui, v *gocui.View) error
title = gui.Tr.SLocalize("RebaseOptionsTitle")
}
return gui.createMenu(title, options, len(options), handleMenuPress)
return gui.createMenu(title, menuItems, createMenuOptions{showCancel: true})
}
func (gui *Gui) genericMergeCommand(command string) error {
+21 -28
View File
@@ -10,41 +10,34 @@ import (
"github.com/jesseduffield/lazygit/pkg/utils"
)
type recentRepo struct {
path string
}
// GetDisplayStrings returns the path from a recent repo.
func (r *recentRepo) GetDisplayStrings(isFocused bool) []string {
yellow := color.New(color.FgMagenta)
base := filepath.Base(r.path)
path := yellow.Sprint(r.path)
return []string{base, path}
}
func (gui *Gui) handleCreateRecentReposMenu(g *gocui.Gui, v *gocui.View) error {
recentRepoPaths := gui.Config.GetAppState().RecentRepos
reposCount := utils.Min(len(recentRepoPaths), 20)
yellow := color.New(color.FgMagenta)
// we won't show the current repo hence the -1
recentRepos := make([]*recentRepo, reposCount-1)
menuItems := make([]*menuItem, reposCount-1)
for i, path := range recentRepoPaths[1:reposCount] {
recentRepos[i] = &recentRepo{path: path}
innerPath := path
menuItems[i] = &menuItem{
displayStrings: []string{
filepath.Base(innerPath),
yellow.Sprint(innerPath),
},
onPress: func() error {
if err := os.Chdir(innerPath); err != nil {
return err
}
newGitCommand, err := commands.NewGitCommand(gui.Log, gui.OSCommand, gui.Tr, gui.Config)
if err != nil {
return err
}
gui.GitCommand = newGitCommand
return gui.Errors.ErrSwitchRepo
},
}
}
handleMenuPress := func(index int) error {
repo := recentRepos[index]
if err := os.Chdir(repo.path); err != nil {
return err
}
newGitCommand, err := commands.NewGitCommand(gui.Log, gui.OSCommand, gui.Tr, gui.Config)
if err != nil {
return err
}
gui.GitCommand = newGitCommand
return gui.Errors.ErrSwitchRepo
}
return gui.createMenu(gui.Tr.SLocalize("RecentRepos"), recentRepos, len(recentRepos), handleMenuPress)
return gui.createMenu(gui.Tr.SLocalize("RecentRepos"), menuItems, createMenuOptions{showCancel: true})
}
// updateRecentRepoList registers the fact that we opened lazygit in this repo,
+6
View File
@@ -95,3 +95,9 @@ func (gui *Gui) handleCheckoutReflogCommit(g *gocui.Gui, v *gocui.View) error {
return nil
}
func (gui *Gui) handleCreateReflogResetMenu(g *gocui.Gui, v *gocui.View) error {
commit := gui.getSelectedReflogCommit()
return gui.createResetMenu(commit.Sha)
}
-60
View File
@@ -1,60 +0,0 @@
package gui
import (
"fmt"
"github.com/fatih/color"
"github.com/jesseduffield/gocui"
)
type reflogResetOption struct {
handler func() error
description string
command string
}
// GetDisplayStrings is a function.
func (r *reflogResetOption) GetDisplayStrings(isFocused bool) []string {
return []string{r.description, color.New(color.FgRed).Sprint(r.command)}
}
func (gui *Gui) handleCreateReflogResetMenu(g *gocui.Gui, v *gocui.View) error {
commit := gui.getSelectedReflogCommit()
resetFunction := func(reset func(string) error) func() error {
return func() error {
if err := reset(commit.Sha); err != nil {
return gui.createErrorPanel(gui.g, err.Error())
}
gui.State.Panels.ReflogCommits.SelectedLine = 0
return gui.refreshSidePanels(gui.g)
}
}
options := []*reflogResetOption{
{
description: gui.Tr.SLocalize("hardReset"),
command: fmt.Sprintf("reset --hard %s", commit.Sha),
handler: resetFunction(gui.GitCommand.ResetHard),
},
{
description: gui.Tr.SLocalize("softReset"),
command: fmt.Sprintf("reset --soft %s", commit.Sha),
handler: resetFunction(gui.GitCommand.ResetSoft),
},
{
description: gui.Tr.SLocalize("cancel"),
handler: func() error {
return nil
},
},
}
handleMenuPress := func(index int) error {
return options[index].handler()
}
return gui.createMenu("", options, len(options), handleMenuPress)
}
+9
View File
@@ -132,3 +132,12 @@ func (gui *Gui) handleSetBranchUpstream(g *gocui.Gui, v *gocui.View) error {
return gui.refreshSidePanels(gui.g)
}, nil)
}
func (gui *Gui) handleCreateResetToRemoteBranchMenu(g *gocui.Gui, v *gocui.View) error {
selectedBranch := gui.getSelectedRemoteBranch()
if selectedBranch == nil {
return nil
}
return gui.createResetMenu(fmt.Sprintf("%s/%s", selectedBranch.RemoteName, selectedBranch.Name))
}
+46
View File
@@ -0,0 +1,46 @@
package gui
import (
"fmt"
"github.com/fatih/color"
)
func (gui *Gui) createResetMenu(ref string) error {
strengths := []string{"soft", "mixed", "hard"}
menuItems := make([]*menuItem, len(strengths))
for i, strength := range strengths {
innerStrength := strength
menuItems[i] = &menuItem{
displayStrings: []string{
fmt.Sprintf("%s reset", strength),
color.New(color.FgRed).Sprint(
fmt.Sprintf("reset --%s %s", strength, ref),
),
},
onPress: func() error {
if err := gui.GitCommand.ResetToCommit(ref, innerStrength); err != nil {
return gui.createErrorPanel(gui.g, err.Error())
}
gui.switchCommitsPanelContext("branch-commits")
gui.State.Panels.Commits.SelectedLine = 0
gui.State.Panels.ReflogCommits.SelectedLine = 0
if err := gui.refreshCommits(gui.g); err != nil {
return err
}
if err := gui.refreshFiles(); err != nil {
return err
}
if err := gui.resetOrigin(gui.getCommitsView()); err != nil {
return err
}
return gui.handleCommitSelect(gui.g, gui.getCommitsView())
},
}
}
return gui.createMenu(fmt.Sprintf("%s %s", gui.Tr.SLocalize("resetTo"), ref), menuItems, createMenuOptions{showCancel: true})
}
+9
View File
@@ -140,3 +140,12 @@ func (gui *Gui) handleCreateTag(g *gocui.Gui, v *gocui.View) error {
return gui.refreshTags()
})
}
func (gui *Gui) handleCreateResetToTagMenu(g *gocui.Gui, v *gocui.View) error {
tag := gui.getSelectedTag()
if tag == nil {
return nil
}
return gui.createResetMenu(tag.Name)
}
+37 -44
View File
@@ -5,23 +5,16 @@ import (
"github.com/jesseduffield/gocui"
)
type workspaceResetOption struct {
handler func() error
description string
command string
}
// GetDisplayStrings is a function.
func (r *workspaceResetOption) GetDisplayStrings(isFocused bool) []string {
return []string{r.description, color.New(color.FgRed).Sprint(r.command)}
}
func (gui *Gui) handleCreateResetMenu(g *gocui.Gui, v *gocui.View) error {
options := []*workspaceResetOption{
red := color.New(color.FgRed)
menuItems := []*menuItem{
{
description: gui.Tr.SLocalize("discardAllChangesToAllFiles"),
command: "reset --hard HEAD && git clean -fd",
handler: func() error {
displayStrings: []string{
gui.Tr.SLocalize("discardAllChangesToAllFiles"),
red.Sprint("reset --hard HEAD && git clean -fd"),
},
onPress: func() error {
if err := gui.GitCommand.ResetAndClean(); err != nil {
return gui.createErrorPanel(gui.g, err.Error())
}
@@ -30,9 +23,11 @@ func (gui *Gui) handleCreateResetMenu(g *gocui.Gui, v *gocui.View) error {
},
},
{
description: gui.Tr.SLocalize("discardAnyUnstagedChanges"),
command: "git checkout -- .",
handler: func() error {
displayStrings: []string{
gui.Tr.SLocalize("discardAnyUnstagedChanges"),
red.Sprint("git checkout -- ."),
},
onPress: func() error {
if err := gui.GitCommand.DiscardAnyUnstagedFileChanges(); err != nil {
return gui.createErrorPanel(gui.g, err.Error())
}
@@ -41,9 +36,11 @@ func (gui *Gui) handleCreateResetMenu(g *gocui.Gui, v *gocui.View) error {
},
},
{
description: gui.Tr.SLocalize("discardUntrackedFiles"),
command: "git clean -fd",
handler: func() error {
displayStrings: []string{
gui.Tr.SLocalize("discardUntrackedFiles"),
red.Sprint("git clean -fd"),
},
onPress: func() error {
if err := gui.GitCommand.RemoveUntrackedFiles(); err != nil {
return gui.createErrorPanel(gui.g, err.Error())
}
@@ -52,9 +49,11 @@ func (gui *Gui) handleCreateResetMenu(g *gocui.Gui, v *gocui.View) error {
},
},
{
description: gui.Tr.SLocalize("softReset"),
command: "git reset --soft HEAD",
handler: func() error {
displayStrings: []string{
gui.Tr.SLocalize("softReset"),
red.Sprint("git reset --soft HEAD"),
},
onPress: func() error {
if err := gui.GitCommand.ResetSoft("HEAD"); err != nil {
return gui.createErrorPanel(gui.g, err.Error())
}
@@ -63,10 +62,12 @@ func (gui *Gui) handleCreateResetMenu(g *gocui.Gui, v *gocui.View) error {
},
},
{
description: gui.Tr.SLocalize("hardReset"),
command: "git reset --hard HEAD",
handler: func() error {
if err := gui.GitCommand.ResetHard("HEAD"); err != nil {
displayStrings: []string{
"mixed reset",
red.Sprint("git reset --mixed HEAD"),
},
onPress: func() error {
if err := gui.GitCommand.ResetSoft("HEAD"); err != nil {
return gui.createErrorPanel(gui.g, err.Error())
}
@@ -74,27 +75,19 @@ func (gui *Gui) handleCreateResetMenu(g *gocui.Gui, v *gocui.View) error {
},
},
{
description: gui.Tr.SLocalize("hardResetUpstream"),
command: "git reset --hard @{upstream}",
handler: func() error {
if err := gui.GitCommand.ResetHard("@{upstream}"); err != nil {
displayStrings: []string{
gui.Tr.SLocalize("hardReset"),
red.Sprint("git reset --hard HEAD"),
},
onPress: func() error {
if err := gui.GitCommand.ResetHard("HEAD"); err != nil {
return gui.createErrorPanel(gui.g, err.Error())
}
return gui.refreshSidePanels(gui.g)
},
},
{
description: gui.Tr.SLocalize("cancel"),
handler: func() error {
return nil
return gui.refreshFiles()
},
},
}
handleMenuPress := func(index int) error {
return options[index].handler()
}
return gui.createMenu("", options, len(options), handleMenuPress)
return gui.createMenu("", menuItems, createMenuOptions{showCancel: true})
}
+6 -3
View File
@@ -136,9 +136,6 @@ func addDutch(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "CannotGitAdd",
Other: "Kan commando niet uitvoeren git add --path untracked files",
}, &i18n.Message{
ID: "CantIgnoreTrackFiles",
Other: "Kan gevolgde bestanden niet negeren",
}, &i18n.Message{
ID: "NoStagedFilesToCommit",
Other: "Er zijn geen staged bestanden om te commiten",
@@ -760,6 +757,12 @@ func addDutch(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "ReturnToRemotesList",
Other: `return to remotes list`,
}, &i18n.Message{
ID: "IgnoreTracked",
Other: "Ignore tracked file",
}, &i18n.Message{
ID: "IgnoreTrackedPrompt",
Other: "Are you sure you want to ignore a tracked file?",
},
)
}
+9 -3
View File
@@ -162,9 +162,6 @@ func addEnglish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "CannotGitAdd",
Other: "Cannot git add --patch untracked files",
}, &i18n.Message{
ID: "CantIgnoreTrackFiles",
Other: "Cannot ignore tracked files",
}, &i18n.Message{
ID: "NoStagedFilesToCommit",
Other: "There are no staged files to commit",
@@ -939,6 +936,15 @@ func addEnglish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "NewBranchNamePrompt",
Other: "new {{.branchType}} name:",
}, &i18n.Message{
ID: "IgnoreTracked",
Other: "Ignore tracked file",
}, &i18n.Message{
ID: "IgnoreTrackedPrompt",
Other: "Are you sure you want to ignore a tracked file?",
}, &i18n.Message{
ID: "viewResetToUpstreamOptions",
Other: "view upstream reset options",
},
)
}
+6 -3
View File
@@ -128,9 +128,6 @@ func addPolish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "CannotGitAdd",
Other: "Nie można git add --patch nieśledzonych plików",
}, &i18n.Message{
ID: "CantIgnoreTrackFiles",
Other: "Nie można zignorować nieśledzonych plików",
}, &i18n.Message{
ID: "NoStagedFilesToCommit",
Other: "Brak zatwierdzonych plików do commita",
@@ -743,6 +740,12 @@ func addPolish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "ReturnToRemotesList",
Other: `return to remotes list`,
}, &i18n.Message{
ID: "IgnoreTracked",
Other: "Ignore tracked file",
}, &i18n.Message{
ID: "IgnoreTrackedPrompt",
Other: "Are you sure you want to ignore a tracked file?",
},
)
}
+20 -8
View File
@@ -151,14 +151,14 @@ func renderDisplayableList(items []Displayable, isFocused bool) (string, error)
stringArrays := getDisplayStringArrays(items, isFocused)
if !displayArraysAligned(stringArrays) {
return "", errors.New("Each item must return the same number of strings to display")
}
return RenderDisplayStrings(stringArrays), nil
}
padWidths := getPadWidths(stringArrays)
paddedDisplayStrings := getPaddedDisplayStrings(stringArrays, padWidths)
func RenderDisplayStrings(displayStringsArr [][]string) string {
padWidths := getPadWidths(displayStringsArr)
paddedDisplayStrings := getPaddedDisplayStrings(displayStringsArr, padWidths)
return strings.Join(paddedDisplayStrings, "\n"), nil
return strings.Join(paddedDisplayStrings, "\n")
}
// Decolorise strips a string of color
@@ -168,10 +168,16 @@ func Decolorise(str string) string {
}
func getPadWidths(stringArrays [][]string) []int {
if len(stringArrays[0]) <= 1 {
maxWidth := 0
for _, stringArray := range stringArrays {
if len(stringArray) > maxWidth {
maxWidth = len(stringArray)
}
}
if maxWidth-1 < 0 {
return []int{}
}
padWidths := make([]int, len(stringArrays[0])-1)
padWidths := make([]int, maxWidth-1)
for i := range padWidths {
for _, strings := range stringArrays {
uncoloredString := Decolorise(strings[i])
@@ -190,8 +196,14 @@ func getPaddedDisplayStrings(stringArrays [][]string, padWidths []int) []string
continue
}
for j, padWidth := range padWidths {
if len(stringArray)-1 < j {
continue
}
paddedDisplayStrings[i] += WithPadding(stringArray[j], padWidth) + " "
}
if len(stringArray)-1 < len(padWidths) {
continue
}
paddedDisplayStrings[i] += stringArray[len(padWidths)]
}
return paddedDisplayStrings
+1 -1
View File
@@ -276,8 +276,8 @@ func TestRenderDisplayableList(t *testing.T) {
Displayable(&myDisplayable{[]string{"b", "c"}}),
},
false,
"a \nb c",
"",
"Each item must return the same number of strings to display",
},
{
[]Displayable{