The following seems to be a technique that lets us control the precise commit history that is generated when a PR is merged. The reason why this matters is that, if we follow this technique, it will simplify our merges in the future.
In my mind, in an ideal world, our commit graph would look something like this:
* <- work-in-progress-feature
|
\
* <- development
|
|
\
* <- staging
|
\
* <- production
|
where merges always go from left-to-right.
Then, when we want to cut a new release, we can simply update the branch pointers for each branch to a later branch, i.e., perform a fast-forward merge.
For example, if we want to merge changes from staging into production, we could use a fast-forward merge:
NOTE: The notation branch@{1} indicates the previous value of a branch (before the operation which most recently updated it).
* <- work-in-progress-feature
|
\
* <- development
|
|
\
staging -> * <- production
|
\
* <- production@{1}
|
However, github, by default, does not permit fast-forward merges.
Even if you start with this nice commit graph, on GH, by default, if we try to merge staging into production, we will get this graph:
* <- work-in-progress-feature
|
\
* <- development
|
| * <- production
\ /|
staging -> * |
| |
\|
* <- production@{1}
|
If we continue this approach for all of the other branches, we will get this zig-zag graph:
* <- development
/|
work-in-progress-feature -> * |
| | * <- staging
\|/|
development@{1} -> * |
| |
| | * <- production
\|/|
staging@{1} -> * |
| |
\|
* <- production@{1}
|
On the other hand, if we had used fast-forward merges for each update, we would obtain the graph:
* <- work-in-progress-feature, development
|
\
* <- development@{1}, staging
|
|
\
* <- staging@{1}, production
|
\
* <- production@{1}
|
As you all probably know, the typical PR workflow goes likes this:
-
Locally:
- Checkout latest
primarybranch - Create a new branch
updatefor a new feature/bugfix/refactor - Do some work; add commits to
updatebranch - Push the
updatebranch up to the GitHub remote
- Checkout latest
-
On GitHub:
- Create a new PR with base branch
primaryand PRbranchupdate - Check that tests pass and PR is reviewed
- Click the green "merge" button
- Create a new PR with base branch
At this point, the changes from update are merged into primary.
The way that this happens is that GH (almost always) creates a new merge commit, resulting in the zig-zag like graphs above.
This workflow permits us to use the exact commit history that we want.
-
Locally:
- Checkout latest
primarybranch - Create a new branch
updatefor a new feature/bugfix/refactor - Do some work; add commits to
updatebranch - Push the
updatebranch up to the GitHub remote
- Checkout latest
-
On GitHub:
- Create a new PR with base branch
primaryand PR branchupdate - Check that tests pass and PR is reviewed
- Create a new PR with base branch
-
Locally:
-
Perform the merge of branches
primaryandupdate; this creates a new a commit on top of theprimarybranchNote: Since you're working on the CLI, you can create a fast-forward merge.
-
Push the
primarybranch up to the GitHub remoteNote: For GitHub repos with branch protection, this is not normally possible. However, in this special case, GitHub dectects that:
- The branch has passing CI and is approved by reveiwers
- The pushed
primarybranch now points to a proper merge commit for branchesprimaryandupdate(presumably, it also checks that the contents ofprimaryare equal toupdate)
-
-
On GitHub: At this point, GH will automaticaly close PR and update the branch
primaryto be the merge commit from the CLI.