For very simple patches that are known to be distinct from patches worked on by other people, git-pull --rebase
may be sufficient to overcome the problem with diamond commits messing with the history. For any type of meaningful development the recommendation for Starlink is to always use a feature branch and handle merging/rebasing in the branch before fixing up master. Git is designed to make branching trivial so we should make use of the facility. It's almost no additional work to work on a development/feature branch (it's known as a "feature" branch because the concept is that each feature you add is kept conceptually distinct in the history).
% git branch dev
% git checkout dev
< edits and commits>
Meanwhile origin/master has had some changes so bring them into your repository
% git checkout master
% git pull
% git checkout dev
If you have some uncommitted changes you may want to stash them first, especially if you are worried that the pull will conflict with a local change (changing branches preserves local changes but can cause conflict if that file is changed in both branches), so in long hand that becomes
% git stash
% git checkout master
% git pull
% git checkout dev
% git stash apply
Now you have an up to date master and some changes on your dev branch
A - B - C - D - E master
\ - F - G dev
Since no-one has seen your patches you are free to tweak the SHA1s at this point. Rather than merge dev into master (resulting in a merge commit) you can linearize the history by rebasing
% git rebase master
will reapply F and G on your branch as if they happened after D and E. This may result in a conflict (but it would have resulted in a conflict when merging) so you can resolve the conflict and use the --continue
option to continue the rebasing. You now have
A - B - C - D - E master
\ - F' - G' dev
and if you want to push those back to the repository
% git checkout master
% git merge --no-ff dev
% git push
As written you will get a merge commit because of the use of the --no-ff
option. Your history will now look like:
A - B - C - D - E - - - - - M
\ - F' - G' /
The merge commit indicates to a reader of the history that F' and G' were done as part of a single feature. Indeed, it is common for the merge commit to include the Github issue number along with some text to indicating that it is close:
Completes #4
when the commits are pushed the ticket #4
will automatically be closed. Without this merge commit there would be no way of knowing that a feature was finished or which commits were part of it. The only solution would be to squash F' and G' into a single commit and include the information in that commit.
In some cases you don't need a merge commit. For example you were working on a few small unrelated things on a development branch and you just want them to be added to master, or your feature was conceptually a single self-contained commit. In that case the rebase and merge will result in a "fast forward" merge with no special merge commit. Your history without the --no-ff
would look like
A - B - C - D - E - F' - G' master+dev
HEAD is simply moved to G' and dev and master will share the same HEAD.
The push will work so long as no-one has committed since you pulled. You can now delete dev since it has no additional information.
% git branch -d dev
If you keep dev alive it will branch from G'
A - B - C - D - E - F' - G'
\ - H