Skip to content

Essential Git Commands: The Complete Developer Cheat Sheet

Essential Git Commands: The Complete Developer Cheat Sheet

A subtle bug caused sporadic payments to fail in production. Somewhere in 200+ commits that week, a <= became < in price rounding. One engineer ran git bisect start, marked the current commit bad and last week's deploy good, then let Git binary-search. Seven test runs later—about 4 minutes—bisect found the exact commit. Without it, half a day of manual diff reading.

TL;DR

Every Git command moves code between four places: working directory (your files) → staging area (git add) → local repository (git commit) → remote server (git push). Master the 10 core commands for daily work and six advanced operations for when things go wrong. Use --force-with-lease instead of --force to protect coworkers' commits. Keep branches short-lived, squash messy commits before pushing, and use git bisect to find breaking changes in minutes instead of hours.

  • 10 core commands: status, add, commit, push, pull, branch, checkout, merge, diff, log
  • 6 advanced: rebase, cherry-pick, stash, reflog, bisect, tag
  • Recovery tools: git reset (undo locally), git revert (undo publicly), git reflog (find lost commits)

Pick the Right Recovery Path

Most production git emergencies are "I broke history — what's safe?" The wrong undo command corrupts shared branches and forces other engineers to re-clone. Route by what you need to undo:

graph TD
    Start[I broke something] --> Where{Where did<br/>the change go?}
    Where -->|Working dir<br/>not staged| WD[git restore file<br/>git restore --source HEAD~1]
    Where -->|Staged<br/>not committed| Stg[git restore --staged file<br/>git reset HEAD]
    Where -->|Committed locally<br/>not pushed| Local{Keep changes<br/>or discard?}
    Where -->|Pushed to remote<br/>shared branch| Remote[git revert SHA<br/>creates new commit<br/>NEVER force-push]
    Where -->|Lost commit<br/>cannot find SHA| Reflog[git reflog<br/>git checkout SHA<br/>git cherry-pick]
    Local -->|Keep changes| Soft[git reset --soft HEAD~1]
    Local -->|Discard changes| Hard[git reset --hard HEAD~1<br/>destructive — verify first]
    style Remote fill:#fdd
    style Hard fill:#fdd
    style Reflog fill:#dfd
    style Soft fill:#dfd
    style WD fill:#dfd
    style Stg fill:#dfd

The diagram is the audit trail for every git emergency: never --hard reset shared history, never force-push main. Recovery via reflog saves more careers than any other git feature[Git docs].

The 10 essential commands

[Git docs]

Every Git workflow is built from these. Commit them to muscle memory:

CommandPurposeExample
git statusShow modified files and branch infogit status
git addStage changes for commitgit add . or git add file.txt
git commit -mCreate a snapshot with messagegit commit -m "Fix auth bug"
git pushSend commits to remotegit push origin main
git pullFetch and merge remote changesgit pull origin main
git branchList or create branchesgit branch -a or git branch feature-x
git checkout / git switchChange branches or restore filesgit switch -c feature-x
git mergeCombine branch historiesgit merge feature-x
git diffShow unstaged changesgit diff or git diff --staged
git logView commit historygit log --oneline --graph

Setup and core workflow

The git data flow — every command moves files between four locations:

graph LR
    WD[Working directory<br/>your edited files] -->|git add| Stage[Index aka<br/>staging area]
    Stage -->|git commit| Local[Local repo<br/>.git/objects]
    Local -->|git push| Remote[Remote repo<br/>origin/main]
    Remote -->|git fetch| Local
    Local -->|git checkout / restore| WD
    Stage -->|git restore --staged| WD
    Local -.->|git stash| Stash[Stash stack<br/>WIP shelf]
    Stash -.->|git stash pop| WD
    Local -.->|git reflog<br/>90-day safety net| Recovery[Recovery via<br/>git checkout SHA]
    style WD fill:#fdd
    style Stage fill:#ffd
    style Local fill:#dfd
    style Remote fill:#dfd
    style Recovery fill:#dfd

The diagram is the entire git mental model: every command is an arrow between four storage locations + the stash and reflog escape hatches.

# Initial setup (one-time)
git config --global user.name "Your Name"
git config --global user.email "[email protected]"
 
# Clone a repository
git clone https://github.com/org/repo.git
 
# Daily workflow
git status                         # Check what changed
git add .                          # Stage all changes
git commit -m "Descriptive message" # Create snapshot
git push origin main               # Send to remote
 
# Undo the last commit (keep changes)
git reset HEAD~1
 
# Discard all unstaged changes
git restore .
 
# Amend the last commit
git add forgotten-file.txt
git commit --amend --no-edit

Branching and merging

[Git docs]
# Create and switch to a new branch
git switch -c feature-x          # Modern (Git 2.23+)
git checkout -b feature-x        # Legacy
 
