mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-06-23 00:39:13 +02:00
Properly render worktrees in files panel
This commit is contained in:
@ -2,6 +2,7 @@ package git_commands
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||||
@ -58,13 +59,32 @@ func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File
|
|||||||
Name: status.Name,
|
Name: status.Name,
|
||||||
PreviousName: status.PreviousName,
|
PreviousName: status.PreviousName,
|
||||||
DisplayString: status.StatusString,
|
DisplayString: status.StatusString,
|
||||||
Type: self.getFileType(status.Name),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
models.SetStatusFields(file, status.Change)
|
models.SetStatusFields(file, status.Change)
|
||||||
files = append(files, file)
|
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
|
return files
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +41,6 @@ func TestFileGetStatusFiles(t *testing.T) {
|
|||||||
HasMergeConflicts: false,
|
HasMergeConflicts: false,
|
||||||
HasInlineMergeConflicts: false,
|
HasInlineMergeConflicts: false,
|
||||||
DisplayString: "MM file1.txt",
|
DisplayString: "MM file1.txt",
|
||||||
Type: "file",
|
|
||||||
ShortStatus: "MM",
|
ShortStatus: "MM",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -54,7 +53,6 @@ func TestFileGetStatusFiles(t *testing.T) {
|
|||||||
HasMergeConflicts: false,
|
HasMergeConflicts: false,
|
||||||
HasInlineMergeConflicts: false,
|
HasInlineMergeConflicts: false,
|
||||||
DisplayString: "A file3.txt",
|
DisplayString: "A file3.txt",
|
||||||
Type: "file",
|
|
||||||
ShortStatus: "A ",
|
ShortStatus: "A ",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -67,7 +65,6 @@ func TestFileGetStatusFiles(t *testing.T) {
|
|||||||
HasMergeConflicts: false,
|
HasMergeConflicts: false,
|
||||||
HasInlineMergeConflicts: false,
|
HasInlineMergeConflicts: false,
|
||||||
DisplayString: "AM file2.txt",
|
DisplayString: "AM file2.txt",
|
||||||
Type: "file",
|
|
||||||
ShortStatus: "AM",
|
ShortStatus: "AM",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -80,7 +77,6 @@ func TestFileGetStatusFiles(t *testing.T) {
|
|||||||
HasMergeConflicts: false,
|
HasMergeConflicts: false,
|
||||||
HasInlineMergeConflicts: false,
|
HasInlineMergeConflicts: false,
|
||||||
DisplayString: "?? file4.txt",
|
DisplayString: "?? file4.txt",
|
||||||
Type: "file",
|
|
||||||
ShortStatus: "??",
|
ShortStatus: "??",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -93,7 +89,6 @@ func TestFileGetStatusFiles(t *testing.T) {
|
|||||||
HasMergeConflicts: true,
|
HasMergeConflicts: true,
|
||||||
HasInlineMergeConflicts: true,
|
HasInlineMergeConflicts: true,
|
||||||
DisplayString: "UU file5.txt",
|
DisplayString: "UU file5.txt",
|
||||||
Type: "file",
|
|
||||||
ShortStatus: "UU",
|
ShortStatus: "UU",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -113,7 +108,6 @@ func TestFileGetStatusFiles(t *testing.T) {
|
|||||||
HasMergeConflicts: false,
|
HasMergeConflicts: false,
|
||||||
HasInlineMergeConflicts: false,
|
HasInlineMergeConflicts: false,
|
||||||
DisplayString: "MM a\nb.txt",
|
DisplayString: "MM a\nb.txt",
|
||||||
Type: "file",
|
|
||||||
ShortStatus: "MM",
|
ShortStatus: "MM",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -137,7 +131,6 @@ func TestFileGetStatusFiles(t *testing.T) {
|
|||||||
HasMergeConflicts: false,
|
HasMergeConflicts: false,
|
||||||
HasInlineMergeConflicts: false,
|
HasInlineMergeConflicts: false,
|
||||||
DisplayString: "R before1.txt -> after1.txt",
|
DisplayString: "R before1.txt -> after1.txt",
|
||||||
Type: "file",
|
|
||||||
ShortStatus: "R ",
|
ShortStatus: "R ",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -151,7 +144,6 @@ func TestFileGetStatusFiles(t *testing.T) {
|
|||||||
HasMergeConflicts: false,
|
HasMergeConflicts: false,
|
||||||
HasInlineMergeConflicts: false,
|
HasInlineMergeConflicts: false,
|
||||||
DisplayString: "RM before2.txt -> after2.txt",
|
DisplayString: "RM before2.txt -> after2.txt",
|
||||||
Type: "file",
|
|
||||||
ShortStatus: "RM",
|
ShortStatus: "RM",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -174,7 +166,6 @@ func TestFileGetStatusFiles(t *testing.T) {
|
|||||||
HasMergeConflicts: false,
|
HasMergeConflicts: false,
|
||||||
HasInlineMergeConflicts: false,
|
HasInlineMergeConflicts: false,
|
||||||
DisplayString: "?? a -> b.txt",
|
DisplayString: "?? a -> b.txt",
|
||||||
Type: "file",
|
|
||||||
ShortStatus: "??",
|
ShortStatus: "??",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||||
)
|
)
|
||||||
@ -107,7 +108,7 @@ func CheckedOutByOtherWorktree(branch *models.Branch, worktrees []*models.Worktr
|
|||||||
return !IsCurrentWorktree(worktree.Path)
|
return !IsCurrentWorktree(worktree.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetCurrentRepoName() string {
|
func GetCurrentRepoPath() string {
|
||||||
pwd, err := os.Getwd()
|
pwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err.Error())
|
log.Fatalln(err.Error())
|
||||||
@ -120,24 +121,58 @@ func GetCurrentRepoName() string {
|
|||||||
log.Fatalln(err.Error())
|
log.Fatalln(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// must be a worktree or bare repo
|
if gitFileInfo.IsDir() {
|
||||||
if !gitFileInfo.IsDir() {
|
// must be in the main worktree
|
||||||
worktreeGitPath, ok := WorktreeGitPath(pwd)
|
return currentPath()
|
||||||
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))))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
pwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err.Error())
|
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
|
||||||
}
|
}
|
||||||
|
@ -18,8 +18,10 @@ type File struct {
|
|||||||
HasMergeConflicts bool
|
HasMergeConflicts bool
|
||||||
HasInlineMergeConflicts bool
|
HasInlineMergeConflicts bool
|
||||||
DisplayString string
|
DisplayString string
|
||||||
Type string // one of 'file', 'directory', and 'other'
|
|
||||||
ShortStatus string // e.g. 'AD', ' A', 'M ', '??'
|
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
|
// sometimes we need to deal with either a node (which contains a file) or an actual file
|
||||||
|
@ -155,10 +155,11 @@ func getFileLine(hasUnstagedChanges bool, hasStagedChanges bool, name string, di
|
|||||||
}
|
}
|
||||||
|
|
||||||
isSubmodule := file != nil && file.IsSubmodule(submoduleConfigs)
|
isSubmodule := file != nil && file.IsSubmodule(submoduleConfigs)
|
||||||
|
isLinkedWorktree := file != nil && file.IsWorktree
|
||||||
isDirectory := file == nil
|
isDirectory := file == nil
|
||||||
|
|
||||||
if icons.IsIconEnabled() {
|
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))
|
output += restColor.Sprint(utils.EscapeSpecialChars(name))
|
||||||
@ -193,10 +194,11 @@ func getCommitFileLine(name string, diffName string, commitFile *models.CommitFi
|
|||||||
}
|
}
|
||||||
|
|
||||||
isSubmodule := false
|
isSubmodule := false
|
||||||
|
isLinkedWorktree := false
|
||||||
isDirectory := commitFile == nil
|
isDirectory := commitFile == nil
|
||||||
|
|
||||||
if icons.IsIconEnabled() {
|
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)
|
output += colour.Sprint(name)
|
||||||
|
@ -323,7 +323,7 @@ func patchFileIconsForNerdFontsV2() {
|
|||||||
extIconMap[".vue"] = "\ufd42" // ﵂
|
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)
|
base := filepath.Base(name)
|
||||||
if icon, ok := nameIconMap[base]; ok {
|
if icon, ok := nameIconMap[base]; ok {
|
||||||
return icon
|
return icon
|
||||||
@ -336,6 +336,8 @@ func IconForFile(name string, isSubmodule bool, isDirectory bool) string {
|
|||||||
|
|
||||||
if isSubmodule {
|
if isSubmodule {
|
||||||
return DEFAULT_SUBMODULE_ICON
|
return DEFAULT_SUBMODULE_ICON
|
||||||
|
} else if isLinkedWorktree {
|
||||||
|
return LINKED_WORKTREE_ICON
|
||||||
} else if isDirectory {
|
} else if isDirectory {
|
||||||
return DEFAULT_DIRECTORY_ICON
|
return DEFAULT_DIRECTORY_ICON
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user