Skip to content

Instantly share code, notes, and snippets.

@sskeirik
Last active June 4, 2025 04:34
Show Gist options
  • Save sskeirik/4cdc134e87245385cf5f60bbc3cfc42f to your computer and use it in GitHub Desktop.
Save sskeirik/4cdc134e87245385cf5f60bbc3cfc42f to your computer and use it in GitHub Desktop.
GitHub PR Merge Strategies

GitHub PR Merges

Introduction

When a PR is opened on GitHub, there are three branches/commit references involved:

Branch/Commit Ref Description
base The branch into which changes will be merged
head The PR branch which contains changes to be merged into base
merge-base The most recent common ancestor commit of base and head

In all cases, the goal of the PR is to incorporate all changes from the commit range merge-base..head into base. Let's call the new commit (generated as a result of the merge) base' (which we pronounce as base prime).

Standard PR Merges

Let's consider an example where commit C is base:HEAD and commit D is head:HEAD.

-A---B---C (base)
  \
   `-D---E (head)

In this case, the merge-base is commit A and the goal is to merge changes from D and E on top of commit C. To do this, one of three strategies is used:

  1. Merge

    In this case, a merge commit F is created with parents C and E.

    -A---B---C---F (base')
      \         /
       `-D---E-'   (head)
    
  2. Squash-Merge

    In this case, commits D and E are squashed into a single commit G (represented by the branch squash) and then a merge commit F is created with parents C and G.

    -A---B---C---F (base')
     |\         /
     | `-G---*-'   (squash)
      \
       `-D---E     (head)
    
  3. Rebase

    In this case, commits D and E get mapped to commits D' and E' on top of C (since they may be modified during rebasing).

    -A---B---C---D'--E' (base')
      \
       `-D---E          (head)
    

Fast-forward PR Merges

To avoid merge conflicts, a very common situation is that PRs are created where the merge-base is the base commit, i.e., we have a situation like:

-A---B---C         (base)
          \
           `-D---E (head)

In this common case, there is actually no need to create a merge commit since all of the desired changes are already encapsulated in commit E. However, what GitHub actually does is the following:

  1. Merge

    In this case, a useless merge commit F is create with parents C and E whose contents are equal to E.

    -A---B---C---*---*---F (base')
              \         /
               `-D---E-'   (head)
    

    This is because, in this case, GitHub merges with git's --no-ff option.

  2. Squash-Merge

    In this case, commits D and E are squashed into a single commit G and then base branch is fast-forwarded to commit G.

    -A---B---C---G     (base', squash)
              \
               `-D---E (head)
    
  3. Rebase

    In this case, base branch is fast-forwarded to the head branch so both branches become identical:

    -A---B---C---D---E (head, base')
    

Note: In this special case, we have the property that the contents of base' and head are identical.

What about Merge Conflicts?

Suppose we have the following case:

-A---B---C (base)
  \
   `-D---E (head)

If GitHub cannot apply one of its strategies due to merge conflicts, it does one of the following:

  1. gives up (it does this when conflicts are detected during a rebase or if detected conflicts are too complex);
  2. presents the user with a merge-conflict editor.

If the user chooses to use the merge-conflict editor, once the conflicts are resolved, the resolved conflicts will be merged into the head branch, i.e., we will have:

-A---B---C     (base)
  \       \
   `-D---E-`-F (head')

This is done, as far as I can tell, because, ultimately, the head branch is the one which must pass reviewer/CI validation, so all changes must become part of the head branch for testing. If this new commit head' passes CI validation and obtains the necessary approvals, then it can be merged into the base branch, using either the merge or squash-merge strategy strategies depicted above (as GitHub doesn't perform conflict resolution for rebases).

References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment