Skip to main content
Back to Blog
GitDevOpsTutorial

Git CLI Crash Course: From Zero to Version Control Hero

Usman Ali Qureshi

Usman Ali Qureshi

30 Jan, 2026 ยท 7 min read

Learn Git the practical way. No fluff, just the commands you'll actually use every day, plus the ones that'll save you when things go wrong.

Git CLI Crash Course: From Zero to Hero ๐Ÿš€

Alright, real talk. Git intimidated the hell out of me when I first started using it. The terminology is weird (what even is a "detached HEAD"?), the commands feel cryptic, and there's this constant low-level fear that you're going to accidentally delete everything and there's no undo button.

Good news: that fear is mostly unfounded. Git is incredibly hard to permanently break. And after years of using it daily, I've realized you only need about 20 commands for 95% of your work. Everything else you can look up when the situation calls for it.

So let's skip the theory and get you productive as fast as possible.


๐Ÿ“‹ What We're Covering

  1. Git fundamentals
  2. Commands you'll use every day
  3. Branching and merging
  4. Working with others
  5. Fixing your mistakes
  6. Power user stuff
  7. Good habits
  8. Common mistakes

๐ŸŽฏ Git Fundamentals

What Even Is Git?

Git tracks changes to your code over time. That's the entire concept. Every time you "commit," Git takes a snapshot of exactly what your project looks like at that moment. You can go back to any snapshot whenever you want.

Unlike something like Google Docs where there's one shared version, Git gives everyone their own complete copy of the project and its full history. You can work offline. You can experiment without fear. And if someone's laptop dies, nothing is lost because every team member has a full backup sitting on their machine.

Where Your Files Actually Live

This concept trips people up initially, so I'll keep it simple:

Working Directory โ†’ Staging Area โ†’ Repository
   (your files)     (ready to save)   (saved history)

Or if you prefer a workflow view:

Edit files โ†’ git add โ†’ git commit โ†’ git push
   ๐Ÿ“           โž•         ๐Ÿ’พ          โ˜๏ธ

You edit files in your working directory. You stage the ones you want to include in your next snapshot. You commit them to save that snapshot. Then you push to share it with your team. That's the whole loop.

One-Time Setup

Before Git lets you do anything, it needs to know who you are. Run these once and you're set for life:

# Your identity
git config --global user.name "Your Name"
git config --global user.email "your@email.com"

# Default branch name (the modern standard)
git config --global init.defaultBranch main

# Your preferred text editor for commit messages
git config --global core.editor "code --wait"  # VS Code
# OR
git config --global core.editor "vim"

# Verify everything is set
git config --list

๐Ÿ’ผ Commands You'll Use Every Day

Starting a Project

# Brand new project
git init
git init my-project  # Creates the folder for you

# Grab someone else's project
git clone https://github.com/user/repo.git
git clone https://github.com/user/repo.git my-folder  # Custom folder name

The Loop You'll Repeat Forever

This is 90% of Git. Seriously.

# 1. Check what's going on (run this obsessively)
git status

# 2. Stage your changes
git add file.txt                    # Specific file
git add folder/                     # Whole folder
git add .                           # Everything
git add *.js                        # Pattern match
git add -p                          # Interactive: review each change one by one

# 3. Commit (save the snapshot)
git commit -m "Add user login validation"
git commit -am "Quick fix"          # Stage tracked files + commit in one shot
git commit --amend                  # Oops, let me fix that last commit message

# 4. Look at history
git log                            # Full log
git log --oneline                  # My personal favorite, compact view
git log --graph --all --decorate   # Visual branch tree
git log -5                         # Just the last 5
git log --since="2 weeks ago"      # Time-based filter

# 5. See exactly what changed
git diff                           # Unstaged changes
git diff --staged                  # Staged changes (what's about to be committed)
git diff HEAD                      # Everything since last commit
git diff branch1 branch2           # Compare two branches

Pro tip: when in doubt about anything, run git status. Then run git log --oneline. These two commands will orient you no matter how lost you feel.


๐ŸŒณ Branching and Merging

This is where Git really shines. Branches let you experiment, build features, and try risky changes without touching the stable code. If the experiment works, merge it in. If it doesn't, delete the branch and move on. Zero consequences.

Branch Basics

# Create a new branch
git branch feature-login
git branch feature-login main      # Branch from a specific point

