It’s no surprise I love git. To date, the worst part about git however, was the lacklustre downright shithouse  state of the pre-packaged GUI tools – git gui and gitk.

Well here’s a command-line replacement i’ve devised for gitk which does a reasonable job at visualising a project tree.

git log --oneline --decorate=full --graph --remotes

Sick.

One thing that bugs me when using Git, is that resolving merge conflicts isn’t a seamless process. It involves the fiddly task of opening files which conflicted, then resolving the conflict and then staging the changes.

The part which bugs me the most is having to either type the full filename in order to open it in any editor, OR i have to use the mouse to clipboard the filename and then paste it onto the commandline. Just not straightforward enough.

So i knocked up a quick bash script which makes use of git’s ability to create extensions for git commands. This script issues a rebase (i also have one for merge) and will fire off my preferred editor for editing files outside Visual Studio.

#!/bin/bash
# git-resolve-rebase

git rebase $1 
modified=`git status | grep 'unmerged' | uniq`

if [ -n "$modified" ]; then
	git status | grep 'unmerged' | awk '{print $3}' | uniq | xargs -n1 e
fi

(where e is the shell-script to launch E-Text Editor

To use this extension, all i need to do is:

$ (MyBranch)> git resolve-rebase master

There are many stupid people in this world. This guy is another one of them:
Picture of Xerxes Battiwalla

Yes that’s right, yours truly really screwed up this time.

For future reference (and in a nutshell), git checkout is not the same as git reset, particularly when you throw in the hard option. I know this now, and in fact I knew it when i accidentally blew away half my repository – didn’t make the end result any less pleasing.

Here’s the gitk output of what my tree looked like before the wipe:

Full project history from gitk

I was intending to checkout a previous revision of the repo to show how some code had evolved over time. Normally I would do this from the command line, but this time I (crazily) chose to use (the very disabled) gitk to do this, and blindly chose the “hard” option without thinking. So what I was left with was a very lacking repository and even sorely bruised ego. Goodbye repo history! It’s been nice knowing you….

Wiped Repository

Thanks to the ever-knowledgeable davesquared, there’s a way to recover your git repo once you bone yourself. If you escape the clutches of the gitk demon, the command line interface gives you a better level of control (and content which is crawlable by search engines 😉 )

It turns out that until the repository is cleaned (git gc) and stale objects are cleared, the repo still has the necessary content stored, it’s just not visible. (if the following block looks terrible in your renderer, click through the link to read it properly.)

xerxes@laptop /d/source/dotnet/CodeKatas/.git/logs (GIT_DIR!)
$ cat HEAD
0000000000000000000000000000000000000000 f167fd4068e4b92134964e073f2e69a0cc8fced9 Xerxes  1250928395 +1000 commit (initial): Initial commit of binary search
f167fd4068e4b92134964e073f2e69a0cc8fced9 dd31afc8d793aaa952e032a77675b8e67f6b26bc Xerxes  1251007341 +1000 commit: Removedbin/obj from source control
dd31afc8d793aaa952e032a77675b8e67f6b26bc 0642a5e64a513f8949f7aa9e1d35a298fb713bfc Xerxes  1251007422 +1000 commit: removed.suo from source control
0642a5e64a513f8949f7aa9e1d35a298fb713bfc 0cf0929cfb9a31707b2ae937f0c6e73bd9e5bcb9 Xerxes  1251007479 +1000 commit: Implemented first binary tree implementation. it is shit
0cf0929cfb9a31707b2ae937f0c6e73bd9e5bcb9 6c27c54370bf2b79369dc5ef02ef7338bbf2865a Xerxes  1251010817 +1000 commit: Refactored first implementation to remove unnecessary elements
6c27c54370bf2b79369dc5ef02ef7338bbf2865a 937fcb1c36bf437e0df11847ee579ca60151144d Xerxes  1251021898 +1000 commit: removedresharper settings file from project
937fcb1c36bf437e0df11847ee579ca60151144d b12a95ea3c636615345559979d3fc0e93fecc0bc Xerxes  1251022036 +1000 commit: Rewritten first implementation of b-search to get practice.
b12a95ea3c636615345559979d3fc0e93fecc0bc f57503a6116822c9632e6b4dc6cb423640c6a152 Xerxes  1251028214 +1000 commit: 3rd implementation of binary search using shifted bounds.
f57503a6116822c9632e6b4dc6cb423640c6a152 be510f0f88baa38d8d2e645f568405d052f5bb14 Xerxes  1251028265 +1000 commit (amend):Another rewrite of binary search using shifted bounds.
be510f0f88baa38d8d2e645f568405d052f5bb14 2c0b21e7aea2dd27fc7281d1a20a887e2e1f3d0d Xerxes  1251090393 +1000 commit: Yet another re-write of the shifted boundary method.
2c0b21e7aea2dd27fc7281d1a20a887e2e1f3d0d d2a23e1cad8b36de1361700b89733a3b08401e2c Xerxes  1251253854 +1000 commit: Moved BinaryTree project to top-level folder
d2a23e1cad8b36de1361700b89733a3b08401e2c 4e8bd70336e89f9000f29553d53d98cb568bc809 Xerxes  1251253901 +1000 commit: Added nunit to the list of dependencies
4e8bd70336e89f9000f29553d53d98cb568bc809 551c754f27f10826dab43c50d8a3151b7e2740f5 Xerxes  1251253922 +1000 commit: Implemented FizzBuzz
551c754f27f10826dab43c50d8a3151b7e2740f5 11884f5935c85d4ec1497fe3a8eb39211d731fdc Xerxes  1251269688 +1000 commit (amend):Moved parameters onto FizzBuzz method and out of constructor
11884f5935c85d4ec1497fe3a8eb39211d731fdc cbda7a8c68c88900eb1c58ab7bf613bad26892ad Xerxes  1251269732 +1000 commit (amend):Implemented FizzBuzz solution
cbda7a8c68c88900eb1c58ab7bf613bad26892ad b40c951696a2d11854fce6a9fca1be9fc436e61b Xerxes  1251341643 +1000 commit: Anotherre-implementation of the shifted-boundaries method
b40c951696a2d11854fce6a9fca1be9fc436e61b b2c2a8b103aa43dac8e443cfc866fa35f9ffb048 Xerxes  1251341967 +1000 commit: RenamedBinarySearch to ShiftedBoundariesBinarySearch
b2c2a8b103aa43dac8e443cfc866fa35f9ffb048 30a224eb40c441df8a18b06eeb68af47a86bc37f Xerxes  1251344036 +1000 commit: Implemented RecursiveBinarySearch (badly). requires refactor
30a224eb40c441df8a18b06eeb68af47a86bc37f 0fbf8ace753f408b0e972b593e2b6a03dd2d0354 Xerxes  1251345463 +1000 commit: CreatedTreeNode Search
0fbf8ace753f408b0e972b593e2b6a03dd2d0354 0cf0929cfb9a31707b2ae937f0c6e73bd9e5bcb9 Xerxes  1251345505 +1000 0cf0929cfb9a31707b2ae937f0c6e73bd9e5bcb9: updating HEAD
0cf0929cfb9a31707b2ae937f0c6e73bd9e5bcb9 2c0b21e7aea2dd27fc7281d1a20a887e2e1f3d0d Xerxes  1251345635 +1000 checkout: moving from master to 2c0b2

xerxes@laptop /d/source/dotnet/CodeKatas/.git/logs (GIT_DIR!)
$ 

The beauty here is that the log has kept full record of all the SHAs for each commit in the repo. NOW i’m able to reset my master back to the appropriate commit by using the SHA-1 hash. So i checked out onto the master branch and issued:

xerxes@laptop /d/source/dotnet/CodeKatas (master)
$ git reset --hard 0fbf8ace753f408b0e972b593e2b6a03dd2d0354 

and that reset my master back to the right revision, thereby restoring my history, code and sanity.

nb: i have taken some of these screenshots trying to reproduce after the event, so they might look a little doctored. Despite this, the findings and end results are the same.

I’ve used a lot of SCM‘s in my time, and none of them have been as esoteric as Git. This post serves as a reminder of the different ways to “revert” changes to a git repository

Scenario:

We are in directory with a local git repository. This repo contains 4 files and no sub-directories. Each file is in one of the 4 different states a file could be in for Git (not considering ignored files for the time being)

File Name State
Unchanged.txt File is unchanged in local directory
New.txt File is new to the repo
Deleted.txt File has been deleted from local directory
Modified.txt File has been modified in the local directory

All files except New.txt are being tracked and none of these changes have been staged/committed (yet).

Action: checkout

$> git checkout
  • This command has an implicit head of [HEAD]
  • This command has an implicit working file/directory of the CWD
  • This command will only affect tracked files
File Name State
Unchanged.txt no action
New.txt no action (because it’s untracked)
Deleted.txt no action (will not restore the file UNLESS explicitly stating file in command)
Modified.txt no action (will not restore the file UNLESS explicitly stating file in command)

Action: checkout <file/path>

$> git checkout .
$> git checkout Deleted.txt
  • This command has an implicit head of [HEAD]
  • This command has an explicit working file/directory of the path on line 1, and of the file Deleted.txt on line 2
  • This command will only affect tracked files
File Name State
Unchanged.txt no action
New.txt no action (because it’s untracked)
Deleted.txt File is reverted back to its original from the current head
Modified.txt File is reverted back to its original from the current head

Action: reset

$> git reset .
  • This command has an implicit head of [HEAD]
  • This command has an explicit working directory of the CWD
  • This command will unstage staged changes
File Name State
Unchanged.txt no action (changes are unstaged)
New.txt no action (changes are unstaged)
Deleted.txt no action (changes are unstaged)
Modified.txt no action (changes are unstaged)

Action: reset (with staged content)

$> git add .
$> git reset
  • This action assumes that all changes have been staged (line 1), so the repo is in the following state:
    File Name State
    Unchanged.txt File is unchanged in repo
    New.txt File is staged for adding
    Deleted.txt File has been staged for deletion
    Modified.txt File has been staged with modification
  • This command has an implicit head of [HEAD]
  • This command has an implicit working directory of the CWD
  • This command will unstage staged changes
File Name State
Unchanged.txt no action (changes are unstaged)
New.txt no action (changes are unstaged)
Deleted.txt no action (changes are unstaged)
Modified.txt no action (changes are unstaged)

Action: clean

$> git clean -d -f
  • This command has an implicit head of [HEAD]
  • This command has an explicit working directory of the CWD
  • This command will remove files and directories which are untracked in the repo
File Name State
Unchanged.txt no action
New.txt File would be deleted
Deleted.txt no action
Modified.txt no action

So in summary, if you want to completely revert your working directory to a clean state (IE: the equivalent of an svn revert) is to:

$> git clean -fd
$> git checkout .

CruiseControl.NET is an automated build system ported from Java to the .NET framework. The current stable release of CCNET is v1.4.3. Unfortunately this version of CCNET does not natively support using Git as a source control provider. So if you’re making the switch from (say) SVN or VSS, at the time of writing, you will have a few bumps in the road ahead. NB: This page assumes you have a working copy of git running on your machine

To get Git working with CCNET, I found the excellent ccnet.git.plugin project on Github. This code is a plugin for CCNET which exposes basic functionality (and a little more) to allow CCNET to use Git as a source repository.

Firstly you need to download said source and compile the binaries. In case you’re super lazy, here’s one I prepared earlier – ccnet.git.plugin binary download

The plugin works by dropping it straight into your CCNET server’s folder with the other binaries. In most cases, this will be c:Program FilesCruiseControl.NETserver. Make sure your restart CCNET.

The next thing is to configure your project to use git as the source control provider. The README has an excellent example of how to configure the project. My initial project block ended up looking something like this (renamed to protect the innocent):

  <project name="FittingApp.Project" queue="FittingApp.Project">
    <sourcecontrol type="git">
	<repository>git@bumblebee:FittingProject.git</repository>
	<timeout>30000</timeout>
	<executable>c:program filesgitbingit.exe</executable>
	<workingDirectory>C:buildprojectsFittingApp.Project</workingDirectory>
    </sourcecontrol>
	
    <triggers>
    </triggers>
	
    <tasks>
    </tasks>

    <publishers>
      <xmllogger />
      <statistics />
    </publishers>
  </project>

One important thing to note is that the README (at the time of writing) doesn’t mention the timeout element you can use in your configuration. The default value is quite high. I prefer to lower it and found this property by perusing the tests.

Finally after all that, everything should be done and ready to rock, right? Turns out not so. One problem I stumbled into (and took a while to resolve) was the build timing out when it was doing a fetch. The CPU was idle and there was no traffic over the network. The process would timeout and the build would fail. The funny thing was that I could open a command-prompt console myself and fetch the remote repo no problem. But when being performed by CCNET, it would timeout during the fetch.

Afer digging further, it looked like the SSH authentication wasn’t working and that the auth process didn’t accept the default SSH credentials I created earlier. I suspect it was waiting for me to enter a password for the remote git account. Of course there’s no interaction with this process so eventually it times out. After a long back-and-forth with the problem, I got in touch with the author of the plugin and he suggested checking that the HOME environment variable is set to %USERPROFILE%, otherwise git wouldn’t be able to find the git config settings. This solved the problem, and the build started working sweet. (big props, Kevin – thanks :))

