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