A place for spare thoughts

06/12/2012

Setting git to work behind NTLM-authenticated proxy: cntlm to the rescue

Filed under: git — Ivan Danilov @ 20:23

Our office has corporate proxy – and that’s pretty common as far as I know. And it happened that our proxy has NTLM authentication which is not very common/popular among linux users (that’s an understatement, I know). So git and msysgit don’t support it out-of-the-box.

Thus, if you try to clone something over SSL behind such proxy, say, from github – you’ll see something like below:

$ git clone https://github.com/libgit2/libgit2sharp.git
Cloning into 'libgit2sharp'...
error: Failed connect to github.com:443; No error while accessing https://github.com/libgit2/libgit2sharp.git/info/refs
fatal: HTTP request failed

And it will take almost forever by the way (dozen of minutes in my case).

Well, to solve the problem you may specify proxy manually like that:

$ git config http.proxy http://domain\\\user:password@proxy.MyCorp.com:8080/

Note three backslashes, it is not a mistake.

But you already see the problem, don’t you? git config is saved in plaintext and I’m not very like the idea to store my password like that, even if in some location where nobody has access rights (domain administrators could see everything in the domain, remember?). And passing token instead of login/password is beyond msysgit abilities.

You may think: if I’m able to get to github in browser, even over SSH, then why git can’t? Maybe there’s some way to intercept git’s queries and wrap them through correct proxy/auth headers? And you’d be right: there is tool capable of that, name is cntlm.

Some time ago cntlm had have a bug preventing it from working in exactly the same situation: http://stackoverflow.com/questions/6345104/proxy-through-cntlm-why-git-clone-hangs

Fortunately, now the bug is fixed (at the time of writing, latest cntlm version was 0.92.3). So, what it is and how you can use it?

Cntlm is the proxy itself, that you run on your local box and when it receives a request – it re-sends it to so-called ‘parent proxy’ (which is exactly your NTLM-authenticated beast) with correct auth headers.

First, you need to download distributive and install it. In the installation folder you will find cntlm.ini file, which you want to edit. You have to set correct Username, Password and auth tokens and remove Password field (as you don’t want to store password in the plaintext). Then run command line and run these:

$ cntlm.exe -H -u username -d domain
Password:
PassLM          1AD35398BE6565DDB5C4EF70C0593492
PassNT          77B9081511704EE852F94227CF48A793
PassNTLMv2      B78FD04127AEDF090C1F7121ED002A4D    # Only for user 'username', domain 'domain'

Just paste these PassNTLMv2 string to the cntlm.ini (don’t forget to type your own username and password instead of the ‘username’ and ‘password’ strings above). Don’t touch PassLM or PassNT tokens if your system supports NTLMv2 because NT and LM are not secure.

It would be all of the configuration. Run ‘cntlm.exe’. You could try ‘cntlm.exe -v’ to activate verbose mode and have much info on the console and see if something went wrong. If you run cntlm without params – it will go to daemon mode and will silently run in background, listening for 3128 port (by default – can be changed in cntlm.ini easily). And by default it won’t accept connections from others non-local programs, so that your box will not become a gateway for everything possible if someone knows you’re running cntlm (that can be changed as well).

Now we need to make git use it. It’s easy:

$ git config --global http.proxy localhost:3128

$ git clone https://github.com/libgit2/libgit2sharp.git
Cloning into 'libgit2sharp'...
remote: Counting objects: 7992, done.
remote: Compressing objects: 100% (2160/2160), done.
remote: Total 7992 (delta 5944), reused 7634 (delta 5680)
Receiving objects: 100% (7992/7992), 39.44 MiB | 217 KiB/s, done.
Resolving deltas: 100% (5944/5944), done.

That’s it. In case you’re interested in detail info about cntlm command-line parameters, here it is.

23/08/2012

My environment for day-to-day work with git-tfs

Filed under: git, git-tfs, tools — Ivan Danilov @ 16:22

