# diff-so-fancy: a Perl pager filter that rewrites git diff output before you read it

> diff-so-fancy is a script that sits between git and your pager and reformats hunks, headers and colour so diffs read like prose instead of patch syntax. It is small, MIT licensed, and the README is honest about what it does not do.

**so-fancy/diff-so-fancy** — Make your diffs human readable for improved code quality and faster defect detection. :tada:

- Repository: https://github.com/so-fancy/diff-so-fancy
- Stars: 18,101 · Forks: 350
- Language: Perl
- License: MIT
- Published: 2026-09-21 · Updated: 2026-09-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/so-fancy-diff-so-fancy

## The problem diff-so-fancy targets: git diff is written for parsers, not people

A unified diff is a machine format that happens to be legible. Every changed line carries a leading + or -, every file starts with a four-line header, and every hunk opens with an @@ marker containing line numbers in a notation most people read past. The README states the goal directly: diff-so-fancy makes your diffs human-readable instead of machine-readable, which the project says helps improve code quality and helps you spot defects faster.

The audience is narrow and specific. This is for people who review patches in a terminal, who run git diff or git show and then scroll, and who find the default output noisy enough that they skim it. It is not a GUI, not a merge tool, and not a code review platform. It is a text filter with opinions about presentation. If you already read diffs comfortably, the value is marginal; if you review a lot of patches, the header and hunk rewriting is the part that pays off.

## How the filter works: stdin to stdout, with git config as its control panel

diff-so-fancy is a Perl script that reads a diff on standard input and writes a reformatted diff on standard output. That is the entire data flow. It has no daemon, no cache and no state between runs, which is why it composes with anything that can produce a unified diff.

The repository layout reflects this. The top level holds the diff-so-fancy script plus a lib/ directory, and the README warns that when you install from a clone, the lib/ directory has to be kept relative to the core script. The Makefile builds a single-file artifact into dist/ using third_party/build_fatpack/build.pl, and package.json points the npm bin entry at dist/diff-so-fancy, so the published package ships the bundled version rather than the loose script.

Behaviour is configured through git config keys rather than command-line flags. The README documents markEmptyLines, changeHunkIndicators, stripLeadingSymbols, useUnicodeRuler, rulerWidth, shortHeaders and semIntegration, all under the diff-so-fancy namespace. changeHunkIndicators simplifies the @@ chunk markers, stripLeadingSymbols removes the leading + or - on each line, and shortHeaders collapses the header into a single line for filename and line number. Because these are git config values, they are global or per-repository settings rather than per-invocation switches, which is convenient for daily use and awkward when you want a one-off plain diff.

## Installing diff-so-fancy and wiring it into git

The README's primary instruction is manual: copy the diff-so-fancy script from the latest GitHub release into your $PATH. For development builds it suggests cloning the repository and placing the script, or a symlink to it, into $PATH while keeping lib/ relative to it. Distribution packages also exist: NPM, Homebrew, Fedora, the Arch extra repo, and a Debian/Ubuntu PPA. The README explicitly says packaging problems should go to those packages' trackers rather than this project.

On macOS with Homebrew the install is a single command:

```bash
brew install diff-so-fancy
```

Then point git at the filter for all diff output. These two lines are copied from the README and are the configuration that matters most:

```bash
git config --global core.pager "diff-so-fancy | less --tabs=4 -RF"
git config --global interactive.diffFilter "diff-so-fancy --patch"
```

The first replaces your pager so every diff is piped through diff-so-fancy before less displays it. The second applies the filter to the patches git shows during interactive staging, so git add -p looks the same as a normal diff. After running both, git diff in any repository should show simplified headers and coloured hunks.

The script is not git-only. The README shows piping plain diff output through it, and it supports recursive mode:

```bash
diff -u file_a file_b | diff-so-fancy
diff --recursive -u /path/folder_a /path/folder_b | diff-so-fancy
```

Note the -u flag in the first example: diff-so-fancy expects unified output, so a plain diff without it will not be reformatted. If you want to try the behaviour before committing to it globally, run the script against a saved diff file and inspect the result first.

## The Perl dependency and other cases where diff-so-fancy is the wrong tool

The first limitation is the runtime. The project is written in Perl, and the primary install path is a script you place on your PATH. The npm package sidesteps this by publishing a fatpacked build, but if you install from a clone you are running Perl and you must keep lib/ beside the script. On systems where Perl is absent or where you cannot add scripts to PATH, the manual route is closed and you are dependent on a distribution package being current.

Second, diff-so-fancy is a line-oriented text filter. It reformats what git already computed; it does not compute a different diff. That means no side-by-side view, no word-level structural analysis, and no syntax awareness. If your complaint with git diff is that it shows the wrong granularity of change, this tool will not fix it, because the underlying diff is unchanged.

