My .gitconfig (annotated)

Don’t crib configuration files from others - at least not without understanding every line and making a conscious decision to include it. You can crib from mine, because you are reading this page and therefore have an explanation of every line.

Note: You can find the history of my .gitconfig file in this public Git repository.

[user]
	name = Raymond E. Pasco
	email = ray@ameretat.dev

My name and email address, which are necessary to make Git commits. I got this domain specifically for open source development; however, I do not ever want Github to send automated emails to it, and Github’s email settings may use other email addresses for me against my wishes on the rare occasions I use the web service.

It is also possible to have, instead of name and email settings, the user.useConfigOnly setting set to true, in which case you will be required to set local name and email settings for each repository. Depending on your usage patterns, this may be more desirable.

If you want to set a per-repository name and email for only certain repositories (e.g., work email for work repositories), you can do that with git config --local, and others will use your main configuration. user.useConfigOnly is for when you want to force yourself to explicitly set it for each repository.

Recommendation: Necessary to make commits, so you should definitely set it.

[core]
	editor = vi

Not vim, although vi is vim on many systems. The main thing this setting does is ensure that Git opens a sufficiently vi-like editor.

Recommendation: Set this to your preferred terminal editor. A non-terminal editor will work, but will awkwardly throw you out of the terminal into your IDE or whatever; if you want to use a more heavy-duty or IDE-like editor for merges specifically, look into git mergetool.

[add "interactive"]
	usebuiltin = true

Use the in-development C version of add-interactive rather than the default Perl version. This is necessary to be able to patch-add new files in Git 2.28 or later, because diffs from intent-to-add entries (git add -N) now (correctly) appear as new file diffs, but the Perl version does not allow these hunks to be edited. (See the mailing list for more information.)

Recommendation: If you’re not impacted, you could wait for this to become the default.

[alias]
	empty = commit --allow-empty

I find myself making toy commit graphs in Git for educational or testing purposes fairly regularly. When doing this, I mostly care about the graph structure, and make many empty commits. This alias makes this a bit quicker to type for me. Note that this option merely permits an empty commit - it does not require one. There could be changes staged in the index.

Recommendation: This is probably not useful to most people.

    ff = merge --ff-only

Allows me to use git ff to explicitly fast-forward a branch, when this is actually the behavior I want. In other words, I have git ff as the command to do a fast-forward, and git merge (in combination with merge.ff = false, described below) as the command to do a merge.

Recommendation: As with any alias, this is a matter of personal taste. I recommend being aware of when git merge will actually merge and when it will fast-forward, at the very least. I discuss this more below.

    l = log --oneline --graph

This is my preferred log format, so I have given it the easy git l alias. --graph implies --topo-order, so I don’t need to include that option. Usually, I use gitk to view histories - this is just a minor convenience.

Recommendation: Whatever your thoughts on log formats and aliasing them, you should make sure gitk is working on your system, and use it. If you already have another tool that does what gitk does (show the graphical history of a repository), that’s fine, but gitk is the one that comes with Git.

    reroll = rebase --interactive --keep-base

Usually, a non-interactive rebase is about replaying commits on top of a new base (hence the name ‘rebase’). An interactive rebase, however, is about editing a branch, and I don’t want it to move to a new base - I want to edit the branch as it is. Therefore, reroll is a handy alias to use instead of git rebase -i.

Recommendation: People are usually fairly haphazard with where they base branches - but you can include more useful information (and sometimes ease workflows) by basing them in meaningful places, e.g., a bugfix on top of the commit which introduced the bug. Like all aliases, this alias is a matter of personal taste, but it points at a useful principle.

[init]
	defaultBranch = master

The default branch name, formerly hardcoded “master”, is now configurable via this setting. If it is not set, a warning is displayed on every git init.

Recommendation: Set this to your preferred default branch name to silence the warning.

[merge]
	autoStash = true

This setting stashes worktree changes prior to running a merge, and unstashes them when the merge is complete, avoiding the loss of uncommitted changes in the worktree.

