I have two branches develop
and master
. There are lots of commits in develop that are not in master
yet. Though I need to make develop
branch look exactly the same as master
. To preserve all changes that happened into develop
, I will create new branch from develop
so all those changes won't be lost.
But after doing "copy" of develop
, how can I safely reset or revert to look like master
?
I saw this: Git: reset/revert a whole branch to another branches state?
So to reset, I can do:
git checkout develop
git reset --hard master
But the problem is develop
branch is already pushed to remote and there others that had pulled develop
already.
Maybe there is safer way to do this using revert or some other means? But I want to revert (if possible) in a way that would revert to master
state, not manually selecting every commit, because some latest commits need to be kept in develop
because they came from master (hotfixes).
So history of commits on develop
looks something like this (most top means latest commit by date):
commit hotfix2 - in both develop and master
some other commits that are only in develop
commit hotfix1 - in both develop and master
some commits that are only in develop
all commits that came when develop was created from master
The standard process to undo commits in a non-destructive way is to use git revert
. This command basically takes the inversed diff of a target commit and tries to apply it. So you get a new commit that undoes all the changes.
In order to undo multiple commits at once, you can also specify a commit range. Given that you only have two ranges you would want to undo (those between those hotfixes), this would be actually manageable.
You can also use the flag --no-commit
, or -n
, to not automatically create a commit. This allows you to chain multiple git revert -n <commit>
commands after another without creating a reverting commit for each. Then, when you’re done selecting all commits or commit ranges you want to undo, you can make a single commit that combines them all.
In your case, since you have another branch which has the exact (working directory) state you want to put your develop
branch into, it is a lot easier to do that. All you have to do is to check out the working tree for master
into your develop
branch and commit that state onto the develop
branch. You would do this using git checkout master -- .
. Unfortunately, this will not work for paths that are unknown to the master
branch. So if you added new files in the develop
branch, those are kept and you would have to delete them separately.
Instead, we start a new branch off master
(which then has the exact same content), and reset that branch so it is based on develop
instead. That way, we keep the working directory state from master
but a commit would be follow develop
instead. Afterwards, we can fast-forward develop
that one commit:
# checkout a new branch off master
git checkout -b new-develop master
# make a soft reset to develop
git reset --soft develop
# commit the changes
git commit
# get back to develop and fast forward
git checkout develop
git merge --ff-only new-develop
git branch -d new-develop
This will result in the same thing you would get by chaining git revert -n
with all the commits that are exclusive to develop
. There are a few other ways to reach that state, but that’s really the easiest.
Regardless of which way you use to get to this state, you should consider making a merge afterwards. The merge will not actually do anything (since both branches have the same content) but it will combine the branches in the history, so you see that they actually converge.
So assuming the history looks like this right originally:
master
↓
* ------------ h1 ------ h2
\ \ \
* -- * -- * -- * -- * -- *
↑
develop
You would want to turn it into this:
master
↓
* ------------ h1 ------ h2 ----- M
\ \ \ / ↖
* -- * -- * -- * -- * -- * -- F develop
F
being the fix commit we created above. This assumes that you would want to merge develop
into master
(git merge develop
while on master
) and then fast-forward develop
(git merge master
while on develop
) to start the development work fresh from that point. Of course, you could also do it in the other direction if you prefer that.
Alternatively, we can also do this merge M
and the fix F
in a single step. You would effectively merge develop
into master
and merge everything in the way that you end up with the contents of master
. This would look like this:
master
↓
* ------------ h1 ------ h2 ---- FM
\ \ \ / ↖
* -- * -- * -- * -- * -- * ---/ develop
You can get there by hand like this:
# since we merge into master, we start there
git checkout master
# start the merge, but don’t attempt to fast-forward and do not
# commit the merge automatically (since we want to change it)
git merge --no-ff --no-commit develop
# reset the index that was prepared during the merge
git reset
# now checkout the files from master and commit the merge
git checkout master -- .
git add .
git commit
And actually, this is such a common scenario that git merge
comes with a merge strategy that does exactly this. So instead of the above, we can just use the ours
merge strategy and discard anything from the branch we merge into the current:
git checkout master
git merge -s ours develop