Those who are using Git but still new to the concept will no doubt have realised that files are treated differently to normal source control tools, and that they transition between different known states, according to git. I am going to try and explain these states, and give commands on how to transition between them.
I needed these commands recently because I wanted to reset my changes and hadn’t really thought about how these states affect what it is that gets ‘reset’.
TL;DR: you can use these commands to perform different state transitions
git add <file>- add all changes to a file to stage
git rm --cached- unstage a new file/stage a delete for an existing file, keep local filesystem copy
git reset <file>- unstage, keep local changes
git checkout .- reset unstaged changes, keep staged
git reset --hard- reset all staged changes, reset all unstaged changes of tracked files(new unstaged files not removed)
git clean -f(use
git clean -nto dry run first) - removes all untracked, unstaged changes
git clean -f -x(use
git clean -n -xto dry run first) - as above, also removes ignored files
Skip to summary for coverage.
Given a file, git will see it as one of several states: Ignored, New-Unstaged, New-PartialStaged, New-Staged, Existing-Clean, Existing-UnstagedChanges, Existing-PartialStaged, and Existing-Staged. First I will work through the states and some of the transitions, Then ill list the commands to achieve these transitions. (There is also a Deleted-Unstaged and Deleted-Staged, but ill leave those as an exercise for the reader.)
You can think or the file as having 3 versions, one commited, one on the file system, and one in a staging area. The Unstaged/PartialStaged/Staged variations just refect whether or not the file system version or staged version has changes from the commited version. Where a change from the commited version is in both the file system and the stage it is staged. When it is in the filesystem but not the stage it is unstaged. When all changes are staged, we are in the Staged state, when all changes are unstaged we are in the Unstaged State, and when it is a mix, we are in the PartialStaged State. Hopefully that helps when looking at the state transitions below.
A file is in the Ignored state when it is covered by an explicit or wildcard entry in your .gitignore, as long as it isn’t already checked in to source control. To transition into this state, Add a new file that is covered by ignore. If you have have staged changes you need to unstage them but keep the changes on disk. If you have a checked in file, you need to delete the file from git but keep the changes on disk.
When you add a new file, it will start in the New-Unstaged state, as long as it isn’t being ignored by .gitignore. By staging the whole file, you get to the New-Staged, or you can stage some of the lines in the file to be in the New-PartialStaged state.
When you have an existing file, it starts in the Existing-Clean state. making changes puts it into the Existing-UnstagedChanges, and staging these produces the Existing-PartialStaged and Existing-Staged, depending on if some or all changes are staged.
Hopefully by now you have learned how to move changes forward with
git add <filename> and how to commit them with
git commit, so next we need to get out head about these 3 commands to go the other way.
So with out uncommited changes, we want to do one of 3 things: reset both stage and filesystem files to the commited version(or remove completely if new) or unstage the changes but keep them on the filesystem (or un-add if new), or with untracked files(ignored) we want to delete them completely.
Lets start with the unstaging. We have a new file staged, and we want to unstage, but keep the changes on disk. Why? well we either want to have it ignored(staged accidentally) or it isn’t going in this commit. Either way, we use
git reset <file> or if it a new file we could also use
git rm --cached instead.
--cached means keep the filesystem changes. Note that if you have a new staged file that is now ignored, or a commited file that you want to become ignored, but deleted, then
git rm --cached is the coomand for the job, as long as you already have your .gitignore updated as well.
Now if have unstaged changes and we want to reset them on disk but leave staging alone? We can use the command
git checkout .. This keeps all staged changes but resets any unstaged changes. The only issue with this command, is that if you have new files, but not staged, they stick around, while new staged files get removed.
And if we want to remove all staged and local changes of tracked(unignored, non new) files?
git reset --hard. Note that any new unstaged files are kept here also. You could use
git add . to stage new files, then
git reset --hard, or you could try this next command after reset instead.
To remove unstaged files, you can use
git clean -f. Watch out though, this cannot be undone, so best to run
git clean -n to test it first. This will only remove new unstaged files(basically what the above two commands miss). As a side note, if the add is staged, this won’t revert any unstaged changes to the file.
One final category is the ignored files. You know, all those artifacts, bin files, test results, .user files, package artifacts etc. These don’t go away in a hurry, whether its changing branches, or resetting changes, these files stick around, and some of them take up space over time. These can be removed using
git clean -f -x, again remembering to run
git clean -n -x first so you don’t lose something you didnt mean to.
One last one for good measure: if you want to keep all your changes for later, but still reset your files, use
git stash -u. The
-u here is shorthand for the
--include-untracked command. This quickly gets rid of all but ignored files in your repo, making it as clean as necessary, and even keeps all your changes stored away to bring them back with
git stash pop. Note though that the staged status of your files might not be the same afterwards. It will collapse together the stages and unstaged changes for each file, and put them all staged or all in unstaged. (This seems to be based on whether it is new or not, but more investigation needed).
So lets summarize all these removals a little simpler based on the way I found it somewhere else online:
- Type 1: Ignored File
- Type 2: Unstaged New File
- Type 3: Unstaged Changes (on new staged, or existing file)
- Type 4: Staged New File(with or without unstaged changes)
- Type 5: Staged Changes on Existing File
git checkout .- Undo Type 3 only
git reset --hard- Undo Types 3, 4 and 5 only
git clean -f- Undo Type 2 only
git clean -f -x- Undo Types 1 and 2 only
git stash -u- Undo Types 2,3,4,5, and creates a stash
git stash -all- Undo All types, and creates a stash(stash includes ignored)
So to nuke it all?
git reset --hardthen
git clean -f -x- All, no recovery
git reset --hardthen
git clean -f- All but ignored, no recovery
git stash -all- All gone, with recovery
git stash -u- All but ignored with recovery