The conversion tool exists. It works. You click a button (or run a command) and 180 days of history later you have a Git repo.
Congratulations. You've just converted your mess into a different kind of mess.
I've been doing TFVC-to-Git migrations for years. Multiple organizations, multiple industries. And I'll tell you what I tell every team I work with: the conversion is the easy part. The decisions are what make this hard. The tool migrates your code. It does not migrate your thinking. And the thinking is where every one of these projects either succeeds or goes sideways.
Here's what you're actually dealing with. Every one of these gets its own section below.
- You can't put this off much longer. YAML pipelines require Git. Classic pipelines are in maintenance mode. The platform has moved.
- You have one giant TFVC repo. Git uses repositories as boundaries. You need to figure out how many repos you're creating.
- Monorepo or solution-based repos? The easy answer imports your dysfunction. The right answer requires knowing where your solution boundaries actually are.
- Your solution roots might not be clean. TFVC let people get sloppy about this for years.
- Cross-root references will break your builds. If Project A references something under Project B's directory tree, that reference points to nothing in Git.
- Folder branches aren't Git branches. Your
$/App/Devand$/App/Prodfolders don't convert the way you think they do. - Workspace mappings don't exist in Git. If your build depends on them, you've got structural work to do first.
- Binary dependencies need a plan. That
lib/folder full of DLLs is going into your Git history forever. - Your builds and releases are broken too. Classic pipelines pointed at TFVC paths that no longer exist. You're probably looking at a pipeline rewrite.
You Can't Put This Off Much Longer
You know how you you have to do periodic maintenance on your car? Stuff like changing the oil every X number of miles. Or replacing your tires when they run out of tread. That's your basic maintenance and the software equivalent would be probably something like periodically updating the version of .NET that your projects target and/or updating your nuget package versions.
But then there are slightly more involved things -- actual repairs. Your air conditioning doesn't work or your wheel makes an awful noise when you go around a corner. And you probably knew that you were developing a problem and you delayed and delayed and put it off until your car actually broke. Because -- let's face it -- you've got stuff to do.
The version control equivalent of that is your stuff in Team Foundation Version Control (TFVC). I don't want to relitigate the Git vs. TFVC or distributed vs. centralized version control debate. Git won. It's now the de facto standard for version control. It's the one to learn.
Git is Where the Tooling Is. TFVC is Where the Tooling ISN'T.
I want to make it clear: TFVC isn't going away. But on the other hand, it's pretty clear what Microsoft's plans are for TFVC. And that plan is...just about nothing. Keep it working but beyond that that's about it.
In January 2026 they broke the obsolete check-in policies. TFVC Proxy now requires upgrades. No new TFVC features are shipping. Nobody at Microsoft is going to say "TFVC is deprecated" but the signal is in what they're not investing in.
Where they are investing is around YAML pipelines.
YAML pipelines require Git. The End. They don't support TFVC. That's not a preference or a best practice -- it's a hard platform constraint. If you're on TFVC, you're on classic pipelines. And classic pipelines are in maintenance mode. Manually triggered stages? YAML-only. Unskippable stages? YAML-only. Stage-level locks? YAML-only. Template metadata? YAML-only. Classic works. Classic is fine. Classic is not getting anything new. Ever.
And if you're thinking about GitHub Actions at some point...you need to be on Git first. There's no path from TFVC to GitHub that doesn't go through a Git migration. Microsoft's Enterprise Live Migrations tool? That's Git-to-Git only. If you're on TFVC, you can't even get to the starting line.
So you're not choosing whether to do this. You're choosing when.
The One Giant Repo Problem
There's a big structural difference between TFVC and Git that is subtly hidden in plain sight if you're regularly using TFVC. With TFVC, there's only one repository. One. For the entire Team Project Collection — or if you're in the cloud, there's one TFVC repo for your Azure DevOps account. Every application, every team, every library, every era of your organization's history -- it all lives in one giant directory tree.
In TFVC, that's fine. That's just how it works. It's not a problem. It's just structural.
In Git, a repository is a boundary. It's a deployment unit. A permissions scope. A CI/CD trigger scope. When someone pushes to a Git repo, pipelines fire for that repo. Not for the twelve other applications that happen to be sitting next to it.
So the first question isn't "how do I convert?" It's "what do I convert?" and "how many repos am I converting into?" And that question sounds simple until you actually try to answer it.
And here begins the stickiness.
How Many Repos: Monorepo or Solution-Based Repos?
You've got two basic options. You can dump everything into one big Git repo -- a monorepo. Or you can split things up, usually along solution (*.sln, *.slnx) boundaries.
The monorepo is faster. Path of least resistance. But it also imports all your version control mess and dysfunction wholesale. Your CI/CD events become a mess because every push triggers pipelines for every application in the repo. Your permissions are all-or-nothing. You've solved the "we need to be on Git" checkbox without actually solving any of the problems that matter.
Solution-based repos are usually the better answer. One solution, one repo. The directory where your .sln file lives becomes the root of the new Git repo. (This directory is known in Visual Studio as the "solution root".) Clean. Bounded. Each repo gets its own pipeline, its own permissions, its own branching strategy.
But -- and there's always a but -- that only works if each solution is actually self-contained.
Spoiler alert: your solution might not be self-contained. In fact, it probably isn't.
The Solution Root Problem
In a well-organized codebase, every solution has a clean root directory. Everything that the solution needs lives under that solution root. The .sln file, the project folders, the shared libraries, binary references, project references. All contained.
In TFVC, things tend to get...informal. Maybe you've got a $/Solutions/ folder with a dozen .sln files in it. That starts to imply twelve repos. Maybe. Unless some of those solutions share project references. Or unless the solution root is ambiguous because someone put the .sln file two levels deeper than the project directories it actually references.
Your solution root needs to be absolutely clear before you convert. If it's ambiguous in TFVC, it'll be broken in Git.
Cross-Root References: The Thing That Will Break You
This is the one. This is the sinkhole that most teams don't discover until they're mid-migration and builds start failing and everyone's looking at each other saying "I thought this was supposed to be easy."
In TFVC, Project A can reference a DLL or a project that lives under Project B's directory tree. A project in $/App1/src/ can reference a shared library over in $/SharedLibs/Common/. This works fine because everything's in one repo. The workspace mapping resolves it. Nobody thinks twice.
In Git, that reference points to nothing. Git will be perfectly happy to take this code but when you go to build it, there just probably won't be anything there. Because it's referencing something outside of the Git repo.
Every cross-root dependency is a broken build waiting to happen. And in my experience, every TFVC codebase of any real size has them. Shared utility libraries. Common assemblies. Binary dependencies in a $/Lib/ folder that six different solutions reference. Project references that reach sideways across the directory tree because it was easy and nobody said "hey, maybe don't do that."
All of that has to be found and dealt with before you convert. Not after. After is too late -- after is "why won't this build?" at 4pm on a Thursday.
Folder Branches Are Not Git Branches
TFVC does branching by copying folders. $/App/Dev. $/App/QA. $/App/Prod. Each one is a complete copy of the directory tree with its own history. They look like directories because...well, they are directories (on disk at least).
Git doesn't work that way. Git branches are repo-level constructs -- lightweight pointers to a commit. When you switch branches, the whole repo switches state. Not a subfolder. (This approach is worlds better and approximately 50 billion times easier to use, too.)
The conversion tool doesn't magically turn your TFVC folder branches into Git branches. You need to decide which folder is "main." You need to decide what happens to the others. And you need to ask yourself the uncomfortable question: should those branches even exist in the new world?
When I help customers with these TFVC-to-Git migrations, this work routinely exposes branching structures that have been accumulating "issues" for years. Branch-per-environment. Zombie branches that haven't been touched in eighteen months. The branch graveyard that everyone pretends isn't there. Now's the time to figure that out and clean it up. Don't carry it forward into Git where it'll be even harder to untangle.
Workspace Mappings
Some teams have TFVC workspace mappings that are load-bearing. Solutions that only build if you have exactly the right combination of folder mappings configured on your machine. Two folders from over here, one from over there, cloak that subfolder, map this other path to that other location.
In Git, there's no equivalent concept. You clone the repo. You get the repo. All of it.
If your builds depend on workspace mappings to work, you've got structural problems to sort out. And that's not a migration problem -- that's a problem that TFVC was hiding from you. The migration is just the part where it becomes visible.
Binary Dependencies
Even if your binaries dependencies live somewhere under the solution root -- and that's a big "if" -- you still need a plan for how you're going to git-ify them.
That lib/ folder full of third-party DLLs that someone checked into source control back in 2014? Git will track it. But every version of every binary goes into your Git history. Forever. Repo size bloats. Clone times go up. Developers start complaining on day two. (Doing this with TFVC wasn't a super-hot idea but now with Git, it's even more of a non-super-hot idea.)
And if you're thinking "we'll just move those to a NuGet feed later" -- sure. But "later" has a way of becoming "never" once the migration is done and everyone moves on to the next fire. This decision is easiest to make during the migration when everything's already in flux. Not after, when nobody wants to touch anything.
And Then There Are Your Builds
So you've gotten this far and you've fixed everything above. It's all building. But then right when you think you've got a handle on all the pain, there's this next part. Builds and Releases.
If you're on TFVC, you're on classic pipelines -- the JSON-based build and release definitions. You have to be because YAML doesn't support TFVC.
Now think of what you're looking at as soon as you're done converting your source control to Git. Your code is in a nice clean Git repo. But your existing classic build definitions are pointing at TFVC paths that don't exist anymore. Your triggers are wired to a source control system you just migrated away from. And your CI/CD eventing is broken.
You could re-point your classic pipelines at the new Git repos. Spend an afternoon clicking through the UI, update the path mappings, test it, done. And it'll work. But you'd be investing effort in a pipeline model that hasn't gotten a new feature in three years. And counting.
So now you're looking at a pipeline rewrite. And here's where it gets interesting -- it's not just a syntax conversion.
The classic model separated "builds" from "releases." (This is a good thing.) You had a Build Definition that compiled your code and produced artifacts. Then you had a Release Definition that picked up those artifacts and deployed them through environments with approval gates. Two separate things. Two separate editors. Two separate concepts. Two separate activities: build and deploy.
In YAML, that separation doesn't exist. The YAML pipelines in azdo and GitHub aren't even specifically for build & release. You're use them for build and release but they're really more of an automated workflow kind of thing. Good for build and release...and also good for other stuff like maintenance tasks and pull request review, etc.
So in the YAML pipelines, build, test, and deploy are all stages in one pipeline file. Environments and approvals still exist but they're configured in AzDO's environment settings and referenced from the YAML. It's not find-and-replace. It's a rethink of how your pipeline is organized.
Which raises the question you were probably going to ask anyway: if I'm rewriting my pipelines from scratch, should I rewrite them in AzDO YAML...or is this the moment to evaluate GitHub Actions?
That's a different article.
This List Isn't Exhaustive
And that's the part I want to leave you with. Everything I've described here -- the repo boundary decisions, the cross-root references, the folder branches, the workspace mappings, the binary dependencies, the pipeline rewrite -- that's just the stuff I keep running into. Every organization has its own special wrinkle. Its own "oh yeah, we also have that" moment halfway through the project.
The teams that treat this as a button-click end up with a Git repo that's just as dysfunctional as the TFVC repo it came from. Except now it's harder to understand because the structural problems that were invisible in TFVC are suddenly very visible in Git. And they're broken.
The conversion tool handles the easy part. Everything on this list is the hard part. And the hard part is where the migration either succeeds or just moves the problem.
-Ben
Want help with this TFVC to Git migration? Trying to figure out your Azure DevOps & GitHub strategy? Got builds and releases that need conversion to YAML? We can help. Drop us a line at info@benday.com.