tpwo.github.io

A personal blog

Useful Git settings

Written on

In my dotfiles I always have a .gitconfig file with some of the settings preconfigured. Without them using Git for me would be much more cumbersome. Here’s a list of them along with some comments and sources.

Core settings

core.autocrlf

Line ending characters are one big mess due to backwards compatibility. Windows systems use CRLF, Unix and consequently Linux uses LF. More than that, older Macs used CR, so the only missing option was LFCR 🤠. Thankfully Apple switched to LF with Mac OS X 10.0, so we’re left with two variants.[1]

Git was created by Linus Torvalds so it’s no surprise that it prefers LF by default, and warns you if you try committing files with other line ending characters.

Theoretically you can stick to e.g. CRLF if you’re doing a strictly Windows project, but I find it much easier to always commit LF, and configure Git (and my CI[2]) to guarantee that.

In practice, you might notice that a lot of files in a lot of repositories in your project were already committed with CRLF. Such is life. Because of that I don’t like setting autocrlf to true, as it tells Git to always convert from CRLF to LF when doing a commit.

This is a bit annoying, as then it shows that the whole file was modified (which is in fact true) in the commit view, and it makes much harder to see the actual changes. This depends on the tool you’re using as some of them can hide line ending changes pretty well. Still, I would advocate to isolate line ending changes from other types of changes.

E.g. you might want to do a single commit in a repo in which you clean it up for all files[3], and then add this commit to .git-blame-ignore-revs, so it doesn’t makes mess with the history regardless of the tool and/or settings you are using.

TLDR

My rule of thumb:

git config --global core.autocrlf true  1
git config --global core.autocrlf input 2
  1. Use that setting on Windows
  2. Use that setting otherwise (Linux, Mac, WSL)

core.excludesFile

Have you noticed that due to your setup, i.e. editor or IDE you’re using, you are the person who always adds particular entries to .gitignore in all repos? Are you tired of that? excludesFile is the solution for you. It’s a globally defined .gitignore which Git uses even if a .gitignore is missing in a repo.

I like to keep it in my home folder along with .gitconfig, and I use the name .gitignore_global:

git config --global core.excludesFile '~/.gitignore_global'

core.sshcommand

I’m a Windows user, but I prefer bash. As terminal is very important in my workflow, I use WSL both at work and in my private projects. There are a lot of quirks about WSL and one of them is having Git remember you SSH passwords. There are Linux solutions for that, and I experimented with them. Unfortunately, it wasn’t working out great for me (but maybe something has changed in recent years). Another solution is to use password-less keys, but you don’t want to do that, right?

Thankfully we can use Windows built-in SSH agent to remember passwords for us even in WSL.[4]

To do that, we have to tell WSL Git to use SSH binary from Windows:

git config --global core.sshcommand '/mnt/c/Windows/System32/OpenSSH/ssh.exe'

Credential settings

credential.helper

This is quite similar to core.sshcommand I mentioned above when using Git in WSL. You can choose to use HTTPS over SSH when talking with Git remotes. But WSL doesn’t have a built-in way to store these credentials, and using plaintext file .git-credentials is not something you want to be doing.

Again, Windows comes to the rescue. Here, you have to install Git on Windows to be able to use its credential manager from WSL Git. Then, you can authenticate once, usually via a browser window, and Git will remember this.

git config --global credential.helper '/mnt/c/Program\ Files/Git/mingw64/bin/git-credential-manager.exe'

Make sure to quote the path as space in Program Files would cause some issues otherwise. And use the actual path of where you have Git for Windows installed, other default alternative is AppData of your Windows user.

Git features

feature.manyFiles

Git introduced this feature a few years ago to improve support for bigger repos. I naively enabled it after reading the change log and it instantly broke some tools for me, particularly Matlab Git integration was completely broken. After these experiences I disabled this feature, so my setting repeats the current default.[5]

git config --global feature.manyFiles false

If you’re struggling with Git performance you might consider to enable it. Hopefully after a few years tools already support it.

git commit

commit.verbose

git commit has a --verbose flag which includes committed diff in a commit window. The commit windows is nothing more but an automatically opened file which you edit, save, and quit, which makes git do the commit.

This option always adds this flag, so you can see what are you committing every time.

git config --global commit.verbose true

git fetch

fetch.prune

You can remove any remote-tracking references (i.e. origin/<branch-name>) which were removed on the remote with git fetch --prune. I find it very convenient to be the default, as branches are usually removed after merging pull request, so you can quickly have a lot of dangling references.

git config --global fetch.prune true

If you have multiple remotes, and would like to prune references from only some of them, it’s also an option. For that, there is another command:

