Home » Git » What are typical use cases of git-reset's –merge and –keep flags?

What are typical use cases of git-reset's –merge and –keep flags?

Posted by: admin November 15, 2021 Leave a comment

Questions:

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.

Answers:

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.

###

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).

###

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.

###

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 [email protected]{1}