Skip to content
DevOps AI ToolKit
Newsletter
Core Guide · Engineering Fundamentals

Git Commands

The Git commands a DevOps engineer actually uses, organized by the workflow you're in — with copyable examples, expected output, and clear risk labels on every operation that can lose work.

Last reviewed August 2026 Reference · Tutorial · Troubleshooting · 34 min read

Technically validated: Commands run against Git 2.43 on Linux and macOS. Output shown is representative; exact wording varies by Git version.

On this page

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 add copies 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
# 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
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
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
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
# 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
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

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
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

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 status explains; git pull --rebase then git push reconciles.
  • ! [rejected] ... (non-fast-forward) on push — the remote has commits you don’t. git fetch then git rebase origin/main (or merge), then push. Never reach for --force to “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 main is safe.
  • Merge conflict you don’t understandgit merge --abort (or git rebase --abort) returns you to safety; re-attempt with a clearer head.
  • Committed to the wrong branchgit reset --soft HEAD~1 to uncommit (keeping changes staged), git switch correct-branch, then git 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

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 commandsgit clean -nd before -fd; prefer git stash over reset --hard.
  • Protect main with 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.

Continue learning

Related Core Guides that build on this one.

Written by James Joyner IV, Sr. Systems Software Engineer — for engineers who run what they build.

Last reviewed August 2026. Found an error or an out-of-date command? Tell us — accuracy is the point of a Core Guide.