Git Complete Guide — What, Why, How, and When to Use Every Command

Git is the version control system used by virtually every software team on earth. This guide covers everything from the mental model to advanced workflows: commits, branches, merging, rebasing, undoing mistakes, stashing, tagging, and real team collaboration patterns with over 30 commands explained in context.

94%

of developers use Git

2005

created by Linus Torvalds

3

trees: working dir, index, HEAD

30+

commands covered in this guide

1

The Mental Model: 3 Trees

Git manages three "trees" (collections of files). Understanding these makes every command make sense immediately instead of feeling like memorized incantations:

Working Directory

The actual files on your disk. Where you edit code every day. Changes here are "unstaged" and not yet tracked by Git.

Staging Area (Index)

A snapshot of changes you have marked to include in the next commit. The `git add` command moves files here. Think of it as the draft commit.

Repository (HEAD)

The permanent history of commits. The `git commit` command moves staged changes here permanently. This is the timeline of your project.

1

Edit files in working directory

You write code, make changes, create or delete files. These exist only on your disk — Git knows about them but does not track them until you stage them.

2

Stage changes with git add

Choose which changes to include in your next commit. You can stage one file, multiple files, or specific lines within a file using git add -p.

3

Commit staged changes

Run git commit to permanently save the staged snapshot to the repository history with a message explaining what and why.

4

Push to remote

Run git push to share your commits with teammates via a remote repository (GitHub, GitLab, Bitbucket, or self-hosted).

2

Essential Daily Commands

bashgit-setup.sh
# Configure your identity (once per machine — appears in every commit)
git config --global user.name "Your Name"
git config --global user.email "you@example.com"

# Set default branch name to main (modern convention)
git config --global init.defaultBranch main

# Set preferred editor for commit messages
git config --global core.editor "code --wait"  # VS Code
git config --global core.editor "vim"           # Vim

# Initialize a new repo in current directory
git init

# Clone an existing repo (downloads entire history)
git clone https://github.com/user/repo.git

# Clone into a specific folder name
git clone https://github.com/user/repo.git my-folder
bashgit-daily-workflow.sh
# Check what's changed (always run this before staging)
git status

# See the actual diff — what changed in tracked files (unstaged)
git diff

# See diff of staged changes (what will go into next commit)
git diff --staged

# Stage specific files
git add file.js
git add src/components/Button.tsx

# Stage everything in current directory (use carefully)
git add .

# Interactively stage specific hunks (HIGHLY recommended!)
# Lets you review each change before staging it
git add -p

# Commit with message on command line
git commit -m "feat: add user authentication"

# Stage all tracked files AND commit in one step
# (does NOT stage new untracked files)
git commit -am "fix: correct null check in user service"

# View commit history
git log

# Compact one-line view (most useful)
git log --oneline

# With branch graph visualization
git log --oneline --graph --all

# See what files changed in each commit
git log --stat
3

Branching and Merging

Branches are cheap in Git — they are just a pointer (a file with a commit hash) to a single commit. Creating a branch costs almost nothing. Create a branch for every feature, bug fix, experiment, or anything you might want to discard later.

bashgit-branches.sh
# List all local branches (* marks current branch)
git branch

# List all branches including remote-tracking branches
git branch -a

# Create a new branch (stays on current branch)
git branch feature/user-auth

# Create AND switch to new branch (most common workflow)
git checkout -b feature/user-auth

# Modern equivalent (Git 2.23+)
git switch -c feature/user-auth

# Switch to an existing branch
git checkout main
git switch main    # modern equivalent

# Rename current branch
git branch -m new-name

# Delete a merged branch (safe — fails if unmerged)
git branch -d feature/user-auth

# Force delete (even if unmerged — permanent data loss risk!)
git branch -D feature/user-auth

# Merge a branch into current branch
git checkout main
git merge feature/user-auth

# Merge with no fast-forward (always create a merge commit)
git merge --no-ff feature/user-auth

# See which branches are merged into current branch
git branch --merged

# See which branches are NOT yet merged
git branch --no-merged
4

Rebase vs Merge

Both integrate changes from one branch into another. The difference is how they rewrite history — and this matters deeply for collaboration with a team.

