threads / discuss / 40444

Why not git reset --hard <path>?

Subject: Why not git reset --hard <path>?

## tl;dr

11 messages between Sep 28, 2015 and Sep 29, 2015.

replies: 10people: 5as markdown or json

George Spelvin· Sep 28, 2015, 20:34 UTC · lore

I was applying an old forgotten stash to see if there were any edits in it I wanted to preserve, and my old changes to one file made no sense any more. I wanted to drop then all and keep the version in HEAD.

I'd been using git reset <path> after resolving conflicts, to leave the changes in the same un-staged state they were before the stash, so I tried using "git reset --hard crypto/842.c" to throw away my local changes.

And I got fatal: Cannot do hard reset with paths.

So I did "git reset <path>" followed by "git checkout <path>", which achieved what I wanted.

But what I don't understand is why git reset couldn't do it for me in one step.

I understand that "git reset --soft" makes no sense with a path, but why not --hard?

Junio C Hamano· Sep 28, 2015, 20:42 UTC · re: George Spelvin · lore

Re: Why not git reset --hard <path>?

"George Spelvin" <linux@horizon.com> writes:
Show 20 quoted lines
> I was applying an old forgotten stash to see if there were any edits in
> it I wanted to preserve, and my old changes to one file made no sense
> any more.  I wanted to drop then all and keep the version in HEAD.
>
> I'd been using git reset <path> after resolving conflicts, to leave
> the changes in the same un-staged state they were before the stash,
> so I tried using "git reset --hard crypto/842.c" to throw away
> my local changes.
>
> And I got
> fatal: Cannot do hard reset with paths.
>
> So I did "git reset <path>" followed by "git checkout <path>", which
> achieved what I wanted.
>
> But what I don't understand is why git reset couldn't do it for me in one
> step.
>
> I understand that "git reset --soft" makes no sense with a path, but
> why not --hard?

I do not think there is anything fundamentally wrong for wishing for "reset --hard <pathspec>". It probably is just that nobody needed it, because "git checkout HEAD <pathspec>" is a 99% acceptable substitute for it (the only case where it makes a difference is when you added a path to the index that did not exist in HEAD).

Jacob Keller· Sep 28, 2015, 20:53 UTC · re: Junio C Hamano · lore

Re: Why not git reset --hard <path>?

On Mon, Sep 28, 2015 at 1:42 PM, Junio C Hamano <gitster@pobox.com> wrote:
Show 10 quoted lines
> "George Spelvin" <linux@horizon.com> writes:
>> I understand that "git reset --soft" makes no sense with a path, but
>> why not --hard?
>
> I do not think there is anything fundamentally wrong for wishing for
> "reset --hard <pathspec>".  It probably is just that nobody needed
> it, because "git checkout HEAD <pathspec>" is a 99% acceptable
> substitute for it (the only case where it makes a difference is when
> you added a path to the index that did not exist in HEAD).
>

Personally, I would like to see this simply given the number of times that I use git reset --hard <path> and then realize I should have used git checkout instead. I conceptually think reset --hard should do that, and that checkout is really not meant to do that as a concept.

I may have some time to try and give this a look in the next few days...

Regards, Jake

Junio C Hamano· Sep 28, 2015, 21:19 UTC · re: Jacob Keller · lore

Re: Why not git reset --hard <path>?

Jacob Keller <jacob.keller@gmail.com> writes:
Show 15 quoted lines
> On Mon, Sep 28, 2015 at 1:42 PM, Junio C Hamano <gitster@pobox.com> wrote:
>> "George Spelvin" <linux@horizon.com> writes:
>>> I understand that "git reset --soft" makes no sense with a path, but
>>> why not --hard?
>>
>> I do not think there is anything fundamentally wrong for wishing for
>> "reset --hard <pathspec>".  It probably is just that nobody needed
>> it, because "git checkout HEAD <pathspec>" is a 99% acceptable
>> substitute for it (the only case where it makes a difference is when
>> you added a path to the index that did not exist in HEAD).
>
> Personally, I would like to see this simply given the number of times
> that I use git reset --hard <path> and then realize I should have used
> git checkout instead. I conceptually think reset --hard should do
> that, and that checkout is really not meant to do that as a concept.

