mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 15:46:26 -04:00
Git limits its tree diff by the pathspec before it looks for renames, so a directory only ever gets one end of a rename whose other end is outside it. Nothing is left to pair up, and the file turns into an addition or a deletion that the commit doesn't contain. Pass the other end along with the directory. This is bounded by the number of renames that cross the directory's boundary, so it costs nothing at all for the vast majority of commits.
72 lines
2.3 KiB
Go
72 lines
2.3 KiB
Go
package controllers
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
|
|
)
|
|
|
|
// Both models.File and models.CommitFile satisfy this. Names returns the file's
|
|
// path, plus the path it was renamed from if it is a rename.
|
|
type fileWithNames[T any] interface {
|
|
*T
|
|
GetPath() string
|
|
GetPreviousPath() string
|
|
Names() []string
|
|
}
|
|
|
|
// pathsForDiff returns the paths to limit a diff command to for showing the
|
|
// changes of the given node. root is the root of the tree that the node belongs
|
|
// to, and isFiltering says whether that tree is reduced to the files matching a
|
|
// text filter.
|
|
func pathsForDiff[T any, PT fileWithNames[T]](node *filetree.Node[T], root *filetree.Node[T], isFiltering bool) []string {
|
|
if file := node.GetFile(); file != nil {
|
|
return PT(file).Names()
|
|
}
|
|
|
|
dir := node.GetPath()
|
|
|
|
if isFiltering {
|
|
// Passing the directory would bring back the files that the filter hides,
|
|
// so we spell out the ones it leaves.
|
|
var paths []string
|
|
forEachFileInDir[T, PT](root, dir, func(file PT) {
|
|
paths = append(paths, file.Names()...)
|
|
})
|
|
return paths
|
|
}
|
|
|
|
// The directory covers everything below it, but git only pairs up the two
|
|
// ends of a rename if both are in the pathspec, and one end can well be
|
|
// outside the directory. Without that end we would get an addition or a
|
|
// deletion where the diff has a rename.
|
|
paths := []string{dir}
|
|
forEachFileInDir[T, PT](root, dir, func(file PT) {
|
|
if path := file.GetPath(); !isInDir(path, dir) {
|
|
paths = append(paths, path)
|
|
}
|
|
if previousPath := file.GetPreviousPath(); previousPath != "" && !isInDir(previousPath, dir) {
|
|
paths = append(paths, previousPath)
|
|
}
|
|
})
|
|
return paths
|
|
}
|
|
|
|
// forEachFileInDir calls cb for each file in the tree that the given directory
|
|
// contains, either at its current or at its previous path.
|
|
func forEachFileInDir[T any, PT fileWithNames[T]](root *filetree.Node[T], dir string, cb func(PT)) {
|
|
_ = root.ForEachFile(func(f *T) error {
|
|
file := PT(f)
|
|
previousPath := file.GetPreviousPath()
|
|
if isInDir(file.GetPath(), dir) || (previousPath != "" && isInDir(previousPath, dir)) {
|
|
cb(file)
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func isInDir(path string, dir string) bool {
|
|
// "." is the root item, which contains every file
|
|
return dir == "." || strings.HasPrefix(path, dir+"/")
|
|
}
|