Seamless Migration from TFS to Git in Azure DevOps: Mastering the Transition Without Disruptions
Migrations from Team Foundation Server (TFS/TFVC) to Git inside Azure DevOps rarely follow a push-button pattern. A direct cutover may preserve history, but without process and cultural adjustments, the advantages of distributed version control are quickly diluted. Teams struggle with merge conflicts, tangled branch history, or loss of traceability unless the migration is intentionally engineered.
Here’s a practical playbook for moving code and workflows from TFVC to Git, preserving what matters and preparing for friction points that don’t show up in the documentation.
Centralized to Distributed: Why Teams Abandon TFVC for Git
TFVC’s centralized model has been dependable for controlling critical assets in large organizations, but it doesn’t scale for modern branching or global teams.
True gain? Git’s distributed design means:
- Developers commit, branch, and test locally, then sync with the central repo, reducing bottlenecks.
- Pull requests integrate code review and approval. No more “shelve/unshelve” gymnastics.
- Branching is cheap and fast, supporting short-lived features or hotfixes.
- The surrounding ecosystem—tooling (e.g., VS Code, GitHub), CI (Azure Pipelines), and even DevSecOps hooks—is standardized on Git.
Downside: Binary assets (expensive to diff/merge) are still challenging. Git LFS mitigates, but doesn’t eliminate, pain. Don’t ignore this during your assessment phase.
Pre-Migration: Detailed Checklist
1. Current State Analysis
- Inventory active branches (
$/project/branches
) vs. obsolete. - Identify directories dominated by binaries (e.g.,
/lib
,/data
). Move to storage outside VCS if possible. - Map contributors—ensure every TFS user maps to a valid Git user/email or risk ambiguous history.
2. Stakeholder Alignment
- Brief leads and architects on expected workflow changes.
- Hold targeted workshops—focus on “branch per feature” and pull request lifecycle.
- Aggregate CI/CD pipeline dependencies on specific TFVC paths.
3. Azure DevOps Scaffold
- Pre-create target Git repositories. Decide between monorepo vs. multi-repo critically—splitting at migration is cleaner than later extraction.
- Migrate only what’s critical. Historical deadweight adds gigabytes, slows every operation afterward.
4. Safeguards
- Full TFS database backup. Confirm recovery procedures work.
- Export work items (use
witadmin
or the Azure DevOps Migration Tools), not just source.
5. Scoping Migration Content
- History depth (full, last N months, or trunk only).
- Branches: All, or just
Main
/Release_*
? - Tag: Migrate releases? (Important for builds/artifacts traceability.)
Stepwise Migration Procedure
Step 1. Obtain and Validate git-tfs
git-tfs
(tested with v0.32.0+) is the de facto tool for history-preserving conversion.
Chocolatey (preferred install):
choco install gittfs -y
Alternatively, download specific builds (Windows/.NET Framework) from Releases. Don’t use git-tf
—it’s deprecated.
Note: On Linux, survival with Mono is possible but not recommended—expect instability.
Step 2. Clone the TFVC Repository
git tfs clone https://tfs.example.com/tfs/DefaultCollection $/Project/Repo --branches=all --debug
- Substitute the collection path and branch root as needed.
--branches=all
is vital for organizations with hundreds of parallel branches.- For partial history (e.g., changesets > 16000), append
--changeset=16000
to reduce scope.
You'll see progress logs like:
TFS changeset 16234 = Git commit 2b4c13e upstream/master
...
error: 315 changeset(s) fetched, 10 skipped due to path filters
Gotcha: Complex renames/branching structures may not map 1:1. Check resulting Git log for discontinuities.
Step 3. Clean Up the Resulting Git Repository
-
Inspect commit lineage:
git log --graph --oneline --all
-
Large file baggage? Use
git-filter-repo
to surgically remove:git filter-repo --path lib/ --invert-paths
-
Check author mapping. If you see generic
TFS_User
, adjust.git/config
usermap, then re-run. -
Non-obvious tip: Map TFVC changeset numbers to commit messages (e.g., “[TFS#15632]” in subject) for post-migration traceability—helps forensics during future audits.
Step 4. Push to Azure Repos
-
Initialize remote in Azure DevOps UI. Retain it empty.
-
Wire up the remote:
git remote add origin https://dev.azure.com/org/project/_git/repo
-
Push everything:
git push --all origin git push --tags origin
-
Confirm in UI: Branches and tags should appear as expected. Don’t fully trust the web UI's “import successful” toast; verify commit depth and tree hash.
Step 5. Enforce Branch Protections and Modern Workflows
-
Lock down
main
/master
. Require pull requests and code reviews.!ascii
main --PR--> build/validate --merge(approve)
!end -
Map legacy TFVC permissions onto Git security groups.
-
Enable build validation (YAML pipelines preferred over classic). Remember, TFVC path filters must be redefined.
Step 6. Team Enablement and Pipeline Refactoring
- Deliver targeted workshops. Focus: using
git rebase
sensibly (not as a blunt “fix history” tool), managing remote branches, handling cherry-pick merge conflicts. - Update all pipeline references (
s/$(TFVCPath)/$(Build.SourcesDirectory)/g
). - Expect setbacks; for example, CI triggers on push behave differently in Git.
Side Note: Power users will initially attempt to “check in” via the old TFVC workflow. Short-circuit confusion by disabling TFVC in the project.
Common Migration Pitfalls and Workarounds
Problem | Solution |
---|---|
Slow git-tfs clone (> 24h), timeout/lock | Clone branches separately, reduce history, check TFS server health. |
Author/commit mapping ambiguity | Explicitly map users in .git-tfs-authors before clone. |
Pipeline fails (missing files/paths) | Audit and rewrite path references, rerun YAML pipeline linter. |
Git repo size explodes | Prune before first push—rewriting history later is costly. |
Binary diff performance is poor | Use Git LFS for large assets; review .gitattributes setup. |
Known issue: Git loses “shelve sets” and detailed merge relationships. Manual recreation may be required for critical audit trails.
Final Word
The jump from TFVC to Git is a shift in both tooling and team dynamics. The technical path—git-tfs
, repository hygiene, branch mapping—is only half the story. The real challenge is transforming working agreements, security models, and delivery pipelines. Be ready for at least one “wasn’t in the docs” fire drill. Every migration run teaches something new.
Resources
For in-depth troubleshooting or postmortems, expect to spend more time reviewing commit mapping and pipeline errors than anticipated. If you ran into unique obstacles or devised a clever workaround not mentioned here, consider sharing with others—we’re still learning, too.