Recommendation: I recommend this just as a caution. Of course, you should probably not merge on a dirty worktree, especially since merging is a maintainer’s operation, but this will at least preserve accidentally dirty worktree content.

    branchdesc = true

Descriptions can be set for branches with git branch --edit-description. With this setting enabled, git merge --log will include these branch descriptions in the branch logs. I would like to submit a patch to Git in the near future to have description-only logs in merge commits as well, without including any commit names.

Recommendation: If you think this feature would be useful for your merges, feel free to start using it.

    conflictStyle = diff3

Ordinarily, merge conflict annotations in files show only two sides: the “left” side, your current (integration) branch, and the “right” side, the branch you are merging into it. This is, of course, not enough information to resolve a conflict. A merge is a three-way operation: it involves finding the merge base, which is the common ancestor of the commits on either side of the merge, and applying the diff between the merge base and the left side, and the diff between the merge base and the right side, simultaneously. When the diffs cannot be applied simultaneously, a conflict arises.

It is absolutely vital to intelligently resolving conflicts that you be able to see the version as of the merge base as well as the left and right versions. This setting will add the merge base’s version of the conflicted portion of the file to the conflict markers, in the middle (with ||||||| above it and ======= below).

Recommendation: Turn on this setting immediately.

    ff = false

git merge may do one of two things - it may actually do a merge, or, if the current HEAD is a direct ancestor of the branch to be merged, it may attempt a fast-forward. In a fast-forward, rather than actually creating a merge, the HEAD branch is simply adjusted to point at the tip of the branch to be merged (“fast-foward” is another of Git’s many tape deck metaphors).

This setting disables fast-forwarding, because I prefer git merge to always do a merge. I have the git ff alias discussed above for when I want to perform a fast-forward.

Recommendation: If you do any integration operations, you should be aware of the difference between a merge and a fast-forward. Doing each explicitly, rather than letting Git decide on its own, is preferable to me.

[pull]
	ff = only

This setting allows the lazy man’s git pull (typing git pull on its own to retrieve remote changes into one’s own repository clone) to be possible. git pull is not an analogous operation to cvs update or similar. Rather, git pull is a command meant for Linus Torvalds to use to merge a branch from someone else’s repository. git pull is actually a remote fetch followed by a merge.

As discussed above, git merge tries to decide on its own whether to actually do a merge, or whether to do a fast-forward instead. Most uses of the lazy man’s git pull trigger a fast-forward, permitting the illusion that this command is analogous to something like cvs update. However, when one has made local changes, an explicit merge is necessary, creating the useless information-free “Merge remote-tracking branch origin/master into master” commits that plague many histories.

Some people set pull.rebase instead, which rebases any local changes on top of fetched changes, to try to preserve the cvs update illusion even with local commits to the branch. The actual correct response to a failure to fast-forward, however, is not to blindly rebase, but to stop and explicitly do whatever the correct operation is. On master, this is often to take the commit you lazily made on master and give it its own branch name, then reset master to the remote master - because master is an integration branch that belongs to maintainers.

On something like a topic branch with multiple people working closely, where the topic branch history will be cleaned up at the end of work, using git pull --ff to get the default behavior is fine - the history will be a temporary mess, but this can be fixed at the end.

Recommendation: Don’t do anything but explicit integration on integration branches. I do recommend this setting to avoid fallout from mistakes, especially if you do integration work and your errors have a chance to be permanent.

[push]
	default = nothing

This setting prevents the lazy man’s git push (i.e., typing just git push to push the current branch to a default remote). The syntax for git push is more or less git push [remote] [local-ref]:[remote-ref]. If you leave any of these out, Git will try to use sensible defaults, which will probably work out alright. However, if you do integration work, anything you push is permanent by social contract - so it pays to be explicit.

Setting push.default to nothing requires you to provide a remote and refspec. Note that this is not perfect - if you have a local branch set up to track a remote branch with a different name, e.g. local master tracking origin/integration, git push origin master will try to push to origin/master because the refspec master means master:master. I always type the full refspec explicitly (e.g. master:master), avoiding this, but if you are used to the default it might be surprising.

