Skip to content

diff-so-fancy Without the Pipeline

 2 min A git pager that runs diff-so-fancy inside less via LESSOPEN and degrades to plain less on machines that don’t have it.
Published

Apr 15, 2018

The usual way to wire diff-so-fancy into git is a pipeline: core.pager = diff-so-fancy | less. Mine runs it inside less instead, as an input preprocessor:

[core]
    pager = "LESSOPEN=\"||-$(command -v diff-so-fancy) # %s\" && export LESSOPEN || unset LESSOPEN; exec less -x2"

Three obscure facts compose here, and each one is load-bearing.

First: a bare variable assignment has an exit status, and it is the exit status of its command substitution. If diff-so-fancy is missing, command -v fails, so the assignment fails, the && short-circuits, and unset LESSOPEN runs instead. The value was still assigned—but unset removes it, and less starts with no preprocessor at all. That is the entire degradation path: on a machine without diff-so-fancy this config is exactly less -x2, not a broken pipe.

Second: the ||- prefix. A LESSOPEN beginning with | names an input-pipe preprocessor—less runs the command and pages its output. Doubling the pipe (||) makes the exit status decide the empty-output case: empty output plus nonzero status means use the original input; empty output plus zero status is an intentionally empty replacement. It uses nonempty preprocessor output even if the command later fails. The - extends the treatment to stdin, which is the only input git ever hands its pager. All three characters are doing work.

Third: %s. Less requires the filename token to appear in LESSOPEN and substitutes the real filename at run time. But diff-so-fancy is a filter; it wants stdin, not a filename argument. So the %s is parked behind a #—the shell running the preprocessor treats everything after it as a comment. The token is present, so less is satisfied; it is commented out, so diff-so-fancy never sees it.

The result is one line of configuration that carries its own feature detection and its own fallback. exec less -x2 finishes the job: exec replaces the wrapper shell rather than leaving it resident, and -x2 renders tabs two columns wide, which suits diff output.

The companion setting reuses the whole trick with one flag appended:

[interactive]
    diffFilter = "LESSOPEN=\"||-$(command -v diff-so-fancy) --patch # %s\" && export LESSOPEN || unset LESSOPEN; exec less -x2"

--patch keeps the output apply-able, so git add --patch hunks get the fancy rendering without corrupting what git needs to parse.

The version floor splits across two releases. The - form that extends an input pipe to stdin arrived in less 429; the doubled-pipe exit-status rule arrived in less 451. This exact ||- form therefore needs less 451 or newer. The pager line has lived in my tracked ~/.config/git/config for years; the diffFilter companion joined later. Both ride along to every machine—including the ones that have never heard of diff-so-fancy.