Monday, November 9, 2015

How to move TFS repository to git

At the company I work for we decided to split one gigantic TFS repository into several smaller ones. We use on prem TFS server which supports multiple git repositories within one project, thus allowing us to track all work items in one place. Unfortunately it’s not the case with TFS repository - you can have just one per project, at least with TFS 2015 (at least prior update 1).

We had the following requirements for new repos: when we split repository we wanted to preserve all version history for specific file/folder/sub-folder, we wanted to preserve association of commits with work item numbers.

Here are steps we took. I’m sure there many solutions, here is just one of them.

1. Convert TFS repository to local git repository using an awesome git-tfs bridge (http://git-tfs.com/):
git tfs clone http://tfs:8080/tfs/collection $/my_project

Depending on repository size it might take a lot of time to get entire version history, so keep that in mind.

2. TFS associates git commits with work items using # sign included into commit message. For example “my commit #105” will associate work item #5 with current commit. git-tfs bridge stores all TFS -> work item associations in git notes, so we need to extract them and append to commit message:

git filter-branch -f --msg-filter 'cat && (git notes show $GIT_COMMIT | grep -E "\[(.*)\]" | sed "s/.*\[\(.*\\)\].*$/#\1/g")' -- --all

3. git-tfs also adds original tfs id references to commit messages, so it makes sense to remove them just to keep commit message cleaner:

git filter-branch -f --msg-filter 'sed "s/git-tfs-id:.*$//g"' -- --all

4. The last step is to actually delete all code that is not required for new repository including version history, but preserve history for the rest. For example if TFS repository contains folders CodeA, CodeB, CodeC, CodeD and only CodeA and CodeD are required, use the following command:

git filter-branch --index-filter 'git rm --cached -qr -- . && git reset -q $GIT_COMMIT -- CodeA CodeD' --prune-empty -- --all

So new repository is ready, just push it to your new location.