Git config: the few settings worth setting on a new Mac

A new Mac has Git the moment the Command Line Tools are installed, and it has almost nothing configured. The first commit fails with a request for a name and an email address, that gets fixed in thirty seconds, and then nothing forces the issue again. Everything else stays at its default for years, including the defaults that produce the same small argument every few weeks.

The reference documentation for git config runs to hundreds of thousands of words and lists close to a thousand variables. That is the right shape for a reference and the wrong shape for a decision. What follows is the short list: the settings that change behaviour you will actually meet, what each one does instead of the default, and how to check afterwards that the value landed where it was meant to.

Which file wins, and how to find out

Before setting anything it is worth knowing the order, because most confusion about git config is really confusion about scope. The manual names five scopes. system is $(prefix)/etc/gitconfig. global is $XDG_CONFIG_HOME/git/config or ~/.gitconfig. local is the repository's own .git/config. worktree is .git/config.worktree. command covers the -c option and the GIT_CONFIG_* environment variables.

Each of the first four has a matching flag, so git config --global writes to your home directory and git config --local, which is the default, writes to the repository. More specific scopes override less specific ones, which means a repository setting quietly beats the one in your home directory. That is usually what you want and occasionally the reason a setting appears to have no effect.

Newer Git versions give git config explicit subcommands, which are easier to read than the older positional forms:

git config list --show-origin --show-scope
git config get user.email
git config set --global pull.rebase true
git config unset --global pull.rebase
git config edit --global

--show-origin and --show-scope together are the answer to almost every question about why a setting is not behaving. The manual describes the first as augmenting the output with the origin type and the actual file path, and the second as augmenting it with the scope. Run the listing, find the variable, and the file that set it is on the same line. There is nothing to guess.

One habit worth forming early: prefer --global for anything about how you work, and --local only for facts about one repository, such as a different email address on a client project. Settings written into a repository travel nowhere, because .git/config is not committed, so a per-repository setting has to be recreated on every clone.

The two Git will not start without

user.name and user.email go into every commit you make, permanently, and Git refuses to commit until it can determine them.

git config set --global user.name "Your Name"
git config set --global user.email "[email protected]"

The trap is that Git will sometimes guess rather than refuse, deriving a value from the machine's hostname and account, which produces commits attributed to an address that does not exist. user.useConfigOnly closes that door. The manual describes it as instructing Git to avoid guessing defaults for user.email and user.name and to take the values only from configuration, prompting you to set one before making commits in a newly cloned repository. It defaults to false.

git config set --global user.useConfigOnly true

Set alongside a global name but with the email left to be decided per repository, this turns a silent mistake into a question asked once per clone. On a machine that will see both employer and personal repositories, that question is worth being asked.

The settings that only matter because this is a Mac

Four variables exist largely for this platform, and three of them are already handled for you.

core.precomposeUnicode is documented as used only by the macOS implementation of Git. When true, it reverses the Unicode decomposition that macOS applies to filenames. Without it, a filename containing a Japanese voiced character or an accented Latin letter can be recorded one way on a Mac and a different way on Linux, and the same file appears twice in a repository shared between them. Recent installations set this to true when the repository is created, and it is worth confirming rather than assuming.

core.ignoreCase records whether the filesystem treats README.md and readme.md as one file. APFS is case-insensitive by default, so the answer on a stock Mac is yes, and the manual notes Git checks the filesystem and sets the variable appropriately when the repository is created. Changing it by hand is explicitly discouraged, because the manual warns that modifying the value may result in unexpected behaviour.

credential.helper is where authentication stops being a recurring interruption. The Command Line Tools ship git-credential-osxkeychain alongside Git itself, so the helper is present without installing anything, and pointing Git at it stores the token in the login keychain instead of prompting.

git config set --global credential.helper osxkeychain

