Git Configuration

Craig Motlin
8 min readJan 14, 2019

--

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 in git 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 like la, 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, like 63978b049.
  • %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, like 2000-12-31.
  • %C(brightcyan)%ar%C(reset)% author date, relative, in bright cyan, like 10 days ago.
git fancy log

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 or git 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/

--

--