Options to close pull requests on GitHub
Tags: beginners, git, github, tutorial
Published: 2023-03-05 16:35:28

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](https://www.youtube.com/watch?v=ASOSEiJCyEM).

## Table of content

- [Table of content](#table-of-content)
- [Pull Requests on GitHub](#pull-requests-on-github)
- [Create a merge commit](#create-a-merge-commit)
  - [Merge in practice](#merge-in-practice)
  - [Default messages of merge](#default-messages-of-merge)
- [Squash and merge](#squash-and-merge)
  - [Squash in practice](#squash-in-practice)
  - [Default messages of squash](#default-messages-of-squash)
- [Rebase and merge](#rebase-and-merge)
  - [Rebase in practice](#rebase-in-practice)
  - [Messages of rebase](#messages-of-rebase)
- [Summary](#summary)

## 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ndul109tjulom1wowwsb.png)


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

![pull requests options in pr](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yp9ctfk4jgtrwvs0sp5l.png)

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](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/about-pull-request-merges). In this post, however, we will go into more details to understand how it looks in practice.

## <u>Create a merge commit</u>

he default way of closing a pull request on GitHub is *merge*. The [git documentation](https://git-scm.com/docs/git-merge) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/08rw2rydlqmc4ke4ctbm.png)

This is the result of the **Merge pull request**:

![git tree after merge](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q2mid3hr3bpw3fqws865.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5ta80v1q83l9ome095wc.png)

![git tree after merge and removal](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/suxfq4km2jn1mhx24o7b.png)

> **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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q7eephnyd4ezhwnb5d9k.png)

**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.

## <u>Squash and merge</u>

The second method of closing pull requests on GitHub is **squash**. According to the [Git documentation](https://git-scm.com/docs/git-merge#Documentation/git-merge.txt---squash):

> 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/koh5rxd0fh2wvblecitu.png)

After:

![git tree after squash](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/260e8purl76j1bl3pgqc.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ieer39bte6vv2al0jgal.png)

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](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bjneq5ecqkzr8kq77glz.png)

**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*.

## <u>Rebase and merge</u>

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](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/about-pull-request-merges#rebase-and-merge-your-commits) 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](https://git-scm.com/docs/git-merge#_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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hef36ux64jqj4bxndpcf.png)

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

![rebase confirmation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l66b86wfrwkavp35e7pr.png)

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

![git tree after rebase](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j94a3ozc6oen7rtu41p4.png)

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.