git config --global remote.<name>.prune true

<name> can be anything, and by convention origin is used as the default remote name.

You might be wondering if setting something like git config --global remote.prune true is allowed. Yes, it is, and it does pretty much the same thing as setting fetch.prune to true.[6]

git pull

pull.rebase

I like explanation from this video why default behavior of git pull is a bit annoying. Its title is a little bit clickbaitish, but I agree with the arguments.

I was also a bit surprised that author didn’t mentioned option we’re talking about here. To do git pull --rebase automatically with each pull set:

git config --global pull.rebase true

pull.ff only

There is also another option which enforces fast-forward merge in git pull. The difference is that --ff-only merge works only if there are no newer commits coming from the remote. Otherwise it fails, and you have to do something with it manually. That means you are forced to do the rebase on your own.

This adds you more work but one can advocate that it’s safer, as you have to explicitly rebase to the new version.

This is in line with the approach never use git pull (i.e. use git fetch and git rebase instead). If you’re into that, consider using this option.

git merge

merge.conflictstyle

I think I don’t know a person who likes to solve merge conflicts. But I noticed that many people are more scared about them then they probably should be.

Setting a better conflict style in Git is one of the steps to make them less scary.

zdiff3 is a relatively new conflict style in Git which was introduced in 2022 with version 2.35. That means that you still can work with systems which don’t have this feature, so make sure to see if your Git version is new enough.

But what is zdiff3? It’s an updated version of diff3 which is much older algorithm[7] and which has one big benefit: in classic conflict view you see two versions of the code, ours and theirs, as Git labels it. diff3 also shows what was before that, which we can name a common ancestor of both commits.[8]

This simple addition often adds much needed context, to solve merge conflict faster, and do it correctly.

Unfortunately, diff3 isn’t ideal, and sometimes creates more noisy conflicts, marking more lines as conflicting.[9]

zdiff3 addresses these issues. Interestingly enough, z in the name comes from the word zealous.

git config --global merge.conflictstyle zdiff3

git init

init.defaultBranch

Like it or not, but main is the new master in Git, and it doesn’t seem to be changing any time soon.

Git though still uses default master, so this option has to be set.

git config --global init.defaultBranch main

Git aliases

git git

I borrowed that idea from Anthony Sottile[10]. It allows you to type git git <command> and make the command still work. In fact, you might type as many git commands as you like, as it’s recursive.

Why? Same as Anthony, I sometimes type git in the terminal, then start thinking about something, then and go back to typing, often typing git again due to muscle memory. Adding an alias is simpler than fighting with it, apparently 😛

git config --global alias.git '!git'

git l

Quickly output oneline log for all branches. Very handy to quickly orient around in the repo. This is one of my most-used git commands.

git config --global alias.l 'log --graph --all --oneline'

git lg

git l with a bit more details. It uses more formatting and I copied it from someone’s dotfiles years ago.

git config --global alias.lg "log --graph --all --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%cr)%C(reset) %C(white)%s%C(reset) %C(bold yellow)- %cn%C(reset)%C(bold red)%d%C(reset)' --abbrev-commit --date=relative"

git llg

Even more detailed log. Also copied from that same person.

git config --global alias.llg "log --graph --all --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%cD%C(reset) %C(bold green)(%cr)%C(reset)%C(bold yellow)%d%C(reset)%n'' %C(white)%s%C(reset) %C(bold yellow)- %cn%C(reset)' --abbrev-commit"

git k

You migth not know this, but Git comes with a GUI called gitk. It isn’t always installed (e.g. MacOS Git via homebrew), but when you have it, it might be handy in some cases.

I’m used to Git commits starting with git <cmd>, so I added an alias which reflects it.

git config --global alias.k '!gitk &'

git ka

Another gitk alias to run the program with --all flag which then shows all branches.

git config --global alias.ka '!gitk --all &'

  1. I recommend a video from Scott Hanselman if you want to learn more about this topic
  2. pre-commit with its mixed-line-ending hook set to lf is a great tool for that
  3. pre-commit mentioned above is a great way to do this. Also make sure to set up it to check it with each new commit or these CRLF files might start reappearing, as we live in a society
  4. Refer to MS docs on how to enable SSH agent
  5. When writing this post, I did some research, and it seems that this might be due to libgit2 incomparability with manyFiles. More details on Stack Overflow
  6. There is quite a good summary of the topic again on SO
  7. Introduced in 2007 with Git 1.5.0
  8. A pretty nice explanation of that is in this blog post
  9. Again, SO for the rescue
  10. Here’s his video about it: https://youtu.be/BkUW2NgfZao
✵