Third, the configuration is global by default. The README's examples all use --global, and the options are read from git config rather than flags. Turning the filter off for a single command means overriding core.pager or bypassing the pager entirely, not passing an option. For scripted diff processing, where you want stable machine-readable output, diff-so-fancy is actively the wrong choice: it is designed to remove the symbols and markers that scripts parse.

## diff-so-fancy compared with delta and difftastic

The README lists three alternatives: Delta, Lazygit with diff-so-fancy integration, and difftastic. The meaningful comparison is with the first and third, because they take a different approach rather than a different configuration.

Delta is a diff viewer written in Rust. Where diff-so-fancy is a Perl filter that transforms text on its way to less, delta is a pager-side renderer with its own layout features, including side-by-side output. That distinction matters for anyone searching for a side-by-side view: diff-so-fancy does not offer one, and the README does not suggest it does.

difftastic takes the other route entirely. It parses source files and compares syntax trees, so it reports structural changes rather than line changes. diff-so-fancy never parses your code; it operates on the diff text. If your diffs are mostly prose, configuration or generated files, a structural diff has little to work with, and a line-oriented filter like diff-so-fancy is the more predictable option. If you review code where a moved block should not read as a delete plus an insert, difftastic addresses a problem diff-so-fancy does not attempt.

Lazygit is a different category: a terminal UI that can be configured to use diff-so-fancy as a custom diff renderer, per the integration doc the README links. That is composition, not competition.

## Maintenance, releases and the MIT licence

The repository is not archived, and the last push was on 2026-09-19. Recent releases include v1.4.12 on 2026-08-16, v1.4.10 on 2026-04-09 and v1.4.8 on 2026-03-27. The release cadence visible in that list is modest, which fits a project whose surface area is a config-driven text filter: there is not much to ship between versions. Note that package.json in the repository declares version 1.4.6 while the release list reaches v1.4.12, so the manifest and the releases are not in lockstep.

Upgrade cost is low by design. The script has no compiled component in the manual install path, so replacing the file is the upgrade. The npm and Homebrew routes move with their own release processes, and the README directs packaging complaints to those trackers, which means a stale Homebrew formula is not something this repository will fix for you. If you install from a clone, pulling the next branch and keeping lib/ in place is the whole operation.

The licence is MIT, as stated in the README and in package.json. That is a permissive licence, and it means you can vendor the script into internal tooling. This is not legal advice; check the LICENSE file and your own organisation's policy before redistributing it inside a product.

## Conclusion

Adopt diff-so-fancy if you read plain git diff in a terminal all day and want headers, hunk markers and colour handled for you, and if Perl is already present or you install the fatpacked build. Do not adopt it if you need side-by-side layout, syntax-aware structural diffs, or a tool with no runtime dependency at all; difftastic and delta occupy those spaces. Before wiring it into core.pager, run the script once on a saved diff from your own repository and confirm the lib/ directory sits next to the script if you installed from a clone, then check that git config --global diff-so-fancy.shortHeaders true gives the single-line header you actually want.

## FAQ

### How do I install diff-so-fancy?

The README says to copy the diff-so-fancy script from the latest GitHub release into your $PATH, or to clone the repository and place the script (a symlink works) into $PATH while keeping lib/ relative to it. It is also available from NPM, Homebrew, Fedora, the Arch extra repo, and a Debian/Ubuntu PPA.

### How do I use diff-so-fancy with git?

Set core.pager to "diff-so-fancy | less --tabs=4 -RF" and interactive.diffFilter to "diff-so-fancy --patch" with git config --global. The first routes all diff output through the filter, and the second applies it to patches shown during interactive staging.

### Can diff-so-fancy show diffs side by side?

No. The README describes diff-so-fancy as a filter that makes diffs human-readable, and it lists no side-by-side option; the documented settings are markEmptyLines, changeHunkIndicators, stripLeadingSymbols, useUnicodeRuler, rulerWidth, shortHeaders and semIntegration.

### Does diff-so-fancy work on Windows?

The README does not document a Windows install path. It points to NPM, Homebrew, Fedora, the Arch extra repo and a Debian/Ubuntu PPA, and it says packaging issues should go to those packages' own trackers.

### What is the difference between diff-so-fancy and delta?

diff-so-fancy is a Perl script that reads a diff on stdin and rewrites the text before your pager displays it, while delta is listed in the README as an alternative and is a separate renderer with its own layout features. The README does not compare them in detail.

## Sources

- [Issues](https://github.com/so-fancy/diff-so-fancy/issues)
- [License: MIT](https://github.com/so-fancy/diff-so-fancy/blob/next/LICENSE)
- [README](https://github.com/so-fancy/diff-so-fancy/blob/next/README.md)
- [Releases](https://github.com/so-fancy/diff-so-fancy/releases)
- [so-fancy/diff-so-fancy on GitHub](https://github.com/so-fancy/diff-so-fancy)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/so-fancy-diff-so-fancy