I agree with you if we limit the scope to "reset --hard" that does not mention any commit on the command line (or says "HEAD").

However, for things like:
    $ git reset --hard HEAD^ Makefile
    $ git reset --hard HEAD@{4.hours.ago} Makefile

I do not think "reset --hard" is a good match. Conceptually, you are grabbing what was stored in a given commit and checking that out to your current workspace (that is, the index and the working tree).

All modes of "git reset" are primarily about updating where in the history DAG your HEAD points at, and then adjusting your current workspace to that update, taking into account the reason why you are repointing your HEAD in the history DAG (e.g. when doing --hard reset, you want the workspace to match what the commit your HEAD now points at; when doing --soft reset, you don't want any changes done).

It is only when you use "git reset" to update your HEAD to point at the exact same commit when interaction with <pathspec> could make sense, but conceptually the use case is covered better by "checkout", which does _not_ futz with the history at all. The command is primarily about preparing your workspace (that is, the index and the working tree) toward the work you do to prepare for the next commit you are going to make. Either you check out a branch so that your next commit goes to that branch (and you take your local changes with you when you do so), or you grab contents from other existing commits to prepare for your work.

So while I am OK with loosening "reset --hard" with <pathspec> if the command weren't given any commit from the command line, I doubt that would be a real improvement.

I suspect it may even hurt the users by placing them in a wrong mental model (i.e. I doubt you would wish "reset --hard <path>" to work if your starting point were "reset is primarily about moving in the history DAG and adjust the workspace accordingly").

Theodore Ts'o· Sep 28, 2015, 21:36 UTC · re: Junio C Hamano · lore

Re: Why not git reset --hard <path>?

I personally have in my .gitconfig:
[alias]
	revert-file = checkout HEAD --

I'm not sure revert-file is the best name, but it's what I've used because I've been contaminated by the concept/naming of "p4 revert", which I do use a fair amount to undo local edits for one or more files when I've been forced to use perforce or perforce-like systems. Given that it confuses the concept of how "git revert" works, maybe something like "git unedit <pathspec>" would work better.

Given though it's so easy to address this with a single line in a user's .gitconfig, I guess the question is whether it's worthwhile to make a change that would be visible to all users, and perhaps more importantly, all new users to git.

	     	     	     	      - Ted
Jacob Keller· Sep 29, 2015, 07:06 UTC · re: Junio C Hamano · lore

Re: Why not git reset --hard <path>?

On Mon, Sep 28, 2015 at 2:19 PM, Junio C Hamano <gitster@pobox.com> wrote:
Show 12 quoted lines
> I agree with you if we limit the scope to "reset --hard" that does
> not mention any commit on the command line (or says "HEAD").
>
> However, for things like:
>
>     $ git reset --hard HEAD^ Makefile
>     $ git reset --hard HEAD@{4.hours.ago} Makefile
>
> I do not think "reset --hard" is a good match.  Conceptually, you
> are grabbing what was stored in a given commit and checking that out
> to your current workspace (that is, the index and the working tree).
>

Agreed. I just get used to thinking about using it against HEAD. it's just weird to me that something which sometimes switches branches is also the thing to grab a version of a file.

reset hard really would be weird in this case, because you really don't know if the user meant "rewind the history, but keep everything *except* that listed file..

That makes sense now that I think about it. Thanks.

Regards, Jake

George Spelvin· Sep 29, 2015, 16:15 UTC · re: Junio C Hamano · lore

Re: Why not git reset --hard <path>?

Show 11 quoted lines
> I agree with you if we limit the scope to "reset --hard" that does
> not mention any commit on the command line (or says "HEAD").
> 
> However, for things like:
>
>     $ git reset --hard HEAD^ Makefile
>     $ git reset --hard HEAD@{4.hours.ago} Makefile
>
> I do not think "reset --hard" is a good match.  Conceptually, you
> are grabbing what was stored in a given commit and checking that out
> to your current workspace (that is, the index and the working tree).

I actually disagree, BUT that was based on an inaccurate mental model, so I'm not sure if my judgement can be trusted. Still, I'll blather on just to give a different perspective.

To me, "git reset --hard" is "git reset" plus checking out from the index to the working directory. That's the difference and the only difference.

So any difference in behaviour between
	git reset --hard <revision> -- <paths>
and
	git reset --mixed <revision> -- <paths>
	git checkout -- <paths>
needs to be justified.  (There might be some if a file does not exist in
the revision.)
Show 7 quoted lines
> All modes of "git reset" are primarily about updating where in the
> history DAG your HEAD points at, and then adjusting your current
> workspace to that update, taking into account the reason why you are
> repointing your HEAD in the history DAG (e.g. when doing --hard
> reset, you want the workspace to match what the commit your HEAD now
> points at; when doing --soft reset, you don't want any changes
> done).

Er... no. Re-pointing HEAD can *only* be done as a global operation. That's the single most fundamental difference between git and CVS.

Any time you specify a path, *obviously* that part can't be done, so git-reset just skips that part and goes on to the index-updating part..

Git reset also skips that part in the single most common invocation scenario: un-doing git-add. That's for a different reason (pointing HEAD to HEAD is a no-op), but it contributes to the mental model that git-reset is fundamentally used for copying from history to the index.

That's my mental model, for what little it's worth (given the caveat above):
- git checkout is fundamentally about copying from the index to the
  working directory.  If can also move HEAD first (and create branches!)
  as a convenience feature.
- git reset is fundamentally about copying from HEAD to the index.
  Like git-checkout, it can also move HEAD first (and copy to the working
  directory) as a convenience feature.

To me, "git reset" is "throw the changes away and get me back to some previous state". That's why it's the tool I reached for in the merge-conflict situation that started this thread.

I didn't think to try "git checkout" because I needed to overwrite the merge conflict in the index, and I don't think of "git checkout" as doing that. (Globally, it fails if there are unresolved conflits.)

Philip Oakley· Sep 29, 2015, 19:40 UTC · re: Jacob Keller · lore

Re: Why not git reset --hard <path>?

From: "Jacob Keller" <jacob.keller@gmail.com>
Show 22 quoted lines
> On Mon, Sep 28, 2015 at 1:42 PM, Junio C Hamano <gitster@pobox.com> wrote:
>> "George Spelvin" <linux@horizon.com> writes:
>>> I understand that "git reset --soft" makes no sense with a path, but
>>> why not --hard?
>>
>> I do not think there is anything fundamentally wrong for wishing for
>> "reset --hard <pathspec>".  It probably is just that nobody needed
>> it, because "git checkout HEAD <pathspec>" is a 99% acceptable
>> substitute for it (the only case where it makes a difference is when
>> you added a path to the index that did not exist in HEAD).
>>
>
> Personally, I would like to see this simply given the number of times
> that I use git reset --hard <path> and then realize I should have used
> git checkout instead. I conceptually think reset --hard should do
> that, and that checkout is really not meant to do that as a concept.
>
> I may have some time to try and give this a look in the next few days...
>
> Regards,
> Jake
> --

I also recently had this problem of expecting to be able to use something like `git reset --hard -- <path>` to clear up some crud and having to cast around for the right approach.

Would it at least be worth flagging up the alternate ` git checkout -- path` a little better within the 'get reset' man pages? At the moment its hidden at the end of the git reset [-q] [<tree-ish>] [--] <paths>…​ section, so is easily missed.

(i.e. should I flesh out a patch, or would the nuances bury it...?)
Philip
George Spelvin· Sep 28, 2015, 22:36 UTC · re: Junio C Hamano · lore

Re: Why not git reset --hard <path>?

Junio C Hamano <gitster@pobox.com> wrote:
> "git checkout HEAD <pathspec>" is a 99% acceptable substitute for it
> (the only case where it makes a difference is when you added a path to
> the index that did not exist in HEAD).
Er, wait a minute...
"git checkout <tree-ish> <pathspec>" modifies the index?

Damn, I've been using git for years and I never knew that. I thought it only modified the working tree.

But I just tested, and it does. Damn, now I have to figure out how to "leapfrog" a file from history into the working tree without overwriting the index; that's occasionally useful.

Junio C Hamano· Sep 28, 2015, 22:58 UTC · re: George Spelvin · lore

Re: Why not git reset --hard <path>?

"George Spelvin" <linux@horizon.com> writes:
Show 10 quoted lines
> Junio C Hamano <gitster@pobox.com> wrote:
>> "git checkout HEAD <pathspec>" is a 99% acceptable substitute for it
>> (the only case where it makes a difference is when you added a path to
>> the index that did not exist in HEAD).
>
> Er, wait a minute...
>
> "git checkout <tree-ish> <pathspec>" modifies the index?
>
> Damn, I've been using git for years and I never knew that.

... which would be an indication that the behaviour is most likely the most natural one.

> But I just tested, and it does.  Damn, now I have to figure out
> how to "leapfrog" a file from history into the working tree without
> overwriting the index; that's occasionally useful.
... and indeed it is useful in some rare cases.  Either
    git diff <tree-ish> <pathspec> | git apply -R
or
    git checkout <tree-ish> <pathspec> &&
    git reset <pathspec>
are what I would use, and as an old-timer, the former is what I do.
George Spelvin· Sep 28, 2015, 23:52 UTC · re: Junio C Hamano · lore

Re: Why not git reset --hard <path>?

Junio C Hamano <gitster@pobox.com> wrote:
Show 7 quoted lines
> "George Spelvin" <linux@horizon.com> writes:
>> "git checkout <tree-ish> <pathspec>" modifies the index?
>>
>> Damn, I've been using git for years and I never knew that.
>
> ... which would be an indication that the behaviour is most likely
> the most natural one.

I think it's more that often, staging a file to the index is pretty harmless, so it doesn't actually matter.

Both of the commonest forms of commit ("git commit -a" and "git commit [-o] <paths>") aren't affected, and if I'm doing something trickier, I'll usually examine the status and make sure I've got it right.

So I could have encountered it and put it down to fat-fingering on my part.

>> But I just tested, and it does.  Damn, now I have to figure out
>> how to "leapfrog" a file from history into the working tree without
>> overwriting the index; that's occasionally useful.
Show 8 quoted lines
> ... and indeed it is useful in some rare cases.  Either
>
>    git diff <tree-ish> <pathspec> | git apply -R
>
> or
> 
>    git checkout <tree-ish> <pathspec> &&
>    git reset <pathspec>
The former would work, and thanks for the idea, but for a single file
I'd probably do one of
	git show <tree-ish>:<path> > <path>
	git cat-file blob <tree-ish>:<path> > <path>

The checkout/reset wouldn't work in the case I'm thinking about, which is when I want to import a small piece (say, helper function that got deleted) from an old or other-branch version of a file. I.e. a partial revert or cherry-pick.

If I had some current changes to merge with, I'd stage them, pull the *other* version into the tree, and use something like git-gui to add the hunk I want to the staged version.

The whole point is that the staged state is important and I don't want it overwritten.

There are other ways, of course:
- Show or cat-file the other version into a temporary file and manually
  copy and paste.  No need to stage anything.
- Commit the current change, then add the additional changes and amend the
  commit.

← back to recent threads