Feature Request: Only allow for --ff merges for PRs · community · Discussion #4618

24 min read Original article ↗

Another reason for supporting actual fast forward merges is that if a user has signed their commits, it will ensure those signatures are kept as part of the merge process. Right now the only solution for maintaining these signatures is to create a merge commit.

This obviously isn't an "answer" but apparently I have no other way of adding my feedback to this.

You must be logged in to vote

1 reply

@masklinn

A third reason for fast-forward merge is if the merge itself needs to contain a merge commit e.g. there are conflicts between the branches and the two can't be reconciled via a rebase, because they're concurrent branches of a project (say, 1.0 and 2.0).

Because Github's UI does not (as far as I know) support merging with conflict resolution, assuming you are not willing or able to disable branch protection you have to "double merge" the branch, with a completely useless and redundant second commit.

This is even more frustrating when you already

Require branches to be up to date before merging

so the fast-forward is valid by definition.

You must be logged in to vote

0 replies

The issue title is a bit weird.
I'd rather call it "Allow fast-forward merges".
The current "rebase and merge" is not a fast-forward merge. It is a rebase, something very different.

To me a good idea would be like this:

  • Provide new option for fast-forward merge, if possible.
  • Provide option to create an annotated tag. This allows to store date and message without polluting the history with an actual merge commit. The tag could be signed by github, or by the github user.
You must be logged in to vote

6 replies

@danlyons-home

Building on this, I'd want three options:

  • options to fast-forward merge on a pull request itself (both -ff and -ff-only)
  • option on a repository and/or branch to change the default merge type in pull requests
  • option on a repository and/or branch to disable one or more merge types in pull requests

In general, I never want a merge commit when a fast-forward is possible.

I also have a repository or two where I'd like to enforce --ff-only (at least for merges to main/master). Alas, I can only do so currently by making GitHub PRs off-limits, which is a shame.

@xmo-odoo

In general, I never want a merge commit when a fast-forward is possible.

I just want to note that I personally disagree with this. Instead I never want a merge commit when a PR has a single commit. Or when the head of the PR is a merge commit (which might be necessary to resolve conflicts).

But I do usually want a merge commit to recapitulate the task when the PR has multiple commits: IME it's common to want to keep the task logically sectioned, but the information of it being a unit should be retained. That's where merge commits show their worth. This is orthogonal to the PR being fast-forward-able.

@dnitsch

Not to mention that this would finally allow the build once principle to be actually followed if you use or wish to use short_sha as a version tag.
Gitlab Flow would be possible as well which is a much more desirable branching/release strategy for bigger organizations.

I believe this option is available in TFS (AzureDevops Server). as mentioned by @donquixote another merge option is the best way to go about.
it is currently possible to do this via commandline but then you need to allow "people" to push to protected branches.

@Elyytscha

But I do usually want a merge commit to recapitulate the task when the PR has multiple commits

