cheat sheet
git
Complete Git reference. Setup, staging, committing, branching, merging, rebasing, stashing, remotes, tags, log inspection, undoing mistakes, submodules, and power-user aliases.
git — Version Control
What it is
Git is a free, open-source distributed version control system originally created by Linus Torvalds in 2005 to manage the Linux kernel source tree, now maintained by the Git project at git-scm.com. It tracks changes to files over time, supports non-linear development through cheap branching and merging, and works entirely locally before synchronizing with a remote like GitHub or GitLab. Reach for Git in any project where you need change history, parallel feature branches, or collaboration — it is the de-facto standard for source code management across virtually every software project today.
Setup
Git configuration is stored at three levels: system (/etc/gitconfig), user (~/.gitconfig), and repo (.git/config). Set your identity and preferences once globally; override per-repo only when you need a different author or behavior for a specific project.
git config --global user.name "Alice Dev"
git config --global user.email "alice@example.com"
git config --global core.editor "vim"
git config --global init.defaultBranch main
git config --global pull.rebase false # merge on pull (safer default)
git config --global push.default current # push current branch
git config --global core.autocrlf input # Linux/macOS: convert CRLF → LF
git config --global diff.colorMoved zebra # highlight moved code differently
git config --list --show-origin # show all config + source file
git config --global --edit # open global config in editor
Output:
# git config --list --show-origin
file:/home/alice/.gitconfig user.name=Alice Dev
file:/home/alice/.gitconfig user.email=alice@example.com
file:/home/alice/.gitconfig core.editor=vim
file:/home/alice/.gitconfig init.defaultbranch=main
file:/home/alice/.gitconfig pull.rebase=false
file:/home/alice/.gitconfig push.default=current
file:.git/config core.repositoryformatversion=0
file:.git/config remote.origin.url=git@github.com:alicedev/myrepo.git
file:.git/config branch.main.remote=origin
Configuration
Git reads configuration from a layered set of files, with later layers overriding earlier ones. Knowing which file owns a setting is the difference between a global preference and a per-repo override that silently masks it.
| Scope | Path | Set with |
|---|---|---|
| System | /etc/gitconfig (or $(prefix)/etc/gitconfig) | git config --system |
| Global (user) | ~/.gitconfig, or ~/.config/git/config if it exists | git config --global |
| Worktree | <repo>/.git/config.worktree (when extensions.worktreeConfig=true) | git config --worktree |
| Local (repo) | <repo>/.git/config | git config --local |
| Per-command | git -c key=value … | command-line override |
Two non-config files in the working tree also shape behaviour: <repo>/.gitignore (path patterns Git should never track — combined with ~/.config/git/ignore for personal-scope ignores) and <repo>/.gitattributes (per-path settings such as merge driver, line-ending conversion, and linguist-generated). .git/info/exclude is a repo-local ignore list that is never committed.
git config --list --show-origin --show-scope # all values with file + scope
git config --get-all user.email # all values (including duplicates)
git config get --file ~/.gitconfig user.email # read from a specific file (git 2.45+ subcommand form)
git config --global include.path ~/.gitconfig.work # conditional include support
# Conditional include (typed in ~/.gitconfig) — apply a config file only when
# the repo lives under ~/work/
[includeIf "gitdir:~/work/"]
path = ~/.gitconfig.work
Output:
# git config --list --show-origin --show-scope (excerpt)
global file:/home/alice/.gitconfig user.name=Alice Dev
global file:/home/alice/.gitconfig user.email=alice@example.com
local file:.git/config remote.origin.url=git@github.com:alicedev/myrepo.git
local file:.git/config branch.main.remote=origin
Init and clone
git init creates a new repository in the current directory; git clone copies an existing one — including all history, branches, and tags — to your machine. Use --depth 1 for large repos when you only need the latest snapshot, not the full history.
git init # new repo in cwd
git init --bare /srv/repo.git # bare repo (server)
git clone https://github.com/u/r # clone
git clone --depth 1 URL # shallow clone (latest commit only)
git clone --branch dev URL # clone specific branch
git clone --single-branch URL # only the cloned branch
git clone --recurse-submodules URL # clone + init submodules
Output: (none — exits 0 on success)
Staging and committing
The staging area (index) lets you build a commit incrementally — you decide exactly which changes go in, leaving unrelated edits in the working tree. Use git add -p to stage individual hunks when a file contains multiple unrelated changes.
git status # working tree state
git status -s # short format
git add file.txt # stage a file
git add src/ # stage a directory
git add -p # interactively stage hunks
git add -u # stage all tracked changes (no new files)
git add . # stage everything in cwd (use with care)
git commit -m "message"
git commit -m "subject" -m "body" # multi-paragraph
git commit --amend # amend last commit (staged changes + msg)
git commit --amend --no-edit # amend without changing message
git commit -C HEAD --amend # amend keeping exact same message
git commit --allow-empty -m "ci: trigger" # empty commit
Output:
# git status
On branch main
Your branch is up to date with 'origin/main'.
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: src/app.py
new file: src/utils.py
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
modified: README.md
Untracked files:
(use "git add <file>..." to include in what will be committed)
config.local.yaml
# git status -s
M src/app.py
A src/utils.py
M README.md
?? config.local.yaml
Diff
git diff compares versions of your files at different stages: working tree vs. index (unstaged), index vs. HEAD (staged), or any two refs. Use --staged before committing to double-check exactly what will go into the next commit.
git diff # unstaged changes
git diff --staged # staged changes (what will be committed)
git diff HEAD # all changes vs last commit
git diff main..feature # between two branches
git diff HEAD~3 # vs 3 commits ago
git diff --stat # summary (files changed, insertions, deletions)
git diff --word-diff # word-level diff
git diff -w # ignore whitespace
Output:
# git diff --stat
src/app.py | 14 ++++++++------
src/utils.py | 8 ++++++++
README.md | 3 ++-
3 files changed, 18 insertions(+), 7 deletions(-)
# git diff (unstaged changes excerpt)
diff --git a/README.md b/README.md
index 3a1b2c4..7f8d9e0 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,6 @@
# My Project
-A short description.
+A longer, more accurate description of the project.
+See the wiki for full documentation.
## Installation
Branching
A branch is a lightweight, movable pointer to a commit — creating one is nearly instant and costs almost no disk space. Create a branch per feature, fix, or experiment so the main line stays stable until you're ready to merge or rebase.
git branch # list local branches
git branch -a # list all (local + remote)
git branch -v # with last commit message
git branch feature/login # create branch (don't switch)
git switch feature/login # switch to branch (stable since git 2.51)
git switch -c feature/login # create + switch
git switch - # switch back to previous branch
git checkout -b feature/login # legacy equivalent
git branch -d feature/login # delete (safe: checks merge)
git branch -D feature/login # delete (force)
git branch -m old-name new-name # rename local branch
git branch --merged main # branches merged into main
git branch --no-merged # branches NOT yet merged
Output:
# git branch -a
* main
feature/login
fix/header-bug
remotes/origin/main
remotes/origin/feature/login
remotes/origin/release/v2.0
Merging
Merging integrates the changes from one branch into another, creating a merge commit that preserves the full history of both lines of development. Use --no-ff to always record a merge commit even when fast-forward is possible, making the branch's existence visible in the log.
git merge feature/login # merge into current branch
git merge --no-ff feature/login # always create merge commit
git merge --squash feature/login # squash into one staged change
git merge --abort # abort in-progress merge
# Conflict resolution
git mergetool # open visual merge tool
git checkout --ours file.txt # take our version
git checkout --theirs file.txt # take their version
git add file.txt && git commit # mark resolved
Output: (none — exits 0 on success)
Rebasing
Rebase replays your branch's commits on top of a new base commit, rewriting their SHAs to produce a linear history. Use it to clean up local work before merging or to keep a feature branch current with main without introducing merge commits — but never rebase commits that others have already pulled.
git rebase main # rebase current branch onto main
git rebase -i HEAD~4 # interactive: rewrite last 4 commits
git rebase --continue # after resolving conflict
git rebase --abort # abandon rebase
git rebase --skip # skip current conflicting commit
# Interactive rebase commands (used in editor)
# pick = keep commit as-is
# reword = keep, edit message
# edit = keep, pause to amend
# squash = meld into previous, combine messages
# fixup = meld into previous, discard message
# drop = remove commit entirely
# exec = run shell command after this line
Output: (none — exits 0 on success)
Stashing
A stash saves your uncommitted changes (staged and unstaged) onto a stack so you can switch context without making a throwaway commit. Use it when you need to pull, switch branches, or apply a hotfix before your current work is ready to commit.
git stash # stash tracked changes
git stash push -m "WIP login form" # named stash
git stash push -u # include untracked files
git stash push -p # interactively pick hunks to stash
git stash list # list stashes
git stash show stash@{0} # inspect latest stash
git stash show -p stash@{1} # full diff of stash
git stash pop # apply + drop latest stash
git stash apply stash@{2} # apply without dropping
git stash drop stash@{0} # delete a stash
git stash clear # delete all stashes
git stash branch feature/fix stash@{0} # create branch from stash
Output:
# git stash list
stash@{0}: On main: WIP login form
stash@{1}: On feature/api: save progress before rebase
stash@{2}: WIP on main: 3f4a1bc fix: correct redirect URL
Remotes
A remote is a named reference to a repository hosted elsewhere — typically origin for your own fork and upstream for the canonical source. fetch downloads remote changes without touching your working tree; pull fetches and then merges (or rebases) into the current branch.
git remote -v # list remotes
git remote add origin URL # add remote
git remote rename origin upstream # rename
git remote remove upstream # remove
git remote set-url origin NEW_URL # change URL
git fetch # download remote changes (no merge)
git fetch --prune # + remove deleted remote branches
git fetch --all # fetch all remotes
git pull # fetch + merge (or rebase if configured)
git pull --rebase # fetch + rebase
git pull origin main --rebase # explicit
git push # push current branch
git push -u origin feature/login # push + set upstream
git push --force-with-lease # force push (safe: checks remote state)
git push origin --delete feature # delete remote branch
git push --tags # push all tags
Output:
# git remote -v
origin git@github.com:alicedev/myrepo.git (fetch)
origin git@github.com:alicedev/myrepo.git (push)
upstream https://github.com/upstream-org/myrepo.git (fetch)
upstream https://github.com/upstream-org/myrepo.git (push)
After a repo rename (GitHub)
When a GitHub repository is renamed, update the local remote and re-sync.
git remote -v # confirm current URL
git remote set-url origin https://github.com/<user>/<new-repo>.git # HTTPS
git remote set-url origin git@github.com:<user>/<new-repo>.git # SSH (alternative)
git fetch origin # download updated refs
git branch # confirm local branch name
git pull origin main # merge latest commits
Output: (none — exits 0 on success)
If the upstream tracking reference is missing after the rename:
git branch --set-upstream-to=origin/main main
# or for master-based repos:
git branch --set-upstream-to=origin/master master
Output: (none — exits 0 on success)
Commit or stash any local changes before pulling:
git status
git stash push -u -m "before pull after rename"
git pull origin main
git stash pop
Output: (none — exits 0 on success)
Tags
Tags mark specific commits as significant — typically release versions. Lightweight tags are just named pointers; annotated tags (-a) store a message, tagger identity, and date and are recommended for releases because they carry richer metadata.
git tag # list tags
git tag v1.0.0 # lightweight tag on HEAD
git tag -a v1.0.0 -m "Release 1.0" # annotated tag (recommended)
git tag -a v1.0.0 abc1234 # tag a specific commit
git tag -d v1.0.0 # delete local tag
git push origin v1.0.0 # push one tag
git push origin --tags # push all tags
git push origin --delete v1.0.0 # delete remote tag
git describe --tags # current tag + distance
Output:
# git tag -l
v0.9.0
v1.0.0
v1.1.0
v1.2.0-rc1
v2.0.0
# git describe --tags
v2.0.0-14-g3f4a1bc
Log and history
git log shows the commit history reachable from the current branch, with filters for author, date, message, and file paths. Combine --oneline --graph --all for a compact, visual overview of how all branches relate to each other.
git log # full log
git log --oneline # one line per commit
git log --oneline --graph --all # branch graph (text)
git log --stat # with file change summary
git log -p # with full diff
git log -10 # last 10 commits
git log --author="Alice"
git log --since="2 weeks ago"
git log --after="2025-01-01" --before="2025-03-31"
git log --grep="fix:" # commits where message matches
git log -S "function_name" # commits that added/removed string (pickaxe)
git log -G "regex" # commits where diff matches regex
git log -- path/to/file # history of a file
git log main..feature # commits in feature not in main
git log origin/main..HEAD # commits not yet pushed
# Pretty format
git log --pretty=format:"%h %as %an %s"
# %h = short hash, %as = author date (YYYY-MM-DD), %an = author name, %s = subject
Output:
# git log --oneline
3f4a1bc fix: correct redirect URL after OAuth flow
a8d2e91 feat: add login page with form validation
c5b3f20 refactor: extract auth helpers into utils module
9e1a047 docs: update README with setup instructions
71c8d3a chore: bump dependencies to latest patch versions
# git log --oneline --graph --all
* 3f4a1bc (HEAD -> main, origin/main) fix: correct redirect URL
* a8d2e91 feat: add login page
| * f2c9a14 (feature/dashboard) feat: add dashboard widgets
| * 4b1d8e3 feat: scaffold dashboard layout
|/
* c5b3f20 refactor: extract auth helpers
* 9e1a047 docs: update README
# git log --author="Alice" --oneline
a8d2e91 feat: add login page with form validation
9e1a047 docs: update README with setup instructions
Searching commits
The pickaxe (-S) finds commits that added or removed a specific string — useful for tracking when a function or variable was introduced or deleted. git bisect automates a binary search through history to pinpoint which commit introduced a bug.
git log -S "myFunction" --oneline # when was myFunction added?
git bisect start # begin binary search for bad commit
git bisect bad # mark current as bad
git bisect good v2.0.0 # mark known-good point
git bisect run ./tests.sh # automated bisect
git bisect reset # done
Output:
# git log -S "myFunction" --oneline
a8d2e91 feat: add login page with form validation
blame
git blame annotates every line of a file with the commit SHA, author, and date of its last change — useful for understanding why code looks the way it does. Use -C to detect lines moved or copied from other files so blame follows the code's true origin.
git blame file.txt # who last changed each line
git blame -L 20,40 file.txt # lines 20–40 only
git blame -w file.txt # ignore whitespace changes
git blame -C file.txt # detect moved code from other files
Output: (none — exits 0 on success)
Undoing changes
Git offers several undo tools with different risk profiles: restore and reset rewrite local state (no new commit), while revert creates a new commit that reverses a previous one — making it safe to use on shared branches. Reach for revert on anything already pushed; use reset only on local, unpushed commits.
# Discard unstaged changes
git restore file.txt # restore to HEAD (stable since git 2.51)
git restore --source=HEAD~3 file.txt # restore to a specific commit's version
git checkout -- file.txt # legacy equivalent
# Unstage (keep changes in working tree)
git restore --staged file.txt
git reset HEAD file.txt # legacy
# Undo last commit (keep changes staged)
git reset --soft HEAD~1
# Undo last commit (keep changes unstaged)
git reset HEAD~1
# Undo last commit (discard changes completely)
git reset --hard HEAD~1
# Revert a commit (creates a new "undo" commit — safe for shared branches)
git revert HEAD # revert last commit
git revert abc1234 # revert a specific commit
git revert -n abc1234 # revert without auto-committing
git revert main..HEAD # revert a range
# Restore a deleted file
git checkout HEAD -- deleted-file.txt
# Recover from a bad reset (reflog saves you)
git reflog # list every HEAD movement
git reset --hard HEAD@{3} # go back to 3 moves ago
Output: (none — exits 0 on success)
Submodules
A submodule pins a specific commit of a separate repository inside your repo — useful when you need a vendored dependency or shared library at an exact version. The parent repo stores only the submodule's URL and the pinned SHA, not the submodule's full history.
git submodule add https://github.com/u/lib libs/lib # add submodule
git submodule init # init after clone
git submodule update # fetch submodule commits
git submodule update --init --recursive # init + update all
git submodule update --remote --merge # update to latest upstream
git submodule foreach 'git pull' # run command in every submodule
git submodule status # show current commit per submodule
git submodule deinit libs/lib # remove submodule
git rm libs/lib && rm -rf .git/modules/libs/lib
Output: (none — exits 0 on success)
Worktrees (multiple working trees)
A worktree lets you check out a second branch into a separate directory without cloning the repo again — both share the same .git directory and object store. Use it when you need to review or test another branch while keeping your current working tree untouched.
git worktree add ../hotfix hotfix/urgent # new worktree for branch
git worktree list # list all worktrees
git worktree remove ../hotfix # clean up
Output:
# git worktree list
/home/alice/myrepo 3f4a1bc [main]
/home/alice/hotfix d9c2e87 [hotfix/urgent]
Reflog — the safety net
The reflog records every movement of HEAD and branch tips in your local repo, even after a reset or dropped stash. It's your last line of defence — if you lose commits with git reset --hard or delete a branch too early, the reflog lets you find the SHA and recover.
git reflog # all HEAD movements (local only)
git reflog show feature/login # reflog for a branch
git reflog expire --expire=90.days.ago --all
git gc --prune=30.days.ago # prune unreachable objects
Output:
# git reflog
3f4a1bc (HEAD -> main) HEAD@{0}: commit: fix: correct redirect URL
a8d2e91 HEAD@{1}: commit: feat: add login page
c5b3f20 HEAD@{2}: rebase (finish): returning to refs/heads/main
c5b3f20 HEAD@{3}: rebase (pick): refactor: extract auth helpers
9e1a047 HEAD@{4}: checkout: moving from feature/login to main
Useful aliases (~/.gitconfig)
[alias]
st = status -s
co = checkout
sw = switch
br = branch -vv
lg = log --oneline --graph --all --decorate
lp = log --oneline -15
df = diff --word-diff
dfs = diff --staged --word-diff
undo = reset HEAD~1
unstage = restore --staged
save = stash push -u -m
wip = commit -am "wip: save progress"
oops = commit --amend --no-edit
pushf = push --force-with-lease
aliases = config --get-regexp alias
Output:
# git aliases (i.e. git config --get-regexp alias)
alias.st status -s
alias.co checkout
alias.sw switch
alias.br branch -vv
alias.lg log --oneline --graph --all --decorate
alias.lp log --oneline -15
alias.df diff --word-diff
alias.dfs diff --staged --word-diff
alias.undo reset HEAD~1
alias.unstage restore --staged
.gitignore patterns
.gitignore lists path patterns that Git should not track — untracked files matching these patterns are silently excluded from git status and git add. Patterns apply recursively from the file's directory; prefix with / to anchor to the repo root, or prefix with ! to un-ignore a previously ignored pattern.
# Directories
node_modules/
dist/
.cache/
# Files by extension
*.log
*.pyc
*.class
# Specific files
.env
*.env.local
secrets.json
# Negate (don't ignore)
!important.log
# Anywhere in tree (no leading slash)
*.DS_Store
# Only at root
/config.local.yaml
git check-ignore -v file.txt # explain why a file is ignored
git ls-files --ignored --exclude-standard # list all ignored files
Output:
# git check-ignore -v build/output.log
.gitignore:3:*.log build/output.log
# git ls-files --ignored --exclude-standard
.cache/webpack/
dist/bundle.js
node_modules/lodash/
Cherry-pick
Cherry-pick applies the diff of a specific commit — or a range of commits — onto the current branch as one or more new commits. Use it to port a bug fix from a release branch to main, or to pull a single feature commit without merging the entire branch.
git cherry-pick abc1234 # apply a specific commit to HEAD
git cherry-pick abc1234..def5678 # apply a range of commits
git cherry-pick -n abc1234 # stage without committing
git cherry-pick --abort
git cherry-pick --continue
Output: (none — exits 0 on success)
Shortlog
git shortlog groups commits by author and summarizes them — a quick way to see contribution counts across a repo or a date range. Pass --no-merges to exclude automated merge commits and get a cleaner picture of individual contributions.
git shortlog -sn # commit counts per author, sorted
git shortlog -sn --no-merges # exclude merge commits
Output:
# git shortlog -sn
47 Alice Dev
23 Alice Chen
18 Bob Martinez
5 dependabot[bot]
Show
git show displays the metadata and full diff of a single commit, or the content of a file as it existed at a given ref. Use it for a quick inspection of what any commit actually changed without switching branches.
git show HEAD # show latest commit details + diff
git show abc1234 # show specific commit
git show HEAD:src/app.py # show file as of a commit
Output:
# git show HEAD
commit 3f4a1bc8e2d1a9f5c7b04e6d38120f4a9c2b1e7d (HEAD -> main)
Author: Alice Dev <alice@example.com>
Date: Fri Apr 25 14:32:11 2026 +1000
fix: correct redirect URL after OAuth flow
diff --git a/src/auth.py b/src/auth.py
index 2a4b8c1..9f3d7e2 100644
--- a/src/auth.py
+++ b/src/auth.py
@@ -42,7 +42,7 @@ def handle_callback(request):
token = exchange_code(request.args.get("code"))
- return redirect("/dashboard")
+ return redirect("/home")
Common scenarios
Practical step-by-step workflows for situations that come up repeatedly.
Sync a fork with upstream
Keep your GitHub fork current with the original repo after it receives new commits.
git remote add upstream https://github.com/<original-owner>/<repo>.git
git fetch upstream
git switch main
git merge upstream/main # use rebase if you prefer a linear history
git push origin main # update your fork on GitHub
Output:
# git remote -v (after adding upstream)
origin git@github.com:alicedev/myrepo.git (fetch)
origin git@github.com:alicedev/myrepo.git (push)
upstream https://github.com/original-owner/myrepo.git (fetch)
upstream https://github.com/original-owner/myrepo.git (push)
Recover a deleted branch
Find the lost tip commit in the reflog and recreate the branch.
git reflog # scan for the last commit on the lost branch
git switch -c feature/login abc1234 # recreate branch at that commit SHA
Output:
# git reflog (excerpt)
3f4a1bc HEAD@{0}: checkout: moving from feature/login to main
abc1234 HEAD@{1}: commit: feat: add login page ← this is the tip to recover
Committed to the wrong branch
You made commits directly on main instead of a feature branch, and haven't pushed yet.
git switch -c feature/my-work # create feature branch preserving the commits
git switch main
git reset --hard origin/main # rewind main back to the remote state
Output: (none — exits 0 on success)
If only the last commit needs moving to an existing branch:
git switch feature/target
git cherry-pick main # bring the commit over
git switch main
git reset --hard HEAD~1 # drop it from main
Output: (none — exits 0 on success)
Squash commits before a PR
Clean up a messy local branch into a few logical commits before requesting review.
git rebase -i origin/main # squash everything since the branch diverged
# or, to squash just the last 4 commits:
git rebase -i HEAD~4
Output: (none — exits 0 on success)
In the editor, leave the first line as pick and change the rest to squash (or s). Git opens a second editor to write the combined commit message.
# interactive rebase editor
pick a1b2c3d feat: scaffold login page
squash d4e5f6a fix: correct form field IDs
squash 7g8h9i0 wip: tweak styles
squash 1j2k3l4 fix: lint errors
Clean up stale local branches
Remove remote-tracking refs for branches deleted on GitHub, then drop merged local branches.
git fetch --prune # remove stale remote-tracking refs
# preview which local branches are fully merged into main
git branch --merged main
# delete them all at once (skips main and the checked-out branch)
git branch --merged main | grep -v '^\*\|^\s*main$\|^\s*master$' | xargs git branch -d
Output:
# git branch --merged main
feature/login
fix/header-bug
chore/update-deps
* main
Migrate a repo to a new remote host
Move an existing repo — all branches, tags, and history — to a new host without re-cloning everything manually.
# 1. Create a bare mirror clone of the source
git clone --mirror https://github.com/<user>/<old-repo>.git
cd <old-repo>.git
# 2. Point origin at the new host (create the empty repo there first)
git remote set-url origin https://github.com/<user>/<new-repo>.git
# 3. Push everything
git push --mirror
Output: (none — exits 0 on success)
After the mirror push, clone fresh from the new URL for day-to-day work.
Revert a merge commit
Undo a merge on a shared branch without rewriting history (safe for branches others have pulled).
git log --oneline --merges -5 # find the merge commit SHA
git revert -m 1 <merge-commit-sha> # creates a new revert commit
Output: (none — exits 0 on success)
-m 1 selects the first parent, which is the branch you merged into (the mainline). The result is a new commit that reverses the merge diff with no history rewrite.
Output:
# git log --oneline after the revert
c7d8e9f Revert "Merge pull request #42 from feature/payments"
4a5b6c7 Merge pull request #42 from feature/payments ← still in history
...
Temporarily ignore changes to a tracked file
Prevent Git from reporting local modifications to a committed file (e.g. a local config override) without adding it to .gitignore.
git update-index --assume-unchanged config/local.yaml # start ignoring
git update-index --no-assume-unchanged config/local.yaml # stop ignoring
git ls-files -v | grep '^h' # list all assume-unchanged files
Output: (none — exits 0 on success)
Use
--skip-worktreeinstead of--assume-unchangedfor config overrides you intend to keep across rebases — it survivesgit stashand rebase better.
Bisect to find a regression
Binary-search the commit history to pinpoint which commit introduced a bug.
git bisect start
git bisect bad # current HEAD is broken
git bisect good v2.0.0 # last known-good tag or SHA
# Git checks out a midpoint commit; test it, then mark it:
git bisect good # or: git bisect bad
# Repeat until Git prints the first bad commit.
git bisect reset # return to HEAD when done
Output: (none — exits 0 on success)
Automate with a test script that exits 0 for good, non-zero for bad:
git bisect run ./scripts/smoke-test.sh
Output:
# git bisect run output (excerpt)
Bisecting: 3 revisions left to test after this (roughly 2 steps)
[abc1234] refactor: extract payment module
abc1234 is the first bad commit
commit abc1234
Author: Alice Dev <alice@example.com>
refactor: extract payment module
git push --force-with-leaseis always safer than--force. It verifies that the remote ref hasn't changed since your last fetch, preventing accidentally overwriting someone else's pushed commits.
[!WARN]
git reset --hard,git clean -fd, andgit checkout -- .permanently discard uncommitted changes. They are not recoverable once your working tree is clean. Usegit stashif there's any chance you want the changes back.
Rewriting history
Changing author identity, stripping commit message trailers, and removing sensitive files from existing commits. All of these rewrite SHAs — force-push afterward and warn any collaborators who have already pulled.
[!WARN] Every operation in this section rewrites history. Anyone else who has pulled the branch will need to
git pull --rebaseor re-clone after your force-push.
Change author name and email on every commit
Useful after commits were recorded with a wrong identity (hostname-derived address, wrong account, CI bot, etc.).
FILTER_BRANCH_SQUELCH_WARNING=1 git filter-branch -f --env-filter '
export GIT_AUTHOR_NAME="Alice Dev"
export GIT_AUTHOR_EMAIL="alice@example.com"
export GIT_COMMITTER_NAME="Alice Dev"
export GIT_COMMITTER_EMAIL="alice@example.com"
' -- --all
git push --force origin main
Output: (none — exits 0 on success)
Change author only for a specific old identity
Rewrite commits from one particular address and leave all others untouched — useful when a shared repo has mixed authors.
FILTER_BRANCH_SQUELCH_WARNING=1 git filter-branch -f --env-filter '
if [ "$GIT_AUTHOR_EMAIL" = "old@wrong-address.com" ]; then
export GIT_AUTHOR_NAME="Alice Dev"
export GIT_AUTHOR_EMAIL="alice@example.com"
fi
if [ "$GIT_COMMITTER_EMAIL" = "old@wrong-address.com" ]; then
export GIT_COMMITTER_NAME="Alice Dev"
export GIT_COMMITTER_EMAIL="alice@example.com"
fi
' -- --all
git push --force origin main
Output: (none — exits 0 on success)
Verify the rewrite
git log --format="%an <%ae>" | sort -u # unique author identities
git log --format="%cn <%ce>" | sort -u # unique committer identities
Output after a clean rewrite:
Alice Dev <alice@example.com>
Remove Co-Authored-By trailers from commit messages
Strips co-author lines added by AI tools, pair-programming workflows, or GitHub's squash merger.
FILTER_BRANCH_SQUELCH_WARNING=1 git filter-branch -f --msg-filter '
sed "/^Co-Authored-By:/d"
' -- --all
git push --force origin main
Output: (none — exits 0 on success)
Verify nothing remains:
git log --format="%B" | grep -i "co-authored" # should return nothing
Output: (none — exits 0 on success)
Remove a file or secret from all history
Scrub a committed credential, large binary, or accidentally tracked file from every commit.
FILTER_BRANCH_SQUELCH_WARNING=1 git filter-branch -f --index-filter \
'git rm --cached --ignore-unmatch path/to/secret.env' \
--prune-empty -- --all
# Also remove from the reflog so GC can collect it
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push --force origin main
Output: (none — exits 0 on success)
--prune-empty drops commits that become empty after the file is removed. After pushing, collaborators should re-clone — the file still exists in their local reflog until their own GC runs.
Rewrite with git-filter-repo (modern alternative)
git filter-branch is slow and has known edge cases. git-filter-repo is faster and safer when available.
pip install git-filter-repo # one-time install
# Fix all author/committer identities in one pass
git filter-repo --name-callback 'return b"Alice Dev"' \
--email-callback 'return b"alice@example.com"'
# Strip Co-Authored-By lines from every message
git filter-repo --message-callback \
'import re; return re.sub(rb"^Co-Authored-By:.*\n?", b"", message, flags=re.MULTILINE)'
# Remove a file from all history
git filter-repo --path path/to/secret.env --invert-paths
git remote add origin <url> # filter-repo removes the remote; re-add it
git push --force origin main
Output: (none — exits 0 on success)
git-filter-reporemoves theoriginremote as a safety measure to prevent accidental force-pushes. Re-add it withgit remote add origin <url>before pushing.
Partial clone and sparse-checkout
A partial clone (--filter=blob:none or --filter=tree:0) downloads commit and tree objects up-front but defers blob (file content) downloads until each file is actually accessed — turning multi-gigabyte clones of large monorepos into seconds. A sparse-checkout further narrows the working tree to a subset of paths, leaving the rest of the repo untouched on disk. Use both together for huge repos where you only need a fraction of the files.
# Blobless clone — fetch blobs on demand
git clone --filter=blob:none https://github.com/<user>/<huge-repo>.git
# Treeless clone — even smaller, defers trees too (best for short-lived clones)
git clone --filter=tree:0 https://github.com/<user>/<huge-repo>.git
# Backfill historical blobs in batches (git 2.49+) — pulls missing objects
# in path-order, faster than fetching them ad-hoc as you check out commits.
git backfill # download all missing blobs
git backfill --sparse # respect sparse-checkout paths
git backfill --min-batch-size=50000 # tune batch size
# Sparse-checkout (cone mode is the recommended default)
git sparse-checkout init --cone # enable cone-mode sparse-checkout
git sparse-checkout set src/ docs/ # only keep these directories
git sparse-checkout list # show current patterns
git sparse-checkout add internal/ # add another directory
git sparse-checkout reapply # re-evaluate after changing patterns
git sparse-checkout clean # remove stray files outside the cone (git 2.52+)
git sparse-checkout disable # restore full working tree
Output:
# git sparse-checkout list
src
docs
# git backfill (excerpt)
remote: Enumerating objects: 12834, done.
remote: Counting objects: 100% (12834/12834), done.
Receiving objects: 100% (12834/12834), 18.42 MiB | 6.10 MiB/s, done.
Reftable backend
Reftable is a binary reference storage format that replaces the loose-file + packed-refs layout with a single sorted, indexed file. It gives near-constant-time lookups regardless of how many refs you have, makes reference updates atomic, and dramatically speeds up git fetch and git push on repos with tens of thousands of branches or tags. Available since git 2.45; migration tooling shipped in git 2.46.
# Create a new repo using reftable
git init --ref-format=reftable my-repo
git clone --ref-format=reftable URL
# Convert an existing repo (requires no worktrees)
git refs migrate --ref-format=reftable
# Check which backend a repo uses
git rev-parse --show-ref-format
# Roll back to the files backend if needed
git refs migrate --ref-format=files
Output:
# git rev-parse --show-ref-format
reftable
Reftable is still considered new —
gh, IDE integrations, and older Git plumbing tools all need recent versions to read the new format. Stick with thefilesbackend on shared servers until your whole toolchain catches up.
What's new (git 2.45 – 2.52)
Git ships on a ~12-week cadence; the highlights below are the practical changes most likely to affect day-to-day work since git 2.45 (April 2024).
git 2.52 (Nov 2025)
git last-modified <path>— likegit log -1 -- <path>for every entry in a directory at once; shows the most-recent commit that touched each path.git repo— new plumbing-style command that reports repository characteristics (object count, ref format, partial-clone filter, …) in a script-friendly form.git sparse-checkout clean— removes files left outside the cone after switching sparse-checkout patterns.- Beginnings of SHA-1 ↔ SHA-256 interoperability for the upcoming git 3.0.
git 2.51 (Aug 2025)
git switchandgit restoreare now stable — no longer experimental, command-line interfaces are committed-to.git stash export --to-ref <ref>/git stash import <commit>— round-trip stashes through a normal ref so you can push, pull, or copy them between machines.- Cruft-aware multi-pack indexes and path-walk packing — smaller packs, faster fetches.
git 2.50 (Jun 2025)
- Multi-pack reachability bitmaps in incremental MIDX chains — each layer has its own
.bitmap, so huge monorepos can keep bitmaps current without a full rewrite. git diff-pairs— new plumbing command that takes raw filepair info on stdin to produce exact patches.git merge-tree --quiet— check whether two refs are mergeable from exit code alone, without writing objects.git maintenancenew tasks: clean stale worktrees, expirererereresolutions, tidy reflogs.- HTTP keepalive settings:
http.keepAliveIdle,http.keepAliveInterval,http.keepAliveCount.
git 2.49 (Mar 2025)
git backfill— download missing blobs in a partial clone in batches, ordered by path (uses the new path-walk API).--name-hash-version=2— accepted upstream as the production form of the previously-experimental--full-name-hash.- Initial Rust foreign-function interface lands in the build.
git 2.48 (Jan 2025)
- Git is now leak-free under the full test suite — last 60 leaky test files cleaned up.
git fsckdetects more reference corruption (bad ref content, symlink-as-symref, dangling symref targets).
git 2.47 (Oct 2024)
- Incremental multi-pack indexes — append new packs to the MIDX chain instead of rewriting the whole thing on every repack.
- Separate hash-function implementation for trailing checksums — GitHub measured a 10-13 % speed-up on fetch/clone serving.
git 2.46 (Jul 2024)
git refs migrate --ref-format=reftable— convert an existing repo's reference backend in place.- New
git refsplumbing command groups reference-related sub-commands.
git 2.45 (Apr 2024)
- Reftable backend introduced experimentally —
git init --ref-format=reftable, near-constant-time ref lookups, atomic ref updates. - Initial SHA-1 / SHA-256 interoperability hooks.
Sources
- Highlights from Git 2.52 — GitHub Blog
- Highlights from Git 2.51 — GitHub Blog
- Highlights from Git 2.50 — GitHub Blog
- Highlights from Git 2.49 — GitHub Blog
- Highlights from Git 2.48 — GitHub Blog
- Highlights from Git 2.47 — GitHub Blog
- Highlights from Git 2.46 — GitHub Blog
- Highlights from Git 2.45 — GitHub Blog
- What's new in Git 2.52.0 — GitLab Blog
- What's new in Git 2.50.0 — GitLab Blog
- What's new in Git 2.49.0 — GitLab Blog
- git-backfill(1) — git-scm.com
- partial-clone — git-scm.com
- reftable — git-scm.com
- BreakingChanges — git-scm.com