From 7b05dacb98b1baa72f61e7decf7d23e8bd241393 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Mon, 17 Jul 2023 15:22:14 +1000 Subject: [PATCH] Properly render worktrees in files panel --- pkg/commands/git_commands/file_loader.go | 22 ++++++- pkg/commands/git_commands/file_loader_test.go | 9 --- pkg/commands/git_commands/worktree.go | 61 +++++++++++++++---- pkg/commands/models/file.go | 4 +- pkg/gui/presentation/files.go | 6 +- pkg/gui/presentation/icons/file_icons.go | 4 +- 6 files changed, 79 insertions(+), 27 deletions(-) diff --git a/pkg/commands/git_commands/file_loader.go b/pkg/commands/git_commands/file_loader.go index b33588475..685062a58 100644 --- a/pkg/commands/git_commands/file_loader.go +++ b/pkg/commands/git_commands/file_loader.go @@ -2,6 +2,7 @@ package git_commands import ( "fmt" + "path/filepath" "strings" "github.com/jesseduffield/lazygit/pkg/commands/models" @@ -58,13 +59,32 @@ func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File Name: status.Name, PreviousName: status.PreviousName, DisplayString: status.StatusString, - Type: self.getFileType(status.Name), } models.SetStatusFields(file, status.Change) files = append(files, file) } + // Go through the worktrees to see if any of these files are actually worktrees + // so that we can render them correctly + worktreePaths := linkedWortkreePaths() + for _, file := range files { + for _, worktreePath := range worktreePaths { + absFilePath, err := filepath.Abs(file.Name) + if err != nil { + self.Log.Error(err) + continue + } + if absFilePath == worktreePath { + 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, "/") + break + } + } + } + return files } diff --git a/pkg/commands/git_commands/file_loader_test.go b/pkg/commands/git_commands/file_loader_test.go index 02ea390a7..ae149c2f2 100644 --- a/pkg/commands/git_commands/file_loader_test.go +++ b/pkg/commands/git_commands/file_loader_test.go @@ -41,7 +41,6 @@ func TestFileGetStatusFiles(t *testing.T) { HasMergeConflicts: false, HasInlineMergeConflicts: false, DisplayString: "MM file1.txt", - Type: "file", ShortStatus: "MM", }, { @@ -54,7 +53,6 @@ func TestFileGetStatusFiles(t *testing.T) { HasMergeConflicts: false, HasInlineMergeConflicts: false, DisplayString: "A file3.txt", - Type: "file", ShortStatus: "A ", }, { @@ -67,7 +65,6 @@ func TestFileGetStatusFiles(t *testing.T) { HasMergeConflicts: false, HasInlineMergeConflicts: false, DisplayString: "AM file2.txt", - Type: "file", ShortStatus: "AM", }, { @@ -80,7 +77,6 @@ func TestFileGetStatusFiles(t *testing.T) { HasMergeConflicts: false, HasInlineMergeConflicts: false, DisplayString: "?? file4.txt", - Type: "file", ShortStatus: "??", }, { @@ -93,7 +89,6 @@ func TestFileGetStatusFiles(t *testing.T) { HasMergeConflicts: true, HasInlineMergeConflicts: true, DisplayString: "UU file5.txt", - Type: "file", ShortStatus: "UU", }, }, @@ -113,7 +108,6 @@ func TestFileGetStatusFiles(t *testing.T) { HasMergeConflicts: false, HasInlineMergeConflicts: false, DisplayString: "MM a\nb.txt", - Type: "file", ShortStatus: "MM", }, }, @@ -137,7 +131,6 @@ func TestFileGetStatusFiles(t *testing.T) { HasMergeConflicts: false, HasInlineMergeConflicts: false, DisplayString: "R before1.txt -> after1.txt", - Type: "file", ShortStatus: "R ", }, { @@ -151,7 +144,6 @@ func TestFileGetStatusFiles(t *testing.T) { HasMergeConflicts: false, HasInlineMergeConflicts: false, DisplayString: "RM before2.txt -> after2.txt", - Type: "file", ShortStatus: "RM", }, }, @@ -174,7 +166,6 @@ func TestFileGetStatusFiles(t *testing.T) { HasMergeConflicts: false, HasInlineMergeConflicts: false, DisplayString: "?? a -> b.txt", - Type: "file", ShortStatus: "??", }, }, diff --git a/pkg/commands/git_commands/worktree.go b/pkg/commands/git_commands/worktree.go index 78fdd703f..ce2d831c1 100644 --- a/pkg/commands/git_commands/worktree.go +++ b/pkg/commands/git_commands/worktree.go @@ -7,6 +7,7 @@ import ( "log" "os" "path/filepath" + "strings" "github.com/jesseduffield/lazygit/pkg/commands/models" ) @@ -107,7 +108,7 @@ func CheckedOutByOtherWorktree(branch *models.Branch, worktrees []*models.Worktr return !IsCurrentWorktree(worktree.Path) } -func GetCurrentRepoName() string { +func GetCurrentRepoPath() string { pwd, err := os.Getwd() if err != nil { log.Fatalln(err.Error()) @@ -120,24 +121,58 @@ func GetCurrentRepoName() string { log.Fatalln(err.Error()) } - // must be a worktree or bare repo - if !gitFileInfo.IsDir() { - worktreeGitPath, ok := WorktreeGitPath(pwd) - if !ok { - return basePath() - } - - // now we just jump up three directories to get the repo name - return filepath.Base(filepath.Dir(filepath.Dir(filepath.Dir(worktreeGitPath)))) + if gitFileInfo.IsDir() { + // must be in the main worktree + return currentPath() } - return basePath() + // must be a worktree or bare repo + worktreeGitPath, ok := WorktreeGitPath(pwd) + if !ok { + // fallback + return currentPath() + } + + // now we just jump up three directories to get the repo name + return filepath.Dir(filepath.Dir(filepath.Dir(worktreeGitPath))) } -func basePath() string { +func GetCurrentRepoName() string { + return filepath.Base(GetCurrentRepoPath()) +} + +func currentPath() string { pwd, err := os.Getwd() if err != nil { log.Fatalln(err.Error()) } - return filepath.Base(pwd) + return pwd +} + +func linkedWortkreePaths() []string { + // first we need to get the repo dir + repoPath := GetCurrentRepoPath() + result := []string{} + worktreePath := filepath.Join(repoPath, ".git", "worktrees") + // for each directory in this path we're going to cat the `gitdir` file and append its contents to our result + err := filepath.Walk(worktreePath, func(path string, info fs.FileInfo, err error) error { + if info.IsDir() { + gitDirPath := filepath.Join(path, "gitdir") + gitDirBytes, err := os.ReadFile(gitDirPath) + if err != nil { + // ignoring error + return nil + } + trimmedGitDir := strings.TrimSpace(string(gitDirBytes)) + // removing the .git part + worktreeDir := filepath.Dir(trimmedGitDir) + result = append(result, worktreeDir) + } + return nil + }) + if err != nil { + log.Fatalln(err.Error()) + } + + return result } diff --git a/pkg/commands/models/file.go b/pkg/commands/models/file.go index dbe015cf1..45f1ec5d7 100644 --- a/pkg/commands/models/file.go +++ b/pkg/commands/models/file.go @@ -18,8 +18,10 @@ type File struct { HasMergeConflicts bool HasInlineMergeConflicts bool DisplayString string - Type string // one of 'file', 'directory', and 'other' ShortStatus string // e.g. 'AD', ' A', 'M ', '??' + + // If true, this must be a worktree folder + IsWorktree bool } // sometimes we need to deal with either a node (which contains a file) or an actual file diff --git a/pkg/gui/presentation/files.go b/pkg/gui/presentation/files.go index 5180264e3..0921878cb 100644 --- a/pkg/gui/presentation/files.go +++ b/pkg/gui/presentation/files.go @@ -155,10 +155,11 @@ func getFileLine(hasUnstagedChanges bool, hasStagedChanges bool, name string, di } isSubmodule := file != nil && file.IsSubmodule(submoduleConfigs) + isLinkedWorktree := file != nil && file.IsWorktree isDirectory := file == nil if icons.IsIconEnabled() { - output += restColor.Sprintf("%s ", icons.IconForFile(name, isSubmodule, isDirectory)) + output += restColor.Sprintf("%s ", icons.IconForFile(name, isSubmodule, isLinkedWorktree, isDirectory)) } output += restColor.Sprint(utils.EscapeSpecialChars(name)) @@ -193,10 +194,11 @@ func getCommitFileLine(name string, diffName string, commitFile *models.CommitFi } isSubmodule := false + isLinkedWorktree := false isDirectory := commitFile == nil if icons.IsIconEnabled() { - output += colour.Sprintf("%s ", icons.IconForFile(name, isSubmodule, isDirectory)) + output += colour.Sprintf("%s ", icons.IconForFile(name, isSubmodule, isLinkedWorktree, isDirectory)) } output += colour.Sprint(name) diff --git a/pkg/gui/presentation/icons/file_icons.go b/pkg/gui/presentation/icons/file_icons.go index b4ff58919..8d32bb36a 100644 --- a/pkg/gui/presentation/icons/file_icons.go +++ b/pkg/gui/presentation/icons/file_icons.go @@ -323,7 +323,7 @@ func patchFileIconsForNerdFontsV2() { extIconMap[".vue"] = "\ufd42" // ﵂ } -func IconForFile(name string, isSubmodule bool, isDirectory bool) string { +func IconForFile(name string, isSubmodule bool, isLinkedWorktree bool, isDirectory bool) string { base := filepath.Base(name) if icon, ok := nameIconMap[base]; ok { return icon @@ -336,6 +336,8 @@ func IconForFile(name string, isSubmodule bool, isDirectory bool) string { if isSubmodule { return DEFAULT_SUBMODULE_ICON + } else if isLinkedWorktree { + return LINKED_WORKTREE_ICON } else if isDirectory { return DEFAULT_DIRECTORY_ICON }