Tracking your home directory in Git Part 1

5 minute read

One of the most annoying things about working with multiple workstations is keeping your various settings and configurations in sync. This is even more annoying when you work on a mixture of Windows and Linux workstations, and some of them need weird one-off changes that differ slightly from the others. Since I already know how to use Git, I’ve decided to use it directly in the root of all my home directories.

Merits

I know that several people will tell me that doing this is a horrible idea, and they are probably right. Let’s get some obvious ones out of the way:

Why this is a horrible idea:

  1. Git can perform destructive actions on our home directory with a single command
  2. Lots of critical and secret information is stored in the home directory
  3. Other Git repositories are likely to exist within our home directory
  4. It’s an overly complex solution to what is essentially a simple problem

Now that we’ve established why this will probably come back to bite me, let’s dive in to how to do it properly!

Creating the base repository

While it is tempting just to stroll into my home directory and run “git init”, I am choosing to create the empty repository in a new clean directory (coincidentally also located in a path beneath my home directory).

Right now this is mostly to avoid accidentally trashing my real home directory until we have things ready, but the separate copy of the repository will come in handy later for some other tasks.

git init ./repos/home-dir

In this new directory we’ll want to start by placing a .gitignore file that ignores everything except itself for now. The asterisk at the top tells Git to ignore all files, and the lines below it starting with an exclamation point explicitly tells Git which files we want to track.

.gitignore

*
!/.gitignore

This file is going to be crucial to preventing a lot of bad outcomes. We don’t want to trust ourselves to only commit files that we intend to, so the .gitignore file serves as an exhaustive list of all the files we intend to make a part of our repo. Let’s commit this now.

git add .
git commit -m "Add .gitignore"

Now we can start adding files that we will treat as global accross all of our workstations. In my case I want my .gitconfig file to be on all of my workstations. We’ll also add this file to our .gitignore negated with a ! at the beginning of the line.

.gitconfig

[user]
 name = Scott Evtuch
 email = scott@example.net

.gitignore

*
!/.gitconfig
!/.gitignore

We’ll add these changes as a commit to our master branch.

git add .
git commit -m "Add .gitconfig"

Setting up branches and pushing to remote

For my use case I’m going to keep separate branches for Windows and Linux configuration that is specific to each operating system. They will both branch off of the master branch which contains files that are the same for both operating systems. In the future this will allow us to introduce global changes and have them appear in both branches via rebase.

Let’s create those two branches off of master now.

git branch windows
git branch linux

I’m also going to set up a new remote on a private GitHub repository for easy access. Depending on your security requirements, you may want to store your remote somewhere else.

git remote add origin git@github.com:example/home-dir.git
git push -u origin --all

Cloning the repository into your existing home directory

Alright now that we have our basic repository set up, we’ll want to set up an existing home directory to track this repo.

Make sure you have a backup of your current home directory and test this process somewhere less important first. You can definitely wreck your home directory if you screw up past this point.

Git doesn’t like cloning into a directory that already has files so let’s just initialize an empty repository in our home directory, set up our origin, and fetch.

cd ~
git init
git remote add origin git@github.com:example/home-dir.git
git fetch
remote: Enumerating objects: 7, done.
remote: Counting objects: 100% (7/7), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 7 (delta 0), reused 7 (delta 0), pack-reused 0
Unpacking objects: 100% (7/7), 1.28 KiB | 41.00 KiB/s, done.
From github.com:example/home-dir
 * [new branch]      linux      -> origin/linux
 * [new branch]      master     -> origin/master
 * [new branch]      windows    -> origin/windows

I’m assuming during initial setup you only copied files from your real home directory, so there shouldn’t be any changes introduced by checking out our branch. However, to do things as safely as possible we’ll still want to inspect the diff between our current home directory and what the repo will set things to.

To view the diff we’re going to soft reset to the remote branch we want to be on, unstage all of our local changes, and then show a git diff. The soft git reset won’t allow us to add any changes since our HEAD is still pointing to an empty master branch, but we’ll learn how to work around that in Part 2.

git reset --soft origin/linux
git reset HEAD .
git diff
diff --git a/.gitconfig b/.gitconfig
index 6249887..eda2d3c 100644
--- a/.gitconfig
+++ b/.gitconfig
@@ -1,3 +1,3 @@
 [user]
- name = Scott Evtuch
+ name = scott evtuch
  email = scott@example.net
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 461f989..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-*
-!/.gitconfig
-!/.gitignore

Looking at the diff we can see that the only changes between our current home directory and the one provided by the repo are:

  1. My name is capitalized in the repo but not in home
  2. The repo introduces .gitignore

The .gitignore file is obviously expected and we probably don’t care about the name capitalization so let’s do a git checkout for real. This action will permanently destroy any changes that you saw in the diff.

git reset --hard origin/linux
HEAD is now at 443e495 Add .gitconfig
git checkout linux
Switched to a new branch 'linux'
M       .gitconfig
D       .gitignore
Branch 'linux' set up to track remote branch 'linux' from 'origin'.

Conclusion

Congratulations! We’re now on track with the remote branch. This means now we can commit new updates to files and push them up to our remote. In the future if there are changes from other machines we can pull them down and introduce them locally too.

In my example scenario this is where I would start adding the files that are specific to Linux that I don’t want in master. My master branch is the parent for both Linux and Windows so it should only contain files that belong in both.

Check out how to safely clone the repo to a second machine in Part 2.