DEV Community

loading...
Cover image for πŸŒ³πŸš€ CS Visualized: Useful Git Commands

πŸŒ³πŸš€ CS Visualized: Useful Git Commands

lydiahallie profile image Lydia Hallie ・9 min read

Although Git is a very powerful tool, I think most people would agree when I say it can also be... a total nightmare 😐 I've always found it very useful to visualize in my head what's happening when working with Git: how are the branches interacting when I perform a certain command, and how will it affect the history? Why did my coworker cry when I did a hard reset on master, force pushed to origin and rimraf'd the .git folder?

I thought it would be the perfect use case to create some visualized examples of the most common and useful commands! πŸ₯³ Many of the commands I'm covering have optional arguments that you can use in order to change their behavior. In my examples, I'll cover the default behavior of the commands without adding (too many) config options! πŸ˜„


Merging

Having multiple branches is extremely convenient to keep new changes separated from each other, and to make sure you don't accidentally push unapproved or broken changes to production. Once the changes have been approved, we want to get these changes in our production branch!

One way to get the changes from one branch to another is by performing a git merge! There are two types of merges Git can perform: a fast-forward, or a no-fast-forward 🐒

This may not make a lot of sense right now, so let's look at the differences!

Fast-forward (--ff)

A fast-forward merge can happen when the current branch has no extra commits compared to the branch we’re merging. Git is... lazy and will first try to perform the easiest option: the fast-forward! This type of merge doesn’t create a new commit, but rather merges the commit(s) on the branch we’re merging right in the current branch πŸ₯³

Perfect! We now have all the changes that were made on the dev branch available on the master branch. So, what's the no-fast-forward all about?

No-fast-foward (--no-ff)

It's great if your current branch doesn't have any extra commits compared to the branch that you want to merge, but unfortunately that's rarely the case! If we committed changes on the current branch that the branch we want to merge doesn't have, git will perform a no-fast-forward merge.

With a no-fast-forward merge, Git creates a new merging commit on the active branch. The commit's parent commits point to both the active branch and the branch that we want to merge!

No big deal, a perfect merge! πŸŽ‰ The master branch now contains all the changes that we've made on the dev branch.

Merge Conflicts

Although Git is good at deciding how to merge branches and add changes to files, it cannot always make this decision all by itself πŸ™‚ This can happen when the two branches we're trying to merge have changes on the same line in the same file, or if one branch deleted a file that another branch modified, and so on.

In that case, Git will ask you to help decide which of the two options we want to keep! Let's say that on both branches, we edited the first line in the README.md.

If we want to merge dev into master, this will end up in a merge conflict: would you like the title to be Hello! or Hey!?

When trying to merge the branches, Git will show you where the conflict happens. We can manually remove the changes we don't want to keep, save the changes, add the changed file again, and commit the changes πŸ₯³

Yay! Although merge conflicts are often quite annoying, it makes total sense: Git shouldn't just assume which change we want to keep.


Rebasing

We just saw how we could apply changes from one branch to another by performing a git merge. Another way of adding changes from one branch to another is by performing a git rebase.

A git rebase copies the commits from the current branch, and puts these copied commits on top of the specified branch.

Perfect, we now have all the changes that were made on the master branch available on the dev branch! 🎊

A big difference compared to merging, is that Git won't try to find out which files to keep and not keep. The branch that we're rebasing always has the latest changes that we want to keep! You won't run into any merging conflicts this way, and keeps a nice linear Git history.

This example shows rebasing on the master branch. In bigger projects, however, you usually don't want to do that. A git rebase changes the history of the project as new hashes are created for the copied commits!

Rebasing is great whenever you're working on a feature branch, and the master branch has been updated. You can get all the updates on your branch, which would prevent future merging conflicts! πŸ˜„

Interactive Rebase

Before rebasing the commits, we can modify them! πŸ˜ƒ We can do so with an interactive rebase. An interactive rebase can also be useful on the branch you're currently working on, and want to modify some commits.

There are 6 actions we can perform on the commits we're rebasing:

  • reword: Change the commit message
  • edit: Amend this commit
  • squash: Meld commit into the previous commit
  • fixup: Meld commit into the previous commit, without keeping the commit's log message
  • exec: Run a command on each commit we want to rebase
  • drop: Remove the commit

Awesome! This way, we can have full control over our commits. If we want to remove a commit, we can just drop it.

Alt Text

