DEV Community

Cover image for Git From Zero to Hero: A Master Class
kali kimanzi
kali kimanzi

Posted on

Git From Zero to Hero: A Master Class

Git From Zero to Hero: A Master Class

Git runs almost every codebase you have ever touched, yet most developers only know a handful of commands by muscle memory. They type git add ., git commit, git push, and hope for the best. This guide is different. It starts from the absolute beginning and walks all the way to the internals that power tools like GitHub, GitLab, and every CI pipeline you rely on.

By the end you will not just know the commands. You will understand the model underneath them, which means you will never be afraid of a merge conflict, a detached HEAD, or a broken rebase again.

This course has three levels.

  • Beginner: what Git is, the three trees, and your first commits
  • Intermediate: branching, merging, rebasing, and working with remotes
  • Expert: the object model, refs, the reflog, and advanced recovery and workflows

Grab a terminal and follow along.

Table of Contents

  1. Why Git Exists
  2. Beginner: The Three Trees
  3. Beginner: Your First Repository
  4. Beginner: Ignoring Files
  5. Intermediate: Branches Are Just Pointers
  6. Intermediate: Merging
  7. Intermediate: Rebasing
  8. Intermediate: Merge vs Rebase, the Real Answer
  9. Intermediate: Working With Remotes
  10. Intermediate: Stash and Tags
  11. Expert: The Object Model
  12. Expert: Refs, HEAD, and Detached States
  13. Expert: The Reflog, Your Safety Net
  14. Expert: Interactive Rebase
  15. Expert: Cherry-pick and Bisect
  16. Expert: Workflows That Teams Actually Use
  17. Cheat Sheet
  18. Closing Thoughts

Why Git Exists

Before Git, most teams used centralized version control. A single server held the entire history, and every commit required a network round trip. If that server went down, work stopped. If your connection was slow, your workflow suffered with it.

Linus Torvalds built Git in 2005 to maintain the Linux kernel, and he designed it around a simple idea: every developer keeps a full copy of the project history on their own machine. There is no single point of failure, and almost every operation is instant because it happens locally.

Diagram by Kali Kimanzi

Notice the dotted line in the distributed model. Developers can even share commits directly with each other without touching the central remote at all. That single design decision is why Git scales from a solo hobby project to a codebase with thousands of contributors.

Beginner: The Three Trees

Everything in Git revolves around three areas living on your machine. People new to Git often skip past this and jump straight to memorizing commands, which is exactly why git add feels mysterious. Once you see the three trees, the commands explain themselves.

Diagram by Kali Kimanzi

  • The working directory is what you see in your editor right now.
  • The staging area (also called the index) is a draft of your next commit. You choose exactly what goes in it.
  • The repository is the permanent, immutable history stored inside the .git folder.

This is the entire mental model. git add moves changes from the working directory into the draft. git commit seals that draft into history forever. Nothing about Git makes sense until this clicks, so read that diagram twice if you need to.

Beginner: Your First Repository

# one time setup on a new machine
git config --global user.name "Kali Kimanzi"
git config --global user.email "you@example.com"

# start a new repo
mkdir my-project && cd my-project
git init

# check what state things are in
git status

# create a file, then stage and commit it
echo "# My Project" > README.md
git add README.md
git commit -m "Initial commit"

# see the history
git log
Enter fullscreen mode Exit fullscreen mode

A few commands you will run constantly:

Command What it actually does
git status Shows which files are in the working directory vs staged
git add <file> Moves a file's changes into the staging area
git commit -m "msg" Seals staged changes into a permanent snapshot
git log Shows the commit history, newest first
git diff Shows unstaged changes between working directory and staging
git diff --staged Shows staged changes between staging and last commit

Diagram by Kali Kimanzi

Beginner: Ignoring Files

Not everything belongs in history. Build artifacts, dependency folders, and secrets should never be committed. Git reads a .gitignore file at the root of your repo.

node_modules/
dist/
.env
*.log
Enter fullscreen mode Exit fullscreen mode