# Switch to it
git checkout feature-login
git switch feature-login           # Newer, cleaner syntax

# Create AND switch in one step (what you'll actually do 99% of the time)
git checkout -b feature-login
git switch -c feature-login

# See your branches
git branch                         # Local
git branch -r                      # Remote
git branch -a                      # Everything
git branch -v                      # With latest commit info

# Rename a branch
git branch -m old-name new-name
git branch -M new-name             # Rename the one you're currently on

# Delete a branch
git branch -d feature-login        # Safe delete (refuses if there are unmerged changes)
git branch -D feature-login        # Force delete (careful with this one)

Merging

# First, switch to the branch you want to merge INTO
git checkout main

# Then merge the feature branch in
git merge feature-login

# Useful options
git merge --no-ff feature-login    # Force a merge commit (good for history clarity)
git merge --squash feature-login   # Squash all commits into one before merging

# Went sideways? Bail out
git merge --abort

When Merges Go Wrong (Conflicts)

Sometimes two people changed the same lines and Git can't automatically figure out which version to keep. When that happens, you'll see markers in the file that look like this:

======= HEAD
Your changes
=======
Their changes
======= branch-name

Don't panic. The fix is straightforward:

# 1. Open the file, read both versions, decide what to keep
# 2. Delete the ======= markers and the branch name lines
# 3. Stage the resolved file
git add conflicted-file.txt

# 4. Finish the merge
git commit

If you want a visual tool for resolving conflicts, run git mergetool. Most editors like VS Code also have built-in merge conflict UIs that make this way easier.


๐Ÿค Working with Others

Connecting to Remote Repositories

# Link your local repo to GitHub (or GitLab, Bitbucket, etc.)
git remote add origin https://github.com/user/repo.git

# See what's connected
git remote -v

# Change the URL (useful when switching from HTTPS to SSH)
git remote set-url origin https://new-url.git

# Remove a remote
git remote remove origin

Pushing and Pulling

# Send your commits to the remote
git push origin main
git push -u origin main            # Set upstream so you can just type 'git push' next time
git push --all                     # Push all branches
git push --tags                    # Push all tags

# Get changes from the remote
git pull origin main               # Fetch + merge
git pull --rebase origin main      # Fetch + rebase (keeps history cleaner)

# Just download without merging (if you want to inspect first)
git fetch origin
git fetch --all

My Actual Daily Workflow

Here's what a typical day looks like for me:

# 1. Start a new feature branch
git checkout -b feature-new-sidebar

# 2. Code, test, commit as I go
git add .
git commit -m "feat(ui): add collapsible sidebar component"

# 3. Push the branch to remote
git push -u origin feature-new-sidebar

# 4. Periodically sync with main to avoid big merge conflicts later
git fetch origin main
git merge origin/main
# OR (if I want cleaner history):
git rebase origin/main

โฎ๏ธ Fixing Your Mistakes

This section alone is worth bookmarking. Almost nothing in Git is permanent, which means almost every mistake is recoverable.

"I staged something I didn't mean to"

# Unstage the file (changes stay in your working directory)
git restore --staged file.txt
git reset HEAD file.txt            # Older syntax, same result

# Throw away changes entirely (BE CAREFUL, this is destructive)
git restore file.txt
git checkout -- file.txt           # Older syntax

# Nuclear option: discard ALL changes
git restore .                      # Wipes everything back to last commit
git reset --hard HEAD              # Same thing, different syntax

"That commit was wrong"

# Undo the commit but keep your changes staged (ready to re-commit)
git reset --soft HEAD~1

# Undo the commit, keep changes but unstaged
git reset HEAD~1
git reset --mixed HEAD~1           # Explicit version of the same command

# Undo the commit AND throw away the changes
git reset --hard HEAD~1            # DESTRUCTIVE, think before you run this

# Undo multiple commits
git reset --soft HEAD~3            # Undo last 3

# Create a NEW commit that reverses an old one (safe for shared branches)
git revert abc123
git revert HEAD                    # Revert the most recent commit

"I already pushed it and other people pulled it..."

# Don't use reset on pushed commits! Use revert instead:
git revert abc123
git push origin main

# If you absolutely must force push (personal branch only):
git push --force-with-lease origin my-feature  # At least this checks for remote changes first

Golden rule: never force push to a shared branch like main or develop. You will overwrite your teammates' work and make enemies.


๐Ÿ”ฅ Power User Stuff

