Git: Commands you need to know when you are messed upšØ! [Part-1]
First things first, this is not an another āKNOW THESE GIT COMMANDS TO BE MORE PRODUCTIVE!ā or āGIT COMMANDS LIST THAT EVERY DEVELOPER MUST KNOW!ā
We got enough of those in the internet. What we donāt have is: āCOMMANDS YOU NEED TO KNOW WHEN YOU ARE MESSED UP!āš
If anyone say they never messed up in Git, Donāt trust them! Unless itās Linus Torvalds or Jeff Dean.
Git is every junior developerās nightmare when they are working in a group. Especially with the people who are experienced and know their shit. Because,
- If you make some mistake, you canāt just delete
.git
from the root folder and pretend everything is ok. (Ohhhhhā¦ Iām guilty of doing thisš¤«. I hope my boss doesnāt read this articleš°) - When they see your mess, they might not think you are dumb or inept of doing work but your conscious will think about it constantly. For people like me, the brain suddenly remembers all the embarrassing things even done during the childhood at 2 A.M.
The above two points makes everyone nervous before making every commit and pushing to the remote.
But sometimes, that extra care we take wonāt even save us and sooner or later we do something stupid, which we want to hide desperately from our peers and manager. So, obviously we turn to Google, frantically searching for some commands for hours to bury the body and pretend everything is normal.
Ok. Ok. Fineš. Iām gonna stop blabbering and get to the stuff you are here for before you feel sleepyš„± and leave šš»āāļøšØ.(Sometimes, I get distracted š . Iām known to talk about ā¦ā¦ā¦ā¦.. See, Iām again getting distracted š¤¦š»š¤·š¼)
Actually, itās a two part series. I donāt want to fry your brains with too much information in one day(Yes, I do care for youš„ŗ). So, this part mainly concentrates on commands which has effects on your local working directory. The next part will be on remote repo like on reverting, deleting, editing commits etc. which requires more of your attention while learning.
Whatās in this?
- Accidentally staged files for commit?
- Accidentally tracking files or donāt want to track a file anymore?
- Want to delete untracked and ignored files?
- Want to revert un-committed changes?
So, letās see some of the things we can mess up and how to clean it without jeopardizing our whole work.
Accidentally staged files for commit?
Have you ever executed the below command and wanted to undo?
git add .git add *git add -Agit add -u
(Yes, there are 4 variations! Checkout this SO post to know everything about them)
Now, you got multiple commands to do that
git reset <filename>
orgit reset HEAD <filename>
git rm --cached <filename>
git restore --staged <filename>
ā Added in Git v2.23 (August 2019)
Actually, these three commands will be recommended to you by the Git itself when you add the files to index or to staging area. If you are using Git v2.23+ then you will be suggested git restore --staged <filename>
instead of git reset HEAD <filename>
I guess the new command git restore --staged <filename>
is self explanatory. Let me explain the other two commands.
git reset HEAD <filename>
: reset command resets the INDEX or STAGING AREA to previous commit i.e., the position of the HEAD in the Git.
git rm --cached <filename>
: removes the files from the INDEX or STAGING AREA completely. If you deleted a file which is already in the repo and run this command, the file will be removed from the INDEX or STAGING AREA and also from the repo in the next commit. That said, if you used git rm --cached
on a new file that is staged, it would basically look like you had just un-staged it since it had never been committed before.
Now, why does Git suggest two different commands?
For git reset HEAD <filename>
or git restore --staged <filename>
to work, there should be at least 1 commit in the repo. Because git reset HEAD
command is saying to reset the files to the HEAD but HEAD is not created until the first commit is made.
But you can still run it by excluding Head
option and it works perfectly
WHYš¤?
In old versions of Git, the above commands are equivalent to
git reset HEAD <file>
andgit reset HEAD
respectively, and will fail ifHEAD
is undefined (because you haven't yet made any commits in your repository) or ambiguous (because you created a branch calledHEAD
, which is a stupid thing that you shouldn't do). This was changed in Git 1.8.2, though, so in modern versions of Git you can use the commands above even prior to making your first commit:āgit resetā (without options or parameters) used to error out when you do not have any commits in your history, but it now gives you an empty index (to match non-existent commit you are not even on).
Note: I will use git revert
instead of git revert Head
in the following article.
You can read more about git revert
vs git revert HEAD
in the below SO posts
So, if you observe next time, Git suggests git rm --cached <filename>
when you donāt have initial commit and when you have at least one commit, Git suggests git reset HEAD
or git restore --staged
Now, you might be scratching your head thinking: āI understand everything but still I donāt understand anything and how to use itā. Donāt worry, been there, done that. So, how about we look at those commands from a different perspective with a mess up to understand it actually.
git reset
git reset HEAD <filename>
: You are lazy and you commit only at the end of the day by staging files and committing multiple times to organise the work.(Yeahā¦ Sometimes I do this tooš)
Entire day you have added/edited many different files like above. Now you want to group server config files in one commit and database config files in one commit. But accidentally you have executed git add .
Now, you see that and want to remove the db config files and any other files to commit only server config files
Now, you can commit server config files and then commit db config files in separate commits.
Doing this doesnāt delete anything from your local working directory.
git rm ā cached
git rm --cached
: Now you see why Git suggests this only when there are no commits. Tip: Donāt use this to unstage files without knowing completely about this!
I repeat! The behaviour of git rm --cached
and git reset
is same when there are no commits!
First, letās see what is the output when we run git rm --cached
with at least one commit
Can you comprehend what happened by just looking at the above image? If not, donāt worry. Thatās why everyone is here. Nobody is gonna judge you.
git rm --cached
deletes the file from the INDEX or STAGING AREA but you still have the file in your filesystem with all itās content and also Git removes it from itās tracking.
In simple terms, git rm --cached
makes it look like you copied a new file into your folder.
Another effect is, this command removes the file from the repo. If anyone pulls the code after you have run git rm --cached <file> && git push
, then that <file>
will also be deleted from their local filesystem. Because it only exists in your local filesystem as an untracked file.
Iām gonna give you an embarrassing mess once I made so that you can get an intuition when to use this.
Story Time: Once, I initialised my node project and forgot to add .gitignore
and ran my alias to add everything and push it to the remote server.(Yeah I know how dumb it is and my brain is known to glitch out sometimes) Doing that pushed my entire node_modules
to the remote server. So, what can I do?
- Add
.gitignore
, runrm -r node_modules && git add . && git commit && git push && npm install
OR - Add
.gitignore
, rungit rm --cached -r node_modules && git commit && git push
The second option is more nice and short. When you run git rm --cached -r
( -r
stands for recursive)it removes the node_modules
from the INDEX or STAGING AREA and also removes it from itās tracking files but it is not removed from your local file system. So, you donāt have to install it again like in first option. Now, whenever you run git add .
, it checks .gitignore
and you wonāt see node_modules
in your git status
as untracked files.
Doing this doesnāt delete anything from your local working directory.
Summary:
Always use git reset <filename>
to unstage some files or git reset .
to unstage all files. This works even if there was no prior commit in the repo.
Use git rm --cached
only when you want to remove the file from the repo but keep a local copy in your local file system. Donāt use it interchangeably with git reset
!
If you want to know from more why there are two ways, checkout this SO post:
Accidentally tracking files or donāt want to track a file anymore?
This is where git rm --cached
is useful actually.
Like I said before, (read the Story Time above if you skipped it to understand)
Ok Fine. Iām gonna say it again in a summary what it does for some lazy people.
git rm --cached
command actually removes it from the INDEX or STAGING AREA and also deletes it from the repo. So, git push
actually removes the file from the repo and any subsequent git pull
by others will also delete that file from their local file systems(not yours. You actually have your copy).
To remove/untrack everything: git rm --cached .
To remove/untrack one file: git rm --cached <filename>
To remove/untrack a directory: git rm --cached -r <directoryname>
Doing this doesnāt delete anything from your local working directory.
Note: git rm
is not same as git rm --cached
. git rm
actually deletes the file from your local file system, repo, and others file system. So, be careful not to forget ā -cached
option!
Want to delete untracked and ignored files?
Suppose you added some new files to the working folder and later decided to remove those. There are too many files to run rm file1 file2 file3 ....
.
Another scenario is, you want to delete the temporary files which are mentioned in .gitignore
Doing this deletes from your local working directory.
Be careful!
You can run git clean
From man pages:
Cleans the working tree by recursively removing files that are not under version control, starting from the current directory.
Normally, only files unknown to Git are removed, but if the -x option is specified, ignored files are also removed. This can, for example, be useful to
remove all build products.
As said, it removes recursively from current directory(where you run that command). So, to remove all untracked and/or ignored files/directories, run it from the root of the project directory.
To remove untracked files: git clean -f
To remove untracked and ignored files: git clean -xf
To remove untracked directories and files: git clean -fd
To remove untracked and ignored directories and files: git clean -xfd
Why -f, --force
?
If the Git configuration variable clean.requireForce is not set to false, git clean will refuse to delete files or directories unless given -f, -n or
-i. Git will refuse to delete directories with .git sub directory or file unless a second -f is given.
This command actually deletes files and directories physically. So, you might be intimidated by running this command and hope you are not doing wrong by deleting ānot-to-deleteā files/directories.
If you are scared(like me), you can use this command in āInteractive Modeā by running git clean -fi
You can select option 4 and Git asks whether to remove it or not for every file.
From man pages:
Want to revert un-committed changes?
You have run git commit
and later edited some files in the project. After hours of work, you want to delete the changes or un-edit those files to previous staging version.
You can use: git checkout
Yeah. You heard that right. The innocent git checkout
which we often use to change branches can actually cause some destruction if used improperly.
Doing this deletes the work from your local working directory up until previous commit.
Be careful!
From man pages:
git-checkout ā Switch branches or restore working tree files
Updates files in the working tree to match the version in the index or the specified tree. If no paths are given, git checkout will also update HEAD to set
the specified branch as the current branch.
Just a side note: You might have seen git restore <filename>
as a suggestion by Git in the above image(that might be because Iām using latest version v2.25.0). Donāt use that because it is experimental as it is mentioned in the man pages
Moving on ā¦.
git checkout .
can only revert any modified file to previous staging version but not to previous commit!
In the above image, the previous staging version of somedbconfigfile1
is empty. So doing git checkout .
reverted the contents First message to commit
to empty.
But you might think āIn the above image,
git checkout .
reverted to previous commit right?ā
Now, letās see another example to really understand what reverting to previous staging version means.
Git doesnāt keep history of INDEX or STAGING AREA. It only has two states.
- Whatās there in INDEX
- Whatās not there in INDEX
If you run git add
, Git replaces/adds itās āWhatās there in INDEXā with new āWhatās not there in INDEXā data. So, you cannot iterate over your INDEX history like with your COMMIT history and restore.
So, as there are no INDEX versions left, Git gets the copy from previous commit which is somedbconfigfile1
is empty.
git checkout <filename>
command can only be run on un-staged files. Thatās the reason I have used git reset .
to unstage the file in the above image.
I know thatās a lot of things to absorb about the innocent git checkout
.
I think itās enough for today. Donāt forget to comeback to check the part 2! If your memory is as bad as mine, Hit that FOLLOW š
Other useful articles
If you still got some energy to burn, then read this one about git reset
. It is a must! Complete guide for git reset
: https://git-scm.com/book/en/v2/Git-Tools-Reset-Demystified
If anyone finds another way to do the same things with different commands, Congratulations! Do share and enlighten our fellow developers! If you donāt know then yes, we can do same thing using different commands in Git. Donāt trust me? Look šš»šš»šš»
Disclaimer: Before anyone calls me out for not giving credits for memes, I have a dope stash of them saved on my phone from all over the internet since the ādot com bubbleā. So, if you need some, mail meš
Liked it? Then šš»šš»šš» it and share it!