14
Visualize Git
Yep, sometimes git can be complicated, but that does not mean necessarily it can not be understood unless you are a git expert. You all probably have used git at some point in your development stages and familiar with the basics. Well today I am here to show you the peculiar aspects of this tool and how we can deal with trouble.
Below representation will be our visual work space.
This is our repository and it has several commits and branches in it. First we have an initial commit (root of our tree) at the top, after which we have 3 commits (added contributors, improvements and refactor), whose parents are the same. As you see, we also have 3 branches, master, feature1, and feature2. We also have HEAD which we will talk about in a bit.
In order to make it more understandable, I will show you how visual representation changes while we execute the corresponding git commands. By doing so, it is going to allow us to notice the relationship between the commands and the bahavior of git.
We will move HEAD from time to time in our visual graph, but what is this magical special word to begin with ? Let me explain, HEAD is nothing more than a pointer in your git workflow, it just marks where you are. That's it. Simply accept HEAD as an arrow pointing to your location on a map. Now in order to mark other locations, we might need a reference point right ? That is for instance, if I need to explain someone where the commit refactor is, I can just say go 2 commits above HEAD and there you will find it. Thus HEAD~1 is like saying "the commit above HEAD" and similarly HEAD~2 is equal to saying "the commit 2 steps above HEAD". By the way HEAD~ is the same as HEAD~1 so no fancy stuff there, we programmers are lazy you know.
In order to prove our point let us execute git checkout feature2
.
HEAD moved to point to the feature2, huh well that was simple enough. Get back to master now, we will continue.
These are amongst the most common misunderstood concepts when starting to learn git. I will simply give an illustration to make it more comprehensible. Imagine you are building a tower of cards, like when you were a child and you have a vicious cat pondering around. Suddenly your mom summons you to dinner and you have to go. If you take your tower of cards with you, that is a soft choice. You don't want your cat to destroy your progress, therefore you take the tower (your changes) with you. On the contrary, if you decide to leave it as is, that vicious cat will definitely tear it down, that's a hard choice. This hard choice will indefinitely discard all the changes you have done, so use it carefully.
In summary:
-
git reset --soft HEAD~
- move back one commit and keep your changes -
git reset --hard HEAD~
- move back one commit and discard all changes
You might sometimes commit a work-in-progress and immediately realize you forgot to add something to the commit.
-
git add
- add the missing/modified file(s) you forgot to add -
git commit --amend --no-edit
- alter the previous commit (remove--no-edit
if you want to modify the commit message)
This will replace your commit with a fresh new one. Now let's see what changed in our visual graph. Oh look at that, only modification was to the hash of our commit. That means we changed the contents of this commit.
We were working casually and finished our work, only to commit to master instead of another branch. Ah, a silly mistake indeed. You can see our mistake in the graph below. I assure you it has a trivial fix.
-
git branch feature3
- create a new branch at your current position -
git reset HEAD~ --hard
- take back master to the previous commit -
git checkout feature3
- switch to your new branch
Let's look at our graph. Okay seems like we now have a branch called feature3 and we successfully recovered our master to where it belongs. HEAD is pointing to the feature3 branch and that's what we expect from the last action we took above.
You made a commit to a wrong branch ? This is a very common scenario and it has an easy fix, unless you have pushed your changes (please tell me you did not). If you haven't pushed your changes yet, hurrraaaay. If you have already pushed your changes, well we'll deal with that later. Let's clear the easier one first.
-
git reset HEAD~ --soft
- take yourself back to the previous commit and keep your changes -
git stash
- stash your changes so we can switch branches
-
git checkout feature2
- switch to another branch -
git stash pop
- unpack your changes from stash
-
git add .
- add your files and commit them normally git commit -m "fixed"
One of those moments where you need to take back a change from daaaaays ago right ? Git has you covered.
-
git log
- find the hash of that commit you want to revert git revert <hash>
This action creates a revert commit, meaning it does NOT modify any of your previous commits and just creates a new commit with the exact opposite file modifications. This is cool. You can see the reverse commit below, a whole new commit.
This one is nasty. It is best we visualize this one in order to understand what's coming step by step, because my friend, most of the beginners are afraid of GETTING DETACHED. Ok jokes aside, seriously when I first encountered a similar scenario where I had to go into detached state and did something wrong, I felt terribly lost, even though I was not. That's why we will spend extra effort to make it more transparent.
Imagine checking out a specific commit, in this case refactor.
$ git checkout c5b64
Note: checking out 'c5b647'.
You are in 'detached HEAD' state...
Ooookayyy, what does that even mean ? Keep it cool and beware, pretty much nothing is unrecoverable in git except that previously mentioned hard reset or maybe deleting your folder altogether and ending this agony. Detached HEAD state means you are pointing to a commit directly, instead of pointing to a branch. HUUUH and that's it ? YEAH ! EXPERIMENT TIME ! Since we are pointing to a lonely commit, let us change something and commit it and see what happens.
Seems like we are still in that evil detached HEAD state. Let's assume we took such an action while not knowing the bits and pieces of git and switched back to master via git checkout master
. Clearly we now know, git checkout master
in its most basic form is like saying "Go ahead and just point to master".
You see, we checked out some unknown commit somewhere in space, made some changes and committed it with the name of "some changes after refactor" and moved back to master, am I correct ? Now, attention please, since you don't know the hash of that "some changes after refactor" commit and it does not have an explicit branch attached to it, you have no way of knowing if it's there. Hence, you think after all your developments and improvements, you lost it all and it's time to start from ground zero. Stop right there because git reflog
is coming for rescue.
We may inspect the output of this command:
$ git reflog
5b35f6d HEAD@{1}: pull ...
ca92d15 HEAD@{2}: ...
759dab1 HEAD@{3}: commit (merge): ...
065e269 HEAD@{4}: commit: ...
f357606 HEAD@{5}: commit: ...
9u7b45 HEAD@{6}: checkout: moving from master to 9u7b45d272867b63d54f96d4aa57f8ecc479cd0
That 9u7b45 HEAD@{6}: checkout: moving from master to 9u7b45d272867b63d54f96d4aa57f8ecc479cd0
should give you a pretty rough idea of what you have done, that is, you have moved to a branchless commit in the past. There it is now, your precious little commit that was drifting in space without any label on it. Now go there and put a label on it, or that is to say, create a branch, whatever.
If you are really done with this insane version control stuff that I have explained, here is a final solution :D
cd ..
sudo rm -r ducking-git-repo-dir
git clone https://some.github.url/ducking-git-repo-dir.git
cd ducking-git-repo-dir
Proceeds to copy the working folder to -> "/home/git_bender/last_last_final_version_of_git_tutorial/"
Inspired from Dangitgit.
14