From 0f0fda16605059ebae73d29f0e4b9b5d1455ce73 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Thu, 30 May 2019 22:45:56 +1000 Subject: allow stashing staged changes reinstate old stash functionality with the 's' keybinding --- pkg/commands/file.go | 1 + pkg/commands/git.go | 42 ++++++++++++++++++++++++++++++++- pkg/commands/git_test.go | 4 ++++ pkg/commands/os.go | 61 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+), 1 deletion(-) (limited to 'pkg/commands') 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 +} -- cgit v1.2.3