I’ve been asked many times to describe how things are organized at our project with git-tfs, so it’s the time to write it down – repeating these things over and over isn’t effective at all.

So, below I will describe how things are arranged to make TFS2010, msysgit and git-tfs work together.

First, setup involves several physical boxes, so lets name them at once:

  1. DevA, DevB – two developer’s boxes;
  2. GitServer – Windows Server 2008 R2 box with IIS – hosts central git repository, exposes git repositories over HTTP and has scheduler task to synchronize central git repository with TFS;
  3. TfsServer – remote host with TFS Server 2010 (it is our corporate beast and I don’t have administrative access there);

I will assume you already installed git and git-tfs to your machines. So, let’s start.

 

1. Cloning TFS repository first time

First, you have to clone repository from TFS somewhere. On your DevA box open bash console, go to the folder where you want sources reside and execute this:

git tfs clone -d http://TfsServer:8080/tfs $/ProjectA/Src proj-git

or maybe, if you don’t interested in full history and want to have just last TFS sources state:

git tfs quick-clone -d http://TfsServer:8080/tfs $/ProjectA/Src proj-git

It may take up to several hours if you have large repository and slow connection. If everything is fine – folder proj-git will appear there. Inside are your working copy with latest TFS version and folder .git with configuration.

 

2. Setting up GitServer and auto-update from TFS

git-tfs doesn’t work with bare repositories (at least it was so at the time I followed its development), thus we will need two repositories for each TFS one: first will be bare, and accessible through HTTP; and second one would be private – there we will setup scheduled fetching from TFS server, each time new changes are found in TFS – we will push them to first repository. To distinguish these two I will refer to first as Central and second as Working.

  1. Install IIS server role
  2. Install GitWebAccess. As I wrote before last version seems broken, so you can download my working version. Unpack it to C:\inetpub\wwwroot\gwa, and follow this manual. I will suppose you created folder C:\GitRepos for your git repos as suggested in this manual.
  3. Create new folder for Working repo somewhere. For this example it would be C:\working
  4. Copy content of proj-git folder (where you’ve cloned TFS repository on 1st step before) to C:\working and execute
    git checkout -B master tfs/default
    

    thus master branch will be set at your last TFS commit

  5. Add C:\working\.gitignore file if it is not present with these lines:
    /update
    /log.txt
    

    If file is present – just add such lines there. update is the script we will write next and log.txt… well, it is log of updating. As updating will be executed by scheduler – it is the only way to know what’s happening.

  6. Now we will need bare repository in the C:\GitRepos\Project.git. Execute from C:\GitRepos these command:
    git clone --bare /c/working Project.git
    git remote rm origin # we don't need origin here
    

    If it was done correctly – you should have your repository accessible by HTTP through GitWebAccess.

  7. Now it’s the time to setup update in Working repository. The convention I embrace is that master branch of my repository will always follow TFS repository line. Thus, no one except update script should push it directly and it will never have changes conflicting with TFS data.
    You need to setup origin remote in the Working repo in order for script below to work:

    git remote add origin /c/GitRepos/Project.git
    

    Next, create file C:\Working\update and put these lines to it:

    #!/bin/sh
    
    check_err()
    {
    	# parameter 1 is last exit code
    	# parameter 2 is error message that should be shown if error code is not 0
    	if [ "${1}" -ne "0" ]; then
    		cat '~temp.log'
    		echo ${2}
    		rm -f '~temp.log' > /dev/null
    		exit ${1}
    	fi;
    	rm -f '~temp.log' > /dev/null
    }
    
    echo "$(date) Update launched"
    
    if [ ! -z "$(git status --porcelain)" ]; then
    	echo "$(date) Your status is not clean, can\'t update"
    	exit 1;
    fi;
    
    echo "$(date) Pulling from central repo first to avoid redundant round-trips to TFS..."
    git pull origin master:master > '~temp.log'
    check_err $? "Pulling from central repo failed"
    
    echo "$(date) Pulling from TFS..."
    git tfs pull -d > '~temp.log'
    check_err $? "Pulling from TFS resulted in error";
    
    local_commits_to_push="$(git rev-list master ^origin/master)"
    if [ -z "$local_commits_to_push" ]; then
    	echo "$(date) Central repo is up-to-date, nothing to push"
    else
    	echo "$(date) Pushing updates to central repo"
    	git push origin master > '~temp.log'
    	check_err $? "Push to central resulted in error";
    fi;
    

    You may check it – just execute update from bash console. Your latest updates should be fetched from TFS. In case there were some changes – they should be also pushed to Central‘s master branch.
    It is simplified version of the script from this post. Also there you can find how to create scheduler task to execute this update script every 5 minutes – do it now.

 