# List branches
git branch -a                    # All (local + remote)
git branch --merged              # Safe to delete
git branch --no-merged           # Not yet merged
 
# Merge a branch into current
git merge feature-x              # Creates merge commit
git merge --no-ff feature-x      # Preserve branch topology (recommended)
 
# Delete a branch
git branch -d feature-x          # Safe (only if fully merged)
git branch -D feature-x          # Force delete
 
# Switch to remote branch
git switch --detach origin/feature-x # Detached HEAD at the remote commit
git switch --track origin/feature-x  # Create local tracking branch

Remote operations

[Git docs]
# List remotes
git remote -v
 
# Add a remote
git remote add origin https://github.com/org/repo.git
git remote add upstream https://github.com/original/repo.git  # Fork workflow
 
# Fetch and pull
git fetch origin                 # Download without merging
git pull origin main             # Fetch + merge (or rebase if configured)
 
# Push
git push origin main             # Send commits to remote
git push -u origin feature       # Set upstream tracking
git push origin --all            # Push all branches
 
# Force push (after rebase/amend)
git push --force-with-lease origin feature  # Safe: checks if remote changed
git push --force-with-lease --force-if-includes origin feature  # Safest (Git 2.30+)

Never use git push --force—it overwrites coworkers' commits if they pushed while you rebased.

History rewriting (rebase & cherry-pick)

[Git docs]
# Interactive rebase: squash, reword, or drop commits
git rebase -i HEAD~5             # Rebase last 5 commits
# Actions: pick (keep), reword (edit message), squash/fixup (merge),
# drop (delete)
 
# Autosquash: auto-apply fixup commits
git commit --fixup=abc123        # Create "fixup! ..." commit
git rebase -i --autosquash main  # Auto-squash into target
 
# Cherry-pick: copy specific commit(s)
git cherry-pick abc123           # Copy single commit
git cherry-pick abc123..def456   # Copy range (after abc123 to def456)
git cherry-pick --abort          # Cancel in-progress cherry-pick
 
# Rebase onto main (linearize history)
git rebase main

Rule: only rebase commits not yet pushed, or branches where you're the sole contributor. Rewriting shared history causes merge conflicts for coworkers.

Stash, inspection & bisect

# Stash (temporarily shelve changes)
git stash push -m "WIP: feature-x"   # Save with a message
git stash list                       # List all stashes
git stash apply                      # Apply most recent (keep it)
git stash pop                        # Apply and remove
git stash apply stash@{2}            # Apply specific stash
git stash branch feature-x stash@{0} # Create branch from stash
 
# Inspect history
git log --oneline --graph            # Compact history with branches
git log --stat                       # Show which files changed
git log --grep="keyword"             # Search commit messages
git log -S "string"                  # Find when string was added/removed
git shortlog -sn                     # Commits per author
 
# Show differences
git diff                             # Unstaged changes
git diff --staged                    # Staged changes
git diff HEAD~1 HEAD                 # Compare commits
git blame filename.txt               # Who changed each line
 
# Binary search for the breaking commit
git bisect start
git bisect bad HEAD
git bisect good v1.0
git bisect good  # or "git bisect bad" as you test commits
# Automated: git bisect run npm test

Tags & advanced features

# Tagging (for releases)
git tag -a v1.0.0 -m "Release 1.0.0"  # Annotated (recommended)
git tag v1.0.0-rc1                    # Lightweight
git tag -l "v1.*"                     # List matching pattern
git push origin v1.0.0                # Push specific tag
git push origin --tags                # Push all tags
 
# Git hooks (auto-run at workflow points; must be in .git/hooks/)
# pre-commit: lint/test before commit
# pre-push: run tests before push
# commit-msg: enforce message format
# Use husky for team-wide shared hooks
 
# Configuration defaults
git config --global pull.rebase true           # Rebase on pull
git config --global push.autoSetupRemote true  # Auto-track branches
git config --global rerere.enabled true        # Remember conflict resolutions

Undo & recovery

[Git docs]
# Undo changes
git reset --soft HEAD~1         # Undo commit, keep changes staged
git reset HEAD~1                # Undo commit, unstage changes
git restore filename.txt        # Discard unstaged changes (one file)
git restore .                   # Discard all unstaged changes
git restore --staged filename.txt # Unstage (keep in working dir)
 
# Merge conflicts
git status                      # See conflicted files
# Edit files, remove <<<<<<< / ======= / >>>>>>> markers
git add conflicted_file.txt
git commit                      # Complete merge
 
# Recovery (reflog shows all HEAD movements)
git reflog                      # List all commits (including deleted branches)
git checkout -b recovered abc123 # Create branch from "lost" commit
git clean -fdn                  # Dry run: show untracked files
git clean -fd                   # Delete untracked files/dirs
 
