git merge, git rebase

git dev management
By Pierre Feilles Published on Last update on

For a team, a wide variety of workflows are possible using git. Here are options that can be used to integrate to the codebase the work that has been completed on a branch.

The choice depends on the importance of this branch which is to be integrated to the code base, the size of the team and the branching model chosen by the team (keep feature branches visible in the commit history or keep the commit history as linear as possible).

The basic tools are:

  • git merge
  • git merge --no-ff
  • git rebase -i
  • git merge --squash

Then, using these options, it is possible to work according to a chosen general git workflow:

  • A list of possible git worflows
  • A comparison: linear history or feature branches

git merge

For example, to merge a branch named a_branch into master:

$ git checkout master
$ git merge a_branch

The merge algorithm is determined by git automatically.

see: https://www.atlassian.com/git/tutorial/git-branches#!merge

This can be an issue in two cases:

  • There's been no new development on master and the branch a_branch is a feature branch that has a commit history you want to keep on a separate branch. A simple git merge will execute a fast forward merge. Use git merge --no-ff instead, which will force the creation of a merge commit.

  • There's been some development on master and the branch a_branch has a commit history you don't want to keep on a separate branch. A simple git merge would create a superfluous merge commit. Use git rebase --interactive instead, which will replay the commit history of the branch a_branch at the tip of the master branch.

git merge --no-ff

For example, to merge a branch named feature_branch into a branch named develop, while keeping the feature_branch commit history separate from develop:

$ git checkout develop
$ git merge --no-ff feature_branch

This will guaranty the creation of a merge commit at the tip of develop branch, even if there is no conflict.

git rebase -i

This will replay the commit history of a branch (e.g. named a_branch) onto another (e.g. master):

$ git checkout a_branch
$ git rebase master
$ git checkout master
$ git merge a_branch
$ git branch -d a_branch

To have the possibility to modify the series of commits while rebasing, start an interactive session:

$ git checkout a_branch
$ git rebase -i master
$ ...

The branch a_branch is moved and new commits are created. So one should not rebase a branch that has been published to a public repository.

" Rebasing (or any other form of rewriting) a branch that others have based work on is a bad idea: anyone downstream of it is forced to manually fix their history. This section explains how to do the fix from the downstream’s point of view. The real fix, however, would be to avoid rebasing the upstream in the first place." Recovering_from_upstream_rebase

git rebase can be used to keep a commit history linear by assuring that a merge will be a fast-forward merge:

$ git checkout a_branch
$ git rebase master
$ git checkout master
$ git merge a_branch

note : by default, git pull is a git fetch + git merge. This will create superfluous merge commits when local and central repository have evolved at once. So for a linear history workflow, use instead:

$ git pull --rebase origin master

see: https://www.atlassian.com/git/workflows#!workflow-centralized

git merge --squash

For example, to transform a private_branch into a unique commit to the master branch:

$ git checkout master
$ git merge --squash private_branch
$ git commit -v
$ git branch -D private_branch

Useful for 'scratch paper' local branches.

A list of possible git worflows

linear history:

feature branches:

forking workflow:

A comparison: linear history or feature branches ?

feature branches model - http://nvie.com/posts/a-successful-git-branching-model , Vincent Driessen - https://www.atlassian.com/git/workflows#!workflow-gitflow - https://www.atlassian.com/git/workflows#!workflow-feature-branch

linear history model - https://sandofsky.com/blog/git-workflow.html, Benjamin Sandofsky - https://www.atlassian.com/git/workflows#!workflow-centralized


In the feature branches model, feature branches are public or are to become public quickly. It will require the use of git merge --no-ff, for example:

$ git checkout develop
$ git merge --no-ff a_feature_branch

In the linear history model, local branches are preparation work and are to be deleted once the work has been integrated into the master branch.

$ git checkout a_private_branch
$ git rebase -i master
$ git checkout master
$ git branch -D a_private_branch

Even if one is using the feature branches model, the 'pristine' linear history model is useful to develop locally 'scratch paper branches'.


feature branches model

http://nvie.com/posts/a-successful-git-branching-model/

successful branching model

According to this model, "Feature branches typically exist in developer repos only, not in origin." But still, since these branches are to be merged in a no fast forward manner, they correspond to public branches in that their history will eventually be public (we want their 'information' not to be lost, in particular to be able to revert a feature easily).

merge without ff © Vincent Driessen

linear history model

https://sandofsky.com/blog/git-workflow.html

In this model, there is a distinction between private 'scratch paper' branches and a public branch. Here are the resulting workflows (slightly edited) proposed to clean and merge a private branch into a public branch:

small private branch:

$ git checkout -b private_branch
$ ... several commits on private_branch ...
$ git checkout public_destination_branch
$ git merge --squash private_branch
$ git commit -v
$ git branch -D private_branch

The private_branch modifications have been squashed into the stage area and can be commited to the public_destination_branch as one commit. The branch private_branch is deleted, as well as the commit history it contained.

larger work

$ git checkout private_branch
$ git rebase --interactive public_destination_branch

Which gives for example:

pick 0d21fbe adding file2
pick 684c196 xwz
...

# Rebase 6202edc..684c196 onto 6202edc
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

We then choose which commands to apply to the commits, e.g.::

pick 0d21fbe adding file2
fixup 684c196 xwz
...

As a result, a clean commit history is rebased on the public_destination_branch.

Then:

$ git checkout public_destination_branch
$ git merge private_branch
$ git branch -D private_branch

clean slate

If the history on a private branch has become too complex, it is possible to completely transfer the work done on it to a clean slate::

$ git checkout destination_public_branch
$ git checkout -b cleaned_up_branch
$ git merge --squash private_branch
$ git reset

Of course this rewriting of history supposes that these private branches have not been made public.

git merge --no-ff or git rebase -i ?

Benjamin Sandofsky, linear history model

"If you treat your public history as pristine, fast-forward merges are not only safe but preferable. They keep revision history linear and easier to follow. The only remaining argument for –no-ff is “documentation.” People may use merge commits to represent the last deployed version of production code. That’s an antipattern. Use tags. "

Vincent Driessen, feature branches model:

"The --no-ff flag causes the merge to always create a new commit object, even if the merge could be performed with a fast-forward. This avoids losing information about the historical existence of a feature branch and groups together all commits that together added the feature. "

I would say it is a question of focus: if the feature branches model is strictly enforced, each individual branch in this model should have a commit history following the linear history model. Scratch paper private local branches are to be squashed or rebased and then deleted. And the remaining branches are designed according to a feature branch model for which each individual branch has a very clean linear history.

Comments

comments powered by Disqus