Rapture in Everything

Migrating history from TFS to Git with git-tfs


Maybe you've found yourself stuck with a codebase in the TFS Version Control System and would like to move it, along with its entire history, into Git. If so, this article explains how to do it. Even if you don't plan to move, you might want to check out git-tfs - a bridge between TFS and Git, that allows you to work with your codebase in TFS as if it were a Git repository. I'd been using git-tfs for multiple projects, and since now the TFS server itself directly supports Git, it's about time to migrate it.[1]

If you want to do the migration, you will need to have git-tfs (and git itself) installed. There are various ways to do this, but I prefer using Chocolatey - just run choco install gittfs -y as admin.

Clone the TFS repository. This step might take a while - since git-tfs is fetching every checkin in your project's history and making it into a git commit. Just run

git tfs clone https://your-tfs/collection/$/Migrated-project-name

Clone the (still empty) target repo of your new Git project.

git clone https://url/to/target-git-repo

If you try just pushing the source repo into the target (as I naively did), you will get an error that goes like this:

remote: error: refusing to update checked out branch: refs/heads/master
remote: error: By default, updating the current branch in a non-bare repository
remote: error: is denied, because it will make the index and work tree inconsistent
remote: error: with what you pushed, and will require 'git reset --hard' to match
remote: error: the work tree to HEAD.

That's basically Git trying to prevent you from shooting yourself in the foot - because hey, you're clearly pushing into a completely unrelated repository.

You can override this behaviour. Go to the target Git repo and set "denyCurrentBranch" to "warn".

cd target-git-repo
git config --local receive.denyCurrentBranch warn

You should be ready now. Just use your favourite client to git push from the Migrated-project-name (the repository created by git-tfs) into your target-git-repo (the new git repository).

Reminder: you're pushing into the newly created repository you made on your local file system - not the central repository (like TFS) it will eventually reside in.

You might want to check out the master branch in your new repo. Run

​git checkout master​​

At this point, you should see the most up-to-date version of the files in your old TFS workspace in target-git-repo. You're ready to push into your git repository's origin. (I.e. into TFS or GitHub.)

​git push

And that's it.

A word of warning: while I like the visually pleasing GitKraken client, I've had many issues with it. Sometimes it outright doesn't do what it should and displays an error (or gets itself into a state where it can't even start), sometimes it's more subtle.

When I'd tried to do this process with GitKraken, I got confusing errors (not to mention it refuses to even open a repo that doesn't have any commits yet). When I tried the same steps with TortoiseGit, everything went smoothly.

At this point, you might want to reset/remove the denyCurrentBranch that's still left in your .git/config file.

git config

  1. Please note that this assumes you will make a new TFS project for your new Git codebase. I haven't tried finding out whether you can switch it 'in place', or anything. ↩︎