19

In a recent answer in which he details the typical use cases of git-reset's three most commonly used options (--hard, --mixed, and --soft), torek mentions in passing that git-reset also offers two relatively esoteric flags, called --merge and --keep. The git-reset man page describes those two flags as follows:

--merge
           
   Resets the index and updates the files in the working tree
   that are different between <commit> and HEAD, but keeps
   those which are different between the index and working tree
   (i.e. which have changes which have not been added). If a
   file that is different between <commit> and the index has
   unstaged changes, reset is aborted.

   In other words, --merge does something like a git read-tree
   -u -m <commit>, but carries forward unmerged index entries.

--keep
    Resets index entries and updates files in the working tree
    that are different between <commit> and HEAD. If a file that
    is different between <commit> and HEAD has local changes,
    reset is aborted.

I perfectly understand when to use --hard, --mixed, or --soft, but I only learned that --merge and --keep existed while reading torek's answer, and I can't think of practical use cases of those two flags... In what situations do you typically use those two flags?

I'm mainly looking for a plain-English explanation. Take the following passage of this answer by VonC, which spells out a typical use case for git reset --soft, as a model:

[...] each time:

  • you are satisfied with what you end up with (in term of working tree and index)
  • you are not satisfied with all the commits that took you to get there:

git reset --soft is the answer.

However, I'm not averse to a little experiment with those flags, similar in spirit to the silly shopping-list example I posted in this answer of mine.

jub0bs
  • 60,866
  • 25
  • 183
  • 186

4 Answers4

8

Frankly I'm not really sure about this; I've never even used the --merge and --keep modes myself. However, the release notes indicate that git reset --merge was added in git version 1.6.2, with the following note:

  • git reset --merge is a new mode that works similar to the way git checkout switches branches, taking the local changes while switching to another commit.

and --keep was added in 1.7.1:

  • git reset learned --keep option that lets you discard commits near the tip while preserving your local changes in a way similar to how git checkout branch does.

Then, in 1.7.9:

  • git checkout -B <current branch> <elsewhere> is a more intuitive way to spell git reset --keep <elsewhere>.

which tells us that the idea behind --keep is, you have started work on some branch, and then you realize: oh, this branch should fork off from some other point (probably the tip of some other branch). For instance you might:

$ git checkout devel
$ git checkout -b fix-bug-1234

and then do some work to fix bug 1234 for the next release; but then someone says: "hey, we need bug 1234 fixed for the old release version!" So now you want fix-bug-1234 to branch off from release instead of devel. Meanwhile you haven't committed anything yet. So you:

$ git checkout -B fix-bug-1234 release

to move it to "coming off release" instead of "coming off devel". Which works in 1.7.9 or later, but in 1.7.1 through 1.7.8 you have to spell it git reset --keep.

That might explain --keep but --merge is still a bit of a mystery.

torek
  • 448,244
  • 59
  • 642
  • 775
  • In your example, if you're editing `buggy-file.c` when you switch from `git reset --keep fix-bug-1234`, the reset will fail if `buggy-file.c` is different in branch `devel` and branch `fix-buf-1234`. Hence, `--merge`. – Dietrich Epp Aug 28 '14 at 22:49
  • @DietrichEpp: but in that case, per the description of `--merge`: `buggy-file.c` *is* different between the index and `` (it's different in `devel` than `release`) *and* it has unstaged changes (we've modified the work tree), so therefore `git reset --merge` will abort the reset too. I have the feeling something is off in the man-page description. The source code indicates that `--merge` is allowed in the middle of a conflicted merge, while `--soft` and `--keep` are not. – torek Aug 28 '14 at 23:20
  • @torek Thanks for that. Those flags remain quite exotic to me, though. If I ever feel the need to use them, I might come back here and post my own answer. – jub0bs Sep 01 '14 at 17:05
1

I agree that at a first look those flags seem to be exotic. It took me hours to understand them, however the difference is pretty clear: --keep unstages the index while --merge completely discards the index.

Going back to a typical use cases: Imagine that you have a configuration file specific to your local environement, e.g., containing credentials to your local database. When you are going to do a "hard" reset, then for sure you don't want to loose your local changes. In that case

git reset --keep <commit>

perfectly does the job. This shows that more often you want to use --keep flag instead of --hard flag.

The flag --merge is a bit more aggressive than --keep since it completely discardes the index (notice that in this case you can lose your work in opposite to --keep), but in my opinion the practical use cases for both of them are the same.

Originally, git reset --merge was introduced to abort a merge, but now git merge --abort is prefered (the first option was introduced in 1.6.2 version while the second one in 1.7.4).

szydra
  • 116
  • 6
  • >> --keep unstages the index There is no such thing as unstaging the index. From documentation: --keep Resets index entries... – avenir Sep 08 '22 at 13:11
1

From https://git-scm.com/docs/git-reset

reset --keep is meant to be used when removing some of the last commits in the current branch while keeping changes in the working tree. If there could be conflicts between the changes in the commit we want to remove and the changes in the working tree we want to keep, the reset is disallowed. That’s why it is disallowed if there are both changes between the working tree and HEAD, and between HEAD and the target. To be safe, it is also disallowed when there are unmerged entries.

I can think about scenario where there are some local commits: Commit0, Commit1, Commit2, ..., CommitX, CommitY, ..., CommitN

and I want to discard changes in Commit1..CommitX, but preserve changes in CommitY..CommitN

In order to do this I could use git reset --soft CommitX, and then git reset --keep Commit0

I guess without --keep, someone would need to use stash in order to do this.

anth
  • 1,724
  • 1
  • 19
  • 22
0

Try this one.

If you pulled from a branch, after that you see, oh! there are multiple conflicts. Then suddenly you remember you had pulled from the wrong branch then it will help to revert it.

git reset --merge HEAD@{1}
Mushif Ali Nawaz
  • 3,707
  • 3
  • 18
  • 31
Ankit Kumar Rajpoot
  • 5,188
  • 2
  • 38
  • 32
  • Thanks, but this is a very terse answer. Could you describe what that command does and why it's useful? – jub0bs Feb 08 '21 at 12:45