mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-10-06 14:06:32 -04:00
An external diff command rides on the git command as a diff.external config, which means it is fixed when the command is built. That is before the layout pass, so the width the renderer is to lay out for isn't known yet, and the command can't be told about it. Pass it as GIT_EXTERNAL_DIFF from the render instead, where the width is known and where a stdin filter is already handed to git the same way. git ranks the variable exactly as it ranks the config, behind a per-path diff driver from .gitattributes, so a repository that defines one still gets it (verified on git 2.22.5 and 2.55). An empty command means the user wants their own git config to apply, so leave the variable unset for that; git takes it being set at all as an instruction. The renderer command leaves the git arguments, so it also leaves the key that says which diff a render is of. Cycling between two external diff renderers now keeps the view's place, the way cycling between two stdin filters already does. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
172 lines
5.2 KiB
Go
172 lines
5.2 KiB
Go
package git_commands
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
|
"github.com/jesseduffield/lazygit/pkg/config"
|
|
"github.com/jesseduffield/lazygit/pkg/env"
|
|
)
|
|
|
|
// OptionalLocksEnvVar is the name of the environment variable that tells git
|
|
// whether it may take "optional" locks — chiefly the index.lock that `git
|
|
// status` grabs to write back a refreshed stat-cache. We set it to 0 on every
|
|
// git command by default (see NewGitCmdObjBuilder) so our invocations never
|
|
// contend for index.lock, neither with each other (e.g. a main-view `git diff
|
|
// --submodule`, which runs `git status` inside submodules, racing a submodule
|
|
// action) nor with git commands the user runs in a terminal. The one command
|
|
// that opts back in is the foreground files refresh; see FileLoader.gitStatus.
|
|
const OptionalLocksEnvVar = "GIT_OPTIONAL_LOCKS"
|
|
|
|
// ForOtherRepo prepares a command that operates on a repo other than the one
|
|
// we have open — a submodule, or another worktree. GIT_DIR and GIT_WORK_TREE
|
|
// say where our repo is, and every command we run inherits them, so a command
|
|
// pointed at a different repo would be resolved against ours instead: `git -C
|
|
// <submodule> log` would silently log the superproject's commits.
|
|
func ForOtherRepo(cmdObj *oscommands.CmdObj) *oscommands.CmdObj {
|
|
return cmdObj.RemoveEnvVar(env.GitDirEnvVar).RemoveEnvVar(env.GitWorkTreeEnvVar)
|
|
}
|
|
|
|
// convenience struct for building git commands. Especially useful when
|
|
// including conditional args
|
|
type GitCommandBuilder struct {
|
|
// command string
|
|
args []string
|
|
}
|
|
|
|
func NewGitCmd(command string) *GitCommandBuilder {
|
|
return &GitCommandBuilder{args: []string{command}}
|
|
}
|
|
|
|
func (self *GitCommandBuilder) Arg(args ...string) *GitCommandBuilder {
|
|
self.args = append(self.args, args...)
|
|
|
|
return self
|
|
}
|
|
|
|
func (self *GitCommandBuilder) ArgIf(condition bool, ifTrue ...string) *GitCommandBuilder {
|
|
if condition {
|
|
self.Arg(ifTrue...)
|
|
}
|
|
|
|
return self
|
|
}
|
|
|
|
func (self *GitCommandBuilder) ArgIfElse(condition bool, ifTrue string, ifFalse string) *GitCommandBuilder {
|
|
if condition {
|
|
return self.Arg(ifTrue)
|
|
}
|
|
return self.Arg(ifFalse)
|
|
}
|
|
|
|
func (self *GitCommandBuilder) Config(value string) *GitCommandBuilder {
|
|
// config settings come before the command
|
|
self.args = append([]string{"-c", value}, self.args...)
|
|
|
|
return self
|
|
}
|
|
|
|
func (self *GitCommandBuilder) ConfigIf(condition bool, ifTrue string) *GitCommandBuilder {
|
|
if condition {
|
|
self.Config(ifTrue)
|
|
}
|
|
|
|
return self
|
|
}
|
|
|
|
// the -C arg will make git do a `cd` to the directory before doing anything else
|
|
func (self *GitCommandBuilder) Dir(path string) *GitCommandBuilder {
|
|
// repo path comes before the command
|
|
self.args = append([]string{"-C", path}, self.args...)
|
|
|
|
return self
|
|
}
|
|
|
|
func (self *GitCommandBuilder) DirIf(condition bool, path string) *GitCommandBuilder {
|
|
if condition {
|
|
return self.Dir(path)
|
|
}
|
|
|
|
return self
|
|
}
|
|
|
|
// Note, you may prefer to use the Dir method instead of this one
|
|
func (self *GitCommandBuilder) Worktree(path string) *GitCommandBuilder {
|
|
// worktree arg comes before the command
|
|
self.args = append([]string{"--work-tree", path}, self.args...)
|
|
|
|
return self
|
|
}
|
|
|
|
func (self *GitCommandBuilder) WorktreePathIf(condition bool, path string) *GitCommandBuilder {
|
|
if condition {
|
|
return self.Worktree(path)
|
|
}
|
|
|
|
return self
|
|
}
|
|
|
|
// Note, you may prefer to use the Dir method instead of this one
|
|
func (self *GitCommandBuilder) GitDir(path string) *GitCommandBuilder {
|
|
// git dir arg comes before the command
|
|
self.args = append([]string{"--git-dir", path}, self.args...)
|
|
|
|
return self
|
|
}
|
|
|
|
func (self *GitCommandBuilder) GitDirIf(condition bool, path string) *GitCommandBuilder {
|
|
if condition {
|
|
return self.GitDir(path)
|
|
}
|
|
|
|
return self
|
|
}
|
|
|
|
func (self *GitCommandBuilder) AddCommonDiffArgs(diffRendererConfigManager *config.DiffRendererConfigManager, userConfig *config.UserConfig, forUI bool) *GitCommandBuilder {
|
|
contextSize := userConfig.Git.DiffContextSize
|
|
useExtDiff := forUI && diffRendererConfigManager.GetDiffRendererType() == config.DiffRendererType_ExtDiff
|
|
|
|
return self.
|
|
ArgIfElse(useExtDiff, "--ext-diff", "--no-ext-diff").
|
|
Arg(fmt.Sprintf("--unified=%d", contextSize)).
|
|
ArgIf(forUI && userConfig.Git.IgnoreWhitespaceInDiffView, "--ignore-all-space").
|
|
Arg(fmt.Sprintf("--find-renames=%d%%", userConfig.Git.RenameSimilarityThreshold)).
|
|
ArgIf(forUI, diffRendererConfigManager.GetRawGitArgs()...)
|
|
}
|
|
|
|
func (self *GitCommandBuilder) ToArgv() []string {
|
|
return append([]string{"git"}, self.args...)
|
|
}
|
|
|
|
func (self *GitCommandBuilder) ToString() string {
|
|
return strings.Join(self.ToArgv(), " ")
|
|
}
|
|
|
|
// runGitCmdOnPaths runs `git <subcommand> -- <paths...>`, splitting into
|
|
// multiple calls if needed to stay under the OS command-line length limit.
|
|
// Windows CreateProcess has a ~32 KB limit; we use 30 KB as a safe threshold.
|
|
func runGitCmdOnPaths(subcommand string, paths []string, cmd oscommands.ICmdObjBuilder) error {
|
|
const maxArgBytes = 30_000
|
|
|
|
start := 0
|
|
for start < len(paths) {
|
|
end := start
|
|
total := 0
|
|
for end < len(paths) {
|
|
total += len(paths[end]) + 1 // +1 for the separating space
|
|
if total > maxArgBytes && end > start {
|
|
break
|
|
}
|
|
end++
|
|
}
|
|
if err := cmd.New(NewGitCmd(subcommand).Arg("--").
|
|
Arg(paths[start:end]...).
|
|
ToArgv()).Run(); err != nil {
|
|
return err
|
|
}
|
|
start = end
|
|
}
|
|
return nil
|
|
}
|