A rule of thumb that will save you from painful incidents: anything that can be regenerated (build output, installed packages) or that contains a secret (API keys, passwords) does not belong in Git.

Intermediate: Branches Are Just Pointers

This is the idea that unlocks Git for good. A branch is not a copy of your code. A branch is a small file containing one thing, the hash of a single commit. When you commit again, Git just moves that pointer forward. That is the entire mechanism, and it is why creating a branch in Git is instant regardless of how large the project is.

Diagram by Kali Kimanzi

git branch feature/login       # create a branch, stay where you are
git switch feature/login       # move to that branch
git switch -c feature/signup   # create and switch in one step
git branch                     # list local branches
git branch -d feature/login    # delete a branch that has been merged
Enter fullscreen mode Exit fullscreen mode

Because a branch is just a movable pointer, switching branches is cheap and creating experimental branches costs nothing. This is why Git culture leans heavily on branching for every feature, every bugfix, and every experiment.

Intermediate: Merging

A merge brings the work from one branch into another. If the two branches changed different parts of the code, Git combines them automatically and creates a new commit with two parents, called a merge commit.

Diagram by Kali Kimanzi

git switch main
git merge feature/login
Enter fullscreen mode Exit fullscreen mode

When both branches edited the same lines, Git cannot guess your intent and asks you to resolve a conflict. It marks the file like this:

<<<<<<< HEAD
const greeting = "Hello there";
=======
const greeting = "Hi friend";
>>>>>>> feature/login
Enter fullscreen mode Exit fullscreen mode

You edit the file to keep what you actually want, remove the markers, then run:

git add <file>
git commit
Enter fullscreen mode Exit fullscreen mode

A conflict is not a failure. It is Git correctly refusing to guess between two changes it cannot reconcile on its own.

Intermediate: Rebasing

Rebase takes the commits from your branch and replays them on top of another branch, one at a time, as if you had started your work later than you actually did. The result is a straight, linear history with no merge commit.

Diagram by Kali Kimanzi

git switch feature/login
git rebase main
Enter fullscreen mode Exit fullscreen mode

Notice the commits become c3' and c4', with a prime mark. Rebase does not move the original commits, it creates brand new ones with the same changes but different parent history and a new hash. This detail matters a lot, and it leads directly to the next section.

Intermediate: Merge vs Rebase, the Real Answer

This is one of the most argued about topics in software engineering, and the honest answer is that both are correct tools for different jobs.

Diagram by Kali Kimanzi

The one rule that prevents real disasters: never rebase a branch that other people have already pulled. Since rebase rewrites commit hashes, anyone who already has the old commits will end up with duplicated, conflicting history. Merge is always safe on shared branches. Rebase is a personal cleanup tool for your own local, unpublished work.

Intermediate: Working With Remotes

A remote is just another copy of the repository, usually hosted on a service like GitHub. Your local repo and the remote sync through three operations.

Diagram by Kali Kimanzi

git clone https://github.com/user/repo.git   # copy a remote repo locally
git remote -v                                # see configured remotes
git fetch origin                             # download new commits, do not touch your branch
git pull origin main                         # fetch, then merge into your current branch
git push origin main                         # upload your commits
git pull --rebase origin main                # fetch, then rebase instead of merge
Enter fullscreen mode Exit fullscreen mode

fetch is always safe. It only downloads data, it never touches your working directory or your current branch. pull is fetch plus an automatic merge, which is convenient but can surprise you with a merge commit you were not expecting. Many experienced developers run git fetch followed by a manual git merge or git rebase so they stay in control of what happens.

Intermediate: Stash and Tags

Sometimes you need to switch branches but you are not ready to commit. Stash lets you shelve your changes temporarily.

git stash              # save working directory changes, revert to clean state
git stash list          # see saved stashes
git stash pop           # reapply the most recent stash and remove it from the list
git stash apply         # reapply without removing it from the list
Enter fullscreen mode Exit fullscreen mode

Tags mark a specific commit permanently, most often used for releases.

