Tools & Workflow
A Practical Introduction to Git and Version Control
Updated 2026-07-12 · 7 min read
Git feels intimidating because most people learn it as a list of magic incantations. You memorize git commit, git push, and a rescue command you found on the internet, and you hope nothing goes wrong. But Git is far less scary once you understand the small model underneath it. Almost every command becomes obvious once you can picture what it's doing.
This guide builds that mental model first, then walks through the commands you'll use every day, and finishes with how to undo things safely — because undoing mistakes calmly is what separates comfortable Git users from anxious ones.
The mental model
Git tracks snapshots of your project over time. Each snapshot is a commit — a complete picture of your files at a moment, plus a message and a pointer to the commit that came before it. Chain those pointers together and you get history.
There are three places your work lives:
- Working directory — the actual files you edit.
- Staging area (index) — a holding zone for changes you've marked as ready to commit.
- Repository — the committed history, stored in the hidden
.gitfolder.
The staging area is the part beginners skip, and it's the key to good commits. It lets you choose exactly which changes go into the next snapshot, so a single commit can be one clean idea instead of a dump of everything you touched.
edit files → git add → staging area → git commit → repository
(working dir) (index) (history)A commit is identified by a hash like a1b2c3d. A branch is just a lightweight, movable label pointing at a commit. HEAD is a pointer to wherever you currently are. That's the whole core.
Getting started
Set your identity once, then create or clone a repository.
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
# start a new repo in the current folder
git init
# or copy an existing one
git clone https://github.com/someone/project.gitCheck status constantly. git status is the command you'll run more than any other — it tells you what's changed, what's staged, and what branch you're on.
git status
git status -s # short, compact outputThe everyday loop
Ninety percent of Git use is the same short cycle: change files, stage them, commit, repeat.
git add file.js # stage one file
git add . # stage everything changed
git commit -m "Add login form validation"Write commit messages that explain why, not just what. A good message is a short imperative summary: "Fix off-by-one in pagination," not "changes" or "stuff." Future you, reading the log at 2am, will be grateful.
Look at history with git log:
git log --oneline --graph --decorateThat flag combination gives you a compact, visual history that shows branches — it's worth aliasing so you type it often.
Branching and merging
A branch lets you work on something without disturbing the main line. Because branches are just pointers, creating one is instant and cheap.
git switch -c feature/search # create and switch to a new branch
# ...make commits...
git switch main # go back to main
git merge feature/search # bring the work inWhen the branches haven't diverged, Git does a fast-forward — it just slides the label forward. When both branches have new commits, Git creates a merge commit that ties the two histories together.
If both branches changed the same lines, you get a merge conflict. Git pauses and marks the clashing region in the file:
<<<<<<< HEAD
color = "blue"
=======
color = "green"
>>>>>>> feature/searchResolving a conflict is manual and low-drama: edit the file to the version you actually want, delete the marker lines, then stage and commit.
# edit the file to resolve
git add file.css
git commit # completes the mergeConflicts feel scary the first time, but they're just Git honestly telling you it can't guess your intent. You decide, and move on.
Working with a remote
A remote is a copy of the repository hosted somewhere else, usually called origin. You sync with it by pushing and pulling.
git push origin main # send your commits up
git pull origin main # fetch and merge others' commits down
git fetch # download changes WITHOUT merging themgit fetch then reviewing before you merge is safer than a blind git pull, because you see what's coming before it touches your working files.
Before pushing a shared branch, pull first so you integrate others' work and avoid a rejected push.
Undoing things without panic
This is the section people wish they'd read first. The right undo depends on where the change is.
Unstage a file (keep the edits, just remove it from staging):
git restore --staged file.jsDiscard uncommitted changes in a file (this throws work away — be sure):
git restore file.jsFix the last commit message or add a forgotten file to it:
git commit --amend -m "Better message"Undo a commit but keep the changes as uncommitted edits:
git reset --soft HEAD~1Undo a commit and its changes entirely (destructive to those changes):
git reset --hard HEAD~1The safest option for shared history is git revert, which makes a new commit that reverses an old one — nothing is rewritten, so it's safe on branches other people use:
git revert a1b2c3dRule of thumb: rewrite history (reset, amend, rebase) only on commits you haven't shared. Once others have your commits, prefer revert.
And the ultimate safety net — git reflog records where HEAD has been, so you can recover commits that seem lost:
git reflog
git reset --hard HEAD@{2} # jump back to a previous stateA sane everyday workflow
You don't need a heavyweight process for personal or small-team work. This is enough:
- Pull the latest
main. - Create a branch for the task:
git switch -c fix/typo-in-header. - Make small, focused commits as you go.
- Push the branch and open a pull request for review.
- Merge when it's approved and green, then delete the branch.
Keep branches short-lived. The longer a branch lives, the more it drifts from main and the uglier the eventual merge. A .gitignore file keeps junk — build output, secrets, node_modules — out of history from the start:
node_modules/
.env
dist/
*.logIf you're on Linux or macOS, many of these commands feel natural alongside shell basics — see Linux for developers.
Common mistakes
- Committing everything at once. Use the staging area to make each commit one coherent idea.
- Vague commit messages. "fix" and "wip" tell future you nothing. Explain the why.
- Committing secrets. Once a password or key is in history, treat it as leaked. Add a
.gitignorebefore your first commit. git reset --hardon shared branches. You'll rewrite history others depend on. Userevertinstead.- Fear of branches. They're cheap and disposable. Make them freely.
- Blind
git pullinto a dirty working directory. Stash or commit first so a merge doesn't tangle with your uncommitted edits. - Letting a branch live for weeks. Merge conflicts grow with time apart. Integrate often.
Checklist
- Can I explain the difference between working directory, staging area, and repository?
- Do my commit messages say why, in the imperative mood?
- Am I working on a branch, not directly on
main? - Do I have a
.gitignorebefore the first commit? - Do I know how to unstage, discard, and revert — and which is safe to share?
- Do I pull (or fetch) before I push a shared branch?
- Do I know
git reflogexists as my safety net?
Keep learning
The fastest way to get comfortable is to make a throwaway repo and deliberately break it: create conflicts, reset commits, recover them with reflog. Nothing is at stake, so you can be fearless.
Concrete next steps:
- Learn interactive rebase to clean up messy local history before sharing (
git rebase -i). - Set up a couple of aliases for the commands you type most.
- Read structuring a software project so your repos are organized before they grow.
- Pair Git with a real workflow habit — small branches, frequent integration — from building a sustainable learning system, applied to how you ship, not just how you study.
