46

In my experiments I haven't been able to find any functional difference between

git reset --hard

and

git reset --merge

The usage instructions don't give any hint either

--hard                reset HEAD, index and working tree
--merge               reset HEAD, index and working tree

I regularly use the --hard option so understand how that works. What's the difference between the --merge and the --hard options?

Cheers, Olly

Perhaps an example would help here, let's use the following sequence:

cd git_repo
touch file_one
git add file_one
git commit -m "commit one" # sha1 of 123abc
echo "one" >> ./file_one
git commit -a -m "commit two" # sha1 of 234bcd
echo "two" >> ./file_one
git add . # populate index with a change
echo "three" >> ./file_one # populate working area with a change

Now if I try

git reset --merge 123abc

I get

error: Entry 'file_one' not uptodate. Cannot merge.
fatal: Could not reset index file to revision '123abc'

the reason being that file_one has changes in both the working area and the index

To remedy this I do

git add .
git reset --merge 123abc

This time it works, however, I get the same result as git reset --hard. The index is empty, working area is empty, file_one is empty, as it was after first commit.

Can someone come up with the steps that illustrate the difference?

opsb
  • 29,325
  • 19
  • 89
  • 99

5 Answers5

32

From git reset manpage:

--hard    Matches the working tree and index to that of the tree being
               switched  to. Any changes to tracked files in the working tree since
               <commit> are lost.

--merge
              Resets the index to match the tree recorded by the named commit, and
              updates the files that are different between the named commit and
              the current commit in the working tree.

The git reset --merge is meant to be a safer version of git reset --hard, when your changes and somebody else changes are mixed together, trying to carry our changes around.

Jakub Narębski
  • 309,089
  • 65
  • 217
  • 230
  • 1
    I have read those docs, but have to say I can't make much sense of them. "Resets the index to match the tree recorded by the named commit" My understanding is that the index is empty after the reset --merge operation, this comment seems to indicate something else. "and updates the files that are different between the named commit and the current commit in the working tree" where are the updates that are made to these files, they don't appear in the index, are they committed automatically? – opsb Oct 27 '09 at 23:36
  • 1
    After `git reset --merge ` you have index == , but it updates only those files in working are that are different between HEAD (current commit) and (named commit), preserving (some of) your local changes. – Jakub Narębski Oct 27 '09 at 23:45
  • In the example I have above I found that I couldn't perform a git reset --merge when I had changes in the working area. Is this in fact possible using different steps from those that I've shown above? – opsb Oct 27 '09 at 23:59
  • Ok, so the issue was that I had the same file in both the index and the working area. With different files I see the behaviour that you describe. – opsb Oct 28 '09 at 00:05
  • 2
    So it appears that the difference between --hard and --merge is that files with changes in the working area are kept if possible. It isn't possible for files that have changed since the version that you are resetting to. In this case they are simply reset to the old version with any changes being discarded. – opsb Oct 28 '09 at 00:15
  • About index == HEAD, and index being empty. These are different ways to think about the index: 1) a tree ("Resets the index to match the tree recorded by the named commit"), 2) a diff between a tree and HEAD ("the index is empty"). – x-yuri Feb 22 '22 at 14:56
16

The article "Git undo, reset or revert?" summarizes the different usages, when used with ORIG_HEAD:

# Reset the latest successful pull or merge
$ git reset --hard ORIG_HEAD

# Reset the latest pull or merge, into a dirty working tree
$ git reset --merge ORIG_HEAD

As mentioned by manojlds's answer, and illustrated by the blog post, the latter is especially useful when you see an error message like:

fatal: You have not concluded your merge. (`MERGE_HEAD` exists)

The thread "[PATCH] refuse to merge during a merge" also details that point:

git reset --merge HEAD

It fills the rather different case where you did a clean merge with some uncommitted changes in the worktree, but then want to discard the merge again without losing the uncommitted changes.
In absence of the changes, you would just use --hard, but here you want to move the branch tip while merging them over, similar to what 'git checkout -m' does for moving HEAD.

