Victor Kropp

Essential Git setup

Every craftsman must know their tools. For us, developers, the tool all of us are using independently of the programming language and technology stack is Git.

Here are some tips and tricks on how I set up Git on all my computers.

Different emails

When you first run Git on a bare machine, you will be prompted to enter your name and email address. By default, Git suggests storing them in your global config. However, I often have both work and personal projects on the same computer. And while my name doesn’t change, the email I use to author commits is different.

I could have set it up for each repository individually, but I have too many and don’t want to bother. Instead, I put work projects in ~/work directory and personal to ~/my and use the following conditional setup in my ~/.gitconfig:

[includeIf "gitdir:~/work/"]
    path = ~/.gitconfig-work

[includeIf "gitdir:~/my/"]
    path = ~/.gitconfig-personal

Each of them may contain any of Git settings, such as:

[user]
    email = victor.kropp@example.com

Use these config files for any other environment-specific option, such as commit signing.

Commit signing

To verify the authenticity of your commits, you may want to sign them. You can do this either with GPG or (starting with Git 2.34) with an SSH key.

I use a Yubikey and a GPG key stored on it, so my setup looks like this:

[commit]
    gpgSign = true
[user]
    signingkey = 4AEFD688FCE4ADBD

But a more convenient way for many would be to use 1Password as SSH agent and also to sign commits with the same key.

Sync main branch only

Another option I desperately need with some of the huge repositories I need to deal with at work is to check out only the master branch.

Usually, the repository’s .git/config contains the following remote section

[remote "origin"]
    url = ssh://git@github.com/kropp/repo.git
    fetch = +refs/heads/*:refs/remotes/origin/*

It tells git to check out all remote branches and all their commits. If hundreds of developers push thousands of commits to the repository every day, it will slow down all fetches significantly. And you don’t need most of those commits anyway, so why spend time, network bandwidth and power doing this?

Instead, change remote.fetch to:

    fetch = +refs/heads/master:refs/remotes/origin/master

From now Git will only sync master branch and ignore all work in progress.

Oh My Zsh

Another option useful in such repositories and when your terminal is running zsh with Oh My Zsh is to tell it not to show branch name in the prompt.

[oh-my-zsh]
    hide-info = 1

With this, your prompt will appear instantly no matter the size of the repository.

Fixup commits

I strive to make commit history as clean as possible. It often involves creating fixup! commits, for example, after rebasing feature branch on top of main/master. After adding a bunch of fixups, one needs to run interactive rebase.

Before doing this, enable the option via git config --global rebase.autosquash true or add these lines to your .gitconfig

[rebase]
    autosquash = true

With this option, commits are arranged automatically.

Prune remotes

Another useful option is git config --global fetch.prune true, which is equivalent of running git fetch --prune. It removes local references to deleted remote branches and is extremely useful when, for example, you often merge your feature branches externally after code review.

Update Jan 3, 2024 Even with this option, you’ll likely have dangling local branches referring to the old remotes, if you’ve ever checked out one. There is no option to get rid of them too, but here is a command I use to achieve that:

git branch -r | awk "{print \$1}" | \
egrep -v -f /dev/fd/0 <(git branch -vv | grep origin) | awk "{print \$1}" | \
xargs git branch -fd

Let’s break it down:

  1. The first line lists all branch names
  2. The second one filters out only those, which remotes are gone. The complicated syntax with -f /dev/fd/0 here is needed because we essentially provide egrep with two inputs: list of all branches, and list of existing branches. Because of -v flag it output lines that do not match the list of existing branches.
  3. And the last line finally deletes the branches

Globally ignore files

Update Apr 16, 2024 It is possible to set up a global .gitignore file with

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

And then on macOS one can prevent any .DS_Store sneaking into the repo with

echo .DS_Store >> ~/.gitignore

I need more options!

Update Feb 21, 2024 I’ve recently stumbled upon a few useful articles on this topic, which I’d like to share.

Julia Evans, known for her explanatory zines, explains a lot of options on her blog.

Scott Chacon (the author of Pro Git) wrote a series on useful options and latest features of Git. Also available as a video of his FOSDEM talk.

That’s a wrap!

I just wish some of these options would be default in Git.

And what are your favorite Git hidden gems?

git

Subscribe to all blog posts via RSS