Or if we want to squash multiple commits together to get a cleaner history, no problem!

Alt Text

Interactive rebasing gives you a lot of control over the commits you're trying to rebase, even on the current active branch!


Resetting

It can happen that we committed changes that we didn't want later on. Maybe it's a WIP commit, or maybe a commit that introduced bugs! πŸ› In that case, we can perform a git reset.

A git reset gets rid of all the current staged files and gives us control over where HEAD should point to.

Soft reset

A soft reset moves HEAD to the specified commit (or the index of the commit compared to HEAD), without getting rid of the changes that were introduced on the commits afterward!

Let's say that we don't want to keep the commit 9e78i which added a style.css file, and we also don't want to keep the commit 035cc which added an index.js file. However, we do want to keep the newly added style.css and index.js file! A perfect use case for a soft reset.

When typing git status, you'll see that we still have access to all the changes that were made on the previous commits. This is great, as this means that we can fix the contents of these files and commit them again later on!

Hard reset

Sometimes, we don't want to keep the changes that were introduced by certain commits. Unlike a soft reset, we shouldn't need to have access to them any more. Git should simply reset its state back to where it was on the specified commit: this even includes the changes in your working directory and staged files! πŸ’£

Alt Text

Git has discarded the changes that were introduced on 9e78i and 035cc, and reset its state to where it was on commit ec5be.


Reverting

Another way of undoing changes is by performing a git revert. By reverting a certain commit, we create a new commit that contains the reverted changes!

Let's say that ec5be added an index.js file. Later on, we actually realize we didn't want this change introduced by this commit anymore! Let's revert the ec5be commit.

Alt Text

Perfect! Commit 9e78i reverted the changes that were introduced by the ec5be commit. Performing a git revert is very useful in order to undo a certain commit, without modifying the history of the branch.


Cherry-picking

When a certain branch contains a commit that introduced changes we need on our active branch, we can cherry-pick that command! By cherry-picking a commit, we create a new commit on our active branch that contains the changes that were introduced by the cherry-picked commit.

Say that commit 76d12 on the dev branch added a change to the index.js file that we want in our master branch. We don't want the entire we just care about this one single commit!

Alt Text

Cool, the master branch now contains the changes that 76d12 introduced!


Fetching

If we have a remote Git branch, for example a branch on Github, it can happen that the remote branch has commits that the current branch doesn't have! Maybe another branch got merged, your colleague pushed a quick fix, and so on.

We can get these changes locally, by performing a git fetch on the remote branch! It doesn't affect your local branch in any way: a fetch simply downloads new data.

Alt Text

We can now see all the changes that have been made since we last pushed! We can decide what we want to do with the new data now that we have it locally.


Pulling

Although a git fetch is very useful in order to get the remote information of a branch, we can also perform a git pull. A git pull is actually two commands in one: a git fetch, and a git merge. When we're pulling changes from the origin, we're first fetching all the data like we did with a git fetch, after which the latest changes are automatically merged into the local branch.

Alt Text

Awesome, we're now perfectly in sync with the remote branch and have all the latest changes! 🀩


Reflog

Everyone makes mistakes, and that's totally okay! Sometimes it may feel like you've screwed up your git repo so badly that you just want to delete it entirely.

git reflog is a very useful command in order to show a log of all the actions that have been taken! This includes merges, resets, reverts: basically any alteration to your branch.

Alt Text)

If you made a mistake, you can easily redo this by resetting HEAD based on the information that reflog gives us!

Say that we actually didn't want to merge the origin branch. When we execute the git reflog command, we see that the state of the repo before the merge is at HEAD@{1}. Let's perform a git reset to point HEAD back to where it was on HEAD@{1}!

Alt Text

We can see that the latest action has been pushed to the reflog!


Git has so many useful porcelain and plumbing commands, I wish I could cover them all! πŸ˜„ I know there are many other commands or alterations that I didn't have time for to cover right now - let me know what your favorite/most useful commands are, and I may cover them in another post!

And as always, feel free to reach out to me! 😊

✨ Twitter πŸ‘©πŸ½β€πŸ’» Instagram πŸ’» GitHub πŸ’‘ LinkedIn πŸ“· YouTube πŸ’Œ Email

Discussion (90)

pic
Editor guide
Collapse
codypearce profile image
Cody Pearce

Awesome visualizations as usual!

It's interesting there's a few different syntaxes for selecting a previous commit:

