Deep Dive: Azure DevOps Version Control

Introduction

Let’s talk about version control in Azure DevOps. If you want to automate your builds in Azure DevOps (or any DevOps process) then you must use version control.  So if you need to get up to speed check out these resource (here, here, and here).

Now let’s dive deep into the whole enchilada: files, commits, pushes, branches, tags, pull requests, and of course integration of all this goodness from within the Visual Studio IDE. If you want to see the source code for the sample project I used for this post go to my repo here: https://github.com/jamesstill/WidgetApi.

When you provision a new project you have two choices within the tool for native version control: Git or Team Foundation Version Control (TFVC). Personally, I haven’t used TFVC since I worked with an on-prem TFS Server several years ago. There’s nothing wrong with it. It’s just that Git has quickly become the de facto standard. So if you’ve got a brand new project just choose Git and be done with it.

If you’re not familiar with Azure DevOps you should probably go read my four-part series before continuing. I’m going to assume you already have an account and know your way around.

Add Some Code

There are four ways to add code to your new Azure DevOps project:

1. Clone the empty repo from Azure DevOps to your workstation.
2. Push to Azure DevOps from the command line.
3. Import from another repo (like GitHub).
4. Push to Azure DevOps from within Visual Studio Team Explorer.

If you’re on Windows 10 and you don’t want to use a GUI then you must download and install Git for Windows in order to use command line git commands. Let’s take these four options one at a time.

1. Clone to Your Workstation

If you click on Repos after creating a new project you will see instructions on how to clone the vanilla repo to your workstation. This is not common for most developers. We tend to start with code scaffolded from a project template and maybe we’ve even implemented a good chunk of functionality. But if you’re looking to work with a readme.md file in advance of the code itself this is an option. Here is a clone from GitHub:

And the same thing from Azure DevOps where you can clone the repo straight into an IDE. You can see that VS Code is the default pick but there’s a whole list of options including Visual Studio, Eclipse, and others:

2. Push From Command Line

To add any local repo to a Git server you would execute these commands from your project root. The clone option above gives you the URL you need:

git remote add origin <URL>
git push -u origin --all

Before the latest version of Visual Studio spoiled me with its Git integration tools I used the Git Bash emulator. This is probably the preferred option for experienced Git users who are comfortable with a shell and don’t use Visual Studio. The choice is yours.

3. Import from Another Repo

Azure DevOps provides a handy button named Import to bring in a project from another repo. For example, you could import the example WidgetApi project from my GitHub repo as in this screenshot:

Many of Microsoft’s own mission-critical open source projects like EntityFramework and ASP.NET Core are on GitHub. If you already have source code in a GitHub repo you can integrate it as a hosted service provider in Visual Studio by installing the GitHub extension.

4. Team Explorer

Finally, you can put a project under source control in Azure DevOps right from within the Visual Studio IDE. In Visual Studio go to Tools > Options and choose the Source Control option. Change the current default plug-in to Git and click OK to close and save. Now open the Team Explorer window. This window supports all integration between our Visual Studio project and the Azure DevOps project:

Under the Local Git Repositories section you can click New to create a new local repo for a new Visual Studio project. Or you’ve already started coding and want to commit the code, click Add to put an existing project under Git source control.

Remember that Git is a distributed version-control system so the local repo you create, add or clone is not the same repo as the one hosted on the cloud. In addition to committing changes to your local repo you must also push those changes up to the Azure DevOps server. I’ll cover that process below.  If you already have an Azure DevOps account click Connect… to login. Otherwise you can click Get started for free and they will set you up.

Master Branch

First I’m going to talk about working straight off the master. Later we’ll get into pull requests. Once you’ve pushed the commit on your local up to Azure DevOps you can see the files there in the browser:

But you don’t have to keep a browser window open. Visual Studio provides a helpful status bar down at the lower-right to show your current work session. Here you can see I am in sync with the server and I have no commits.

