Skip to content

Commit 6266e19

Browse files
committed
Add support for external diff commands (e.g. difftastic)
1 parent c67aa49 commit 6266e19

File tree

7 files changed

+77
-22
lines changed

7 files changed

+77
-22
lines changed

docs/Custom_Pagers.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,25 @@ git:
6262
```
6363

6464
If you set `useConfig: true`, lazygit will use whatever pager is specified in `$GIT_PAGER`, `$PAGER`, or your *git config*. If the pager ends with something like ` | less` we will strip that part out, because less doesn't play nice with our rendering approach. If the custom pager uses less under the hood, that will also break rendering (hence the `--paging=never` flag for the `delta` pager).
65+
66+
## Using external diff commands
67+
68+
Some diff tools can't work as a simple pager like the ones above do, because they need access to the entire diff, so just post-processing git's diff is not enough for them. The most notable example is probably [difftastic](https://difftastic.wilfred.me.uk).
69+
70+
These can be used in lazygit by using the `externalDiffCommand` config; in the case of difftastic, that could be
71+
72+
```yaml
73+
git:
74+
paging:
75+
externalDiffCommand: difft --color=always
76+
```
77+
78+
The `colorArg`, `pager`, and `useConfig` options are not used in this case.
79+
80+
You can add whatever extra arguments you prefer for your difftool; for instance
81+
82+
```yaml
83+
git:
84+
paging:
85+
externalDiffCommand: difft --color=always --display=inline --syntax-highlight=off
86+
```

pkg/commands/git_commands/commit.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,9 +199,11 @@ func (self *CommitCommands) AmendHeadCmdObj() oscommands.ICmdObj {
199199
func (self *CommitCommands) ShowCmdObj(sha string, filterPath string, ignoreWhitespace bool) oscommands.ICmdObj {
200200
contextSize := self.UserConfig.Git.DiffContextSize
201201

202+
extDiffCmd := self.UserConfig.Git.Paging.ExternalDiffCommand
202203
cmdArgs := NewGitCmd("show").
204+
ConfigIf(extDiffCmd != "", "diff.external="+extDiffCmd).
205+
ArgIfElse(extDiffCmd != "", "--ext-diff", "--no-ext-diff").
203206
Arg("--submodule").
204-
Arg("--no-ext-diff").
205207
Arg("--color="+self.UserConfig.Git.Paging.ColorArg).
206208
Arg(fmt.Sprintf("--unified=%d", contextSize)).
207209
Arg("--stat").

pkg/commands/git_commands/commit_test.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ func TestCommitShowCmdObj(t *testing.T) {
186186
filterPath string
187187
contextSize int
188188
ignoreWhitespace bool
189+
extDiffCmd string
189190
expected []string
190191
}
191192

@@ -195,28 +196,40 @@ func TestCommitShowCmdObj(t *testing.T) {
195196
filterPath: "",
196197
contextSize: 3,
197198
ignoreWhitespace: false,
198-
expected: []string{"show", "--submodule", "--no-ext-diff", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890"},
199+
extDiffCmd: "",
200+
expected: []string{"show", "--no-ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890"},
199201
},
200202
{
201203
testName: "Default case with filter path",
202204
filterPath: "file.txt",
203205
contextSize: 3,
204206
ignoreWhitespace: false,
205-
expected: []string{"show", "--submodule", "--no-ext-diff", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890", "--", "file.txt"},
207+
extDiffCmd: "",
208+
expected: []string{"show", "--no-ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890", "--", "file.txt"},
206209
},
207210
{
208211
testName: "Show diff with custom context size",
209212
filterPath: "",
210213
contextSize: 77,
211214
ignoreWhitespace: false,
212-
expected: []string{"show", "--submodule", "--no-ext-diff", "--color=always", "--unified=77", "--stat", "--decorate", "-p", "1234567890"},
215+
extDiffCmd: "",
216+
expected: []string{"show", "--no-ext-diff", "--submodule", "--color=always", "--unified=77", "--stat", "--decorate", "-p", "1234567890"},
213217
},
214218
{
215219
testName: "Show diff, ignoring whitespace",
216220
filterPath: "",
217221
contextSize: 77,
218222
ignoreWhitespace: true,
219-
expected: []string{"show", "--submodule", "--no-ext-diff", "--color=always", "--unified=77", "--stat", "--decorate", "-p", "1234567890", "--ignore-all-space"},
223+
extDiffCmd: "",
224+
expected: []string{"show", "--no-ext-diff", "--submodule", "--color=always", "--unified=77", "--stat", "--decorate", "-p", "1234567890", "--ignore-all-space"},
225+
},
226+
{
227+
testName: "Show diff with external diff command",
228+
filterPath: "",
229+
contextSize: 3,
230+
ignoreWhitespace: false,
231+
extDiffCmd: "difft --color=always",
232+
expected: []string{"-c", "diff.external=difft --color=always", "show", "--ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890"},
220233
},
221234
}
222235

@@ -225,6 +238,7 @@ func TestCommitShowCmdObj(t *testing.T) {
225238
t.Run(s.testName, func(t *testing.T) {
226239
userConfig := config.GetDefaultConfig()
227240
userConfig.Git.DiffContextSize = s.contextSize
241+
userConfig.Git.Paging.ExternalDiffCommand = s.extDiffCmd
228242

229243
runner := oscommands.NewFakeRunner(t).ExpectGitArgs(s.expected, "", nil)
230244
instance := buildCommitCommands(commonDeps{userConfig: userConfig, runner: runner})

pkg/commands/git_commands/git_command_builder.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ func (self *GitCommandBuilder) Config(value string) *GitCommandBuilder {
4444
return self
4545
}
4646

47+
func (self *GitCommandBuilder) ConfigIf(condition bool, ifTrue string) *GitCommandBuilder {
48+
if condition {
49+
self.Config(ifTrue)
50+
}
51+
52+
return self
53+
}
54+
4755
// the -C arg will make git do a `cd` to the directory before doing anything else
4856
func (self *GitCommandBuilder) Dir(path string) *GitCommandBuilder {
4957
// repo path comes before the command

pkg/commands/git_commands/working_tree.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,10 +243,13 @@ func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain
243243
contextSize := self.UserConfig.Git.DiffContextSize
244244
prevPath := node.GetPreviousPath()
245245
noIndex := !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached && node.GetIsFile()
246+
extDiffCmd := self.UserConfig.Git.Paging.ExternalDiffCommand
247+
useExtDiff := extDiffCmd != "" && !plain
246248

247249
cmdArgs := NewGitCmd("diff").
250+
ConfigIf(useExtDiff, "diff.external="+extDiffCmd).
251+
ArgIfElse(useExtDiff, "--ext-diff", "--no-ext-diff").
248252
Arg("--submodule").
249-
Arg("--no-ext-diff").
250253
Arg(fmt.Sprintf("--unified=%d", contextSize)).
251254
Arg(fmt.Sprintf("--color=%s", colorArg)).
252255
ArgIf(ignoreWhitespace, "--ignore-all-space").
@@ -279,9 +282,13 @@ func (self *WorkingTreeCommands) ShowFileDiffCmdObj(from string, to string, reve
279282
colorArg = "never"
280283
}
281284

285+
extDiffCmd := self.UserConfig.Git.Paging.ExternalDiffCommand
286+
useExtDiff := extDiffCmd != "" && !plain
287+
282288
cmdArgs := NewGitCmd("diff").
289+
ConfigIf(useExtDiff, "diff.external="+extDiffCmd).
290+
ArgIfElse(useExtDiff, "--ext-diff", "--no-ext-diff").
283291
Arg("--submodule").
284-
Arg("--no-ext-diff").
285292
Arg(fmt.Sprintf("--unified=%d", contextSize)).
286293
Arg("--no-renames").
287294
Arg(fmt.Sprintf("--color=%s", colorArg)).

pkg/commands/git_commands/working_tree_test.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ func TestWorkingTreeDiff(t *testing.T) {
231231
ignoreWhitespace: false,
232232
contextSize: 3,
233233
runner: oscommands.NewFakeRunner(t).
234-
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=always", "--", "test.txt"}, expectedResult, nil),
234+
ExpectGitArgs([]string{"diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--", "test.txt"}, expectedResult, nil),
235235
},
236236
{
237237
testName: "cached",
@@ -245,7 +245,7 @@ func TestWorkingTreeDiff(t *testing.T) {
245245
ignoreWhitespace: false,
246246
contextSize: 3,
247247
runner: oscommands.NewFakeRunner(t).
248-
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=always", "--cached", "--", "test.txt"}, expectedResult, nil),
248+
ExpectGitArgs([]string{"diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--cached", "--", "test.txt"}, expectedResult, nil),
249249
},
250250
{
251251
testName: "plain",
@@ -259,7 +259,7 @@ func TestWorkingTreeDiff(t *testing.T) {
259259
ignoreWhitespace: false,
260260
contextSize: 3,
261261
runner: oscommands.NewFakeRunner(t).
262-
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=never", "--", "test.txt"}, expectedResult, nil),
262+
ExpectGitArgs([]string{"diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=never", "--", "test.txt"}, expectedResult, nil),
263263
},
264264
{
265265
testName: "File not tracked and file has no staged changes",
@@ -273,7 +273,7 @@ func TestWorkingTreeDiff(t *testing.T) {
273273
ignoreWhitespace: false,
274274
contextSize: 3,
275275
runner: oscommands.NewFakeRunner(t).
276-
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=always", "--no-index", "--", "/dev/null", "test.txt"}, expectedResult, nil),
276+
ExpectGitArgs([]string{"diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--no-index", "--", "/dev/null", "test.txt"}, expectedResult, nil),
277277
},
278278
{
279279
testName: "Default case (ignore whitespace)",
@@ -287,7 +287,7 @@ func TestWorkingTreeDiff(t *testing.T) {
287287
ignoreWhitespace: true,
288288
contextSize: 3,
289289
runner: oscommands.NewFakeRunner(t).
290-
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=always", "--ignore-all-space", "--", "test.txt"}, expectedResult, nil),
290+
ExpectGitArgs([]string{"diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--ignore-all-space", "--", "test.txt"}, expectedResult, nil),
291291
},
292292
{
293293
testName: "Show diff with custom context size",
@@ -301,7 +301,7 @@ func TestWorkingTreeDiff(t *testing.T) {
301301
ignoreWhitespace: false,
302302
contextSize: 17,
303303
runner: oscommands.NewFakeRunner(t).
304-
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=17", "--color=always", "--", "test.txt"}, expectedResult, nil),
304+
ExpectGitArgs([]string{"diff", "--no-ext-diff", "--submodule", "--unified=17", "--color=always", "--", "test.txt"}, expectedResult, nil),
305305
},
306306
}
307307

@@ -343,7 +343,7 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
343343
ignoreWhitespace: false,
344344
contextSize: 3,
345345
runner: oscommands.NewFakeRunner(t).
346-
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--no-renames", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
346+
ExpectGitArgs([]string{"diff", "--no-ext-diff", "--submodule", "--unified=3", "--no-renames", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
347347
},
348348
{
349349
testName: "Show diff with custom context size",
@@ -354,7 +354,7 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
354354
ignoreWhitespace: false,
355355
contextSize: 123,
356356
runner: oscommands.NewFakeRunner(t).
357-
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=123", "--no-renames", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
357+
ExpectGitArgs([]string{"diff", "--no-ext-diff", "--submodule", "--unified=123", "--no-renames", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
358358
},
359359
{
360360
testName: "Default case (ignore whitespace)",
@@ -365,7 +365,7 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
365365
ignoreWhitespace: true,
366366
contextSize: 3,
367367
runner: oscommands.NewFakeRunner(t).
368-
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--no-renames", "--color=always", "1234567890", "0987654321", "--ignore-all-space", "--", "test.txt"}, expectedResult, nil),
368+
ExpectGitArgs([]string{"diff", "--no-ext-diff", "--submodule", "--unified=3", "--no-renames", "--color=always", "1234567890", "0987654321", "--ignore-all-space", "--", "test.txt"}, expectedResult, nil),
369369
},
370370
}
371371

pkg/config/user_config.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,10 @@ type GitConfig struct {
101101
}
102102

103103
type PagingConfig struct {
104-
ColorArg string `yaml:"colorArg"`
105-
Pager string `yaml:"pager"`
106-
UseConfig bool `yaml:"useConfig"`
104+
ColorArg string `yaml:"colorArg"`
105+
Pager string `yaml:"pager"`
106+
UseConfig bool `yaml:"useConfig"`
107+
ExternalDiffCommand string `yaml:"externalDiffCommand"`
107108
}
108109

109110
type CommitConfig struct {
@@ -469,9 +470,10 @@ func GetDefaultConfig() *UserConfig {
469470
},
470471
Git: GitConfig{
471472
Paging: PagingConfig{
472-
ColorArg: "always",
473-
Pager: "",
474-
UseConfig: false,
473+
ColorArg: "always",
474+
Pager: "",
475+
UseConfig: false,
476+
ExternalDiffCommand: "",
475477
},
476478
Commit: CommitConfig{
477479
SignOff: false,

0 commit comments

Comments
 (0)