3. Create rcheckin and reap the benefits

Now, back to the developer’s environment. We want to use that auto-updating central repository effective, don’t we? So:

  • We don’t need to use git tfs fetch at all anymore: we may limit ourselves to git fetch because latest code is always in the Central git repository.
  • When git tfs rcheckin is executed – it fetches latest changes from TFS. We need to reuse as much as possible from already fetched changes. So, we need to fetch manually from Central before executing git tfs rcheckin and somehow make git-tfs mark it as correct TFS-fetched commits.
  • And finally, after we checkined something to TFS – we want to push it to Central, so auto-updater won’t fetch what we already have – it could have significant performance impact in case of large commits, or long line of commits with slow connection. In such case we may even want to disable auto-updater for some time until we push things to Central and then just re-enable it.

So, in order to achieve all these points – instead of using git tfs rcheckin that doesn’t know anything about our environment – we will use custom script rcheckin (do not forget to add it to .gitignore if needed):

#!/bin/sh

check_err()
{
	# parameter 1 is last exit code
	# parameter 2 is error message that should be shown if error code is not 0
	if [ "${1}" -ne "0" ]; then
		cat '~temp.log'
		echo ${2}
		rm -f '~temp.log' > /dev/null
		exit ${1}
	fi;
	rm -f '~temp.log' > /dev/null
}

if [ ! -z "$(git status --porcelain)" ]; then
	echo "Your status is not clean, can't continue"
	exit 1;
fi;

echo "Fetching origin..."
git fetch origin

if [ -n "`git rev-list HEAD..origin/master`" ]; then
	echo "origin/master has some TFS checkins that are conflicting with your branch. Please reabse first, then try to rcheckin again"
	exit 1;
fi;

echo "Marking latest TFS commit with bootstrap..."
git tfs bootstrap -d

git tfs rcheckin
check_err $? "rcheckin exited with error"

git push origin HEAD:master
check_err $? "Can't push HEAD to origin"

Well, that’s almost all.

 

4. Setup another developer environment

So, suppose DevB also needs to work with your server. Here is how he can start his work easily:

  1. git clone http://GitServer:8080/project.git project
    cd project
    git tfs bootstrap -d # make git-tfs mark what is required
    
  2. Get rcheckin script from previous section.
  3. Work!

 

5. My common workflow

So, I’ll try to describe how I work in general, supposing I’m in my dev repository, set up as described above.

  1. First, let’s check that no changes appeared while I was writing all of these: git fetch origin. OK, no changes.
  2. Working, making changes hard… committing several times, squashing, rewriting etc.
  3. Ready to push to TFS? Great! Let’s check again – maybe someone pushed something while I have been working: git fetch origin. Yeah, there’re some changes, origin/master has updates. Let’s rebase my work there (I don’t like merging my local work which is not seen by anyone except me – it just clutters commit graph): git rebase origin/master, maybe some conflict resolving. Hey, I’m ready to push: rcheckin.
  4. Yet some work.
  5. Working time is over and I have not finished my current task. I want it backed up on server, so my local machine failure can’t waste my work: git push origin HEAD:idanilov/temp. idanilov is my ‘private’ branch namespace, every developer has one. By convenience it is just backups and it is not considered published. For open-source projects you probably won’t do this, but corporative rules sometimes require some assurance that you won’t lost your work that is already paid for.