git tag v1.0.0                       # lightweight tag on current commit
git tag -a v1.0.0 -m "First release" # annotated tag with metadata
git push origin v1.0.0               # tags are not pushed automatically
Enter fullscreen mode Exit fullscreen mode

Expert: The Object Model

Here is the part that turns Git from a tool you use into a system you understand. Everything in a Git repository, every file, every folder, every commit, is stored as an object identified by the SHA hash of its content. This is called content addressable storage, and it is the real foundation everything else sits on.

There are four object types.

Diagram by Kali Kimanzi

  • A blob stores the raw content of a single file. It has no filename, no permissions, nothing but content. Two files with identical content anywhere in your repo history share the exact same blob.
  • A tree represents a directory. It lists blobs and other trees along with filenames and modes.
  • A commit points to one tree (the full snapshot of the project at that moment) plus one or more parent commits, an author, and a message.
  • A tag (the annotated kind) points at a commit and adds extra metadata like a signature.

You can inspect this yourself:

git cat-file -p HEAD                 # show a commit object
git cat-file -p HEAD^{tree}          # show the tree it points to
git cat-file -t <hash>               # ask Git what type an object is
git hash-object -w somefile.txt      # manually create a blob, see the hash
Enter fullscreen mode Exit fullscreen mode

Because objects are addressed by the hash of their content, a commit hash is really a hash of a hash of hashes, a fingerprint of the entire project history up to that point. This is exactly why changing anything in the past, even one character in one old commit, changes that commit's hash and every hash after it. That single fact explains why rebase produces new commit hashes and why Git history is described as immutable.

Expert: Refs, HEAD, and Detached States

A ref is a human friendly name that points at a commit hash. Branches live at .git/refs/heads/, remote tracking branches at .git/refs/remotes/, and tags at .git/refs/tags/. HEAD is special, it usually points at a branch, which then points at a commit.

Diagram by Kali Kimanzi

When you check out a specific commit hash instead of a branch, HEAD points directly at that commit. This is called a detached HEAD.

Diagram by Kali Kimanzi

A detached HEAD is not dangerous, it just means new commits you make will not belong to any branch. If you commit here and then switch to another branch without creating a new branch first, those commits become unreachable from any ref. They are not deleted immediately, but they will eventually be garbage collected. If you find yourself in a detached HEAD and want to keep your work, run git switch -c new-branch-name before doing anything else.

Expert: The Reflog, Your Safety Net

Even "unreachable" commits are not gone right away. Git keeps a local log of every place HEAD has pointed, called the reflog, and it is the single most underused recovery tool in Git.

git reflog
Enter fullscreen mode Exit fullscreen mode
a1b2c3d HEAD@{0}: commit: fix login bug
e4f5g6h HEAD@{1}: checkout: moving from main to feature/login
i7j8k9l HEAD@{2}: reset: moving to HEAD~1
m0n1o2p HEAD@{3}: commit: initial version
Enter fullscreen mode Exit fullscreen mode

Accidentally ran git reset --hard and lost commits? Force pushed and overwrote a branch? As long as it is still within the reflog window (90 days by default for reachable commits), you can recover it.

git reflog                        # find the hash right before the disaster
git reset --hard HEAD@{2}         # or: git checkout -b rescue-branch <hash>
Enter fullscreen mode Exit fullscreen mode

Learn this one tool well. It has saved more careers than any other Git feature, and it is the reason the phrase "I force pushed and lost everything" is almost never actually true.

Expert: Interactive Rebase

Interactive rebase lets you rewrite local history before sharing it: squash messy commits into one, reorder them, edit messages, or drop a commit entirely.

git rebase -i HEAD~4
Enter fullscreen mode Exit fullscreen mode

This opens an editor with your last four commits listed oldest to newest:

pick a1b2c3d Add login form
pick e4f5g6h Fix typo
pick i7j8k9l Add validation
pick m0n1o2p Fix typo again
Enter fullscreen mode Exit fullscreen mode

Change the keywords to control what happens:

