Video

Want to see the full-length video right now for free?

Sign In with GitHub for Free Access

Notes

In this video, we're going to take all the workflows and tips we've learned over the course of the previous videos and apply them by working through the development of a small feature on the Upcase Rails app. We're going to work through all the steps in the typical thoughtbot Git workflow, as well as review the thought process behind each of the steps. In addition to this video and the associated notes, you can see a detailed summary of this workflow in our Git Protocol page in our Guides repo.

Context

Throughout this video, we'll be demonstrating the thoughtbot feature flow while working on a pull request to update the ordering of flashcard decks. This is a small but real example of the approach we take on all changes to our apps.

Always Start With a Branch

We'll begin by creating a branch for our feature. I think it's worth highlighting that here at thoughtbot we commit almost nothing directly to master, preferring instead to go through the same branch, pull request, code review workflow for even the smallest changes. We've worked hard as a group to remove any friction in the workflow to ensure that it is feasible to always use that full workflow, and it is now something we use for even the smallest change. This can feel like overkill for small changes, but we're often surprised how often our coworkers will correct errors or offer excellent suggestions even on small commits.

$ g checkout -b decks-ordering

Code Away

In the video, we use the :Gstatus command provided by the Fugitive.vim plugin (check out the Vim & Git Step on this trail for more detail on this), although the command line or GUI tools like GitHub Desktop would work just as well.

The one main recommendation is to try to keep the commits small. In the video there is only a single commit due to the simplicity of the feature, but on larger features it's generally a good idea to commit regularly. Are your tests passing? Commit away! Just finished a nice refactoring? Great time to commit! Going to lunch? Why not commit? Commits are cheap, and we can always refine them with interactive rebase at the end.

Push Up to a PR

In order to create a pull request (PR), we'll need to push up our local branch to GitHub. In addition, we'll want to set up tracking, which we can do with the -u flag to the git push command:

$ git push -u origin decks-ordering

Now that the branch has been pushed up to GitHub, we have a few options for opening our PR. Many of the developers prefer to use the hub pull-request command, which allows them to author their PR title and description locally in their favorite editor. In the video, we used the pr subcommand discussed in the Github & Remotes video, which will open the GitHub pull request creation page directly.

One benefit of using the GitHub UI is that by virtue of doing so much code review via the GitHub compare view, my brain has been trained and conditioned to review code there. Typically when composing a PR, I'll actually scan through the diff one last time, essentially performing a code review on my own code. Surprisingly often I'll find something and make a quick edit before actually opening the PR.

PR Authoring Tips

Here are a few tips that help keep things moving when going through code review. They all center around the same theme, which is making it easier on your reviewer. You want your reviewers to be able to pop in and understand and comment on your code in just a few minutes. Anything you can do to help them is helping you get better and more rapid feedback on your code.

Break up your changes

Split architecture changes from implementation. For instance, here is an example of Ben refactoring some of our charge related code, followed by the PR that actually introduces the feature:

By splitting these changes up, he made it easier to review the code and give meaningful feedback.

Provide context in the description (the why)

Another tip is to provide as much context as possible when drafting your PR description. Try to provide as much useful detail as you can. Answering the following questions is a great start:

  • Why is this change needed?
  • Were other solutions considered?
  • Were any assumptions made?

As an example, this PR updates a setting on the Upcase video embeds. You can see that the code change is a single line, while the PR description (and assoicated commit message) is two paragraphs long, capturing all the context that went into the change.

This sort of context makes it much easier for a reviewer to put themselves in your shoes and better understand the choices that you made. Plus, it makes a great dress rehearsal for the final commit message!

Include screenshots for UI changes

For any UI-related changes, often the best context you can give is a screenshot or two showing the changes to the UI. As an example, here is a PR that updated the code and table styling. This would be somewhat hard to review just based on the CSS changes, but the before and after screenshots in the PR description make the change clear.

Animated gifs

If a picture is worth a thousand words, then an animated gif is priceless. Gifs can be great for demonstrating more complex interactions and workflows. As an example, the PR introducing the admin flashcard editor includes a gif in order to demonstrate the whole flow, rather than just describe it.

One tool for easily capturing gifs is licecap. An odd name, but a great tool.

Consider opening early

One final tip about opening your PRs is that, from time to time, especially when working on a bigger PR that you just can't break down into smaller parts, it can be useful to open the PR early and use Github Task lists to indicate the WIP status, as well as lay out the plan for the remaining work.

