Sam Fen
Sam Fen

Reputation: 5254

What's the best way to force a merge in git?

I have a project with a dev branch and a production branch. Recently I've been working on a big new set of features in dev, so I haven't merged into production for about two weeks. In the meantime, though, there were some bugs that needed to be fixed in production.

For the most part I was able to make the fixes in dev and cherry-pick them into production. Sometimes, however, I would need to fix production by hand, as the fixes were substantially different in the two branches. Point is, the two branches have diverged a fair bit since they split.

Now I want to just merge all of dev into production. I don't care about keeping any of the commits in production since the split, I just want production to look exactly like dev since the split, but don't want to rewrite history before the split.

However, when I try to merge I get dozens of conflicts that I'd prefer not to fix by hand.

What's the best way to force a merge in git? Can I revert my production changes back to the split and then just fast-forward to the dev branch?

Upvotes: 35

Views: 81296

Answers (3)

Josiah Yoder
Josiah Yoder

Reputation: 3766

The OP's desire is: "I want production to look exactly like dev since the split, but don't want to rewrite history before the split."

Indeed, you don't need to rewrite history or force push at all! If you are looking to force the code from dev into main without rewriting history, just follow these steps: (main is the production branch here)

git checkout dev
git merge -s ours main

Then create a merge/pull request from dev into main or do an ordinary merge of dev back into main:

git checkout main
git merge

This replaces everything in main with the current contents of dev, as the OP requests. It does this by recording a merge of main into dev without actually changing anything in dev. But because the main is there in the merge history of dev, the pull request from dev into main is considered a fast-forward merge.

The whole process looks something like this:

$ git adog
* 890f8d9 (dev) dev continues further with better code
* 3ce948b dev branches from main with great code
| * d5dbf64 (HEAD -> main) main continues further
|/
* 6e24a88 main continues
* f389591 Start of main branch

$ git checkout dev
Switched to branch 'dev'

$ git merge -s ours main -m "Merge branch 'main' into dev, keeping only contents of dev."
Merge made by the 'ours' strategy.

$ git checkout main
Switched to branch 'main'

$ git merge dev
Updating d5dbf64..f28fddb
Fast-forward
 tmp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

$ git adog
*   f28fddb (HEAD -> main, dev) Merge branch 'main' into dev, keeping only contents of dev.
|\
| * d5dbf64 main continues further
* | 890f8d9 dev continues further with better code
* | 3ce948b dev branches from main with great code
|/
* 6e24a88 main continues
* f389591 Start of main branch

Notice the clean history that is easy for later developers to follow.

This solution is much simpler and cleaner than the current community consensus on this question. There is no need for a dangerous force push and the git history clearly shows where the intentional overwrite occurred. Be sure to mention in the commit comment that it is a merge -s ours commit, e.g., by saying "Keeping contents of dev only" in the commit message.

There is one condition under which I know git merge -s ours main fails: That is when the dev is directly downstream main, such as if you try to do the -s ours merge twice:

$ git adog
*   f28fddb (HEAD -> dev) Merge branch 'main' into dev, keeping only contents of dev.
|\
| * d5dbf64 (main) main continues further
* | 890f8d9 dev continues further with better code
* | 3ce948b dev branches from main with great code
|/
* 6e24a88 main continues
* f389591 Start of main branch

yoder@MSOE-PF3TQZ3S MINGW64 ~/Box/Josiah/msoe/class/s/swe2410/24s2/Labs/tmp (dev)
$ git merge -s ours main
Already up to date.

In this case, the solution is clear -- just continue to the other steps, and they will work as expected. dev is already considered a continuation of the main branch, so an ordinary merge will do exactly what you want.


What about -s recursive -X ours?

Some readers will already be familiar with git merge -s recursive -X ours or have heard elsewhere about it. In cases like this where you want to keep the contents of the current branch only, the command presented in this post, git merge -s ours is the one on you want. Kelvin has explained in a comment on a different question when you would want -s recursive -X ours (emphasis added).

Note about git's merge "strategies": --strategy=ours [-s ours] is different from --strategy=recursive -Xours [-s recursive -X ours]. I.e. ours can be a strategy in itself (result will be the current branch no matter what), or passed as an option to the "recursive" strategy (bring in other branch's changes, and automatically prefer current branch's changes when there's a conflict).

In other words, you would use -s recursive -X ours when you want an automated merge that takes changes from both branches, but brings in changes from the current branch to resolve conflicts. (But personally, in such a case, I would generally recommend just performing the manual merge. If there is conflict, that means both branches have changed code at the same spot in a file. Rarely do you want to keep just one branch's version in such cases.

On the other hand, I use git merge -s ours a few times a year now. Wanting to make the side branch the main branch without losing the main branch's history is a very common simple operation that you may want to do.

Acknowledgement

The solution in this answer was previously posted to a different question here. This answer goes into a little more detail which may help users who are performing it for the first time.

Upvotes: 0

Pawan Maheshwari
Pawan Maheshwari

Reputation: 15366

This will merge your newBranch in existing baseBranch

git checkout <baseBranch> // checkout baseBranch
git merge -s ours <newBranch> // this will simply merge newBranch in baseBranch
git rm -rf . 
git checkout newBranch -- . 

Upvotes: -5

smparkes
smparkes

Reputation: 14073

You can just push your dev branch onto the master repo production branch:

git push --force upstream-remote dev:production

upstream-remote may just be origin if you're on a default clone.

Updated for mod'ed question:

You probably don't want to revert in the git sense but, yes, that's more or less what you want to do. Something like

git checkout -b merge <split hash>
git merge dev
git push --force origin merge:production

<split hash> was the last commit on production that you want to keep.

Upvotes: 28

Related Questions