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
- Table of content
- Pull Requests on GitHub
- Create a merge commit
- Squash and merge
- Rebase and merge
- 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:
And this is how it looks like in a pull request:
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.
This is the result of the Merge pull request:
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.
TIP 💡
Do not call your branches justfeature
orrelease
. 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.
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:
After:
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:
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.
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
- the title pattern:
-
multiple commits
- the title pattern:
<pull request title> (#<PR number>)
- the body is a list of commits' entire messages
- the title pattern:
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, whereasgit 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:
Right after Rebase and merge button is pressed, there is no input field to provide or edit a commit message this time.
And this is the result after Confirm rebase and merge is pressed:
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?
- 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.
- 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)