This gives your teammates a chance to comment on the overall approach, and perhaps offer suggestions on alternate architectures or design choices, while also making it clear that they should hold off on a more in depth code level review.

As an example, we used a task list with the Continue Trail Redirect work connecting Upcase Exercises and Upcase proper.

Wait for PR Comments

While the previous tips provide a handful of ways to make your code easier to review, it's also worth mentioning that there is an art to providing good feedback via code review. We won't go deeply into that here, but two great resources on the topic are Derek Prior's talk on Code Review Culture and the thoughtbot guide to code review.

Revise and Push Updates

After waiting for code review and any automated PR checks like CodeClimate, TravisCI, CircleCI, or HoundCI, as well as code review comments from your team, you can go through and update your code based on the comments. Fix any failing specs or style issues, clean up code based on PR comments from reviewers, and generally try to refine and refactor the code to make it the best it can be. Push up any new commits on the branch and they'll automatically be included in the PR.

When making these cleanup commits, we'll often use commit messages like "PR comments", knowing that these commits will be squashed down before merging in.

Rebase for Fast-Forward Merge

One of the guiding principles in the thoughtbot Git flow is that we generally prefer a clean history built using fast-forward merges. In order to ensure this, before merging our PR we always pull master and rebase our feature branch onto master to ensure that our commits are ahead of master.

One nice helper for this is the mup alias which checks out master, pulls, then checks back out our feature branch:

# ~/.gitconfig file
[alias]
  # ...
  mup = !git checkout master && git pull && git checkout -

Interactive Rebase

Once we're ahead of master, we can perform an interactive rebase to revise our commits and craft our history. In particular, we can use this time to squash down cleanup and WIP commits, ensuring that each commit we keep is useful and has a solid commit message.

Composing the Final Commit Message

This is the time to ensure that we've captured as much context as possible in our commit message to describe the "why" of the change. Two great resources on this topic are the Giant Robots post, Five Rules for A Good Git Commit Message and Stephen Ball's Deliberate Git talk.

Force Push Our Branch

If we've performed any form of rebase, then we'll have created new commits and will want to push those up to GitHub in order to get everything in sync. To do this we can force push (git push -f) our branch.

Note - You should only force push onto your branch, never onto master or any other branches that colleagues may be working on.

Close the Pull Request

Now we're ready to close and merge our pull request. There are a few steps to this process which we'll list out below:

Ensure the PR branch is up to date

If we've force pushed after rebasing as described above, we should be all set, but never hurts to give one last git push just to confirm that our local and remote feature branches are in sync.

Merge fast-forward

Here we can check out master and use the - argument, representing the previous branch, to perform our merge:

$ git co master
$ git merge -

Since we've made sure to rebase, this will inherently be a fast-forward merge, but we can use the --ff-only flag to ensure this:

$ git merge - --ff-only

Push master

Now that we've merged master, we can push it up to GitHub with git push.

As a reminder, with a fast-forward merge we are simply moving our master branch pointer to point at our feature branches tip commit, not actually creating any new commits. This is one of the main benefits of using fast-forward merges, namely that all commits are created and can be reviewed on our feature branch before merging into master. With "Big Green Button on GitHub" merges and other non-fast-forward merges, the merge commit is created directly on master based on Git's merging algorithm.

Delete local branch

Next, we can clean up our local branch with:

$ git branch -d decks-ordering

Delete remote branch

And finally we can delete our remote branch. Now we have two options as to how to achieve this:

  1. We can run git push origin --delete <branchName> from the command line (as of Git 1.7)
  2. We can delete the branch via the GitHub PR page, and then git pull on master, letting the fetch prune setting automatically clean up our local reference to the remote branch.

Pull request auto closing

Assuming we've performed the steps outlined above, GitHub will have automatically closed the PR based on the fact that master now contains our branch's commits.

Conclusion

And with that, we have our feature merged into master. By purposefully crafting our history on our feature branch, writing detailed commit messages that fully capture the context and the "why" for our change, and by using fast-forward merges, we've ensured that our future teammates (and ourselves) will be able to revisit this change and fully understand it.

We here at thoughtbot truly value this sort of concise and detailed history and it is a cornerstone of our approach to development. The workflow outlined in this video ensures we have this solid history to come back to, while remaining lightweight enough for even the smallest of changes.