← All stacks
Git

Git

11 sections · 101 entries

Setup & Config

Initialize a Repository

syntax
git init [directory]
example
git init my-project
cd my-project
output
Initialized empty Git repository in /home/user/my-project/.git/

Note Creates a hidden .git folder. Running this in an existing repo is safe and will not overwrite anything.

Clone a Repository

syntax
git clone <url> [directory]
example
git clone https://github.com/user/project.git
git clone git@github.com:user/project.git my-local-name
output
Cloning into 'project'...

Note HTTPS asks for credentials each time unless you configure a credential helper. SSH uses your key pair and is generally preferred for frequent pushes.

Set User Name & Email

syntax
git config [--global] user.name "Name"
git config [--global] user.email "email"
example
git config --global user.name "Samira Khan"
git config --global user.email "samira@example.com"

# Per-repo override:
git config user.email "samira@work.com"

Note Without --global, the setting only applies to the current repository. Your commits will be attributed to whatever name/email is configured, so double-check with git config user.email before committing to a work repo.

Set Default Editor

syntax
git config --global core.editor "<editor>"
example
git config --global core.editor "code --wait"
# Or for vim:
git config --global core.editor "vim"

Note The --wait flag for VS Code is essential. Without it, Git thinks the editor closed immediately and aborts the commit message.

Create Aliases

syntax
git config --global alias.<shortcut> '<command>'
example
git config --global alias.co 'checkout'
git config --global alias.br 'branch'
git config --global alias.lg 'log --oneline --graph --all'

# Now use:
git co main
git lg

Note Aliases are stored in ~/.gitconfig. You can also edit that file directly under the [alias] section for complex aliases.

Ignoring Files with .gitignore

syntax
# .gitignore file patterns:
pattern
dir/
*.ext
!exception
example
# .gitignore
node_modules/
*.log
.env
.DS_Store
build/

# Track this specific log despite the wildcard:
!important.log

Note If a file is already tracked, adding it to .gitignore will NOT stop tracking it. You must first run: git rm --cached <file> to untrack it.

View All Configuration

syntax
git config --list [--global|--local|--system]
example
git config --list --global
git config --list --local
output
user.name=Samira Khan
user.email=samira@example.com
core.editor=code --wait
alias.co=checkout

Note Local config overrides global, which overrides system. Use --show-origin to see which file each setting comes from.

Credential Storage

syntax
git config --global credential.helper <store|cache|osxkeychain>
example
# macOS — use the system keychain:
git config --global credential.helper osxkeychain

# Linux — cache for 1 hour:
git config --global credential.helper 'cache --timeout=3600'

Note The 'store' helper saves passwords in plain text on disk. Prefer 'osxkeychain' on Mac or a credential manager on Windows. For SSH repos, credentials are handled by your SSH key instead.

Global .gitignore

syntax
git config --global core.excludesFile <path>
example
git config --global core.excludesFile ~/.gitignore_global

# Then in ~/.gitignore_global:
.DS_Store
Thumbs.db
*.swp
.idea/
.vscode/

Note Use this for OS and editor-specific files. Project-specific ignores still belong in the repo's own .gitignore so all contributors benefit.

Basic Workflow

Check Repository Status

syntax
git status [-s]
example
git status
git status -s
output
On branch main
Changes not staged for commit:
  modified:   app.js
Untracked files:
  utils.js

# Short format:
 M app.js
?? utils.js

Note The short flag (-s) gives a compact two-column output: left column = staging area, right column = working tree. M = modified, A = added, ? = untracked, D = deleted.

Stage Changes

syntax
git add <file|pattern|.>
example
git add app.js
git add src/
git add *.ts
git add -A

Note git add . stages new and modified files in the current directory and below. git add -A stages everything including deletions from anywhere in the repo. Prefer staging specific files to avoid accidentally committing secrets or large binaries.

Stage Part of a File

syntax
git add -p [file]
example
git add -p utils.js
# Git shows each hunk and asks:
# Stage this hunk [y,n,q,a,d,s,e,?]?
# y = stage, n = skip, s = split into smaller hunks

Note Extremely useful when a file has multiple unrelated changes. You can commit them separately for a cleaner history. Use 's' to split a hunk if Git grouped too many changes together.

Commit Staged Changes

syntax
git commit [-m "message"]
example
git commit -m "Add user authentication endpoint"

# Multi-line message:
git commit -m "Fix cart total calculation

The discount was applied after tax instead of before.
Updated the order of operations in calculateTotal()."
output
[main a1b2c3d] Add user authentication endpoint
 2 files changed, 45 insertions(+), 3 deletions(-)

Note Without -m, Git opens your configured editor for the message. A good commit message has a short summary line (under 72 chars), a blank line, then optional details.

Stage & Commit in One Step

syntax
git commit -am "message"
example
git commit -am "Update API response format"
output
[main f4e5d6c] Update API response format
 1 file changed, 12 insertions(+), 8 deletions(-)

Note The -a flag only stages already-tracked files that have been modified or deleted. It will NOT add new untracked files. You still need git add for brand new files.

View Unstaged Changes

syntax
git diff [file]
git diff --staged [file]
example
# Changes in working tree (not yet staged):
git diff

# Changes that are staged for the next commit:
git diff --staged

# Diff for a specific file:
git diff src/auth.js
output
diff --git a/src/auth.js b/src/auth.js
--- a/src/auth.js
+++ b/src/auth.js
@@ -10,3 +10,5 @@
+  const token = generateToken(user);
+  return { token, expiresIn: 3600 };

