mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-11 16:16:28 -04:00
Pager authors reviewing the protocol asked to drop the EMIT_ prefix: the OSC number already names the protocol, so EMIT_ was redundant. Rename the handshake variable to OSC1717_METADATA in the spec and in the two places lazygit advertises it (the pager PTY and the metadata probe).
188 lines
7.3 KiB
Go
188 lines
7.3 KiB
Go
package git_commands
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
|
"github.com/mgutz/str"
|
|
)
|
|
|
|
type DiffCommands struct {
|
|
*GitCommon
|
|
}
|
|
|
|
func NewDiffCommands(gitCommon *GitCommon) *DiffCommands {
|
|
return &DiffCommands{
|
|
GitCommon: gitCommon,
|
|
}
|
|
}
|
|
|
|
// metadataHandshake is the OSC sequence a metadata-aware pager emits, as its first
|
|
// output, to announce that it speaks the diff-line-metadata protocol: a version-only
|
|
// OSC 1717 record (no fields). See ProbePagerEmitsDiffMetadata and, for how it's
|
|
// swallowed on a real render, escapeInterpreter.dropMetadataIfHandshake.
|
|
const metadataHandshake = "\x1b]1717"
|
|
|
|
// ProbePagerEmitsDiffMetadata reports whether the configured pager speaks the
|
|
// diff-line-metadata protocol, by running it on empty input and checking for its
|
|
// handshake. It's the focused main view's signal for whether it can act on the
|
|
// pager's rendered diff or must fall back to the raw diff (see
|
|
// StagingHelper.DiffMainViewShouldRenderRaw). The verdict is content-independent —
|
|
// the handshake is the pager's first output regardless of the diff — so the caller
|
|
// caches it per pager.
|
|
//
|
|
// No PTY is needed: git needs a terminal to decide to invoke a pager, but the pager
|
|
// itself emits the handshake whenever OSC1717_METADATA is set, so we can run it
|
|
// directly with empty input.
|
|
//
|
|
// A git-config external diff driver (useExternalDiffGitConfig) is chosen per file via
|
|
// .gitattributes and a single diff can mix drivers, so there's no one pager to probe;
|
|
// we conservatively report false (the focused main view then always renders raw).
|
|
func (self *DiffCommands) ProbePagerEmitsDiffMetadata() bool {
|
|
if extDiffCmd := self.diffRendererConfigManager.GetExternalDiffCommand(3); extDiffCmd != "" {
|
|
return self.externalDiffEmitsMetadata(extDiffCmd)
|
|
}
|
|
if pagerCmd := self.diffRendererConfigManager.GetStdinFilterCommand(0); pagerCmd != "" {
|
|
return self.probeEmitsMetadata(self.cmd.NewShell(pagerCmd, ""))
|
|
}
|
|
return false
|
|
}
|
|
|
|
// externalDiffEmitsMetadata probes an external diff command, invoking it the way git
|
|
// invokes a diff.external driver — with 7 positional args
|
|
// (path old-file old-hex old-mode new-file new-hex new-mode) — but on two empty temp
|
|
// files, so it emits its handshake without there being a real diff to render.
|
|
func (self *DiffCommands) externalDiffEmitsMetadata(extDiffCmd string) bool {
|
|
oldFile, err := os.CreateTemp("", "lazygit-probe-old-*")
|
|
if err != nil {
|
|
return false
|
|
}
|
|
defer os.Remove(oldFile.Name())
|
|
oldFile.Close()
|
|
|
|
newFile, err := os.CreateTemp("", "lazygit-probe-new-*")
|
|
if err != nil {
|
|
return false
|
|
}
|
|
defer os.Remove(newFile.Name())
|
|
newFile.Close()
|
|
|
|
args := append(str.ToArgv(extDiffCmd),
|
|
"probe", oldFile.Name(), "0000000", "100644", newFile.Name(), "0000000", "100644")
|
|
return self.probeEmitsMetadata(self.cmd.New(args))
|
|
}
|
|
|
|
func (self *DiffCommands) probeEmitsMetadata(cmdObj *oscommands.CmdObj) bool {
|
|
cmdObj.AddEnvVars("OSC1717_METADATA=V1")
|
|
// The pager may exit non-zero on the synthetic input; we only care about whether
|
|
// it emitted the handshake first, and the output is captured either way.
|
|
output, _ := cmdObj.RunWithOutput()
|
|
return strings.Contains(output, metadataHandshake)
|
|
}
|
|
|
|
// This is for generating diffs to be shown in the UI (e.g. rendering a range
|
|
// diff to the main view). It uses a custom diff renderer if one is configured, unless
|
|
// ignoreExternalDiff is set (the focused main view's raw-diff fallback; keeps the
|
|
// colour, unlike a plain diff).
|
|
func (self *DiffCommands) DiffCmdObj(diffArgs []string, ignoreExternalDiff bool) *oscommands.CmdObj {
|
|
return self.cmd.New(
|
|
NewGitCmd("diff").
|
|
Config("diff.noprefix=false").
|
|
AddCommonDiffArgs(self.diffRendererConfigManager, self.UserConfig(), !ignoreExternalDiff).
|
|
Arg("--submodule").
|
|
Arg(fmt.Sprintf("--color=%s", self.diffRendererConfigManager.GetColorArg())).
|
|
Arg(diffArgs...).
|
|
Dir(self.repoPaths.worktreePath).
|
|
ToArgv(),
|
|
)
|
|
}
|
|
|
|
// CustomPatchDiffCmdObj builds the command that renders the custom patch shown in the
|
|
// secondary pane: a `git diff --no-index` of the two file trees PatchCommands materialized
|
|
// under dir (a/ = before, b/ = after; see WriteCustomPatchDiffTrees). It uses the same pager
|
|
// wiring as DiffCmdObj so the patch renders exactly like any other diff — through a stdin
|
|
// pager, an external diff tool, or (when ignoreExternalDiff is set, the focused main view's
|
|
// raw-diff fallback) git's own colour. --no-prefix is used because the a/ and b/ tree names
|
|
// already stand in for git's conventional a//b/ path prefixes, so the diff's paths come out
|
|
// as the real repo-relative paths.
|
|
func (self *DiffCommands) CustomPatchDiffCmdObj(dir string, ignoreExternalDiff bool) *oscommands.CmdObj {
|
|
return self.cmd.New(
|
|
NewGitCmd("diff").
|
|
AddCommonDiffArgs(self.diffRendererConfigManager, self.UserConfig(), !ignoreExternalDiff).
|
|
Arg("--no-index").
|
|
Arg("--no-prefix").
|
|
Arg(fmt.Sprintf("--color=%s", self.diffRendererConfigManager.GetColorArg())).
|
|
Arg("a", "b").
|
|
Dir(dir).
|
|
ToArgv(),
|
|
)
|
|
}
|
|
|
|
// This is a basic generic diff command that can be used for any diff operation
|
|
// (e.g. copying a diff to the clipboard). It will not use a custom diff renderer,
|
|
// and does not use user configs such as ignore whitespace.
|
|
// If you want to diff specific refs (one or two), you need to add them yourself
|
|
// in additionalArgs; it is recommended to also pass `--` after that. If you
|
|
// want to restrict the diff to specific paths, pass them in additionalArgs
|
|
// after the `--`.
|
|
func (self *DiffCommands) GetDiff(staged bool, additionalArgs ...string) (string, error) {
|
|
return self.cmd.New(
|
|
NewGitCmd("diff").
|
|
Config("diff.noprefix=false").
|
|
Arg("--no-ext-diff", "--no-color").
|
|
ArgIf(staged, "--staged").
|
|
Dir(self.repoPaths.worktreePath).
|
|
Arg(additionalArgs...).
|
|
ToArgv(),
|
|
).DontLog().RunWithOutput()
|
|
}
|
|
|
|
type DiffToolCmdOptions struct {
|
|
// The path to show a diff for. Pass "." for the entire repo.
|
|
Filepath string
|
|
|
|
// The commit against which to show the diff. Leave empty to show a diff of
|
|
// the working copy.
|
|
FromCommit string
|
|
|
|
// The commit to diff against FromCommit. Leave empty to diff the working
|
|
// copy against FromCommit. Leave both FromCommit and ToCommit empty to show
|
|
// the diff of the unstaged working copy changes against the index if Staged
|
|
// is false, or the staged changes against HEAD if Staged is true.
|
|
ToCommit string
|
|
|
|
// Whether to reverse the left and right sides of the diff.
|
|
Reverse bool
|
|
|
|
// Whether the given Filepath is a directory. We'll pass --dir-diff to
|
|
// git-difftool in that case.
|
|
IsDirectory bool
|
|
|
|
// Whether to show the staged or the unstaged changes. Must be false if both
|
|
// FromCommit and ToCommit are non-empty.
|
|
Staged bool
|
|
}
|
|
|
|
func (self *DiffCommands) OpenDiffToolCmdObj(opts DiffToolCmdOptions) *oscommands.CmdObj {
|
|
return self.cmd.New(NewGitCmd("difftool").
|
|
Arg("--no-prompt").
|
|
ArgIf(opts.IsDirectory, "--dir-diff").
|
|
ArgIf(opts.Staged, "--cached").
|
|
ArgIf(opts.FromCommit != "", opts.FromCommit).
|
|
ArgIf(opts.ToCommit != "", opts.ToCommit).
|
|
ArgIf(opts.Reverse, "-R").
|
|
Arg("--", opts.Filepath).
|
|
ToArgv())
|
|
}
|
|
|
|
func (self *DiffCommands) DiffIndexCmdObj(diffArgs ...string) *oscommands.CmdObj {
|
|
return self.cmd.New(
|
|
NewGitCmd("diff-index").
|
|
Config("diff.noprefix=false").
|
|
Arg("--submodule", "--no-ext-diff", "--no-color", "--patch").
|
|
Arg(diffArgs...).ToArgv(),
|
|
)
|
|
}
|