Keyword Effect
pick keep the commit as is
reword keep the changes, edit the message
squash merge into the previous commit, combine messages
fixup merge into the previous commit, discard this message
drop remove the commit entirely
pick a1b2c3d Add login form
fixup e4f5g6h Fix typo
pick i7j8k9l Add validation
fixup m0n1o2p Fix typo again
Enter fullscreen mode Exit fullscreen mode

Save and exit, and those four messy commits become two clean ones. This is exactly how experienced developers keep a readable history despite committing constantly while actually working. The same rule from earlier still applies, only rebase commits that are still local and unpushed, or that you are certain nobody else has already pulled.

Expert: Cherry-pick and Bisect

cherry-pick copies a single commit from one branch onto another, creating a new commit with the same changes but a new hash and parent.

git cherry-pick a1b2c3d
Enter fullscreen mode Exit fullscreen mode

This is the tool for the common real world situation where a hotfix landed on main and you need that exact fix on a release branch too, without merging all of main's other changes.

bisect performs a binary search through history to find the exact commit that introduced a bug. Instead of manually checking commits one by one, you tell Git one good commit and one bad commit, and it checks out the midpoint for you to test.

git bisect start
git bisect bad                 # current commit has the bug
git bisect good v1.2.0         # this old tag was fine
# Git checks out a commit in the middle, you test it, then:
git bisect good   # or: git bisect bad
# repeat until Git names the exact commit that broke things
git bisect reset  # return to where you started
Enter fullscreen mode Exit fullscreen mode

Diagram by Kali Kimanzi

In a history of 1000 commits, bisect finds the culprit in about 10 tests instead of up to 1000, because it halves the search space every step.

Expert: Workflows That Teams Actually Use

The commands are the same everywhere, but teams organize branches differently depending on how often they release.

Diagram by Kali Kimanzi

Git Flow uses long lived develop and main branches, plus feature, release, and hotfix branches. It gives strong structure and suits products with scheduled releases, but it adds overhead that many fast moving teams find unnecessary.

Trunk based development keeps everyone working off a single main branch with short lived feature branches, often merged within a day. It pairs well with feature flags and continuous deployment, and it is what most modern SaaS companies use today.

GitHub flow sits in between: branch from main, open a pull request, review, merge, deploy. Simple, and the default most newer teams should reach for unless they have a specific reason not to.

There is no universally correct choice. Pick the workflow that matches how often your team actually ships.

Cheat Sheet

# setup
git init
git clone <url>
git config --global user.name "Kali Kimanzi"
git config --global user.email "you@example.com"

# daily loop
git status
git add <file>
git commit -m "message"
git push
git pull

# branching
git switch -c <branch>
git switch <branch>
git branch -d <branch>
git merge <branch>
git rebase <branch>

# inspecting
git log --oneline --graph --all
git diff
git show <hash>
git blame <file>

# undoing
git restore <file>              # discard working directory changes
git restore --staged <file>     # unstage, keep changes
git commit --amend              # fix the last commit
git reset --soft HEAD~1         # undo commit, keep changes staged
git reset --hard HEAD~1         # undo commit, discard changes (careful)
git revert <hash>               # safe undo, creates a new commit

# recovery
git reflog
git reset --hard HEAD@{n}

# advanced
git rebase -i HEAD~n
git cherry-pick <hash>
git bisect start
git stash
git tag -a v1.0.0 -m "release"
Enter fullscreen mode Exit fullscreen mode

Closing Thoughts

Git rewards understanding over memorization. Once you see that a branch is just a pointer, that a commit is just a snapshot linked to its parent, and that almost every command is a small, predictable operation on that graph, the tool stops feeling like magic. You stop fearing conflicts, detached HEADs, and rebases, because you know exactly what is happening underneath and you know the reflog has your back.

Start with the three trees. Get comfortable branching and merging. Then, when you are ready, go read your own .git folder with cat-file and watch the object model become real in front of you. That is the moment Git actually clicks.

If this guide helped you, follow along for more deep dives like this one.

Kali Kimanzi

Top comments (1)

Collapse
 
kokiste profile image
Koki

This is hella helpful. Thank you.