To work with Git in Visual Studio you follow these typical steps from that lower-right status bar. Click the master branch up arrow (Ctrl + Alt + F3) and choose Pull (or Fetch) to merge changes from other team members on the server to your current local master branch:

As you work the pencil icon down in the status bar shows the number of files you’ve changed. When you’re ready to check in those changes click the pencil icon:

Team Explorer will pop up and ask you to enter a commit message. Alternatively you can stash the code changes without committing them to the master branch. After all git-stash is the “sock drawer” of version control. Stashing is particularly useful if your code isn’t in a good state but you have to switch gears and (God forbid) go home for the weekend or deal with some other issue. See the docs for stashing code.

Once you’ve clicked Commit All your code changes are committed to your local repo. Again, those changes aren’t on the server yet. Just your local repo. To push these changes up to Azure DevOps you can click the up arrow on the status bar or the Sync link in the Team Explorer window:

After the sync Team Explorer shows you the outgoing commit along with your commit message. Click Push to make it so:

And you should get a message showing that your push to the server was successful. Notice that the status bar is reset back to zero across the board. If it’s been awhile and you have other team members working you would want once again to pull or fetch to get the latest changes on the server’s master branch and then you can get back to developing again. That’s the typical lifecycle of working on a team with a distributed version control system like Git.

Commits View and Pushes

Click Repos > Commits to see a nice and intuitive UI of the code changes made by you and your team. Click on commits to see a graph of change over time. On a very active project you can filter by date range or author. Click on any commit number to see file diffs:

Click Repos > Pushes for a list of pushes and their commits. Drilling down into the commits will take you to the same inline diff view as above.

Now of course you don’t have to go to the website in order to see this information. You can view the commit graph as well as the side-by-side diffs right from within Visual Studio. Just right-click any file and choose View History from the context menu. This brings up a commit history in topological order:

I can right-click any commit in the list (except for the first one of course) and choose Compare with Previous from the context menu. This will generate a diff of both files. Suppose you have a few dozen commits in the list and want to compare any two of them. After selecting the first file just hold down the Ctrl key while you select the second one. Then right click and choose Compare to generate the side-by-side comparison. You can select any file prior to the latest commit and cherry-pick or revert changes back to this point.

If you want to view the commit graph for the whole solution just select the up arrow next to the branch name on the lower right status bar and choose View History. You can double-click a commit and all changes will display in Team Explorer. This is all part of Microsoft trying to be helpful and giving you views into source control from within Visual Studio so you don’t have to context switch and go out and open a web browser.

Tags

An Azure DevOps repo supports Git annotated tags. For example, it’s a common practice to tag a release branch with the version number. When you create a new tag you enter the tag name, description, and then you tag from branch name (like master) or a particular commit. Once you’ve created a tag you can view them in Visual Studio Team Explorer or in the Azure DevOps Commits view or by going to Repos > Tags.

Branch Policies

I don’t want to go too deep into Git branching strategies. But I do want to talk about the branch policies available to you in Azure DevOps. Click on Repos > Branches and you’ll see your default master branch with the last commit. Why the Azure DevOps Team hides this I don’t know but there’s an invisible ellipse button just to the left of the commit number that will show up when you hover over it:

You just have to know it is there. 🙂 Click on it and you can see configuration choices for the branch. Most are self-explanatory but let’s talk about branch policies. There are four policies from which to choose. By default none are checked. So you and your team can check in code changes straight to the branch with no restrictions. However, if you check the first policy, requiring a minimum number of reviewers for approval then you are by definition enabling pull requests.

Here I’ve configured a basic policy where two reviewers on the team (other than myself) must up-vote my PR before it is merged into the branch. I also checked the Reset option. Suppose Reviewer A approves my PR, but before Reviewer B has a chance to look at it I make another change. In that case, if the Reset option is checked then the branch policy will remove Reviewer A’s vote and require re-approval of the new changes.

