Skip to content

Commit

Permalink
⚡ Improve retrieving worktree status
Browse files Browse the repository at this point in the history
Large worktrees were causing significant delays in displaying the user
interface. This was due to calculcating the hash of files to determine
the overall status of the worktree.

Go has poor performance with SHA1 hashing. Too many files were
unnecessarily hashed as well. These combinations caused some
repositories to take well over 10 seconds to display the user
interface.

This is a known problem in worktree status and an issue already exists.
go-git/go-git#181

Shelling out to call "git status" allowed for significant performance
increases often in the sub second range. A modified implementation was
used based on: gitleaks/gitleaks#463

The variation tries to use "git status" and if it fails falls back to
the original go-git implementation.
  • Loading branch information
mikelorant committed Feb 7, 2023
1 parent b6175d6 commit 2c9df9a
Showing 1 changed file with 44 additions and 1 deletion.
45 changes: 44 additions & 1 deletion internal/repository/worktree.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package repository

import (
"fmt"
"os/exec"
"strings"

"github.com/go-git/go-git/v5"
)
Expand All @@ -18,11 +20,52 @@ func (r *Repository) Worktree() (Worktree, error) {
return Worktree{}, fmt.Errorf("unable to get worktree: %w", err)
}

s, err := w.Status()
// Performance of internal status was poor. Replace with external call
// which is significantly faster.
// s, err := w.Status()
s, err := status(w)
if err != nil {
return Worktree{}, fmt.Errorf("unable to get status of worktree: %w", err)
}
wt.Status = s

return wt, nil
}

// Alternative method to determine file status. Modified from original
// version which was part of the following pull request.
// https://github.com/zricethezav/gitleaks/pull/463
func status(wt *git.Worktree) (git.Status, error) {
c := exec.Command("git", "status", "--porcelain", "-z")
c.Dir = wt.Filesystem.Root()

out, err := c.Output()
if err != nil {
return wt.Status()
}

lines := strings.Split(string(out), "\000")
status := make(map[string]*git.FileStatus, len(lines))

for _, line := range lines {
if len(line) == 0 {
continue
}

ltrim := strings.TrimLeft(line, " ")

pathStatusCode := strings.SplitN(ltrim, " ", 2)
if len(pathStatusCode) != 2 {
continue
}

statusCode := []byte(pathStatusCode[0])[0]
path := strings.Trim(pathStatusCode[1], " ")

status[path] = &git.FileStatus{
Staging: git.StatusCode(statusCode),
}
}

return status, err
}

0 comments on commit 2c9df9a

Please sign in to comment.