mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-08-24 10:13:41 -05:00
fixup! Support git's own word diff as a metadata-emitting diff renderer
Probe git for the handshake like the other renderers, not for a record. git announces itself for exactly the formats it describes, so asked with the renderer's own arguments the handshake already answers the question, and on empty input at that. Looking for a record was over-specified and made git the one renderer probed differently for no reason. The two probes that now both stand a diff up from two empty temp files share that setup.
This commit is contained in:
@@ -3,7 +3,6 @@ package git_commands
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
@@ -26,20 +25,17 @@ func NewDiffCommands(gitCommon *GitCommon) *DiffCommands {
|
||||
// swallowed on a real render, escapeInterpreter.dropMetadataIfHandshake.
|
||||
const metadataHandshake = "\x1b]1717"
|
||||
|
||||
// metadataRecordPattern matches a per-line record rather than the handshake: a record
|
||||
// carries fields after the version, so a second ";" follows it. Used where the
|
||||
// handshake alone doesn't answer whether records will actually follow (see
|
||||
// rawGitEmitsMetadata).
|
||||
var metadataRecordPattern = regexp.MustCompile(`\x1b]1717;\d+;`)
|
||||
|
||||
// ProbePagerEmitsDiffMetadata reports whether the configured diff renderer speaks the
|
||||
// diff-line-metadata protocol, by running it and checking its output. It's the focused
|
||||
// main view's signal for whether it can act on the rendered diff or must fall back to
|
||||
// the raw diff (see StagingHelper.DiffMainViewShouldRenderRaw). The verdict is
|
||||
// content-independent, so the caller caches it per renderer.
|
||||
// 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 rendered
|
||||
// diff or must fall back to the raw diff (see
|
||||
// StagingHelper.DiffMainViewShouldRenderRaw). The verdict is content-independent — the
|
||||
// handshake is the renderer's first output regardless of the diff — so the caller caches
|
||||
// it per renderer.
|
||||
//
|
||||
// No PTY is needed: git needs a terminal to decide to invoke a pager, but a renderer
|
||||
// emits its records whenever OSC1717 is set, so we can run it directly.
|
||||
// emits the handshake whenever OSC1717 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 renderer to probe;
|
||||
@@ -57,44 +53,26 @@ func (self *DiffCommands) ProbePagerEmitsDiffMetadata() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// rawGitEmitsMetadata probes git itself, run with the diff renderer's own arguments, on
|
||||
// a synthetic diff of two differing temp files.
|
||||
// rawGitEmitsMetadata probes git itself, run with the diff renderer's own arguments.
|
||||
//
|
||||
// It looks for an actual record where the other probes settle for the handshake. What we
|
||||
// depend on is that the rows we are about to show carry records, and only running git
|
||||
// the way we will run it answers that: git annotates only the formats whose output can't
|
||||
// be read back from its own text, so an installed git that doesn't speak the protocol
|
||||
// and arguments that select no word diff both leave us without records. Looking for a
|
||||
// record covers both, and keeps this independent of how git decides to announce itself.
|
||||
// The arguments are the point: git describes only the formats whose output can't be read
|
||||
// back from its own text, and announces itself for exactly those, so the same git answers
|
||||
// differently depending on what it is asked for — a handshake for --color-words, silence
|
||||
// for a unified diff.
|
||||
func (self *DiffCommands) rawGitEmitsMetadata(rawGitArgs []string) bool {
|
||||
oldFile, err := os.CreateTemp("", "lazygit-probe-old-*")
|
||||
if err != nil {
|
||||
oldPath, newPath, cleanup, ok := probeFiles()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
defer os.Remove(oldFile.Name())
|
||||
fmt.Fprintln(oldFile, "old")
|
||||
oldFile.Close()
|
||||
defer cleanup()
|
||||
|
||||
newFile, err := os.CreateTemp("", "lazygit-probe-new-*")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer os.Remove(newFile.Name())
|
||||
fmt.Fprintln(newFile, "new")
|
||||
newFile.Close()
|
||||
|
||||
cmdObj := self.cmd.New(
|
||||
return self.probeEmitsMetadata(self.cmd.New(
|
||||
NewGitCmd("diff").
|
||||
Arg("--no-index").
|
||||
Arg(rawGitArgs...).
|
||||
Arg(oldFile.Name(), newFile.Name()).
|
||||
Arg(oldPath, newPath).
|
||||
ToArgv(),
|
||||
)
|
||||
cmdObj.AddEnvVars("OSC1717=V1")
|
||||
// git exits non-zero because the two files differ, which is the point; the output
|
||||
// is captured either way.
|
||||
output, _ := cmdObj.RunWithOutput()
|
||||
return metadataRecordPattern.MatchString(output)
|
||||
))
|
||||
}
|
||||
|
||||
// externalDiffEmitsMetadata probes an external diff command, invoking it the way git
|
||||
@@ -102,23 +80,38 @@ func (self *DiffCommands) rawGitEmitsMetadata(rawGitArgs []string) bool {
|
||||
// (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 {
|
||||
oldPath, newPath, cleanup, ok := probeFiles()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
defer os.Remove(oldFile.Name())
|
||||
defer cleanup()
|
||||
|
||||
args := append(str.ToArgv(extDiffCmd),
|
||||
"probe", oldPath, "0000000", "100644", newPath, "0000000", "100644")
|
||||
return self.probeEmitsMetadata(self.cmd.New(args))
|
||||
}
|
||||
|
||||
// probeFiles creates the two empty temp files a probe stands a diff up from, and a
|
||||
// cleanup that removes them. Empty because a probe needs the renderer to announce
|
||||
// itself, not to have anything to render.
|
||||
func probeFiles() (string, string, func(), bool) {
|
||||
oldFile, err := os.CreateTemp("", "lazygit-probe-old-*")
|
||||
if err != nil {
|
||||
return "", "", nil, false
|
||||
}
|
||||
oldFile.Close()
|
||||
|
||||
newFile, err := os.CreateTemp("", "lazygit-probe-new-*")
|
||||
if err != nil {
|
||||
return false
|
||||
os.Remove(oldFile.Name())
|
||||
return "", "", nil, 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))
|
||||
return oldFile.Name(), newFile.Name(), func() {
|
||||
os.Remove(oldFile.Name())
|
||||
os.Remove(newFile.Name())
|
||||
}, true
|
||||
}
|
||||
|
||||
func (self *DiffCommands) probeEmitsMetadata(cmdObj *oscommands.CmdObj) bool {
|
||||
|
||||
Reference in New Issue
Block a user