Git Configuration
This post is about one-time git configuration; commands that start with git config --global
.
These settings are a subset of my configuration that I’m comfortable recommending for any user on any machine. I’ve gathered git configuration from the git-scm book, the GitHub Blog, Julia Evans, and Scott Chacon.
Even after running these commands, you may still want to copy from my .gitconfig since it includes comments.
Aliases
Shorthand aliases
The git-scm book includes a few aliases that have become very common.
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.st status
git config --global alias.unstage "reset HEAD --"
git config --global alias.last "log -1 HEAD"
Here are a few of my own that seem to be pretty common.
git config --global alias.cp cherry-pick
git config --global alias.oops "commit --amend --no-edit"
And a few more of my own that aren’t too common, but you might like.
git config --global alias.ri "rebase --interactive --autosquash"
git config --global alias.f "fetch --all --prune --jobs=16"
git config --global alias.detach "checkout --detach"
Plural aliases
git
has inconsistent commands to list all branches, tags, stashes, workrees, remotes, and aliases.
git config --global alias.branches "branch --all"
git config --global alias.tags "tag --list"
git config --global alias.stashes "stash list"
git config --global alias.aliases "config --get-regexp ^alias\."
git config --global alias.worktrees "worktree list"
git config --global alias.remotes "remote --verbose"
git config --global alias.configs "config --list --show-origin"
StackOverflow aliases
Use git root
to get the root directory of the repository.
Use git first
to get the initial commit(s) that are ancestors of HEAD.
git config --global alias.root "rev-parse --show-toplevel"
git config --global alias.first "rev-list --max-parents=0 HEAD"
git push --force-with-lease
is a safer alternative to git push --force
that ensures that you've at least fetched/seen the history that you're about to wipe out.
There is no way to configure git to always use force-with-lease instead of force. The next best available option is to create an alias.
git config --global alias.pushf "push --force-with-lease --force-if-includes"
Log aliases
You can view a nice graph of your git history with git log --decorate --graph --oneline
. You can get fancy with the --format
option. These aliases all use the same format, but display different subsets of refs.
l
for specified branches or HEAD, as ingit l main
.la
for almost all branches and refs, excluding refs/notes, refs/stash, dependabot stuff, and pull requests.laa
for truly all refs.decorate
is likela
, but with--simplify-by-decoration
.
git config --global alias.l "log \
--graph \
--decorate \
--date=short \
--format=format:'%C(auto)%(decorate:prefix=👇 ,suffix=%n,tag=,separator= )%C(reset)%C(brightyellow)%h%C(reset) %C(normal)%<|(-50,trunc)%s%C(reset) %C(brightmagenta)%<(15,trunc)%an%C(reset) %C(brightblue)%ad%C(reset) %C(brightcyan)%ar%C(reset)'"
git config --global alias.laa "l --all"
git config --global alias.la "l \
--exclude=refs/prefetch/* \
--exclude=refs/remotes/origin/pr/* \
--exclude=refs/remotes/upstream/pr/* \
--exclude=refs/remotes/origin/dependabot/* \
--exclude=refs/notes/tests/* \
--exclude=refs/stash \
--all"
git config --global alias.decorate "la --simplify-by-decoration"
The common formatting options deserve explanation.
%C(auto)%(decorate:prefix=👇 ,suffix=%n,tag=,separator= )%C(reset)
ref names, like the--decorate
option but on a separate line, like* upstream/pr/123
.%C(brightyellow)%h%C(reset)
abbreviated commit hash in bright yellow, like63978b049
.%C(normal)%<|(-50,trunc)%s%C(reset)
subject (commit message) truncated 50 characters away from the right edge of the screen.%C(brightmagenta)%<(15,trunc)%an%C(reset)
author name truncated to 15, in bright magenta.%C(brightblue)%ad%C(reset)
author date, in bright blue, like2000-12-31
.%C(brightcyan)%ar%C(reset)%
author date, relative, in bright cyan, like10 days ago
.
Fetch and Push
git push
behavior varies based on the push.default setting. Possible values are nothing
, current
, upstream
, tracking
, simple
, and matching
. The default changed from matching
to simple
in Git 2.0.
I prefer current
because it is intuitive. It pushes the current branch to a branch of the same name on the remote. If the remote branch doesn't exist, it creates one.
git config --global push.default current
Diff
By default, git
detects copies but not renames. Turning on rename detection supposedly slows down git diff
but I haven't noticed a difference.
git diff
should use a prefix pair that is different from the standard "a/" and "b/" depending on what is being compared. This option is only relevant when not piping through a pager/formatter like diff-so-fancy.
git config --global diff.renames copies
git config --global diff.mnemonicPrefix true
Branches
Branch
Scott Chacon wrote a 3-part series called Git Tips and Tricks. A few tips cover global config.
One thing that’s always bugged me about Git is that I run git branch a lot to view what branches I have, but they’re in the dumbest possible order (alphabetic) and there are a million of them after a while.
We can configure git to sort by “negative committer date.”
Git also has a way to take a list of branches and try to split it into columns to make better use of the screen real estate. You can do this either with the new — column option, or with the column.ui setting.
git config --global branch.sort -committerdate
git config --global column.ui auto
It is idiomatic to name the first/main branch main
. It used to be idiomatic to name it master
and and unconfigured git init
call will still create a master
branch and print this hint.
❯ git init
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint:
hint: git config --global init.defaultBranch <name>
hint:
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint:
hint: git branch -m <name>
Configuring the default branch name suppresses this hint.
git config --global init.defaultBranch main
Merge and rebase
git
can auto-stash before a merge or rebase. This means that you can run merge or rebase in a dirty worktree.
git
can prevent merge commits by default.
git
can warn or error if commits are missing during an interactive rebase to prevent accidental deletion of commits.
git
can automatically force-update any branches that point to commits that are being rebased.
When a new branch is created with git branch
or git checkout
that tracks another branch, git
should set up pull
to rebase instead of merge.
git
should set up new branches so that git-pull
will merge from the starting point branch.
git config --global merge.autoStash true
git config --global rebase.autoStash true
git config --global rebase.missingCommitsCheck error
git config --global rebase.updateRefs true
git config --global branch.autoSetupRebase always
Rerere
According to the git-scm book:
The git rerere functionality is a bit of a hidden feature. The name stands for “reuse recorded resolution” and, as the name implies, it allows you to ask Git to remember how you’ve resolved a hunk conflict so that the next time it sees the same conflict, Git can resolve it for you automatically.
This feature is off by default, but there’s no downside to enabling it.
For convenience, configure rerere to stage changes automatically. This skips the git add
step and makes it clear when rerere has resolved a merge conflict, as git status
will show no unstaged changes.
git config --global rerere.enabled true
git config --global rerere.autoUpdate true
Log
git log
has a --follow
option that continues listing the history of a file beyond renames. This option only works for a single file.
git log --follow -- <path>
We can configure git to act as if the --follow
option was used when a single <path>
is given.
git config --global log.follow true
git log
has an option --decorate
that displays the ref names of any logged commits. git
can be configured to always show these ref names.
If short is specified, the ref name prefixes refs/heads/
, refs/tags/
and refs/remotes/
will not be printed. If full is specified, the full ref name (including prefix) will be printed.
git config --global log.decorate short
Commit
The docs for git commit --verbose
say:
Show unified diff between the HEAD commit and what would be committed at the bottom of the commit message template to help the user describe the commit by reminding what changes the commit has.
This diff shows up in the same editor where you write the commit message, and can be helpful for auto-completing identifier names that appear in code.
We can set commit.verbose to always show this diff.
git config --global commit.verbose true
Submodules
If you don’t use submodule, you can skip this section.
Diffs of submodules should show the changed contents rather than a list of commits.
git status should show a summary of commits for modified submodules.
git config --global diff.submodule diff
git config --global status.submoduleSummary true
Include
After following the advice here, you’ll have a ~/.gitconfig
file that you may want to share between machines. I keep mine in a dotfiles repository and sync it using dotbot. This setup requires splitting out machine-specific configuration into a separate file.
Git supports including other configuration files with the include
directive.
git config --global include.path "~/.gitconfig.local"
I like to move the entire user
section into ~/.gitconfig.local
.
❯ grep -F '[user]' ~/.gitconfig.local -A1
[user]
name = Craig Motlin
email = <email@email.com>
I follow GitHub’s advice to set user.useConfigOnly
to avoid git guessing my email address.
if, say, you want Git to use one email address for your open source projects and a different one for your work projects, you’ve undoubtedly made the mistake of committing to a new Git repository without having first set your email address in that repository. In this situation, Git emits a warning, but it creates the commit anyway, using an email address that it guesses from the local system hostname. If you’re trying to do something as complicated as different addresses for different projects, this is almost certainly not what you want.
git config --global user.useConfigOnly true
Performance
Caching
In Improve Git monorepo performance with a file system monitor, the GitHub blog recommends turning on the built-in file system monitor and untracked cache to speed up subsequent git commands like git status
in a working directory with many files.
git config --global core.fsmonitor true
git config --global core.untrackedCache true
Fast Merging
The GitHub Blog recommends using the ort
merge strategy for merges over the default recursive
strategy because it's 500x faster.
The new merge-ort is likely to become the default strategy in a future version of Git.
In the meantime you can configure it with:
git config --global pull.twohead ort
Git Maintenance
The git maintenance
command registers a repository for periodic background optimizations.
Run tasks to optimize Git repository data, speeding up other Git commands and reducing storage requirements for the repository.
Git commands that add repository data, such as
git add
orgit fetch
, are optimized for a responsive user experience. These commands do not take time to optimize the Git data, since such optimizations scale with the full size of the repository while these user commands each perform a relatively small action.The
git maintenance
command provides flexibility for how to optimize the Git repository.
git maintenance start
The list of repositories is machine specific. After running git maintenance
, move the list of repositories from ~/.gitconfig
to ~/.gitconfig.local
.
[maintenance]
repo = ~/.dotfiles
This was originally posted at https://motlin.com/git/2019-01-13-git-configuration/