35

I just upgraded to magit 2.1.0. (And also to emacs 25.0.50 and git 2.3.1.)

Previously, in the *magit* buffer I could:

  1. Select a hunk in the Unstaged area.
  2. Type v and answer yes to reverse it.

This was handy.

But now in magit 2.1.0 it gives an error: "Cannot reverse unstaged changes".

Why?


Taking a hint from the error message, I discovered I can still do this, albeit in a somewhat "backwards" way with more steps:

  1. stage the hunk. (Feels backwards; moving it closer to committed state.)
  2. Nav down and select it in the Staged area.
  3. Press v, answer yes.
  4. However the hunk is still Staged, so finally I have to unstage the hunk.

Is this a bug, or, is it intentional and/or I'm being dense? If the latter, can you help me understand?


UPDATE: After thoroughly RTFinfo-ing, I see that there are two commands:

  • v magit-reverse Reverse the change at point in the working tree.
  • k magit-discard Remove the change at point from the working tree.

It seems that k magit-discard does what I was used to v doing before. It does work on an unstaged hunk.

So practically I just need to retrain my muscle memory to use k. I could post that as a self-answer. But I guess I'm still curious about the rationale, because I imagine understanding it will help me understand magit better overall.

4
  • Glad you're Reading The Fine Info :) I'm confused what you mean by "reversing" a hunk. I've never heard that term before.
    – PythonNut
    Commented Jul 14, 2015 at 22:18
  • 2
    k discards anuncommitted change in earlier versions of magit as well, and seems the appropriate command for what you're doing. v is for git revert: creating a new commit that makes the opposite change of a prior one. I guess reverting a change that hasn't actually been committed is the same as discarding it, but 'revert' has specific meaning as a git command.
    – glucas
    Commented Jul 15, 2015 at 0:22
  • OK, it looks like v was bound to magit-revert-item (the "reverse" terminology comes from there, @PythonNut) and for unstaged items this used to do a magit-discard-item (as also bound to k) -- see line 4872 here. Apparently I accidentally learned that special meaning of v, which worked, when I ought to have learned to use k. Commented Jul 15, 2015 at 2:17
  • Although I'm usually not a big fan of self-answers, I think in this case that's the most merciful way to conclude this. :) Posted one below. Commented Jul 15, 2015 at 2:28

2 Answers 2

28

Magit implements five "apply variants" described in the manual: stage, unstage, "regular apply", discard, and reverse. The first three should be pretty obvious to most Git users. The latter two do not exist in the Git porcelain (in Magit they are implemented using Git plumbing commands and Emacs Lisp).

These two variants are described like this:

  • Discard. On a staged change, remove it from the working tree and the index. On an unstaged change, remove it from the working tree only.
  • Reverse. Reverse a change in the working tree. Both committed and staged changes can be reversed. Unstaged changes cannot be reversed. Discard them instead.

These two variants do very different things, so neither of these variants should fall back to the other variant in cases where itself cannot be used. Keeping the old behavior (of falling back from reverse to discard in some contexts) might have been more convenient in the short run, but in the long run it prevents users from really trying to understand what these two variants are for.

Discard is much more dangerous than reverse. The former "throws away uncommitted changes" (these changes are lost, they are not anywhere anymore), while the latter actually "creates changes", by taking an older change and doing the opposite in the worktree (the old change is not lost, it is still in a commit or the index).

Falling back from "creating" to "deleting" is very dangerous, so Magit doesn't do that anymore.


Also note by using the new wip modes you can protect yourself from losing changes due to an accidental discard.

2
  • 3
    Thank you very much for taking the time to answer and explain the rationale. Commented Jul 17, 2015 at 14:04
  • For me, the discard did not update the buffer within Emacs. Could have been pilot error. magit-version is v3.3.0-239-g29f5be35. emacs-version is 28.1
    – pedz
    Commented Aug 29, 2022 at 17:52
12

It looks like I had accidentally learned that v, bound to magit-revert-item, did used to do a magit-discard-item in this special case of unstaged hunks. See the <=== HERE === comment I put below:

(defun magit-revert-item ()
  "Revert the item at point.
The change introduced by the item is reversed in the current
working tree."
  (interactive)
  (magit-section-action revert (info)
    ([* unstaged] (magit-discard-item))  ;; <=== HERE ===
    (commit (when (or (not magit-revert-item-confirm)
                      (yes-or-no-p "Revert this commit? "))
              (magit-revert-commit info)))
    (diff   (when (or (not magit-revert-item-confirm)
                      (yes-or-no-p "Revert this diff? "))
              (magit-apply-diff-item it "--reverse")))
    (hunk   (when (or (not magit-revert-item-confirm)
                      (yes-or-no-p "Revert this hunk? "))
              (magit-apply-hunk-item it "--reverse")))))

Source: 1.4.2 code.

But now that doesn't happen:

(defun magit-reverse (&rest args)
  "Reverse the change at point in the working tree."
  (interactive (and current-prefix-arg (list "--3way")))
  (--when-let (magit-current-section)
    (pcase (list (magit-diff-type) (magit-diff-scope))
      (`(untracked ,_) (user-error "Cannot reverse untracked changes"))
      (`(unstaged  ,_) (user-error "Cannot reverse unstaged changes"))
      (`(,_      list) (magit-reverse-files (magit-section-children it) args))
      (`(,_     files) (magit-reverse-files (magit-region-sections) args))
      (`(,_      file) (magit-reverse-files (list it) args))
      (_               (magit-reverse-apply it args)))))

Source: master:


However k is bound directly to magit-discard-item. I should have learned to use that in the first place. That worked before 2.1.0, and still works.

In conclusion, magit 2.1.0 has been significantly redesigned. It's inevitable that some weird corner cases might not have survived. And, I would agree, don't need to have survived. I'll relearn the key.

1
  • 1
    Nice detailed self-answer -- may as well accept it!
    – glucas
    Commented Jul 15, 2015 at 13:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.