Consider three remote branches origin/master
, origin/staging
and origin/production
. The master is the shared developers' edge. Staging
is what is tested before a push to production and production is the code
that gets deployed.
Locally, I have a development
branch that I rebase frequently from my
local master
which tracks the remote origin/master
. The development
branch is what I work in if I am not working on a topic branch for a
certain feature.
$ git branch
* development
master
staging
production
I commit in very small increments with the feature tracking number from my project planning tool (i.e. jira ticket number, lighthouse number, pivotal tracker card id, etc.). I put the number as the first bit of information in my commit message.
$ git commit -m "[#1122] Test coverage on widget X"
$ git commit -m "[#1122] Slight regression, but nice refactoring on widget X"
$ git rebase master
$ git commit -m "[#1122] Feature for widget X is complete"
$ git rebase master
Note that I don't commit local changes directly to my local master
.
This allows me to git pull
my local master without rebasing.
(Admittedly for this example, I would have likely created a topic
branch for the #1122 feature).
When I am finished and some commits I want to share, I rebase them to master interactively. This allows me to squish multiple commits down.
$ git rebase -i development master
Once satisfied, I can push my changes up to origin/master
.
NOTE: The previous rebase
command should have put you into your local master
branch. Check with a quick git branch
and git status
; it's a good idea to
get in the habit of checking the status, log (gitk, gitx, etc.) and what branch
you are in throughout the process.
$ git push origin master
I recommend not cherry-picking across locally-tracked versions of remote branches. So avoid cherry-picking from staging
to master
(assuming these track origin/staging
and origin/master
respectively). There should be
no issue cherry-picking across local non-shared branches.
We git-tag origin/production
with release tags. A patch is a bug that
needs to be deployed immediately -- it can't wait for the next release. The
workflow is to create the patch that ends up in origin/staging
, then
deploy to the staging environment to test. After successful testing push
origin/staging
to origin/production
, git-tag and then deploy to the
production environment.
Here's an example, assume my local staging
is tracking origin/staging
:
$ git checkout staging
$ git pull origin staging
$ git checkout -b patch_for_1001
Test, code, commit, code, commit, etc.
$ git checkout staging
$ git rebase -i patch_for_1001
Here squish all commits into a single one for staging -- makes maintenance simpler, and ("Half of a bugfix is useless.")1.
At this point, we should test the patch on the staging environment. If
successful, we can do the push to origin/production
, create a new git-tag,
and run our deploy scripts to the production environment.
We would like to backport patches to origin/master
. Since origin/staging
is a branch of origin/master
, there is some point of shared history. Rebasing
either from our patch branch or from staging (after the patch has been applied
to staging) should be analogous -- with the exception of squishing during
an interactive rebase. Assume your patch_for_1001 has been rebased into
origin/staging
, you can now rebase master from staging.
Make sure we are up to date:
$ git checkout master
$ git pull origin master
$ git fetch origin staging:staging
Then rebase staging into master.
$ git rebase staging master
Observe the git history and make sure things look okay (gitx
, gitk --all
, etc).
Now since we have re-wound the tip, we can't do a regular git push, as we will
get an error like this:
$ git push
To [email protected]:project
! [rejected] HEAD -> master (non-fast forward)
error: failed to push some refs to '[email protected]:project'
To prevent you from losing history, non-fast-forward updates were rejected
Merge the remote changes before pushing again. See the 'non-fast forward'
section of 'git push --help' for details.
So to avoid confusion with the people who fetch from the branch, we have a policy
that we will regularly be rewinding the tip of the branch for patches. This
should avoid confusion when seeing (forced update) when pulling down from
origin/master
.
Therefore we push as "non-forward", using the +
modifier.
$ git push origin +master:master