# Abort in-progress operations
git merge --abort
git rebase --abort
git cherry-pick --abort

When to use what

ScenarioUse ThisWhyDanger
Feature done, integrate to maingit merge --no-ff feature-xPreserves branch topology; clean "feature added" commit in historyPlain git merge loses branch shape; hard to see which commits belong together
Fix local mistake, not pushedgit reset HEAD~1Moves HEAD back, unstages changes; changes still in working dir for retrygit reset --hard loses the changes entirely; can't recover
Undo a push (public mistake)git revert abc123Creates a new commit that inverts changes; doesn't rewrite history; safe for shared branchesgit reset --hard on a shared branch will delete coworkers' work
Sync with main (non-destructive)git pull --rebaseReplays your commits on top of latest main; linear history; CI sees each commit in ordergit pull (merge) creates merge commits; clutters history
Copy 1-2 commits to another branchgit cherry-pick abc123Isolates exact changes; useful for backports or selective mergesCherry-picking across diverged branches causes conflict nightmares
Find breaking commit in 200+ commitsgit bisectBinary search; log(n) steps; finds culprit in ~8 commits instead of manual readingManual git log and git show — takes hours
Temporarily switch contextgit stash then git stash popSaves WIP without committing; clean working dir for switching branchesLeaving uncommitted changes; can cause merge conflicts when switching
Rewrite history before pushinggit rebase -i HEAD~3Squash/reword/drop; clean commit history; each commit is logically completeRewriting after pushing — rewrites shared history, breaks coworkers' branches
Force push after rebasegit push --force-with-leaseChecks if remote changed since your last fetch; fails safely if someone else pushedgit push --force — blindly overwrites remote, deletes coworkers' commits

Gotchas that bite in production

  1. git push --force on a shared branch silently deletes coworkers' commits

    • A hotfix pushed while you rebased is gone the moment your force-push lands.
    • Fix: always --force-with-lease (fails safely if the remote moved); Git 2.30+ adds --force-if-includes.
  2. git reset --hard on the wrong branch destroys uncommitted work

    • Committed work survives in reflog; uncommitted changes are simply gone.
    • Fix: prefer --soft / plain reset (mixed); run git status and confirm what you're about to lose before any --hard.
  3. git rebase -i HEAD~N with the wrong N rewrites shared history

    • Miscount and you're rebasing commits teammates already built on.
    • Fix: git rebase -i main (branch name — Git computes the range); preview with git log main..HEAD.
  4. git stash only stashes tracked files

    • Untracked files stay behind, then get lost in the branch-switch shuffle.
    • Fix: git stash -u (include untracked), or git stash branch feature-recovery instead of popping into a diverged branch.
  5. git cherry-pick conflicts under deploy pressure ship the wrong resolution

    • Fix: pick onto a temp branch first — git switch -c temp-cherry && git cherry-pick abc123 — test, then merge. Cherry-pick is for surgical backports, not large merges.

Production checklist

Before pushing to main or creating a release, run through these steps to prevent broken deploys and security leaks:

  • Rebase onto maingit rebase main: linear history, early conflicts
  • Squash messy commitsgit rebase -i main, change non-first commits to squash/fixup for clean history
  • Verify no secrets leakedgit diff main..HEAD and scan output for API keys, tokens, credentials
  • Run tests before push — local npm test or add a pre-push hook to block broken code
  • Use --force-with-lease if rebasedgit push --force-with-lease origin main is safe; --force deletes coworkers' commits
  • Tag releases semanticallygit tag -a v1.0.0 -m "Release: feature X, fix Y" then git push origin v1.0.0
  • Consider signing commitsgit config --global commit.gpgsign true (with GPG or SSH key) for verified badges

Git in CI: caching, partial clone, and shallow trade-offs

For a 200 MB monorepo with 50k commits, a naive git clone burns 30-60 seconds per job before any test runs. Across a 40-job pipeline that is 30 minutes of pure clone wait per pull request. The fix is not "throw a faster runner at it" — it is matching the clone strategy to what each job actually needs.

Shallow clone is the lazy fix. It downloads only the most recent commits, skipping history:

# Default GitHub Actions / GitLab CI behaviour: shallow with depth=1
git clone --depth=1 https://github.com/org/repo.git
 
# Need merge-base with main for a diff-based test selector? depth=1 fails
git fetch --unshallow                       # convert to full clone (slow, undoes the win)
git fetch --deepen=50                       # extend by 50 commits (cheaper)

Shallow clone breaks history-walkers: git blame, git log, merge-base computation, semantic-release inference, and changelog generators. If your CI runs git diff main...HEAD to decide which packages to rebuild, depth=1 silently returns the wrong diff because main is not in the clone.