i would want to squash a pr with multiple commits to one, a branch should be named something like feat/* fix/* chore/* so when i name my branch it should already be clear what should be the one commit message coming from that pr, that makes it easy to really group features and fixes etc. into one single meaningful commit containing all changes made for this fix or feature etc.

@yesudeep

As a workaround, here's two workflows that allow fast-forwarding either via commenting /fast-forward or applying the label fast-forward, inspired by the comments above. I wish GitHub allowed triggering workflows via reactions on PRs! Anyway,

  • these will merge the commits with the same hashes as in PR and no merge commit
  • all checks will run as expected
  • the PR will be marked as merged with the message such as

    your-repo-name (bot) merged commit 1234567 into main 10 minutes ago

You will need to set up an empty GitHub app for the purpose of generating access tokens. The default token that comes with a workflow, GITHUB_TOKEN, is not suitable as workflows using it cannot trigger other workflows. Here are some instructions to set up a GitHub app. I just named mine the same I named my repo, and you can also set up an avatar for it! So, you:

  • set up the app with r/w repo permission (you might also want to add r/w workflow permission if you want to be able to merge PRs that change workflow files),
  • add it to your repo,
  • put its id and private key in LOKSMITH_ID and LOCKSMITH_PRIVATE_KEY secrets of your repo,
  • and create a file .github/workflows/fast-forward.yml with one of the following (this assumes your main branch is called main):
Fast-forward via commenting `/fast-forward`
name: Fast-forward

on:
  issue_comment:
    types: [created, edited]

jobs:
  fast_forward:
    name: Fast-forward
    runs-on: ubuntu-latest
    if: |
      github.event.issue.pull_request &&
      github.event.comment.author_association == 'OWNER' && 
      contains(github.event.comment.body, '/fast-forward')

    steps:
      - name: Generate access token
        uses: tibdex/github-app-token@v1
        id: generate-token
        with:
          app_id: ${{ secrets.LOKSMITH_ID }}
          private_key: ${{ secrets.LOCKSMITH_PRIVATE_KEY }}

      - name: Fetch repository
        uses: actions/checkout@v3
        with:
          token: ${{ steps.generate-token.outputs.token }}
          fetch-depth: 0

      - name: Checkout pull request
        run: hub pr checkout ${{ github.event.issue.number }}
        env:
          GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}

      - name: Fast-forward & push
        run: |
          export PR_COMMIT=$(git rev-parse HEAD)
          git checkout main
          git merge --ff-only "$PR_COMMIT"
          git push origin main
Fast-forward via applying the label `fast-forward`
name: Fast-forward

on:
  pull_request:
    types: [labeled]

jobs:
  fast_forward:
    name: Fast-forward
    runs-on: ubuntu-latest
    if: github.event.label.name == 'fast-forward'

    steps:
      - name: Generate access token
        uses: tibdex/github-app-token@v1
        id: generate-token
        with:
          app_id: ${{ secrets.LOKSMITH_ID }}
          private_key: ${{ secrets.LOCKSMITH_PRIVATE_KEY }}

      - name: Checkout pull request
        uses: actions/checkout@v3
        with:
          token: ${{ steps.generate-token.outputs.token }}
          fetch-depth: 0

      - name: Fast-forward & push
        env:
          PR_COMMIT: ${{ github.event.pull_request.head.sha }}
        run: |
          git checkout main
          git merge --ff-only "$PR_COMMIT"
          git push origin main

That's it!

You must be logged in to vote

0 replies

It would be really great for GitHub to execute on this feature request.

GitHub is nearly 15 years old, and there is still no way for a pull request merge to be fast-forward-only (linear history) AND preserve the same commits as pull request source branch.

Today, if you want to preserve commits in your development branch (and not squash them; squashing is great sometimes), you have two bad options

  1. merge commits. This is bad because linear history is an excellent risk reduction mechanism for a production build.
  2. rebase commits. These would be ok, except when github rebases the commits into linear history, the commit hashes change (they are new commits) even though the patch and metadata are identical. So your development branch itself constantly needs to be rebased on your production branch, because the commits being added to production are newly created by github rebase & merge.

What we need is a fourth option "Fast-forward merge", which requires linear history and only preserves (ie copies) commits from the PR source branch into the target branch.

Please, do this. Thanks!

You must be logged in to vote

1 reply

@gdamore

💯 * 💯 -- It is inconceivable that GH still hasn't implemented this.

I'd actually be almost as happy if they just made the rebase a "rebase or fast-forward" button, so that it fast forwarded when it can.

You must be logged in to vote

0 replies

This feature is a must and can be an advance option and selectable from the dashboard. I’ve been having to —ff-only on my own, as PRs from dev to main in GitHub is a source for headaches. It’s important, make it an experimental feature, let users have linear history please!

You must be logged in to vote

0 replies

Would be very useful to have this feature! We would use this to power a flow for merging commits from main to release. release is never be committed to directly, and ideally it is either strictly at or behind main but never ahead. We have a github action workflow that we run to sync commits from main to release, but unfortunately all three existing methods of merging (merge, squash, rebase) cause some kind of divergence in the history of main and release. The only true way we can keep the histories in sync is by merging main to release locally via git (which allows for an FF merge), and then push that branch, but then we lose the benefits of PRs and protected branches.

You must be logged in to vote

5 replies

@jcgruenhage

We have a very similar setup with test, qa and prod branches. That worked absolutely fine on GitLab (which we're migrating from), and also works fine on competing self-hosted projects like gitea and forgejo. Have you found a workaround for your setup?

@akloss-cibo

...but then we lose the benefits of PRs and protected branches.

This isn't strictly speaking true. You can leave the branch protection in place and, as long as there's a PR open that has its requirements met, still push a fast-forward to the protected branch. But yes, only through the CLI, and yes, it's annoying there isn't a "fast-forward merge" option in the web UI. I haven't tried it yet, but it seems combining require linear commit history with disabling merge, squash, and rebase merges might be a sort-of nice solution.

@jcgruenhage

You can't disable all three. Good to know that pushing a fast forward works when the PR has it's requirements met, that might be a feasible workaround for us.

@akloss-cibo

Hmm... I wonder if you can use/abuse requiring signed commits to clobber the UI entirely. Of course it is super annoying that we're having this conversation about using unintended consequences of GitHub features to compose the reasonable behavior of "I want linear history".

@acwest

I was assured by devs at github that they were working on this issue, but there were extensive layoffs shortly after that, so who knows...

I'm about to take my business to a competitor. This is one of the most core parts of an effective git workflow. This needs to be worked on.

You must be logged in to vote

3 replies

@cortexcompiler

@dev1joe

@RipplB

does git lab have it ??

Yes it does

I had absolutely no idea that this was the case for years and I'm actually quite surprised that Github is doing something non-'standard' here. I didn't actually believe it when I first read about it and had to try it myself. Sure enough, Github is performing full replays of history instead of doing fast-forwards, resulting in different commits. My own personal workflow isn't particular impacted by this but thinking back to professional settings this is starting to paint a picture as to why rebases were so inexplicably problematic when people used the Github UI to do them as opposed to on the command line.

You must be logged in to vote

1 reply

@Qix-

Update: still somewhat floored this isn't possible on GH two years later. This is making multi-stage releases via review PRs impossible.

It appears as if I can either

  • merge with a merge commit <--> why would I want a merge commit that doesn't really tell me anything when there's no conflicts?
  • rebase merge <--> clear to people who know git that it rewrites history
  • squash merge <--> nice to have, but again, history is lost; git branch -d errors/warns

"require linear history" doesn't work either for this purpose.

This forces me to merge on the command line (which, by the way, instructions-as-described in enterprise server 3.5 don't actually force a merge commit, but the button does); which I don't know how this plays with "disable pushes to master".

I could have sworn that historically there was some kind of option that would perform a ff-merge, perhaps I've gone insane though.

I guess I'll have to write a bot / app for our CI for this custom procedure? That feels dumb.

You must be logged in to vote

4 replies

@oakkitten

I guess I'll have to write a bot / app for our CI for this custom procedure? That feels dumb.

Here's my attempt further up.

But I also want to note that if you merely don't have conflicts it doesn't mean that you can fast forward. To be able to fast-forward, your feature branch must be based on the last commit of your main branch. Unless you are willing to update your feature branch every time the main branch is updated, you can benefit from fast forwarding only when there's very few people developing very few feature branches on your project.

@13steinj

To be able to fast-forward, your feature branch must be based on the last commit of your main branch.

Yeah that's fine. The main thing here is people sometimes make one-commit PRs / features, and doing another merge commit on top of this is silly and confuses people unnecessarily (as much as I'd love to say people should just learn git and good practices, I can't force them to).

I'm fine with merge commits, but if it can be fast forwarded, I'd prefer that. Maybe I want a rebase merge, which is fine too, but that detaches the history for things like git branch -d and should be used with careful intent.

Which is the same reason single commit squash doesn't solve the "I don't want an extra merge commit" problem, because it's really a "I want git to keep track of the history, and I don't want useless merge commits." Because people are lazy and don't fill out the message box; I'd rather have (in order, assuming no conflicts) ff, (rebase + ff or merge (with appropriate message preferrably) or squash) dependent on the individual scenario. If there are conflicts what I'd really want people to do is ladder-tree-stitch merge, but nobody has time for that so any option other than ff as long as the intent is clear and it makes sense.

@cortexcompiler

If teams work with narrowly scoped microservices and use trunk-based development with short-lived feature branches, then rebasing your branch prior to creating and merging your PR tends to be trivial. This also ensures you control what the commits look like and specifically ensure any merge conflicts are dealt with the way you want. I think at a minimum adding a Fast-Forward merge option on the UI is a must. Allowing that to be enforced would be nice to have after that.

@mikkorantalainen

As I see it, somebody trying to send pull request using the GitHub UI should be the one doing the rebase and GitHub could offer to do this automatically for them before they submit the pull request. If a merge could be done without a conflict, the rebase should result in perfectly identical state but with simpler history.

Rebasing your code to allow fast-forward should be the default and true merges should be reserved for special cases.

Generally if there are no conflicts, it is fairly trivial to rebase with main before commiting, and, in fact, the user interface already supports doing so automatically.

On Fri, Jul 14, 2023 at 6:09 AM oakkitten ***@***.***> wrote: I guess I'll have to write a bot / app for our CI for this custom procedure? That feels dumb. Here's my attempt <#4618 (comment)> further up. But I also want to note that if you merely don't have conflicts it doesn't mean that you can fast forward. To be able to fast-forward, your feature branch must be based on the last commit of your main branch. Unless you are willing to update your feature branch every time the main branch is updated, you can benefit from fast forwarding only when there's very few people developing very few feature branches on your project. — Reply to this email directly, view it on GitHub <#4618 (reply in thread)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/AAANTD3OJECIVUXLNFGE7MDXQELF5ANCNFSM5AI3TFGQ> . You are receiving this because you are subscribed to this thread.Message ID: ***@***.*** com>

You must be logged in to vote

2 replies

@chrisnc

It's mentioned elsewhere in this thread that the rebase merge in the web UI always rewrites the commits, even if a fast-forward were possible.

@mauricioklein

Also, not all teams uses a merge to main strategy.

Some have an intermediary branch (e.g. staging, preprod, etc), and this would require a manual reconciliation of main's new commit hashes with the original commits in the intermediary branch.

To do the rebase and merge from the command line: git pull --rebase origin main git branch -D main git branch -m main git push origin main

You must be logged in to vote

1 reply

@chrisnc

The command-line workaround has been discussed extensively here and elsewhere. This thread is a feature request for the GitHub web UI so this is not required. The situation being described is where the PR is already fast-forwardable and does not require a rebase. This can be done with one step:

git push origin origin/<branch>:<base>

My comment was more in reference to difficulties if there other commits the need to be rebased. The ui is able to handle this in the non-fast-forward case automatically, so it should be no difficulty to do so with fast forward. My example command was a demonstration that it is trivial to actually do, as long as there are no conflicts.

On Sat, 15 Jul 2023, 17:47 Chris Copeland, ***@***.***> wrote: The command-line workaround has been discussed extensively here and elsewhere. This thread is a feature request for the GitHub web UI so this is not required. The situation being described is where the PR is already fast-forwardable and does not require a rebase. This can be done with one step: git push origin origin/<branch>:<base> — Reply to this email directly, view it on GitHub <#4618 (reply in thread)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/AAANTDY4VL2N3DWDX22PGE3XQMFY5ANCNFSM5AI3TFGQ> . You are receiving this because you are subscribed to this thread.Message ID: ***@***.*** com>

You must be logged in to vote

2 replies

@ian-twilightcoder

The pull/rebase will break any commit signatures

@chrisnc

Read the rest of the thread for why "it should be no difficulty" is not accurate. If GitHub's merge-with-rebase option would fast-forward when possible, it would be a little better, but it doesn't.

I'm saying with little difficulty on THEIR part. The fact that they haven't done it is the problem, but it is certainly doable

You must be logged in to vote

0 replies

Just switched from Bitbucket and took this possibility for granted. Please include it.

You must be logged in to vote

0 replies

I was wondering why every time I synced main with the release branch I got this weird branch divergence behavior....
Hopefully github fixes this soon

You must be logged in to vote

2 replies

@joe-cisar

Don't hold your breath. Anyone know how to make this feature request go viral?
image

@rettichschnidi

I was having some success at getting their attention by working for a very large company who pays them a LOT of money, but then they laid off everybody who was working on this

You must be logged in to vote

1 reply

@AndreaPontrandolfo

I was having some success at getting their attention by working for a very large company who pays them a LOT of money, but then they laid off everybody who was working on this

This is crazy!

plus one to this issue. absolutely unbelievable. please github please. i've worked so hard on my github repo configuration don't make me migrate back to gitlab

You must be logged in to vote

0 replies

+1, We just migrated from AzureDevOps which has fast-forward, please add this, having to always disable the branch protection rule just to rebase and force push the main development branch after an update defeats the purpose of having protection rules.

You must be logged in to vote

5 replies

@danielparks

FWIW, you can push without disabling the branch protection rule. I have my main branch protected and have a set of required CI checks. To merge a PR, I wait for the PR checks to succeed (with the CLI: gh pr checks --watch --fail-fast), then merge the branch into main locally and do git push. The PR will automatically register as merged.

@izaakschroeder

@danielparks is correct, you can fast-forward merge from the CLI without disabling merge checks. It's not intuitive at all, but it does work.

@Minecraftschurli

Well, I'm the only one who uses a cli (and even I only use it occasionally), and my boss wants to do everything in UI so merging manual using the cli is a no go.

@izaakschroeder

Well, I'm the only one who uses a cli (and even I only use it occasionally), and my boss wants to do everything in UI so merging manual using the cli is a no go.

Also been my experience – most engineers just want to press big green button. It's bad enough I've considered writing a chrome extension to hijack the button to do my bidding (i.e. call an endpoint and perform the merge via GH API).

@13steinj

Have you considered (similarly) adding this functionality to an existing extension such as https://github.com/refined-github/refined-github ? that might be a better

I planned to do this when at the time, I was at an org that used GitHub... but even if I did, I don't think I could upstream it, because a maintainer decided that commenting on an old issue that it / similar still occurs / happens on older (yet still in support by GitHub at the time) versions of GitHub Enterprise was a blockable offense 😆 .

Here is our workaround for this problem.
The only dependency is actions/checkout.
It runs in about 15 seconds, and can be triggered by posting a comment to the PR that reads exactly "/merge".
If you have multiple people working on this, I recommend a second action that posts a brief explanation comment whenever a PR is created that targets the relevant branch.

nname: "Comment on PR: Handle commands"
on:
  issue_comment:
    types: [created]

jobs:
  FastForwardMerge:
    # Only run if the comment is exactly "/merge".
    if: ${{ github.event.issue.pull_request && github.event.comment.body == '/merge' }}
    runs-on: ubuntu-latest
    permissions:
      contents: write  # To push changes
      pull-requests: write  # To post to PR on failure
      statuses: write  # To create the commit status
    env:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    steps:
      - name: Get PR refs/shas
        id: pr_details
        run: |
          PR_NUMBER=${{ github.event.issue.number }}
          echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_OUTPUT
          FROM_REF=$(gh pr view $PR_NUMBER --repo ${{ github.repository }} --json headRefName --jq .headRefName)
          echo "FROM_REF=$FROM_REF" >> $GITHUB_OUTPUT
          INTO_REF=$(gh pr view $PR_NUMBER --repo ${{ github.repository }} --json baseRefName --jq .baseRefName)
          echo "INTO_REF=$INTO_REF" >> $GITHUB_OUTPUT
          FROM_SHA=$(gh pr view $PR_NUMBER --repo ${{ github.repository }} --json headRefOid --jq .headRefOid)
          echo "FROM_SHA=$FROM_SHA" >> $GITHUB_OUTPUT
      - uses: actions/checkout@v4
        with:
          ref: ${{ steps.pr_details.outputs.INTO_REF }}
      - name: Fast-Forward Merge PR
        run: |
          git fetch origin ${{ steps.pr_details.outputs.FROM_REF }}
          git merge --ff-only origin/${{ steps.pr_details.outputs.FROM_REF }}
      - name: Register check to the PR to make pushing possible
        if: ${{ success() || failure() }}
        run: |
          gh api \
            --method POST \
            -H "Accept: application/vnd.github+json" \
            -H "X-GitHub-Api-Version: 2022-11-28" \
             /repos/${{ github.repository }}/statuses/${{ steps.pr_details.outputs.FROM_SHA }} \
             -f 'state=${{ job.status }}' \
             -f 'description=FF Merge ${{ job.status }}' \
             -f 'context=FF Merge'
      - name: Push FF-merged changes
        if: ${{ success() }}
        run: |
            git push origin HEAD:${{ steps.pr_details.outputs.INTO_REF }}

This will checkout the base branch and attempt to merge the head branch with --ff-only.
If that fails, a status check failure is registered for the head commit.
If it succeeds, two things happen:

  • A status check success is registered for the head commit
  • The merge result is pushed to the remote base branch

You can use the status check to effectively enforce FF-only merges by requiring the "FF Merge" status check under your branch protection rules. Note that this status check has to be registered at least once (ie above action needs to run at least once) for it to be found in the "Status checks that are required" menu.

Additionally enabling "Do not allow bypassing the above settings" will make it so the merge button on GitHub effectively cannot be pressed, and only a "/merge" comment to the PR can trigger a merge.

One small caveat is that if the git merge --ff-only step succeeds, and thus the check is registered as passing, the git push step can still fail if other branch protection rules (such as other required status checks) block it. Simply commenting /merge after those problems are fixed should work though.

You must be logged in to vote

11 replies

@derpda

You are right, in the case where you aren't the owner and cannot add workflows, this won't help. That repository forces developers to use the CLI to merge? Not unreasonable, but slightly inconvenient.

I only felt the negativity should be exclusively directed towards GitHub, and not someone posting a workaround. This issue is several years old and GitHub hasn't even responded, so I'm not holding my breath for what would seem like an easy thing to implement...

@mwilck

That repository forces developers to use the CLI to merge?

No. You can only update the maste branch through PRs, meaning that no fast-forwarding is possible at all.

I only felt the negativity should be exclusively directed towards GitHub,

Of course. I felt generally grumpy when I found this issue, so my comment ended up sounding negative against you. Sorry again.

@acwest-tss

The way github is set up, once the PR is approved and all of the requirements are met, you can push from the command line. This is how I do all of my repos. Up until somebody clicks the UI submit button, and I then need to force push onto a protected branch to fix it...

@mwilck

Ok, I'll try that next time. Thanks for the hint.

@derpda

Totally understandable, I was grumpy too when I first ran into this issue...

Didn't know that we can FF merge and push via the CLI if the PR checks all passed, good to know :)

I don't understand why this hasn't been implemented yet. Why should we need to do all these kind of workarounds for a simple thing that is already possible on the CLI? This feature needs to be added.

You must be logged in to vote

0 replies

What a mess! We ended up designing a completely different process, with no Pull Requests and no Peer Reviews. Based on a classic Open Source contribution workflow with the 👀👀-principle is based on paired programming and mob programming.

I've covered it in a blog post all the tools; a GitHub CLI extension and a handfull of generic callable workflows we developed are open source.

You must be logged in to vote

0 replies

+1 to adding this. I'm shocked there's features ADO supports that Github doesn't.

You must be logged in to vote

0 replies

You must be logged in to vote

0 replies

There is no way this issue still needs bumping and there is not even a single official mention of resolving it.... but sure go ahead add another shitty ai slop to the platform!

You must be logged in to vote

0 replies

The fact that so many people suggest workarounds drives me even more crazy. The kindness and willingness to solve our own problem as well as others is the heart of this platform.
The problem is that we, the people, don't like it when it is made so obvious that we absolutely don't give a sh1t about us.
Instead of leveraging more and more data experts to provide even more AI slop bs features everywhere, just give this job to 2 or 3 juniors that will be more than happy to help thousands of people with 3 of their work days.

Come on. Life is hard enough to have to fight with the number 1 platform that host OSS for such minor feature request.

You must be logged in to vote

0 replies

You must be logged in to vote

0 replies

All I can say is that you can tell Microsoft own Github now - and by that I mean: they don't give two hoots about the people that use the product... Market share is their only metric for concern.

You must be logged in to vote

0 replies

+1 - again, this is needed.

You must be logged in to vote

0 replies