The “Pre-Receive Hook Declined” Nightmare: How to Resolve Git Conflicts When Force-Pushing is Disabled
Picture this: It’s late afternoon, your feature branch is complete, and you’re ready to merge your pull request. You open the PR only to see the dreaded red warning: This branch has conflicts that must be resolved.
Instinctively, you pull up your terminal, run git rebase origin/release/v3.4.0, locate the conflicting files, clean up the markers, and get ready to update your remote branch. You confidently type:
git push --force-with-leaseYou hit Enter, expecting a clean state and a green merge button. Instead, your terminal screen flashes red with a frustrating block of text:
remote: Force-pushing to this repository has been disabled
! [remote rejected] feature/user-auth -> feature/user-auth (pre-receive hook declined)
error: failed to push some refs to 'ssh://github.com/your-org/your-repo.git'Game over.
If you work in a modern enterprise environment (using GitHub Enterprise, Bitbucket/Stash, or GitLab), you have likely run face-first into this security wall.
In this article, we’ll unpack exactly why this happens, why your usual “rebase-and-force-push” workflow is blocked, and look at the step-by-step “Golden Path” to resolve your conflicts cleanly — without ever needing a force-push.
Why Enterprise Repos Block Force-Pushing (And Why It’s Actually a Good Thing)
First, let’s clear the air: your DevOps team didn’t set this up just to make your life difficult.
When you rebase a branch, Git takes your commits and “re-plays” them on top of the target branch. This process actually deletes your old commits and replaces them with brand-new ones that have completely different hashes. Because you are rewriting history, Git won’t let you push normally; you have to use a force push (-f or --force-with-lease).
In a collaborative enterprise repo, force-pushing is highly dangerous. If a colleague pulled your branch, made a change, and pushed it while you were rebasing, a force push would permanently wipe out their work.
To prevent this, repository admins enable pre-receive hooks that block force-pushing entirely. Your history must remain strictly additive.
Enter the “Standard Merge”: The Savior of Locked-Down Repos
If we can’t rewrite history (rebase), we must add to it. We do this using a Standard Merge.
Instead of moving your commits to the top of the release branch, a merge pulls the release branch into your feature branch and wraps the integration up in a single, clean Merge Commit. No rewritten history, no force-pushing required.
Here is the step-by-step recipe to rescue your branch.
Step 1: Discard the Stalled Rebase (The Clean Slate)
If your terminal is currently stuck in the middle of a rebase, we need to abort it and reset our local files to match what’s on the remote server.
# Fetch the latest updates from the remote server
git fetch origin# Force your local branch to match the remote server exactly
git reset --hard origin/feature/user-auth⚠️ Warning:
git reset --hardwill discard any uncommitted local changes. Make sure your local workspace is clean before running this!
Step 2: Merge the Target Branch Into Your Branch
Now, we explicitly merge the target branch (the one you are trying to merge into, like a release or main branch) into your current working branch.
git merge origin/release/v3.4.0Git will attempt to auto-merge what it can, but it will pause on files modified in both branches:
CONFLICT (content): Merge conflict in config/default.json
Automatic merge failed; fix conflicts and then commit the result.Step 3: Resolve the Conflicts (Mind the HEAD!)
Open the conflicting files in your editor (VS Code, IntelliJ, or your preferred IDE). You will see Git’s conflict markers:
<<<<<<< HEAD
"TIMEOUT_MS": 5000, (<- This is YOUR branch's code)
=======
"TIMEOUT_MS": 10000, (<- This is the TARGET branch's code)
>>>>>>> origin/release/v3.4.0💡 Crucial Git Tip: HEAD behaves differently in Merges vs. Rebases!
- During a Rebase,
HEADrefers to the target branch you are rebasing onto. - During a Merge,
HEADrefers to your active feature branch.
Because we are doing a Merge, the top section (HEAD) contains the code you wrote in your feature branch. The bottom section contains the incoming changes from the target release branch.
Get ramesh516390’s stories in your inbox
Join Medium for free to get updates from this writer.
To resolve the conflict:
- Delete the conflict markers (
<<<<<<<,=======,>>>>>>>). - Keep the code you want (usually your feature branch’s code, or a combination of both if required).
- Ensure the spacing, syntax, and formatting remain intact.
Your final file should look clean:
"TIMEOUT_MS": 5000,Step 4: Stage, Commit, and Push Normally
Now that the files are clean, we simply commit the resolution and push it. Because this adds a new commit instead of rewriting the past, a normal git push is all you need.
# 1. Stage the resolved files
git add config/default.json# 2. Commit the merge resolution
git commit -m "Merge branch 'release/v3.4.0' into feature/user-auth and resolve conflicts"# 3. Push normally! (No force flags!)
git push
Your terminal will show a beautiful success message:
To ssh://github.com/your-org/your-repo.git
c0f243c..294c6eb feature/user-auth -> feature/user-authThe Verdict: Rebase vs. Merge in the Real World
While “clean git history” advocates love rebasing because it keeps commit graphs perfectly linear, the reality of working in a secure enterprise ecosystem requires adaptability.
Using git merge to pull target branches into your feature branch:
- Keeps your work safe from being overwritten.
- Respects enterprise pre-receive security hooks.
- Keeps a clear, auditable trail of when and how conflicts were resolved.
The next time your terminal shouts at you with a blocked force-push, don’t panic. Abort the rebase, merge the target, resolve the code, and push normally. Your PR (and your DevOps team) will thank you!
Did this save you from a Git headache? Let me know in the comments below, and don’t forget to clap if this helped you out!





