3

git reset HEAD~1

I was under the impression that the ~1 meant: start at the HEAD, follow 1 link, and set the HEAD tag to that new commit node. I was expecting

git reset HEAD~2

to follow 2 links and then set the HEAD tag. However, if I try it, I get an error:

$ git reflog
c83bbda HEAD@{0}: reset: moving to HEAD~1
44c3540 HEAD@{1}: commit: you will be garbage soon
c83bbda HEAD@{2}: reset: moving to HEAD~1
aee7955 HEAD@{3}: commit: back to 4 lines
c83bbda HEAD@{4}: reset: moving to HEAD~1
19ec1d5 HEAD@{5}: commit: 3 lines
c83bbda HEAD@{6}: reset: moving to HEAD~1
a049538 HEAD@{7}: commit: added new line
c83bbda HEAD@{8}: commit (initial): first commit


$ git reset --hard HEAD~2
fatal: ambiguous argument 'HEAD~2': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'

Apparently I was mistaken, but the doc page for git reset is not very useful in clarifying this. So, what does the ~1 mean and why do I need it?

cfischer
  • 24,452
  • 37
  • 131
  • 214
  • 2
    In this particular case, the "unknown revision" part means that `HEAD~2` simply does not exist. `HEAD` is commit `c83bbda` according to the reflog, and that's your initial commit, so it has no parents, and `HEAD^`, `HEAD~1`, etc., simply don't exist. – torek Aug 07 '13 at 12:22
  • 2
    Please study the [`gitrevisions(7)` manual](https://www.kernel.org/pub/software/scm/git/docs/gitrevisions.html) which explains all these `~` and `^` funny characters. – kostix Aug 07 '13 at 12:36
  • possible duplicate of [What's the difference between ~ and ^ in git](http://stackoverflow.com/questions/14733687/whats-the-difference-between-and-in-git) – kostix Aug 07 '13 at 12:38
  • possible duplicate of [What is a Git Revision Expression?](http://stackoverflow.com/questions/1215814/what-is-a-git-revision-expression) – Joe Aug 07 '13 at 12:41

4 Answers4

5

HEAD~1 is "the first parent of HEAD", while HEAD~2 is "the first parent of the first parent of HEAD, and so on (so HEAD~n for some n is like HEAD followed by n ^ symbols and no numbers). Again, all the specifics are in the git-rev-parse manual page.

Be careful when mixing git reset with "revisions counting backwards from HEAD". git reset will, in general, change the value of HEAD, e.g.:

$ git checkout master    # now on tip of "master" branch
$ git branch save master # copy branch tip to another label, for safekeeping
$ git reset HEAD^        # or git reset HEAD~1

moves HEAD (and master) to its first parent. Another way to name that parent is save^, and yet another is save~1. After the move finishes, though, HEAD now names that parent revision, so HEAD^ names its parent:

$ git reset HEAD^

moves you back another step, so that master and HEAD now name the same commit that save~2 names. This is easy to see using git rev-parse, which tells you the commit-ID that some symbolic-name maps to:

$ git rev-parse save~2 master
0f5a13497dd3da8aff8e452c8f56630f83253e79
0f5a13497dd3da8aff8e452c8f56630f83253e79

At this point, you can restore master to the save-point with:

$ git reset save

which moves HEAD and master back to the saved revision, and then you can delete save safely if you like:

$ git branch -d save

Note that you could save a save-point with git tag too: the only difference between a branch and a tag is the behavior of new check-ins when "on a branch" (tags don't move, branches do) and check-outs (tags put you in "detached HEAD" = not-on-a-branch state, branch names put you in "on-a-branch" state).

torek
  • 448,244
  • 59
  • 642
  • 775
3

See git help revisions or Git - Revision Selection for more details on how to specify a commit:

<rev>~<n>, e.g. master~3

A suffix ~<n> to a revision parameter means the commit object that is the <n>th generation ancestor of the named commit object, following only the first parents. I.e. <rev>~3 is equivalent to <rev>^^^ which is equivalent to <rev>^1^1^1.

This syntax can be used with most Git commands, so it's not specific to git reset.

Chronial
  • 66,706
  • 14
  • 93
  • 99
Joe
  • 29,416
  • 12
  • 68
  • 88
2

git reser --hard HEAD~1 removes the last 1 (or any other number you put) commit from the current branch, like this
git reset HEAD ... removes the last commit
git reset HEAD~1 ... remove the last 2 commits
and so on

Luiz E.
  • 6,769
  • 10
  • 58
  • 98
  • 2
    Almost: `git reset HEAD` won't remove a commit. The default for `reset` is `reset --mixed` which resets the index but does not change the working tree, and in any case, `HEAD` names "where you are now" so this does not move a branch label either. Thus, what it achieves is to undo the effect of any `git add`s and/or `git rm --cached`. (It's also an overstatement to say that it *removes* commits, it just makes the commits *appear* to vanish, by changing the SHA-1 values for branch names. They eventually go away via `git gc`.) – torek Aug 07 '13 at 12:30
1

You get this error message when you run git reset --hard HEAD~2

fatal: ambiguous argument 'HEAD~2': unknown revision or path not in the working tree.

because you're trying to reset your working copy to a commit that doesn't exist. According to your reflog, you have your initial root commit checked out when running this command:

$ git reflog
c83bbda HEAD@{0}: reset: moving to HEAD~1
# etc ...
c83bbda HEAD@{8}: commit (initial): first commit

So according to the reflog above, you're current working copy, i.e. HEAD, is at your first commit, so by doing git reset --hard head~2 from here, you're telling Git to go back 2 commits before your first commit, which of course it can't do because nothing exists before your first commit:

fatal: ambiguous argument 'HEAD~2': unknown revision or path not in the working tree.