Partial clone is the better default for medium and large repos. It downloads commit and tree objects but defers blob download until a file is actually read:

# Skip all blobs at clone time — fetch on demand
git clone --filter=blob:none https://github.com/org/repo.git
 
# Skip blobs above a size threshold (good for repos with mixed binary + code)
git clone --filter=blob:limit=1m https://github.com/org/repo.git
 
# Combine with sparse-checkout for monorepos: clone metadata, check out one path
git clone --filter=blob:none --no-checkout https://github.com/org/monorepo.git
cd monorepo
git sparse-checkout init --cone
git sparse-checkout set services/payments libs/shared
git checkout main

Partial clone keeps full history — git log/git blame work — fetching blobs lazily when you cat a file or run git checkout against a path. For a CI job that compiles one service in a monorepo, this can cut clone time from 45s to 4s and disk usage from 800 MB to 60 MB. The trade-off: every blob access becomes a network round trip, so test runs that read many files pay incremental latency. Pair it with sparse-checkout to bound the working tree.

Caching the .git directory is the highest-leverage CI optimization most teams skip. GitHub Actions' actions/checkout@v4 re-clones every run; instead, cache .git/ keyed by the previous commit and run git fetch on hit:

# .github/workflows/ci.yml — cache .git across runs
- uses: actions/cache@v4
  with:
    path: .git
    key: git-${{ github.sha }}
    restore-keys: git-
 
- name: Fast clone via cache
  run: |
    if [ -d .git ]; then
      git fetch --no-tags --filter=blob:none origin "${{ github.sha }}"
      git reset --hard "${{ github.sha }}"
      git clean -fdx
    else
      git clone --filter=blob:none --no-tags \
        https://github.com/${{ github.repository }}.git .
      git reset --hard "${{ github.sha }}"
    fi

A cache hit fetches only new commits — typically a few hundred KB. On a cache miss it falls back to a partial clone. Result: median checkout time drops from 38s to 3s. The git clean -fdx line is critical; without it, leftover artifacts from the cached run (built binaries, node_modules, generated files) corrupt the next build.

Reference repos are the nuclear option for self-hosted runners. Maintain a single up-to-date mirror on each runner host, then have every job clone with --reference:

# One-time setup on the runner host
git clone --bare https://github.com/org/repo.git /opt/git-mirror/repo.git
 
# Cron: keep the mirror fresh
0 * * * * git -C /opt/git-mirror/repo.git fetch --all --prune
 
# In every CI job
git clone --reference /opt/git-mirror/repo.git --dissociate \
  https://github.com/org/repo.git

--reference reads shared objects from the mirror's objects/ directory — clone time drops to single-digit seconds even for full history. The --dissociate flag copies referenced objects into the new clone after fetch so the working clone is self-contained — safer if the mirror is deleted mid-build.

Decision rule: jobs running git log, git blame, or merge-base computations must not use shallow. Use partial clone (--filter=blob:none) by default for any repo over 50 MB. Add sparse-checkout for monorepos where each job touches one subtree. Cache .git/ across runs once the partial-clone strategy is stable. Reach for reference repos only on self-hosted runners where you control the host.

On hosted runners the cost compounds: 200 PRs/day x 60 jobs x 30s of naive cloning is 100 hours of compute monthly — at GitHub Actions rates, real money. Tuning the clone is one of the cheapest CI wins available[Git docs].

Frequently Asked Questions

How do I undo a push?

If you pushed commits you want to remove, don't panic. Create a new commit that reverts the changes: git revert abc123 (creates inverse commit). Or rebase and git push --force-with-lease origin main if the branch allows it. Never use --force — always use --force-with-lease.

What's the difference between git reset and git revert?

git reset moves HEAD back and rewrites history (use before pushing). git revert creates a new commit that inverts changes (use after pushing). For shared branches, always revert.

How do I find which commit introduced a bug?

Use git bisect: mark current state as bad, a known-good commit as good, then Git binary-searches. For automated testing: git bisect run npm test. Finds the culprit in log(n) steps.

Why should I use --force-with-lease instead of --force?

--force blindly overwrites the remote branch. If a coworker pushed 3 commits while you were rebasing, --force deletes their work. --force-with-lease checks if the remote changed—it fails safely if someone else pushed.

How do I stage only some changes in a file?

Use git add -p (interactive hunk staging). Git shows each change and asks: [y]es / [n]o / [s]plit / [e]dit / [?]help. Perfect for creating focused, single-purpose commits.

Keep Reading

Was this article helpful?

Your feedback directly shapes our editorial depth and technical accuracy.

BackendBytes Engineering Team
BackendBytes Engineering Team

Engineering Team

An independent engineering publication covering distributed systems, databases, and production infrastructure. Every factual claim is cited to a primary source or removed.

Read Next