1

I have accidentally typed git reset --hard origin/<branch> (to be precisely, I mixed up some of my aliases). The problem is I haven't pushed my commits and they are now lost. Can I get them somehow back?

0

2 Answers 2

4

Do git reflog, then checkout or reset to the SHA1 before the erroneous reset. (Maybe fix your aliases first.)

1
  • Well after some commands and changes it did work eventually. Thank you!
    – Niklas
    Commented Jul 13, 2014 at 16:45
2

I see you've recovered from this, but, here's the way to think about it. You must view your commits in terms of the "commit graph": a set of commits (which I'll label with single letters) where each commit points back to its parent commit(s). A chain with one branch and merge in it looks like this:

         C <- D
       /        \
A <- B            G <- H <- I
       \        /
         E <- F

All the arrows point back (or for the angled ones, back-and-up/down, where I don't have arrows available so I just use \ and /).

Branches, like master or devel, are simply labels with arrows pointing to one of the commits in the graph. (The special label HEAD normally just contains the name of another label, like master, so I write it as HEAD=master. That lets you know, if there are additional labels, which one is the "active" one. If we had color we could make it a bright red or green or whatever, but with text, we settle for HEAD=.) So with two branches, we might have this:

         C <- D
       /        \
A <- B            G <- H <- I   <-- HEAD=master
       \        /
         E <- F                 <-- feature

This tells us that we're on branch master, that branch master names the commit labeled I (really some big ugly SHA-1 like d1574b8...—that's why I use single letters to draw the graph!), and that branch feature names the commit labeled F.

What git reset does—well, it has several jobs, but what it does with the labels—is to change which commit the label points to. Since you're on master, if you git reset feature (we'll ignore --hard for now), you're telling git: "Erase the label master from wherever it is now and make it point to the same commit as feature, i.e., commit F." This does not change the commit graph itself, just moves the label:

         C <- D
       /        \
A <- B            G <- H <- I
       \        /
         E <- F                 <-- feature, HEAD=master

Commit I is still in there (is retained in the repository) for a while (30 to 90 days by default) via of the "reflog", so you can find the SHA-1 for commit I through the reflog, up until those reflog entries expire anyway, and make your label point back to it, using another git reset.1

(The "remote branch" label origin/master is just like your own, local, branch labels, with one big difference:2 it updates when you use git fetch or git pull to get new stuff from origin, rather than when you do things locally. So it lets you locate commits within the commit graph, just like your own branches. That's why you can use those names with git reset: reset just needs to identify the commit to which you want your current branch label to point.)


The reset command also has a bunch of mode options: --soft, --mixed, --hard, and some more esoteric ones. These affect its other job. Besides moving a branch label from one commit to another, git reset can update your index, and can also update your work-tree. The --soft argument tells it: "Don't update either one! Leave both alone!" In this case, the only thing git reset accomplishes is to move the label.3

The default, --mixed, tell it: "Update the index, but leave the work-tree alone." In this case, git reset moves the label, and updates the index to match the commit you moved to.

With --hard, you tell it: "Update the index, and make the work-tree match." In this case it can erase work in progress, since it makes the work-tree match the index which it makes match the target commit.

Note that if you say git reset HEAD—i.e., you tell git reset to move the current branch from wherever it is now, to wherever it is now—the label change is actually no change at all. If you're on branch master, you tell git to erase the label master and then redraw it to wherever master pointed before it just erased it. It winds up pointing exactly where it pointed before. In this particular case, the only real effect is any index and/or work-tree changes you ask for.

You can limit the index and/or work-tree changes to specific files, as well. In full generality, you do this with git reset branch -- path.4 That means: "move current branch to branch, and also do a --mixed reset on the given path." If branch is HEAD, the branch doesn't move, but the index entry for path is made to match the current commit. In other words, any new version you git add-ed is un-done. Hence git reset HEAD -- foo.txt tells git: don't move anywhere, but restore foo.txt in the index to the current commit, un-staging foo.txt.

If you leave out the branch part, it defaults to HEAD, so that's why git reset is how you undo a git-add. The work-tree version remains in place but the index entry has been re-set. (More precisely, the index entries for the files you named, if you named any, or for all files.)

With --hard, it wipes out changes in files, by resetting index entries and making the work-tree match them. And it still defaults to using HEAD, so that's why git reset --hard (with no additional arguments) gets rid of all uncommitted changes (and hence loses your work, so you really did mean to get rid of them, we hope...).


1In fact, you can do it without using the raw SHA-1: git reset HEAD@{1}, for instance. Reflog names work wherever commit-IDs are needed.

2And a bunch of smaller differences, e.g., you can't make HEAD name a "remote branch". If you try you just get a "detached HEAD", which is essentially a local branch with no label.

3This is useful for doing "squash commits", but—since there's git commit --amend—not much else anymore. In the past, you could use it to back up one commit and then make a replacement, but now you can do that more easily with --amend.

4Actually, where I have branch, you can use any reference that can be resolved to a commit-ID. This is why the reflog-name in footnote 1 works, for that matter.

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