Kurt McKee

lessons learned in production

Archive

Managing your git config using dotbot

Posted 19 June 2022 in dotbot, dotfiles, and git

I'm using both Linux and Windows for work and personal computing, and that has finally pushed me to start tracking my dotfiles in a git repository. I settled on a dotfiles manager named dotbot because it's written in Python and, with some effort, works well on Windows.

Initial setup

My initial attempt to manage my git config was simple: track ~/.gitconfig in a git repo and use dotbot to create a symlink to it.

My dotfiles directory

dotfiles/
│   dotbot.yaml
│
└── shared/
    └── git/
            gitconfig.ini

dotbot.yaml

link:
    ~/.gitconfig: shared/git/gitconfig.ini

This configuration meant that dotbot would create a symlink from ~/.gitconfig to the gitconfig.ini file in my dotfiles repository.

This worked well, but I quickly encountered a problem.

Challenge 1: Platform-specific configurations

I wanted to start signing my git commits; my git configuration was mostly shared between both Windows and Linux, but to sign commits on Windows I have to specify the absolute path to the GPG executable. This would break things on Linux, so I could no longer share my git config as a single file.

Luckily, git supports an include.path config option which will include the contents of another file directly into your .gitconfig file.

By splitting my Linux- and Windows-specific git config options into separate files, dotbot can then symlink the correct file to a known location, and my .gitconfig file can include a generically-named platform-specific file.

My dotfiles directory

dotfiles/
│   dotbot-linux.yaml
│   dotbot-windows.yaml
│
└── shared/
    └── git/
            gitconfig.ini
            linux.ini
            windows.ini

gitconfig.ini

[include]
    path = .gitconfig-platform-specific

dotbot-linux.yaml

link:
    ~/.gitconfig: shared/git/gitconfig.ini
    ~/.gitconfig-platform-specific: shared/git/linux.ini

dotbot-windows.yaml

link:
    ~/.gitconfig: shared/git/gitconfig.ini
    ~/.gitconfig-platform-specific: shared/git/windows.ini

This setup means that git will include a file named .gitconfig-platform-specific in my home directory on both Windows and Linux. Thanks to dotbot, that file will exist and point to the correct git config file for whichever platform it's running on.

However, I encountered another problem.

Challenge 2: Constant changes to .gitconfig

I use various git tools that store their configuration in .gitconfig. In many cases, that "configuration" is simply a list of recently-opened repositories.

As a result, my git config frequently contains garbage, transient changes. I want the tools to continue showing me useful lists of recent repos, but I don't want my dotfiles repo to have merge conflicts when synchronizing changes.

I tried several ways to deal with this:

  • Commit a clean gitconfig.ini file, then git-ignore it.

    This doesn't work because git can only ignore files that are untracked in the git repo. Once gitconfig.ini is committed, it cannot be ignored.

  • Run git update-index --skip-worktree <path>.

    This could work, but makes it impossible to change branches in my dotfiles repo. This turned out to be a major headache.

In the end, my solution was to move all of the shared git configuration to a common.ini file, then have ~/.gitconfig include that file. dotbot was then used to copy -- NOT symlink -- the .gitignore file.

My dotfiles directory

dotfiles/
│   dotbot-linux.yaml
│   dotbot-windows.yaml
│
└── shared/
    └── git/
            gitconfig.ini
            linux.ini
            shared.ini
            windows.ini

gitconfig.ini

[include]
    path = .gitconfig-common
    path = .gitconfig-platform-specific

dotbot-linux.yaml

- shell:
    -   description: "Copy shared/git/gitconfig.ini to ~/.gitconfig (if needed)"
        command: >
            if [ ! -e ~/.gitconfig ]; then
                cp shared/git/gitconfig.ini ~/.gitconfig;
            fi

link:
    ~/.gitconfig-common: shared/git/common.ini
    ~/.gitconfig-platform-specific: shared/git/linux.ini

dotbot-windows.yaml

- shell:
    -   description: "Copy shared/git/gitconfig.ini to ~/.gitconfig (if needed)"
        command: >
            pwsh -c "
                if (! (test-path ~/.gitconfig)) {
                    copy-item shared/git/gitconfig.ini ~/.gitconfig
                }
            "

link:
    ~/.gitconfig-common: shared/git/common.ini
    ~/.gitconfig-platform-specific: shared/git/windows.ini

My home directory

~/
    .gitconfig
    .gitconfig-common             -> shared/git/common.ini
    .gitconfig-platform-specific  -> shared/git/windows.ini

Thus, ~/.gitconfig is relegated to being a transient file that I categorically ignore. All of my real configuration is stored in included files!

What this means for you

First, if you're not using a dotfiles manager, I recommend doing so.

Second, if you're a software author, please consider splitting the transient configuration -- like lists of recent files -- into a separate configuration file. That'll allow people like me to store valuable settings in a git repo without constant updates. Thanks!