An Intro to the Intro
I’ve previously written about our experience with source control and our eventual migration to Git. I said that pull requests in Azure DevOps are awesome and are one of the biggest reasons to consider the switch to Git. In this post we’ll dig a little more into the details of why they are so good and how to use them.
What Are You Trying to Achieve?
Before we start, don’t forget that code review (i.e. pull requests in Git) and source control are tools. They are a means to an end and not an end in themselves.
I get it. We’re developers and typically we love the latest tools and gadgets. We go to a conference and we hear “You should be using… Docker / PowerShell / Agile / Azure DevOps / pair programming / test-driven development / insert some other tech or best practice here…” That’s great, as long as we don’t lose sight of why we should be using them. What are you trying to achieve? What problem do you have that this new tool or practice will alleviate? What will its introduction make more efficient?
Think about how you’d answer those questions. Write them down. Discuss with colleagues. Leave yourself a voice memo. Whatever works. Just make sure you’ve got some idea of how introducing this tool is going to help achieve your team’s goals.
OK, let’s start with the goal. Better quality software, delivered faster.
- Better quality means the code is clear, easy to read and maintain, does what it is supposed to do and doesn’t do more than it is supposed to do
- Delivered faster means we are able to take a requirement or bug, make the code changes and get them out to our users in a shorter space of time
One of the ways we will work towards that goal is by reviewing code before it is shipped. You might query how adding a review step allows us to deliver faster but consider time that is sometimes wasted going back and forth with a consultant or customer fixing bugs that could have been found during a code review.
Before we get stuck into the specifics of pull requests in Azure DevOps, take a minute to think about how you’d want this process to work. Consider the requirements of both the reviewers and the author. This is my list.
- Clearly identify the code changes that are under review
- Select one or more colleagues to review the code
- Allow the reviewers to add comments. It must be clear which line(s) of code the comments are about. Comments must be visible to all reviewers
- Allow for discussion of particular issues. The author may need to answer questions, reviewers may need to add clarifications to their comments
- The author must be able to make further code changes to create a new version of the code under review. Reviewers should be able to see the changes that have been made between versions
- Send notifications to reviewers when a change is made to a review that they are involved in
- Record when reviewers are satisfied that the changes can be shipped
- Keep a record of the review after it has been completed so that it can be referred back to, if necessary
Beyond the scope of this post, but related:
- Run automated tests against the code under review and record the test results
- Prevent a review from being completed if any associated tests have failed
- Mandate that code can only be shipped after it has been through a code review
Do you agree with those requirements? What does your current process look like? How many of those points can you tick off? Would you see value in adopting a process that would allow you to tick more, or all, of those points of the list?
On to the topic at hand. A pull request is the process of merging code changes between branches in Git repositories – or in our scenario between two branches in the same repository.
- Developer clones the repository to their local machine
- Create a new local branch to start some new feature e.g. the branch might be called feature/some-new-feature
- Start developing and committing their changes to that local branch
- Push local branch to create a copy on the server (usually referred to as origin)
- Create a pull request to merge the changes from the feature/some-new-feature branch to the master branch
- Reviewers and author discuss the changes. Author (or another developer) pushes new commits to create an update to the pull request. Repeat as necessary
- Complete the pull request to merge the changes into the master branch
- While completing, optionally squash the commits into a new single commit (as shown in the gif)
Creating the Pull Request
You’ve done some work in a new branch in your local repository and have pushed that branch to the server. When you view the branches in Azure DevOps in the browser portal it prompts you to create a pull request for this new branch.
Typically you will be prompted to create a pull request from your new branch (referred to as the “source branch”) into the master branch (the “target branch”). If you follow some workflow that merges your changes into a development / release / some other branch first you can change the target branch and the request will update accordingly.
You will see the code differences between the source and target branches – these are the changes that are under review. If you have already associated the commit(s) in the source branch with work items they will be automatically associated with the pull request. You can manually add or remove work items as well. This provides useful context for the reviewers. Also some might ask, if you don’t have a work item describing the changes you’ve made…why have you changed anything?
Add individual or groups of reviewers and they will receive email notifications that their expertise and opinions are required.
The pull request shows a tree of folders/files that have been modified. The changes for each file are highlighted on the right. It’s nice and easy for everyone to see the code changes that are included in this pull request. You can also see the work item(s) that are associated with this pull request for a description of the requirements that these changes are designed to meet.
By default you’ll be looking at the changes that have been made across all updates made to the pull request i.e. all pushes to the source branch since the request has been opened. You can, however, just view changes made in a given update. Imagine you’ve already reviewed the code and given some feedback and the author has made a small change to address your comments. You can select the latest update to only see the latest changes.
The most impressive thing about the pull request flow is the comments. Highlighting the code that the comment relates to and posting your message creates a new thread which supports:
- Others posting new messages in context to that thread
- Tracking the status of the comment (active, resolved, won’t fix)
- @mentioning colleagues to alert them to something
- Linking to work items with #work item no.
- Pasting images and emoji, liking comments
- Seeing which update the comment refers to
- Tracking how the code in question has changed between updates
If you have a requirement to get your team reviewing each other’s work and collaborating on code (and if you don’t…really?) then this is a lovely tool to help you do it.
The last point is especially good. If I arrive late to a review and some comments and updates have already been made I am easily able to catch up. I can see the comments that have already been made and the code changes that were made to resolve them.
Azure DevOps provides a lot of flexibility to configure how and when you want to be notified about pull requests. You can receive an email when:
- You are included as a reviewer on a new pull request
- A new update is created i.e. new commits are pushed to the source branch
- The request is completed or abandoned
- A reply is posted to a comment thread that you opened
- You are @mentioned
In addition to notifications the _pulls view (https://dev.azure.com/organisation/_pulls) provides an overview of the pull requests that you have created or are a reviewer for and their status.
When you’ve reviewed the code changes you cast your vote on the pull request. The options are: Approve, Approve with suggestions, Wait for author, Reject.
Once the comments have been commented upon and the votes voted on you can hit the big Complete button. This marks the pull request as being complete and merges its code changes from the source branch into the target branch. With the following options:
- Complete linked worked items
- Delete source branch
- Squash changes into a single, new commit on the target branch
We tend to have all three ticked. If there are a bunch of tiny changes in the source branch e.g. fixing typos then I don’t particularly want to see those in the target branch. Generally we’re happy with all the changes related to the request being grouped into a single commit.
The request, complete with comments, commits and votes is archived and remains on Azure DevOps if you need to refer back to it. Like most things in Azure DevOps you can access them through the REST API as well – as I did the other day to get some stats on how many requests we had completed in 2018.
And there is a load more than that as well. Beyond this post, but maybe a topic for another day. I hope the above has been enough to whet your code review appetite to try it out and investigate further.
- Protecting branches to only allow changes from a pull request (as opposed to pushing commits directly to the branch)
- Enforcing a minimum number of reviewers and preventing users from reviewing their own changes
- Enforcing that a build must run – and succeed – before the request can be completed
- Enforcing that all comments are resolved before completing the request
- Automatically include certain users or groups as reviewers on specified branches