Subject: Re: Reset by checkout?

From: Kevin Bracey <[email-masked]>
Date: Sun, 01 Jun 2014 11:45:08 +0300

On 01/06/2014 07:26, Atsushi Nakagawa wrote:
> Kevin Bracey <[email-masked]> wrote:
>> The original "git reset --hard" used to be a pretty top-level command.
>> It was used for aborting merges in particular. But I think it now
>> stands out as being one of the only really dangerous porcelain
>> commands, and I can't think of any real workflow it's still useful
>> for.
> My thoughts exactly.  I think the 'reset --soft/--mixed/--hard' pattern
> is so ingrained, that many people just don't realize there's a safer
> alternative.  (I've heard work mates on more than one occasion
> recommending 'reset --hard' as the go-to command for discarding commits.)
>
> I believe this is likely because many third party GUI tools just don't
> support 'reset --keep', and these tools present a "Reset..." dialog with
> the de facto Soft/Mixed/Hard options.  (Even 'gitk' does this.)
True on the GUI - "hard" really needs demotion.

It would help if the documentation explained better straight off what 
the different reset modes are intended /for/ in a more practical way, 
rather than the technical jargon.

There is the "EXAMPLES" section, but I think the problem is that it's 
not clearly laid out by mode, meaning people checking to see what "git 
reset" can do are inclined to go first to the "--xxx" mode list in 
"DESCRIPTION", and stop there, baffled, probably not finding any example 
for that mode. Maybe the examples should have clearer "--option" 
subheadings? (And all the existing examples for --hard should really 
suggest --keep instead).

But given that the "DISCUSSION" section now has the full internal 
details on what exactly each mode does in every state, and now that we 
have more than the "simple" soft/mixed/hard to deal with, I think the 
main "DESCRIPTION" could be simplified for end users.

Most useful for visualisation, I feel, would just showing what "git 
status" will look like afterwards, primarily from the point of view of a 
"backwards" reset to HEAD~n. In particular, normal users don't think in 
terms of the absolute contents of the index, but rather in terms of diffs.

Maybe something like this:

"All modes move the current branch pointer so that HEAD now points to 
the specified commit. ORIG_HEAD is set to the original HEAD location. 
The modes differ in what happens to the contents of ORIG_HEAD, that are 
no longer on the reset branch; and also what happens to your 
not-yet-committed changes.

--soft
      Retains the contents of ORIG_HEAD in the index+work area, leaving 
the difference as "changes to be committed". "git reset --soft HEAD~1" 
would be the first step if you want to remove the last commit, but 
intend to recommit most or all of its changes.

"git status" after reset --soft shows:

   To be committed:
        Changes in ORIG_HEAD relative to HEAD
        (+Any initial staged changes)

   Not staged:
        (Any initial unstaged changes)

--mixed (default)
     Retains the contents of ORIG_HEAD in the work area, leaving the 
difference as unstaged changes. "git reset HEAD~1" would be the first 
step if you want to remove the last commit, and think again from scratch 
about which of its changes should be committed.

"git status" after reset --mixed shows:

    Not staged:
        Changes in ORIG_HEAD relative to HEAD
        (+Any initial staged changes)
        (+Any initial unstaged changes)

--keep
    The contents of ORIG_HEAD are dropped, leaving the work area and 
index containing the new HEAD; your uncommitted changes to unaffected 
files are retained. If you have uncommitted changes to any files that 
differ in the proposed new HEAD, the operation is refused; you would 
need to "git stash" first. "git reset --keep HEAD~1" can be used to 
totally remove the last commit. (This removal can itself be undone with 
another "git reset --keep ORIG_HEAD", or "git reset --keep 
<branch>@{<n>}" - see git-reflog(1)). "git reset --keep" is a safe 
alternative to "--hard", and is roughly equivalent to "git checkout -B 
<current-branch-name>".

"git status" after reset --keep shows:

    Not staged
        (Any initial staged changes)  [should these be left staged, as 
per "git checkout"?]
        (+Any initial unstaged changes)

--hard
    All other changes are dropped, and the work area and index are 
forcibly reset to the new HEAD. Note that this is dangerous if used 
carelessly: ALL uncommitted changes to ALL tracked files will be lost, 
even if you were only trying to drop an unrelated commit that didn't 
touch those files. Older documentation often recommends "git reset 
--hard" to undo commits; the newer "--keep" option is a much better 
alternative in almost all cases.

"git status" after reset --hard shows:

    Work area clean (or untracked files present)

--merge
    Performs the operation of "git merge --abort", intended for use 
during a merge resolution - see git-merge(1) for more information. This 
form is not normally used directly. [Not really true? Still the best 
command to abort "git checkout --merge"/"git stash pop|apply"? Do those 
need "--abort"?]

> If people just forgot about '--hard' and used '--mixed/--keep' for
> regular cases, '--hard' would effectively be -f. ;)

True, but this is quite a big shift. And I think "--keep" isn't a clear 
name in isolation. It's named relative to "--hard", rather than 
absolutely...  "Soft/Mixed/Keep" is a sensible 3-way choice, but it 
doesn't sound like it. "Keep" keeps less than soft or mixed!

Maybe a new parallel naming scheme is in order? Could we instead phrase 
it from the point of view of what we want to do with the stuff in 
ORIG_HEAD? "--to-index"/"--to-workspace"/"--drop"? (--hard deliberately 
excluded...)

Kevin