diff-hl: uncommitted change indicators in the Emacs fringe
Emacs package for highlighting uncommitted changes
At a glance
- What is it?
- diff-hl is an Emacs package that marks the lines you have changed in the fringe of the buffer, lets you jump to and revert them one at a time, and works with Git, Mercurial, Bazaar and SVN through Emacs's own vc layer. It is a small package with a long list of honest limitations, from fringe width limits to a conflict with Flycheck that the author documents rather than hides.
- Who is it for?
- Adopt diff-hl if you edit files under version control in Emacs and want to see your uncommitted changes in the buffer without leaving it, and if you are comfortable with Emacs 27.1 or newer and with the vc layer it builds on. Do not adopt it if you need per file staging semantics beyond what the package offers, if you run a graphical setup where Flycheck also owns the fringe, or if your workflow commits through a package that does not fire vc-checkin-hook.
- Can I use it commercially?
- Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
- Is it still maintained?
- Yes. The repository last received commits 22 days ago.
- What is it written in?
- Mainly Emacs Lisp, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A fringe overlay for the working tree
The package is described in a single sentence: diff-hl-mode highlights uncommitted changes on the side of the window, allows you to jump between and revert them selectively. The README also names the feature it implements, source control gutter indicators, which is the term other editors use for the same idea.
What it does not do is replace your version control interface. diff-hl is a buffer-local minor mode that draws a marker next to each line that differs from the reference revision, and it gives you commands to move to the next changed line and to revert one hunk at a time. In buffers controlled by Git it goes a step further and lets you stage and unstage changes.
The backend is Emacs's own vc layer rather than a Git specific implementation, which is the design decision that gives the package its reach. The README states that it is tested with Git, Mercurial, Bazaar and SVN, and that it may work with other VC backends too. Anyone who has used a different VCS in Emacs therefore gets the same indicators without a separate package.
The package is GPL-3.0 licensed, the default branch is master, the repository is not archived and the last push was on 2026-10-01. There are no GitHub releases, so the package is distributed through the usual Emacs channels rather than as a tagged artefact.
Enabling it takes one line and one precondition
The usage section is short. You add the package to your init script and turn the mode on globally:
(global-diff-hl-mode)There is a precondition that is easy to miss and produces a silent no-op when it is not met. You must also ensure that VC is enabled, for example by making sure vc-handled-backends is not nil. Without that, the mode has no backend to query for changes, and you get an editor with no indicators rather than an error.
The documentation strategy is unusual and worth knowing about. The README says that for the usage instructions and the list of commands you should see the Commentary section inside the file. The Commentary is the standard Emacs documentation convention, a comment block at the top of the source that the built in help system can display, and it means the full command reference travels with the code and matches the version you have installed. The consequence for a reader is that the README is deliberately incomplete, and the Commentary is the real manual.
The requirements section is a single line: Emacs 27.1 or newer. There is no dependency on Magit and no requirement for a particular Git version, because the main mode diffs through the vc layer that Emacs already ships with.
Six auxiliary modes for different buffers and workflows
The package ships a set of related modes rather than one mode with many switches, and each of them exists for a concrete reason.
diff-hl-dired-mode provides the same functionality in Dired, so the indicators appear when you are browsing a directory listing rather than editing a file. diff-hl-margin-mode changes the highlighting function to use the margin instead of the fringe, which is the answer for a console Emacs, where there is no fringe at all. The README is specific about the fallback: in a non-graphical terminal the package falls back to using the margins anyway, as long as diff-hl-fallback-to-margin is non-nil and the margin width is non-zero.
diff-hl-amend-mode sets the reference revision to the one before the recent one, which is the situation where you have just committed and want to see the previous commit as the baseline. diff-hl-set-reference-rev sets it to any revision, with the details in its docstring.
diff-hl-flydiff-mode implements highlighting changes on the fly. By default diff-hl-mode only updates the display when the buffer is in a saved state, and flydiff mode changes that to updates on a timer. The trade-off is the one the README names: a different tradeoff between freshness and work done.
diff-hl-show-hunk-mouse-mode makes the fringe and the margin react to mouse clicks to show the corresponding hunk, and the README presents it as the alternative to using diff-hl-show-hunk and its relatives. The source files match this structure one for one, with diff-hl.el, diff-hl-dired.el, diff-hl-margin.el, diff-hl-amend.el, diff-hl-flydiff.el, diff-hl-show-hunk.el, diff-hl-show-hunk-inline.el and diff-hl-show-hunk-posframe.el at the repository root.
The limits the README admits to
The Notes section is unusually candid for a package README, and it is the most useful part of the document.
First, the display rules. Fringe width up to 16 works best, because the package cannot define a bitmap with a width above that number. Frame-local and buffer-local values of line-spacing are not supported. And in the console, where there is no fringe, the navigation and revert commands still work, which is worth knowing since the visible feedback disappears while the functionality does not.
Second, the conflict with other packages. The README says the package conflicts with other modes when they put indicators on the fringe, such as Flycheck, and argues this is rarely a significant problem, since if you are using such a mode you would usually want to fix all errors and warnings before continuing, and then the conflicting indicators go away. The console has the same issue with Flycheck and Flymake, and turning on diff-hl-margin-mode is the recommended way to avoid it even on graphical frames.
Third, the diff algorithm. To use an alternative diff algorithm with Git you add a corresponding argument to vc-git-diff-switches, for example (setq vc-git-diff-switches '("--histogram")). The README explains why the diff.algorithm option does not work, and the reason is specific and useful: vc-git-diff calls git diff-index, so the configuration Git itself reads is bypassed. And diff-hl-flydiff-mode does not support alternative algorithms at all, because it uses the external diff program.
Hooks for Magit, psvn and Tramp
The Integration section covers the case where you commit through something other than Emacs's vc package, and the reason is a missing hook rather than a missing feature. If the package you use to commit changes might not run vc-checkin-hook after commits, you need to either add diff-hl-update to the hook it does run, or advise some function that is called in the buffer after its state has changed.
For psvn, that advice is one line:
(advice-add 'svn-status-update-modeline :after #'diff-hl-update)For Magit, the advice depends on the version. If you are using a version before 2.4.0, it defines magit-revert-buffer-hook, or magit-not-reverted-hook, and the package uses it. When using Magit 2.4 or newer, you add this to your init script instead:
(add-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh)Tramp is the third case and the one with a performance caveat. The package should just work with Tramp, but slow or high latency connections can cause performance problems. If you hit them, the fix is to customise diff-hl-disable-on-remote to t, and the README notes that this changes the behaviour of both turn-on-diff-hl-mode and global-diff-hl-mode.
That last point is a good example of the package's defaults. Nothing is disabled for remote files by default, which is right for the common case and wrong for a slow link.
The Makefile shows how the package is tested
The build file is a plain GNU make file and it tells you the whole development story.
EMACS ?= emacs
SOURCES=diff-hl.el
ARTIFACTS=$(patsubst %.el, %.elc, $(SOURCES))
all: compile test
test:
$(EMACS) -batch -L . -l test/diff-hl-test.el -l test/diff-hl-adjust-test.el -f ert-run-tests-batch-and-exitThe default target runs compile and then test. The test target runs Emacs in batch mode, loads two test files, test/diff-hl-test.el and test/diff-hl-adjust-test.el, and executes them with the ERT test runner. ERT is the test framework built into Emacs, so there is no external test dependency, which is what you want in a package that has to work across a wide range of Emacs configurations.
The compile target byte-compiles every source file, and there is a second target, compile-warn, that runs with -Q and with byte-compile-error-on-warn set to t, so warnings become errors. That is the target to run before a release, and it is separate from the ordinary compile so that a normal build does not fail on a warning.
The clean target removes the compiled .elc artifacts, and EMACS is overridable from the environment, so a test run against a specific Emacs build is a matter of setting the variable.
The repository root also carries a .dir-locals.el, an .elpaignore and a .github/ directory with a CI workflow, and the licence is GPL-3.0 in the LICENSE file. There is no package manifest, no keyword file and no version header, which tells you the package is meant to be installed from a source repository or a package archive rather than from a registry with metadata.
A display overlay versus a version control front end
The honest alternative is not another gutter indicator, it is a different tool for the job.
If you want to manage a repository, stage hunks, see the log and commit, then a full version control front end in Emacs, Magit, is a different approach rather than a competing implementation of the same one. Magit owns the workflow: it presents the status, the diff and the commit, and you act inside it. diff-hl stays out of the way and paints a marker in the buffer of the file you are already editing, and it is deliberately backend agnostic, working through the vc layer so it applies to Git, Mercurial, Bazaar and SVN alike.
The two can be used together, and the README's Magit section is about making them cooperate rather than choosing one. The integration requirement exists because the buffer needs to be told to update after a commit, and Magit's hooks have changed across versions, so the package needs to be told which hook to attach to. In a team that commits through Magit and edits in the buffer, the two approaches complement each other rather than compete.
The trade-off is one of scope. diff-hl deliberately does not provide a status view, a log or a commit interface, and the staging and unstaging it supports is limited to the buffer in front of you. If your workflow needs to move changes between branches, inspect history or manage several files at once, that functionality lives in the other tool, and the indicators are the part that belongs in the editor.
Editorial conclusion
Adopt diff-hl if you edit files under version control in Emacs and want to see your uncommitted changes in the buffer without leaving it, and if you are comfortable with Emacs 27.1 or newer and with the vc layer it builds on. Do not adopt it if you need per file staging semantics beyond what the package offers, if you run a graphical setup where Flycheck also owns the fringe, or if your workflow commits through a package that does not fire vc-checkin-hook. Verify first that vc-handled-backends is not nil, that the reference revision your diff is computed against is the one you expect, and that diff-hl-flydiff-mode is off if you are on a slow Tramp connection.
Frequently asked questions
How do I enable diff-hl in Emacs?
Add (global-diff-hl-mode) to your init script. You must also ensure that VC is enabled, for example by making sure vc-handled-backends is not nil, otherwise the mode has no backend to query. The requirements section states Emacs 27.1 or newer.
Which version control systems does diff-hl work with?
The README states that it is tested with Git, Mercurial, Bazaar and SVN, and that it may work with other VC backends too. The package goes through the Emacs vc layer rather than talking to a specific VCS directly.
Why do my diff-hl indicators not update as I type?
By default diff-hl-mode only updates the display when the buffer is in a saved state. Enabling diff-hl-flydiff-mode changes that so updates happen on a timer, which is the different tradeoff the README describes.
How do I use a different diff algorithm with diff-hl?
Add the argument to vc-git-diff-switches, for example (setq vc-git-diff-switches '("--histogram")). The diff.algorithm option does not work because vc-git-diff calls git diff-index, and diff-hl-flydiff-mode does not support alternative algorithms because it uses the external diff program.
Does diff-hl conflict with Flycheck?
The README notes that the package conflicts with other modes that put indicators on the fringe, such as Flycheck, and that this is rarely a significant problem because the conflicting indicators disappear once the warnings are fixed. Turning on diff-hl-margin-mode avoids the conflict, including on graphical frames.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/dgutov-diff-hl)Was this useful?