mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-05-17 22:32:58 +02:00
Merge pull request #1438 from Ryooooooga/feature/rename-files-with-modification
Fix staged renamed file with unstaged in file pane #1408
This commit is contained in:
commit
c878f34ff1
@ -8,8 +8,6 @@ import (
|
|||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const RENAME_SEPARATOR = " -> "
|
|
||||||
|
|
||||||
// GetStatusFiles git status files
|
// GetStatusFiles git status files
|
||||||
type GetStatusFileOptions struct {
|
type GetStatusFileOptions struct {
|
||||||
NoRenames bool
|
NoRenames bool
|
||||||
@ -24,36 +22,29 @@ func (c *GitCommand) GetStatusFiles(opts GetStatusFileOptions) []*models.File {
|
|||||||
}
|
}
|
||||||
untrackedFilesArg := fmt.Sprintf("--untracked-files=%s", untrackedFilesSetting)
|
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 {
|
if err != nil {
|
||||||
c.Log.Error(err)
|
c.Log.Error(err)
|
||||||
}
|
}
|
||||||
files := []*models.File{}
|
files := []*models.File{}
|
||||||
|
|
||||||
for _, statusString := range statusStrings {
|
for _, status := range statuses {
|
||||||
if strings.HasPrefix(statusString, "warning") {
|
if strings.HasPrefix(status.StatusString, "warning") {
|
||||||
c.Log.Warningf("warning when calling git status: %s", statusString)
|
c.Log.Warningf("warning when calling git status: %s", status.StatusString)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
change := statusString[0:2]
|
change := status.Change
|
||||||
stagedChange := change[0:1]
|
stagedChange := change[0:1]
|
||||||
unstagedChange := statusString[1:2]
|
unstagedChange := change[1:2]
|
||||||
name := statusString[3:]
|
|
||||||
untracked := utils.IncludesString([]string{"??", "A ", "AM"}, change)
|
untracked := utils.IncludesString([]string{"??", "A ", "AM"}, change)
|
||||||
hasNoStagedChanges := utils.IncludesString([]string{" ", "U", "?"}, stagedChange)
|
hasNoStagedChanges := utils.IncludesString([]string{" ", "U", "?"}, stagedChange)
|
||||||
hasMergeConflicts := utils.IncludesString([]string{"DD", "AA", "UU", "AU", "UA", "UD", "DU"}, change)
|
hasMergeConflicts := utils.IncludesString([]string{"DD", "AA", "UU", "AU", "UA", "UD", "DU"}, change)
|
||||||
hasInlineMergeConflicts := utils.IncludesString([]string{"UU", "AA"}, 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{
|
file := &models.File{
|
||||||
Name: name,
|
Name: status.Name,
|
||||||
PreviousName: previousName,
|
PreviousName: status.PreviousName,
|
||||||
DisplayString: statusString,
|
DisplayString: status.StatusString,
|
||||||
HasStagedChanges: !hasNoStagedChanges,
|
HasStagedChanges: !hasNoStagedChanges,
|
||||||
HasUnstagedChanges: unstagedChange != " ",
|
HasUnstagedChanges: unstagedChange != " ",
|
||||||
Tracked: !untracked,
|
Tracked: !untracked,
|
||||||
@ -61,7 +52,7 @@ func (c *GitCommand) GetStatusFiles(opts GetStatusFileOptions) []*models.File {
|
|||||||
Added: unstagedChange == "A" || untracked,
|
Added: unstagedChange == "A" || untracked,
|
||||||
HasMergeConflicts: hasMergeConflicts,
|
HasMergeConflicts: hasMergeConflicts,
|
||||||
HasInlineMergeConflicts: hasInlineMergeConflicts,
|
HasInlineMergeConflicts: hasInlineMergeConflicts,
|
||||||
Type: c.OSCommand.FileType(name),
|
Type: c.OSCommand.FileType(status.Name),
|
||||||
ShortStatus: change,
|
ShortStatus: change,
|
||||||
}
|
}
|
||||||
files = append(files, file)
|
files = append(files, file)
|
||||||
@ -70,13 +61,20 @@ func (c *GitCommand) GetStatusFiles(opts GetStatusFileOptions) []*models.File {
|
|||||||
return files
|
return files
|
||||||
}
|
}
|
||||||
|
|
||||||
// GitStatus returns the plaintext short status of the repo
|
// GitStatus returns the file status of the repo
|
||||||
type GitStatusOptions struct {
|
type GitStatusOptions struct {
|
||||||
NoRenames bool
|
NoRenames bool
|
||||||
UntrackedFilesArg string
|
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 := ""
|
noRenamesFlag := ""
|
||||||
if opts.NoRenames {
|
if opts.NoRenames {
|
||||||
noRenamesFlag = "--no-renames"
|
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)
|
statusLines, err := c.RunCommandWithOutput("git status %s --porcelain -z %s", opts.UntrackedFilesArg, noRenamesFlag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []string{}, err
|
return []FileStatus{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
splitLines := strings.Split(statusLines, "\x00")
|
splitLines := strings.Split(statusLines, "\x00")
|
||||||
response := []string{}
|
response := []FileStatus{}
|
||||||
|
|
||||||
for i := 0; i < len(splitLines); i++ {
|
for i := 0; i < len(splitLines); i++ {
|
||||||
original := splitLines[i]
|
original := splitLines[i]
|
||||||
if len(original) < 2 {
|
|
||||||
|
if len(original) < 3 {
|
||||||
continue
|
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.
|
// if a line starts with 'R' then the next line is the original file.
|
||||||
next := strings.TrimSpace(splitLines[i+1])
|
status.PreviousName = strings.TrimSpace(splitLines[i+1])
|
||||||
original = "R " + next + RENAME_SEPARATOR + strings.TrimPrefix(original, "R ")
|
status.StatusString = fmt.Sprintf("%s %s -> %s", status.Change, status.PreviousName, status.Name)
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
response = append(response, original)
|
|
||||||
|
response = append(response, status)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response, nil
|
return response, nil
|
||||||
|
@ -136,6 +136,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)
|
assert.EqualValues(t, expected, files)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -29,8 +29,6 @@ type IFile interface {
|
|||||||
GetPath() string
|
GetPath() string
|
||||||
}
|
}
|
||||||
|
|
||||||
const RENAME_SEPARATOR = " -> "
|
|
||||||
|
|
||||||
func (f *File) IsRename() bool {
|
func (f *File) IsRename() bool {
|
||||||
return f.PreviousName != ""
|
return f.PreviousName != ""
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user