HEAD~2          // previous two commits fro head
HEAD~~         // previous two commits from head
HEAD@{2}     // reflog order
18fe5              // previous commit hash
Enter fullscreen mode Exit fullscreen mode
Collapse
lydiahallie profile image
Lydia Hallie Author

Good idea to add that! I'm thinking of generally creating a "cheatsheet" format that also covers all that stuff :) Will do in the next one or when I update the format πŸ˜„

Collapse
toby profile image
toby • Edited

Came here to comment specifically along these lines - I can never ever remember what the difference between HEAD~2 and HEAD^2 is !

Had completely forgotten about HEAD@{n} syntax :D

Collapse
udayvunnam profile image
Uday Vunnam

Nice visualizations! What do you use to create them?

Collapse
flatrick profile image
Patrik

I'd love to know the answer to that too! :)

Collapse
dguhl profile image
D. Guhl

Lydia answered in another post of hers, which I recommend myself, that she used Keynote (the presentation software by Apple) to make the animations and then screen-recorded the slides.

The Post is "JavaScript Visualized: the JavaScript Engine"

Thread Thread
thiagolottici profile image
ThiagoBL

Lol! Her profile description has the asnwer. :P

Thread Thread
murroughfoley profile image
Murrough Foley

Thanks for clearing that up

Collapse
mansoor_aman profile image
Mansoor

Awesome animations. It is great to see a diagrammatic representation of these git commands.

For "git pull", is the animation correct? I would have expected it to fetch the commits and then do a ff merge.

For clarity, it would also help to distinguish between the remote repository and the local remote branches in the animations.

Collapse
fmeyertoens profile image
Fabian

I also expected the ff merge. But now she already has an animation for git pull --no-ff if ever she needs one. πŸ˜‰

Collapse
ginomempin profile image
Gino Mempin • Edited

Great visualizations :)

It might be worth noting that git reset does not delete untracked files, even with --hard. It only affects those that are already tracked.

So, for example, some recent commits causes your app to generate some files, then you later reset those commits, it will leave those files as untracked.

For that, you need git clean.

Collapse
damsalem profile image
Dani Amsalem

Amazing article Lydia!

I prefer to use Git in terminal as opposed to a GUI like the others on my team so I can face my Git fears. However, most of the documentation I read online is very complicated. Yours is the first long-form article I got to the bottom of and didn't have 2X the confusions as when I started!

Please write more Git visualized articles. I'll devour them, I swear.

Collapse
chiragshahklc profile image
Chirag Shah

I have one question.
For example, I am working on the dev branch. Meanwhile, my colleague has pushed 2 more commits.
What should I do? Should I pull first then commit or should I commit first and then pull?
I am always confused over here.

Collapse
thamaraiselvam profile image
Thamaraiselvam • Edited

You cannot pull before commit because git does not know what do with changes in local.

This is what we do.

  • commit local changes
  • git pull --rebase (This will copy commits to top. without rebase commits be will merged)
  • git push

if you dont want to commit ur changes and still you want to pull data you do stash

stash will push changes to stack and you can get it from it later or you can auto stash

git pull --rebase --auto-stash

Collapse
chiragshahklc profile image
Chirag Shah

Thank you so much for the answer. Very Helpful!

Collapse
borekb profile image
Borek Bernard

Beautifully done! Seeing rebase --onto visualized would be great too πŸ˜„.

Collapse
yangc22 profile image
Chason Young

Thanks so much for this awesome post. I have one question is that for this paragraph
"""
This example shows rebasing on the master branch. In bigger projects, however, you usually don't want to do that. A git rebase changes the history of the project as new hashes are created for the copied commits!
"""
You rebase the dev branch over the master branch. But you said in bigger projects you don't want to do that. I'm a little confused here. So in a bigger project, what do we do? Do we rebase the master over other branches? Thanks!

Collapse
nikulabs profile image
nikulabs

When you rebase your branch from a different commit on master, you rewrite the history of your branch. This requires a force push. If there are multiple developers working on that branch, this may cause issues if they have work based on the old history of the branch. Rather than doing a rebase, merging master into the branch may make more sense.

Collapse
yangc22 profile image
Chason Young

thanks so much for the reply. You mean a force push, do you mean the newer stuff on my branch will be pushed to the mater branch? I'm pretty new so sorry for the dumb questions. Thanks!

Thread Thread
nikulabs profile image
nikulabs

TL;DR: Master is unchanged in the process of this rebase example, only the branch is changed.

