Compare commits

...

9 Commits

Author SHA1 Message Date
Stefan Haller
b5db34b119 WIP Start adapting tests 2025-03-01 16:07:43 +01:00
Stefan Haller
2ee500c63e Better way of skipping root item when there's only one file
Ommitting the "./" prefix from the paths in that case is a bad idea; this breaks
keeping the same item selected when switching from a state that has a root item
to one that doesn't.
2025-03-01 16:07:43 +01:00
Stefan Haller
61cc7852f9 WIP Try to fix some problems, e.g. switch between tree and flat view 2025-03-01 14:53:50 +01:00
Stefan Haller
4b9298f17a Only add root item if more than one file is changed 2025-03-01 10:39:09 +01:00
Stefan Haller
5c2e7a7571 Add a <root> item if files at top level have changed 2025-03-01 07:49:06 +01:00
Stefan Haller
7579a80dce Make Node.path private 2025-03-01 07:42:38 +01:00
Stefan Haller
475f14e37d Use GetPath accessor 2025-03-01 07:42:38 +01:00
Stefan Haller
55fe6ade88 fixup! Rename Name to Path in File and CommitFile 2025-03-01 07:41:17 +01:00
Stefan Haller
38354622b4 Rename Name to Path in File and CommitFile
Name was very confusing and misleading.
2025-02-28 20:59:09 +01:00
26 changed files with 368 additions and 329 deletions

View File

@@ -55,7 +55,7 @@ func getCommitFilesFromFilenames(filenames string) []*models.CommitFile {
return lo.Map(lo.Chunk(lines, 2), func(chunk []string, _ int) *models.CommitFile {
return &models.CommitFile{
ChangeStatus: chunk[0],
Name: chunk[1],
Path: chunk[1],
}
})
}

View File

