TFS Version Control Is Dead
Or why you should already be moving away from TFVC to Git on TFS / Azure DevOps by now.
Before I dive deeper into why Git nowadays is a better option in most cases, I should take a few things out of the way:
- TFS is a Microsoft solution for DevOps and Azure DevOps is the cloud-based version of TFS. TFS is being renamed to Azure DevOps Server for the next major version, while VSTS (Visual Studio Team Services) has been renamed to Azure DevOps. Since Azure DevOps Server hasn’t been launched it and Azure DevOps has already made the change I will refer to TFS simply as TFS and VSTS as Azure DevOps. Also, everything I say about TFS here is also valid for Azure DevOps.
- TFS (Team Foundation Server) and TFVC (Team Foundation Version Control) are different things. TFVC used to be the only option of version control on TFS so there’s still a misconception that to use TFS you have to use TFVC — but that’s not the case anymore since 2013 when Microsoft started adding Git support.
- Git and GitHub are different things. Git is a Version Control System (such as TFVC is) and GitHub is a service where you can host your git repositories as well as collaborate on other people’s repositories (such as Azure DevOps, but much more focused on the code part and on open source). Many features of Azure DevOps (such as Pull Requests) came from GitHub. But this article is not about moving from TFS to GitHub. It is about moving from TFVC on TFS to Git on TFS.
- Git nowadays is the default version control for Azure DevOps and TFS. I’m not the one saying, check this article from Microsoft for yourself: Choosing the right version control for your project.
Now that we’ve sorted it out, let me make something clear: my goal is not to bash TFVC, I respect it as an important tool that played an important role to the success of TFS. But I truly believe that its role has already been played and it should be left to rest in peace. Truth is, nowadays I hear more people avoiding TFS because they believe that TFVC is the only version control tool available on TFS than I hear people actually praising TFVC as a version control solution. So I separated some reasons while I think it’s definitely worth the switch (most of these are based on my own past experiences from a TFVC to Git migration).
So, in case you are still using TFVC as your version control on TFS or Azure DevOps, here are a few reasons to make the move to Git:
Lighter Branching Model
Git has a really light branching model where each branch you create is extremely lightweight — every branch you create is like a link to a specific commit, instead of a copy of your whole project. This makes the idea of creating a branch for every different feature you’re adding to the system not only possible but also actually recommended. It makes history much easier to read as well depending on how you structure your merges (ideally with the help of Pull Requests).
TFVC on the other side has a really heavy way of dealing with branches — every branch you create is a complete copy of your project together with all of it’s dependencies (such as the node_modules folders on node and front-end projects).
Not only this occupies much more space on disk but also causes problems such as opening the files of the wrong branch and only realizing this late — you can move the pending changes from one branch to the other using TFS Power Tools, but this isn’t trivial and the TFVC advocates I knew would rather copy and paste all the files from the wrong branch to the right one).
Much Less Bureaucracy
TFVC is more focused on typical hierarchy-oriented scenarios, so things like creating a branch are often seen as something that only people with higher ranks should be allowed to do and deleting a branch is even more restrictive. This makes the idea of “one-branch-per-feature” not a good idea at all to the point that it is discouraged except when extremely needed — just like on these tips to improve TFVC performance — Two Quick TFS Performance Tips. Also, even when the developer has the ability to create or delete a branch themselves it takes so much time that it’s simply not worth it.
Git on the other side has a very different approach. Anyone can create as many local branches as they feel is needed and usually, the restrictions only apply when pushing or deleting branches on the server — on TFS and Azure DevOps you can delete any branch that doesn’t have any policies and that you created yourself. This doesn’t mean that the code can’t be protected, though — the good practice is to always protect your master branch with Pull Requests, that way anyone can make their own branches and do what they will with them, but when it comes the time to merge their changes to the master they have to do it on a way that is controlled, tracked and (ideally) reviewed by other developers.
Disclaimer: If you work on an extremely hierarchy-dependent enterprise or similar environment, Git might be too much “open-minded” and moving away from a Centralized Version Control solution might not be an option at all. I found that to be a much rarer case though, most enterprises shouldn’t have trouble by segregating access to the code by creating separate repositories for each team or project, for example.
No More Teams Getting Stuck Because TFS Is Offline
This one might not affect people on Azure DevOps as much as it affects people on TFS, but since there were some Azure availability issues recently, both may be affected by it. Point is whenever TFS is down the whole team gets blocked. Visual Studio blocks them from doing any work at all because to even change a single file you’re supposed to tell the server that you’re changing it.
Local workspace theoretically solves this, but I’ve seem so many people have problems with it (mostly performance related) that a lot of developers just avoid it.
There are some ways to try to work around it such as working offline and later plugging the project back after TFS is back online but this often creates a lot of conflicts (such as files not being checked and such). Depending on how often these downtimes happen these situations can create really hard to solve problems, such as the server version being outdated and this only surfacing when someone else gets the latest version from the server and find reference problems because of missing files. I’ve even seen a case when that happened after the original developer had already formatted his HDD. He had to develop all the missing files again, even considering that he barely remembered how they looked like because he developed them months ago. This was an extreme case and not at all usual, but files not being checked or projects that only worked on a specific machine were painfully common.
But downtimes were not the only problem we faced frequently — whenever the server got slow for any reason, the whole team got a productivity hit.
On these situations, everything from committing to simply opening the Source Control Explorer would take awfully long. Local workspace theoretically would help that too, but I remember moving to server workspace because a simple file rename would take more than a minute to be completed. And I tried keeping only a single branch on the workspace — local workspace just couldn’t deal with the number of files the solution had.
On Git those downtimes affect us much less. Whenever the server is down it takes the CI/CD server with it and we lose the ability to deploy changes to the systems, but anyone who was actively working can continue to do so while the server is offline — (s)he won’t be able to push the changes to the server or check the kanban board, but will be able to develop and commit the changes locally at will. Also, I have the feeling that the other TFS features (such as the kanban board and CI/CD pipeline) became considerably faster after we made the move to git. This might be related to the much fewer resources used by the server now that it doesn’t have to be notified every time someones touches a file.
Faster File Editing
TFVC seems to check if the file is supposed to be watched or not on a file basis — every time you add a file to an ignored folder he seems to first check if the file should be tracked or not. This makes processes that includes many files such as installing NuGet packages much slower (even if the related files are ignored). Simply reinstalling the files of a solution (to enforce a specific version or to update them all) could easily take more than 5 minutes (sometimes 15 or more).
I was already used to working with NuGet before my first TFVC project so I found that incredibly hard to understand — installing or reinstalling packages was never a slow process to me before.
After watching it closer though, I’ve realized that Visual Studio was telling me that he was busy with TFVC operations — even if after the whole operation there was absolutely no pending change (the packages folder was ignored). This was also a problem when updating WCF client references — the number of files changed whenever there was a change on the server WCF often took more than one minute to be completed. And this is considering the TFS Server was actually installed on the same building as the developer machines — when I worked at an Enterprise which had the TFS Server on another state it could take like 5 seconds just to start editing a single file (the first time you would try to change any file it would go to the server to check-in the file and until it received the answer it blocked the file from edition).
Git uses a different strategy — instead of always being notified every time a file changes it only checks the current status when asked to do so. That way every time you (or a program you are using such as Visual Studio) wants to update the current file status they do a git status (which is extremely fast) to check if anything changed. I found that to be not only way faster than TFVC’s behavior but also way safer — I’ve seen some cases where a file which was supposed to be tracked on TFVC remained ignored (probably a downtime or something similar while the file was being added) and I’ve never faced such a case when using Git.
Faster Version Control Related Operations
The cost of this one is often underestimated because Version Control Related Operations are not so frequently done — especially on a TFVC environment. The problem here is that you often when you need them when you got some problem at hand and have to either fix it or discover the root of it. These times every minute counts.
One thing I often do when I’m maintaining someone else’s code is to double check the root of a bug whenever I find one after making changes. One way I usually do this is by stashing all the changes I’ve just made and checking to see if the bug is still there. If it is, the root is someone’s code, so no point in checking my own recent code for the bug. If the bug can’t be reproduced though, the culprit is my own change, so I double check it to see what I did wrong (ideally with a unit test that proves the bug on either case). This saved me sometimes.
Checking history also saved us sometimes by helping us understand some weird business rules which looked more like bugs — we would check which commit added the code and from there we would find the Pull Request which added it to the master branch. This Pull Request often contained the requester of the demand and what the code change was all about. Of course, this only worked because we are using a complete CI/CD pipeline, starting from the work items all the way to the deploy, but Git makes it all much easier to work with (more on that later).
While on TFVC I find it way too hard to get to the same level of interactivity with the Version Control System that I have on Git because it is often too slow and doesn’t seem as trustworthy when making changes such as briefly rolling back all the files to a previous version locally.
The latter one is something trivial on Git (just run “git checkout [commit-id]”) but was something everyone was afraid to do in TFVC (was too slow and had a good chance of causing problems). On Git I can check all the tracked files to the version they were on more than a year ago and come back to the most updated version on a few seconds.
Also, actions like getting the latest version on a not yet downloaded project take way too long on TFVC. I even made a test: I took a 1-year long project I started on TFVC and migrated its whole history to Git (using GIT-TFS command-line tool) then I checked the time TFS would take to get the latest version on an empty folder and the time it would take to clone the git migrated repository. I don’t remember the values exactly but it was like 40 seconds for TFVC and 30 seconds for Git. But the TFVC download includes only the current version, while the git clone includes the whole history (you can check out any previous commit even if you’re disconnected to the internet). And this was on Visual Studio 2015 and using Visual Studio to download the repository (git support was slower back then and doing git clone on cmd or PowerShell is considerably faster — cloning a git repository usually takes a few seconds on a command line).
Easy and Explicit Way to Ignore Files
This one I always found hard to understand when I was working with TFVC. It’s something so easy and trivial to do in Git that I never truly understand why it took so long to be a feature on TFVC (and why it never actually worked as expected when it became a feature). The idea is simple — have a list somewhere accessible that tells the Version Control System all the files or folder (or pattern of files and folders) that it shouldn’t care about. These files should never be added or tracked unless explicitly being told to do so. This is extremely useful for NuGet, for example, as a way to ignore the “packages” folder (because it should be downloaded by demand whenever needed).
Git does this using the .gitignore file. This should actually be the first file checked on any new project and Visual Studio actually commits it for you every time you tell it to add a project to version control.
The good thing of the .gitignore file is that it is extremely explicit: any file, folder or pattern of files that are supposed to be ignored has to be included on the .gitignore to be ignored (and changes to the file take effect immediately).
If you want to stop ignoring some pattern all you have to do is erase the related line. If you want to start ignoring a new pattern of files all you have to do is add the pattern to the .gitignore (and remove references of the pattern already committed).
TFVC tried to do the same with the .tfignore file — which is TFVC’s version of the .gitignore file. It’s a remarkable try but on my experience, it just doesn’t manage to solve the problem. There are too many gotchas such as only working with local workspaces (the .tfignore is ignored when using server workspace) and even when only using local workspace I’ve seen cases where files that should be ignored and came back to haunt the pending changes window. And it all becomes much more cumbersome to deal with when you actually have to negate a default ignore setting, even a recently added one (such as the case told on this article: Visual Studio (VS) 2015.3 does not detect any files added under the folder named ‘Release’ on Team Projects with Team Foundation Version Control (TFVC) on VSTS.
Disclaimer: my team faced that very same problem stated on the above article. It was a pattern we used to have the “TFVC master branch” be called Release and after VS 2015 Update 3 the folder started getting ignored even though it is a branch. My team was afraid to break something by renaming the branch (and renaming it would be against the naming conventions the Enterprise had) so I tried to solve it using the .tfignore approach of the article. It helped indeed — but the problem still resurfaced sometimes, maybe because someone was using server workspace. But considering that while using the server workspace a simple rename could take many seconds we had only but two options: use the new recommended approach and suffer with unbearable performance or come back to the older, faster and more prone to error behavior of the server workspace. Either way it was troublesome. So I managed to eventually convince the team to move to Git.
Better Toolset and Features Even Inside TFS and Azure DevOps
Most new features which are version control related are coming to Git only, sometimes because they wouldn’t even make sense on a TFVC scenario. Take the “New Branch” feature that work items have on the kanban board now for example. With Git this allows us to create a new branch directly from the work item and when we make a Pull Request to master later this same work item will already be referenced. Also, whenever someone wants to check the progress of this work item will be able to see the branch’s commits referenced from the work item itself. Now consider the TFVC scenario. Can you even consider creating a branch for every work item? Depending on the size of the project the time to create the feature branch on TFVC will be more than it would take to complete the demand and push it to the master on Git.
The whole workflow around the Pull Requests that Git on TFS and Azure DevOps gives us is on itself a strong reason to make the move. There is simply nothing remotely close to it on TFVC.
And nope, Code Review doesn’t come close either — it doesn’t even allow you to make a review and keep tracking the changes while the original requester fixes the issues found. Pull Requests on the other side allow you to keep updating the feature branch until the reviewers are satisfied — and all the changes are tracked and the involved people are notified during the changes. And all that with the ability to make comments referencing the code directly.
Much Better Tooling Across Platforms
TFVC is a Windows product and it was born to be used inside Visual Studio. There are clients for other platforms, but they aren’t known for working well without headaches. Even the official VS Code Azure Repos extension (which allows VS Code to connect to TFVC) isn’t free from headaches (I remember being surprised by the number of negative reviews I saw for it, but after checking the comments it became clear that TFVC was to blame for much of the hate the poor extension was getting). I’ve also seen eclipse developers using Visual Studio exclusively to handle version control because that was less stressful than dealing with Team Explorer Everywhere.
And Git… Well, I’ve been using it to keep track of notes and even to write this article in my spare time. And this includes writing on a Windows Notebook, a Linux Desktop and an Android Smartphone. So yeah, while it’s still a pain to use TFVC inside VS Code on Windows I’ve been using Git for months on a cell phone. It figures.
Truth is: outside the Microsoft stack, Git has already been the definite choice for version control for years. And nowadays Microsoft has already embraced it as well.
Conclusion
TFVC might still be your best choice if there is a really strong reason for using it (such as regulations that demand you to know at all times every single developer who have access to any single file on your codebase and to have the ability to block access to a single file to specific groups of people). On most cases though, you would be better off moving to Git.
Keep in mind that moving to Git will make you uncomfortable on the beginning if you’re used to TFVC because you will be taken away from your comfort zone and Git’s learning curve is not the smoothest (it’s quite sharp for beginners, actually). But I still haven’t met anyone who used both TFVC and Git professionally and who would rather come back to TFVC — and I’ve faced strong resistance against the change when I first proposed it. Now the same developers who were strongly against the change agree with me that their lives are way easier now.
So, why don’t you give Git on TFS / Azure DevOps a try as well? And share with us your experiences with Git and TFVC on the comments.