So say I have commits my dev
branch,
a
b
c
I do a pull/merge request to the main
branch, with squash merge option turned on.
So the main
branch now looks like,
merge from 'dev' to 'main'
squash: a, b, c
But my source branch dev
remains to be three seperated commits. And that could be a problem when I do git rebase main
on dev
branch, especially when the main
branch is stuffed with other developers' squashed merges.
Normally, I would cherry-pick
my ahead commits onto a dev_bak
branch. delete my current branch dev
and republishes it by doing
git switch [any-branch]
git branch -d dev // delete dev branch
git checkout -b dev // re-create dev branch
git rebase main // do rebase
git push --force // force push to remote to overwrite
And cherry-picks my ahead commits back onto the dev
branch.
So I was wondering if there is a quick way of doing this? Perhaps git rebase --force
?
Thanks!!
CodePudding user response:
First, let's visualize your scenario:
o---o---o-----S (main)
\
A---B---C (dev)
Commit S
is the result of squashing A
, B
and C
into a single commit and applying it to the target branch (a.k.a a "squash merge").
Now, the problem you're referring to is due to the fact that S
is unrelated to the original commits A
, B
and C
even though it contains the same changes. Rebasing dev
on top of main
would result in merge conflicts as Git tries to reapply the same changes on top of the squashed commit.
The command you're looking for is reset
with the --hard
option:
git switch dev
git reset --hard main
This will move the dev
branch to point to the same commit as main
:
o---o---o-----S (main, dev)
\
A---B---C
Commits A
, B
and C
will now become unreachable and will eventually be deleted.
Note that the --hard
option will also synchronize the files in your working directory to match the commit you're resetting to. This means that any uncommitted changes in your working directory will be discarded when you do this. Make sure to stash them or commit them to a temporary branch before doing git reset --hard
.
CodePudding user response:
Your best option is don't use squash merges for a branch you're not deleting. A squash merge throws away the history on a branch; it only makes sense when you're about to delete that branch anyway. For long-running branches, it is better to create a merge commit, so that the relationship between branches is recorded.
Because a squash merge doesn't record which commits were merged, you will need to track that manually if you carry on using the branch.
If you visualise the commit graph you have and what you are trying to create, then you can work out what rebase command to use - it's not about "forcing" the rebase, it's about telling git what commits you want to keep.
Let's say we start with this:
... <--A <--B <--C <--D <--E <--F
^ ^
main dev
Then you squash-merge dev to main (commit DEF), and carry on committing to dev:
... <--A <--B <--C <--D <--E <--F <--G <--H
\ ^
<-- DEF dev
^
main
What you want to end up with is this (commits GR and HR recreating the changes of G and H, respectively):
... <--A <--B <--C
\
<-- DEF <--GR <--HR
^ ^
main dev
git rebase
has a three-argument form which I read as "rebase commits from old_base to tip onto new_base": git rebase old_base tip --onto new_base
(some prefer to put the --onto new_base
first, it really doesn't matter).
So in this case, we want to "rebase commits from F to H onto DEF" (because F is the last commit that we squashed):
git rebase F H --onto DEF
Since we have some branch pointers, we only need to look up the commit hash for F, and can write:
git rebase F dev --onto main