@@ -23,7 +23,7 @@ func TestGetCommitFilesFromFilenames(t *testing.T) {
input: "MM\x00Myfile\x00",
output: []*models.CommitFile{
{
Name: "Myfile",
Path: "Myfile",
ChangeStatus: "MM",
},
},
@@ -33,11 +33,11 @@ func TestGetCommitFilesFromFilenames(t *testing.T) {
input: "MM\x00Myfile\x00M \x00MyOtherFile\x00",
output: []*models.CommitFile{
{
Name: "Myfile",
Path: "Myfile",
ChangeStatus: "MM",
},
{
Name: "MyOtherFile",
Path: "MyOtherFile",
ChangeStatus: "M ",
},
},
@@ -47,15 +47,15 @@ func TestGetCommitFilesFromFilenames(t *testing.T) {
input: "MM\x00Myfile\x00M \x00MyOtherFile\x00 M\x00YetAnother\x00",
output: []*models.CommitFile{
{
Name: "Myfile",
Path: "Myfile",
ChangeStatus: "MM",
},
{
Name: "MyOtherFile",
Path: "MyOtherFile",
ChangeStatus: "M ",
},
{
Name: "YetAnother",
Path: "YetAnother",
ChangeStatus: " M",
},
},

View File

@@ -68,12 +68,12 @@ func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File
}
file := &models.File{
Name: status.Name,
PreviousName: status.PreviousName,
Path: status.Path,
PreviousPath: status.PreviousPath,
DisplayString: status.StatusString,
}
if diff, ok := fileDiffs[status.Name]; ok {
if diff, ok := fileDiffs[status.Path]; ok {
file.LinesAdded = diff.LinesAdded
file.LinesDeleted = diff.LinesDeleted
}
@@ -87,7 +87,7 @@ func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File
worktreePaths := linkedWortkreePaths(self.Fs, self.repoPaths.RepoGitDirPath())
for _, file := range files {
for _, worktreePath := range worktreePaths {
absFilePath, err := filepath.Abs(file.Name)
absFilePath, err := filepath.Abs(file.Path)
if err != nil {
self.Log.Error(err)
continue
@@ -96,7 +96,7 @@ func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File
file.IsWorktree = true
// `git status` renders this worktree as a folder with a trailing slash but we'll represent it as a singular worktree
// If we include the slash, it will be rendered as a folder with a null file inside.
file.Name = strings.TrimSuffix(file.Name, "/")
file.Path = strings.TrimSuffix(file.Path, "/")
break
}
}
@@ -153,8 +153,8 @@ type GitStatusOptions struct {
type FileStatus struct {
StatusString string
Change string // ??, MM, AM, ...
Name string
PreviousName string
Path string
PreviousPath string
}
func (fileLoader *FileLoader) gitDiffNumStat() (string, error) {
@@ -197,14 +197,14 @@ func (self *FileLoader) gitStatus(opts GitStatusOptions) ([]FileStatus, error) {
status := FileStatus{
StatusString: original,
Change: original[:2],
Name: original[3:],
PreviousName: "",
Path: original[3:],
PreviousPath: "",
}
if strings.HasPrefix(status.Change, "R") {
// if a line starts with 'R' then the next line is the original file.
status.PreviousName = splitLines[i+1]
status.StatusString = fmt.Sprintf("%s %s -> %s", status.Change, status.PreviousName, status.Name)
status.PreviousPath = splitLines[i+1]
status.StatusString = fmt.Sprintf("%s %s -> %s", status.Change, status.PreviousPath, status.Path)
i++
}

View File

@@ -41,7 +41,7 @@ func TestFileGetStatusFiles(t *testing.T) {
showNumstatInFilesView: true,
expectedFiles: []*models.File{
{
Name: "file1.txt",
Path: "file1.txt",
HasStagedChanges: true,
HasUnstagedChanges: true,
Tracked: true,
@@ -55,7 +55,7 @@ func TestFileGetStatusFiles(t *testing.T) {
LinesDeleted: 1,
},
{
Name: "file3.txt",
Path: "file3.txt",
HasStagedChanges: true,
HasUnstagedChanges: false,
Tracked: false,
@@ -69,7 +69,7 @@ func TestFileGetStatusFiles(t *testing.T) {
LinesDeleted: 2,
},
{
Name: "file2.txt",
Path: "file2.txt",
HasStagedChanges: true,
HasUnstagedChanges: true,
Tracked: false,
@@ -83,7 +83,7 @@ func TestFileGetStatusFiles(t *testing.T) {
LinesDeleted: 0,
},
{
Name: "file4.txt",
Path: "file4.txt",
HasStagedChanges: false,
HasUnstagedChanges: true,
Tracked: false,
@@ -97,7 +97,7 @@ func TestFileGetStatusFiles(t *testing.T) {
LinesDeleted: 2,
},
{
Name: "file5.txt",
Path: "file5.txt",
HasStagedChanges: false,
HasUnstagedChanges: true,
Tracked: true,
@@ -119,7 +119,7 @@ func TestFileGetStatusFiles(t *testing.T) {
ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"}, "MM a\nb.txt", nil),
expectedFiles: []*models.File{
{
Name: "a\nb.txt",
Path: "a\nb.txt",
HasStagedChanges: true,
HasUnstagedChanges: true,
Tracked: true,
@@ -142,8 +142,8 @@ func TestFileGetStatusFiles(t *testing.T) {
),
expectedFiles: []*models.File{
{
Name: "after1.txt",
PreviousName: "before1.txt",
Path: "after1.txt",
PreviousPath: "before1.txt",
HasStagedChanges: true,
HasUnstagedChanges: false,
Tracked: true,
@@ -155,8 +155,8 @@ func TestFileGetStatusFiles(t *testing.T) {
ShortStatus: "R ",
},
{
Name: "after2.txt",
PreviousName: "before2.txt",
Path: "after2.txt",
PreviousPath: "before2.txt",
HasStagedChanges: true,
HasUnstagedChanges: true,
Tracked: true,
@@ -179,7 +179,7 @@ func TestFileGetStatusFiles(t *testing.T) {
),
expectedFiles: []*models.File{
{
Name: "a -> b.txt",
Path: "a -> b.txt",
HasStagedChanges: false,
HasUnstagedChanges: true,
Tracked: false,

View File

@@ -88,11 +88,11 @@ func (self *WorkingTreeCommands) BeforeAndAfterFileForRename(file *models.File)
var beforeFile *models.File
var afterFile *models.File
for _, f := range filesWithoutRenames {
if f.Name == file.PreviousName {
if f.Path == file.PreviousPath {
beforeFile = f
}
if f.Name == file.Name {
if f.Path == file.Path {
afterFile = f
}
}
@@ -134,13 +134,13 @@ func (self *WorkingTreeCommands) DiscardAllFileChanges(file *models.File) error
if file.ShortStatus == "AA" {
if err := self.cmd.New(
newCheckoutCommand().Arg("--ours", "--", file.Name).ToArgv(),
newCheckoutCommand().Arg("--ours", "--", file.Path).ToArgv(),
).Run(); err != nil {
return err
}
if err := self.cmd.New(
NewGitCmd("add").Arg("--", file.Name).ToArgv(),
NewGitCmd("add").Arg("--", file.Path).ToArgv(),
).Run(); err != nil {
return err
}
@@ -149,14 +149,14 @@ func (self *WorkingTreeCommands) DiscardAllFileChanges(file *models.File) error
if file.ShortStatus == "DU" {
return self.cmd.New(
NewGitCmd("rm").Arg("--", file.Name).ToArgv(),
NewGitCmd("rm").Arg("--", file.Path).ToArgv(),
).Run()
}
// if the file isn't tracked, we assume you want to delete it
if file.HasStagedChanges || file.HasMergeConflicts {
if err := self.cmd.New(
NewGitCmd("reset").Arg("--", file.Name).ToArgv(),
NewGitCmd("reset").Arg("--", file.Path).ToArgv(),
).Run(); err != nil {
return err
}
@@ -167,7 +167,7 @@ func (self *WorkingTreeCommands) DiscardAllFileChanges(file *models.File) error
}
if file.Added {
return self.os.RemoveFile(file.Name)
return self.os.RemoveFile(file.Path)
}
return self.DiscardUnstagedFileChanges(file)
@@ -199,7 +199,7 @@ func (self *WorkingTreeCommands) DiscardUnstagedDirChanges(node IFileNode) error
}
} else {
if file.Added && !file.HasStagedChanges {
return self.os.RemoveFile(file.Name)
return self.os.RemoveFile(file.Path)
}
if err := self.DiscardUnstagedFileChanges(file); err != nil {
@@ -226,7 +226,7 @@ func (self *WorkingTreeCommands) RemoveUntrackedDirFiles(node IFileNode) error {
}
func (self *WorkingTreeCommands) DiscardUnstagedFileChanges(file *models.File) error {
cmdArgs := newCheckoutCommand().Arg("--", file.Name).ToArgv()
cmdArgs := newCheckoutCommand().Arg("--", file.Path).ToArgv()
return self.cmd.New(cmdArgs).Run()
}

View File

@@ -87,7 +87,7 @@ func TestWorkingTreeDiscardAllFileChanges(t *testing.T) {
{
testName: "An error occurred when resetting",
file: &models.File{
Name: "test",
Path: "test",
HasStagedChanges: true,
},
removeFile: func(string) error { return nil },
@@ -98,7 +98,7 @@ func TestWorkingTreeDiscardAllFileChanges(t *testing.T) {
{
testName: "An error occurred when removing file",
file: &models.File{
Name: "test",
Path: "test",
Tracked: false,
Added: true,
},
@@ -111,7 +111,7 @@ func TestWorkingTreeDiscardAllFileChanges(t *testing.T) {
{
testName: "An error occurred with checkout",
file: &models.File{
Name: "test",
Path: "test",
Tracked: true,
HasStagedChanges: false,
},
@@ -123,7 +123,7 @@ func TestWorkingTreeDiscardAllFileChanges(t *testing.T) {
{
testName: "Checkout only",
file: &models.File{
Name: "test",
Path: "test",
Tracked: true,
HasStagedChanges: false,
},
@@ -135,7 +135,7 @@ func TestWorkingTreeDiscardAllFileChanges(t *testing.T) {
{
testName: "Reset and checkout staged changes",
file: &models.File{
Name: "test",
Path: "test",
Tracked: true,
HasStagedChanges: true,
},
@@ -148,7 +148,7 @@ func TestWorkingTreeDiscardAllFileChanges(t *testing.T) {
{
testName: "Reset and checkout merge conflicts",
file: &models.File{
Name: "test",
Path: "test",
Tracked: true,
HasMergeConflicts: true,
},
@@ -161,7 +161,7 @@ func TestWorkingTreeDiscardAllFileChanges(t *testing.T) {
{
testName: "Reset and remove",
file: &models.File{
Name: "test",
Path: "test",
Tracked: false,
Added: true,
HasStagedChanges: true,
@@ -177,7 +177,7 @@ func TestWorkingTreeDiscardAllFileChanges(t *testing.T) {
{
testName: "Remove only",
file: &models.File{
Name: "test",
Path: "test",
Tracked: false,
Added: true,
HasStagedChanges: false,
@@ -224,7 +224,7 @@ func TestWorkingTreeDiff(t *testing.T) {
{
testName: "Default case",
file: &models.File{
Name: "test.txt",
Path: "test.txt",
HasStagedChanges: false,
Tracked: true,
},
@@ -239,7 +239,7 @@ func TestWorkingTreeDiff(t *testing.T) {
{
testName: "cached",
file: &models.File{
Name: "test.txt",
Path: "test.txt",
HasStagedChanges: false,
Tracked: true,
},
@@ -254,7 +254,7 @@ func TestWorkingTreeDiff(t *testing.T) {
{
testName: "plain",
file: &models.File{
Name: "test.txt",
Path: "test.txt",
HasStagedChanges: false,
Tracked: true,
},
@@ -269,7 +269,7 @@ func TestWorkingTreeDiff(t *testing.T) {
{
testName: "File not tracked and file has no staged changes",
file: &models.File{
Name: "test.txt",
Path: "test.txt",
HasStagedChanges: false,
Tracked: false,
},
@@ -284,7 +284,7 @@ func TestWorkingTreeDiff(t *testing.T) {
{
testName: "Default case (ignore whitespace)",
file: &models.File{
Name: "test.txt",
Path: "test.txt",
HasStagedChanges: false,
Tracked: true,
},
@@ -299,7 +299,7 @@ func TestWorkingTreeDiff(t *testing.T) {
{
testName: "Show diff with custom context size",
file: &models.File{
Name: "test.txt",
Path: "test.txt",
HasStagedChanges: false,
Tracked: true,
},
@@ -314,7 +314,7 @@ func TestWorkingTreeDiff(t *testing.T) {
{
testName: "Show diff with custom similarity threshold",
file: &models.File{
Name: "test.txt",
Path: "test.txt",
HasStagedChanges: false,
Tracked: true,
},
@@ -470,7 +470,7 @@ func TestWorkingTreeDiscardUnstagedFileChanges(t *testing.T) {
scenarios := []scenario{
{
testName: "valid case",
file: &models.File{Name: "test.txt"},
file: &models.File{Path: "test.txt"},
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-c", disableHooksFlag, "checkout", "--", "test.txt"}, "", nil),
test: func(err error) {

View File

@@ -2,18 +2,17 @@ package models
// CommitFile : A git commit file
type CommitFile struct {
// TODO: rename this to Path
Name string
Path string
ChangeStatus string // e.g. 'A' for added or 'M' for modified. This is based on the result from git diff --name-status
}
func (f *CommitFile) ID() string {
return f.Name
return f.Path
}
func (f *CommitFile) Description() string {
return f.Name
return f.Path
}
func (f *CommitFile) Added() bool {
@@ -25,5 +24,5 @@ func (f *CommitFile) Deleted() bool {
}
func (f *CommitFile) GetPath() string {
return f.Name
return f.Path
}

View File

@@ -8,8 +8,8 @@ import (
// File : A file from git status
// duplicating this for now
type File struct {
Name string
PreviousName string
Path string
PreviousPath string
HasStagedChanges bool
HasUnstagedChanges bool
Tracked bool
@@ -37,14 +37,14 @@ type IFile interface {
}
func (f *File) IsRename() bool {
return f.PreviousName != ""
return f.PreviousPath != ""
}
// Names returns an array containing just the filename, or in the case of a rename, the after filename and the before filename
func (f *File) Names() []string {
result := []string{f.Name}
if f.PreviousName != "" {
result = append(result, f.PreviousName)
result := []string{f.Path}
if f.PreviousPath != "" {
result = append(result, f.PreviousPath)
}
return result
}
@@ -55,11 +55,11 @@ func (f *File) Matches(f2 *File) bool {
}
func (f *File) ID() string {
return f.Name
return f.Path
}
func (f *File) Description() string {
return f.Name
return f.Path
}
func (f *File) IsSubmodule(configs []*SubmoduleConfig) bool {
@@ -68,7 +68,7 @@ func (f *File) IsSubmodule(configs []*SubmoduleConfig) bool {
func (f *File) SubmoduleConfig(configs []*SubmoduleConfig) *SubmoduleConfig {
for _, config := range configs {
if f.Name == config.Path {
if f.Path == config.Path {
return config
}
}
@@ -90,11 +90,11 @@ func (f *File) GetIsTracked() bool {
func (f *File) GetPath() string {
// TODO: remove concept of name; just use path
return f.Name
return f.Path
}
func (f *File) GetPreviousPath() string {
return f.PreviousName
return f.PreviousPath
}
func (f *File) GetIsFile() bool {

View File

@@ -221,7 +221,7 @@ func (self *CommitFilesController) openCopyMenu() error {
copyPathItem := &types.MenuItem{
Label: self.c.Tr.CopyFilePath,
OnPress: func() error {
if err := self.c.OS().CopyToClipboard(node.Path); err != nil {
if err := self.c.OS().CopyToClipboard(node.GetPath()); err != nil {
return err
}
self.c.Toast(self.c.Tr.FilePathCopiedToast)
@@ -370,7 +370,7 @@ func (self *CommitFilesController) toggleForPatch(selectedNodes []*filetree.Comm
// Find if any file in the selection is unselected or partially added
adding := lo.SomeBy(selectedNodes, func(node *filetree.CommitFileNode) bool {
return node.SomeFile(func(file *models.CommitFile) bool {
fileStatus := self.c.Git().Patch.PatchBuilder.GetFileStatus(file.Name, self.context().GetRef().RefName())
fileStatus := self.c.Git().Patch.PatchBuilder.GetFileStatus(file.Path, self.context().GetRef().RefName())
return fileStatus == patch.PART || fileStatus == patch.UNSELECTED
})
})
@@ -383,7 +383,7 @@ func (self *CommitFilesController) toggleForPatch(selectedNodes []*filetree.Comm
for _, node := range selectedNodes {
err := node.ForEachFile(func(file *models.CommitFile) error {
return patchOperationFunction(file.Name)
return patchOperationFunction(file.Path)
})
if err != nil {
return err
@@ -477,7 +477,7 @@ func (self *CommitFilesController) enterCommitFile(node *filetree.CommitFileNode
}
func (self *CommitFilesController) handleToggleCommitFileDirCollapsed(node *filetree.CommitFileNode) error {
self.context().CommitFileTreeViewModel.ToggleCollapsed(node.GetPath())
self.context().CommitFileTreeViewModel.ToggleCollapsed(node.GetInternalPath())
self.c.PostRefreshUpdate(self.context())

View File

@@ -372,7 +372,7 @@ func (self *FilesController) optimisticChange(nodes []*filetree.FileNode, optimi
err := node.ForEachFile(func(f *models.File) error {
// can't act on the file itself: we need to update the original model file
for _, modelFile := range self.c.Model().Files {
if modelFile.Name == f.Name {
if modelFile.Path == f.Path {
if optimisticChangeFn(modelFile) {
rerender = true
}
@@ -410,7 +410,7 @@ func (self *FilesController) pressWithLock(selectedNodes []*filetree.FileNode) e
toPaths := func(nodes []*filetree.FileNode) []string {
return lo.Map(nodes, func(node *filetree.FileNode, _ int) string {
return node.Path
return node.GetPath()
})
}
@@ -875,7 +875,7 @@ func (self *FilesController) openDiffTool(node *filetree.FileNode) error {
return self.c.RunSubprocessAndRefresh(
self.c.Git().Diff.OpenDiffToolCmdObj(
git_commands.DiffToolCmdOptions{
Filepath: node.Path,
Filepath: node.GetPath(),
FromCommit: fromCommit,
ToCommit: "",
Reverse: reverse,
@@ -891,7 +891,7 @@ func (self *FilesController) switchToMerge() error {
return nil
}
return self.c.Helpers().MergeConflicts.SwitchToMerge(file.Name)
return self.c.Helpers().MergeConflicts.SwitchToMerge(file.Path)
}
func (self *FilesController) createStashMenu() error {
@@ -973,7 +973,7 @@ func (self *FilesController) openCopyMenu() error {
copyPathItem := &types.MenuItem{
Label: self.c.Tr.CopyFilePath,
OnPress: func() error {
if err := self.c.OS().CopyToClipboard(node.Path); err != nil {
if err := self.c.OS().CopyToClipboard(node.GetPath()); err != nil {
return err
}
self.c.Toast(self.c.Tr.FilePathCopiedToast)
@@ -1072,7 +1072,7 @@ func (self *FilesController) handleToggleDirCollapsed() error {
return nil
}
self.context().FileTreeViewModel.ToggleCollapsed(node.GetPath())
self.context().FileTreeViewModel.ToggleCollapsed(node.GetInternalPath())
self.c.PostRefreshUpdate(self.c.Contexts().Files)

View File

@@ -554,11 +554,11 @@ func (self *RefreshHelper) refreshStateFiles() error {
prevConflictFileCount++
}
if file.HasInlineMergeConflicts {
hasConflicts, err := mergeconflicts.FileHasConflictMarkers(file.Name)
hasConflicts, err := mergeconflicts.FileHasConflictMarkers(file.Path)
if err != nil {
self.c.Log.Error(err)
} else if !hasConflicts {
pathsToStage = append(pathsToStage, file.Name)
pathsToStage = append(pathsToStage, file.Path)
}
}
}

View File

@@ -14,7 +14,7 @@ func BuildTreeFromFiles(files []*models.File) *Node[models.File] {
var curr *Node[models.File]
for _, file := range files {
splitPath := split(file.Name)
splitPath := split("./" + file.Path)
curr = root
outer:
for i := range splitPath {
@@ -40,8 +40,13 @@ func BuildTreeFromFiles(files []*models.File) *Node[models.File] {
continue outer
}
if i == 0 && len(files) == 1 {
// skip the root item when there's only one file; we don't need it in that case
continue outer
}
newChild := &Node[models.File]{
Path: path,
path: path,
File: setFile,
}
curr.Children = append(curr.Children, newChild)
@@ -70,7 +75,7 @@ func BuildTreeFromCommitFiles(files []*models.CommitFile) *Node[models.CommitFil
var curr *Node[models.CommitFile]
for _, file := range files {
splitPath := split(file.Name)
splitPath := split("./" + file.Path)
curr = root
outer:
for i := range splitPath {
@@ -83,14 +88,19 @@ func BuildTreeFromCommitFiles(files []*models.CommitFile) *Node[models.CommitFil
path := join(splitPath[:i+1])
for _, existingChild := range curr.Children {
if existingChild.Path == path {
if existingChild.path == path {
curr = existingChild
continue outer
}
}
if i == 0 && len(files) == 1 {
// skip the root item when there's only one file; we don't need it in that case
continue outer
}
newChild := &Node[models.CommitFile]{
Path: path,
path: path,
File: setFile,
}
curr.Children = append(curr.Children, newChild)

View File

@@ -17,7 +17,7 @@ func TestBuildTreeFromFiles(t *testing.T) {
name: "no files",
files: []*models.File{},
expected: &Node[models.File]{
Path: "",
path: "",
Children: nil,
},
},
@@ -25,25 +25,25 @@ func TestBuildTreeFromFiles(t *testing.T) {
name: "files in same directory",
files: []*models.File{
{
Name: "dir1/a",
Path: "dir1/a",
},
{
Name: "dir1/b",
Path: "dir1/b",
},
},
expected: &Node[models.File]{
Path: "",
path: "",
Children: []*Node[models.File]{
{
Path: "dir1",
path: "dir1",
Children: []*Node[models.File]{
{
File: &models.File{Name: "dir1/a"},
Path: "dir1/a",
File: &models.File{Path: "dir1/a"},
path: "dir1/a",
},
{
File: &models.File{Name: "dir1/b"},
Path: "dir1/b",
File: &models.File{Path: "dir1/b"},
path: "dir1/b",
},
},
},
@@ -54,31 +54,31 @@ func TestBuildTreeFromFiles(t *testing.T) {
name: "paths that can be compressed",
files: []*models.File{
{
Name: "dir1/dir3/a",
Path: "dir1/dir3/a",
},
{
Name: "dir2/dir4/b",
Path: "dir2/dir4/b",
},
},
expected: &Node[models.File]{
Path: "",
path: "",
Children: []*Node[models.File]{
{
Path: "dir1/dir3",
path: "dir1/dir3",
Children: []*Node[models.File]{
{
File: &models.File{Name: "dir1/dir3/a"},
Path: "dir1/dir3/a",
File: &models.File{Path: "dir1/dir3/a"},
path: "dir1/dir3/a",
},
},
CompressionLevel: 1,
},
{
Path: "dir2/dir4",
path: "dir2/dir4",
Children: []*Node[models.File]{
{
File: &models.File{Name: "dir2/dir4/b"},
Path: "dir2/dir4/b",
File: &models.File{Path: "dir2/dir4/b"},
path: "dir2/dir4/b",
},
},
CompressionLevel: 1,
@@ -90,22 +90,22 @@ func TestBuildTreeFromFiles(t *testing.T) {
name: "paths that can be sorted",
files: []*models.File{
{
Name: "b",
Path: "b",
},
{
Name: "a",
Path: "a",
},
},
expected: &Node[models.File]{
Path: "",
path: "",
Children: []*Node[models.File]{
{
File: &models.File{Name: "a"},
Path: "a",
File: &models.File{Path: "a"},
path: "a",
},
{
File: &models.File{Name: "b"},
Path: "b",
File: &models.File{Path: "b"},
path: "b",
},
},
},
@@ -114,32 +114,32 @@ func TestBuildTreeFromFiles(t *testing.T) {
name: "paths that can be sorted including a merge conflict file",
files: []*models.File{
{
Name: "b",
Path: "b",
},
{
Name: "z",
Path: "z",
HasMergeConflicts: true,
},
{
Name: "a",
Path: "a",
},
},
expected: &Node[models.File]{
Path: "",
path: "",
// it is a little strange that we're not bubbling up our merge conflict
// here but we are technically still in tree mode and that's the rule
Children: []*Node[models.File]{
{
File: &models.File{Name: "a"},
Path: "a",
File: &models.File{Path: "a"},
path: "a",
},
{
File: &models.File{Name: "b"},
Path: "b",
File: &models.File{Path: "b"},
path: "b",
},
{
File: &models.File{Name: "z", HasMergeConflicts: true},
Path: "z",
File: &models.File{Path: "z", HasMergeConflicts: true},
path: "z",
},
},
},
@@ -164,7 +164,7 @@ func TestBuildFlatTreeFromFiles(t *testing.T) {
name: "no files",
files: []*models.File{},
expected: &Node[models.File]{
Path: "",
path: "",
Children: []*Node[models.File]{},
},
},
@@ -172,23 +172,23 @@ func TestBuildFlatTreeFromFiles(t *testing.T) {
name: "files in same directory",
files: []*models.File{
{
Name: "dir1/a",
Path: "dir1/a",
},
{
Name: "dir1/b",
Path: "dir1/b",
},
},
expected: &Node[models.File]{
Path: "",
path: "",
Children: []*Node[models.File]{
{
File: &models.File{Name: "dir1/a"},
Path: "dir1/a",
File: &models.File{Path: "dir1/a"},
path: "dir1/a",
CompressionLevel: 0,
},
{
File: &models.File{Name: "dir1/b"},
Path: "dir1/b",
File: &models.File{Path: "dir1/b"},
path: "dir1/b",
CompressionLevel: 0,
},
},
@@ -198,23 +198,23 @@ func TestBuildFlatTreeFromFiles(t *testing.T) {
name: "paths that can be compressed",
files: []*models.File{
{
Name: "dir1/a",
Path: "dir1/a",
},
{
Name: "dir2/b",
Path: "dir2/b",
},
},
expected: &Node[models.File]{
Path: "",
path: "",
Children: []*Node[models.File]{
{
File: &models.File{Name: "dir1/a"},
Path: "dir1/a",
File: &models.File{Path: "dir1/a"},
path: "dir1/a",
CompressionLevel: 0,
},
{
File: &models.File{Name: "dir2/b"},
Path: "dir2/b",
File: &models.File{Path: "dir2/b"},
path: "dir2/b",
CompressionLevel: 0,
},
},
@@ -224,22 +224,22 @@ func TestBuildFlatTreeFromFiles(t *testing.T) {
name: "paths that can be sorted",
files: []*models.File{
{
Name: "b",
Path: "b",
},
{
Name: "a",
Path: "a",
},
},
expected: &Node[models.File]{
Path: "",
path: "",
Children: []*Node[models.File]{
{
File: &models.File{Name: "a"},
Path: "a",
File: &models.File{Path: "a"},
path: "a",
},
{
File: &models.File{Name: "b"},
Path: "b",
File: &models.File{Path: "b"},
path: "b",
},
},
},
@@ -248,56 +248,56 @@ func TestBuildFlatTreeFromFiles(t *testing.T) {
name: "tracked, untracked, and conflicted files",
files: []*models.File{
{
Name: "a2",
Path: "a2",
Tracked: false,
},
{
Name: "a1",
Path: "a1",
Tracked: false,
},
{
Name: "c2",
Path: "c2",
HasMergeConflicts: true,
},
{
Name: "c1",
Path: "c1",
HasMergeConflicts: true,
},
{
Name: "b2",
Path: "b2",
Tracked: true,
},
{
Name: "b1",
Path: "b1",
Tracked: true,
},
},
expected: &Node[models.File]{
Path: "",
path: "",
Children: []*Node[models.File]{
{
File: &models.File{Name: "c1", HasMergeConflicts: true},
Path: "c1",
File: &models.File{Path: "c1", HasMergeConflicts: true},
path: "c1",
},
{
File: &models.File{Name: "c2", HasMergeConflicts: true},
Path: "c2",
File: &models.File{Path: "c2", HasMergeConflicts: true},
path: "c2",
},
{
File: &models.File{Name: "b1", Tracked: true},
Path: "b1",
File: &models.File{Path: "b1", Tracked: true},
path: "b1",
},
{
File: &models.File{Name: "b2", Tracked: true},
Path: "b2",
File: &models.File{Path: "b2", Tracked: true},
path: "b2",
},
{
File: &models.File{Name: "a1", Tracked: false},
Path: "a1",
File: &models.File{Path: "a1", Tracked: false},
path: "a1",
},
{
File: &models.File{Name: "a2", Tracked: false},
Path: "a2",
File: &models.File{Path: "a2", Tracked: false},
path: "a2",
},
},
},
@@ -322,7 +322,7 @@ func TestBuildTreeFromCommitFiles(t *testing.T) {
name: "no files",
files: []*models.CommitFile{},
expected: &Node[models.CommitFile]{
Path: "",
path: "",
Children: nil,
},
},
@@ -330,25 +330,25 @@ func TestBuildTreeFromCommitFiles(t *testing.T) {
name: "files in same directory",
files: []*models.CommitFile{
{
Name: "dir1/a",
Path: "dir1/a",
},
{
Name: "dir1/b",
Path: "dir1/b",
},
},
expected: &Node[models.CommitFile]{
Path: "",
path: "",
Children: []*Node[models.CommitFile]{
{
Path: "dir1",
path: "dir1",
Children: []*Node[models.CommitFile]{
{
File: &models.CommitFile{Name: "dir1/a"},
Path: "dir1/a",
File: &models.CommitFile{Path: "dir1/a"},
path: "dir1/a",
},
{
File: &models.CommitFile{Name: "dir1/b"},
Path: "dir1/b",
File: &models.CommitFile{Path: "dir1/b"},
path: "dir1/b",
},
},
},
@@ -359,31 +359,31 @@ func TestBuildTreeFromCommitFiles(t *testing.T) {
name: "paths that can be compressed",
files: []*models.CommitFile{
{
Name: "dir1/dir3/a",
Path: "dir1/dir3/a",
},
{
Name: "dir2/dir4/b",
Path: "dir2/dir4/b",
},
},
expected: &Node[models.CommitFile]{
Path: "",
path: "",
Children: []*Node[models.CommitFile]{
{
Path: "dir1/dir3",
path: "dir1/dir3",
Children: []*Node[models.CommitFile]{
{
File: &models.CommitFile{Name: "dir1/dir3/a"},
Path: "dir1/dir3/a",
File: &models.CommitFile{Path: "dir1/dir3/a"},
path: "dir1/dir3/a",
},
},
CompressionLevel: 1,
},
{
Path: "dir2/dir4",
path: "dir2/dir4",
Children: []*Node[models.CommitFile]{
{
File: &models.CommitFile{Name: "dir2/dir4/b"},
Path: "dir2/dir4/b",
File: &models.CommitFile{Path: "dir2/dir4/b"},
path: "dir2/dir4/b",
},
},
CompressionLevel: 1,
@@ -395,22 +395,22 @@ func TestBuildTreeFromCommitFiles(t *testing.T) {
name: "paths that can be sorted",
files: []*models.CommitFile{
{
Name: "b",
Path: "b",
},
{
Name: "a",
Path: "a",
},
},
expected: &Node[models.CommitFile]{
Path: "",
path: "",
Children: []*Node[models.CommitFile]{
{
File: &models.CommitFile{Name: "a"},
Path: "a",
File: &models.CommitFile{Path: "a"},
path: "a",
},
{
File: &models.CommitFile{Name: "b"},
Path: "b",
File: &models.CommitFile{Path: "b"},
path: "b",
},
},
},
@@ -435,7 +435,7 @@ func TestBuildFlatTreeFromCommitFiles(t *testing.T) {
name: "no files",
files: []*models.CommitFile{},
expected: &Node[models.CommitFile]{
Path: "",
path: "",
Children: []*Node[models.CommitFile]{},
},
},
@@ -443,23 +443,23 @@ func TestBuildFlatTreeFromCommitFiles(t *testing.T) {
name: "files in same directory",
files: []*models.CommitFile{
{
Name: "dir1/a",
Path: "dir1/a",
},
{
Name: "dir1/b",
Path: "dir1/b",
},
},
expected: &Node[models.CommitFile]{
Path: "",
path: "",
Children: []*Node[models.CommitFile]{
{
File: &models.CommitFile{Name: "dir1/a"},
Path: "dir1/a",
File: &models.CommitFile{Path: "dir1/a"},
path: "dir1/a",
CompressionLevel: 0,
},
{
File: &models.CommitFile{Name: "dir1/b"},
Path: "dir1/b",
File: &models.CommitFile{Path: "dir1/b"},
path: "dir1/b",
CompressionLevel: 0,
},
},
@@ -469,23 +469,23 @@ func TestBuildFlatTreeFromCommitFiles(t *testing.T) {
name: "paths that can be compressed",
files: []*models.CommitFile{
{
Name: "dir1/a",
Path: "dir1/a",
},
{
Name: "dir2/b",
Path: "dir2/b",
},
},
expected: &Node[models.CommitFile]{
Path: "",
path: "",
Children: []*Node[models.CommitFile]{
{
File: &models.CommitFile{Name: "dir1/a"},
Path: "dir1/a",
File: &models.CommitFile{Path: "dir1/a"},
path: "dir1/a",
CompressionLevel: 0,
},
{
File: &models.CommitFile{Name: "dir2/b"},
Path: "dir2/b",
File: &models.CommitFile{Path: "dir2/b"},
path: "dir2/b",
CompressionLevel: 0,
},
},
@@ -495,22 +495,22 @@ func TestBuildFlatTreeFromCommitFiles(t *testing.T) {
name: "paths that can be sorted",
files: []*models.CommitFile{
{
Name: "b",
Path: "b",
},
{
Name: "a",
Path: "a",
},
},
expected: &Node[models.CommitFile]{
Path: "",
path: "",
Children: []*Node[models.CommitFile]{
{
File: &models.CommitFile{Name: "a"},
Path: "a",
File: &models.CommitFile{Path: "a"},
path: "a",
},
{
File: &models.CommitFile{Name: "b"},
Path: "b",
File: &models.CommitFile{Path: "b"},
path: "b",
},
},
},

View File

@@ -27,7 +27,7 @@ type CommitFileTree struct {
func (self *CommitFileTree) CollapseAll() {
dirPaths := lo.FilterMap(self.GetAllItems(), func(file *CommitFileNode, index int) (string, bool) {
return file.Path, !file.IsFile()
return file.path, !file.IsFile()
})
for _, path := range dirPaths {
@@ -119,7 +119,7 @@ func (self *CommitFileTree) CollapsedPaths() *CollapsedPaths {
func (self *CommitFileTree) GetFile(path string) *models.CommitFile {
for _, file := range self.getFiles() {
if file.Name == path {
if file.Path == path {
return file
}
}

View File

@@ -148,12 +148,12 @@ func (self *CommitFileTreeViewModel) ToggleShowTree() {
if selectedNode == nil {
return
}
path := selectedNode.Path
path := selectedNode.path
if self.InTreeMode() {
self.ExpandToPath(path)
} else if len(selectedNode.Children) > 0 {
path = selectedNode.GetLeaves()[0].Path
path = selectedNode.GetLeaves()[0].path
}
index, found := self.GetIndexForPath(path)
@@ -170,7 +170,7 @@ func (self *CommitFileTreeViewModel) CollapseAll() {
return
}
topLevelPath := strings.Split(selectedNode.Path, "/")[0]
topLevelPath := strings.Split(selectedNode.path, "/")[0]
index, found := self.GetIndexForPath(topLevelPath)
if found {
self.SetSelectedLineIdx(index)
@@ -186,7 +186,7 @@ func (self *CommitFileTreeViewModel) ExpandAll() {
return
}
index, found := self.GetIndexForPath(selectedNode.Path)
index, found := self.GetIndexForPath(selectedNode.path)
if found {
self.SetSelectedLineIdx(index)
}

View File

@@ -51,7 +51,7 @@ func (self *FileNode) GetHasInlineMergeConflicts() bool {
if !file.HasInlineMergeConflicts {
return false
}
hasConflicts, _ := mergeconflicts.FileHasConflictMarkers(file.Name)
hasConflicts, _ := mergeconflicts.FileHasConflictMarkers(file.Path)
return hasConflicts
})
}
@@ -69,5 +69,5 @@ func (self *FileNode) GetPreviousPath() string {
return ""
}
return self.File.PreviousName
return self.File.PreviousPath
}

View File

@@ -21,103 +21,103 @@ func TestCompress(t *testing.T) {
{
name: "leaf node",
root: &Node[models.File]{
Path: "",
path: "",
Children: []*Node[models.File]{
{File: &models.File{Name: "test", ShortStatus: " M", HasStagedChanges: true}, Path: "test"},
{File: &models.File{Path: "test", ShortStatus: " M", HasStagedChanges: true}, path: "test"},
},
},
expected: &Node[models.File]{
Path: "",
path: "",
Children: []*Node[models.File]{
{File: &models.File{Name: "test", ShortStatus: " M", HasStagedChanges: true}, Path: "test"},
{File: &models.File{Path: "test", ShortStatus: " M", HasStagedChanges: true}, path: "test"},
},
},
},
{
name: "big example",
root: &Node[models.File]{
Path: "",
path: "",
Children: []*Node[models.File]{
{
Path: "dir1",
path: "dir1",
Children: []*Node[models.File]{
{
File: &models.File{Name: "file2", ShortStatus: "M ", HasUnstagedChanges: true},
Path: "dir1/file2",
File: &models.File{Path: "file2", ShortStatus: "M ", HasUnstagedChanges: true},
path: "dir1/file2",
},
},
},
{
Path: "dir2",
path: "dir2",
Children: []*Node[models.File]{
{
File: &models.File{Name: "file3", ShortStatus: " M", HasStagedChanges: true},
Path: "dir2/file3",
File: &models.File{Path: "file3", ShortStatus: " M", HasStagedChanges: true},
path: "dir2/file3",
},
{
File: &models.File{Name: "file4", ShortStatus: "M ", HasUnstagedChanges: true},
Path: "dir2/file4",
File: &models.File{Path: "file4", ShortStatus: "M ", HasUnstagedChanges: true},
path: "dir2/file4",
},
},
},
{
Path: "dir3",
path: "dir3",
Children: []*Node[models.File]{
{
Path: "dir3/dir3-1",
path: "dir3/dir3-1",
Children: []*Node[models.File]{
{
File: &models.File{Name: "file5", ShortStatus: "M ", HasUnstagedChanges: true},
Path: "dir3/dir3-1/file5",
File: &models.File{Path: "file5", ShortStatus: "M ", HasUnstagedChanges: true},
path: "dir3/dir3-1/file5",
},
},
},
},
},
{
File: &models.File{Name: "file1", ShortStatus: "M ", HasUnstagedChanges: true},
Path: "file1",
File: &models.File{Path: "file1", ShortStatus: "M ", HasUnstagedChanges: true},
path: "file1",
},
},
},
expected: &Node[models.File]{
Path: "",
path: "",
Children: []*Node[models.File]{
{
Path: "dir1",
path: "dir1",
Children: []*Node[models.File]{
{
File: &models.File{Name: "file2", ShortStatus: "M ", HasUnstagedChanges: true},
Path: "dir1/file2",
File: &models.File{Path: "file2", ShortStatus: "M ", HasUnstagedChanges: true},
path: "dir1/file2",
},
},
},
{
Path: "dir2",
path: "dir2",
Children: []*Node[models.File]{
{
File: &models.File{Name: "file3", ShortStatus: " M", HasStagedChanges: true},
Path: "dir2/file3",
File: &models.File{Path: "file3", ShortStatus: " M", HasStagedChanges: true},
path: "dir2/file3",
},
{
File: &models.File{Name: "file4", ShortStatus: "M ", HasUnstagedChanges: true},
Path: "dir2/file4",
File: &models.File{Path: "file4", ShortStatus: "M ", HasUnstagedChanges: true},
path: "dir2/file4",
},
},
},
{
Path: "dir3/dir3-1",
path: "dir3/dir3-1",
CompressionLevel: 1,
Children: []*Node[models.File]{
{
File: &models.File{Name: "file5", ShortStatus: "M ", HasUnstagedChanges: true},
Path: "dir3/dir3-1/file5",
File: &models.File{Path: "file5", ShortStatus: "M ", HasUnstagedChanges: true},
path: "dir3/dir3-1/file5",
},
},
},
{
File: &models.File{Name: "file1", ShortStatus: "M ", HasUnstagedChanges: true},
Path: "file1",
File: &models.File{Path: "file1", ShortStatus: "M ", HasUnstagedChanges: true},
path: "file1",
},
},
},
@@ -141,13 +141,13 @@ func TestGetFile(t *testing.T) {
}{
{
name: "valid case",
viewModel: NewFileTree(func() []*models.File { return []*models.File{{Name: "blah/one"}, {Name: "blah/two"}} }, nil, false),
viewModel: NewFileTree(func() []*models.File { return []*models.File{{Path: "blah/one"}, {Path: "blah/two"}} }, nil, false),
path: "blah/two",
expected: &models.File{Name: "blah/two"},
expected: &models.File{Path: "blah/two"},
},
{
name: "not found",
viewModel: NewFileTree(func() []*models.File { return []*models.File{{Name: "blah/one"}, {Name: "blah/two"}} }, nil, false),
viewModel: NewFileTree(func() []*models.File { return []*models.File{{Path: "blah/one"}, {Path: "blah/two"}} }, nil, false),
path: "blah/three",
expected: nil,
},

View File

@@ -123,7 +123,7 @@ func (self *FileTree) Get(index int) *FileNode {
func (self *FileTree) GetFile(path string) *models.File {
for _, file := range self.getFiles() {
if file.Name == path {
if file.Path == path {
return file
}
}
@@ -183,7 +183,7 @@ func (self *FileTree) ToggleCollapsed(path string) {
func (self *FileTree) CollapseAll() {
dirPaths := lo.FilterMap(self.GetAllItems(), func(file *FileNode, index int) (string, bool) {
return file.Path, !file.IsFile()
return file.path, !file.IsFile()
})
for _, path := range dirPaths {

View File

@@ -18,67 +18,67 @@ func TestFilterAction(t *testing.T) {
name: "filter files with unstaged changes",
filter: DisplayUnstaged,
files: []*models.File{
{Name: "dir2/dir2/file4", ShortStatus: "M ", HasUnstagedChanges: true},
{Name: "dir2/file5", ShortStatus: "M ", HasStagedChanges: true},
{Name: "file1", ShortStatus: "M ", HasUnstagedChanges: true},
{Path: "dir2/dir2/file4", ShortStatus: "M ", HasUnstagedChanges: true},
{Path: "dir2/file5", ShortStatus: "M ", HasStagedChanges: true},
{Path: "file1", ShortStatus: "M ", HasUnstagedChanges: true},
},
expected: []*models.File{
{Name: "dir2/dir2/file4", ShortStatus: "M ", HasUnstagedChanges: true},
{Name: "file1", ShortStatus: "M ", HasUnstagedChanges: true},
{Path: "dir2/dir2/file4", ShortStatus: "M ", HasUnstagedChanges: true},
{Path: "file1", ShortStatus: "M ", HasUnstagedChanges: true},
},
},
{
name: "filter files with staged changes",
filter: DisplayStaged,
files: []*models.File{
{Name: "dir2/dir2/file4", ShortStatus: "M ", HasStagedChanges: true},
{Name: "dir2/file5", ShortStatus: "M ", HasStagedChanges: false},
{Name: "file1", ShortStatus: "M ", HasStagedChanges: true},
{Path: "dir2/dir2/file4", ShortStatus: "M ", HasStagedChanges: true},
{Path: "dir2/file5", ShortStatus: "M ", HasStagedChanges: false},
{Path: "file1", ShortStatus: "M ", HasStagedChanges: true},
},
expected: []*models.File{
{Name: "dir2/dir2/file4", ShortStatus: "M ", HasStagedChanges: true},
{Name: "file1", ShortStatus: "M ", HasStagedChanges: true},
{Path: "dir2/dir2/file4", ShortStatus: "M ", HasStagedChanges: true},
{Path: "file1", ShortStatus: "M ", HasStagedChanges: true},
},
},
{
name: "filter files that are tracked",
filter: DisplayTracked,
files: []*models.File{
{Name: "dir2/dir2/file4", ShortStatus: "M ", Tracked: true},
{Name: "dir2/file5", ShortStatus: "M ", Tracked: false},
{Name: "file1", ShortStatus: "M ", Tracked: true},
{Path: "dir2/dir2/file4", ShortStatus: "M ", Tracked: true},
{Path: "dir2/file5", ShortStatus: "M ", Tracked: false},
{Path: "file1", ShortStatus: "M ", Tracked: true},
},
expected: []*models.File{
{Name: "dir2/dir2/file4", ShortStatus: "M ", Tracked: true},
{Name: "file1", ShortStatus: "M ", Tracked: true},
{Path: "dir2/dir2/file4", ShortStatus: "M ", Tracked: true},
{Path: "file1", ShortStatus: "M ", Tracked: true},
},
},
{
name: "filter all files",
filter: DisplayAll,
files: []*models.File{
{Name: "dir2/dir2/file4", ShortStatus: "M ", HasUnstagedChanges: true},
{Name: "dir2/file5", ShortStatus: "M ", HasUnstagedChanges: true},
{Name: "file1", ShortStatus: "M ", HasUnstagedChanges: true},
{Path: "dir2/dir2/file4", ShortStatus: "M ", HasUnstagedChanges: true},
{Path: "dir2/file5", ShortStatus: "M ", HasUnstagedChanges: true},
{Path: "file1", ShortStatus: "M ", HasUnstagedChanges: true},
},
expected: []*models.File{
{Name: "dir2/dir2/file4", ShortStatus: "M ", HasUnstagedChanges: true},
{Name: "dir2/file5", ShortStatus: "M ", HasUnstagedChanges: true},
{Name: "file1", ShortStatus: "M ", HasUnstagedChanges: true},
{Path: "dir2/dir2/file4", ShortStatus: "M ", HasUnstagedChanges: true},
{Path: "dir2/file5", ShortStatus: "M ", HasUnstagedChanges: true},
{Path: "file1", ShortStatus: "M ", HasUnstagedChanges: true},
},
},
{
name: "filter conflicted files",
filter: DisplayConflicted,
files: []*models.File{
{Name: "dir2/dir2/file4", ShortStatus: "DU", HasMergeConflicts: true},
{Name: "dir2/file5", ShortStatus: "M ", HasUnstagedChanges: true},
{Name: "dir2/file6", ShortStatus: " M", HasStagedChanges: true},
{Name: "file1", ShortStatus: "UU", HasMergeConflicts: true, HasInlineMergeConflicts: true},
{Path: "dir2/dir2/file4", ShortStatus: "DU", HasMergeConflicts: true},
{Path: "dir2/file5", ShortStatus: "M ", HasUnstagedChanges: true},
{Path: "dir2/file6", ShortStatus: " M", HasStagedChanges: true},
{Path: "file1", ShortStatus: "UU", HasMergeConflicts: true, HasInlineMergeConflicts: true},
},
expected: []*models.File{
{Name: "dir2/dir2/file4", ShortStatus: "DU", HasMergeConflicts: true},
{Name: "file1", ShortStatus: "UU", HasMergeConflicts: true, HasInlineMergeConflicts: true},
{Path: "dir2/dir2/file4", ShortStatus: "DU", HasMergeConflicts: true},
{Path: "file1", ShortStatus: "UU", HasMergeConflicts: true, HasInlineMergeConflicts: true},
},
},
}

View File

@@ -103,8 +103,8 @@ func (self *FileTreeViewModel) SetTree() {
// for when you stage the old file of a rename and the new file is in a collapsed dir
for _, file := range newFiles {
if selectedNode != nil && selectedNode.Path != "" && file.PreviousName == selectedNode.Path {
self.ExpandToPath(file.Name)
if selectedNode != nil && selectedNode.path != "" && file.PreviousPath == selectedNode.path {
self.ExpandToPath(file.Path)
}
}
@@ -139,7 +139,7 @@ func (self *FileTreeViewModel) findNewSelectedIdx(prevNodes []*FileNode, currNod
if node.File != nil && node.File.IsRename() {
return node.File.Names()
} else {
return []string{node.Path}
return []string{node.path}
}
}
@@ -151,7 +151,7 @@ func (self *FileTreeViewModel) findNewSelectedIdx(prevNodes []*FileNode, currNod
// If you started off with a rename selected, and now it's broken in two, we want you to jump to the new file, not the old file.
// This is because the new should be in the same position as the rename was meaning less cursor jumping
foundOldFileInRename := prevNode.File != nil && prevNode.File.IsRename() && node.Path == prevNode.File.PreviousName
foundOldFileInRename := prevNode.File != nil && prevNode.File.IsRename() && node.path == prevNode.File.PreviousPath
foundNode := utils.StringArraysOverlap(paths, selectedPaths) && !foundOldFileInRename
if foundNode {
return idx
@@ -178,12 +178,12 @@ func (self *FileTreeViewModel) ToggleShowTree() {
if selectedNode == nil {
return
}
path := selectedNode.Path
path := selectedNode.path
if self.InTreeMode() {
self.ExpandToPath(path)
} else if len(selectedNode.Children) > 0 {
path = selectedNode.GetLeaves()[0].Path
path = selectedNode.GetLeaves()[0].path
}
index, found := self.GetIndexForPath(path)
@@ -200,7 +200,7 @@ func (self *FileTreeViewModel) CollapseAll() {
return
}
topLevelPath := strings.Split(selectedNode.Path, "/")[0]
topLevelPath := strings.Split(selectedNode.path, "/")[0]
index, found := self.GetIndexForPath(topLevelPath)
if found {
self.SetSelectedLineIdx(index)
@@ -216,7 +216,7 @@ func (self *FileTreeViewModel) ExpandAll() {
return
}
index, found := self.GetIndexForPath(selectedNode.Path)
index, found := self.GetIndexForPath(selectedNode.path)
if found {
self.SetSelectedLineIdx(index)
}

View File

@@ -2,6 +2,7 @@ package filetree
import (
"path"
"strings"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/types"
@@ -19,7 +20,8 @@ type Node[T any] struct {
Children []*Node[T]
// path of the file/directory
Path string
// private; use either GetPath() or GetInternalPath() to access
path string
// rather than render a tree as:
// a/
@@ -45,8 +47,21 @@ func (self *Node[T]) GetFile() *T {
return self.File
}
// This returns the logical path from the user's point of view. It is the
// relative path from the root of the repository.
// Use this for display, or when you want to perform some action on the path
// (e.g. a git command).
func (self *Node[T]) GetPath() string {
return self.Path
return strings.TrimPrefix(self.path, "./")
}
// This returns the internal path from the tree's point of view. It's the same
// as GetPath() except that it has a "./" prefix when we are showing a root
// item.
// Use this when interacting with the tree itself, e.g. when calling
// ToggleCollapsed.
func (self *Node[T]) GetInternalPath() string {
return self.path
}
func (self *Node[T]) Sort() {
@@ -88,7 +103,7 @@ func (self *Node[T]) SortChildren() {
return false
}
return a.GetPath() < b.GetPath()
return a.path < b.path
})
// TODO: think about making this in-place
@@ -158,7 +173,7 @@ func (self *Node[T]) EveryFile(test func(*T) bool) bool {
func (self *Node[T]) Flatten(collapsedPaths *CollapsedPaths) []*Node[T] {
result := []*Node[T]{self}
if len(self.Children) > 0 && !collapsedPaths.IsCollapsed(self.GetPath()) {
if len(self.Children) > 0 && !collapsedPaths.IsCollapsed(self.path) {
result = append(result, lo.FlatMap(self.Children, func(child *Node[T], _ int) []*Node[T] {
return child.Flatten(collapsedPaths)
})...)
@@ -184,7 +199,7 @@ func (self *Node[T]) getNodeAtIndexAux(index int, collapsedPaths *CollapsedPaths
return self, offset
}
if !collapsedPaths.IsCollapsed(self.GetPath()) {
if !collapsedPaths.IsCollapsed(self.path) {
for _, child := range self.Children {
foundNode, offsetChange := child.getNodeAtIndexAux(index-offset, collapsedPaths)
offset += offsetChange
@@ -200,11 +215,11 @@ func (self *Node[T]) getNodeAtIndexAux(index int, collapsedPaths *CollapsedPaths
func (self *Node[T]) GetIndexForPath(path string, collapsedPaths *CollapsedPaths) (int, bool) {
offset := 0
if self.GetPath() == path {
if self.path == path {
return offset, true
}
if !collapsedPaths.IsCollapsed(self.GetPath()) {
if !collapsedPaths.IsCollapsed(self.path) {
for _, child := range self.Children {
offsetChange, found := child.GetIndexForPath(path, collapsedPaths)
offset += offsetChange + 1
@@ -224,7 +239,7 @@ func (self *Node[T]) Size(collapsedPaths *CollapsedPaths) int {
output := 1
if !collapsedPaths.IsCollapsed(self.GetPath()) {
if !collapsedPaths.IsCollapsed(self.path) {
for _, child := range self.Children {
output += child.Size(collapsedPaths)
}
@@ -308,5 +323,5 @@ func (self *Node[T]) Description() string {
}
func (self *Node[T]) Name() string {
return path.Base(self.Path)
return path.Base(self.path)
}

View File

@@ -52,11 +52,11 @@ func commitFilePatchStatus(node *filetree.Node[models.CommitFile], tree *filetre
// be whatever status it is, but if it's a non-leaf it will determine its status
// based on the leaves of that subtree
if node.EveryFile(func(file *models.CommitFile) bool {
return patchBuilder.GetFileStatus(file.Name, tree.GetRef().RefName()) == patch.WHOLE
return patchBuilder.GetFileStatus(file.Path, tree.GetRef().RefName()) == patch.WHOLE
}) {
return patch.WHOLE
} else if node.EveryFile(func(file *models.CommitFile) bool {
return patchBuilder.GetFileStatus(file.Name, tree.GetRef().RefName()) == patch.UNSELECTED
return patchBuilder.GetFileStatus(file.Path, tree.GetRef().RefName()) == patch.UNSELECTED
}) {
return patch.UNSELECTED
} else {
@@ -91,11 +91,11 @@ func renderAux[T any](
arr := []string{}
if !isRoot {
isCollapsed := collapsedPaths.IsCollapsed(node.GetPath())
isCollapsed := collapsedPaths.IsCollapsed(node.GetInternalPath())
arr = append(arr, renderLine(node, treeDepth, visualDepth, isCollapsed))
}
if collapsedPaths.IsCollapsed(node.GetPath()) {
if collapsedPaths.IsCollapsed(node.GetInternalPath()) {
return arr
}
@@ -293,13 +293,19 @@ func getColorForChangeStatus(changeStatus string) style.TextStyle {
}
func fileNameAtDepth(node *filetree.Node[models.File], depth int) string {
splitName := split(node.Path)
splitName := split(node.GetInternalPath())
if depth == 0 && splitName[0] == "." {
if len(splitName) == 1 {
return "<root>"
}
depth = 1
}
name := join(splitName[depth:])
if node.File != nil && node.File.IsRename() {
splitPrevName := split(node.File.PreviousName)
splitPrevName := split(node.File.PreviousPath)
prevName := node.File.PreviousName
prevName := node.File.PreviousPath
// if the file has just been renamed inside the same directory, we can shave off
// the prefix for the previous path too. Otherwise we'll keep it unchanged
sameParentDir := len(splitName) == len(splitPrevName) && join(splitName[0:depth]) == join(splitPrevName[0:depth])
@@ -314,7 +320,13 @@ func fileNameAtDepth(node *filetree.Node[models.File], depth int) string {
}
func commitFileNameAtDepth(node *filetree.Node[models.CommitFile], depth int) string {
splitName := split(node.Path)
splitName := split(node.GetInternalPath())
if depth == 0 && splitName[0] == "." {
if len(splitName) == 1 {
return "<root>"
}
depth = 1
}
name := join(splitName[depth:])
return name

View File

@@ -34,17 +34,17 @@ func TestRenderFileTree(t *testing.T) {
{
name: "leaf node",
files: []*models.File{
{Name: "test", ShortStatus: " M", HasStagedChanges: true},
{Path: "test", ShortStatus: " M", HasStagedChanges: true},
},
expected: []string{" M test"},
},
{
name: "numstat",
files: []*models.File{
{Name: "test", ShortStatus: " M", HasStagedChanges: true, LinesAdded: 1, LinesDeleted: 1},
{Name: "test2", ShortStatus: " M", HasStagedChanges: true, LinesAdded: 1},
{Name: "test3", ShortStatus: " M", HasStagedChanges: true, LinesDeleted: 1},
{Name: "test4", ShortStatus: " M", HasStagedChanges: true, LinesAdded: 0, LinesDeleted: 0},
{Path: "test", ShortStatus: " M", HasStagedChanges: true, LinesAdded: 1, LinesDeleted: 1},
{Path: "test2", ShortStatus: " M", HasStagedChanges: true, LinesAdded: 1},
{Path: "test3", ShortStatus: " M", HasStagedChanges: true, LinesDeleted: 1},
{Path: "test4", ShortStatus: " M", HasStagedChanges: true, LinesAdded: 0, LinesDeleted: 0},
},
showLineChanges: true,
expected: []string{
@@ -57,12 +57,12 @@ func TestRenderFileTree(t *testing.T) {
{
name: "big example",
files: []*models.File{
{Name: "dir1/file2", ShortStatus: "M ", HasUnstagedChanges: true},
{Name: "dir1/file3", ShortStatus: "M ", HasUnstagedChanges: true},
{Name: "dir2/dir2/file3", ShortStatus: " M", HasStagedChanges: true},
{Name: "dir2/dir2/file4", ShortStatus: "M ", HasUnstagedChanges: true},
{Name: "dir2/file5", ShortStatus: "M ", HasUnstagedChanges: true},
{Name: "file1", ShortStatus: "M ", HasUnstagedChanges: true},
{Path: "dir1/file2", ShortStatus: "M ", HasUnstagedChanges: true},
{Path: "dir1/file3", ShortStatus: "M ", HasUnstagedChanges: true},
{Path: "dir2/dir2/file3", ShortStatus: " M", HasStagedChanges: true},
{Path: "dir2/dir2/file4", ShortStatus: "M ", HasUnstagedChanges: true},
{Path: "dir2/file5", ShortStatus: "M ", HasUnstagedChanges: true},
{Path: "file1", ShortStatus: "M ", HasUnstagedChanges: true},
},
expected: toStringSlice(
`
@@ -111,19 +111,19 @@ func TestRenderCommitFileTree(t *testing.T) {
{
name: "leaf node",
files: []*models.CommitFile{
{Name: "test", ChangeStatus: "A"},
{Path: "test", ChangeStatus: "A"},
},
expected: []string{"A test"},
},
{
name: "big example",
files: []*models.CommitFile{
{Name: "dir1/file2", ChangeStatus: "M"},
{Name: "dir1/file3", ChangeStatus: "A"},
{Name: "dir2/dir2/file3", ChangeStatus: "D"},
{Name: "dir2/dir2/file4", ChangeStatus: "M"},
{Name: "dir2/file5", ChangeStatus: "M"},
{Name: "file1", ChangeStatus: "M"},
{Path: "dir1/file2", ChangeStatus: "M"},
{Path: "dir1/file3", ChangeStatus: "A"},
{Path: "dir2/dir2/file3", ChangeStatus: "D"},
{Path: "dir2/dir2/file4", ChangeStatus: "M"},
{Path: "dir2/file5", ChangeStatus: "M"},
{Path: "file1", ChangeStatus: "M"},
},
expected: toStringSlice(
`

View File

@@ -47,8 +47,8 @@ func fileShimFromModelFile(file *models.File) *File {
}
return &File{
Name: file.Name,
PreviousName: file.PreviousName,
Name: file.Path,
PreviousName: file.PreviousPath,
HasStagedChanges: file.HasStagedChanges,
HasUnstagedChanges: file.HasUnstagedChanges,
Tracked: file.Tracked,
@@ -141,7 +141,7 @@ func commitFileShimFromModelRemote(commitFile *models.CommitFile) *CommitFile {
}
return &CommitFile{
Name: commitFile.Name,
Name: commitFile.Path,
ChangeStatus: commitFile.ChangeStatus,
}
}

View File

@@ -72,6 +72,7 @@ var ResetToUpstream = NewIntegrationTest(NewIntegrationTestArgs{
Contains("hard commit"),
)
t.Views().Files().Lines(
Contains("<root>"),
Contains("file-1").Contains("A"),
Contains("file-2").Contains("A"),
)

View File

@@ -57,6 +57,7 @@ var UndoCommit = NewIntegrationTest(NewIntegrationTestArgs{
t.Views().Files().
Lines(
Contains("<root>"),
Contains("A file"),
Contains(" M other-file"),
)
@@ -84,6 +85,7 @@ var UndoCommit = NewIntegrationTest(NewIntegrationTestArgs{
t.Views().Files().Focus().
Lines(
Contains("<root>"),
Contains("A file"),
Contains(" M other-file").IsSelected(),
).