Skip to main content

Git Collaboration Strategies

Git Merge Versus Rebase

In this article we are looking on strategies on how to collaborate on a shared git repository: we want to compare the two strategies: git merge and git rebase. We make some simple assumptions on the branching strategy, but the discussion can easily be adapted to other branching strategies.

Assumptions

In order to make the discussion concrete we assume the following situation:

  • there is a shared remote code repository with a main branch of production level code
  • each collaborator works on a feature branch with a custom name
  • there are code reviews before feature branches are merged into main

Situation

A developer has worked on feature branch feature, that was split off from main at a commit M2, but now the main branch has expanded to commit M4. So we want to compare strategies to integrate the work that has been done on the feature branch into main.

Beginner's Strategy with Merge

During development of the feature many developers merge main into the feature branch several times to keep up with the latest changes from other collaborators.


Advantages

Merge Conflicts

With merge you only need to solve any merge conflict once at each Merge commit.

Forward Thinking - "safe"

The strategy is "safe": merge is always forward thinking and cannot mess up your code base in any way as it never touches the history.

Good for Squash-Merging a Pull-Request

When you squash merge your pull-request into main you do not take the history onto the main branch and the Sync* merge commits on branch feature are not reachable from main, which make the history linear and clean.


Disadvantages

Bad for Normal-Merging a Pull-Request

When the feature branch is merged, the history is not linear anymore:

Remedy: Squash merge as shown here.

Advanced Strategy with Rebase

Rebase is an advanced strategy as it changes the history of what happened. It has the potential to disrupt other people's work and should always be used with care:

Rebase Explained

Let's consider the same situation: the feature branched off at M2 and both feature and main have moved on since.

Now the developer wants to get ready for review or just integrate the latest changes from main.

With

git checkout feature
git rebase main

you can achieve the following:

This looks elegant and it is. It moves the feature branch forward on main as if it has not been checked out at M2 but instead at the most recent commit M5on main. Almost like wishful thinking, but it comes at a price. The commits F1*, F2*, F3* are not the original ones F1, F2, F3. Those original commits are now detached (git will garbage collect them in approx. 2 weeks).

tip

You can always rebase your feature branches (like feature) since by best-practice you should be the only person to work on it and rewriting history only has impact for you.

git checkout feature
git rebase -i main
warning
  • Never rebase any long-living branches, like main (or release/*) since you rewrite history and thats forbidden on these branches.

    Don't do this:

    git checkout main
    git rebase -i <other-commit-ref>
  • Be careful when rebasing a feature branch where you are not the only one working on since you rewrite history. If you still need to do a rebase on a shared feature branch branch with others, communicate the rebase before hand such that your collaborators know that a rebase happened.


Advantages

Good for Normal-Merging a Pull-Request

If you want your clean commits to be reachable (for the history) on the main branch, then a rebase with normal merge to main will result in a clean history.

Preserving feature history:

tip

But you can also decide to just squash merge to main and forget about the history..


Disadvantages

Collaboration

If a collaborater C uses your feature branch for syncing in your work on its own branch:

After the rebase of the feature branch, a collaborator cannot update their local feature branch anymore due to changed history. The commits are rewritten and now have a different SHA1 hash.

The collaborator can however reset its local feature branch (if its needed, you can always use origin/feature)

git checkout feature # Collaborate C should not work on that branch!
git reset --hard origin/feature

Merge Conflicts

With rebase merge conflicts may need to be resolved several times: a rebase happens like this:

During a rebase commits are transferred one by one: starting at F1

  • resolve merge conflicts for F1, then git rebase --continue
  • resolve merge conflicts for F2, then git rebase --continue
  • resolve merge conflicts for F3, then git rebase --continue

In case you did not plan for this, it can be a tedious process. Compare this with a merge where you resolve all conflicts only once.

Remedies:

  • First clean up all your commits on the feature branch by doing a rebase on its merge base. Starting from

    with git rebase -i $(git merge-base origin/main HEAD) you can reorder/edit/squash commits (from M2) which might result in

    where F3 comes now first resulting in F3* and F2 has been squased into F1 resulting in F1-2*

    This will now help in rebasing again onto main while having less merge conflicts.

Conclusion

Leave the syncing strategy on the feature branch to the developer, to chose what they feel most comfortable with:

  • A sync with merge is easier and safe for beginners but you should

    • Only squash merge your changes to the base branch, e.g. main. Do not do normal merges due to convoluted history.
  • A sync with rebase takes a lot more experience and planning, but has certain advantages in regards to the history.

    • You can normal merge to the base branch, e.g. main, if you want the history on your branch to be reachable (in that case you should have properly formatted commits). Squash merging is of course also possible.