1

As I understand it calling "git reset --hard" should undo any changes on my computer. However after running it, I have 1,459 files on my system Git says I need to add or merge to the branch I am working on.

How can I have it throw away all changes on my computer (I do want to keep the changes I have pushed up already)?

If it's relevant, this occurred because Visual Studio ran out of memory half way through switching to another branch. So I think it left files from the one branch on my system but marked itself as on the switched to branch.

David Thielen
  • 28,723
  • 34
  • 119
  • 193
  • Call `git reset --hard origin/master` or using another actual branch reference. – poke Dec 09 '17 at 13:05
  • @poke The branch I am on is named support-15.2 so would I then run "git reset --hard origin/support-15.2"? And that then resets whats on my computer but does not in any way change anything pushed to any branch - correct? – David Thielen Dec 09 '17 at 13:08
  • Yes, of course `origin/support-15.2` assumes that you have pushed that branch previously under the same name to your remote repository. – poke Dec 09 '17 at 13:11
  • @poke - Did that, still shows all these changes it wants me to check in. Is it possible VisualStudio put it in some weird state that I can't get out of? What if I delete the root folder holding everything and then start over pulling everything down? – David Thielen Dec 09 '17 at 13:30
  • What kind of changes are that even? Try `git diff` to see what the changes are. – poke Dec 09 '17 at 13:53
  • @poke it's a ton of files it wants to add and a bunch more it wants to change. 1,495 changes in total. – David Thielen Dec 09 '17 at 15:07
  • But what kind of changes are there? – poke Dec 09 '17 at 15:13
  • @poke Many of the files in our project. .cs, .java. .resx, etc. – David Thielen Dec 09 '17 at 18:41

1 Answers1

2

TL;DR

Try removing the index (remove the file .git/index), then running git reset --hard origin/support-15.2. This isn't a normal thing to do, but your situation is not normal either.

Description

It sure sounds like Visual Studio goofed up the "disk full" error-handling.

As another recent Q&A noted, when we work in Git (without some IDE in the way), we have three main parts of interest:

  • The repository itself, usually hidden inside a directory named .git (but not when using a --bare repository).

    The repository stores objects (it's a sort of key/value database), plus some ancillary data, most significantly a mapping from names—which Git normally presents as "branch B" or "tag T", but the general format is reference names—to object hash IDs, such as the big ugly commit hashes. Since the hashes appear random, Git (and we) need a way to turn a consistent, sensible name like master into the hash ID of the commit to treat as the most recent commit on a branch. (This mapping changes over time, as we add more and more commits.)

    Objects inside the repository are stored in a special Git-only format. They are also read-only, once created: an object is either there, containing the same value it had when it was saved into the key/value store initially and forever; or it's not there at all. If one runs out of disk space, this database must maintain this same property (objects must still be all-or-nothing).

    The name-to-object-ID mappings are read/write, since they do change. Running out of disk space could cause you to lose some mappings, which is significant since it's these mappings themselves that make objects reachable.

  • The work-tree (or working-tree, or working directory, or various additional names, all for "the place where you do your work").

    Files in the work-tree have their normal everyday form, and are read/write, like normal everyday files.

  • The index, also called the staging area and the cache.

    The index is, in a sense, entirely unnecessary, but it's how Git gets much of its speed. It also enables a bunch of tricky items like git add -p. The actual implementation of the index is as a file, .git/index, but the format and contents of that file are nontrivial (and now there's something called a split index where the index is in more than one file; and when you use git worktree add, you get one index per work-tree, so you should not assume .git/index anyway).

    The index, like the work-tree, is necessarily read/write. It has several roles: it not only lets you stage items for commit (via git add <file>), it also caches information about all the work-tree files. This is why it has the names staging area and cache. The cached information lets git status go very fast—but assumes that the index has been correctly adjusted based on the contents of the work-tree, and vice versa.

A normal git checkout and series of git adds keeps this cache data up to date, so that the index and work-tree match each other. I suspect that after running out of space, VS and/or the underlying Git code got the two out of sync. If they got them sufficiently badly de-synchronized, it's possible that even an ordinary git reset --hard is not getting them back in sync.

In this case, as long as you have no staged modifications, no in-progress merges, and the like, it's safe enough to remove the index entirely. This loses all the cached information about the current commit and about the current work-tree contents, which forces Git to rebuild it all from scratch. A subsequent git reset with either --mixed or --hard will do this rebuilding. With --mixed, Git will make index entries for files in the target commit of the git reset,1 looking at what's in the work-tree, without touching the work-tree. With --hard, Git will make these index entries and at the same time re-set the work-tree files to match the target commit. If some work-tree files are left over from a half-conversion from some other commit hash ID, making them all match the hash ID to which origin/support-15.2 resolves is probably the right thing to do.

