This document describes the high-level process of using git as part of the software development process, including development commits, rewriting history, code reviews, squashing, rebasing, and merging.
Not all projects will conform exactly to our git best practices. Some are deliberate deviations; others are accidental. As a first step, check the project README for any mention of how to use git. Project-level instructions should supercede general best practices.
Start by creating a branch from the release branch or from the master branch if your project is not using the release branch strategy. Switch to your new branch.
Now, implement the feature. The focus is generally on development, not using git, during this phase. Some developers will not use git at all until the entire feature is done. Others will add code to the index when it feels done, but commit only at the end. Others will create terse work-in-progress (WIP) commits as they go. Still others will take the time to create commits that are cleanly separated in functionality and well described.
Any of these approaches is acceptable. It is useful to maintain your development flow and not get hung up on git. However, note that it is a kindness to your reviewer to have good logical commits, and commits are generally easier to combine than to separate, so you may find the overall development process easier if you bias towards many small commits rather than committing at the end.
Next, tell a logical story of how this feature came to be, in preparation for your reviewer seeing your code. As described in The Purpose of Git, we don't recommend using git as a log of what happened but rather as a readable history. At a high level, the difference is that the latter will contain cleanly separated and well described commits.
Unless you were disciplined with your development process and already have a
good story in your commit messages, you will need to rewrite history. Our
general recommendation is to use git rebase -i
for
this. Commit Messages talks more about what a good
history looks like and includes a tutorial of using git rebase -i
. Just
remember: the point is to explain what you did, and why, to your reviewer, so
that he doesn't get a large glob of changes with no explanation.
Once your history of changes is ready for review, open a pull request (called a merge request in Gitlab). Inform your reviewer that the code is ready for review.
Sometimes your reviewer will approve your code as-is, with no revisions needed. Other times, he may have questions about what you did or why you did it that way. Other times, he may want you to change what you did in some respects, either by yourself or in a pairing session.
If you do need to change your code, append new commits to the history rather than rewriting history. Once your reviewer has read your history, don't change the story on him. If various kinds of changes (or "fixups") are needed, keeping each kind in its own commit is a kindness to your reviewer, since it keeps related changes closer together.
When your code is approved to be merged, there are a few things to do to ensure clean merging: rebase, squash, merge, push, and clean up.
To ensure a linear history, it's important to rebase
your branch onto the HEAD
of the
current release branch. First, fetch the latest code
from origin
. Then rebase your code onto it.
Once your branch is rebased, squash your commits. In most cases, a feature is a good granularity for commits at this level. Again, the Commit Messages best practice provides a good way to think about this issue.
Next, merge your branch into the release branch, typically with the --ff-only
option to git merge
. More details can be found in
the Merge Strategies best practice.
Next you need to push your local updates to the origin
remote, so that others
will see your changes.
If you get an error that a fast-forward merge was not possible, it may mean that somebody else pushed commits to the release branch, and you need to fetch the latest commits and rebase again.
You should be careful to push your local feature branch (typically using the
--force-with-lease
option to overwrite history on the remote) before you push
your local release branch so that your pull request (or merge request) is
correctly marked as "merged".
Finally, your feature is done and merged into the release branch. Delete your
feature branch, both locally and on the origin
remote, so that it doesn't
linger and clutter the list of open branches.
Before starting a new feature update master
using:
git rebase origin/master
So far, we've elided the commands one would use during development, in order not to distract from the concepts. We include them here for completeness.
To create a new branch named feature
that branches from release
, and
simultaneously switch to the feature
branch, say:
git checkout -b feature release
To create commits during development, say git status
and follow the
instructions to add and remove files from the index, or staging area for changes
before they are committed. When you're ready to create a commit from what's in
the index, say git commit
. (You may want to set your $EDITOR
environment
variable before you do.)
To rewrite history, we recommend reading
the relevant section from the Pro Git Book.
Don't worry too much about messing things up. If you do, you can abort any
in-progress rebase or, worst case, simply reset your branch to be what's on
origin
. Remember that your local changes (including rebases) aren't published
until you push
. If you're intimidated, ask for help.
To open a pull request, you can use the web-based UI of GitHub or Gitlab. Or, if
GitHub is your origin
, you can use the hub
command for to open a preview
page for the pull request. The following instructions assume git
is aliased to
hub
, which is common practice when using hub
since it passes unrecognized
commands through to git
.
git checkout feature # switch to your feature branch
git push -u origin feature # push it to `origin`
git compare feature # use the `hub` command to open a comparison webpage
To respond to code review comments, you're usually stuck with the web UI and in some cases email replies. If you want to add new commits to your pull request:
# on your `feature` branch
# do normal development, including `git add` and `git commit`
git push
To rebase your feature
branch onto the latest release
branch:
git checkout release
git pull
git checkout feature
git rebase release
If you run into problems with the rebasing, it helps to remember what rebasing
is actually doing: it replays commits onto a new starting point (the updated
release
branch in this case). git status
can help orient you to where you
are in the rebase process. Also, git rebase --onto
is helpful to more
precisely specify which commits should be replayed where, and git rebase -i
is
helpful to provide a preview of which commits are getting replayed before it
actually happens. But, this is perhaps the most tricky part of using git, and it
does take a while to orient to what's going on. Don't be afraid to ask for help.
Assuming you escaped from the rebase unscathed, you can squash your commits with
git rebase -i
, which opens up a text editor. If you change the commands in all
but the first line to squash
, all of your commits will be squashed into the
first one, and you will have the chance to combine the commit messages before
everything is committed. For more information, read
the Pro Git book or
Deliberate Git.
To merge your squashed feature
branch into release
, say:
git checkout release
git merge --ff-only feature
To push your changes up, say:
git checkout feature
git push
git checkout release
git push
The ordering is important here, so that any open PRs will get marked as "merged" instead of merely "closed".
If your last git push
was rejected, it probably means that somebody else
pushed commits to origin/release
before you could. In that case, from the
release
branch, say:
git fetch origin
git rebase origin/release
git push
To delete your feature branch locally and on origin
, say:
git branch -d feature
git push origin --delete feature