The fourth is newer and worth knowing about on a large repository. core.fsmonitor set to true enables Git's built-in filesystem monitor daemon, which the manual describes as speeding up commands that refresh the index, such as git status, in a working directory with many files. The same passage notes the built-in monitor is available only on a limited set of platforms, and that this currently includes Windows and macOS. On a repository with tens of thousands of files it is the single setting with the most visible effect on how fast the shell feels.

git config set --local core.fsmonitor true

There is also the case of .DS_Store, which the Finder writes into every folder it displays. Adding it to each project's .gitignore is a chore repeated forever; a global ignore file is set once. The default location is documented as $XDG_CONFIG_HOME/git/ignore, falling back to $HOME/.config/git/ignore when that variable is unset or empty, and core.excludesFile points somewhere else if you prefer.

The ones that end a recurring argument

These change defaults that were chosen for safety or for history, and each of them removes a decision you would otherwise make repeatedly.

Setting Value What changes
init.defaultBranch main The initial branch name in new repositories
pull.rebase true git pull replays local commits instead of creating a merge commit
push.autoSetupRemote true A first push configures the upstream instead of failing
fetch.prune true Every fetch removes remote-tracking refs for deleted branches
rebase.autoStash true A rebase stashes and restores a dirty working tree
merge.conflictStyle zdiff3 Conflict markers include the original text
branch.sort -committerdate Branch listings put recent work first
column.ui auto Supported commands print in columns rather than one per line

Several of those deserve a sentence more than a table row.

init.defaultBranch matters because of what the default actually is. The git init manual states that without --initial-branch the command falls back to the default name, currently master, and notes that this is subject to change and can be customised through this variable. Hosted services create repositories named main, so leaving this unset means every locally created repository starts on a branch that does not match the remote.

pull.rebase is the one to think about rather than copy. The manual is unusually direct about it, attaching a note that this is a possibly dangerous operation and should not be used without understanding the implications. Rebasing on pull produces a linear history and rewrites the commit IDs of anything not yet pushed. For a branch only you have touched that is an improvement. For a branch other people have pulled it makes work for them.

merge.conflictStyle changes what you are shown during a conflict. The default, merge, prints your side, a divider, and their side. The manual describes diff3 as adding a marker and the original text before the divider, and zdiff3 as similar to diff3 but with matching lines removed from the conflict region. Seeing the common ancestor is often the difference between understanding a conflict and guessing at it.

rebase.autoStash defaults to false, and the manual pairs the description with a caution: the final stash application after a successful rebase might produce non-trivial conflicts. Set it if interrupting work to rebase is a frequent annoyance, and know that the stash can still land awkwardly.

push.autoSetupRemote and fetch.prune work as a pair, at the two ends of a branch's life. The first, described in the manual as assuming --set-upstream on a default push when no upstream tracking exists, means a branch created locally and pushed for the first time ends up connected without anyone typing -u. It takes effect with the simple, upstream and current values of push.default, and simple has been the default since Git 2.0. The second removes remote-tracking refs for branches that no longer exist on the server, which is what stops git branch --remotes from filling up with names deleted months ago.

Three more are small enough to mention in a line each. rerere.enabled records how you resolved a conflict so the same conflict resolves itself next time, which pays off during a long rebase; the manual notes it is enabled automatically once an rr-cache directory exists. diff.colorMoved colours moved lines differently from added and removed ones, which makes a refactoring diff readable instead of a wall of red and green. And help.autoCorrect defaults to 0, meaning a mistyped subcommand prints a suggestion; a positive number runs the suggestion after that many tenths of a second, which is either convenient or alarming depending on temperament.

Two accounts, one home directory

Work and personal repositories on the same Mac is the common case, and setting a global email address means half your commits carry the wrong one. Conditional includes solve this properly.

