Make the path of a diff's lines absolute once per file

Every query about the lines of the main view, such as moving the
selection to the next change block, finds out which line of which file
each row shows. For every row, this joined the path that the diff
states with the path of the worktree. In a diff of 300,000 lines, this
took more than half of the time of the query.

The lines of a diff come file by file, so remember the last path that
was made absolute and reuse it while the path stays the same. Do the
same in the other places that go through every line of a diff. A query
over that diff now takes about 27 milliseconds instead of 63.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Haller
2026-10-14 08:41:50 +02:00
co-authored by Claude Opus 5.5
parent 185f147db2
commit cbfa22cfbe
4 changed files with 73 additions and 12 deletions
+35 -10
View File
@@ -5,6 +5,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/samber/lo"
)
type DiffLineHelper struct {
@@ -134,10 +135,13 @@ func (self *DiffLineHelper) resolveDiffLines(contents []gocui.DiffLineContent) [
// side-by-side row leads with the deletion whose addition was what got remembered
// under a unified one.
func (self *DiffLineHelper) resolveDiffLineIdentities(contents []gocui.DiffLineContent) [][]types.DiffLineInfo {
toDiffLineInfo := self.diffLineInfoConverter()
identities := make([][]types.DiffLineInfo, len(contents))
for i, parsed := range parseDiffLineIdentities(contents) {
if len(parsed) > 0 {
identities[i] = self.diffLineInfos(parsed)
identities[i] = lo.Map(parsed, func(line parsedDiffLine, _ int) types.DiffLineInfo {
return toDiffLineInfo(line)
})
}
}
return identities
@@ -159,19 +163,40 @@ func (self *DiffLineHelper) diffLineInfos(parsed []parsedDiffLine) []types.DiffL
return infos
}
// diffLineInfoConverter returns diffLineInfo as a function, for the callers that turn
// every line of a diff into an identity (see diffLineInfoConverterIn).
func (self *DiffLineHelper) diffLineInfoConverter() func(parsedDiffLine) types.DiffLineInfo {
return diffLineInfoConverterIn(self.c.Git().RepoPaths.WorktreePath())
}
// diffLineInfoIn is diffLineInfo against a given worktree, for the callers that can't
// ask which repo we are in where they run: a repo switch replaces it, so only the UI
// thread may read it.
func diffLineInfoIn(worktreePath string, parsed parsedDiffLine) types.DiffLineInfo {
path := parsed.Path
if !filepath.IsAbs(path) {
path = filepath.Join(worktreePath, path)
}
return diffLineInfoConverterIn(worktreePath)(parsed)
}
return types.DiffLineInfo{
Path: path,
Type: parsed.Type,
NewLine: parsed.NewLine,
OldLine: parsed.OldLine,
// diffLineInfoConverterIn returns diffLineInfoIn for the given worktree as a function,
// for the callers that turn every line of a diff into an identity. The lines of a diff
// come file by file, so the path of a line is nearly always the path of the line
// before it. The function makes a path absolute only when it differs from the one
// before.
func diffLineInfoConverterIn(worktreePath string) func(parsedDiffLine) types.DiffLineInfo {
path, absolutePath := "", ""
return func(parsed parsedDiffLine) types.DiffLineInfo {
if absolutePath == "" || parsed.Path != path {
path = parsed.Path
absolutePath = parsed.Path
if !filepath.IsAbs(absolutePath) {
absolutePath = filepath.Join(worktreePath, absolutePath)
}
}
return types.DiffLineInfo{
Path: absolutePath,
Type: parsed.Type,
NewLine: parsed.NewLine,
OldLine: parsed.OldLine,
}
}
}
@@ -0,0 +1,34 @@
package helpers
import (
"path/filepath"
"testing"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/stretchr/testify/assert"
)
func TestDiffLineInfoConverterIn(t *testing.T) {
worktreePath := t.TempDir()
absolutePath := filepath.Join(t.TempDir(), "d.txt")
toDiffLineInfo := diffLineInfoConverterIn(worktreePath)
lines := []parsedDiffLine{
{Path: "a.txt", Type: types.DiffLineAdded, NewLine: 1},
{Path: "a.txt", Type: types.DiffLineDeleted, NewLine: 2, OldLine: 1},
{Path: "dir/b.txt", Type: types.DiffLineContext, NewLine: 3},
{Path: absolutePath, Type: types.DiffLineAdded, NewLine: 4},
{Path: "a.txt", Type: types.DiffLineAdded, NewLine: 5},
}
expected := []types.DiffLineInfo{
{Path: filepath.Join(worktreePath, "a.txt"), Type: types.DiffLineAdded, NewLine: 1},
{Path: filepath.Join(worktreePath, "a.txt"), Type: types.DiffLineDeleted, NewLine: 2, OldLine: 1},
{Path: filepath.Join(worktreePath, "dir", "b.txt"), Type: types.DiffLineContext, NewLine: 3},
{Path: absolutePath, Type: types.DiffLineAdded, NewLine: 4},
{Path: filepath.Join(worktreePath, "a.txt"), Type: types.DiffLineAdded, NewLine: 5},
}
for i, line := range lines {
assert.Equal(t, expected[i], toDiffLineInfo(line), "line %d", i)
}
}
@@ -72,12 +72,13 @@ func (self *DiffLineHelper) PlainDiffOfSelection(
}
diffLines := strings.Split(strings.TrimSuffix(plainDiff(relPaths), "\n"), "\n")
toDiffLineInfo := diffLineInfoConverterIn(worktreePath)
runs := map[string][2]int{}
for i, parsed := range parseAllDiffLinesFromBuffer(diffLines) {
if !parsed.ok {
continue
}
info := diffLineInfoIn(worktreePath, parsed.parsed)
info := toDiffLineInfo(parsed.parsed)
selected := selection[info.Path]
if selected == nil || !selected.covers(info) {
continue
@@ -47,13 +47,14 @@ func (self *DiffLineHelper) DiffLinesInBufferRange(view *gocui.View, first int,
func (self *DiffLineHelper) ChangeLineOrdinals(
diff string, infos []types.DiffLineInfo,
) map[string][]int {
toDiffLineInfo := self.diffLineInfoConverter()
ordinals := map[patchLine]int{}
counts := map[string]int{}
for _, parsed := range parseAllDiffLinesFromBuffer(strings.Split(diff, "\n")) {
if !parsed.ok {
continue
}
info := self.diffLineInfo(parsed.parsed)
info := toDiffLineInfo(parsed.parsed)
if !info.IsChange() {
continue
}