skip to content

In Git, what determines which editor opens for a commit message, and how do you change it?

level: middleimportance: nice to knowfreq 28%

answer

  1. Git does not have its own editor
  2. environment first, then configuration
  3. two long-standing Unix variables at the end
  4. one command prints the winner
  5. GIT_EDITOR, core.editor, VISUAL, EDITOR

basics

~20 s

Git picks the first of GIT_EDITOR, the core.editor config value, VISUAL and EDITOR that is set, falling back to a built-in default. Set core.editor globally to change it, and check the result with git var GIT_EDITOR.

solid answer

~50 s

When Git needs a message — `git commit` without `-m`, `git tag -a`, an interactive rebase todo list — it launches an editor chosen by a fixed search order: the `GIT_EDITOR` environment variable, then the `core.editor` configuration value, then `VISUAL`, then `EDITOR`, then a compiled-in fallback. `git var GIT_EDITOR` prints whichever one won, which settles the argument immediately. Set it with `git config --global core.editor "<command>"`; the value is a shell command, so arguments are allowed. The classic failure is a graphical editor that returns as soon as it hands the file to an already-running window: Git sees the command exit, reads the file it wrote, finds it unchanged or empty, and aborts the commit — so the command must include whatever option makes it block until the file is closed. `GIT_EDITOR=true` is a handy scripted override that accepts the prepared message without any interaction.

code

bash · 5 lines
bash
git config --global core.editor "vim"
git var GIT_EDITOR            # prints the editor after the full search order

GIT_EDITOR=true git rebase --continue   # accept the prepared message, no prompt
git --no-pager log -3                   # skip the pager for one command

go deeper

for a junior

Remember that Git launches an external editor and that git config --global core.editor changes it. Know the symptom of an editor that does not wait: the commit aborts complaining about an empty message.

for a middle

Explain the full precedence chain from GIT_EDITOR down to EDITOR, why Git waits for the process to exit and then reads the file, and how git var GIT_EDITOR reports the winner.

for a senior

Show the automation angle: GIT_EDITOR=true to accept a prepared message non-interactively, and recognising a blocked editor or pager as the cause of a hung job on a machine with no terminal.

for a principal

Frame it as delegation: Git defers editors, pagers, shells and credential prompts to the environment, each with a documented chain and an environment override, which is what keeps it scriptable across wildly different developer setups.

## When Git needs an editor at all Several commands hand you a file to fill in: `git commit` with no `-m`, `git commit --amend`, `git tag -a` without `-m`, `git merge` when it wants a merge message, `git rebase -i` for the todo list, and `git config --edit`. In every case Git writes a temporary file, launches an editor on it, waits for the editor to exit, and then reads the file back. That last sequence is the whole mental model, and it explains almost every problem people hit. ## The search order Git picks the editor from the first of these that is set: 1. `GIT_EDITOR` — environment variable, highest priority, ideal for one-off overrides and scripts. 2. `core.editor` — configuration value, subject to the ordinary scope cascade, so a repository can override your global choice. 3. `VISUAL` — a long-standing Unix convention for a full-screen editor. 4. `EDITOR` — the older, more general convention. 5. A compiled-in default when none of the above is set. `git var GIT_EDITOR` prints the resolved result, which is the fastest way to answer "why does *that* keep opening". Because `core.editor` is ordinary configuration, `git config --show-origin --get core.editor` will name the file it came from if the value is unexpected. ## Setting it `git config --global core.editor "<command>"` is the normal form. The value is interpreted as a shell command, so options are allowed and the temporary file's path is appended as the final argument. That appending is worth remembering: your command must be one that accepts a filename at the end. A repository-local `core.editor` is legitimate but rarely what you want — the editor is a property of the human, not the project. ## The blocking problem Many graphical editors, when a window is already open, pass the request to the running instance and exit immediately. Git interprets that exit as "the user finished editing", reads the still-unmodified template, sees no message content, and aborts with a complaint about an empty commit message — while your editor cheerfully displays the file you never got to save. The fix is always the same shape: configure the editor's option that makes the launched process wait until the file is closed. If a command does not offer one, wrap it in a small script that blocks. The symptom is unmistakable once you know it: the commit aborts instantly and the editor window opens a fraction of a second later. ## Scripted and non-interactive use In automation you want the prepared message accepted without a human. `GIT_EDITOR=true git rebase --continue` runs `true` as the "editor": it exits successfully, leaves the file untouched, and Git proceeds with whatever message was already prepared. This is far cleaner than trying to feed keystrokes to a real editor, and because `GIT_EDITOR` sits at the top of the search order it overrides whatever the user has configured. Conversely, if a CI job hangs, an editor waiting for input on a terminal that will never provide it is a prime suspect. ## The related pager setting Output paging follows the same layered idea with its own names: Git chooses `GIT_PAGER`, then `core.pager`, then `PAGER`, then its default. `pager.<command>` turns paging on or off for a specific command — `pager.branch = false` stops short branch listings from opening a pager — and `git --no-pager <command>` disables it for one invocation. Git also sets sensible default options for `less` when the environment does not specify any, which is why colour and short output behave well out of the box. Knowing the pager knobs matters mostly for scripting, where an unexpected pager blocking on a non-terminal is a classic hang. ## What interviewers are really checking This question is a small proxy for whether you understand that Git leans on the surrounding environment rather than reimplementing it: editors, pagers, shells and credential prompts are all delegated, each with a documented precedence chain and an environment-variable escape hatch at the top. Being able to name the chain, and to say how you would *verify* which entry won rather than guessing, is the whole answer.

  • Why does a commit abort immediately with some graphical editors?
    Because the launched command hands the file to an already-running instance and exits right away. Git treats the exit as "editing finished", reads the unmodified template, finds no message, and aborts. Configure whatever option makes the command block until the file is closed.
  • How do you accept the prepared message without any interaction in a script?
    Run the command with `GIT_EDITOR=true`. The `true` program exits successfully without touching the file, so Git proceeds with the message it already prepared. Since `GIT_EDITOR` is first in the search order, it overrides whatever the user configured.
  • What is the equivalent chain for Git's pager?
    `GIT_PAGER`, then `core.pager`, then `PAGER`, then Git's default. `pager.<command>` enables or disables paging per command, and `git --no-pager <command>` turns it off for one invocation — worth knowing because an unexpected pager is a common cause of a hung script.
  • How do you find out which editor Git will actually launch?
    `git var GIT_EDITOR` prints the resolved value after applying the whole search order. If it names something surprising that came from configuration, `git config --show-origin --get core.editor` identifies which file set it.

saying these in an interview costs you the question

  • Says Git has its own built-in editor
  • Thinks core.editor outranks the GIT_EDITOR environment variable
  • Blames Git when a non-blocking graphical editor aborts the commit
  • Confuses the editor chain with the pager chain
  • Guesses at the resolved editor instead of running git var

context