On this page
- The mental model: three trees
- Repository setup
- The daily workflow
- Branches
- Merging
- Rebasing
- Remote repositories
- Undoing changes
- Stashing
- Tags and releases
- Debugging Git history
- Rewriting history safely
- Git recovery
- Troubleshooting common Git situations
- Command reference
- Production checklist
- Frequently asked questions
- Related resources
Git is the substrate every other DevOps workflow sits on: CI triggers on refs, GitOps reconciles from commits, and every rollback is ultimately a Git operation. Knowing Git deeply — not just add, commit, push, but what those commands do to the object graph — is what lets you recover a “lost” branch at 2am instead of re-doing a day’s work.
This guide is organized by the workflow you’re in, not alphabetically, because that’s how you actually reach for commands. Every destructive operation is labeled, and the command reference table at the end is searchable.
The mental model: three trees
Almost every Git command moves data between three “trees”:
- Working tree — the actual files on disk you edit.
- Index (staging area) — a snapshot you’re building for the next commit.
git addcopies working-tree changes here. - Repository (
HEAD) — the committed history: an immutable graph of commit objects, each pointing at a tree and its parent(s).
A fourth location matters the moment you collaborate: the remote (e.g. origin), a copy of the repository on another machine.
working tree ──git add──▶ index ──git commit──▶ repository (local) ──git push──▶ remote
▲ │ │
└────── git restore ─────┘ │
└──────────────── git restore --staged ────────────┘
Once you can name which tree a command touches, its behavior stops being mysterious. git reset is confusing only until you know it moves HEAD and optionally the index and working tree.
Repository setup
| Command | What it does | Risk |
|---|---|---|
git init | Create a new repository in the current directory (.git/). | Safe |
git clone <url> | Copy a remote repository, its history, and set up 'origin'. | Safe |
git remote -v | List configured remotes and their URLs. | Safe |
git remote add <name> <url> | Add a new remote (e.g. a fork's upstream). | Safe |
No commands match that filter.
# Start a repo and make the first commit
git init
git add .
git commit -m "Initial commit"
# Clone an existing repo (shallow clone is much faster for CI)
git clone https://github.com/org/repo.git
git clone --depth 1 https://github.com/org/repo.git # CI: history not needed
# Point a fork at the original repo so you can pull upstream changes
git remote add upstream https://github.com/org/repo.git
git remote -v
The daily workflow
This is the loop you run dozens of times a day: see what changed, stage it, commit it.
| Command | What it does | Risk |
|---|---|---|
git status | Show staged, unstaged, and untracked changes. | Safe |
git status -sb | Compact status with branch/tracking info. | Safe |
git add <path> | Stage a file (or . for everything) for the next commit. | Safe |
git add -p | Interactively stage individual hunks — review before you commit. | Safe |
git commit -m "msg" | Record the staged snapshot as a commit. | Safe |
git commit --amend | Replace the last commit (message and/or content). | Rewrites history |
git diff | Working-tree changes not yet staged. | Safe |
git diff --staged | Changes staged for the next commit. | Safe |
git log --oneline --graph | Compact, visual commit history. | Safe |
git restore <path> | Discard unstaged changes to a file. | Modifies working tree |
git restore --staged <path> | Unstage a file (keep the changes). | Safe |
No commands match that filter.
git status -sb
git add -p # stage hunk by hunk; keeps commits focused
git diff --staged # last look before committing
git commit -m "fix: handle empty upstream response in health check"
git log --oneline --graph -10
Writing commit messages that survive review
A good message explains why, not what (the diff already shows what). The widely used convention is a concise, imperative subject line under ~50 characters, a blank line, then a body:
fix: reject health-check responses over 5s as failures
The upstream occasionally returns 200 after a 30s hang. Treating slow
responses as healthy masked a real outage on 2026-08-14. Cap the timeout
at 5s and surface the latency in the metric.
Branches
Branches are just movable pointers to commits — creating one is O(1) and free. Modern Git separates switching branches (git switch) from restoring files (git restore); both were historically overloaded onto git checkout, which is the source of endless confusion.
| Command | What it does | Risk |
|---|---|---|
git branch | List local branches (current marked with *). | Safe |
git branch <name> | Create a branch (does not switch to it). | Safe |
git switch <name> | Switch to an existing branch. Clearer than checkout. | Safe |
git switch -c <name> | Create and switch to a new branch. | Safe |
git checkout <name> | Legacy switch (still works; switch/restore are preferred). | Safe |
git branch -d <name> | Delete a merged branch (safe — refuses if unmerged). | Modifies working tree |
git branch -D <name> | Force-delete a branch even if unmerged. | Destructive |
git branch -m <new> | Rename the current branch. | Safe |
No commands match that filter.
git switch -c feat/rate-limit # create + switch
# ...work, commit...
git switch main # back to main
git branch -d feat/rate-limit # tidy up after merge (safe form)
Merging
Merging integrates one branch’s history into another. Git picks the strategy automatically:
- Fast-forward — if the target hasn’t diverged, Git just moves the pointer forward. No merge commit.
- Three-way merge — if both branches have new commits, Git creates a merge commit with two parents.
| Command | What it does | Risk |
|---|---|---|
git merge <branch> | Merge <branch> into the current branch. | Modifies working tree |
git merge --no-ff <branch> | Always create a merge commit (preserves the branch's shape). | Modifies working tree |
git merge --abort | Bail out of an in-progress merge with conflicts. | Safe |
git merge --squash <branch> | Stage the branch's changes as one commit (you commit it). | Modifies working tree |
No commands match that filter.
git switch main
git merge --no-ff feat/rate-limit
# CONFLICT (content): Merge conflict in src/limiter.ts
# Fix the marked sections, then:
git add src/limiter.ts
git commit # completes the merge
# ...or, to give up entirely and return to pre-merge state:
git merge --abort
When Git can’t reconcile changes automatically it writes conflict markers into the file:
<<<<<<< HEAD
const LIMIT = 100;
=======
const LIMIT = 250;
>>>>>>> feat/rate-limit
Edit the file to the correct final state, delete the markers, git add it, and complete the merge. git merge --abort is always a safe escape hatch back to the pre-merge state.
Rebasing
Rebasing replays your commits on top of another base, producing a linear history instead of a merge commit. It rewrites commit hashes, so it is powerful and hazardous in equal measure.
| Command | What it does | Risk |
|---|---|---|
git rebase <base> | Replay current branch's commits on top of <base>. | Rewrites history |
git rebase -i <base> | Interactive rebase: reorder, squash, edit, drop commits. | Rewrites history |
git rebase --continue | Resume a rebase after resolving a conflict. | Rewrites history |
git rebase --abort | Cancel a rebase and return to the original state. | Safe |
git pull --rebase | Rebase local commits onto fetched upstream instead of merging. | Rewrites history |
No commands match that filter.
# Clean up a feature branch before opening a PR: squash "wip" commits
git rebase -i main
# In the editor, change 'pick' to 'squash' (or 's') on the commits to fold together.
Remote repositories
| Command | What it does | Risk |
|---|---|---|
git fetch | Download remote commits/refs. Does NOT change your working tree. | Safe |
git pull | fetch + merge (or + rebase) the upstream into your branch. | Modifies working tree |
git pull --rebase | fetch + rebase — linear history, no merge commit. | Rewrites history |
git push | Upload local commits to the remote branch. | Safe |
git push -u origin <branch> | Push and set the upstream tracking branch. | Safe |
git push --force-with-lease | Force-push, but refuse if the remote moved since you fetched. | Destructive |
git push --force | Overwrite the remote branch unconditionally. | Destructive |
No commands match that filter.
git fetch origin # see what's upstream WITHOUT touching your work
git log --oneline HEAD..origin/main # what you're missing
git pull --rebase origin main # integrate, keep history linear
git push -u origin feat/rate-limit # first push of a new branch
git fetch is the safest way to see upstream state — it never modifies your working tree, so you can always fetch, inspect with git log HEAD..origin/main, and decide how to integrate.
Undoing changes
This is where Git earns its reputation for danger — and where the right command is a lifesaver. The correct tool depends on what you want to undo and whether it’s been pushed.
| Command | What it does | Risk |
|---|---|---|
git restore <path> | Discard unstaged changes to a file (irreversible for that file). | Destructive |
git restore --staged <path> | Unstage a file but keep the edits. | Safe |
git reset --soft HEAD~1 | Undo last commit, keep changes staged. | Rewrites history |
git reset --mixed HEAD~1 | Undo last commit, keep changes unstaged (default). | Rewrites history |
git reset --hard HEAD~1 | Undo last commit AND discard the changes. No safety net. | Destructive |
git revert <commit> | Create a new commit that undoes <commit>. Safe for shared history. | Safe |
git clean -fd | Delete untracked files and directories permanently. | Destructive |
git clean -nd | Dry run: show what clean WOULD delete. | Safe |
No commands match that filter.
The three flavors of git reset are the crux:
git reset --soft HEAD~1 # "uncommit" but keep everything staged — fix a message, re-commit
git reset --mixed HEAD~1 # uncommit and unstage — re-decide what to include (this is the default)
git reset --hard HEAD~1 # uncommit AND throw away the changes — gone from the working tree
Stashing
Stash sets aside your uncommitted work so you can switch context, then restore it.
git stash push -m "wip: half-done rate limiter" # shelve changes, clean the working tree
git switch main # go fix the urgent thing
git switch feat/rate-limit
git stash pop # reapply and drop the stash
git stash list # see all stashes
git stash apply stash@{1} # reapply a specific one, keep it in the list
Tags and releases
Tags mark specific commits — almost always releases. Use annotated tags (-a) for releases: they store a tagger, date, and message, and are what git describe and most release tooling expect.
| Command | What it does | Risk |
|---|---|---|
git tag | List tags. | Safe |
git tag -a v1.4.0 -m "msg" | Create an annotated (release) tag on HEAD. | Safe |
git push origin v1.4.0 | Push a single tag (tags are NOT pushed by default). | Safe |
git push --tags | Push all local tags. | Safe |
git tag -d v1.4.0 | Delete a local tag. | Modifies working tree |
git describe --tags | Human-readable name based on the nearest tag. | Safe |
No commands match that filter.
git tag -a v1.4.0 -m "Release 1.4.0: rate limiting + health-check fix"
git push origin v1.4.0
git describe --tags # e.g. v1.4.0-3-gab12cd (3 commits past v1.4.0)
Debugging Git history
When something broke and you need to know which commit did it, these are the power tools.
| Command | What it does | Risk |
|---|---|---|
git log -p <path> | Full diff of every change to a file over time. | Safe |
git log -S"text" | Find commits that added/removed a string (pickaxe). | Safe |
git blame <path> | Show which commit and author last touched each line. | Safe |
git bisect start | Begin a binary search for the commit that introduced a bug. | Safe |
git reflog | Log of everywhere HEAD has been — your recovery lifeline. | Safe |
git show <commit> | Show a commit's metadata and diff. | Safe |
No commands match that filter.
git bisect finds a regression in log(n) steps by binary-searching commits:
git bisect start
git bisect bad # current commit is broken
git bisect good v1.3.0 # this old tag worked
# Git checks out the midpoint. Test it, then tell Git:
git bisect good # ...or 'git bisect bad'
# Repeat until Git prints the first bad commit, then:
git bisect reset # return to where you started
For a repository with 1,000 commits between “good” and “bad”, bisect finds the culprit in ~10 tests. You can even automate it: git bisect run ./test.sh runs a script at each step (exit 0 = good, non-zero = bad).
Rewriting history safely
Sometimes you legitimately need to reshape unpublished history — squash noise, fix a bad message, drop a secret you just committed.
git commit --amend # fix the most recent commit
git rebase -i HEAD~5 # reword/squash/reorder the last 5 commits
Git recovery
The single most important recovery fact: Git rarely deletes commits immediately. Even after a bad reset --hard or a deleted branch, the commit objects usually still exist and are reachable via the reflog for ~90 days (until garbage collection).
git reflog # every position HEAD has held, most recent first
# ab12cd3 HEAD@{0}: reset: moving to HEAD~3
# 9f8e7d6 HEAD@{1}: commit: the work you thought you lost
git switch -c rescue 9f8e7d6 # recover those commits onto a new branch
Troubleshooting common Git situations
- “Your branch is ahead/behind ‘origin/main’” — you have local commits not pushed (ahead) or upstream commits not pulled (behind).
git statusexplains;git pull --rebasethengit pushreconciles. ! [rejected] ... (non-fast-forward)on push — the remote has commits you don’t.git fetchthengit rebase origin/main(or merge), then push. Never reach for--forceto “fix” this unless you truly own the branch.- “detached HEAD” — you checked out a commit or tag directly, so new commits aren’t on any branch. Create one to keep them:
git switch -c my-branch. If you didn’t commit anything,git switch mainis safe. - Merge conflict you don’t understand —
git merge --abort(orgit rebase --abort) returns you to safety; re-attempt with a clearer head. - Committed to the wrong branch —
git reset --soft HEAD~1to uncommit (keeping changes staged),git switch correct-branch, thengit commit.
For specific error messages, the site’s error library goes deeper — for example GitLab: clone access denied, and the full DevOps error guides index.
Command reference
Every command in this guide, in one searchable table. Filter by name or description.
Searchable — type to filter
| Command | What it does | Risk |
|---|---|---|
git init | Create a new repository. | Safe |
git clone <url> | Copy a remote repository and its history. | Safe |
git status | Show working-tree and index state. | Safe |
git add <path> | Stage changes for the next commit. | Safe |
git add -p | Interactively stage individual hunks. | Safe |
git commit -m | Record the staged snapshot. | Safe |
git commit --amend | Replace the previous commit. | Rewrites history |
git diff | Unstaged changes. | Safe |
git diff --staged | Staged changes. | Safe |
git log | Show commit history. | Safe |
git log -S"text" | Find commits adding/removing a string. | Safe |
git branch | List/create branches. | Safe |
git switch <name> | Switch branches. | Safe |
git switch -c <name> | Create and switch to a branch. | Safe |
git checkout <name> | Legacy branch switch / file restore. | Safe |
git merge <branch> | Merge a branch into the current one. | Modifies working tree |
git merge --abort | Abort an in-progress merge. | Safe |
git rebase <base> | Replay commits onto a new base. | Rewrites history |
git rebase -i <base> | Interactive rebase. | Rewrites history |
git rebase --abort | Cancel a rebase. | Safe |
git fetch | Download remote refs; leaves working tree alone. | Safe |
git pull | Fetch + integrate upstream. | Modifies working tree |
git pull --rebase | Fetch + rebase for linear history. | Rewrites history |
git push | Upload commits to the remote. | Safe |
git push --force-with-lease | Safer force-push (refuses if remote moved). | Destructive |
git push --force | Unconditionally overwrite the remote branch. | Destructive |
git remote -v | List remotes. | Safe |
git tag -a | Create an annotated release tag. | Safe |
git stash | Shelve uncommitted changes. | Safe |
git stash pop | Reapply and drop the latest stash. | Modifies working tree |
git reset --soft HEAD~1 | Uncommit, keep changes staged. | Rewrites history |
git reset --mixed HEAD~1 | Uncommit, keep changes unstaged. | Rewrites history |
git reset --hard HEAD~1 | Uncommit and DISCARD changes. | Destructive |
git revert <commit> | New commit that undoes another. Safe on shared history. | Safe |
git restore <path> | Discard unstaged changes to a file. | Destructive |
git restore --staged <path> | Unstage a file, keep edits. | Safe |
git clean -nd | Dry-run: show untracked files clean would remove. | Safe |
git clean -fd | Permanently delete untracked files/dirs. | Destructive |
git cherry-pick <commit> | Apply a specific commit onto the current branch. | Modifies working tree |
git blame <path> | Who last changed each line. | Safe |
git bisect start | Binary-search for a regressing commit. | Safe |
git reflog | History of HEAD — recovery lifeline. | Safe |
No commands match that filter.
Production checklist
Habits that keep a team’s Git history clean and recoverable:
- Default to
--force-with-lease, never bare--force, on any shared remote. - Dry-run destructive commands —
git clean -ndbefore-fd; prefergit stashoverreset --hard. - Protect
mainwith branch protection: require PRs, passing checks, and block force-pushes server-side. - Rebase private branches, merge shared ones. Never rebase published history.
- Use annotated tags for releases and push them explicitly in your release job.
- Know the reflog exists — it’s the difference between “I lost a day” and “I lost a minute.”
- Rotate, don’t just rewrite, any secret that reached a commit.
- Shallow-clone in CI (
--depth 1) unless a step genuinely needs history.
Frequently asked questions
What’s the difference between git reset and git revert?
git reset moves the branch pointer and rewrites history — use it on local, unpushed commits. git revert creates a new commit that undoes a previous one, leaving history intact — use it on shared or pushed branches.
When should I rebase instead of merge? Rebase to keep a private feature branch linear and clean before opening a PR. Merge to integrate shared branches, because rebasing published commits rewrites hashes and disrupts everyone who pulled them.
How do I undo the last commit but keep my changes?
git reset --soft HEAD~1 keeps everything staged; git reset --mixed HEAD~1 (the default) keeps the changes but unstages them. Avoid --hard, which also discards the changes.
How do I recover a branch I deleted or a commit I hard-reset?
Run git reflog, find the commit hash from before the mistake, and git switch -c recovered <hash>. The objects usually survive ~90 days until garbage collection. This only recovers committed work.
Is git pull safe?
git pull runs git fetch then merges (or rebases) into your branch, which can create conflicts. git fetch alone is always safe — it never touches your working tree — so fetch, inspect with git log HEAD..origin/main, then integrate deliberately.
How do I remove a secret I accidentally committed?
Rewrite history with git filter-repo (or BFG) to purge it, force-push, and rotate the credential — assume it’s compromised the moment it was pushed.
Related resources
- Free tool: the DevOps command cheat sheets collect the commands you reach for under pressure.
- Academy: the GitHub AI Engineering path goes deep on GitHub workflows, PR automation, and Copilot for DevOps.
- Error library: hit a specific Git/GitLab error? Search the 800+ DevOps error guides.
- Prompts: browse DevOps AI prompts for commit-message, code-review, and Git-workflow assistance.
Continue learning
Related Core Guides that build on this one.
- GitHub ActionsA complete CI/CD guide to GitHub Actions — workflow architecture, runners, secrets, OIDC, matrices, caching and production deployment patterns that actually hold up.
- Bash ScriptingWrite production-safe Bash: strict mode, error handling, traps, argument parsing and real automation templates you can drop into a pipeline.
- DevOps PracticesThe practices that define modern delivery — IaC, CI/CD, GitOps, observability, SRE, progressive delivery — with when to use each, and when not to.
- Linux CommandsA searchable Linux command reference for engineers — files, text, storage, processes, networking, services and troubleshooting, with Ubuntu-first examples.