Sometimes I may switch branches, stash changes and do other git stuff, but it is not so frequent. Most often all of my workflow is fetch-rebase-rcheckin chain.

I wanted to explain yet some things – e.g., how to have a bunch of bash scripts in a way that it doesn’t get to TFS (it’s easy, just use .gitignore) and have a way to synchronize them across all developers using git (it’s much harder if you don’t want to bother running to every developer, copying new versions of scripts each time you want to correct one line in some script). Or how to automate git console, so you don’t need to type long commands every time which is pretty boring. But… this post is already pretty long, so I will do this later.

Have fun moving away from TFS with git and git-tfs! 😉

16/08/2012

Git-Web-Access: access git through IIS WebApp

Filed under: git — Ivan Danilov @ 05:00

I’m using subject to host my repositories and in general it works well. It was the only really working git HTTP server on windows at the time (at least I hadn’t found others then).

Today I was installing GWA on another server and was suddenly struck with errors. After quick check I found that binaries from codeplex are not correspond to latest sources (namely, ILSpy shown that GitHandler.cs was without line context.Response.Charset = null; which is critical for working with JGit-based clients including TeamCity or Eclipse’s egit plugin).

And after I downloaded sources and built them… well, it wasn’t working at all:

$ git fetch
fatal: protocol error: bad line length character:
00a

Same it TeamCity. So, I reverted to my own last commit and rebuilt. At that point I got working tool, at last.

It seems GWA is abandoned – undeservedly so, IMO. In any case – if you want Git HTTP Server on Windows – either build it from working sources or download here.

19/07/2011

Double-star pattern in .gitignore

Filed under: git — Ivan Danilov @ 14:36

Today I was asked why pattern /folder/**/*.abc doesn’t work. The goal is pretty clear here – to ignore every file with abc extension in the folder and its subfolders.

Countless tries lead to nothing. Nevertheless here and there I’ve found several mentioning of the double-star pattern, so I thought there should be some way to get it working. Then I encountered this blog post. Mentioning slash in the pattern remembered me something and I rechecked gitignore(5) manual page:

  • If the pattern does not contain a slash /, git treats it as a shell glob pattern and checks for a match against the pathname relative to the location of the .gitignore file (relative to the toplevel of the work tree if not from a .gitignore file).
  • Otherwise, git treats the pattern as a shell glob suitable for consumption by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will not match a / in the pathname.

Turns out git uses two different approaches to handling ignore pattern depending on slash symbol presence in the pattern! It was a surprise. After checking fnmatch(3) desctiption and sources I assured that double-star pattern is unsupported by .gitignore. At least directly.

What git DOES support is hierarchical .gitignore files, i.e. having several .gitignores at different locations in the folder structure. So the equivalent solution to using /folder/**/*.abc pattern would be placing another .gitignore file into /folder/ and having pattern *.abc there.

Well, I’d still prefer single .gitignore if it is possible because it is easier to manage single file… but several of them also solve the problem well. And so be it.

UPDATE: Since git 1.8.4 according to the announce by JC Hamano (last point):

Use of platform fnmatch(3) function (many places like pathspec matching, .gitignore and .gitattributes) have been replaced with wildmatch, allowing “foo/**/bar” to match “foo/bar”, “foo/a/bar”, etc.

UPDATE2: msysgit (git on windows) supports this feature since version 1.8.2 according to RelNotes.

18/07/2011

How to establish git central repository for working against TFS with git-tfs bridge

Filed under: git, git-tfs — Ivan Danilov @ 17:40

As I described here previously my goal was to establish central git repository and avoid redundant round-trips to TFS whenever possible.

To achieve this now you probably want to follow my advise in the above mentioned article about having user.name=<tfs account name without domain> and same user.email for everyone fetching changes from TFS.
In the latest git-tfs sources this was already fixed, so you don’t need to set this kind of things.

