3

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
Community
  • 1
  • 1
Andrius
  • 19,658
  • 37
  • 143
  • 243
  • So, what you want to do is undo all commits that were only done in develop? (e.g. undo the “some commits“ and the “some other commits” from your history) – poke Jun 30 '16 at 07:06
  • @poke Well yes, but is there some easier way than to go through all commits and pick the ones I need to undo? This is just a sample, in real case there are lots of commits that need to be undo and there are commits in between which need to be kept. With `reset`, it is easy, I can just specify branch and it will reset to look exactly like `master`. – Andrius Jun 30 '16 at 07:10

2 Answers2

3

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
poke
  • 369,085
  • 72
  • 557
  • 602
  • Hm, I tried this, but it seems to not undo `develop`. It only undo files that were changed in both `master` and `develop`. But there are many files that are only added into `develop`, but not `master`. And those are kept. So when I finish checking out `develop`, and then when I merge `develop` into `master` it actually adds all those files into `master`. Am I missing something here? Do I need to just manually delete all those files and commit it? – Andrius Jun 30 '16 at 07:53
  • Which solution did you try? The very last one (using the `ours` merge strategy) should definitely work; for the other ones, you may need a `git reset` before the `git checkout master -- .` (I forgot that). After the merge, `git diff ` should return nothing, and `git diff ` should show many changes which are no longer on the current branch. – poke Jun 30 '16 at 08:14
  • That’s odd, that should work (assuming that I have the correct idea of your history). Can you try the last option? If that doesn’t work, can you open a [chat room](http://chat.stackoverflow.com/) and we can continue there? – poke Jun 30 '16 at 08:25
  • Also third option third options with git merge ours, do not do anything to `develop` and when I go to `master` I see commits from `develop`, but no files from develop. Shouldn't this be done for `develop` instead of `master`? To me it looks that only the first option does anything to develop – Andrius Jun 30 '16 at 08:26
  • Yes, the last option merges `develop` into `master` while discarding any changes. That way, you converge the history but preserve all the changes from `develop` (without keeping the content). This updates `master`, so afterwards you need to fast-forward `develop` to `master` (`git merge master`). – poke Jun 30 '16 at 08:31
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/116054/discussion-between-andrius-and-poke). – Andrius Jun 30 '16 at 08:32
2

Below command will get all files (except new ones added in current branch) from origin/master

git checkout origin/master .

Then, one can use git rm to remove the new files for example

git rm newfile
Marcello B.
  • 4,177
  • 11
  • 45
  • 65
jerry.pepper
  • 165
  • 1
  • 12