(As always, if you're not quite sure, you can save the work-tree contents somewhere out of the reach of Git itself, then use whatever tools you like to compare the saved tree with what Git extracts.)


1The target is what I call the commit argument to git reset:

git reset [--type] target (where type is one of soft, mixed, or hard)

The Git documentation calls this a <commit>, but there are lots of commits. If you specify a raw hash ID, this must be the hash ID of a commit, and Git will change the current branch name—whatever branch name that is—so that it names that specific hash ID as its current commit. If you specify a name, such as origin/support-15.2, Git just resolves that name to a commit hash ID, and then proceeds as if you'd given it a commit hash ID.

With --soft, Git changes the current branch to point to the target commit, and stops. With --mixed, Git goes on to modify the index as well, and then stops. With --hard, Git updates the work-tree while modifying the index.

torek
  • 448,244
  • 59
  • 642
  • 775
  • This does nothing to what's pushed up already, just resets everything on my computer - correct? If so, this is exactly what is needed as I have nothing changed. This occurred when I was trying to change branches after successfully pushing up my changes. – David Thielen Dec 09 '17 at 18:45
  • Yes: primarily only `git push` and `git fetch` actually talk with any other Git (though there are some other special cases, e.g., `git ls-remote` or `git remote show` will sometimes call up another remote for a brief conversation about their references). Fetch is a "from them, to us" operation so it does not change anything anywhere else either; it's only `git push` that affects another Git, and even then it's really *I give you these database objects, then request (default) or command (force-push) that you set a name to remember them by* and even then the other Git can refuse commands. – torek Dec 09 '17 at 20:45
  • Note that `git clone` is `git init` followed by `git fetch` (and a few other local Git operations), and `git pull` is `git fetch` followed by (usually) `git merge` or `git rebase`—so virtually every Git-to-Git interaction is defined by fetch or push. – torek Dec 09 '17 at 20:46
  • Ok, did the delete and reset. Then tried to checkout a different branch and am getting "The following untracked working tree files would be overwritten by checkout". Any idea what next? (I think VisualStudio really did a number on Git.) – David Thielen Dec 10 '17 at 13:34
  • Success!!! I went and deleted the files it didn't want there and then it all worked. Thank you VERY much. – David Thielen Dec 10 '17 at 13:39
  • I spoke too soon - it still wants me to add 1,441 files after all this. Is there a way to have it clear out the files that are not in the branch? I don't want to ignore them because in the newer branch these are valid files. – David Thielen Dec 10 '17 at 13:47
  • 1
    If it wants you to add those files, they either don't match the index, or aren't in the index at all. (You can run `git ls-files --stage` to see what's in the index, but in a large project the output is too large to be useful for anything other than another computer program, or some spot-checking.) These next options are much more dangerous since they remove *untracked* files, but: the next option is to use `git clean`, with either `-f` or `-f -x`. (You can use it without `-f` to see what it would remove.) Or, get the list of files it would remove, and move them out of the way manually. – torek Dec 10 '17 at 19:33
  • I want to remove the files from my system (they shouldn't be there). But don't want to remove them from the branch on the server. Does git -f clean just remove the files from my computer? – David Thielen Dec 10 '17 at 23:21
  • Yes: as I noted above, about the only thing that affects the *server* is `git push`. Moreover, without `--force`, the server will only *accept* requests that merely *add to* the commit graph. It's important to remember that Git doesn't store *files*, it stores *commits* (which then store files, as a snapshot). Any existing commits are (mostly) permanent and (entirely) unchangeable, so once the commit exists, those files are stored forever, unless you remove the commits themselves, which is by design difficult. – torek Dec 10 '17 at 23:53
  • First off, thank you very much for helping me with this. VisualStudio clearly really messed things up. Ok, did "del .git/index", "git reset --hard", "git clean -f", and then "git checkout master" - and got "error: The following untracked working tree files would be overwritten by checkout:" Any idea what to do next? thanks - dave – David Thielen Dec 11 '17 at 01:42
  • `git clean` will have removed files that are *currently* untracked, except for ones that are ignored, so this suggests that the ones being complained-about here are both untracked *and* ignored (listed in `.gitignore`). In this case you can remove those files as well, either manually, or with `git clean -fx` (which removes both regular untracked files, and untracked-and-also-ignored files). After that `git checkout master` should work. – torek Dec 11 '17 at 05:59
  • I did the 3 steps again with "git clean -fx" and then on "git checkout master" same error again. Is there anything else I can do? thanks – David Thielen Dec 11 '17 at 16:56
  • Wow. I've never seen a repository get that broken. (On the other hand, I avoid Windows; the only Mac software that will do that sort of thing to a Git repository is Dropbox.) We've already eliminated all the index based issues by removing the index, so at this point I'd have to experiment directly on the system to figure out what's going on. – torek Dec 11 '17 at 17:52
  • What if I just delete the entire folder and then go rejoin that Git repository again? Also, it was Visual Studio that ran out of memory and so I think it managed to do so at a very vary bad point. – David Thielen Dec 11 '17 at 21:00
  • That (switch to new copy) seems like the best thing at this point, since the existing copy seems to be terribly damaged in some undefinable way :-) – torek Dec 11 '17 at 21:02