Git for Centralists

@for Developers who like a central server
@author Kai Ruhl
@since 2012-05

Abstract

Git [wiki] is a versioning system much like Subversion (SVN) or CVS. The main difference is that each checkout has a full repository (not only the current HEAD state). You change things, commit to your local repository very often, and at the end of the day commit your local repository against the server repository. In this article, I describe a day with a new project.

1 Global Configuration

Each commit carries your username and email address. Since you do not want to enter them every time, do this in a terminal:

git config --global user.name  "<your name>"
git config --global user.email "<your email>"

This will save them in ~/.gitconfig (there are also /etc/gitconfig for system-wide settings and .git/config in each repository). You will want colors in the terminal, and aliases for things that you will type over and over again:

git config --global color.diff auto
git config --global color.status auto
git config --global color.branch auto
git config --global --add alias.st status
git config --global core.whitespace cr-at-eol
(Windows only)

... and you are good to go.

2 Server Project and Client Checkout

Usually git books will tell you to start with a local repository but we are centralists here. On the server, create a directory (e.g. /github/repo.git) and go into it. Then:

git init --bare
chgrp -R gitusers .
chmod -R o-rwx,
g+rw .

Under the assumption that you have a group gitusers and all of them shall be able to read and write here. On the client then, you will want to checkout the project.

git clone ssh://user@host.com/github/repo.git/

Note that there is no ":" between host and directory, unlike most SSH interactions. To get changes back the the server, commit locally (see below) and then push ("commit") to server, but only after you have performed a pull ("checkout").

git pull origin master
git push origin master

Where origin is the name of the server entry (look at .git/config for a section [remote "origin"]) and master is the current (and so far only) branch (see [branch "master"]). If you have only one branch and only one remote (server), you can just type git pull and git push after the first time. And if you had the local repository first and cannot use git clone, use this (clone would have done that automatically):

git remote add origin ssh://user@host.org/github/repo.git/
git config branch.master.remote origin
git config branch.master.merge refs/heads/master

If you want to add a second server, say your homeserver, you can set up a second remote home (instead of origin).

git remote add home ssh://homeuser@homeserver.org/github/repo.git/
git push home master

Where you always have to specify at least git push home, since there are now two remotes [more]. And that would be the client/server part!

3 A Day in Programming

There are two ways to go about this: Either you can remember things or you cannot. If you can, do this at the start of each day:

git branch test
git checkout test

You create and change to a branch with this (type git branch to see a list). You can omit this step, but then you are still on branch master. So you edit and edit, and want to commit something:

git add <file_or_dir>
git commit -a -m "My comment."

Adding will make git aware of this file, but only commit (-a for all) will actually put it into the local repository (type git status and git diff to see the changes). Unlike SVN, there is only one .git directory regardless of the number of subdirectories, and it does not matter where you are in the path: git will always go up until it finds .git.

So somebody stops by, and you want to show your work. Of course your current status is non-running, so you change back to branch master. After showing, you get back to test.

git checkout master
...
git checkout test

Or, if you are still on the master branch, you stash your changes ("put it on the stash"), and re-stash it once you are done:

git stash
...
git stash apply

So the day ends. One final git commit. If you have a test branch, you merge all of it into master before deleting it (go into master, get everything from test).

git checkout master
git merge test
git branch -d test

This "replays" all the changes you made to test in the current branch, which is master. After that, the usual git push will bring all changes to server. Day done!

4 Visualizations

To assess your current situation, you already have git status, git diff, and git branch. To get the history, use:

git log
git show 8df7bb16484c7bd2d895acbea7f1338d66829416
git show-branch --more=10

The log shows the entire history in summary form. Each commit has a 40-char identifier, which show elaborates on. Finally, to see a shortened history of 10 entries, show-branch concentrates on the current branch.

For more, install qgit, a graphical interface. And there is gource for creating nice videos.

5 SVN

If you have to do it, checkout, update and commit with [more]:

git svn clone <svn path>
git svn rebase
git svn dcommit

Make sure that the checked-out repo is NOT EMPTY. Otherwise git svn rebase and dcommit will fail with a cryptic message about not being able to find the remote head.

If you are not working alone, ensure you are on a branch apart from the master branch, otherwise there will be havoc when somebody changes the same file as you. Pull from the remote SVN repo with the following (assuming you created a branch mywork):

git checkout master
git svn rebase
git checkout mywork
git rebase master

So you will be working in branch mywork, rebased to master, which in turn is rebased to the remote SVN repo. After you have commited your changes in branch mywork, first pull as above, then push as follows:

# MAKE SURE you pulled first!
git checkout master

git merge mywork
git svn dcommit

So after rebasing both master and mywork, you switch to master and fast-forward your mywork changes over potential changes made by other people.

6 Large Files Removal

Once a big file is in the git, it is hard to completely remove it since that means history rewriting. Try this [forum]:

export RM_GIT=".git"
export RM_DIR="python/res"
git filter-branch --prune-empty --index-filter 'git rm -rf --cached --ignore-unmatch $RM_DIR' --tag-name-filter cat -- --all
rm -Rf $RM_GIT/refs/original $RM_GIT/logs
git reflog expire --all --expire='0 days'
git fsck --full --unreachable
git repack -A -d
git prune
git count-objects -v |grep size-pack

You need to be at the root of the repository. For a bare repository, RM_GIT="." instead of ".git". The filter-branch rewrites history, the rest of the commands expires, unlinks, repackages, and prunes everything in an unlikely turn of events. Lastly, count-objects will verify whether the removal was successful.

Do this both on the remote and the local repository. Then git pull and push until nobody complains anymore.

Conclusion

Git is far cooler than previous versioning approaches. While everything I have shown you here relies on the command line, most IDE integrations (e.g. EGit for Eclipse) should be able to do the same. Enjoy your day! (man-page generator, X-men: days of future past)

EOF (Dec:2015)