49

While reset and checkout have different usages most of the time, I can't see what difference there is between these two.

There probably is one or nobody would have bothered adding a --hard option to do something the basic checkout can do.

Maybe there is a difference is the way you will see the history?

5
  • 1
    I covered this in an update to my answer to one of your previous questions - look at the ascii art near the top, particularly where it says "Digression: ..." (as much as I'd love more rep for re-answering it here)
    – Cascabel
    Commented Mar 29, 2010 at 22:02
  • I do think you can post your answer here and gain rep from it. If somebody search for this particular knowledge, he won't find the other post. This one aim a very specific topic, and it deserve to have it's separate page. BTW, it seems you are my Git mentor :-) harigato, senseï !
    – Bite code
    Commented Mar 29, 2010 at 22:10
  • 1
    But is I get it, the difference is that reset moves the branch and not checkout.
    – Bite code
    Commented Mar 29, 2010 at 22:12
  • @e-satis: Yeah, you're right. The answer's here now, and I'd retract my close vote if I could! And you're quite welcome - I'm glad to be using all the knowledge I've accumulated outside my own work.
    – Cascabel
    Commented Mar 29, 2010 at 22:14
  • See also progit.org/2011/07/11/reset.html
    – VonC
    Commented Aug 19, 2011 at 7:33

3 Answers 3

65

This answer is mostly quoted from my answer to a previous question: git reset in plain english.

The two are very different. They result in the same state for your index and work tree, but the resulting history and current branch aren't the same.

Suppose your history looks like this, with the master branch currently checked out:

- A - B - C (HEAD, master)

and you run git reset --hard B. You'll get this:

- A - B (HEAD, master)      # - C is still here, but there's no
                            # branch pointing to it anymore

You'd actually get that effect if you use --mixed or --soft too - the only difference is what happens to your work tree and index. In the --hard case, the work tree and index match B.

Now, suppose you'd run git checkout B instead. You'd get this:

- A - B (HEAD) - C (master)

You've ended up in a detached HEAD state. HEAD, work tree, index all match B, same as with the hard reset, but the master branch was left behind at C. If you make a new commit D at this point, you'll get this, which is probably not what you want:

- A - B - C (master)
       \
        D (HEAD)

So, you use checkout to, well, check out that commit. You can fiddle with it, do what you like, but you've left your branch behind. If you want the branch moved too, you use reset.

1
  • 7
    +1 as usual. This thread (marc.info/?l=git&m=120955970704567&w=2) also added one side-effect: if you are in the middle of a merge (e.g. when there are merge conflicts, or after git merge --no-commit), git reset --hard forgets about the merge, but git checkout -f does not; hence, a git commit after the latter would create a merge commit, which is usually not what you want.
    – VonC
    Commented Mar 29, 2010 at 22:18
15

If documentation provided with Git doesn't help you, take a look at A Visual Git Reference by Mark Lodato.

In particular if you are comparing git checkout <non-branch> with git reset --hard <non-branch> (hotlinked):

git checkout master~3
(source: github.com)

git reset --hard master~3

Note that in the case of git reset --hard master~3 you leave behind a part of DAG of revisions - some of commits are not referenced by any branch. Those are protected for (by default) 30 days by reflog; they would ultimately be pruned (removed).

6

git-reset hash sets the branch reference to the given hash, and optionally checks it out, with--hard.

git-checkout hash sets the working tree to the given hash; and unless hash is a branch name, you'll end up with a detached head.

ultimately, git deals with 3 things:

                   working tree (your code)
-------------------------------------------------------------------------
                     index/staging-area
-------------------------------------------------------------------------
      repository (bunch of commits, trees, branch names, etc)

git-checkout by default just updates the index and the working tree, and can optionally update something in the repository (with the -b option)

git-reset by default just updates the repository and the index, and optionally the working tree (with the --hard option)

You can think of the repository like this:

 HEAD -> master

 refs:
    master -> sha_of_commit_X
    dev -> sha_of_commit_Y

 objects: (addressed by sha1)

    sha_of_commit_X, sha_of_commit_Y, sha_of_commit_Z, sha_of_commit_A ....

git-reset manipulates what the branch references point to.

Suppose your history looks like this:

           T--S--R--Q [master][dev]
          / 
   A--B--C--D--E--F--G [topic1]
                   \
                    Z--Y--X--W [topic2][topic3]

Keep in mind that branches are just names that advance automatically when you commit.

So you have the following branches:

 master -> Q
 dev -> Q
 topic1 -> G
 topic2 -> W
 topic3 -> W

And your current branch is topic2, that is, the HEAD points to topic2.

HEAD -> topic2

Then, git reset X will reset the name topic2 to point to X; meaning if you make a commit P on branch topic2, things will look like this:

           T--S--R--Q [master][dev]
          / 
   A--B--C--D--E--F--G [topic1]
                   \
                    Z--Y--X--W [topic3]
                           \
                            P [topic2]

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