Skip to main content

Handling Long-Lived Feature Branches and Rebasing

When using Gitflow, long-lived feature branches can create complications if other branches are based on them and the original feature branch is later merged to master via a squash merge. This guide explains how to manage these situations, including using git rebase instead of git merge to avoid conflicts and how to effectively rebase branches after a squash merge.

1. Handling Extensive Commit Histories and Merge Conflicts

When the commit history is extensive, and there are multiple merge conflicts to address, it is advisable to do the rebasing work on a separate branch to ensure the changes are safe and verified. You can follow these steps:

  1. Create a New Branch for Rebasing

    git checkout -b feature/your-branch-name-rebase-1
  2. Perform the Rebase

    git rebase origin/target-branch
  3. Verify the Contents After rebasing, use git diff to verify the differences between the original branch and the newly rebased branch:

    git diff feature/your-branch-name feature/your-branch-name-rebase-1 <filename>

    This helps you confirm that the intended changes are correct and no unintended changes were introduced.

2. Keep Feature Branches Up-to-Date Using git merge or git rebase

1. Scenario: Branch originated from master

In this scenario, the feature can be kept up-to-date either by merge or rebase. Since the branch originated from master, both methods will bring the changes to the feature branch. And in the PR view of this branch, we would see only related commits that are going to be merged to the master branch.

2. Scenario: Branch originated from another branch than master

Suppose we have two branches:

  • feature/feature-1: A branch that introduces major changes and is actively developed.
  • feature/feature-2: A branch based on feature/feature-1.

If feature/feature-1 is merged to master using a squash merge, feature/feature-2 will no longer have a straightforward history compared to master. This can create significant rebase complications.

Caveats:

  • Rewriting History: Rebasing rewrites commit history, which may require force-pushing (git push -f). Be careful not to overwrite others' work, and always communicate with your team if you are sharing branches.

3. Rebasing after originated branch is squash merged to master

When a feature branch, such as feature/feature-1, is squash merged into master, any branches based on it (e.g., feature/feature-2) need to be rebased to reflect the new state of master. Considering that the PR branch has been deleted after merge, so the following process will help you rebase successfully.

Fetch deleted PR references

First, run the following command to ensure you have access to all remote references, including deleted PR branches:

git fetch origin +refs/*:refs/remotes/origin/*
  • What this command does: This command fetches all references, even those that were automatically deleted after the PR was merged.

Locate the original branch for Rebasing

Next, locate the deleted branch that was merged via squash using:

git branch -r | grep "pull/PR_NUMBER/head"
  • Note: Replace PR_NUMBER with the actual PR number. This will give you access to the latest version of the deleted branch.

Rebase feature branch onto master

Once you have identified the correct branch reference, rebase your feature branch (e.g., feature/feature-2) onto master:

# On feature/feature-2 branch
git rebase --onto master origin/pull/PR_NUMBER/head
  • What this does:
    • git rebase --onto master: Rebases the branch starting from the point where it branched from feature/feature-1 onto master.
    • Instead of giving directly master, it is also possible to give the commit hash of the commit where the feature/feature-1 was merged into master. This might be needed if the changes in feature-2 is not yet compatible with the ones later in master.
    • origin/pull/PR_NUMBER/head: Uses the latest commit from the original feature/feature-1 branch before it was squashed and merged.

Verifying Changes:

After the rebase, verify the new commit history with:

git log --oneline -30 | cat

Ensure that the changes align with the intended commits after the rebase process.

Resolving Conflicts

During the rebase, you may need to resolve conflicts. Always pay careful attention to conflicts that arise and resolve them thoughtfully to maintain consistency with the changes from master.

Branch Tree for Squash Merge Scenario

Before Squash Merge of feature/feature-1 into master

A---B---C  master
\
D---E---F feature/feature-1
\
G---H feature/feature-2

Current status:

master...feature-1 -> 3 commits ahead, 1 commit behind
master...feature-2 -> 4 commits ahead, 1 commit behind
feature-1...feature-2 -> 2 commits ahead, 1 commit behind

After Squash Merge of feature/feature-1 into master

In repository, you can still access to reference of the branch even if ti was deleted.

A---B---C---S master
\
D---E---F origin/pull/PR_NUMBER/head (deleted feature/feature-1)
\
G---H feature/feature-2

= (S is the squash merge commit representing all changes from feature/feature-1. origin/pull/PR_NUMBER/head retains commits D, E, F as they originally were, and feature/feature-2 is still based on these commits.) If the feature-2 branch was kept rebased on top of the feature/feature-1 branch, it would have the F commit as the base, and it would be easier to rebase it onto the master branch.

Current status:

master...feature-2 -> 4 commits ahead, 2 commit behind

After Rebasing feature/feature-2 onto master with git rebase --onto master origin/pull/PR_NUMBER/head

A---B---C---S master
| \
| G'---H' feature/feature-2
\
D---E---F origin/pull/PR_NUMBER/head (deleted feature/feature-1)

(G' and H' are the rebased versions of commits G and H.)

Current status:

master...feature-2 -> 2 commits ahead