Recommendation: The default “simple” mode, which pushes to the remote-tracking branch but fails if it has a different name, should work for most people. You can always be more explicit in your commands. If you do public integration work, though, you should be careful about your pushes.

[rebase]
	abbreviateCommands = true

This setting changes the automatically generated command script created by git rebase -i to use single-letter commands (e.g., p instead of pick) rather than full word commands. Interactive rebase will always accept either form, this setting only affects the script it generates itself. I set this simply because I use vi, and the r command to replace a single letter is faster than replacing a word.

Recommendation: Pure personal preference. Learning at least the basics of the interactive rebase command language is an important Git skill, however.

    autoSquash = true

This setting has nothing to do with the popular “worst practice” of aggressively oversquashing entire branches which the word “squash” has come to be associated with.

rebase.autoSquash changes the behavior of interactive rebase as follows: a commit with a message like squash! some commit message or fixup! some commit message will be automatically placed directly after the commit with the message some commit message, and its initial command will be changed from pick to squash or fixup.

These commit messages can be generated for you with git commit --{squash,fixup}=hash.

Recommendation: This is a useful feature which saves you some editing time on the interactive rebase script. It is basically safe to enable unless you, for some reason, name commits which are not intended to be combined things starting with squash! or fixup!, which seems unlikely - therefore, I recommend this setting.

    autoStash = true

This setting stashes worktree changes prior to running a rebase, and unstashes them when the rebase is complete, avoiding the loss of uncommitted changes in the worktree.

Recommendation: This was almost exactly the same description as the one above for merge.autoStash, but this setting is more important for rebases, which are more likely to be run with a dirty worktree. I recommend turning this on.

[rerere]
	enabled = true

Git has the capability to remember merge conflict resolutions and automatically apply them if the same conflict is encountered again. This is disabled by default; once it is enabled, Git will start recording resolutions and applying them. Incorrect resolutions can be removed with git rerere forget and related commands. The cache of remembered resolutions is local, but you can teach rerere a conflict resolution from an existing merge commit by locally replaying the merge. The rerere-train.sh script included in Git’s contrib/ directory may help with this.

Automatically resolved conflicts are not added to the index by default. If you would prefer them to be, there is the rerere.autoUpdate option. I prefer taking the step explicitly.

Recommendation: The reason this is disabled by default is because reaching a conflict, opening the file, and finding no conflict in the file (because a recorded resolution was applied) is surprising behavior to someone who does not know about rerere. You have now read about it and know what it does, and can therefore save yourself a lot of time by enabling it now.

[sendemail]
	annotate = true

This setting lets me stop to annotate commits being sent by git send-email without explicitly specifying the --annotate option every time.

Recommendation: This is a good idea to set; most of the time you will at least want to look over the emails to be sent, even if you don’t end up annotating them.

    confirm = always

This setting forces git send-email to always stop for confirmation before sending emails. It nearly always stops for confirmation anyway, due to adding the sender to the CC list, but if you manually formatted patches it might not.

Recommendation: I tend to prefer explicit confirmation before irrevocable actions like sending mail to a mailing list. I recommend this for that reason, but maybe you prefer living fast and dangerous.

    # SMTP settings, if applicable, below

If your system is not set up to send mail via the system mailer daemon, you will need to specify your SMTP server settings here.

Recommendation: This is probably how most people will set up their email. I prefer having a working system mailer where I can, but even I use this much of the time. You may use forges like Github most of the time, but I do recommend becoming familiar with git send-email and its counterpart, git am (apply from email), which are the standard way to share patches with Git.

[status]
	showUntrackedFiles = all

Ordinarily, when you have a new directory in your worktree that is not yet git added, git status shows only the directory name. This setting makes git status display each file individually, in the same way it would if the directory were already tracked.

Recommendation: I recommend this setting. The only reason I can think of not to include it is if you would regularly get huge git status output from it - but in that case, you would get the same huge output after git add, anyway.