Note Plain git diff shows only unstaged changes. Use --staged (or --cached, they're identical) to see what will go into the next commit. If git diff shows nothing, your changes are probably already staged.

View Commit History

syntax
git log [options]
example
git log
git log --oneline
git log --oneline -10
git log --since='2 weeks ago'
git log --author='Samira'
output
a1b2c3d Add user authentication endpoint
f4e5d6c Update API response format
9g8h7i6 Initial commit

Note git log shows most recent commits first. Use -n or -<number> to limit output. Press q to exit the pager if the log is long.

Inspect a Specific Commit

syntax
git show <commit-hash|HEAD|tag>
example
git show a1b2c3d
git show HEAD
git show HEAD~2
output
commit a1b2c3d...
Author: Samira Khan <samira@example.com>
Date:   Mon Mar 30 14:22:01 2026 +0000

    Add user authentication endpoint

diff --git a/src/auth.js b/src/auth.js
...

Note HEAD~2 means 'two commits before HEAD'. You can also show specific files from a commit: git show HEAD:src/app.js

Summarize Commits by Author

syntax
git shortlog [-s] [-n] [-e]
example
git shortlog -sne
git shortlog --since='1 month ago'
output
    23  Samira Khan <samira@example.com>
    15  Dev Patel <dev@example.com>
     7  Aiko Tanaka <aiko@example.com>

Note -s shows only counts (no commit messages), -n sorts by number of commits (most first), -e includes email addresses. Handy for seeing who contributed most.

Diff Summary Statistics

syntax
git diff --stat [branch|commit]
example
git diff --stat main
git diff --stat HEAD~5
output
 src/auth.js   | 12 ++++++--
 src/routes.js |  4 ++--
 2 files changed, 10 insertions(+), 4 deletions(-)

Note Shows a compact summary of which files changed and how many lines were added/removed. Combine with --staged to see staged changes summary.

Branching

Create a Branch

syntax
git branch <name>
example
git branch feature/user-profile

Note This only creates the branch but does NOT switch to it. Use git switch or git checkout to move to the new branch. Branch names cannot contain spaces.

Switch Branches

syntax
git switch <branch>
git switch -c <new-branch>
example
git switch feature/user-profile

# Create and switch in one step:
git switch -c feature/payments
output
Switched to branch 'feature/user-profile'

Note git switch is the modern replacement for git checkout (for branch switching). The -c flag creates the branch if it doesn't exist. You'll get an error if you have uncommitted changes that conflict with the target branch.

Checkout a Branch (Classic)

syntax
git checkout <branch>
git checkout -b <new-branch>
example
git checkout main
git checkout -b bugfix/login-error
output
Switched to branch 'main'

Note git checkout does double duty: switching branches AND restoring files. The newer git switch (branches) and git restore (files) commands split this up for clarity. Both approaches still work.

Merge a Branch

syntax
git merge <branch>
example
# Switch to the target branch first:
git switch main
git merge feature/user-profile
output
Updating a1b2c3d..d4e5f6g
Fast-forward
 src/profile.js | 85 ++++++++++
 1 file changed, 85 insertions(+)

Note If there are no diverging commits, Git does a 'fast-forward' merge (no merge commit). Use --no-ff to force a merge commit even when fast-forward is possible, which keeps branch history visible in the log.

Resolve Merge Conflicts

syntax
# After a merge with conflicts:
git status
# Edit conflicted files, then:
git add <resolved-file>
git commit
example
git merge feature/payments
# CONFLICT in src/cart.js

# Open the file — look for conflict markers:
# <<<<<<< HEAD
# ... your changes ...
# =======
# ... their changes ...
# >>>>>>> feature/payments

# Manually resolve, then:
git add src/cart.js
git commit -m "Merge feature/payments, resolve cart conflict"

Note Never leave conflict markers (<<<, ===, >>>) in your code. Use git diff --check before committing to verify no markers remain. You can abort a conflicted merge with git merge --abort.

Delete a Branch

syntax
git branch -d <branch>
git branch -D <branch>
example
# Safe delete (only if fully merged):
git branch -d feature/user-profile

# Force delete (even if unmerged):
git branch -D experiment/failed-idea

# Delete remote branch:
git push origin --delete feature/user-profile
output
Deleted branch feature/user-profile (was d4e5f6g).

Note WARNING: -D (uppercase) force-deletes without checking if the branch is merged. You could lose unmerged work. Always use lowercase -d first, which will warn you if the branch has unmerged changes.

List Branches

syntax
git branch [-a] [-r]
example
# Local branches:
git branch

# All branches (local + remote):
git branch -a

# Remote branches only:
git branch -r
output
  bugfix/login-error
  feature/payments
* main
  remotes/origin/main
  remotes/origin/feature/payments

Note The asterisk (*) marks your current branch. Remote branches show as remotes/origin/<name>. Use git fetch first to update the remote branch list.

Rename a Branch

syntax
git branch -m <old-name> <new-name>
git branch -m <new-name>
example
# Rename the current branch:
git branch -m feature/user-settings

# Rename a different branch:
git branch -m old-name new-name

Note If the branch has been pushed, you'll also need to delete the old remote branch and push the new one: git push origin --delete old-name && git push -u origin new-name

Create Branch from a Specific Commit

syntax
git branch <name> <commit-hash>
git switch -c <name> <commit-hash>
example
git switch -c hotfix/urgent-patch a1b2c3d
output
Switched to a new branch 'hotfix/urgent-patch'

Note Useful when you need to branch from a point other than the current HEAD, such as creating a hotfix from a release tag or a known-good commit.

List Merged / Unmerged Branches

syntax
git branch --merged [branch]
git branch --no-merged [branch]
example
# Branches already merged into main:
git branch --merged main

# Branches NOT yet merged into main:
git branch --no-merged main
output
  feature/user-profile
  bugfix/login-error

Note Great for cleanup. Branches listed by --merged are safe to delete. Combine with xargs to bulk-delete: git branch --merged main | grep -v main | xargs git branch -d

Remote Repositories

Add a Remote

syntax
git remote add <name> <url>
example
git remote add origin https://github.com/user/project.git
git remote add upstream https://github.com/original/project.git

Note By convention, 'origin' is your fork or main remote, and 'upstream' is the original repo you forked from. You can have multiple remotes.

View Remotes

syntax
git remote -v
example
git remote -v
output
origin    https://github.com/user/project.git (fetch)
origin    https://github.com/user/project.git (push)
upstream  https://github.com/original/project.git (fetch)
upstream  https://github.com/original/project.git (push)

Note The -v flag shows the full URLs. Without it, you only see remote names. Fetch and push URLs can differ if configured separately.

Fetch from Remote

syntax
git fetch [remote] [branch]
example
git fetch origin
git fetch origin main
git fetch --all
output
remote: Counting objects: 15, done.
From https://github.com/user/project
   a1b2c3d..f4e5d6c  main -> origin/main

Note Fetch downloads new data but does NOT modify your working directory or merge anything. It's always safe to run. Use --all to fetch from every configured remote.

Pull (Fetch + Merge)

syntax
git pull [remote] [branch]
example
git pull origin main

# Pull with rebase instead of merge:
git pull --rebase origin main
output
Updating a1b2c3d..f4e5d6c
Fast-forward
 src/app.js | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

Note git pull = git fetch + git merge. Use --rebase to replay your local commits on top of the fetched changes (cleaner linear history). Some teams mandate pull --rebase to avoid unnecessary merge commits.

Push to Remote

syntax
git push [remote] [branch]
example
git push origin main
git push origin feature/payments
output
Counting objects: 5, done.
To https://github.com/user/project.git
   a1b2c3d..f4e5d6c  main -> main

Note Push will be rejected if the remote has commits you don't have locally. Pull first, resolve any conflicts, then push again. Avoid --force unless you truly understand the consequences (see Common Mistakes).

Set Upstream Tracking Branch

syntax
git push -u origin <branch>
git branch --set-upstream-to=origin/<branch>
example
# First push of a new branch — set tracking:
git push -u origin feature/payments

# After this, just use:
git push
git pull
output
Branch 'feature/payments' set up to track remote branch 'feature/payments' from 'origin'.

Note The -u flag (or --set-upstream-to) links your local branch to a remote branch. After that, plain git push and git pull know where to go without specifying the remote and branch every time.

Prune Stale Remote Branches

syntax
git remote prune <remote>
git fetch --prune
example
git remote prune origin

# Or prune during fetch:
git fetch --prune
output
Pruning origin
 * [pruned] origin/feature/old-feature

Note When someone deletes a remote branch, your local reference to it lingers. Pruning removes these stale references. Does not delete your local branches — only the remote-tracking references.

Rename or Remove a Remote

syntax
git remote rename <old> <new>
git remote remove <name>
example
git remote rename origin backup
git remote remove upstream

Note Renaming a remote also updates all remote-tracking branches. Removing a remote deletes all its tracking branches and configuration entries.

Push All Branches

syntax
git push --all <remote>
git push --tags <remote>
example
git push --all origin
git push --tags origin

Note Pushes every local branch to the remote. Be cautious with this in shared repos — you might push experimental branches others don't want. --tags pushes all tags separately.

Stashing

Stash Changes

syntax
git stash [push] [-m "message"]
example
git stash
git stash push -m "WIP: payment form validation"
output
Saved working directory and index state WIP on main: a1b2c3d Add auth

Note Stash saves your uncommitted changes (staged and unstaged) and reverts the working directory to HEAD. New untracked files are NOT stashed by default — use -u to include them.

Stash Including Untracked Files

syntax
git stash push -u [-m "message"]
git stash push --include-untracked
example
git stash push -u -m "WIP: new config files"
output
Saved working directory and index state On main: WIP: new config files

Note Without -u, brand new files that Git hasn't started tracking will be left behind when you stash. This catches most people off guard.

Restore Stashed Changes

syntax
git stash pop [stash@{n}]
example
# Restore most recent stash and remove it from the stash list:
git stash pop

# Restore a specific stash:
git stash pop stash@{2}
output
On branch main
Changes not staged for commit:
  modified:   src/cart.js

Note pop = apply + drop in one step. If there's a conflict during pop, the stash is NOT dropped — you'll need to resolve the conflict and manually run git stash drop afterwards.

Apply Stash Without Removing

syntax
git stash apply [stash@{n}]
example
git stash apply
git stash apply stash@{1}

Note Unlike pop, apply keeps the stash in the list. Useful when you want to apply the same stash to multiple branches.

List All Stashes

syntax
git stash list
example
git stash list
output
stash@{0}: On main: WIP: payment form validation
stash@{1}: WIP on feature/auth: a1b2c3d Add login
stash@{2}: On main: WIP: new config files

Note Stashes are stored as a stack — stash@{0} is always the most recent. The list shows which branch and commit each stash was created from.

Delete a Stash

syntax
git stash drop [stash@{n}]
git stash clear
example
# Drop a specific stash:
git stash drop stash@{1}

# Delete ALL stashes:
git stash clear
output
Dropped stash@{1} (a1b2c3d4e5f6...)

Note WARNING: git stash clear deletes every stash with no confirmation and no way to recover them. Drop individual stashes if you're not sure.

View Stash Contents

syntax
git stash show [-p] [stash@{n}]
example
# Summary of changes:
git stash show stash@{0}

# Full diff:
git stash show -p stash@{0}
output
 src/cart.js | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

Note Without -p, you see a summary like git diff --stat. With -p, you see the full diff. Useful for checking what's in a stash before applying it.

Create Branch from Stash

syntax
git stash branch <branch-name> [stash@{n}]
example
git stash branch feature/rescue-work stash@{0}
output
Switched to a new branch 'feature/rescue-work'
On branch feature/rescue-work
Changes not staged for commit:
  modified:   src/cart.js
Dropped stash@{0} (a1b2c3d...)

Note Creates a new branch starting from the commit where the stash was originally created, applies the stash, then drops it. Perfect when you stashed on the wrong branch.

Stash Specific Files

syntax
git stash push [-m "message"] -- <file1> <file2>
example
git stash push -m "just the config changes" -- config.yaml .env.example
output
Saved working directory and index state On main: just the config changes

Note The -- separator tells Git that everything after it is a file path. You can also use git stash push -p to interactively choose which hunks to stash.

Undoing Changes

Discard Working Directory Changes

syntax
git restore <file>
git restore .
example
# Discard changes to one file:
git restore src/app.js

# Discard all unstaged changes:
git restore .

Note WARNING: This permanently discards your uncommitted changes — there is no undo. The file reverts to whatever is in the staging area (or HEAD if nothing is staged). Double-check with git diff first.

Unstage a File

syntax
git restore --staged <file>
example
git restore --staged secrets.env
git restore --staged .

Note This moves the file from staged back to unstaged. Your actual changes in the working directory are preserved — nothing is lost. This is the modern replacement for git reset HEAD <file>.

Undo Commit, Keep Changes Staged

syntax
git reset --soft HEAD~<n>
example
# Undo the last commit, keep everything staged:
git reset --soft HEAD~1

Note Moves HEAD back but leaves your changes in the staging area, ready to be committed again. Perfect for rewriting a commit message or combining the last few commits into one.

Undo Commit, Keep Changes Unstaged

syntax
git reset HEAD~<n>
git reset --mixed HEAD~<n>
example
# Undo last commit, unstage changes but keep them:
git reset HEAD~1
output
Unstaged changes after reset:
M  src/auth.js
M  src/routes.js

Note --mixed is the default mode. Changes end up in your working directory as unstaged modifications. You can re-add specific files and commit differently.

Undo Commit and Discard All Changes

syntax
git reset --hard HEAD~<n>
git reset --hard <commit>
example
# Throw away the last commit entirely:
git reset --hard HEAD~1

# Reset to a specific commit:
git reset --hard a1b2c3d
output
HEAD is now at f4e5d6c Update API response format

Note DANGER: This permanently destroys uncommitted changes AND the commits you reset past. There is no undo for uncommitted work. Commits can be recovered via git reflog within ~30 days, but unstaged/uncommitted edits are gone forever.

Revert a Commit (Safe Undo)

syntax
git revert <commit>
example
git revert a1b2c3d
git revert HEAD
output
[main g7h8i9j] Revert "Add user authentication endpoint"
 2 files changed, 3 deletions(-), 45 insertions(-)

Note Revert creates a NEW commit that undoes the changes from the specified commit. Unlike reset, it doesn't rewrite history, making it safe for shared/public branches. Always prefer revert over reset for pushed commits.

Amend the Last Commit

syntax
git commit --amend [-m "new message"]
example
# Fix the commit message:
git commit --amend -m "Add user auth endpoint with JWT support"

# Add forgotten files to the last commit:
git add forgotten-file.js
git commit --amend --no-edit
output
[main b2c3d4e] Add user auth endpoint with JWT support

Note WARNING: Amending rewrites the commit hash. Never amend a commit that has already been pushed to a shared branch — other developers who pulled the original commit will have conflicts. Use git revert instead for pushed commits.

Restore a File from a Specific Commit

syntax
git restore --source=<commit> <file>
git checkout <commit> -- <file>
example
# Restore app.js to how it was 3 commits ago:
git restore --source=HEAD~3 src/app.js

# Classic syntax:
git checkout a1b2c3d -- src/app.js

Note This replaces the file in your working directory with the version from that commit. The change is staged automatically. Useful for recovering a deleted or broken file without resetting the whole branch.

Remove Untracked Files

syntax
git clean -f [-d] [-n]
example
# Preview what would be deleted:
git clean -n -d

# Actually delete untracked files and directories:
git clean -f -d
output
Would remove build/
Would remove temp.log

Removing build/
Removing temp.log

Note DANGER: This permanently deletes files that are not tracked by Git. Always run with -n (dry run) first to preview what will be removed. Use -x to also remove files that match .gitignore patterns (like node_modules).

Reset a Single File to HEAD

syntax
git checkout HEAD -- <file>
git restore --source=HEAD --staged --worktree <file>
example
# Fully reset one file (both staging and working directory):
git restore --source=HEAD --staged --worktree src/config.js

Note This restores the file to exactly how it is in the last commit, discarding both staged and unstaged changes to that specific file only.

Rebasing

Rebase onto Another Branch

syntax
git rebase <base-branch>
example
# While on feature/payments:
git switch feature/payments
git rebase main
output
Successfully rebased and updated refs/heads/feature/payments.

Note Rebase replays your branch's commits on top of the target branch, creating a linear history. NEVER rebase commits that have been pushed to a shared branch — it rewrites commit hashes and will cause chaos for collaborators.

Interactive Rebase

syntax
git rebase -i HEAD~<n>
git rebase -i <commit>
example
# Rewrite the last 4 commits:
git rebase -i HEAD~4

# Editor opens with:
# pick a1b2c3d Add login page
# pick d4e5f6g Add signup page
# pick h7i8j9k Fix typo in login
# pick l0m1n2o Update styles
#
# Change 'pick' to: squash, reword, edit, drop, etc.

Note Commands: pick (keep as-is), reword (change message), squash (merge into previous commit), fixup (squash but discard message), edit (pause to modify), drop (delete commit). Save and close the editor to execute.

Squash Multiple Commits into One

syntax
git rebase -i HEAD~<n>
# Change 'pick' to 'squash' or 'fixup' for commits to merge
example
git rebase -i HEAD~3

# In the editor, change to:
# pick a1b2c3d Add user profile feature
# squash d4e5f6g Fix profile image upload
# squash h7i8j9k Add profile validation

# Save — a new editor opens to combine commit messages
output
[detached HEAD x1y2z3w] Add user profile feature
 Date: Mon Mar 30 14:22:01 2026 +0000
 3 files changed, 120 insertions(+), 15 deletions(-)

Note Use 'squash' to combine commit messages. Use 'fixup' (or just 'f') to discard the squashed commit's message entirely. Great for cleaning up WIP commits before merging to main.

Rebase onto a Different Base

syntax
git rebase --onto <new-base> <old-base> <branch>
example
# Move feature/settings from feature/auth onto main:
git rebase --onto main feature/auth feature/settings

Note This is for when you branched off the wrong branch. It takes the commits unique to <branch> (after <old-base>) and replays them onto <new-base>. Visualize it as transplanting a range of commits.

Abort a Rebase in Progress

syntax
git rebase --abort
example
# Things went wrong during rebase — bail out:
git rebase --abort

Note Restores your branch to exactly how it was before the rebase started. Safe to run at any point during a rebase. No work is lost.

Continue Rebase After Resolving Conflict

syntax
git rebase --continue
example
# After resolving conflicts in a file:
git add src/auth.js
git rebase --continue

Note When a conflict occurs during rebase, resolve it, stage the file with git add, then continue. Do NOT commit — rebase --continue handles the commit for you.

Skip a Conflicting Commit During Rebase

syntax
git rebase --skip
example
git rebase --skip

Note Drops the current conflicting commit entirely and moves to the next one. Use this when the conflict is caused by a commit that's no longer needed (e.g., it was already applied upstream).

Autosquash with Fixup Commits

syntax
git commit --fixup=<commit>
git rebase -i --autosquash <base>
example
# Make a commit that will auto-squash into a1b2c3d:
git commit --fixup=a1b2c3d

# Later, rebase with autosquash:
git rebase -i --autosquash main

Note Git automatically reorders and marks fixup commits to be squashed into their target. The commit message starts with 'fixup!' followed by the target's message. Set rebase.autoSquash=true in config to always enable this.

History & Inspection

Compact Log

syntax
git log --oneline [-n]
example
git log --oneline -5
output
a1b2c3d Add user authentication endpoint
f4e5d6c Update API response format
9g8h7i6 Fix cart total calculation
b3c4d5e Add product search
1e2f3g4 Initial commit

Note Shows each commit on one line with abbreviated hash and message. Add -n to limit the number of results.

Visual Branch Graph

syntax
git log --oneline --graph --all
example
git log --oneline --graph --all --decorate
output
* a1b2c3d (HEAD -> main) Merge feature/payments
|\  
| * d4e5f6g Add payment processing
| * h7i8j9k Add payment form
|/  
* 9g8h7i6 Update dependencies
* b3c4d5e Initial commit

Note This is the CLI equivalent of a graphical Git viewer. --all shows all branches, --decorate labels commits with branch/tag names. A great alias candidate: git config --global alias.lg 'log --oneline --graph --all --decorate'

See Who Changed Each Line

syntax
git blame <file> [-L start,end]
example
git blame src/auth.js
git blame -L 10,25 src/auth.js
output
a1b2c3d (Samira Khan  2026-03-30 14:22) const validateToken = (token) => {
d4e5f6g (Dev Patel    2026-03-28 09:15)   if (!token) return null;
a1b2c3d (Samira Khan  2026-03-30 14:22)   return jwt.verify(token, SECRET);

Note Shows the commit, author, date, and line content for each line. Use -L to limit to a range. Blame doesn't mean assigning fault — it's just finding who last changed a line and why.

Binary Search for Bug Introduction

syntax
git bisect start
git bisect bad [commit]
git bisect good <commit>
example
git bisect start
git bisect bad          # Current commit is broken
git bisect good v1.0.0  # This tag was working

# Git checks out a middle commit, you test it:
# If it's good:
git bisect good
# If it's bad:
git bisect bad
# Repeat until Git finds the first bad commit

git bisect reset  # Return to original branch
output
Bisecting: 8 revisions left to test after this (roughly 3 steps)
a1b2c3d is the first bad commit

Note Bisect uses binary search to find the exact commit that introduced a bug. For N commits, it takes at most log2(N) steps. You can automate it with: git bisect run <test-script>

View Reference Log (Recovery Tool)

syntax
git reflog [branch]
example
git reflog
git reflog show feature/payments
output
a1b2c3d HEAD@{0}: commit: Add auth endpoint
f4e5d6c HEAD@{1}: reset: moving to HEAD~1
9g8h7i6 HEAD@{2}: commit: Broken attempt
b3c4d5e HEAD@{3}: checkout: moving from feature to main

Note The reflog records every time HEAD moves. It's your safety net — even after a hard reset or accidental branch deletion, you can find the commit hash here and recover with: git reset --hard HEAD@{n}. Entries expire after ~90 days.

Diff Between Two Branches

syntax
git diff <branch1>..<branch2>
git diff <branch1>...<branch2>
example
# All differences between the two branches:
git diff main..feature/payments

# Only changes introduced in feature/payments since it diverged from main:
git diff main...feature/payments

Note Two dots (..) shows the full diff between branch tips. Three dots (...) shows only what changed in the right branch since the common ancestor. Three dots is usually what you want for reviewing a feature branch.

Cherry-Pick a Commit

syntax
git cherry-pick <commit> [<commit2> ...]
example
git switch main
git cherry-pick a1b2c3d

# Cherry-pick a range:
git cherry-pick d4e5f6g..h7i8j9k
output
[main x1y2z3w] Add user authentication endpoint
 2 files changed, 45 insertions(+), 3 deletions(-)

Note Creates a NEW commit with the same changes (but different hash). Useful for applying a bugfix from one branch to another without merging the whole branch. If there's a conflict, resolve it and run git cherry-pick --continue.

Search Commit Messages & Content

syntax
git log --grep="<pattern>"
git log -S "<string>"
git log -G "<regex>"
example
# Find commits mentioning 'authentication':
git log --grep="authentication" --oneline

# Find commits where 'validateToken' was added or removed:
git log -S "validateToken" --oneline

# Regex search in diffs:
git log -G "api.*endpoint" --oneline
output
a1b2c3d Add user authentication endpoint
f4e5d6c Refactor authentication middleware

Note -S (pickaxe) finds commits where the number of occurrences of a string changed. -G finds commits where the diff matches a regex. --grep searches commit messages only.

View History of a Specific File

syntax
git log [--follow] -- <file>
example
git log --oneline -- src/auth.js
git log --follow --oneline -- src/auth.js
output
a1b2c3d Add user authentication endpoint
f4e5d6c Refactor auth module
9g8h7i6 Initial auth setup

Note Use --follow to track the file across renames. Without it, Git stops at the rename point. Add -p to see the actual diff for each commit.

Custom Log Format

syntax
git log --pretty=format:"<placeholders>"
example
git log --pretty=format:"%h %an %ar %s" -10
output
a1b2c3d Samira Khan 2 days ago Add auth endpoint
f4e5d6c Dev Patel 5 days ago Update API format
9g8h7i6 Aiko Tanaka 1 week ago Fix cart bug

Note Common placeholders: %h (short hash), %H (full hash), %an (author name), %ae (author email), %ar (relative date), %s (subject), %b (body). %C(color) adds color — e.g. %C(yellow)%h%C(reset).

Tags

Create a Lightweight Tag

syntax
git tag <name> [commit]
example
git tag v1.0.0
git tag v0.9.0 a1b2c3d

Note Lightweight tags are just pointers to a commit — no extra metadata. For releases, prefer annotated tags which include author, date, and a message.

Create an Annotated Tag

syntax
git tag -a <name> -m "message" [commit]
example
git tag -a v2.0.0 -m "Major release: new payment system"
git tag -a v1.5.0 -m "Performance improvements" a1b2c3d

Note Annotated tags store the tagger's name, email, date, and a message. They're full Git objects. Use these for releases so you have a record of who tagged and why.

List Tags

syntax
git tag [-l "pattern"]
example
git tag
git tag -l "v2.*"
output
v1.0.0
v1.5.0
v2.0.0
v2.1.0

Note Use -l with a glob pattern to filter. Combine with --sort=-version:refname to sort by semantic version descending.

Push Tags to Remote

syntax
git push origin <tag>
git push origin --tags
example
# Push a single tag:
git push origin v2.0.0

# Push all tags:
git push origin --tags
output
To https://github.com/user/project.git
 * [new tag]         v2.0.0 -> v2.0.0

Note Tags are NOT pushed automatically with git push. You must explicitly push them. --tags pushes all local tags that don't exist on the remote.

Delete a Tag

syntax
git tag -d <tag>
git push origin --delete <tag>
example
# Delete locally:
git tag -d v1.0.0-beta

# Delete from remote:
git push origin --delete v1.0.0-beta
output
Deleted tag 'v1.0.0-beta' (was a1b2c3d)

Note You must delete a tag both locally and on the remote — they're independent. Other collaborators who already fetched the tag will still have it until they prune.

Checkout a Tag

syntax
git checkout <tag>
example
git checkout v2.0.0
output
Note: switching to 'v2.0.0'.
You are in 'detached HEAD' state.

Note Checking out a tag puts you in detached HEAD state — you're not on any branch. To make changes, create a branch from the tag: git switch -c hotfix/v2.0.1

View Tag Details

syntax
git show <tag>
example
git show v2.0.0
output
tag v2.0.0
Tagger: Samira Khan <samira@example.com>
Date:   Mon Mar 30 14:22:01 2026 +0000

Major release: new payment system

commit a1b2c3d...
...

Note For annotated tags, shows the tagger info and message plus the commit it points to. For lightweight tags, just shows the commit.

Find the Latest Tag

syntax
git describe --tags --abbrev=0
git describe --tags
example
git describe --tags --abbrev=0
git describe --tags
output
v2.0.0
v2.0.0-3-ga1b2c3d

Note --abbrev=0 gives just the tag name. Without it, Git also shows how many commits since that tag and the current abbreviated hash. Useful in CI/CD for version stamping.

Advanced

Working with Multiple Worktrees

syntax
git worktree add <path> <branch>
git worktree list
git worktree remove <path>
example
# Check out main in a separate directory:
git worktree add ../project-main main

# Now you can work on both branches simultaneously
# List all worktrees:
git worktree list
output
/home/user/project         a1b2c3d [feature/payments]
/home/user/project-main    f4e5d6c [main]

Note Worktrees let you have multiple branches checked out at once in different directories, sharing the same .git database. Huge time-saver when you need to quickly fix a bug on main without stashing your feature work.

Submodules

syntax
git submodule add <url> <path>
git submodule update --init --recursive
example
# Add a submodule:
git submodule add https://github.com/lib/shared-utils.git libs/shared

# After cloning a repo with submodules:
git submodule update --init --recursive

# Update submodule to latest:
cd libs/shared && git pull origin main
cd ../.. && git add libs/shared && git commit -m "Update shared-utils submodule"

Note Submodules are notoriously tricky. Each submodule is pinned to a specific commit. Collaborators must run git submodule update --init after cloning or they'll have empty directories. Consider alternatives like package managers when possible.

Sparse Checkout (Partial Clone)

syntax
git sparse-checkout init --cone
git sparse-checkout set <dir1> <dir2>
example
git clone --filter=blob:none --sparse https://github.com/large/monorepo.git
cd monorepo
git sparse-checkout set services/auth packages/shared

Note Only checks out specified directories from a large repo. Extremely useful for monorepos where you only need a small portion. --filter=blob:none avoids downloading file content you don't need.

Rewrite Repository History (git-filter-repo)

syntax
git filter-repo --path <path-to-keep>
git filter-repo --invert-paths --path <path-to-remove>
example
# Remove a file from entire history (e.g., accidentally committed secret):
git filter-repo --invert-paths --path secrets.env

# Extract a subdirectory into its own repo:
git filter-repo --path src/auth/

Note WARNING: This rewrites ALL commit hashes. Every collaborator must re-clone. git-filter-repo is a separate tool (pip install git-filter-repo) that replaces the deprecated git filter-branch. Back up your repo before using this.

Git Hooks

syntax
# Hooks live in .git/hooks/
# Common hooks: pre-commit, pre-push, commit-msg, post-merge
example
# Create a pre-commit hook that runs linting:
# File: .git/hooks/pre-commit
#!/bin/sh
npm run lint
if [ $? -ne 0 ]; then
  echo "Linting failed. Commit aborted."
  exit 1
fi

# Make it executable:
chmod +x .git/hooks/pre-commit

Note Hooks in .git/hooks/ are local and not committed to the repo. Use tools like Husky (Node) or pre-commit (Python) to share hooks via the repo. The hook must be executable and must exit 0 to allow the action to proceed.

Sign Commits with GPG

syntax
git config --global commit.gpgsign true
git config --global user.signingkey <key-id>
git commit -S -m "message"
example
# Find your GPG key:
gpg --list-secret-keys --keyid-format=long

# Configure Git to sign:
git config --global user.signingkey ABC123DEF456
git config --global commit.gpgsign true

# Verify a signed commit:
git log --show-signature -1
output
gpg: Signature made Mon 30 Mar 14:22:01 2026
gpg: Good signature from "Samira Khan <samira@example.com>"

Note Signing proves you actually authored a commit. GitHub shows a 'Verified' badge for signed commits. You can also use SSH keys for signing (Git 2.34+): git config --global gpg.format ssh

Reuse Recorded Resolution (rerere)

syntax
git config --global rerere.enabled true
example
git config --global rerere.enabled true

# Now when you resolve a merge conflict, Git remembers.
# Next time the same conflict appears, Git resolves it automatically.
output
Recorded resolution for 'src/config.js'.
...
Resolved 'src/config.js' using previous resolution.

Note rerere = 'reuse recorded resolution'. Incredibly useful when you repeatedly rebase a long-lived branch and keep hitting the same conflict. Enable it globally — there's no downside.

Ignore Bulk Formatting Commits in Blame

syntax
git blame --ignore-revs-file .git-blame-ignore-revs <file>
example
# Create .git-blame-ignore-revs in the repo root:
# Content: one commit hash per line
# a1b2c3d4e5f6... (bulk prettier formatting)

git config blame.ignoreRevsFile .git-blame-ignore-revs
git blame src/app.js

Note After running a bulk formatting tool (Prettier, Black, etc.), every line shows you as the author in blame. This file tells Git to skip those commits, preserving meaningful blame history. GitHub also respects this file automatically.

Create a Repository Bundle

syntax
git bundle create <file> <refs>
git clone <bundle-file> <dir>
example
# Bundle the entire repo:
git bundle create repo.bundle --all

# Bundle just the last 10 commits on main:
git bundle create recent.bundle -10 main

# Clone from a bundle (offline transfer):
git clone repo.bundle my-project

Note Bundles are single-file snapshots of a repo that can be transferred via USB, email, etc. Useful for air-gapped environments or when network access is limited.

Common Mistakes & Recovery

Force Push Dangers

syntax
git push --force-with-lease origin <branch>
example
# DANGEROUS — overwrites remote without checking:
git push --force origin main  # DON'T DO THIS

# SAFER — fails if someone else pushed since your last fetch:
git push --force-with-lease origin feature/my-work

Note NEVER force push to main/master or any shared branch. It rewrites remote history and other developers' work will be lost or corrupted. If you must force push (e.g., after rebase on a personal branch), always use --force-with-lease which checks that the remote hasn't changed since you last fetched.

Detached HEAD State

syntax
# You're in detached HEAD when:
git checkout <commit-hash>
git checkout <tag>

# To fix — create a branch:
git switch -c <new-branch>
example
git checkout a1b2c3d
# Warning: you are in 'detached HEAD' state.

# If you made commits here and want to keep them:
git switch -c rescue-branch

# If you just want to go back to a branch:
git switch main

Note Detached HEAD means you're not on any branch. Commits made here will be garbage-collected if you switch away without creating a branch. If you accidentally left commits behind, find them with git reflog.

Merge vs Rebase: When to Use Which

syntax
# Merge: preserves history as it happened
git merge feature/branch

# Rebase: creates linear history
git rebase main
example
# Team rule of thumb:
# - Rebase your local/personal feature branch onto main regularly
git switch feature/my-work
git rebase main

# - Merge the feature branch into main when done
git switch main
git merge --no-ff feature/my-work

Note The golden rule: NEVER rebase commits that exist on a remote branch others are using. Rebase rewrites history (new hashes), while merge preserves it. Use rebase to keep your feature branch up-to-date privately; use merge to integrate completed work publicly.

Recover Lost Commits

syntax
git reflog
git reset --hard <hash-from-reflog>
git cherry-pick <hash>
example
# Find the lost commit:
git reflog
# Look for the entry before the mistake:
# a1b2c3d HEAD@{5}: commit: Important work

# Option 1: Reset to that point:
git reset --hard a1b2c3d

# Option 2: Cherry-pick just that commit:
git cherry-pick a1b2c3d

# Option 3: Create a branch from it:
git branch recovery a1b2c3d

Note Reflog entries expire after ~90 days by default. As long as the commit is in the reflog, it hasn't been garbage-collected and you can recover it. If you ran git gc manually, some commits may be permanently lost.

.gitignore Not Ignoring Files

syntax
git rm --cached <file>
git rm -r --cached <directory>
example
# File was tracked before adding to .gitignore:
git rm --cached .env
git rm -r --cached node_modules/
git commit -m "Stop tracking ignored files"
output
rm '.env'
rm 'node_modules/package-lock.json'
...

Note .gitignore only prevents UNTRACKED files from being added. If a file is already tracked, .gitignore has no effect on it. You must remove it from tracking with git rm --cached (the file itself stays on disk). This is one of the most common Git gotchas.

Undo a Commit That's Already Pushed

syntax
git revert <commit>
git push
example
# The safe way — create a new commit that reverses the changes:
git revert a1b2c3d
git push origin main

# If you need to undo multiple commits:
git revert a1b2c3d..f4e5d6g
git push origin main

Note NEVER use git reset + force push on a shared branch. Use git revert instead, which creates a new commit that undoes the changes while preserving history. Everyone who pulled the original commit won't have conflicts.

Accidentally Committed a Secret

syntax
git filter-repo --invert-paths --path <secret-file>
# Then: rotate the credential immediately
example
# Step 1: ROTATE THE SECRET IMMEDIATELY
# (It's in the remote history even if you delete it)

# Step 2: Remove from all history:
pip install git-filter-repo
git filter-repo --invert-paths --path .env

# Step 3: Force push the rewritten history:
git push --force --all

# Step 4: Tell all collaborators to re-clone

Note CRITICAL: Even if you delete the file in a new commit, the secret is still in the Git history and anyone can find it. The FIRST thing to do is rotate/revoke the compromised credential. Rewriting history with filter-repo is step two. GitHub also has a 'secret scanning' feature that alerts you.

Committed to the Wrong Branch

syntax
# Move last commit to a different branch:
git switch <correct-branch>
git cherry-pick <commit>
git switch <wrong-branch>
git reset --hard HEAD~1
example
# Oops, committed to main instead of feature branch:
git log --oneline -1  # Note the hash: a1b2c3d

git switch feature/payments
git cherry-pick a1b2c3d

git switch main
git reset --hard HEAD~1

Note If the wrong branch hasn't been pushed yet, reset is safe. If it's already pushed, you'll need git revert on the wrong branch instead of reset, to avoid force-pushing shared history.

Accidentally Committed Large Files

syntax
git rm --cached <large-file>
git filter-repo --strip-blobs-bigger-than 50M
example
# If not yet pushed — remove and recommit:
git rm --cached data/huge-dataset.csv
echo 'data/*.csv' >> .gitignore
git commit -m "Remove large file, update gitignore"

# If already pushed — rewrite history:
git filter-repo --strip-blobs-bigger-than 50M

Note Git stores every version of every file forever. A 500MB file committed once bloats the repo permanently (even after deletion) unless you rewrite history. Use Git LFS for large files, and add size limits to your .gitignore proactively.