Itemgit mergegit rebase
HistoryPreserves all branch history + adds a merge commitRewrites commits onto target branch — linear history
Safety for shared branchesSafe on any branch including shared remote branchesNever rebase shared branches — rewrites history for others
Log readabilityShows when branches diverged and merged (more accurate)Cleaner, linear log (looks like no branching happened)
Conflict handlingResolve conflicts once during mergeMay need to resolve conflicts for each replayed commit
Best used whenMerging completed feature into main branchUpdating your local feature branch with latest main changes
Commit countAdds one merge commit to the logNo extra commits — rewrites existing ones
bashgit-rebase.sh
# On your feature branch, update it with latest main:
git checkout feature/my-feature
git rebase main   # replays your commits on top of latest main

# If conflicts arise during rebase:
# 1. Fix the conflict in the file
git add conflicted-file.js
git rebase --continue

# Abort the entire rebase (restore to pre-rebase state)
git rebase --abort

# Interactive rebase — squash, edit, reorder, drop commits:
git rebase -i HEAD~3   # edit the last 3 commits interactively
# In the editor: pick / squash / reword / edit / drop

# Squash all feature branch commits into one before merging:
# From main:
git merge --squash feature/my-feature
git commit -m "feat: complete user authentication feature"
5

Undoing Mistakes

This is where Git's real power shows. Almost nothing is truly lost in Git — every commit is preserved in the reflog for at least 30 days. But you need the right command for each scenario:

1

Undo unstaged changes (restore from HEAD)

Discard changes in working directory that have not been staged yet. This is irreversible for unsaved changes.

2

Undo staged changes (keep the edits)

Move changes back from staging area to working directory. Your edits are preserved, just unstaged.

3

Amend the last commit

Fix the commit message or add a forgotten file to the last commit. Only use this for commits that have NOT been pushed to a shared remote.

4

Revert a commit (safe for shared branches)

Creates a new commit that undoes the changes from a previous commit. Preserves the full history — the safest undo for shared branches.

5

Reset to a previous commit

Moves HEAD back to a past commit. Three modes: soft (keep staged), mixed (keep working dir), hard (discard everything). Dangerous on shared branches.

bashgit-undo.sh
# Discard unstaged changes in a specific file
git restore file.js
git checkout -- file.js   # older syntax (same effect)

# Discard ALL unstaged changes (irreversible!)
git restore .

# Unstage a file (move from staging back to working dir)
git restore --staged file.js
git reset HEAD file.js   # older syntax

# Unstage everything
git restore --staged .

# Amend the last commit message
git commit --amend -m "corrected commit message"

# Add forgotten file to last commit (same message)
git add forgotten-file.js
git commit --amend --no-edit

# Revert a specific commit (creates an undo commit)
git revert abc1234

# Reset to 1 commit back — keep changes staged
git reset --soft HEAD~1

# Reset to 1 commit back — keep changes in working dir (default)
git reset HEAD~1
git reset --mixed HEAD~1

# Reset to 1 commit back — DISCARD all changes (DANGEROUS!)
git reset --hard HEAD~1

# Recover deleted commits via reflog (within ~30 days)
git reflog                         # find lost commit hash
git checkout -b recovery abc1234   # create branch at lost commit

Never force push to main or master

`git push --force` on a shared branch rewrites remote history and breaks everyone else's local repositories. If you need to force push on your own feature branch, use `git push --force-with-lease` instead — it fails if someone else pushed since you last pulled.
6

Working with Remotes

bashgit-remotes.sh
# List remotes with their URLs
git remote -v

# Add a remote (origin is the conventional default name)
git remote add origin https://github.com/user/repo.git

# Change a remote URL (e.g. after renaming a repo)
git remote set-url origin https://github.com/user/new-name.git

# Fetch changes from remote WITHOUT merging
git fetch origin

# Compare your local main with remote after fetching
git diff main origin/main

# Pull = fetch + merge (most common)
git pull origin main

# Pull with rebase instead of merge (linear history)
git pull --rebase origin main

# Push your branch to remote
git push origin feature/my-feature

# Push and set upstream tracking (first push of a new branch)
git push -u origin feature/my-feature
# After -u is set, you can just run: git push

# Delete a remote branch
git push origin --delete feature/old-branch
7

Stashing

Need to switch branches but have unfinished work you are not ready to commit? Stash saves your changes temporarily in a stack so you can switch contexts and come back later.