When I established central repository I faced some inconveniences: firstly pattern “checkout master, check central repository for changes already there but not in your local repository, pull new changes from TFS, push to central repository if anything new was pulled, checkout old working branch again, optionally rebase it onto master” was very boring and very repeatable. And the second inconvenience: this pattern should be repeated each time something new appears in TFS. I want to have some scheduled updating so I wouldn’t think about such things as when and how I should update latest version etc.

Well, it is perfect target for automation. So now I want to share my bash script that does these things and saves you several dozens of key-pressing each time you need get changes from TFS.

#!/bin/sh

check_err()
{
    # parameter 1 is last exit code
    # parameter 2 is error message that should be shown if error code is not 0
    if [ "${1}" -ne "0" ]; then
        cat '~temp.log'
        echo ${2}
        rm -f '~temp.log' &gt; /dev/null
        exit ${1}
    fi;
    rm -f '~temp.log' &gt; /dev/null
}

echo "$(date) Update launched"

if [ ! -z "$(git status --porcelain)" ]; then
    echo "$(date) Your status is not clean, can't update"
    exit 1;
fi;

branch_name="$(git symbolic-ref HEAD 2&gt;/dev/null)"
branch_name=${branch_name##refs/heads/}

if [ ! "$branch_name" == "master" ]; then
    git checkout master
fi;

echo "$(date) Pulling from central repo first to avoid redundant round-trips to TFS..."
git pull &gt; '~temp.log'
check_err $? "Pulling from central repo failed"

echo "$(date) Pulling from TFS..."
git tfs pull -d &gt; '~temp.log'
check_err $? "Pulling from TFS resulted in error";

remote_conflicting_commits="$(git rev-list origin/master ^master)"
if [ ! -z "$remote_conflicting_commits" ]; then
    echo "origin/master has conflicting commits, can't push"
    exit 1;
fi;

local_commits_to_push="$(git rev-list master ^origin/master)"
if [ -z "$local_commits_to_push" ]; then
    echo "$(date) Central repo is up-to-date, nothing to push"
else
    echo "$(date) Pushing updates to central repo"
    git push --tags origin master &gt; '~temp.log'
    check_err $? "Push to central resulted in error";
fi;

if [ ! "$branch_name" == "master" ]; then
	git checkout $branch_name
	if [ "$1" == "-r" ]; then
		echo "Rebasing $branch_name on master";
		git rebase master
	fi;
fi;

This script assumes you want master branch to mirror TFS and rebases your working branch onto new changes only if you have specified -r switch. And for the second inconvenience you have just to set-up some dedicated working copy and run this script each, say, five minutes in it. It is very easy to do with built-in Windows Task Scheduler:

  1. Go to Start -> Administrative tools -> Task Scheduler
  2. Click Create Task on the right side
  3. Check radio button ‘Run whether user is logged on or not’
  4. Select Triggers tab, click ‘New’. Set Start=’One time’ and ‘Repeat task every’ whatever, check ‘Enabled’, click OK.
  5. Go to Actions tab, ‘New’. Set Program/script to ‘cmd‘, Add arguments=’/c "sh.exe update >> log.txt"‘ and in the Start in field put path to your working copy, dedicated to central repository updating.
    1. That’s all. Now you should have updates regularly without any efforts. It is important to note though, that script assumes your remote named origin/master and noone pushes conflicting changes to central repo’s master branch.

      And not forget to add /log.txt to .gitignore. Otherwise your updates will just fail to start because of not clean working tree.

21/06/2011

Git resources

Filed under: git — Ivan Danilov @ 17:21

Recently I was surfing the web actively looking for info about git. This post is something like a summary of good (IMO) resources about this DVCS.

Official main page contains useful documentation, FAQ and large list of tools available.

The most comprehensive single resource probably is Pro Git Book

There’s also site with many howtos, tip-and-tricks and the like: Git Ready

Several atricles too:
Why staging area matters?

In praise of Git’s index

A successful git branching model

My Git Workflow by Oliver Steele

What is rebase and how one could use it:
The Case for Git Rebase
A Rebase Workflow for Git

What is NOT a good idea to do in git:
Avoiding Git Disasters: A Gory Story

And my own posts related to git.

Currently my git tool-set consists of:

  • msysgit – actually git’s implementation on Windows.
  • GitExtensions – GUI of choice. Mostly for viewing trees and adding files to staging area.
  • And the last but not least is the tool which made me interest in git in the first place: git-tfs bridge.

There’s also GitSharp – implementation of Git for the .NET. Actually, GitExtensions and git-tfs uses this library internally to manipulate git’s repository.

Other suggestions are welcome 🙂

GitTfs rebasing workflow. Is it possible?

Filed under: git, git-tfs — Ivan Danilov @ 06:55

Here is the issue on GitTfs about some complexities in usage. The point is GitTfs allows you to take a feature branch and put it into the TFS in single checkin. On the git side this action produces merge commit with two parents: one for previous commit fetched from TFS and another from your feature branch. If you want to have fine-grained history on TFS side – you have a problem.

So what could we do with it?

I’ve just finished a patch that allows to perform fine-grained workflow more-or-less painlessly. The idea is new option to checkin command, namely –rebase-workflow or just -r.

Lets name for simplicity commit being checked into TFS as ‘source’ and fetched afterwards as ‘result’. Source belongs to feature branch that we want to check into TFS commit-by-commit.

So what -r key is supposed to do? First and the most important result of -r is that it suppresses marking result as merge commit. Just skips assigning source as parent. So after checkin we’ll have two separate branches, and result will actually contain all changes from source but it won’t be shown in the graph.
See the diagrams below:

  A
[tfs]
     \
      \<-- B <--- C
               [branch]

becomes

  A <----- B'
    \    [tfs]
     \
      \<-- B <--- C
               [branch]

Here B’ has all changes from B.

Just to compare with default behavior:

              [tfs]
  A <---------- M
    \         /
     \       /
      \<-- B <----- C
                [branch]

M is merge commit and B is original. They are just the same change from A most of the time and it is confuses history very much.

So how could we going to turn two diverging branches into linear history? With a rebase of course.
We could take remaining of the local branch (so it is C actually) and rebase it to the B’. It will go smoothly as we are just applying clean patch essentially. Thus B becomes (most likely – if there were no third branches spawned from feature branch) orphan commit and will go away. Which is ok as we have all changes from it in the B’. And it is exactly what -r key tries to do for you.

I did some testing (simplest actually and with some conflicts/interfering with native TFS client) on my local TFS server and it seems working well and producing much more understandable history. But I’m not an expert in git so I could miss some cases. Currently I only check with rev-list that source doesn’t have parents which are not parents of HEAD thus we could apply source..HEAD to result smoothly.

Intended workflow is like that:

git checkout -b local
# make changes
git commit -m 'blah'
# make changes
git commit -m 'blah2'
git tfs fetch
git rebase tfs/default

git tfs checkin -r -m 'blah goes to tfs with rebase' HEAD^
# thus we are sending just first commit leaving history clean

git tfs checkin -r -m 'blah2 goes to tfs also'
# here source is HEAD so we can omit it

git checkout master
git merge tfs/default
git branch -D local

As a result you should have just a linear history in git consisting from TFS commits.

So the comments/objections/suggestions are welcome.

Branch with corresponding changes is here

UPDATE: currently rebase-workflow branch is integrated in mainline of the git-tfs project in form of rcheckin command. So the branch mentioned above is no longer exist.

17/06/2011

GitExtensions command line usability

Filed under: git — Ivan Danilov @ 17:07

I prefer to use git bash command line for manipulations with repository (push, pull, checkout, rebase, merge, git-tfs’ commands etc) and GUI for things like staging, merging files, viewing history, blaming etc. Thus I always have bash console opened at my repository root.
Unfortunately when I tried to run commit process via GUI I have this:

$ gitex
sh.exe": gitex: command not found</blockquote>

From cmd.exe’s console everything is just working. cmd.exe in fact is calling gitex.cmd from GitExtensions’ root folder which is just settings paths and starts GitExtensions.exe with given command line arguments.

Bash just can’t run *.cmd file. Well, it is not a big problem. I just have to write my own script to do the same. It is strange that GitExtensions didn’t include this thing from the start. Or maybe I missed something?

Anyway, you need just file “gitex” (without extension) with this lines:

#!/bin/sh

GitExtensions.exe "$@" &

$@ means to pass to GitExtensions.exe all command line arguments that were passed to the script and & means we want it to be started separately, i.e. mingw console should return immediately and do not wait until GitExtensions.exe process finished.

P.S. As a Windows user I also fallen into a trap of missing “#!/bin/sh” at the beginning and got same message. And ‘chmod a+x gitex’ to make it executable didn’t help either. It seems mingw determines executability of a file just by that header.

16/06/2011

First git-tfs usage problems

Filed under: git, git-tfs — Ivan Danilov @ 21:45

[This post assumes reader is at least somewhat acknowledged with git and git-tfs bridge]

UPDATE: the bug described here is already fixed in git-tfs, so if you have latest version built from sources – it is not relevant for you. v0.11 still has the bug. Probably next version wouldn’t.

TFS doesn’t differ checkins based on author’s email. In fact it doesn’t even know author’s email. And that fact leads to some problems when working with git-tfs bridge as for git author’s email is crucial part of every commit influencing commit SHA-1 hash.

Goals

Suppose you have remote TFS server with slow connection (or – more likely today – it is not very reliable connection and could fail often) and you want to minimize network activity. And TFS server has some developers behind it of course. Naturally with DVCS like git it leads to such desired schema:

Dev1 \                                                     / TFS Dev1
      \        git         [   slow   ]                   /
Dev2 ------- Central ----- [  network ] ---- TFS Server ---- TFS Dev2
...   /     repository     [connection]                   \   ...
DevN /                                                     \ TFS DevN

So when TFS pulling is required – any developer on the left executes git tfs pull (or fetch), and pushes tfs/default branch to the Central so that every other developer on the left could get it without going to TFS.

That is the goal. In the ideal world it would work this way from the begining. Oh, wait! In the ideal world there wouldn’t have been TFS on the schema in the first place 🙂

First attempt

So lets return to reality. Just to test things when not everything going as expected I set up git repo called test-central:

test-central$ git tfs clone tfs_url test-central
--- and make it bare, like that
test-central $ cd test-central
test-central $ mv .git ..    # save .git somewhere (not important where really)
test-central $ rm -fr *      # remove everything in the folder
test-central $ mv ../.git .  # take .git back
test-central $ mv .git/* .   # get everything from .git to the repo's root
test-central $ rmdir .git    # delete .git
test-central $ git config --bool core.bare true  # tell git it is bare repo

Then I created test-dev-1 repository:

test-dev-1 $ git clone test-central test-dev-1
test-dev-1 $ cd test-dev-1
test-dev-1 $ git tfs bootstrap
test-dev-1 $ git config user.name dev-1
test-dev-1 $ git config user.email dev-1@email.com

And test-dev-2 repository:

test-dev-2 $ git clone test-central test-dev-2
test-dev-2 $ cd test-dev-2
test-dev-2 $ git tfs bootstrap
test-dev-2 $ git config user.name dev-2
test-dev-2 $ git config user.email dev-2@email.com

Both devs have the same TFS history cloned from test-central. Now tfs-dev-1 have some changes and sends them to TFS. test-dev-1 spots new changeset in TFS and decides to pull them:

test-dev-1 $ git tfs pull  # suppose fast-forward merge for simplicity

Now this changeset is stored in his local repository with author’s name tfs-dev-1 and (as TFS don’t have emails) author’s email dev-1@email.com. So he pushes this commit to test-central to share it with other developers:

test-dev-1 $ git push

At this time test-dev-2 also spots new changeset. He doesn’t know that dev-1 already got it (or just forgot to check) so he also decides to pull it from TFS:

test-dev-2 $ git tfs pull

His commits have author’s name also tfs-dev-1, but author’s email is dev-2@email.com this time! So his commit from git’s point of view is entirely different from dev-1’s commits. And so…

test-dev-2 $ git push

…results in a conflict.

That seems pretty bad. So to provide commits originated in TFS with ‘shareability’ they should have the same email, right? So probably git-tfs bridge should set email to some predefined value for every commit that originates from TFS changeset.
This way test-dev-1’s and test-dev-2’s commits will both have some identical fake value like TFS@email.com and SHA-1 hashes will be equal and everything will be great. Right?

Second attempt

Apparently it is not so easy (we’re already back to the real world, remember?)

Let me explain with an example a problem I’ve faced an hour ago. The most simple scenario. Single dev, single git local repository, just one new commit. As basic as possible.

At the start git repo is like that (tfs is tfs/default – just shortage):

   A <---- B
        [master]
         [tfs]

Then I make some changes and commit them to git:

   A <---- B <------ C
         [tfs]    [master]

Commit C is normal git commit so it has author='dev' and email='dev@email.com'.

After that I want to checkin my commit to TFS so I execute 'git tfs checkin'. Nothing changes within my git repo. 'git tfs fetch' gets back my commit from tfs. And weird things start to happen…

Commit that came from TFS when we did 'git tfs fetch' (lets call it D for clarity) has author='dev-1-tfs-account-name' and email='TFS@email.com' (as we agreed above). You're already know how graph will look like, yeah? 🙂

   A <---- B <------ C
           \      [master]
            \
             \<----- D
                   [tfs]

That doesn’t seem like fast-forward we were desiring from [tfs] branch… For the same reason as before commit D differs from C. But we want them to be equal! What we need for such outcome to become real, then?
Yeah, even more restrictive rule:

$ git config user.name dev-1-tfs-account-name
$ git config user.email TFS@email.com

Well, TFS@email.com was chosen absolutely arbitrary so you could set it to any fake value you like.

Conclusion

To work more-or-less comfortable with TFS every developer should have git’s user.name equal to TFS account name and all developers should share single email.

P.S. In the last example you could merge C with D, get some commit E (without any conflicts actually as B->C and B->D diffs are absolutely the same)… but than you’ll have even the simplest graph looks like DNA molecule. It’s not what I can call comfortable work.

Troubleshooting: Git Home directory

Filed under: git — Ivan Danilov @ 08:10

After installing GitExtensions it starts to show me error each time complaining about lack of permissions to get .gitconfig from u://.gitconfig. Wow! I didn’t even have a U drive. As it is not very likely that everybody who uses GitExtensions (GE for short further) has U drive – this path should be set somewhere.

When you run GE -> Settings you could see on ‘Git’ tab some text about home directory where GE search for .gitconfig. Well, actually it says that if %HOME% environment dir is present – it will look for .gitconfig there.

So I went to Computer -> Properties -> Advanced system settings -> Environment variables… -> System variables and set there variable HOME to some local path.

But after starting GE again – it still can’t find .gitconfig.

Well, it costed me getting sources and some debugging to figure out that HOME variable is being looked only in _user_ variables, not system ones. It makes sense actually, because HOME is home directory of current user. But I was so accustomed to set system vars, that did it almost automatically.

UPDATE: There’s another issue exists. Make sure %HOME% is not equal to Git’s base directory. The thing is msysgit treats C:\Program Files (x86)\Git (or wherever you have it installed) as root / and adds /bin to it literally – similarly to command PATH=$HOME/bin:$PATH. Thus if your %HOME% is C:\Program Files (x86)\Git you’ll have $PATH starting with //bin: which is obviously incorrect.
This can lead to inability to find extensions like git-tfs.exe and others.

Older Posts »

Create a free website or blog at WordPress.com.