summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--pkg/commands/file.go1
-rw-r--r--pkg/commands/git.go42
-rw-r--r--pkg/commands/git_test.go4
-rw-r--r--pkg/commands/os.go61
-rw-r--r--pkg/gui/files_panel.go43
-rw-r--r--pkg/gui/keybindings.go10
-rw-r--r--pkg/gui/menu_panel.go2
-rw-r--r--pkg/gui/stash_panel.go9
-rw-r--r--pkg/i18n/dutch.go15
-rw-r--r--pkg/i18n/english.go15
-rw-r--r--pkg/i18n/polish.go15
11 files changed, 199 insertions, 18 deletions
diff --git a/pkg/commands/file.go b/pkg/commands/file.go
index 39d989634..3c4e13f06 100644
--- a/pkg/commands/file.go
+++ b/pkg/commands/file.go
@@ -14,6 +14,7 @@ type File struct {
HasInlineMergeConflicts bool
DisplayString string
Type string // one of 'file', 'directory', and 'other'
+ ShortStatus string // e.g. 'AD', ' A', 'M ', '??'
}
// GetDisplayStrings returns the display string of a file
diff --git a/pkg/commands/git.go b/pkg/commands/git.go
index 2a6f57659..8ef3a1903 100644
--- a/pkg/commands/git.go
+++ b/pkg/commands/git.go
@@ -187,6 +187,7 @@ func (c *GitCommand) GetStatusFiles() []*File {
HasMergeConflicts: change == "UU" || change == "AA" || change == "DU",
HasInlineMergeConflicts: change == "UU" || change == "AA",
Type: c.OSCommand.FileType(filename),
+ ShortStatus: change,
}
files = append(files, file)
}
@@ -436,7 +437,7 @@ func (c *GitCommand) UnStageFile(fileName string, tracked bool) error {
// GitStatus returns the plaintext short status of the repo
func (c *GitCommand) GitStatus() (string, error) {
- return c.OSCommand.RunCommandWithOutput("git status --untracked-files=all --short")
+ return c.OSCommand.RunCommandWithOutput("git status --untracked-files=all --porcelain")
}
// IsInMergeState states whether we are still mid-merge
@@ -965,3 +966,42 @@ func (c *GitCommand) SquashAllAboveFixupCommits(sha string) error {
),
)
}
+
+// StashSaveStagedChanges stashes only the currently staged changes. This takes a few steps
+// shoutouts to Joe on https://stackoverflow.com/questions/14759748/stashing-only-staged-changes-in-git-is-it-possible
+func (c *GitCommand) StashSaveStagedChanges(message string) error {
+
+ if err := c.OSCommand.RunCommand("git stash --keep-index"); err != nil {
+ return err
+ }
+
+ if err := c.StashSave(message); err != nil {
+ return err
+ }
+
+ if err := c.OSCommand.RunCommand("git stash apply stash@{1}"); err != nil {
+ return err
+ }
+
+ if err := c.OSCommand.PipeCommands("git stash show -p", "git apply -R"); err != nil {
+ return err
+ }
+
+ if err := c.OSCommand.RunCommand("git stash drop stash@{1}"); err != nil {
+ return err
+ }
+
+ // if you had staged an untracked file, that will now appear as 'AD' in git status
+ // meaning it's deleted in your working tree but added in your index. Given that it's
+ // now safely stashed, we need to remove it.
+ files := c.GetStatusFiles()
+ for _, file := range files {
+ if file.ShortStatus == "AD" {
+ if err := c.UnStageFile(file.Name, false); err != nil {
+ return err
+ }
+ }
+ }
+
+ return nil
+}
diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go
index be9f8c883..35e97b181 100644
--- a/pkg/commands/git_test.go
+++ b/pkg/commands/git_test.go
@@ -372,6 +372,7 @@ func TestGitCommandGetStatusFiles(t *testing.T) {
HasMergeConflicts: false,
DisplayString: "MM file1.txt",
Type: "other",
+ ShortStatus: "MM",
},
{
Name: "file3.txt",
@@ -382,6 +383,7 @@ func TestGitCommandGetStatusFiles(t *testing.T) {
HasMergeConflicts: false,
DisplayString: "A file3.txt",
Type: "other",
+ ShortStatus: "A ",
},
{
Name: "file2.txt",
@@ -392,6 +394,7 @@ func TestGitCommandGetStatusFiles(t *testing.T) {
HasMergeConflicts: false,
DisplayString: "AM file2.txt",
Type: "other",
+ ShortStatus: "AM",
},
{
Name: "file4.txt",
@@ -402,6 +405,7 @@ func TestGitCommandGetStatusFiles(t *testing.T) {
HasMergeConflicts: false,
DisplayString: "?? file4.txt",
Type: "other",
+ ShortStatus: "??",
},
}
diff --git a/pkg/commands/os.go b/pkg/commands/os.go
index 02d79a339..32d22ea5a 100644
--- a/pkg/commands/os.go
+++ b/pkg/commands/os.go
@@ -7,6 +7,7 @@ import (
"path/filepath"
"regexp"
"strings"
+ "sync"
"github.com/go-errors/errors"
@@ -301,3 +302,63 @@ func (c *OSCommand) GetLazygitPath() string {
func (c *OSCommand) RunCustomCommand(command string) *exec.Cmd {
return c.PrepareSubProcess(c.Platform.shell, c.Platform.shellArg, command)
}
+
+// PipeCommands runs a heap of commands and pipes their inputs/outputs together like A | B | C
+func (c *OSCommand) PipeCommands(commandStrings ...string) error {
+
+ cmds := make([]*exec.Cmd, len(commandStrings))
+
+ for i, str := range commandStrings {
+ cmds[i] = c.ExecutableFromString(str)
+ }
+
+ for i := 0; i < len(cmds)-1; i++ {
+ stdout, err := cmds[i].StdoutPipe()
+ if err != nil {
+ return err
+ }
+
+ cmds[i+1].Stdin = stdout
+ }
+
+ // keeping this here in case I adapt this code for some other purpose in the future
+ // cmds[len(cmds)-1].Stdout = os.Stdout
+
+ finalErrors := []string{}
+
+ wg := sync.WaitGroup{}
+ wg.Add(len(cmds))
+
+ for _, cmd := range cmds {
+ currentCmd := cmd
+ go func() {
+ stderr, err := currentCmd.StderrPipe()
+ if err != nil {
+ c.Log.Error(err)
+ }
+
+ if err := currentCmd.Start(); err != nil {
+ c.Log.Error(err)
+ }
+
+ if b, err := ioutil.ReadAll(stderr); err == nil {
+ if len(b) > 0 {
+ finalErrors = append(finalErrors, string(b))
+ }
+ }
+
+ if err := currentCmd.Wait(); err != nil {
+ c.Log.Error(err)
+ }
+
+ wg.Done()
+ }()
+ }
+
+ wg.Wait()
+
+ if len(finalErrors) > 0 {
+ return errors.New(strings.Join(finalErrors, "\n"))
+ }
+ return nil
+}
diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go
index 3d2001ac1..043c6ceab 100644
--- a/pkg/gui/files_panel.go
+++ b/pkg/gui/files_panel.go
@@ -627,3 +627,46 @@ func (gui *Gui) handleCustomCommand(g *gocui.Gui, v *gocui.View) error {
return gui.Errors.ErrSubProcess
})
}
+
+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{
+ {
+ description: gui.Tr.SLocalize("stashAllChanges"),
+ handler: func() error {
+ return gui.handleStashSave(gui.GitCommand.StashSave)
+ },
+ },
+ {
+ description: gui.Tr.SLocalize("stashStagedChanges"),
+ handler: 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)
+}
+
+func (gui *Gui) handleStashChanges(g *gocui.Gui, v *gocui.View) error {
+ return gui.handleStashSave(gui.GitCommand.StashSave)
+}
diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go
index 5cbd7b6eb..ce12cf66f 100644
--- a/pkg/gui/keybindings.go
+++ b/pkg/gui/keybindings.go
@@ -230,10 +230,16 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Description: gui.Tr.SLocalize("refreshFiles"),
}, {
ViewName: "files",
+ Key: 's',
+ Modifier: gocui.ModNone,
+ Handler: gui.handleStashChanges,
+ Description: gui.Tr.SLocalize("stashAllChanges"),
+ }, {
+ ViewName: "files",
Key: 'S',
Modifier: gocui.ModNone,
- Handler: gui.handleStashSave,
- Description: gui.Tr.SLocalize("stashFiles"),
+ Handler: gui.handleCreateStashMenu,
+ Description: gui.Tr.SLocalize("viewStashOptions"),
}, {
ViewName: "files",
Key: 'a',
diff --git a/pkg/gui/menu_panel.go b/pkg/gui/menu_panel.go
index 66dabd03e..370c7fa5a 100644
--- a/pkg/gui/menu_panel.go
+++ b/pkg/gui/menu_panel.go
@@ -70,7 +70,7 @@ 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 {
- return err
+ return gui.createErrorPanel(gui.g, err.Error())
}
if _, err := gui.g.View("menu"); err == nil {
if _, err := gui.g.SetViewOnBottom("menu"); err != nil {
diff --git a/pkg/gui/stash_panel.go b/pkg/gui/stash_panel.go
index 9c1851971..4a2b463ce 100644
--- a/pkg/gui/stash_panel.go
+++ b/pkg/gui/stash_panel.go
@@ -130,16 +130,15 @@ func (gui *Gui) stashDo(g *gocui.Gui, v *gocui.View, method string) error {
return gui.refreshFiles()
}
-func (gui *Gui) handleStashSave(g *gocui.Gui, filesView *gocui.View) error {
+func (gui *Gui) handleStashSave(stashFunc func(message string) error) error {
if len(gui.trackedFiles()) == 0 && len(gui.stagedFiles()) == 0 {
- return gui.createErrorPanel(g, gui.Tr.SLocalize("NoTrackedStagedFilesStash"))
+ return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("NoTrackedStagedFilesStash"))
}
- gui.createPromptPanel(g, filesView, gui.Tr.SLocalize("StashChanges"), func(g *gocui.Gui, v *gocui.View) error {
- if err := gui.GitCommand.StashSave(gui.trimmedContent(v)); err != nil {
+ return gui.createPromptPanel(gui.g, gui.getFilesView(), gui.Tr.SLocalize("StashChanges"), func(g *gocui.Gui, v *gocui.View) error {
+ if err := stashFunc(gui.trimmedContent(v)); err != nil {
gui.createErrorPanel(g, err.Error())
}
gui.refreshStashEntries(g)
return gui.refreshFiles()
})
- return nil
}
diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go
index 6774928fd..d736e2771 100644
--- a/pkg/i18n/dutch.go
+++ b/pkg/i18n/dutch.go
@@ -86,9 +86,6 @@ func addDutch(i18nObject *i18n.Bundle) error {
ID: "execute",
Other: "uitvoeren",
}, &i18n.Message{
- ID: "stashFiles",
- Other: "stash-bestanden",
- }, &i18n.Message{
ID: "open",
Other: "open",
}, &i18n.Message{
@@ -739,6 +736,18 @@ func addDutch(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "pressEnterToReturn",
Other: "Press enter to return to lazygit",
+ }, &i18n.Message{
+ ID: "viewStashOptions",
+ Other: "view stash options",
+ }, &i18n.Message{
+ ID: "stashAllChanges",
+ Other: "stash-bestanden",
+ }, &i18n.Message{
+ ID: "stashStagedChanges",
+ Other: "stash staged changes",
+ }, &i18n.Message{
+ ID: "stashOptions",
+ Other: "Stash options",
},
)
}
diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go
index 36fb3e021..82ba1b505 100644
--- a/pkg/i18n/english.go
+++ b/pkg/i18n/english.go
@@ -106,9 +106,6 @@ func addEnglish(i18nObject *i18n.Bundle) error {
ID: "execute",
Other: "execute",
}, &i18n.Message{
- ID: "stashFiles",
- Other: "stash files",
- }, &i18n.Message{
ID: "open",
Other: "open",
}, &i18n.Message{
@@ -762,6 +759,18 @@ func addEnglish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "pressEnterToReturn",
Other: "Press enter to return to lazygit",
+ }, &i18n.Message{
+ ID: "viewStashOptions",
+ Other: "view stash options",
+ }, &i18n.Message{
+ ID: "stashAllChanges",
+ Other: "stash changes",
+ }, &i18n.Message{
+ ID: "stashStagedChanges",
+ Other: "stash staged changes",
+ }, &i18n.Message{
+ ID: "stashOptions",
+ Other: "Stash options",
},
)
}
diff --git a/pkg/i18n/polish.go b/pkg/i18n/polish.go
index c165ea2f0..bd5fed9d5 100644
--- a/pkg/i18n/polish.go
+++ b/pkg/i18n/polish.go
@@ -84,9 +84,6 @@ func addPolish(i18nObject *i18n.Bundle) error {
ID: "execute",
Other: "wykonaj",
}, &i18n.Message{
- ID: "stashFiles",
- Other: "przechowaj pliki",
- }, &i18n.Message{
ID: "open",
Other: "otwórz",
}, &i18n.Message{
@@ -722,6 +719,18 @@ func addPolish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "pressEnterToReturn",
Other: "Press enter to return to lazygit",
+ }, &i18n.Message{
+ ID: "viewStashOptions",
+ Other: "view stash options",
+ }, &i18n.Message{
+ ID: "stashAllChanges",
+ Other: "przechowaj pliki",
+ }, &i18n.Message{
+ ID: "stashStagedChanges",
+ Other: "stash staged changes",
+ }, &i18n.Message{
+ ID: "stashOptions",
+ Other: "Stash options",
},
)
}