Stashing (Pause Your Work)

Mid-feature and need to switch branches for a quick bug fix? Stash your work-in-progress.

# Save current changes to the stash
git stash
git stash save "halfway through login refactor"

# See your stashes
git stash list

# Get them back
git stash apply                    # Bring back changes, keep the stash entry
git stash pop                      # Bring back changes, delete the stash entry
# For a specific stash: git stash pop stash@[N]

# Peek at what's in a stash
git stash show
git stash show -p                  # Full diff

# Clean up old stashes
# Delete specific: git stash drop stash@[N]
git stash clear                    # Delete everything

I use git stash at least once a day. It's one of those commands that feels invisible until you need it, and then it's indispensable.

Cherry-Picking

Need one specific commit from another branch without merging the whole thing? Cherry-pick it.

git cherry-pick abc123             # Apply one commit to current branch
git cherry-pick abc123 def456      # Multiple commits
git cherry-pick --no-commit abc123 # Apply changes without committing (if you want to modify first)

Interactive Rebase (Handle With Care)

This lets you rewrite commit history. Incredibly powerful for cleaning up messy commit logs before opening a pull request. Also incredibly dangerous if misused.

# Rewrite the last 3 commits
git rebase -i HEAD~3

Your editor opens with options for each commit:

  • pick = keep it as is
  • reword = change the commit message
  • edit = stop and let you amend the commit
  • squash = combine with the previous commit
  • fixup = combine, but throw away this commit's message
  • drop = delete the commit entirely

Example (squash those "fix typo" commits into the original):

pick abc123 Add user authentication
squash def456 Fix typo in auth module
reword ghi789 Update documentation

Reflog: Your Emergency Parachute

Even when you think you've deleted something, Git usually remembers it for about 30 days.

# See EVERYTHING that happened (every checkout, commit, reset, merge...)
git reflog

# Found what you lost? Recover it
git checkout abc123                # Just look at it
git cherry-pick abc123             # Grab that specific commit
git reset --hard abc123            # Reset your entire branch to that point

Reflog has saved me more times than I can count. Before you declare something permanently lost, check the reflog.

Tags (Mark Important Milestones)

# Lightweight tag
git tag v1.0.0

# Annotated tag (better for releases, includes metadata)
git tag -a v1.0.0 -m "Release version 1.0.0"

# List tags
git tag
git tag -l "v1.*"                  # Filter by pattern

# Push tags to remote
git push origin v1.0.0
git push origin --tags             # Push all tags

# Delete a tag
git tag -d v1.0.0                  # Local
git push origin :refs/tags/v1.0.0  # Remote

โœ… Good Habits

Write Commit Messages That Mean Something

I can't stress this enough. "Fixed stuff" tells nobody anything. Six months from now, you'll be reading your own commit history trying to figure out why you made a change, and "Fixed stuff" is going to make you want to scream.

Use Conventional Commits format:

type(scope): subject

body (optional)

footer (optional)

Common types:

  • feat: New feature
  • fix: Bug fix
  • docs: Documentation changes
  • style: Formatting only (no logic change)
  • refactor: Code restructure without behavior change
  • test: Adding or updating tests
  • chore: Build config, dependency updates, maintenance

Real examples:

git commit -m "feat(auth): add password reset flow with email verification"
git commit -m "fix(api): handle null response in user profile endpoint"
git commit -m "docs(readme): update installation steps for Node 20"

Branch Naming That Doesn't Suck

main          โ”€โ”€โ—โ”€โ”€โ”€โ”€โ—โ”€โ”€โ”€โ”€โ—โ”€โ”€โ”€โ”€โ—โ”€โ”€  (production)
               /      \
develop    โ”€โ”€โ”€โ—โ”€โ”€โ”€โ”€โ—โ”€โ”€โ”€โ—โ”€โ”€โ”€โ”€โ”€โ”€โ—โ”€โ”€  (integration)
             /    /     \
feature-x  โ”€โ—โ”€โ”€โ”€โ”€โ—       \
                  \       \
feature-y         โ—โ”€โ”€โ”€โ”€โ—โ”€โ”€โ”€โ—

Clear, descriptive branch names:

feature/user-authentication
bugfix/login-error-on-mobile
hotfix/critical-xss-patch
release/v1.2.0

One Commit = One Logical Change

# Don't do this
git commit -m "Fix login, update styles, add tests, refactor utils"

