mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 07:36:27 -04:00
Keep showing files whose conflicts have been resolved
When several files have conflicts, resolving one of them makes it vanish
from the files panel as soon as it is auto-staged, and it only comes back
once the last conflict is resolved and the filter turns off again. By
then it sits among all the other changed files of the merge, so it is
hard to find the ones whose resulting diff you still wanted to check.
So remember which files had conflicts while the conflicted-files filter
is on, and keep showing them once they are resolved. This is the general
solution that 39513d244d called for; that commit only helped for the
case of a single conflicted file.
The consequence is that the selection no longer moves on to the next
conflicted file when one is resolved: it stays on the file you just
resolved, which shows you its diff right away.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c10bc3b697
commit
43b47d16dd
|
|
@ -1436,6 +1436,10 @@ func (self *RefreshHelper) refreshStateFiles(captured capturedFilesState, env re
|
|||
self.c.Contexts().Files.GetView().Subtitle = ""
|
||||
}
|
||||
|
||||
if fileTreeViewModel.GetStatusFilter() == filetree.DisplayConflicted {
|
||||
fileTreeViewModel.RememberConflictedPaths(conflictedPaths)
|
||||
}
|
||||
|
||||
self.c.Model().Submodules = submoduleConfigs
|
||||
self.c.Model().Files = files
|
||||
markWorktreeFiles(files, self.c.Model().Worktrees, env.git.RepoPaths.WorktreePath())
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package filetree
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/generics/set"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
|
|
@ -42,6 +43,7 @@ type IFileTree interface {
|
|||
|
||||
FilterFiles(test func(*models.File) bool) []*models.File
|
||||
SetStatusFilter(filter FileTreeDisplayFilter)
|
||||
RememberConflictedPaths(paths []string)
|
||||
ForceShowUntracked() bool
|
||||
Get(index int) *FileNode
|
||||
GetFile(path string) *models.File
|
||||
|
|
@ -54,25 +56,31 @@ type IFileTree interface {
|
|||
}
|
||||
|
||||
type FileTree struct {
|
||||
getFiles func() []*models.File
|
||||
tree *Node[models.File]
|
||||
showTree bool
|
||||
common *common.Common
|
||||
filter FileTreeDisplayFilter
|
||||
collapsedPaths *CollapsedPaths
|
||||
textFilter string
|
||||
useFuzzySearch bool
|
||||
getFiles func() []*models.File
|
||||
tree *Node[models.File]
|
||||
showTree bool
|
||||
common *common.Common
|
||||
filter FileTreeDisplayFilter
|
||||
// Paths of the files that had conflicts while the current filter has been
|
||||
// active. The DisplayConflicted filter keeps showing them after their
|
||||
// conflicts have been resolved, so that their diffs can be reviewed while
|
||||
// the remaining files are still being worked on.
|
||||
conflictedPaths *set.Set[string]
|
||||
collapsedPaths *CollapsedPaths
|
||||
textFilter string
|
||||
useFuzzySearch bool
|
||||
}
|
||||
|
||||
var _ IFileTree = &FileTree{}
|
||||
|
||||
func NewFileTree(getFiles func() []*models.File, common *common.Common, showTree bool) *FileTree {
|
||||
return &FileTree{
|
||||
getFiles: getFiles,
|
||||
common: common,
|
||||
showTree: showTree,
|
||||
filter: DisplayAll,
|
||||
collapsedPaths: NewCollapsedPaths(),
|
||||
getFiles: getFiles,
|
||||
common: common,
|
||||
showTree: showTree,
|
||||
filter: DisplayAll,
|
||||
conflictedPaths: set.New[string](),
|
||||
collapsedPaths: NewCollapsedPaths(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -100,7 +108,9 @@ func (self *FileTree) getFilesForDisplay() []*models.File {
|
|||
case DisplayUntracked:
|
||||
files = self.FilterFiles(func(file *models.File) bool { return !(file.Tracked || file.HasStagedChanges) })
|
||||
case DisplayConflicted:
|
||||
files = self.FilterFiles(func(file *models.File) bool { return file.HasMergeConflicts })
|
||||
files = self.FilterFiles(func(file *models.File) bool {
|
||||
return file.HasMergeConflicts || self.conflictedPaths.Includes(file.Path)
|
||||
})
|
||||
default:
|
||||
panic(fmt.Sprintf("Unexpected files display filter: %d", self.filter))
|
||||
}
|
||||
|
|
@ -122,9 +132,16 @@ func (self *FileTree) FilterFiles(test func(*models.File) bool) []*models.File {
|
|||
|
||||
func (self *FileTree) SetStatusFilter(filter FileTreeDisplayFilter) {
|
||||
self.filter = filter
|
||||
self.conflictedPaths = set.New[string]()
|
||||
self.SetTree()
|
||||
}
|
||||
|
||||
// RememberConflictedPaths records which files have conflicts right now, so that
|
||||
// the DisplayConflicted filter keeps showing them once they are resolved.
|
||||
func (self *FileTree) RememberConflictedPaths(paths []string) {
|
||||
self.conflictedPaths.Add(paths...)
|
||||
}
|
||||
|
||||
func (self *FileTree) ToggleShowTree() {
|
||||
self.showTree = !self.showTree
|
||||
self.SetTree()
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/jesseduffield/generics/set"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
|
|
@ -12,10 +13,11 @@ import (
|
|||
|
||||
func TestFilterAction(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
filter FileTreeDisplayFilter
|
||||
files []*models.File
|
||||
expected []*models.File
|
||||
name string
|
||||
filter FileTreeDisplayFilter
|
||||
conflictedPaths []string
|
||||
files []*models.File
|
||||
expected []*models.File
|
||||
}{
|
||||
{
|
||||
name: "filter files with unstaged changes",
|
||||
|
|
@ -84,11 +86,29 @@ func TestFilterAction(t *testing.T) {
|
|||
{Path: "file1", ShortStatus: "UU", HasMergeConflicts: true, HasInlineMergeConflicts: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "keep showing conflicted files whose conflicts have been resolved",
|
||||
filter: DisplayConflicted,
|
||||
conflictedPaths: []string{"dir2/dir2/file4", "file1"},
|
||||
files: []*models.File{
|
||||
{Path: "dir2/dir2/file4", ShortStatus: "M ", HasStagedChanges: true},
|
||||
{Path: "dir2/file5", ShortStatus: "M ", HasUnstagedChanges: true},
|
||||
{Path: "file1", ShortStatus: "UU", HasMergeConflicts: true, HasInlineMergeConflicts: true},
|
||||
},
|
||||
expected: []*models.File{
|
||||
{Path: "dir2/dir2/file4", ShortStatus: "M ", HasStagedChanges: true},
|
||||
{Path: "file1", ShortStatus: "UU", HasMergeConflicts: true, HasInlineMergeConflicts: true},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
mngr := &FileTree{getFiles: func() []*models.File { return s.files }, filter: s.filter}
|
||||
mngr := &FileTree{
|
||||
getFiles: func() []*models.File { return s.files },
|
||||
filter: s.filter,
|
||||
conflictedPaths: set.NewFromSlice(s.conflictedPaths),
|
||||
}
|
||||
result := mngr.getFilesForDisplay()
|
||||
assert.EqualValues(t, s.expected, result)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import (
|
|||
)
|
||||
|
||||
var ResolveMultipleFiles = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Ensures that upon resolving conflicts for one file, the next file is selected",
|
||||
Description: "Ensures that a file whose conflicts have been resolved keeps being shown while other files still have conflicts",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
|
|
@ -37,11 +37,16 @@ var ResolveMultipleFiles = NewIntegrationTest(NewIntegrationTestArgs{
|
|||
SelectNextItem().
|
||||
PressPrimaryAction()
|
||||
|
||||
// The resolved file is still shown, and stays selected so that its diff
|
||||
// can be reviewed
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Equals("UU file2").IsSelected(),
|
||||
Equals("▼ /"),
|
||||
Equals(" M file1").IsSelected(),
|
||||
Equals(" UU file2"),
|
||||
).
|
||||
SelectNextItem().
|
||||
PressEnter()
|
||||
|
||||
// coincidentally these files have the same conflict
|
||||
|
|
@ -54,7 +59,14 @@ var ResolveMultipleFiles = NewIntegrationTest(NewIntegrationTestArgs{
|
|||
).
|
||||
PressPrimaryAction()
|
||||
|
||||
t.Views().Files().SelectedLines(Contains("file2"))
|
||||
// Now that all conflicts are resolved, the filter is turned off again
|
||||
t.Views().Files().
|
||||
Lines(
|
||||
Equals("▼ /"),
|
||||
Equals(" M file1"),
|
||||
Equals(" M file2").IsSelected(),
|
||||
Equals(" A file3"),
|
||||
)
|
||||
|
||||
t.Common().ContinueOnConflictsResolved("merge")
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue