DEV Community

Cover image for Options to close pull requests on GitHub
Kacper Rychel
Kacper Rychel

Posted on • Edited on

Options to close pull requests on GitHub

One of the common practices of contributing with a developer's solution to a common main code base is Pull Requests.

Are pull requests good practice? Dave Farley doubts it on: Why Pull Requests Are A BAD IDEA.

Table of content

Pull Requests on GitHub

On GitHub, you can choose up to 3 ways of closing them, by adding the code to the main branch. You can choose which ways are allowed for a given repository in its Setting > General:

pull requests options in settings

And this is how it looks like in a pull request:

pull requests options in pr

As we can see, merge, squash, and rebase are explained in the GitHub repository settings and also in pull requests. So, we can close the topic, can't we?

Not just yet. Those commits types are a little bit more complex. You can read more about that in the GitHub documentation. In this post, however, we will go into more details to understand how it looks in practice.

Create a merge commit

he default way of closing a pull request on GitHub is merge. The git documentation says about it:

Incorporates changes from the named commits [...] into the current branch.

What Incorporates mean, it is easier to show than describe, so let's see.

Merge in practice

After Create a merge commit button is pressed, the input fields appear. We can edit a merge commit message here, it is: its title and its body.

merge message

This is the result of the Merge pull request:

git tree after merge

The changes from the feature have been merged with a new merge commit. Now 007a0485 and 0f830a20 commits are a part of the main, but still only on a side branch.

Now, we can delete the feature from GitHub since deleting feature branches is a good practice. Keep in mind, that branches removed from the origin by GitHub are not removed from your local.

remove branch after merge

git tree after merge and removal

TIP 💡
Do not call your branches just feature or release. Such feature causes troubles with e.g. feature/whatever later.

Default messages of merge

GitHub generates a default commit message depending on a chosen option in repository Setting > General.

merge default messages

Default message

The default message generates a commit title with the pattern: Merge pull request #<PR number> from <source branch>, where #<PR number> is a reference to the current pull request (#2 in the sample above). From Git point of view, it is just a plain text, we can put it to a message even locally. GitHub resolves each #<number> syntax to a link to a pull request of a given repository, even if such a pull request doesn't exist.

The body is consisted of:

  • merged commit's message - in case of only one commit to merge
  • PR's title - in case of multiple commits.

Default to pull request title

Only the title of the commit is generated with the pattern <PR title> (#<PR number>). The body is empty.

Default to pull request title and description

In addition to the Default to pull request title, the commit body is copied from the PR's description.

Squash and merge

The second method of closing pull requests on GitHub is squash. According to the Git documentation:

Produce the working tree and index state as if a real merge happened [...]. This allows you to create a single commit on top of the current branch, whose effect is the same as merging another branch.

Squash in practice

This is the result of Squash and merge option on GitHub:

Before:

git tree before squash

After:

git tree after squash

What happened here?

There is a new commit pushed to the main with the changes from feature/6 branch, but both main and feature/6 branches are not related to each other. It looks like a merge of another branch than feature/6.

After the deletion of feature/6 from both remote and local, the history looks like this:

git tree after squash and removal

There is no sign of feature/6 at all. The only trace kept is the reference to a pull request (#3), so pay attention to keeping it in commit messages.

Such result is called linear history. GitHub allows us to enforce such history for given branches as their Branch protection rule. You can read more about it in the documentation: About protected branches > Require linear history.

Default messages of squash

Squash and merge option generates default messages as well, and the same as in Merge pull request option.

squash default messages

Default message

The default message generates a commit depending on a count of commits to squash and merge:

  • 1 commit

    • the title pattern: <commit title> (#<PR number>)
    • the body is copied from the commit body
  • multiple commits

    • the title pattern: <pull request title> (#<PR number>)
    • the body is a list of commits' entire messages

Default to pull request title

The same as in merge's Default to pull request title.

Default to pull request title and commit details

The same as the multiple commits case for default message of squash.

Default to pull request title and description

The same as in merge's Default to pull request title and description.

Rebase and merge

The last GitHub's method of closing a PR is rebase. The Git rebase is a complex and powerful tool that can rewrite a Git history. Fortunately, rebasing by GitHub is limited only to a single specific case. Before I explain it, let’s see what GitHub documentation says:

When you select the Rebase and merge option on a pull request on GitHub.com, all commits from the topic branch (or head branch) are added onto the base branch individually without a merge commit. In that way, the rebase and merge behavior resembles a fast-forward merge by maintaining a linear project history. However, rebasing achieves this by re-writing the commit history on the base branch with new commits.

Rebase in practice

Actually GitHub implements its own way:

The rebase and merge behavior on GitHub deviates slightly from git rebase. Rebase and merge on GitHub will always update the committer information and create new commit SHAs, whereas git rebase outside of GitHub does not change the committer information when the rebase happens on top of an ancestor commit.

And this is how it works in practice:

git tree before rebase

Right after Rebase and merge button is pressed, there is no input field to provide or edit a commit message this time.

rebase confirmation

And this is the result after Confirm rebase and merge is pressed:

git tree after rebase

Similarly to the squash, the merge doesn't bind a source branch to the target one and creates new commits (with new hash) to the target.

Messages of rebase

The difference is that the merge creates commits with the same changes and the same messages as commits in the source branch. After the feature/8 is removed, there is no track where the commit came from.

So, what can we do about that?

  1. We can create a new draft pull request for our feature branch right after it is created. The draft pull request can't be closed nor merged.
  2. Knowing the PR's number, we can just add a reference (#<PR number>) to the titles of our commits of the feature branch.

Summary

Now we know what we can do to close a pull request on GitHub.

  • Merge joins a source branch with all its commits and a base branch by creating a new merge commit in the base. Such a commit is the point in git history where both branches were bound.
  • Squash consolidates changes from all commits from a source branch and pushes them together within a new single commit into the base branch. The branches are not bound and the same changes on both branches are considered as totally different ones in comparison.
  • Rebase copies commits from the target branch and pushes them into the base with the same changes, commit messages, but different SHAs.

In the next post, we will consider when to use the three of them. We will dive into their pros and cons and use cases.

Top comments (0)