1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-06-25 00:46:54 +02:00
This commit is contained in:
Jesse Duffield
2021-03-21 15:58:15 +11:00
parent 9e67f74ca3
commit d5639e6e95
9 changed files with 61 additions and 57 deletions

View File

@ -9,6 +9,7 @@ import (
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
"github.com/mgutz/str" "github.com/mgutz/str"
) )
@ -137,12 +138,12 @@ func (c *GitCommand) DiscardAllFileChanges(file *models.File) error {
return c.DiscardUnstagedFileChanges(file) return c.DiscardUnstagedFileChanges(file)
} }
func (c *GitCommand) DiscardAllDirChanges(node *models.FileChangeNode) error { func (c *GitCommand) DiscardAllDirChanges(node *filetree.FileChangeNode) error {
// this could be more efficient but we would need to handle all the edge cases // this could be more efficient but we would need to handle all the edge cases
return node.ForEachFile(c.DiscardAllFileChanges) return node.ForEachFile(c.DiscardAllFileChanges)
} }
func (c *GitCommand) DiscardUnstagedDirChanges(node *models.FileChangeNode) error { func (c *GitCommand) DiscardUnstagedDirChanges(node *filetree.FileChangeNode) error {
if err := c.RemoveUntrackedDirFiles(node); err != nil { if err := c.RemoveUntrackedDirFiles(node); err != nil {
return err return err
} }
@ -155,9 +156,9 @@ func (c *GitCommand) DiscardUnstagedDirChanges(node *models.FileChangeNode) erro
return nil return nil
} }
func (c *GitCommand) RemoveUntrackedDirFiles(node *models.FileChangeNode) error { func (c *GitCommand) RemoveUntrackedDirFiles(node *filetree.FileChangeNode) error {
untrackedFilePaths := node.GetPathsMatching( untrackedFilePaths := node.GetPathsMatching(
func(n *models.FileChangeNode) bool { return n.File != nil && !n.File.GetIsTracked() }, func(n *filetree.FileChangeNode) bool { return n.File != nil && !n.File.GetIsTracked() },
) )
for _, path := range untrackedFilePaths { for _, path := range untrackedFilePaths {

View File

@ -21,6 +21,14 @@ type File struct {
ShortStatus string // e.g. 'AD', ' A', 'M ', '??' ShortStatus string // e.g. 'AD', ' A', 'M ', '??'
} }
// sometimes we need to deal with either a node (which contains a file) or an actual file
type IFileChange interface {
GetHasUnstagedChanges() bool
GetHasStagedChanges() bool
GetIsTracked() bool
GetPath() string
}
const RENAME_SEPARATOR = " -> " const RENAME_SEPARATOR = " -> "
func (f *File) IsRename() bool { func (f *File) IsRename() bool {

View File

@ -1,9 +0,0 @@
package models
// sometimes we need to deal with either a node (which contains a file) or an actual file
type IFileChange interface {
GetHasUnstagedChanges() bool
GetHasStagedChanges() bool
GetIsTracked() bool
GetPath() string
}

View File

@ -15,13 +15,14 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
"github.com/mgutz/str" "github.com/mgutz/str"
) )
// list panel functions // list panel functions
func (gui *Gui) getSelectedFileChangeNode() *models.FileChangeNode { func (gui *Gui) getSelectedFileChangeNode() *filetree.FileChangeNode {
selectedLine := gui.State.Panels.Files.SelectedLineIdx selectedLine := gui.State.Panels.Files.SelectedLineIdx
if selectedLine == -1 { if selectedLine == -1 {
return nil return nil
@ -557,8 +558,8 @@ func (gui *Gui) refreshStateFiles() error {
// nodes until we find one that exists in the new set of nodes, then move the cursor // nodes until we find one that exists in the new set of nodes, then move the cursor
// to that. // to that.
// prevNodes starts from our previously selected node because we don't need to consider anything above that // prevNodes starts from our previously selected node because we don't need to consider anything above that
func (gui *Gui) findNewSelectedIdx(prevNodes []*models.FileChangeNode, currNodes []*models.FileChangeNode) int { func (gui *Gui) findNewSelectedIdx(prevNodes []*filetree.FileChangeNode, currNodes []*filetree.FileChangeNode) int {
getPaths := func(node *models.FileChangeNode) []string { getPaths := func(node *filetree.FileChangeNode) []string {
if node == nil { if node == nil {
return nil return nil
} }

View File

@ -9,10 +9,10 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
) )
func BuildTreeFromFiles(files []*models.File) *models.FileChangeNode { func BuildTreeFromFiles(files []*models.File) *FileChangeNode {
root := &models.FileChangeNode{} root := &FileChangeNode{}
var curr *models.FileChangeNode var curr *FileChangeNode
for _, file := range files { for _, file := range files {
split := strings.Split(file.Name, string(os.PathSeparator)) split := strings.Split(file.Name, string(os.PathSeparator))
curr = root curr = root
@ -33,7 +33,7 @@ func BuildTreeFromFiles(files []*models.File) *models.FileChangeNode {
} }
} }
newChild := &models.FileChangeNode{ newChild := &FileChangeNode{
Path: path, Path: path,
File: setFile, File: setFile,
} }
@ -49,7 +49,7 @@ func BuildTreeFromFiles(files []*models.File) *models.FileChangeNode {
return root return root
} }
func BuildFlatTreeFromFiles(files []*models.File) *models.FileChangeNode { func BuildFlatTreeFromFiles(files []*models.File) *FileChangeNode {
rootAux := BuildTreeFromFiles(files) rootAux := BuildTreeFromFiles(files)
sortedFiles := rootAux.GetLeaves() sortedFiles := rootAux.GetLeaves()
@ -59,5 +59,5 @@ func BuildFlatTreeFromFiles(files []*models.File) *models.FileChangeNode {
return sortedFiles[i].File != nil && sortedFiles[i].File.HasMergeConflicts && !(sortedFiles[j].File != nil && sortedFiles[j].File.HasMergeConflicts) return sortedFiles[i].File != nil && sortedFiles[i].File.HasMergeConflicts && !(sortedFiles[j].File != nil && sortedFiles[j].File.HasMergeConflicts)
}) })
return &models.FileChangeNode{Children: sortedFiles} return &FileChangeNode{Children: sortedFiles}
} }

View File

@ -19,7 +19,7 @@ const NOTHING = " "
type FileChangeManager struct { type FileChangeManager struct {
files []*models.File files []*models.File
tree *models.FileChangeNode tree *FileChangeNode
showTree bool showTree bool
log *logrus.Entry log *logrus.Entry
collapsedPaths map[string]bool collapsedPaths map[string]bool
@ -39,7 +39,7 @@ func (m *FileChangeManager) ToggleShowTree() {
m.SetTree() m.SetTree()
} }
func (m *FileChangeManager) GetItemAtIndex(index int) *models.FileChangeNode { func (m *FileChangeManager) GetItemAtIndex(index int) *FileChangeNode {
// need to traverse the three depth first until we get to the index. // need to traverse the three depth first until we get to the index.
return m.tree.GetNodeAtIndex(index+1, m.collapsedPaths) // ignoring root return m.tree.GetNodeAtIndex(index+1, m.collapsedPaths) // ignoring root
} }
@ -49,7 +49,7 @@ func (m *FileChangeManager) GetIndexForPath(path string) (int, bool) {
return index - 1, found return index - 1, found
} }
func (m *FileChangeManager) GetAllItems() []*models.FileChangeNode { func (m *FileChangeManager) GetAllItems() []*FileChangeNode {
if m.tree == nil { if m.tree == nil {
return nil return nil
} }
@ -83,15 +83,15 @@ func (m *FileChangeManager) Render(diffName string, submoduleConfigs []*models.S
return m.renderAux(m.tree, "", -1, diffName, submoduleConfigs) return m.renderAux(m.tree, "", -1, diffName, submoduleConfigs)
} }
func (m *FileChangeManager) IsCollapsed(s *models.FileChangeNode) bool { func (m *FileChangeManager) IsCollapsed(s *FileChangeNode) bool {
return m.collapsedPaths[s.GetPath()] return m.collapsedPaths[s.GetPath()]
} }
func (m *FileChangeManager) ToggleCollapsed(s *models.FileChangeNode) { func (m *FileChangeManager) ToggleCollapsed(s *FileChangeNode) {
m.collapsedPaths[s.GetPath()] = !m.collapsedPaths[s.GetPath()] m.collapsedPaths[s.GetPath()] = !m.collapsedPaths[s.GetPath()]
} }
func (m *FileChangeManager) renderAux(s *models.FileChangeNode, prefix string, depth int, diffName string, submoduleConfigs []*models.SubmoduleConfig) []string { func (m *FileChangeManager) renderAux(s *FileChangeNode, prefix string, depth int, diffName string, submoduleConfigs []*models.SubmoduleConfig) []string {
isRoot := depth == -1 isRoot := depth == -1
if s == nil { if s == nil {
return []string{} return []string{}

View File

@ -10,7 +10,7 @@ import (
func TestRender(t *testing.T) { func TestRender(t *testing.T) {
scenarios := []struct { scenarios := []struct {
name string name string
root *models.FileChangeNode root *FileChangeNode
collapsedPaths map[string]bool collapsedPaths map[string]bool
expected []string expected []string
}{ }{
@ -21,9 +21,9 @@ func TestRender(t *testing.T) {
}, },
{ {
name: "leaf node", name: "leaf node",
root: &models.FileChangeNode{ root: &FileChangeNode{
Path: "", Path: "",
Children: []*models.FileChangeNode{ Children: []*FileChangeNode{
{File: &models.File{Name: "test", ShortStatus: " M", HasStagedChanges: true}, Path: "test"}, {File: &models.File{Name: "test", ShortStatus: " M", HasStagedChanges: true}, Path: "test"},
}, },
}, },
@ -31,12 +31,12 @@ func TestRender(t *testing.T) {
}, },
{ {
name: "big example", name: "big example",
root: &models.FileChangeNode{ root: &FileChangeNode{
Path: "", Path: "",
Children: []*models.FileChangeNode{ Children: []*FileChangeNode{
{ {
Path: "dir1", Path: "dir1",
Children: []*models.FileChangeNode{ Children: []*FileChangeNode{
{ {
File: &models.File{Name: "dir1/file2", ShortStatus: "M ", HasUnstagedChanges: true}, File: &models.File{Name: "dir1/file2", ShortStatus: "M ", HasUnstagedChanges: true},
Path: "dir1/file2", Path: "dir1/file2",
@ -49,10 +49,10 @@ func TestRender(t *testing.T) {
}, },
{ {
Path: "dir2", Path: "dir2",
Children: []*models.FileChangeNode{ Children: []*FileChangeNode{
{ {
Path: "dir2/dir2", Path: "dir2/dir2",
Children: []*models.FileChangeNode{ Children: []*FileChangeNode{
{ {
File: &models.File{Name: "dir2/dir2/file3", ShortStatus: " M", HasStagedChanges: true}, File: &models.File{Name: "dir2/dir2/file3", ShortStatus: " M", HasStagedChanges: true},
Path: "dir2/dir2/file3", Path: "dir2/dir2/file3",

View File

@ -1,4 +1,4 @@
package models package filetree
import ( import (
"fmt" "fmt"
@ -6,28 +6,30 @@ import (
"path/filepath" "path/filepath"
"sort" "sort"
"strings" "strings"
"github.com/jesseduffield/lazygit/pkg/commands/models"
) )
type FileChangeNode struct { type FileChangeNode struct {
Children []*FileChangeNode Children []*FileChangeNode
File *File File *models.File
Path string // e.g. '/path/to/mydir' Path string // e.g. '/path/to/mydir'
CompressionLevel int // equal to the number of forward slashes you'll see in the path when it's rendered in tree mode CompressionLevel int // equal to the number of forward slashes you'll see in the path when it's rendered in tree mode
} }
func (s *FileChangeNode) GetHasUnstagedChanges() bool { func (s *FileChangeNode) GetHasUnstagedChanges() bool {
return s.AnyFile(func(file *File) bool { return file.HasUnstagedChanges }) return s.AnyFile(func(file *models.File) bool { return file.HasUnstagedChanges })
} }
func (s *FileChangeNode) GetHasStagedChanges() bool { func (s *FileChangeNode) GetHasStagedChanges() bool {
return s.AnyFile(func(file *File) bool { return file.HasStagedChanges }) return s.AnyFile(func(file *models.File) bool { return file.HasStagedChanges })
} }
func (s *FileChangeNode) GetHasInlineMergeConflicts() bool { func (s *FileChangeNode) GetHasInlineMergeConflicts() bool {
return s.AnyFile(func(file *File) bool { return file.HasInlineMergeConflicts }) return s.AnyFile(func(file *models.File) bool { return file.HasInlineMergeConflicts })
} }
func (s *FileChangeNode) AnyFile(test func(file *File) bool) bool { func (s *FileChangeNode) AnyFile(test func(file *models.File) bool) bool {
return s.Any(func(node *FileChangeNode) bool { return s.Any(func(node *FileChangeNode) bool {
return node.IsLeaf() && test(node.File) return node.IsLeaf() && test(node.File)
}) })
@ -39,7 +41,7 @@ func (s *FileChangeNode) Any(test func(node *FileChangeNode) bool) bool {
} }
for _, child := range s.Children { for _, child := range s.Children {
if test(child) { if child.Any(test) {
return true return true
} }
} }
@ -231,7 +233,7 @@ func (s *FileChangeNode) Description() string {
return s.GetPath() return s.GetPath()
} }
func (s *FileChangeNode) ForEachFile(cb func(*File) error) error { func (s *FileChangeNode) ForEachFile(cb func(*models.File) error) error {
if s.File != nil { if s.File != nil {
if err := cb(s.File); err != nil { if err := cb(s.File); err != nil {
return err return err

View File

@ -1,8 +1,9 @@
package models package filetree
import ( import (
"testing" "testing"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -22,13 +23,13 @@ func TestCompress(t *testing.T) {
root: &FileChangeNode{ root: &FileChangeNode{
Path: "", Path: "",
Children: []*FileChangeNode{ Children: []*FileChangeNode{
{File: &File{Name: "test", ShortStatus: " M", HasStagedChanges: true}, Path: "test"}, {File: &models.File{Name: "test", ShortStatus: " M", HasStagedChanges: true}, Path: "test"},
}, },
}, },
expected: &FileChangeNode{ expected: &FileChangeNode{
Path: "", Path: "",
Children: []*FileChangeNode{ Children: []*FileChangeNode{
{File: &File{Name: "test", ShortStatus: " M", HasStagedChanges: true}, Path: "test"}, {File: &models.File{Name: "test", ShortStatus: " M", HasStagedChanges: true}, Path: "test"},
}, },
}, },
}, },
@ -41,7 +42,7 @@ func TestCompress(t *testing.T) {
Path: "dir1", Path: "dir1",
Children: []*FileChangeNode{ Children: []*FileChangeNode{
{ {
File: &File{Name: "file2", ShortStatus: "M ", HasUnstagedChanges: true}, File: &models.File{Name: "file2", ShortStatus: "M ", HasUnstagedChanges: true},
Path: "dir1/file2", Path: "dir1/file2",
}, },
}, },
@ -50,11 +51,11 @@ func TestCompress(t *testing.T) {
Path: "dir2", Path: "dir2",
Children: []*FileChangeNode{ Children: []*FileChangeNode{
{ {
File: &File{Name: "file3", ShortStatus: " M", HasStagedChanges: true}, File: &models.File{Name: "file3", ShortStatus: " M", HasStagedChanges: true},
Path: "dir2/file3", Path: "dir2/file3",
}, },
{ {
File: &File{Name: "file4", ShortStatus: "M ", HasUnstagedChanges: true}, File: &models.File{Name: "file4", ShortStatus: "M ", HasUnstagedChanges: true},
Path: "dir2/file4", Path: "dir2/file4",
}, },
}, },
@ -66,7 +67,7 @@ func TestCompress(t *testing.T) {
Path: "dir3/dir3-1", Path: "dir3/dir3-1",
Children: []*FileChangeNode{ Children: []*FileChangeNode{
{ {
File: &File{Name: "file5", ShortStatus: "M ", HasUnstagedChanges: true}, File: &models.File{Name: "file5", ShortStatus: "M ", HasUnstagedChanges: true},
Path: "dir3/dir3-1/file5", Path: "dir3/dir3-1/file5",
}, },
}, },
@ -74,7 +75,7 @@ func TestCompress(t *testing.T) {
}, },
}, },
{ {
File: &File{Name: "file1", ShortStatus: "M ", HasUnstagedChanges: true}, File: &models.File{Name: "file1", ShortStatus: "M ", HasUnstagedChanges: true},
Path: "file1", Path: "file1",
}, },
}, },
@ -84,29 +85,29 @@ func TestCompress(t *testing.T) {
Children: []*FileChangeNode{ Children: []*FileChangeNode{
{ {
Path: "dir1/file2", Path: "dir1/file2",
File: &File{Name: "file2", ShortStatus: "M ", HasUnstagedChanges: true}, File: &models.File{Name: "file2", ShortStatus: "M ", HasUnstagedChanges: true},
CompressionLevel: 1, CompressionLevel: 1,
}, },
{ {
Path: "dir2", Path: "dir2",
Children: []*FileChangeNode{ Children: []*FileChangeNode{
{ {
File: &File{Name: "file3", ShortStatus: " M", HasStagedChanges: true}, File: &models.File{Name: "file3", ShortStatus: " M", HasStagedChanges: true},
Path: "dir2/file3", Path: "dir2/file3",
}, },
{ {
File: &File{Name: "file4", ShortStatus: "M ", HasUnstagedChanges: true}, File: &models.File{Name: "file4", ShortStatus: "M ", HasUnstagedChanges: true},
Path: "dir2/file4", Path: "dir2/file4",
}, },
}, },
}, },
{ {
Path: "dir3/dir3-1/file5", Path: "dir3/dir3-1/file5",
File: &File{Name: "file5", ShortStatus: "M ", HasUnstagedChanges: true}, File: &models.File{Name: "file5", ShortStatus: "M ", HasUnstagedChanges: true},
CompressionLevel: 2, CompressionLevel: 2,
}, },
{ {
File: &File{Name: "file1", ShortStatus: "M ", HasUnstagedChanges: true}, File: &models.File{Name: "file1", ShortStatus: "M ", HasUnstagedChanges: true},
Path: "file1", Path: "file1",
}, },
}, },