With all that done, you should now be staring down the barrel of a CCNET installation successfully talking to Git. Hope this helps someone else out there.

I’m in a situation where I want to keep different settings for several Git repositories. My work’s Git repo and settings (like email address and private key) would be different to my GitHub email address and key.

After following the setup details on GitHub of how to setup username and email for github, and providing your SSH keys , I was left in an awkward situation where my global configuration was setup for GitHub, but didn’t know how to configure my work repo to authenticate properly.

It turns out that Live search does actually work for one scenario, and I found another guide on GitHub explaining everything required to configure multiple Git accounts.

  1. What’s most important is knowing that unless you’re using the same public/private key pair, you will need to generate a new key for the server, and give it a filename different to the default id_rsa
    $ ssh-keygen
    Generating public/private rsa key pair.
    Enter file in which to save the key (/c/Documents and Settings/Xerxes/.ssh/id_rsa): /c/Documents and Settings/Xerxes/.ssh/id_rsa_github

    This file needs to be given a name different to the default id_rsa, ideally consisting the name of the repo.

  2. Once the key is generated, you need to create a config file in your ~/.ssh/ directory. This file allows you to configure connection settings per repository, overriding the global values set earlier.
    Host github.com
      HostName github.com
      User git
      IdentityFile ~/.ssh/id_rsa_github
    

    Save that file.

  3. One final step in the mix is to configure the repo itself to use the correct email address when committing to the git repo. This is really only to ensure that the commit history has a valid email address associated to it. For instance, I don’t want my private email address being recorded in my work commit logs, and similarly I don’t want my work email address getting recorded in my GitHub commit logs.

    I’m sure there would have to be a way to do this using the console, but the way I know to set the email address for a single repo is to use the git gui command, goto Edit -> Options and do it via the interface.

    git gui repo configuration

    git gui repo configuration

Now you should be right to issue any commands to GitHub and have it authenticate using the key. When you push back to the origin, it will now also use the repo settings and not the global settings.

EDIT: For some reason, i omitted the “.com” in the github.com host entry. Thanks to @davetchepak for the pickup