I like following a Git development model with 3 main branches. A good description of the model (with diagrams) can be found in the Successful Git branching model blog post. The main features of this model are:
- There is a master (or development) branch. All commits are merged there (usually after code review), and I like to collapse separate feature branches into a single commit before landing the feature on the master branch.
- The latest master branch is deployed to a dev server for testing.
- Once a feature is confirmed working on the dev server, we can cherry pick it onto stage branch. Again, there is a corresponding staging server, where the latest stage branch gets deployed after each commit.
- Once a feature is confirmed to work in staging, we cherry pick it to the production branch.
- The decision when and how to deploy the production branch is complex. I recommend waiting for a meaningful list of features to release and then use "blue green" deployment model to ensure clean release.
If each commit corresponds to (roughly) a single feature, then we need to a way to figure out which commits are still on dev, which ones are on stage and which ones are already on prod branches. This can get quite complex, since the cherry picking the commits might be done by a separate testing team, not the original developer. Here are a couple of Git commands to use when trying to figure out which features (commits) can be moved from branch to branch.
Plain file comparison
Imagine we have the latest master (which is our development) branch locally. How is the file different from the same file on stage branch? If both branches are available locally then it is simply
git diff master stage <filename>
If you are already on the "master" branch and want to compare the file to "stage", you can use "..stage" notation (".." means "from the current branch")
git diff ..stage <filename>
In general, Git uses the notation
<to>..<from> when comparing things (files, commits)
git diff master..stage <filename>
In our case, the
master is the current code, the
stage is behind, thus
stage is the
Most likely you will not have the remote branches locally (that's why they are remote!). In this case, you will need to fetch information about available remote branches (without the content) and then compare the file
git fetch --all git diff master origin/stage <filename> git diff ..origin/stage <filename>
Next, let us look at the big picture - there are lots of commits on "master", which ones are on the remote "stage" already and which ones are not yet? I use the following command to see the list of local commits on the current branch:
git log --oneline
Since I am mostly interested in my own commits
git log --oneline --author gleb
Similarly, we can view the list of commits by specific user on the remote branch
git fetch --all git log --oneline --author gleb origin/stage
Since our local environment might be behind, I recommend looking at the remote master as the ground truth
git log --oneline --author gleb origin/master
Now we can compare the two lists to find my commits that are on master but not on stage yet.
git log --oneline --author gleb origin/stage..origin/master 14a0104 feature one c8c910f feature two ...
Similarly, we can check which commits are already on stage, but not on prod
git log --oneline --author gleb origin/prod..origin/stage
With this information we can cherry pick specific commits from one branch to the next one.
Picking (moving) commits
Once we have a bunch of commits, let us move them from one branch to another. For this you do need the actual branches locally. Once you have local branches "stage" and "master" let us get the "stage" commits
git checkout stage git pull --rebase origin
Then compare the local list of commits again (using the local branch "stage" against the local branch "master")
git log --oneline --author gleb ..master
Then take the commit you want to pick and apply, for example
git checkout stage git cherry-pick -x aaabbb
-x is important here - the commit lands on "stage" with a NEW SHA ID. Thus to
know the original commit, we want to know where it came from. The option
-x adds a line
to the commit on "stage" with the source information, something like
commit dddeee feature ... (cherry picked from commit aaabbb)
Which will be very useful in the future when trying to figure out which commits are still have not been moved from "master" to "stage"
Let's say our work branch is called
develop and our production branch is
master. When we are happy with the code in
develop branch we want to push some commits to
master. Pull all code then switch to develop
branch. Then show all commits that are NOT onmaster` yet.
git checkout develop
If we switch to
master we need to reverse the direction and place
.. before the source branch name
git checkout master
List of commits yet to be cherry picked
With the above approach, if we do not rebase, pretty soon we will see a lot of commits in the
difference log between the two branches. The
git log does not understand that the two branches
have the same commits (since cherry picked commit gets a new ID), thus shows the same stuff over
and over. A good thing is that if we try to cherry pick the same commit several times, the diff
will be empty (unless there are file changes), and will not go through without
One way to show which commits have not been cherry picked yet is to use the git cherry command
git cherry stage master - aaabbb + ffffff
Which shows a list of commits marked '+' that have not been cherry picked yet
ffffff in this case). I prefer adding
-v option to show the messages with each commit ID.
This is much shorter than the options available in
git log command for filtering the already
git log --left-right --cherry-pick --oneline stage...master > ffffff
For practice, here are a couple of commands to run in an empty folder that will create a couple of branches and will add a few commits. Just make a new folder and copy/paste
git init git touch a.txt echo start >> a.txt git commit -am "start" git checkout -b stage git checkout master echo one >> a.txt git commit -am "feature one" echo two >> a.txt git commit -am "feature two" echo three >> a.txt git commit -am "feature three"
At this point we have 4 commits on "master", and 1 commit on "stage"
$ git checkout master $ git log --oneline 824b453 feature three aa3c02e feature two 4a038e3 feature one 79d06af start $ git checkout stage $ git log --oneline 79d06af start
There are 3 commits on "master" not on "stage"
git log --oneline ..master 824b453 feature three aa3c02e feature two 4a038e3 feature one
All 3 commits can be cherry picked from "master" to "stage"
$ git cherry -v stage master + 4a038e331b1c16a1a8847a6e91a5516dbd06bd6b feature one + aa3c02ec512acc57a1c02ac5ddc7aff480b4cb1d feature two + 824b453d61477e9d59c963892eab0e712be0a5f4 feature three
Let us pick "4a038e3 feature one" commit to the "stage" branch.
git cherry-pick -x 4a038e3
Now we have the same commit but under the different SHA. We can plot the divergent branches
git log with a few more options
$ git log --oneline --graph --all --decorate
The list of commits that can be cherry picked is shorter
$ git cherry -v stage master
We can bring the second feature from "master" to "stage"
$ git cherry-pick -x aa3c02e
At some point the log becomes too long, and you would want to limit it using a commit ID
to stop the list (see
git help cherry).