git merge --squash to protected master branch

There are other similar posts to this and I believe I know the answer but I’d like to sanity check this and then see if there are either ideas for workarounds or whether GitHub would consider this worth implementing a feature for.


  • We care a lot about security!

  • We want to enforce signed commits to our repo

  • We want to run verification of signatures against a list of trusted public keys (we will implement this)

  • We want to mandate that our devs don’t use GitHub UI to resolve or merge. The problem here is that GitHub uses it’s own key to sign commits so we can’t guarantee that the commit was made by a trusted user. We must thus not allow any commits signed with GitHub’s key.

  • Separately we want merges to master to require a PR and master branch to be protected from force commits

  • We also don’t want admin users on our repos (admin priviledges are generally bad - we have particular threats here we want to mitigate)

  • Specifically this means we don’t want admins to be able to override branch protections

  • We want to use squash commits

  • Squash commits can’t be done from the command line, even with an approved PR (regular merges can)


  • Is my assessment that squash commits can’t be done from the command line in this scenario?
  • Any good workaround? 
  • Would GitHub implement a feature to support this (that would be fiddly but I don’t think impossible)

One workaround would be to squash to a separate branch and do the PR against that. However, I’m pretty sure that’s just going to be a painful overhead for our devs

1 Like

@hormyajp wrote:

  • Is my assessment that squash commits can’t be done from the command line in this scenario?

Squash commits isn’t something that GitHub created. It is a built-in capability of Git. So squash commits can be created from the command-line. If you look at the history of my test repository, I just created two similar PRs: one using a regular merge and the other using a squash merge. The resulting commit history looks like this:

* d775cae (HEAD -> master, origin/master, origin/HEAD) Test a squash merge (#14)
| * 4d9448c (origin/squash-merge, squash-merge) Third commit
| * aed7c17 Second commit
| * e7ab628 First commit
* b899b70 Merge pull request #13 from lee-dohm/regular-merge
| * 69e5e24 (origin/regular-merge) Third commit
| * a8dc29a Second commit
| * ee9a4fa First commit
* 0e74932 Add exact same text as the issue

The merge commit at b899b70 has two parents, the head of the branch being merged and the head of the branch being merged into. The merge commit at d775cae has only one parent, the head of the branch being merged into. But the content of the commit at d775cae is the end result of replaying all of the commits in the branch being merged onto the head commit in the branch being merged into. This can be replicated by using a squash rebase followed by a fast-forward merge.

The process a trusted user could follow would be something like this:

  1. Check out the branch to be merged locally
  2. Use the interactive rebase feature (see the link above) to squash the commits of the branch –  note: this creates a new commit locally
  3. Check out the default branch
  4. Execute git merge --ff-only branch-to-be-merged
  5. Push the updated default branch to the remote repository

Other than the local branch ref for branch-to-be-merged not pointing to the same commit as origin/branch-to-be-merged, the end result would be a commit graph of the same structure as you see in my dump above. Much, if not all, of this could be automated with a script that could be executed by a trusted user.

I hope that helps!