# Do this instead
git commit -m "fix(auth): resolve expired token causing 401 on refresh"
git commit -m "style(login): match button colors to updated brand guide"
git commit -m "test(auth): add unit tests for token refresh flow"
git commit -m "refactor(utils): extract date formatting into helper module"

Small, focused commits make code reviews easier, make git bisect useful, and make reverts surgical instead of catastrophic.

Always Pull Before You Push

git pull --rebase origin main
# then
git push origin main

This avoids unnecessary merge commits and keeps your history clean.

Set Up .gitignore Early

Create it in the root of your project before your first commit:

touch .gitignore

Common patterns to exclude:

# Dependencies
node_modules/
vendor/

# Environment variables (NEVER commit these)
.env
.env.local

# Build output
dist/
build/
out/

# IDE config
.vscode/
.idea/
*.swp

# OS files
.DS_Store
Thumbs.db

# Logs
*.log
npm-debug.log*

โš ๏ธ Common Mistakes

Coding for Hours Without Committing

I used to do this. Eight-hour coding session, one massive commit at the end. Don't do it. If something breaks or you need to undo part of your work, you've got nothing to fall back on.

Commit every time you finish something logical. A function works? Commit. A bug is fixed? Commit. It doesn't need to be perfect. That's what interactive rebase is for later.

Committing to the Wrong Branch

Everyone's done this at least once. Here's the recovery:

git reset --soft HEAD~1            # Undo the commit (keep your changes)
git stash                          # Stash the changes
git checkout -b correct-branch     # Create or switch to the right branch
git stash pop                      # Apply your changes here
git commit -m "Add feature"        # Commit on the correct branch

Takes 30 seconds once you know the steps.

Forgetting to Pull Before Starting Work

Start every work session with:

git pull origin main
git checkout -b new-feature

You don't want to build a whole feature on top of stale code and then discover ten merge conflicts when you try to merge.

Force Pushing to Shared Branches

# NEVER do this on main or develop
git push --force origin main

# If you must force push, only do it on your own feature branch:
git push --force-with-lease origin my-feature-branch

--force-with-lease is your safety net. It checks that nobody else pushed to the branch since your last fetch. Regular --force just overwrites everything blindly.

Accidentally Committing Large Files

Git doesn't handle binary files or large assets well. If you've committed a 500MB video file, your repo is going to be slow to clone forever because Git stores every version of every file.

Prevention:

# Set up Git LFS for large files
git lfs track "*.psd"
git lfs track "*.mp4"
git lfs track "*.zip"

If you've already committed a large file, use BFG Repo-Cleaner to scrub it from history.


๐ŸŽ“ Quick Reference Card

Setup

git config --global user.name "Name"
git config --global user.email "email"
git init
git clone [url]

Daily Workflow

git status
git add [file]
git commit -m "message"
git push
git pull

Branching

git branch [name]
git checkout [name]
git checkout -b [name]
git merge [branch]
git branch -d [name]

Inspecting History

git log
git log --oneline
git diff
git show [commit]

Undoing Things

git restore [file]
git reset HEAD~1
git revert [commit]
git stash

Remotes

git remote add origin [url]
git push origin [branch]
git pull origin [branch]
git fetch

๐Ÿš€ Where to Go from Here

You've got the core skills. Here's how to keep getting better:

  1. Use Git for everything. Even a throwaway script. Even your dotfiles. The muscle memory builds fast.
  2. Read commit history on open source projects. Seeing how experienced developers structure their commits is incredibly educational.
  3. Learn interactive rebase properly. git rebase -i changes how you think about commits once you really get it.
  4. Pick a branching workflow. GitFlow, GitHub Flow, or trunk-based development. Stick with one.
  5. Explore hooks and automation. Pre-commit hooks for linting, commit-msg hooks for format validation. Automate the boring parts.

Resources I Actually Recommend


๐Ÿ’ก Final Thoughts

  • Commit early, commit often. Push when it makes sense.
  • Read the error messages. Git actually tells you what went wrong and usually suggests the fix.
  • Don't panic. The reflog has your back for at least 30 days.
  • Branches are free. Use them for everything.
  • Pull before you push. Always.

Now stop reading and go build something. ๐ŸŽ‰


Questions? Find me on X/Twitter.

Working on a WordPress project?

Let's talk about what you're building.

I'm available for custom plugin development, performance optimization, and headless WordPress projects.