The manual documents includeIf.<condition>.path, where the condition begins with a keyword. gitdir: takes a glob pattern matched against the location of the .git directory, gitdir/i: does the same case-insensitively, which matters on a case-insensitive Mac filesystem, and onbranch: matches the name of the checked-out branch. A pattern starting with ~/ gets the home directory substituted, and a pattern ending in / matches that directory and everything inside it recursively.

[user]
	name = Your Name
[includeIf "gitdir/i:~/work/"]
	path = ~/.gitconfig-work
[includeIf "gitdir:~/projects/"]
	path = ~/.gitconfig-personal

Each included file holds only the differences, typically an email address and possibly a signing key. Clone a repository into the right folder and the correct identity applies with nothing to remember. git config list --show-origin confirms which file supplied the address, which is the check worth running the first time.

Signing fits the same pattern. gpg.format defaults to openpgp, and the manual lists x509 and ssh as the other values. Choosing ssh lets an existing SSH key sign commits, which avoids maintaining a separate key just for signatures, and because it can live in an included file each account can sign with its own key.

Reading it back, and putting it back

Configuration accumulates. Something set two years ago from a blog post is still in effect, and the way to see the whole picture is the listing rather than memory.

git config list --show-origin --show-scope
git config get --all --show-origin remote.origin.url

The second form matters for variables that can legitimately appear more than once. git config get emits the last value by default and returns exit code 1 when the key is absent, and --all emits every value. Knowing that a key is absent rather than set to something unexpected is half of most diagnoses.

Removing a setting is git config unset, which the manual notes refuses to unset a multi-valued key unless --all is passed. For a broader tidy, git config edit --global opens the file itself, which is often faster than a sequence of unset commands and makes the whole file reviewable in one pass. Because the file is plain text under your home directory, the reliable backup is a copy of ~/.gitconfig kept somewhere you can find it. Every file discussed here begins with a dot, so whether they are visible without a keystroke is a genuine daily difference between file managers. Keeping that file, the terminal that reads it and the repository it applies to visible together in one window turns this from an occasional expedition into a two minute check.

What to change first

Set user.useConfigOnly to true and init.defaultBranch to main, then run git config list --show-origin --show-scope once and read the whole thing, because a setting you forgot about is more likely than a setting you need. If work and personal repositories share the machine, move the email address into an includeIf block before doing anything else. A window where the config file, the shell and the repository sit together is the shape Atriens is built around.

Frequently asked questions

Where is the global Git config file on a Mac?

~/.gitconfig, or $XDG_CONFIG_HOME/git/config when that variable is set. Both are listed as the global scope in the manual, and git config list --show-origin prints the actual path next to every value, so there is no need to guess which one your installation is using.

Why is my Git setting being ignored?

Almost always because a more specific scope overrides it. A value in a repository's .git/config beats the same value in your home directory, and the command scope beats both. Run git config list --show-origin --show-scope, find the variable, and the file that actually supplied it is on the same line.

How do I use a different email address for work repositories?

Put the work address in a separate file such as ~/.gitconfig-work and load it with an includeIf block keyed on gitdir/i:~/work/. Any repository cloned under that folder picks up the address automatically. Setting user.useConfigOnly to true as well means Git asks rather than guesses if a repository falls outside every pattern.

Should I set pull.rebase to true?

It depends on whether anyone else has your commits. Rebasing on pull gives a linear history and rewrites commit IDs that have not been pushed, which is an improvement on a branch only you touch. The Git manual attaches an explicit caution to this variable, so set it deliberately rather than as part of a copied block.

Do I need to install anything to store my GitHub credentials?

No. The Command Line Tools install git-credential-osxkeychain alongside Git, so setting credential.helper to osxkeychain stores the token in the login keychain with nothing else to download. Other helpers exist and can be listed as alternatives, but the built-in one covers the common case.

How do I remove a setting I no longer want?

git config unset --global <key> removes one value, and --all is required for a key that appears more than once. For a general clean-up, git config edit --global opens the file directly, which makes it easier to see what has accumulated than removing keys one at a time.

Back to all posts