Want to see the full-length video right now for free?
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.
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.
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
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.
[Vim & Git Step]: https://upcase.com/videos/git-vim-and-git
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.
[GitHub Desktop]: https://desktop.github.com/
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.
[the Github & Remotes video]: https://upcase.com/videos/git-github-and-remotes
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.
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.
[Inject downgrade_plan into Cancellation]: https://github.com/thoughtbot/upcase/pull/1251 [Update a user's card when they sign up again]: https://github.com/thoughtbot/upcase/pull/1252
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:
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!
[this PR updates a setting on the Upcase video embeds]: https://github.com/thoughtbot/upcase/pull/1346
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.
[PR that updated the code and table styling]: https://github.com/thoughtbot/upcase/pull/1353
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.
[the PR introducing the admin flashcard editor]: https://github.com/thoughtbot/upcase/pull/1295 [licecap]: http://www.cockos.com/licecap/
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.
[Github Task lists]: https://help.github.com/articles/writing-on-github/#task-lists [Continue Trail Redirect]: https://github.com/thoughtbot/upcase/pull/1259
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][].
[Derek Prior's talk on Code Review Culture]: http://confreaks.tv/videos/railsconf2015-implementing-a-strong-code-review-culture [thoughtbot guide to code review]: https://github.com/thoughtbot/guides/tree/master/code-review
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.
[CodeClimate]: https://codeclimate.com/ [TravisCI]: https://travis-ci.org/ [CircleCI]: https://circleci.com/ [HoundCI]: https://houndci.com/
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 -
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.
[craft our history]: https://upcase.com/videos/git-crafting-history
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][].
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.
[Stephen Ball's Deliberate Git talk]: http://rakeroutes.com/blog/deliberate-git [Five Rules for A Good Git Commit Message]: https://robots.thoughtbot.com/5-useful-tips-for-a-better-commit-message
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:
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.
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
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.
Next, we can clean up our local branch with:
$ git branch -d decks-ordering
And finally we can delete our remote branch. Now we have two options as to how to achieve this:
git push origin --delete <branchName>
from the command line
(as of Git 1.7)git pull
on
master
, letting the [fetch prune setting][] automatically clean up our
local reference to the remote branch.[fetch prune setting]: https://upcase.com/videos/git-customizing#useful-configurations
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.
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.