x-yuri
  • 16,722
  • 15
  • 114
  • 161
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • You seem to be mixing 2 cases here: 1) [the one](https://gist.github.com/x-yuri/08736f613e87e8148c72232d37574589), mentioned by the developers, 2) [merge before `merge --continue`](https://gist.github.com/x-yuri/999fe7ed483ecdb0f92b5ca1858efc53). As you can see the second merge doesn't change anything, so the second case is not different from a plain merge conflict. And unless you start working on a new feature at this point (make changes not related to resolving the conflict), `git reset --hard` is not different from `git reset --merge`... – x-yuri Feb 22 '22 at 14:25
  • ...From what I can see, `git reset --merge` is for the cases where you had changes in the working tree before the merge, and you want to undo the merge preserving the changes, as explained in the @manojids's answer. – x-yuri Feb 22 '22 at 14:26
  • @x-yuri Indeed. manojids' answer is referenced in the answer you just edited (Thank you for the edit, by the way. Much appreciated). – VonC Feb 22 '22 at 14:54
10

This is helpful when you do a pull with changes in working tree, and find that the merge is not as expected ( you might have been expecting that the commits would not affect the files you were working on ). At this point, if you do git reset --hard ORIG_HEAD, you blow away everything, including your local changes. If you do git reset --merge ORIG_HEAD, you will keep your local changes.

manojlds
  • 290,304
  • 63
  • 469
  • 417
  • This case is mentioned [here](https://lore.kernel.org/all/200905301257.27630.trast@student.ethz.ch/) ([steps to reproduce](https://gist.github.com/x-yuri/08736f613e87e8148c72232d37574589)). I think it's worth mentioning that it also works when you had changes in the working directory before a merge *and* you're in the middle of resolving the conflict. This case is not mentioned by the developers. – x-yuri Feb 22 '22 at 15:59
5

Apparently according to:

http://www.kernel.org/pub/software/scm/git/docs/git-reset.html

--hard - Matches the working tree and index to that of the tree being switched to. Any changes to tracked files in the working tree since <commit> are lost.

--merge - Resets the index to match the tree recorded by the named commit, and updates the files that are different between the named commit and the current commit in the working tree.

falsarella
  • 12,217
  • 9
  • 69
  • 115
Jonathan Holloway
  • 62,090
  • 32
  • 125
  • 150
1

tl;dr git reset --merge is git reset --keep for undoing a merge. git reset --keep is git reset --hard that tries to preserve unstaged changes. Also, git reset --hard may change unstaged files (git-reset):

Any untracked files or directories in the way of writing any tracked files are simply deleted.

I think what you generally want to know about git reset --merge is covered by by @manojlds (changes in the working tree before a merge; a conflict or a clean merge). Because that looks like the use case for git reset --merge.

But you may also want to consider the following 2 cases:

  1. If you had no changes in the working tree before a merge, and you're in the middle of resolving a conflict, git reset --merge ORIG_HEAD is not much different from git reset --hard ORIG_HEAD. Unless you start working on a new feature at this point (make changes unrelated to resolving a conflict). That is, if you want to undo the merge, you most likely want to undo everything. And in this case you can simply do git merge --abort.

    But git reset --merge ORIG_HEAD will preserve unstaged changes in this case. Except for changes made to files with conflicts (discard), and changes made to staged files (abort).

    Although theoretically it may be handy, it doesn't sound like a use case for git reset --merge.

  2. If you're not in the middle of resolving a conflict, and want to git reset --merge to an arbitrary commit, it will try to preserve unstaged changes. But so will git reset --keep. The latter simply doesn't work in the middle of a merge, and ignores staged changes.

    They will abort if they can't preserve unstaged changes. That will happen if you changed a file that was also changed between HEAD and <commit>.

x-yuri
  • 16,722
  • 15
  • 114
  • 161