all posts

Git Loves the Environment

One of the things that people that come from the Subversion world tend to find pretty cool about Git is that there is no .svn directory in every subdirectory of your project, but instead just one .git directory in the root of your project. Actually, it’s even better than that. The .git directory does not even need to actually be within your project. Git allows you to tell it where your .git directory is, and there are a couple of ways to do that.

Let’s say you have your project and want to move the .git directory somewhere else. First let’s see what happens when we move our .git directory without telling Git.

$ git log --oneline
6e948ec my second commit
fda8c93 my initial commit
$ mv .git /opt/myproject.git
$ git log
fatal: Not a git repository (or any of the parent directories): .git

Well, since Git can’t find a .git direcotry, it appears that you are simply in a directory that is not controlled by Git. However, it’s pretty easy to tell Git to look elsewhere by providing the --git-dir option to any Git call:

$ git --git-dir=/opt/myproject.git log --oneline
6e948ec my second commit
fda8c93 my initial commit

However, you probably don’t want to do that for every Git call, as that is a lot of typing. You could create a shell alias, but you can also export an environment variable called GIT_DIR.

$ export GIT_DIR=/opt/myproject.git
$ git log --oneline
6e948ec my second commit
fda8c93 my initial commit

There are a number of ways to customize Git functionality via specific environment variables. You can also tell Git where your working directory is with GIT_WORK_TREE, so you can run the Git commands from any directory you are in, not just the current working directory. To see this, first we’ll change a file and then change directories and run git status.

$ echo 'test' >> README 
$ git status --short
M README

OK, but now if we change working directories, we’ll get weird output.

$ cd /tmp
$ git status --short
 D README
 ?? .ksda.1F5/
 ?? aprKhGx02
 ?? qlm.log
 ?? qlmlog.txt
 ?? smsi02122

Now Git is comparing your last commit to what is in your current working directory. However, you can tell it where your real Git working directory is without being in it, either with the --work-tree option or by exporting the GIT_WORK_TREE variable:

$ git --work-tree=/tmp/myproject status --short
 M README
$ export GIT_WORK_TREE=/tmp/myproject
$ git status --short
 M README

Now you’re doing operations on a Git repository outside of your working directory, which you’re not even in.

The last interesting variable you can set is your staging area. That is normally in the .git/index file, but again, you can set it somewhere else, so that you can have multiple staging areas that you can switch between if you want.

$ export GIT_INDEX_FILE=/tmp/index1
$ git add README
$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
# modified:   README
#

Now we have the README file changed staged in the new index file. If we switch back to our original index, we can see that the file is no longer staged:

$ export GIT_INDEX_FILE=/opt/myproject.git/index
$ git status
# On branch master
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified:   README
#
no changes added to commit (use "git add" and/or "git commit -a")

This is not quite as useful in day to day work, but it is pretty cool for building arbitrary trees and whatnot. We’ll explore how to use that to do neat things in a future post when we talk more about some of the lower level Git plumbing commands.