bashgit-stash.sh
# Stash current changes (tracked files only)
git stash

# Stash with a descriptive message
git stash push -m "WIP: half-done auth refactor"

# Stash including untracked new files
git stash push -u -m "WIP: new feature including new files"

# List all stashes
git stash list
# → stash@{0}: WIP: half-done auth refactor
# → stash@{1}: WIP on main: abc1234 prev commit

# Apply the most recent stash (keeps it in stash list)
git stash apply

# Apply AND remove from stash list
git stash pop

# Apply a specific stash by index
git stash apply stash@{2}

# Show what a stash contains
git stash show stash@{0}
git stash show -p stash@{0}   # with full diff

# Drop a specific stash
git stash drop stash@{0}

# Drop all stashes
git stash clear
8

Tagging Releases

bashgit-tags.sh
# List all tags
git tag

# Create a lightweight tag (just a pointer to a commit)
git tag v1.0.0

# Create an annotated tag (recommended — stores metadata)
git tag -a v1.0.0 -m "Release version 1.0.0 — initial stable release"

# Tag a specific past commit
git tag -a v1.0.0 abc1234 -m "Release 1.0.0"

# Push a specific tag to remote
git push origin v1.0.0

# Push all tags to remote
git push origin --tags

# Delete a local tag
git tag -d v1.0.0

# Delete a remote tag
git push origin --delete v1.0.0

# Checkout the code at a specific tag (detached HEAD)
git checkout v1.0.0
9

Commit Message Best Practices

Useless commit history

❌ Bad
# Vague, useless commit messages
git commit -m "fix"
git commit -m "changes"
git commit -m "asdf"
git commit -m "WIP"
git commit -m "stuff"
git commit -m "final version"
git commit -m "final version 2"

Conventional Commits format

✅ Good
# Conventional Commits format: type(scope): description
# Types: feat, fix, refactor, docs, test, chore, perf, style

git commit -m "feat(auth): add JWT refresh token rotation"
git commit -m "fix(api): handle null response from user service"
git commit -m "refactor(db): extract query builder to separate module"
git commit -m "docs(readme): add local development setup guide"
git commit -m "test(auth): add unit tests for token expiry edge cases"
git commit -m "perf(images): add lazy loading to product gallery"
git commit -m "chore(deps): upgrade Next.js 13 to 14"

Why Conventional Commits?

Conventional Commits format enables automated changelog generation, semantic versioning (auto-bump major/minor/patch based on commit type), and searchable history. Tools like semantic-release and release-please parse your commit messages to automate the entire release workflow.

10

Git Flow — Team Branching Strategy

main (or master)

Always deployable to production. Only production-tested code lives here. Never commit directly to main — always merge via pull request after review.

develop

Integration branch for the next release. Features are merged here first for integration testing before going to main. Some teams skip this and merge directly to main.

feature/name

Branch from develop (or main). One feature per branch. Named descriptively: feature/user-auth, feature/payment-gateway. Merge back when complete and reviewed.

hotfix/name

Emergency production fix. Branch from main directly, fix the bug, then merge back to both main AND develop. Deploy immediately from main after merging.

release/1.2.0

Created from develop when preparing a release. Only bug fixes go in here. Merged to both main (tag the release) and develop when finished.

Trunk-based development (simpler alternative)

Skip the develop branch entirely. All features merge directly to main behind feature flags. Faster and simpler for small-to-medium teams with good CI/CD.

11

Advanced Commands Worth Knowing

ItemCommandWhen to Use It
git cherry-pick abc1234Apply a specific commit from another branch to current branchBackporting a fix to an older release branch
git bisect startBinary search through commits to find when a bug was introducedYou know it worked 3 months ago but not when it broke
git blame file.jsShow who last modified each line of a file and in which commitUnderstanding why a line was written before changing it
git shortlog -snSummarize commit count per authorTeam activity overview or contribution stats
git clean -fdRemove all untracked files and directoriesClean build artifacts or generated files not in .gitignore
git worktree addCheck out multiple branches simultaneously in separate directoriesWork on a hotfix while keeping feature branch open

Frequently Asked Questions

Related Developer Tools & Git Guides

Continue with closely related troubleshooting guides and developer workflows.