If you’ve ever felt a moment of panic when your Git repository suddenly seems to have dangling commits with the upstream right after your maintainer squash-merged, you are not alone.


It’s not a sign that you’ve broken anything; it’s a collision of two different philosophies of history. Your branch tells the epic story of your journey, with every trial and error documented. In my situation, the upstream maintainer — also happens to be my manager, who has a sixth sense for when I’m unsure about something, often prefers a clear and concise summary. The result? My fork is left holding the full, detailed history, while upstream has only the final release.


While there are many paths to reconcile, I will reveal the one that worked best for me - after a Google search.


Let’s break down a typical scenario to understand what’s really happening.

Scenario: Implementing a New Feature

You are contributing to a project, and you are implementing a new feature. Your workflow looks like this:


You implement a new feature: You started to develop a new feature in the master branch instead of creating a new branch. At your local branch, you diligently create a commit with the core implementation.


You received feedback: You submitted a Merge Request(MR). The maintainers review your code and request some refactoring as you made 200 lines of code unnecessarily. You make the changes and create a follow-up commit. At this point, your branch has a clean, detailed history of work.


The Maintainer squashes and merges: To keep the main project’s history linear and clean, the maintainer uses the “Squash and Merge” option. This action takes all the commits that you push and combines them into a single commit on the project’s main or master branch.


The Aftermath: A tale of two histories: This is where the apparent “out of sync issue” arises. Let’s visualize the divergence to check the final state of the repositories: upstream(main) and origin.



The out-of-sync problem:


Let’s get this fixed before my manager’s frown becomes a permanent feature on his face 😉

. . .

1. Hard Reset

A hard reset resolves the divergence by rewinding your local main branch to the ancestor commit 019b52b. This effectively removes the conflicting two local commits that were causing the divergence, and there will be a clean, conflict-free state that mirrors the upstream.


git reset --hard 019b52b


However, be cautious when you use --hard option, it will permanently discard your uncommitted changes in your local branch.


Your current state looks like this:


After the git reset:


2. Fetch

Let’s update all the newest commits, branches from the upstream repository without merging them into your local branches. It’s a safe and read-only synchronization with the upstream repository.


While it updates your repository’s knowledge of the upstream changes, the magic lies in what it leaves alone: your half-baked local branches, experimental code, and working files will remain completely untouched.


git fetch sets the next stage for you — integrate changes with a merge? Prefer a clean, linear history with rebase? Whatever you choose to do, you should be able to proceed with perfect information and zero surprises.


git fetch upstream


Before git fetch:


After git fetch:


3. Rebase

Since you had no local commits after the reset, this simply fast-forwarded your local branch with a sequence of commits to match upstream/master exactly. It creates a linear, clean project history. Unlike merging, which creates an unnecessary merge commit.


git rebase upstream/master



4. Push


git push --force-with-lease origin master


The final step is to publish this clean history on the local branch to the remote repository. This command will replace the outdated, conflicting commit history with the new, linear timeline, fully resolving the synchronization issue between your fork and the upstream source. As a result, the personal fork’s branch is realigned, now mirroring the desired linear project history and eliminating the previous divergence.


Unlike a standard force-push, the — force-with-lease flag provides a safety check by ensuring that no unexpected changes have been made to the remote branch by others.

. . .

Issues

When you try to force-update, you might encounter the following issues if your branch is protected in the personal origin repository:


Failed to push some refers to ‘git.your-company.net: username/repository.git’


To make it a temporarily unprotected branch,


1. Go to Project > Settings > Repository. Click ‘Expand’ from the ‘Protected branches’ section.

2. Click the 'Unprotect' button.

3. Run the command again,

git push --force-with-lease origin master 


. . .

The Current State of Origin and Upstream

git branch -avv
* master                     8713289 [origin/master] update agent handoff
  remotes/origin/HEAD        -> origin/master
  remotes/origin/master      8713289 update agent handoff
  remotes/upstream/master    8713289 update agent handoff

The local master branch (commit 8713289) is perfectly in sync with both origin/master and upstream/master. This is the ideal state after a successful sync operation, which is a standard and healthy forking workflow.

. . .

Important Notes for Future

This was essentially a nuclear option that completely wiped the divergent history and started fresh from the upstream.