There’s one more thing to do here. Click the Add automatic reviewers button at the bottom. You don’t have to preconfigure reviewers but it saves trouble later when preparing a PR. Here you can see I’ve added myself (as the lone person on my sample project) as a required reviewer:

Protecting the branch and requiring code review in this way can be a very powerful way to improve code quality. Obviously your team culture has to be healthy and supportive for this to be effective. But code reviews can catch simple mistakes that we all make in the heat of battle. Once I implement one or more policies I’ll see a little status icon on my branch:

Pull Requests

Suppose I just enabled a branch policy to enable pull requests. If I click on Repos > Pull Requests I can see that there are none that need by attention:

Now I’ll go to Visual Studio and do some work by adding another endpoint to my WidgetController:

[HttpGet("[action]/{term}")]
[ProducesResponseType(typeof(IEnumerable<Widget>), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> Search(string term)
{
    var items = await _context.Widgets
        .Where(w => w.Name.ToLower().Contains(term.ToLower()))
        .ToListAsync();

    if (items == null || items.Count == 0)
    {
        var message = "No widgets found!";
        _telemetryClient.TrackTrace(message, SeverityLevel.Error);
        _telemetryClient.TrackEvent(NOT_FOUND);
        return NotFound();
    }

    return Ok(items);
}

The status bar down in the lower right shows my new change. So I click on it, add a message, and click Commit All to commit my changes. No problem. But so far my commit has yet to be pushed up to the Azure DevOps repo. So I do that next and push the change. I get back an error message. Epic fail:

And sure enough down in the Output window I get a more detailed trace message that tells me that the master rejected my push because it is protected by a branch policy. I need to submit a PR. So let’s do that.

I begin by going back down to the status bar, clicking the up arrow next to the master branch and choosing New Branch.

Team Explorer pops up and asks me to give the new branch a name. I’m going to name it after the feature I implemented:

I then click the Create Branch button and I can see down in the status bar that I’m now working on that branch. Now I want to push the change up to the repo by clicking the up arrow down in the status bar. This time I get the success message meaning that my branch is now in the repo on Azure DevOps.

To prepare the PR I open my browser and go back to Repos > Pull Requests. I get a little info message at the top telling me that I just checked in a change to a branch and would I like to create a pull request:

I can go ahead and click that link or I can click the New pull request button. It doesn’t matter. Once I click to create a new PR I get a screen asking me to pick the source branch that I want merged into the master branch:

Once I do that I get a small form with a description field that I can fill out to describe the change. I can also link the change to a work item or story. For now, I click Create and I’m done. If you have not changed the default values for notifications, the system will send out an email to all reviewers notifying them of the new PR. Time to get some coffee.

You can also install extensions for different kinds of notifications. For example, go into your organization settings and click General > Extensions > Browse the Marketplace and search for Slack. If you install Slack Integration into your Azure DevOps tenant you can configure a notification that posts a message to your Slack channel when a new PR is ready for review. It’s worth your time to look at the extensions in the Marketplace. There are more added every day.

Now let me switch hats and act as a reviewer. I’ve been notified of a new PR and I go into Repos > Pull Requests to see the list of active pull requests waiting for me:

After clicking on the request I can get a very detailed overview of the change along with the graph history and description. Click on Files to see the diffs. Here the reviewer can make comments on specific lines of code. Now that I’ve got my code reviewer hat on I can make this comment:

When I’m done with my code review I have four options including approval and rejection:

In this case I might choose to approve with suggestions but I might just as likely reject in order to give the developer the opportunity to make the change before this is committed to the master branch. Once I’m satisfied that the code is good to go I can resolve my comment and approve and complete the PR.

Once the merge is complete I can go back to Repos > Commits and my graph shows the pull request is now in the master branch:

This is the typical lifecycle of a PR. You might not need it on your project, especially if you’re working alone or on a small team. But this is a very proven process. The review step increases code quality and gives you more confidence when you do automated deployments to Azure.