D365FO: Embracing the Shift from TFVC to Git
Change is probably the only constant in the technology landscape we live in, and the way we manage code for Dynamics 365 Finance and Operations (D365 F&O) is no exception. While many of us built our foundations on Team Foundation Version Control (TFVC), Microsoft has clearly indicated that the future belongs to Git.
This shift represents more than just a change in tooling; it’s a fundamental change in how we collaborate, manage history, and integrate our code. Let's delve deep into the key differences between these two systems from a Dynamics perspective.
The Centralized vs. Distributed Architecture
The core difference lies in their fundamental architecture.
TFVC is a Centralized Version Control System (CVCS). Think of it like a bank vault. You (the developer) check files out from the central server to your workspace, make changes, and then check them back in. The history lives primarily on that server. To understand the past or collaborate with others, you must be connected to the system (you must be online and in sync with the TFVserver).
Git is a Distributed Version Control System (DVCS). When you "clone" a repository, you are not just getting the latest snapshot of the files; you are downloading a full copy of the entire repository, including its complete history and all its branches, right to your local machine. You effectively possess a full-fledged version control server on your own hard drive.
This means you can work, commit, branch, and merge while completely offline. This speed and local autonomy are major benefits of the Git ecosystem.
🎬 Play 1: Welcome to the New World
Dev1 (The Git Enthusiast): Hey Dev2! Welcome to the new F&O repo. It's Git now, no more server locking!
Dev2 (The TFVC Veteran): No server locking? But how do I know if you're editing the
SalesTable? If we both change it, I'll overwrite you! We need that lock.Dev1: Trust me, you don't. Git doesn't work that way. We don't "check out" files anymore; we clone the whole repo. We both have copies of the entire project. We can edit anything we want simultaneously on our machines.
Dev2: Simultaneously? That sounds like a recipe for chaos. The central server won't know what hit it.
Dev1: It handles it, I promise! We work in our own branches, and when we merge, Git uses magic algorithms to put our work together. The server won't be overwhelmed; it will be tidy!
Collaboration and the Infamous "Lock"
In TFVC, the fear of overwriting work led to the use of locks (exclusive check-outs). If Developer A locked a file, Developer B could not edit it until Developer A checked it back in.
Git turns this model on its head. Multiple developers can work on the exact same file simultaneously in their respective local environments.
How is chaos avoided? Git’s merge algorithms are remarkably intelligent. When changes are pushed back to the server, usually through a Pull Request (PR), Git compares the changes. If Developers A and B worked on different sections of the same file (e.g., different methods), Git can automatically combine them.
The Pull Request is a critical checkpoint for review and testing, ensuring quality before code hits the main branch.
Resolving Code Conflicts: The Human Element
But what if Developers A and B edited the exact same lines of code? This is known as a Merge Conflict.
Git cannot, and will not, decide which version is correct. Instead, it pauses the merge process and places special markers (<<<<<<< HEAD, =======, >>>>>>>) directly into the file. These markers clearly delineate the conflicting changes.
This is where the human element is required. A developer must open the file, manually review the two conflicting versions, decide how to combine them (or which one to keep), remove the markers, and then finalize the merge. Git forces this careful decision-making process to preserve code integrity.
🎬 Play 2: The Dreaded Merge Conflict
Dev2: (Staring at the screen in panic) Aaaah! Dev1, help! I just tried to complete my Pull Request, and the screen is yelling "Merge Conflict" in big red letters. This is what I was afraid of!
Dev1: (Calmly pulls up a chair) Breathe. It's okay. It just means we both touched the exact same spot in the code. Let's look at the file.
Dev2: Oh no, look at this mess. What are these
<HEADand=======arrows? Is my code broken?Dev1: No, that's just Git helping us. See? The part between
HEADand=======is what’s currently in the main branch. And the part between=======and your commit ID is what you added.Dev2: So… they don't look broken. They just look different.
Dev1: Right! So, we decide. Your change looks like a fix, and mine was just a comment cleanup. Let's keep your code and delete the markers and my old lines. You are the final judge!
Dev2: Ah, okay. That's actually… kind of nice. I thought the machine just picked one or crashed. I get to choose.
Branching: Heavyweight vs. Lightweight
Branching in TFVC often feels like a significant event. It typically involves creating a complete new directory on the server, which can be time-consuming and cumbersome, making feature branching less appealing for small tasks.
Branching in Git is fundamentally different. A Git branch is merely a lightweight pointer (or a simple text file) that references a specific commit. Creating or switching branches is nearly instantaneous because no actual file copying is required. This architecture encourages developers to create branches for every feature, bug fix, or experiment, isolating their work safely.
Merging is also much smoother in Git because it has a superior tracking of history, allowing it to easily identify where branches diverged and how they can be brought back together.
Terminologies: Translating from TFVC to Git
As we navigate this new landscape, it helps to map familiar TFVC terms to their Git equivalents.
| TFVC Concept | Description | Git Equivalent (Action) | Git Description |
| Check-out (Locking) | Prevent others from editing a file while you work. | (None) | Git doesn't lock files. Collaborative editing is default. |
| Work Item Tracking | Link a check-in to a work item (e.g., a bug or user story). | Linking commits to PRs/Work Items | Commits can contain work item references in the message. Pull Requests are often the vehicle for connecting work items to the repository (especially in Azure DevOps). |
| Check-in | Send changes from local workspace to the central server. | Commit followed by Push | Commit saves changes locally. Push sends local commits to the remote server. |
| Get Latest | Download the newest version of files from the server. | Pull | Downloads (Sync = updates) from the server and automatically merges them into your current local branch. |
| Shelveset | Temporarily save pending changes on the server without checking them in (e.g., for code review or backup). | Stash | Temporarily saves changes locally on your machine. Often used for switching tasks quickly without committing incomplete work. (Code reviews are usually done via the Pull Request process). |
| Merge Branch | Integrate changes from one branch into another (e.g., Dev branch to Main). | Merge | Combines changes from one branch into another, preserving full history (a "Merge Commit"). |
| Label | Mark a specific version of files with a human-readable name (e.g., for a release). | Tag | Marks a specific commit with a name, primarily for releases or key milestones. |
| (No exact equivalent) | TFVC lacks a mechanism to easily clean up or rewrite local history before integration. | Rebase | We’ll cover this specialized command in the next section! |
The "Three-Way Merge" and Rebase
The Three-Way Merge (The standard merge)
When Git needs to combine two branches (say Branch A and Branch B) that have diverged, it doesn't just look at the two end commits. It identifies a "Common Base" (the last commit they both shared) and then looks at three points:
The Common Base (where they split).
The latest commit of Branch A.
The latest commit of Branch B.
By knowing where they both started, Git can often auto-merge changes that were made in non-overlapping parts of the file in both branches. If overlaps occur, it asks for help (a conflict).
The Rebase: Rewriting History (Git’s Unique Tool)
While standard merging keeps a complete history of how branches diverged and came back together, Git offers another option: Rebase.
Rebase is essentially like rewriting the history of a branch. If you divergence from the main branch and other developers add commits there, rebasing your feature branch will:
Temporarily take all your unique commits off your branch.
Get the very latest code from the main branch and apply it to your feature branch.
Then, it "replays" your original commits, one by one, on top of that new, updated base.
The resulting history looks like a perfect straight line, as if you started your work right off the latest version of main.
Rebase vs. Merge: When to Use Which?
This is a critical decision point.
Merge (Standard): Ideal when you want to preserve the actual, full historical context of when branches diverged and merged. This is generally preferred for shared branches.
Rebase: Powerful for keeping your personal feature branch clean and updated while you work alone. It simplifies the history before you integrate your changes back to a shared repository.
Crucial Rule: Never rebase branches that you have already pushed to a shared repository. Since rebasing rewrites commit histories, this will create severe confusion and issues for other developers who are working off the original history.
🎬 Play 3: To Rebase or To Merge, That is the Question
Dev2: Okay, I just finished my feature on our
NewFeature-Branch. Me, you, and Dev3 all worked on it for two weeks. There are like 20 commits in there. Now I should merge it back to the main branch, right?Dev1: Wait! You have a choice. Do you want to keep the messy history showing how all three of us committed over the two weeks, or do you want a clean, straight line that looks like we built it yesterday?
Dev2: A straight line sounds lovely and clean. That's Rebase, right? But what about the dates? I did that critical work on Tuesday!
Dev1: (Smiling) The best part! Rebase preserves the Author Date (when you wrote it), so Tuesday is safe. But it updates the Commit Date to today (when the rebase happened).
Dev2: Oh, I see. But we both pushed this
NewFeature-Branchto the server already. You, me, and Dev3 all have copies. Can I still rebase it?Dev1: (Immediately serious) Stop! NO! If you rebase a shared branch, you rewrite history. Everyone else's copy will be broken, and they'll hate us. Since we all touched it, we must use a standard Merge. We have to live with the messy history to avoid breaking everything for our team. Rebasing is for you when you're alone. Merging is for the team.
The transition from TFVC to Git for D365 F&O development is indeed a shift, but it is one that offers significant rewards in terms of speed, flexibility, and powerful modern tooling. While terms like distributed architecture, three-way merges, and rebasing may seem daunting initially, they quickly become intuitive once the fundamental concepts are understood.
The future of D365 F&O development is Git-based. Embracing this shift not only aligns with Microsoft’s standard, but it also empowers teams to collaborate more effectively and develop code with greater efficiency.
So, let's clone that repo and start committing!
Comments