Keeping branches in sync in a Monorepo: The Pre-Push hook solution




Introduction

If you have ever worked in a monorepo with multiple teams pushing code daily, you know the pain when, your feature branch is ready, all checks pass and just as you are about to merge, GitHub blocks it because your branch is out of date with main branch. Now you have to pull, rebase or merge, wait for builds to rerun and lose another 20 so minutes.
This cycle was frustrating and costly for us. We needed to keep branches in sync with main without slowing engineers down. That is when we turned to Git hooks and specifically the pre-push hook.




Problem Statement

In our monorepo setup, every engineer pushed code to the same repository. With main being updated frequently, feature branches often drifted out of sync. Merging those branches without first pulling main risked overwriting important changes.




Our First Attempt: Branch protection rules

We started with GitHub’s branch protection rules, which required branches to be up to date with main before merging.
While effective at catching outdated branches, this introduced new pain points.

  • Our Jenkins builds took 8+ minutes and had to be re-run each time a new commit gets added to the branch.
  • Engineers had to merge quickly after getting approvals or risk falling behind again.

Eventually we had to remove the rule, it was enforcing correctness but at the cost of engineer’s productivity.




The Game Changer: Pre-Push Hook

After more discussions and research, we looked into Git hooks. Since we already used Husky for pre-commit hook, extending it for pre-push felt natural.

Here is how our pre-push hook works:

  1. An Engineer creates a branch from main, works on this branch and gets ready to push.
  2. On running git push, the pre-push hook automatically runs.
  3. The hook performs a few checks:
    • Ensures the branch is not main (we also have separate checks preventing direct pushes to main).
    • Confirms the branch has an upstream.
    • Checks if the branch is out of sync with main.
  4. If the branch is behind main:
    • It pulls the latest main from remote and merges it into the feature branch.
    • If conflicts exist, the hook stops and shows an error message asking the engineer to resolve them manually before pushing again.
    • If no conflicts are found, the merge is committed, and the push proceeds.

Result: Every branch is guaranteed to be up to date with main at the time of push.




Things to watch out for

While this solution has been a huge improvement, it is not foolproof. Some caveats to keep in mind.

  • Bypassing hooks: Engineers can still use git push --no-verify to skip running the hook.
  • Late merges: Even after approvals, if merges happens much later, the branch might still drift behind.
  • IDE quirks: We have sometimes seen IDE show “unpushed changes” even though the code has been pushed to remote branch. Running git fetch usually resolves this.



Conclusion

Adding a pre-push hook to our workflow has been a simple but powerful change. It automated away one of the most annoying pain points in a fast moving monorepo – Keeping feature branches in sync with main

This small improvement saved hours of wasted build time, reduced merge conflicts and let engineers focus on writing code instead of wrestling with Git.

If your team is facing the same problem, give pre-push hooks a try.



Source link

Leave a Reply

Your email address will not be published. Required fields are marked *