Recently we had a task of converting a Git repo to a TFS. The original source code repository was managed under TFS, then during a trial period we ran it using Git (exported from TFS). However, due to organizational policies we had to switch back to TFS. At the same time our project was assigned a new name in TFS, so we had to create a new TFS source repository and preserve Git commit history. Below is a description of how we managed to achieve this. We used Git-Tf open-source tool released by Microsoft.
1. Cloning an existing Git repo
A new copy of a Git repo was created to be used as an import source. This was done using a trivial Git command:
git clone //gitserver/Source/OurProject.git OurProject.Git.Tfs
So the OurProject.Git.Tfs is the name of a new repository that will be converted to TFS.
2. Configuring Git-Tf to establish a link between Git and TFS repositories
The following command instructs Git-Tf to establish a link between the Git and TFS repos:
git-tf configure http://tfsserver:8080/tfs $/OurProject/Master
3. Importing Git commit history… if it works
The following command should convert to TFS all your Git commits, assuming they have a linear form:
git-tf checkin –deep
Most likely this won’t work for any Git repo with non-rebased branches. Then you have a choice of either import all Git commits as a single changeset (losing the history) or add some more efforts to transform the history into a shape suitable for TFS export. But first…
4. Ensuring Git commit messages are not empty
If you try to rebase a Git repo where some commit messages are empty (bad practice!), you are going to fail. But you can fix the messages by running a script similar to the one I’ve found on StackOverflow:
git filter-branch -f --msg-filter ' read msg if [ -n "$msg" ] ; then echo "$msg" else echo "The commit message was empty" fi'
If your repository is large, it will take some time, but after that you can move on to rebase step.
5. Rebasing a Git repo to make a linear commit history
Git and TFS have significant differences in its architecture, so in order for Git commit history to be swallowed by TFS branch, it has to have a linear form. This can be achieved by finding a last commit prior the first branch (i.e. find the very first (oldest) branch and take a commit prior to that) and using its hash as a parameter to an interactive rebase command:
git rebase -i 5fa13f88cf61b37fea760d24e78819383a8df8ca
This method was also suggested at StackOverflow.
Git interactive rebase tool will popup with a long list of actions. Don’t change anything there, just save them, and the rebase commences. It may interrupt quite a few times complaining about merge conflicts. Resolving the conflicts is on you, but usually they are trivial and can be resolved just by invoking “git add .” or “git add add –u” followed by a “git commit”. In the end you will have a rebased Git repo that is ready to be exported to TFS.
6. Importing Git commit history… second attempt
So now you type again the checkin command:
git-tf checkin –deep
Worked? Then you’re lucky. Because in our case it didn’t due to a bug in the current version of Git-Tf tool. If your commits include renames with moving files between directories, the tool gets confused and stops with a message like this:
Git-tf: failed to pend changes to TFS due to the following errors. Please fix the errors and retry check in. The item $/OurProject/Master2/Backend/Client/Service References/Entities already exists.
We contacted Microsoft team, they admitted it is a bug in the Git-Tf tool but suggested a workaround:
git tf checkin --deep --renamemode=none
This workaround causes renames to be handled as deletions followed by insertions – not quite right but acceptable in our case, because we only need to preserve historical code snapshots and not how they were reached.
After the last step a TFS repository with full commit history should be established, and developers may either use it directly or keep Git-Tf to use Git locally and send it to TFS using this tool.