In the given example, the branch is being based off a different commit than it originally was. In other words, the commits made on master since the branch was originally made will now appear at the start of the branch's history. You will often see this referred to as "replaying commits". The branch commits will have a different hash (you can see this in the example if you look closely), but will have the same contents in them.
git rebase can also be used to "replay" the commits from the dev branch back onto master, but I'm not as familiar with that work flow, so I won't try to give advice on it.

Thread Thread
yangc22 profile image
Chason Young

Thanks so much for the awesome reply! Now I understand the flaw of doing this and a better understanding between 'rebase' and 'merge'. I really appreciate it!

Collapse
vaibhavkhulbe profile image
Vaibhav Khulbe

Wow! Never knew about something like Reflog! Thanks for your efforts πŸ€—

Collapse
vagoel profile image
Varun

Amazing work Lydia....Extremely helpful for all dev peeps.

Collapse
petr7555 profile image
Petr Janik

I can read about these basic commands many times a year and each time learn something new and refresh what I have forgotten. Thanks for the great article!

Collapse
dhughesxumak profile image
Dave Hughes

Great post, @lydiahallie ! I'll definitely be using this to help team members in the future.

A few thoughts:

  • The soft reset example may have been clearer if the animation showed the files which were changed in the two commits being "removed" (9e78i and 035cc)
  • You kind of touched on the reset --mixed (default), but an animation would have been great
  • A couple more rebase examples, such as rebase --onto, would have been really informative

Thanks again for the awesome resource!

Collapse
peteruithoven profile image
Peter Uithoven

Very clarifying, the visualizations I wish where there when I first learned Git.
2 tiny text issues:

  1. "we can cherry-pick that command!" should probably be "we can cherry-pick that commit!".
  2. "We don't want the entire we just" should probably be "We don't want the entire branch we just"

Keep up the good work!

Collapse
msk61 profile image
Mohammed El-Afifi

Nice read. One note though about rebasing is that we can still run into conflicts just as we do with merges.

Collapse
sergeikuznetsov profile image
Sergey Kuznetsov

I agree. Here, the article is misleading.

Collapse
isajal07 profile image
Sajal Shrestha

TheAvocoder back with another masterpiece. πŸ‘ŠπŸΌ

Collapse
lydiahallie profile image
Lydia Hallie Author

thank uu πŸ₯‘πŸ˜Ž

Collapse
artoodeeto profile image
aRtoo

You really are the lost supergirl. Thank you super champ. :(

Collapse
codingsafari profile image
Nico Braun

Amazing post, like every inch of it.

Collapse
donghoon759 profile image
Donghoon Song

Such a nice visualization!!! Thanks a lot. It helped me a lot to better understand git commands. Would you mind if I translate it in Korean and repost on my blog? I will leave the original link of course :)

Collapse
pestrinmarco profile image
Marco Pestrin

wooow! amazing :) very good job

Collapse
malej_pan profile image
Bedrich Horak

Really awesome!! Thank you.
Do you think you could make an animation of git pull --rebaseπŸ™? I would like to show it in comparison to git pull.

Collapse
alexaegis profile image
AlexAegis

pull is fetch + merge
pull --rebase is fetch + rebase

Collapse
thisisthien profile image
Thien Nguyen

Very useful!

Collapse
akashpreet profile image
Akash Preet

This is interesting, Thanks for this post :-)
Added in my reading list

Collapse
josephj11 profile image
Joe

Thanks for the explanations. I really need to start using Git, but I keep putting it off.

Collapse
viciojha profile image
Vivek Ojha

Awesome. I wish we could see this visualization on our actual git repository.
Is there any way ?

Collapse
ityy profile image
Yang Yang

you are superstar!

Collapse
rosered11 profile image
Rosered

I like it.

Collapse
chidioguejiofor profile image
Chidiebere Ogujeiofor

This is very cool. Really love the visualisation.

What tool did you use to make those visualisation?

Great job!

Collapse
comscience profile image
kapeel kokane

In the animation for Pulling, shouldn't the 2 commits (7e456 and efi81) get copied over to the left side?

Collapse
tothricsaj profile image
Richard Toth

No doubt, the best tool is vi to resolve conflicts. πŸ˜„ Your animations are excellent again, thanks a lot

Collapse
rad_hombre profile image
Matthew Orndoff

These visualizations are πŸ”₯πŸ”₯πŸ”₯
Great post!