How to Use Git: Beginner’s Guide to Version Control (2026)
Here is what development looks like without version control: you rename app_final.py to app_final_v2.py to app_FINAL_USE_THIS.py. A teammate overwrites your changes. You need to roll back a change from last Tuesday but cannot find where the working version is. You are afraid to refactor anything because you might break something and have no way back.
Git solves all of this. According to the Stack Overflow Developer Survey 2025, Git is used by 97.7% of professional developers — the most universally adopted tool in the survey’s history. Git was created by Linus Torvalds in 2005 specifically to manage the Linux kernel, which receives thousands of patches from hundreds of contributors per release cycle. As of 2025, the Linux kernel Git repository contains over 1.1 million commits from more than 25,000 contributors.
This guide covers everything from your first git init to the branching workflows used at companies with hundreds of engineers. Every section includes the mistakes beginners make and the mental models that prevent them.
Key Takeaways
- ▸The three-area model is everything: working directory → staging area → repository. All Git commands move changes between these three areas.
- ▸Branches are free: creating a branch takes milliseconds and costs almost no disk space. Use one per feature, bug fix, or experiment — never commit directly to main.
- ▸git fetch vs git pull: fetch downloads changes but does not apply them. Pull = fetch + merge. Always fetch first on shared branches to see what is incoming.
- ▸Never rebase pushed commits: rebase rewrites commit hashes. Rebasing commits already on a shared remote branch will break your teammates’ histories.
- ▸Write meaningful commit messages: “fix bug” is useless in 6 months. Format: imperative verb + what changed + why if non-obvious. E.g., “Fix pagination off-by-one on last page”.
Installing Git and First-Time Setup
# Install
# macOS (via Homebrew)
brew install git
# Ubuntu / Debian
sudo apt update && sudo apt install git
# Windows — download Git for Windows (includes Git Bash)
# https://git-scm.com/download/win
# Verify installation
git --version # git version 2.47.x
# First-time global config (required before your first commit)
git config --global user.name "Your Name"
git config --global user.email "[email protected]"
# Set default branch name to 'main' (Git 2.28+)
git config --global init.defaultBranch main
# Set your preferred editor for commit messages
git config --global core.editor "code --wait" # VS Code
git config --global core.editor "vim" # Vim
git config --global core.editor "nano" # Nano
# Useful aliases
git config --global alias.st status
git config --global alias.co checkout
git config --global alias.lg "log --oneline --graph --decorate --all"
# Verify your config
git config --list --globalThe Three-Area Model: The Mental Model That Unlocks Git
Every confusing Git behavior becomes logical once you internalize the three areas Git uses to track your work:
| Area | What it is | How to move changes here | How to undo |
|---|---|---|---|
| Working Directory | Your file system — what you see in your editor | Edit files normally | git restore <file> |
| Staging Area (Index) | A snapshot of what your next commit will contain | git add <file> | git restore --staged <file> |
| Repository (.git) | The full history of committed snapshots | git commit | git reset / git revert |
The staging area is what surprises most beginners. It exists so you can craft precise commits: you can edit 10 files but stage only 3 of them for a focused commit, then stage the rest later. This is called atomic committing — each commit represents one logical change, making history navigable and blame meaningful.
Starting a Repository
# Option A: Start a new repository from scratch
mkdir my-project && cd my-project
git init # creates a hidden .git directory
# this is the entire Git database for your project
# Option B: Clone an existing repository from GitHub / GitLab
git clone https://github.com/org/repo.git
git clone https://github.com/org/repo.git my-custom-folder # into custom directory
git clone [email protected]:org/repo.git # SSH (faster for frequent pushes)
# After init, check what Git sees
git status # shows working directory and staging area stateCreating a .gitignore Before Your First Commit
The biggest mistake beginners make is committing node_modules, .env files, or IDE folders. Create your .gitignore before your first commit:
# .gitignore — Node.js project example
node_modules/
dist/
build/
.env
.env.local
.env.*.local
*.log
logs/
.DS_Store
Thumbs.db
.idea/
.vscode/
# Python project example
__pycache__/
*.py[cod]
*.egg-info/
.venv/
venv/
dist/
.env
.pytest_cache/
# Patterns
*.log # any file ending in .log
logs/ # any directory named logs
!logs/.gitkeep # exception: keep the directory but not its contents
/config.json # only config.json at root level, not nested ones
config?.json # config1.json, configA.json (? = one character)The Core Workflow: Stage, Commit, Repeat
# Check current state — run this constantly when learning
git status
# Stage specific files
git add src/auth.js
git add src/auth.js src/middleware.js # multiple files
# Stage an entire directory
git add src/
# Stage all changes in the current directory
git add . # use with care — check git status first
# Stage parts of a file interactively (hunk by hunk)
git add -p src/auth.js # shows each change, y/n to stage
# Review what is staged before committing
git diff --staged # shows staged changes (not working dir)
git diff # shows unstaged changes (working dir vs staging)
# Commit what is staged
git commit -m "Add JWT validation to auth middleware"
# Stage all tracked files and commit in one step (skips staging for untracked files)
git commit -am "Fix typo in error message"
# Amend the last commit (only if not yet pushed)
git commit --amend -m "Better commit message"
git commit --amend --no-edit # add staged changes to last commit without editing messageWriting Good Commit Messages
The Conventional Commits specification, adopted by major projects including Angular, Vue.js, and Electron, defines a structured commit message format that makes automated changelog generation and semantic versioning possible. Even without tooling, the format improves readability:
# Format: type(scope): description
# Types: feat, fix, docs, style, refactor, test, chore, perf
feat(auth): add OAuth2 login with Google
fix(api): handle null user_id in order creation
docs(readme): update local development setup steps
refactor(db): extract connection pooling to separate module
test(auth): add unit tests for JWT expiry edge cases
chore(deps): upgrade express from 4.18 to 4.21
# Multi-line commit (first line = title, blank line, then body)
git commit -m "Fix race condition in session refresh
The session refresh endpoint was not atomic — two concurrent requests
could both pass the expiry check and both get new tokens. Fixed with
a database-level lock on the session row.
Fixes #342"
# BAD commit messages (avoid these)
git commit -m "fix"
git commit -m "wip"
git commit -m "changes"
git commit -m "asdf"Viewing History
# Full log
git log
# Compact one-line view
git log --oneline
# With branch graph (essential for visualizing branches)
git log --oneline --graph --decorate --all
# Show commits by a specific author
git log --author="Alice"
# Show commits that changed a specific file
git log --oneline -- src/auth.js
# Show commits since a date
git log --since="2026-01-01" --until="2026-04-01"
# Search commit messages
git log --grep="authentication"
# Show what changed in each commit
git log -p # full diffs
git log --stat # summary of files changed
# Inspect a specific commit
git show abc1234 # by hash
git show HEAD # most recent commit
git show HEAD~2 # two commits before HEAD
# Who last modified each line of a file
git blame src/auth.js
git blame -L 10,25 src/auth.js # only lines 10–25Branching: The Core of Git Collaboration
A Git branch is a 41-byte file containing a commit hash. Creating or switching branches is instant. This is fundamentally different from version control systems like SVN or Perforce, where branching was expensive enough that teams avoided it. In Git, branching is the default pattern for all work.
# List branches
git branch # local branches
git branch -a # local + remote branches
git branch -v # with last commit message
# Create a branch
git branch feature/user-auth
# Switch to a branch
git checkout feature/user-auth
git switch feature/user-auth # modern syntax (Git 2.23+)
# Create and switch in one step
git checkout -b feature/user-auth
git switch -c feature/user-auth # modern syntax
# Rename a branch
git branch -m old-name new-name
# Delete a branch (must be merged)
git branch -d feature/user-auth
# Force delete (unmerged — data loss possible)
git branch -D feature/user-auth
# Show which branches are fully merged into main
git branch --merged mainMerging Branches
# Merge a feature branch into main
git checkout main
git merge feature/user-auth
# Types of merges:
# Fast-forward: no divergence — main pointer just moves forward
# Three-way merge: branches diverged — Git creates a merge commit
# Force a merge commit even when fast-forward is possible
git merge --no-ff feature/user-auth # preserves branch topology in history
# Squash: combine all feature commits into one before merging
git merge --squash feature/user-auth
git commit -m "feat: add user authentication"
# Abort a merge in progress
git merge --abort
# When a merge conflict occurs:
# 1. Git marks the conflicted files
git status # shows 'both modified' files
# 2. Open the file — look for conflict markers:
# <<<<<<< HEAD
# your changes
# =======
# incoming changes
# >>>>>>> feature/user-auth
# 3. Edit the file to resolve, then:
git add src/auth.js # mark as resolved
git commit # complete the mergeRebasing: Linear History Without Merge Commits
Rebase replays your branch’s commits on top of another branch’s tip. The result is a linear history with no merge commits — cleaner in theory, but it rewrites commit hashes. The golden rule of rebasing: never rebase commits that have already been pushed to a shared remote branch. Your teammates’ histories will diverge and you will have a very bad day.
# Rebase your feature branch onto main
# (safe: your feature branch is local / not shared)
git checkout feature/user-auth
git rebase main
# Rebase conflicts work like merge conflicts:
# Fix the file, then:
git add src/auth.js
git rebase --continue # apply next commit
git rebase --abort # undo the entire rebase
# Interactive rebase: edit, squash, reorder your local commits
# Rewrite the last 3 commits:
git rebase -i HEAD~3
# In the editor that opens, each line is a commit:
# pick abc1234 Add login endpoint
# pick def5678 Fix typo in login endpoint
# pick ghi9012 Add tests for login endpoint
# Change 'pick' to:
# squash (s): combine with previous commit
# fixup (f): combine, discard this commit's message
# reword (r): keep commit, edit the message
# drop (d): delete this commit entirely
# edit (e): pause to amend this commit| Scenario | Use merge or rebase? | Why |
|---|---|---|
| Integrating a feature into main | Merge (or squash merge) | Preserves branch context; safe on shared branches |
| Updating a local feature branch with latest main | Rebase | Keeps linear history, not yet pushed |
| Cleaning up messy local commits before PR | Interactive rebase | Squash WIP commits into logical units |
| Updating a branch already pushed to remote | Never rebase | Rewrites hashes, breaks teammates’ history |
Working with Remotes (GitHub, GitLab, Bitbucket)
# List remotes
git remote -v
# Add a remote (typically done automatically by git clone)
git remote add origin https://github.com/you/repo.git
# Push to remote
git push origin main # push main branch
git push -u origin feature/user-auth # push and set upstream tracking
git push # push current branch (after -u set)
git push --force-with-lease # safer force push (fails if remote changed)
# Fetch without merging (safe — look before you merge)
git fetch origin # download all remote changes
git fetch origin main # download only main
# Inspect what is on the remote before pulling
git log origin/main --not main --oneline # commits on remote not in local
# Pull (fetch + merge)
git pull origin main
git pull # from tracked upstream
# Pull with rebase (keeps history linear)
git pull --rebase origin main
git config --global pull.rebase true # make rebase the default for all pulls
# Delete a remote branch
git push origin --delete feature/old-branch
# Show remote tracking branches
git branch -rUndoing Mistakes
Git’s undo commands are the most feared part of the tool for beginners — mostly because the difference between --soft, --mixed, and --hard is not obvious. Here is the complete map:
# Discard changes in working directory (un-edit a file)
git restore src/auth.js # Git 2.23+
git checkout -- src/auth.js # older syntax
# Unstage a file (keep changes in working directory)
git restore --staged src/auth.js # Git 2.23+
git reset HEAD src/auth.js # older syntax
# Undo the last commit (keep changes staged)
git reset --soft HEAD~1
# Undo the last commit (keep changes in working directory, unstaged)
git reset --mixed HEAD~1 # default if you just run: git reset HEAD~1
# Undo the last commit and DISCARD all changes (destructive)
git reset --hard HEAD~1
# Undo a pushed commit safely: create a new commit that reverses it
git revert HEAD # revert last commit, opens editor
git revert abc1234 # revert a specific commit
git revert HEAD~3..HEAD # revert last 3 commits
# Find lost commits (reflog is a safety net)
git reflog # shows every HEAD movement including resets
git checkout abc1234 # check out a "lost" commit to recover work
git branch recovery-branch abc1234 # create branch from lost commit
# Temporarily save work without committing
git stash # stash all uncommitted changes
git stash save "WIP: user auth flow" # with description
git stash list # see all stashes
git stash pop # apply most recent stash and remove it
git stash apply stash@{2} # apply specific stash, keep in stash list
git stash drop stash@{2} # remove a specific stashGit Workflows for Teams
According to GitHub’s 2025 Octoverse report, there are over 4 billion contributions annually across GitHub repositories. Coordination at that scale requires consistent branching conventions. Three workflows dominate:
| Workflow | Branch structure | Best for | Complexity |
|---|---|---|---|
| GitHub Flow | main + feature branches | Continuous deployment, SaaS | Low |
| Git Flow | main, develop, feature, release, hotfix | Versioned software releases | High |
| Trunk-Based | main only (short-lived branches, ≤1 day) | High-velocity CI/CD teams | Low (with feature flags) |
For most teams, GitHub Flow is the right default: every change starts on a feature branch, gets reviewed via a pull request, then merges into main which deploys immediately. Git Flow’s additional layers (develop, release branches) add overhead that only makes sense when you ship versioned software on a fixed release cadence.
For a deep dive on branching models with real tradeoffs, see Git Branching Strategies and Git Rebase vs Merge.
Useful Git Commands You Probably Missed
# Cherry-pick: apply a specific commit from another branch
git cherry-pick abc1234 # apply commit to current branch
git cherry-pick abc1234 def5678 # multiple commits
# Bisect: binary search to find which commit introduced a bug
git bisect start
git bisect bad # current commit has the bug
git bisect good v1.0.0 # this tag was good
# Git checks out the middle commit — test it, then:
git bisect good # or git bisect bad
# Git halves the range each time — finds the culprit in O(log n) steps
git bisect reset # when done, return to original HEAD
# Clean: remove untracked files
git clean -n # dry run — show what would be removed
git clean -fd # remove untracked files and directories
# Worktree: check out multiple branches simultaneously (different directories)
git worktree add ../project-hotfix hotfix/payment-bug
# Now you have two working directories on two branches at once
# Tags: mark release points
git tag v1.0.0 # lightweight tag
git tag -a v1.0.0 -m "Initial release" # annotated (preferred for releases)
git push origin v1.0.0 # push a specific tag
git push origin --tags # push all tags
git tag --list # list all tagsFAQ
What is the difference between git fetch and git pull?
git fetch downloads commits from the remote and updates remote-tracking branches (origin/main) without touching your working directory or local branches. git pull is git fetch followed by git merge (or rebase if configured). Use git fetch first on shared branches to review incoming changes before merging: run git fetch origin, then git log origin/main to inspect.
What is the difference between git merge and git rebase?
git merge creates a merge commit that preserves the full history of when branches diverged and reunited. git rebase replays your commits on top of another branch, producing a linear history without merge commits. Rebase rewrites commit hashes — never rebase commits already pushed to a shared remote branch.
How do I undo a git commit?
To undo the last commit but keep changes staged: git reset --soft HEAD~1. To undo and unstage (keep files modified): git reset --mixed HEAD~1. To completely discard: git reset --hard HEAD~1. If the commit was already pushed, prefer git revert HEAD to create a new undoing commit — never force-push to shared branches.
What is a Git branch and why use one?
A branch is a lightweight movable pointer to a commit — creating one takes milliseconds and costs almost no disk space. Branches let you develop features or fix bugs in complete isolation from the main codebase. The pattern: create a branch per task, commit to it, merge or rebase back into main when done. Main stays always deployable.
What should I put in a .gitignore file?
Exclude generated files (node_modules/, dist/, __pycache__/), environment secrets (.env, credentials.json), OS files (.DS_Store, Thumbs.db), IDE configs (.idea/, .vscode/), log files (*.log), and build artifacts. Use gitignore.io to generate a template for your stack. Add .gitignore before your first commit.
How do I resolve a merge conflict in Git?
Git marks conflicts with <<<<<<< HEAD (your changes), ======= (separator), >>>>>>> branch-name (incoming). Open each conflicted file, edit to keep the right version, remove conflict markers, then git add the resolved file and git commit to complete the merge. Run git status to see which files still have conflicts.
What is git stash and when should I use it?
git stash temporarily shelves uncommitted changes so you can switch branches with a clean working directory. Use it when you need to pull updates or switch context mid-task without committing half-finished work. git stash pop restores the latest stash. git stash list shows all saved stashes. Stash is short-term — commit or branch for anything you need longer.
Keep a Git Command Reference Handy
Once you have the concepts down, keep the Git Cheat Sheet open for command syntax. For CI/CD and environment variable management, see the guides below.