diff options
-rw-r--r-- | pkg/commands/files.go | 4 | ||||
-rw-r--r-- | pkg/commands/files_test.go | 6 | ||||
-rw-r--r-- | pkg/commands/loading_files.go | 65 | ||||
-rw-r--r-- | pkg/commands/loading_files_test.go | 75 | ||||
-rw-r--r-- | pkg/commands/models/file.go | 2 | ||||
-rw-r--r-- | pkg/commands/submodules.go | 18 |
6 files changed, 126 insertions, 44 deletions
diff --git a/pkg/commands/files.go b/pkg/commands/files.go index 51523ea5f..9fa5fb1bd 100644 --- a/pkg/commands/files.go +++ b/pkg/commands/files.go @@ -250,12 +250,12 @@ func (c *GitCommand) ShowFileDiffCmdStr(from string, to string, reverse bool, fi reverseFlag = " -R " } - return fmt.Sprintf(`git diff --submodule --no-ext-diff --no-renames --color=%s %s %s %s -- "%s"`, colorArg, from, to, reverseFlag, fileName) + return fmt.Sprintf("git diff --submodule --no-ext-diff --no-renames --color=%s %s %s %s -- %s", colorArg, from, to, reverseFlag, c.OSCommand.Quote(fileName)) } // CheckoutFile checks out the file for the given commit func (c *GitCommand) CheckoutFile(commitSha, fileName string) error { - return c.RunCommand("git checkout %s %s", commitSha, fileName) + return c.RunCommand("git checkout %s -- %s", commitSha, c.OSCommand.Quote(fileName)) } // DiscardOldFileChanges discards changes to a file from an old commit diff --git a/pkg/commands/files_test.go b/pkg/commands/files_test.go index fd4b9f69f..01c1a6793 100644 --- a/pkg/commands/files_test.go +++ b/pkg/commands/files_test.go @@ -455,7 +455,7 @@ func TestGitCommandCheckoutFile(t *testing.T) { "test999.txt", test.CreateMockCommand(t, []*test.CommandSwapper{ { - Expect: "git checkout 11af912 test999.txt", + Expect: "git checkout 11af912 -- test999.txt", Replace: "echo", }, }), @@ -469,7 +469,7 @@ func TestGitCommandCheckoutFile(t *testing.T) { "test999.txt", test.CreateMockCommand(t, []*test.CommandSwapper{ { - Expect: "git checkout 11af912 test999.txt", + Expect: "git checkout 11af912 -- test999.txt", Replace: "test", }, }), @@ -606,7 +606,7 @@ func TestGitCommandDiscardOldFileChanges(t *testing.T) { Replace: "echo", }, { - Expect: "git checkout HEAD^ test999.txt", + Expect: "git checkout HEAD^ -- test999.txt", Replace: "echo", }, { diff --git a/pkg/commands/loading_files.go b/pkg/commands/loading_files.go index 2b9bfc2be..d3638a724 100644 --- a/pkg/commands/loading_files.go +++ b/pkg/commands/loading_files.go @@ -8,8 +8,6 @@ import ( "github.com/jesseduffield/lazygit/pkg/utils" ) -const RENAME_SEPARATOR = " -> " - // GetStatusFiles git status files type GetStatusFileOptions struct { NoRenames bool @@ -24,36 +22,29 @@ func (c *GitCommand) GetStatusFiles(opts GetStatusFileOptions) []*models.File { } untrackedFilesArg := fmt.Sprintf("--untracked-files=%s", untrackedFilesSetting) - statusStrings, err := c.GitStatus(GitStatusOptions{NoRenames: opts.NoRenames, UntrackedFilesArg: untrackedFilesArg}) + statuses, err := c.GitStatus(GitStatusOptions{NoRenames: opts.NoRenames, UntrackedFilesArg: untrackedFilesArg}) if err != nil { c.Log.Error(err) } files := []*models.File{} - for _, statusString := range statusStrings { - if strings.HasPrefix(statusString, "warning") { - c.Log.Warningf("warning when calling git status: %s", statusString) + for _, status := range statuses { + if strings.HasPrefix(status.StatusString, "warning") { + c.Log.Warningf("warning when calling git status: %s", status.StatusString) continue } - change := statusString[0:2] + change := status.Change stagedChange := change[0:1] - unstagedChange := statusString[1:2] - name := statusString[3:] + unstagedChange := change[1:2] untracked := utils.IncludesString([]string{"??", "A ", "AM"}, change) hasNoStagedChanges := utils.IncludesString([]string{" ", "U", "?"}, stagedChange) hasMergeConflicts := utils.IncludesString([]string{"DD", "AA", "UU", "AU", "UA", "UD", "DU"}, change) hasInlineMergeConflicts := utils.IncludesString([]string{"UU", "AA"}, change) - previousName := "" - if strings.Contains(name, RENAME_SEPARATOR) { - split := strings.Split(name, RENAME_SEPARATOR) - name = split[1] - previousName = split[0] - } file := &models.File{ - Name: name, - PreviousName: previousName, - DisplayString: statusString, + Name: status.Name, + PreviousName: status.PreviousName, + DisplayString: status.StatusString, HasStagedChanges: !hasNoStagedChanges, HasUnstagedChanges: unstagedChange != " ", Tracked: !untracked, @@ -61,7 +52,7 @@ func (c *GitCommand) GetStatusFiles(opts GetStatusFileOptions) []*models.File { Added: unstagedChange == "A" || untracked, HasMergeConflicts: hasMergeConflicts, HasInlineMergeConflicts: hasInlineMergeConflicts, - Type: c.OSCommand.FileType(name), + Type: c.OSCommand.FileType(status.Name), ShortStatus: change, } files = append(files, file) @@ -70,13 +61,20 @@ func (c *GitCommand) GetStatusFiles(opts GetStatusFileOptions) []*models.File { return files } -// GitStatus returns the plaintext short status of the repo +// GitStatus returns the file status of the repo type GitStatusOptions struct { NoRenames bool UntrackedFilesArg string } -func (c *GitCommand) GitStatus(opts GitStatusOptions) ([]string, error) { +type FileStatus struct { + StatusString string + Change string // ??, MM, AM, ... + Name string + PreviousName string +} + +func (c *GitCommand) GitStatus(opts GitStatusOptions) ([]FileStatus, error) { noRenamesFlag := "" if opts.NoRenames { noRenamesFlag = "--no-renames" @@ -84,23 +82,34 @@ func (c *GitCommand) GitStatus(opts GitStatusOptions) ([]string, error) { statusLines, err := c.RunCommandWithOutput("git status %s --porcelain -z %s", opts.UntrackedFilesArg, noRenamesFlag) if err != nil { - return []string{}, err + return []FileStatus{}, err } splitLines := strings.Split(statusLines, "\x00") - response := []string{} + response := []FileStatus{} for i := 0; i < len(splitLines); i++ { original := splitLines[i] - if len(original) < 2 { + + if len(original) < 3 { continue - } else if strings.HasPrefix(original, "R ") { + } + + status := FileStatus{ + StatusString: original, + Change: original[:2], + Name: original[3:], + PreviousName: "", + } + + if strings.HasPrefix(status.Change, "R") { // if a line starts with 'R' then the next line is the original file. - next := strings.TrimSpace(splitLines[i+1]) - original = "R " + next + RENAME_SEPARATOR + strings.TrimPrefix(original, "R ") + status.PreviousName = strings.TrimSpace(splitLines[i+1]) + status.StatusString = fmt.Sprintf("%s %s -> %s", status.Change, status.PreviousName, status.Name) i++ } - response = append(response, original) + + response = append(response, status) } return response, nil diff --git a/pkg/commands/loading_files_test.go b/pkg/commands/loading_files_test.go index eefb8f924..456e4aca7 100644 --- a/pkg/commands/loading_files_test.go +++ b/pkg/commands/loading_files_test.go @@ -139,6 +139,81 @@ func TestGitCommandGetStatusFiles(t *testing.T) { assert.EqualValues(t, expected, files) }, }, + { + "Renamed files", + func(cmd string, args ...string) *exec.Cmd { + return secureexec.Command( + "printf", + `R after1.txt\0before1.txt\0RM after2.txt\0before2.txt`, + ) + }, + func(files []*models.File) { + assert.Len(t, files, 2) + + expected := []*models.File{ + { + Name: "after1.txt", + PreviousName: "before1.txt", + HasStagedChanges: true, + HasUnstagedChanges: false, + Tracked: true, + Added: false, + Deleted: false, + HasMergeConflicts: false, + HasInlineMergeConflicts: false, + DisplayString: "R before1.txt -> after1.txt", + Type: "other", + ShortStatus: "R ", + }, + { + Name: "after2.txt", + PreviousName: "before2.txt", + HasStagedChanges: true, + HasUnstagedChanges: true, + Tracked: true, + Added: false, + Deleted: false, + HasMergeConflicts: false, + HasInlineMergeConflicts: false, + DisplayString: "RM before2.txt -> after2.txt", + Type: "other", + ShortStatus: "RM", + }, + } + + assert.EqualValues(t, expected, files) + }, + }, + { + "File with arrow in name", + func(cmd string, args ...string) *exec.Cmd { + return secureexec.Command( + "printf", + `?? a -> b.txt`, + ) + }, + func(files []*models.File) { + assert.Len(t, files, 1) + + expected := []*models.File{ + { + Name: "a -> b.txt", + HasStagedChanges: false, + HasUnstagedChanges: true, + Tracked: false, + Added: true, + Deleted: false, + HasMergeConflicts: false, + HasInlineMergeConflicts: false, + DisplayString: "?? a -> b.txt", + Type: "other", + ShortStatus: "??", + }, + } + + assert.EqualValues(t, expected, files) + }, + }, } for _, s := range scenarios { diff --git a/pkg/commands/models/file.go b/pkg/commands/models/file.go index d3aafb48e..c5e76949a 100644 --- a/pkg/commands/models/file.go +++ b/pkg/commands/models/file.go @@ -29,8 +29,6 @@ type IFile interface { GetPath() string } -const RENAME_SEPARATOR = " -> " - func (f *File) IsRename() bool { return f.PreviousName != "" } diff --git a/pkg/commands/submodules.go b/pkg/commands/submodules.go index 856bc4cbe..9a5c49951 100644 --- a/pkg/commands/submodules.go +++ b/pkg/commands/submodules.go @@ -69,11 +69,11 @@ func (c *GitCommand) SubmoduleStash(submodule *models.SubmoduleConfig) error { return nil } - return c.RunCommand("git -C %s stash --include-untracked", submodule.Path) + return c.RunCommand("git -C %s stash --include-untracked", c.OSCommand.Quote(submodule.Path)) } func (c *GitCommand) SubmoduleReset(submodule *models.SubmoduleConfig) error { - return c.RunCommand("git submodule update --init --force %s", submodule.Path) + return c.RunCommand("git submodule update --init --force -- %s", c.OSCommand.Quote(submodule.Path)) } func (c *GitCommand) SubmoduleUpdateAll() error { @@ -84,13 +84,13 @@ func (c *GitCommand) SubmoduleUpdateAll() error { func (c *GitCommand) SubmoduleDelete(submodule *models.SubmoduleConfig) error { // based on https://gist.github.com/myusuf3/7f645819ded92bda6677 - if err := c.RunCommand("git submodule deinit --force %s", submodule.Path); err != nil { + if err := c.RunCommand("git submodule deinit --force -- %s", c.OSCommand.Quote(submodule.Path)); err != nil { if strings.Contains(err.Error(), "did not match any file(s) known to git") { - if err := c.RunCommand("git config --file .gitmodules --remove-section submodule.%s", submodule.Name); err != nil { + if err := c.RunCommand("git config --file .gitmodules --remove-section submodule.%s", c.OSCommand.Quote(submodule.Name)); err != nil { return err } - if err := c.RunCommand("git config --remove-section submodule.%s", submodule.Name); err != nil { + if err := c.RunCommand("git config --remove-section submodule.%s", c.OSCommand.Quote(submodule.Name)); err != nil { return err } @@ -119,11 +119,11 @@ func (c *GitCommand) SubmoduleAdd(name string, path string, url string) error { func (c *GitCommand) SubmoduleUpdateUrl(name string, path string, newUrl string) error { // the set-url command is only for later git versions so we're doing it manually here - if err := c.RunCommand("git config --file .gitmodules submodule.%s.url %s", name, newUrl); err != nil { + if err := c.RunCommand("git config --file .gitmodules submodule.%s.url %s", c.OSCommand.Quote(name), newUrl); err != nil { return err } - if err := c.RunCommand("git submodule sync %s", path); err != nil { + if err := c.RunCommand("git submodule sync -- %s", c.OSCommand.Quote(path)); err != nil { return err } @@ -131,11 +131,11 @@ func (c *GitCommand) SubmoduleUpdateUrl(name string, path string, newUrl string) } func (c *GitCommand) SubmoduleInit(path string) error { - return c.RunCommand("git submodule init %s", path) + return c.RunCommand("git submodule init -- %s", c.OSCommand.Quote(path)) } func (c *GitCommand) SubmoduleUpdate(path string) error { - return c.RunCommand("git submodule update --init %s", path) + return c.RunCommand("git submodule update --init -- %s", c.OSCommand.Quote(path)) } func (c *GitCommand) SubmoduleBulkInitCmdStr() string { |