Git for Beginners: Commits, Branches, and Merge
Understand Git's mental model — commits, branches, and merge — with the commands you'll actually use every day, no fluff.
You made a change that broke everything, you don't remember what you edited, and your only backup is a file you emailed yourself yesterday. That has a name: no version control. Git exists so that scenario never has to happen — but the learning curve is steep enough that plenty of developers reach their first job without really understanding the mental model behind it.
What Git actually does
Git is not an automatic backup. It's a system that intentionally records the state of your project at moments you choose — and lets you navigate between those states, work on parallel versions, and merge changes from multiple people without overwriting each other's work.
The mental model that makes everything click: think of each commit as a snapshot of your project. You decide when to take the snapshot, what to include in it, and you write a caption. Git stores every snapshot and knows how to "go back" to any of them. Branches are alternate timelines — you fork at a point, work separately, and eventually merge the timelines back.
Installing and configuring
If you don't have Git installed yet:
# macOS (with Homebrew)
brew install git
# Ubuntu/Debian
sudo apt install git
# Windows: download at git-scm.com
After installing, set your name and email — they appear on every commit you make:
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
Initializing a repository
To start versioning an existing project:
cd my-project
git init
This creates a hidden .git folder — that's where Git stores all history. Never delete it manually.
If you're cloning an existing project (from GitHub, for example):
git clone https://github.com/user/repository.git
The basic cycle: working directory, staging, commit
This is where most people get confused. Git has three states for your files:
- Working directory — what you've edited but haven't told Git to save yet
- Staging area (index) — what you've marked to include in the next commit
- Repository — what's been committed and is part of the history
The flow is always: edit → add to stage → commit.
# Check the current state
git status
# Add a specific file to the stage
git add src/utils.js
# Add everything that changed
git add .
# Create the commit with a message
git commit -m "feat: add input validation"
The staging area exists because commits should be atomic — one cohesive change with a clear reason. If you edited three files but only two relate to what you were solving, you stage those two and commit. The third one waits for the next commit.
How to write a good commit message
Bad commit message: "changes", "fix", "wip".
Good commit message: "fix: handle zero quantity in discount calculation".
The most widely adopted standard is Conventional Commits: type: description in imperative mood. The most common types are feat (new feature), fix (bug fix), refactor (no behavior change), docs, test, chore.
You don't need to follow the standard from day one, but the golden rule is: the message should explain what and why, not how. The code already explains the how.
Branches: working in parallel
Imagine you want to add a new feature but don't want to mess with code that's working. That's what branches are for.
# See which branch you're on
git branch
# Create and switch to a new branch
git checkout -b feature/google-login
# (Modern equivalent, Git 2.23+)
git switch -c feature/google-login
The most common convention is main (or master) for stable code, and descriptive branches for everything in development: feature/name, fix/name, hotfix/name.
While you work on the new branch, main stays untouched. You can switch branches at any time (as long as you've committed your current changes):
git switch main
Merge: joining the timelines
When the feature is ready, you merge the changes back into main:
git switch main
git merge feature/google-login
Git will try to combine the two histories automatically. If the changes don't overlap, it succeeds — this is called a fast-forward or a merge commit depending on the history.
If the same lines were edited in both branches, you'll have a merge conflict. Git pauses and marks the problematic files like this:
<<<<<<< HEAD
return calculateDiscount(value, 0.1);
=======
return calculateDiscount(value, rate);
>>>>>>> feature/google-login
You resolve it manually — decide which version stays (or combine both), remove the markers, then:
git add resolved-file.js
git commit
A conflict isn't a Git error. It's Git saying "these two changes contradict each other — you decide."
Commands you'll use every day
Beyond the basic cycle, a few commands quickly become routine:
# See commit history
git log --oneline
# See what changed before committing
git diff
# See what's staged
git diff --staged
# Discard uncommitted changes in a file
git restore src/utils.js
# Create an alias (shortcut)
git config --global alias.st status
git log --oneline is particularly useful — each commit shows up on one line with a short hash and message. It's easy to scan history without noise.
Remote repositories: GitHub, GitLab, Bitbucket
Git is local by default. To collaborate or back up to the cloud, you connect to a remote repository:
# Add the remote (usually called "origin")
git remote add origin https://github.com/user/repo.git
# Push your commits to the remote
git push origin main
# Pull changes from the remote
git pull origin main
Typical team workflow: pull the latest changes from remote, work on your local branch, and when done, push and open a pull request for someone to review before merging into main.
Before opening the PR, it's worth reviewing the diff of what you're about to submit — it's easy to miss a stray console.log or a change unrelated to what you were solving. When I need to compare versions of a file outside a Git context, I use the Text Diff Checker — paste the before and after, and see the differences highlighted side by side.
Frequently asked questions
Can I undo a commit I already made?
Yes, but it depends on whether you've pushed yet. Locally, you have a few options:
# Undo the commit but keep changes in working directory
git reset --soft HEAD~1
# Undo the commit and unstage the changes
git reset --mixed HEAD~1
# Undo the commit and discard all changes (careful — irreversible)
git reset --hard HEAD~1
If the commit has already been pushed to a remote that others may have pulled from, use git revert instead of reset — it creates a new commit that undoes the previous one, without rewriting history.
What's the difference between merge and rebase?
Merge creates a join commit that preserves the complete history of both branches. Rebase rewrites your branch's commits as if they had been made on top of the destination branch — cleaner linear history, but it rewrites commit hashes.
For beginners: use merge. Once you understand the history model well, evaluate rebase — but never rebase commits that are already on a remote others have used.
What is .gitignore?
A file at the root of the project that tells Git which files to ignore. You don't want to version node_modules/, .env, build output, or local IDE config. The format is simple:
node_modules/
.env
dist/
*.log
.DS_Store
The site gitignore.io generates the file for any stack in seconds.
What's the difference between git fetch and git pull?
git fetch downloads changes from the remote but doesn't apply them to your working directory. git pull does a fetch and then automatically merges. For more control, you can fetch first and inspect the changes before applying — this avoids surprises when the remote changed in unexpected ways.
The mental model that carries everything
Git is confusing at first because most people learn the commands before learning the model. What holds everything together is simple: commits are immutable snapshots, branches are pointers to commits, and merge is combining two lines of history.
With that model in mind, commands like reset, rebase, and cherry-pick start to make intuitive sense — you're not manipulating files, you're manipulating pointers and history.
- 01 What DevOps Actually Is Beyond the Tools DevOps isn't a pipeline or a job title. It's shared ownership between the people who write code and the people who run it in production — and why most teams get it wrong.
- 02 Relational vs NoSQL: How to Actually Choose An honest comparison of relational and NoSQL databases — real tradeoffs on consistency, schema, and scale, with a clear default recommendation.