Git Tips & Tricks

Interactive rebase, reflog recovery, useful aliases, and branch cleanup tricks.

Undo & Recovery

Browse the reflog to find a lost commit or branch tip. Every HEAD movement is recorded here for roughly 90 days.

git reflog

Undo the last commit but keep all changes staged, ready to re-commit with a different message or after edits.

git reset --soft HEAD~1

Discard unstaged changes in a single file. Use git restore (Git 2.23+) as the modern replacement.

# legacy approach
git checkout -- path/to/file.txt

# modern equivalent
git restore path/to/file.txt

# unstage a file (keep working-tree changes)
git restore --staged path/to/file.txt

Create a new commit that reverses a specific commit. Safe for shared branches because it doesn't rewrite history.

git revert abc1234

Recover a deleted branch using reflog. Find the SHA, then recreate the branch.

git reflog | grep checkout
git branch recovered-branch abc1234

Rebase

Interactive rebase lets you reorder, squash, edit, or drop commits. This rewrites history, so only use it on local/unpushed branches.

# rebase last 4 commits interactively
git rebase -i HEAD~4

Inside the interactive editor, change the action word before each commit:

pick   abc1234 Add login form          # keep as-is
reword def5678 fix tpyo               # edit commit message
squash 111aaaa Add validation          # meld into previous commit, combine messages
fixup  222bbbb Lint fix                # meld into previous, discard this message
drop   333cccc Debug logging           # remove commit entirely

Rebase your feature branch onto the latest main to get a linear history before merging.

git checkout feature-branch
git rebase main

# if conflicts arise, resolve them then:
git add .
git rebase --continue

# to abort a rebase in progress:
git rebase --abort

Transplant a branch onto a different base with --onto. Useful when a feature was started from the wrong branch.

# move commits that are on feature but not on old-base, onto new-base
git rebase --onto new-base old-base feature

Stash

Stash working changes with a descriptive message so you can switch branches quickly.

git stash push -m "WIP: sidebar redesign"

List, inspect, and apply stashes. pop applies and removes; apply keeps the stash entry.

git stash list
git stash show -p stash@{0}
git stash pop              # apply most recent + drop it
git stash apply stash@{2}  # apply specific stash, keep it
git stash drop stash@{1}   # delete a stash entry

Stash only selected hunks interactively with -p. Git prompts you for each change.

git stash push -p -m "partial: only auth changes"

Include untracked files in the stash, or stash everything including ignored files.

# include untracked files
git stash push -u -m "with untracked"

# include untracked AND ignored files
git stash push -a -m "everything"

Aliases

Add these to your ~/.gitconfig under [alias] to speed up everyday workflows.

[alias]
    s  = status -sb
    co = checkout
    br = branch
    ci = commit
    d  = diff --stat
    dc = diff --cached

A compact, coloured log that shows the branch graph at a glance.

[alias]
    lg = log --oneline --graph --decorate --all
    ll = log --pretty=format:'%C(yellow)%h%Creset %s %C(cyan)(%cr)%Creset %C(green)<%an>%Creset' --abbrev-commit -20

Handy shortcuts for amending and unstaging.

[alias]
    amend   = commit --amend --no-edit
    unstage = restore --staged
    last    = log -1 HEAD --stat
    undo    = reset --soft HEAD~1

Set aliases from the command line instead of editing the config file directly.

git config --global alias.s "status -sb"
git config --global alias.lg "log --oneline --graph --decorate --all"

Branch Cleanup

List local branches already merged into main. Safe to delete since their work is in the main line.

git branch --merged main

Delete merged local branches in one pass (excludes main/master/develop).

git branch --merged main | grep -vE '(main|master|develop)' | xargs git branch -d

Prune remote-tracking references that no longer exist on the server.

git fetch --prune

# or prune without fetching new data
git remote prune origin

Delete a branch both locally and on the remote.

# delete local branch (safe — refuses if unmerged)
git branch -d feature/old-thing

# force-delete local branch
git branch -D feature/old-thing

# delete remote branch
git push origin --delete feature/old-thing

Find stale branches: remote branches with no commits in the last 3 months.

git for-each-ref --sort=committerdate --format='%(committerdate:short) %(refname:short)' refs/remotes/origin | head -20

Bisect

Binary-search through commits to find the one that introduced a bug. Git picks the midpoint; you mark each commit good or bad.

git bisect start
git bisect bad              # current commit is broken
git bisect good v1.2.0      # this tag/commit was working

Git checks out a commit. Test it, then tell Git the result. Repeat until the culprit is found.

# after testing the checked-out commit:
git bisect good   # this commit is fine
# or
git bisect bad    # this commit has the bug

Automate bisect with a test script. The script should exit 0 for good, 1-124/126-127 for bad, 125 to skip.

git bisect start HEAD v1.2.0
git bisect run npm test

When bisect is done, it prints the first bad commit. Reset to return to your original HEAD.

git bisect reset

Use bisect with a custom script for more complex checks.

# test.sh — exits 0 if the build output is under 500KB
#!/bin/bash
npm run build
size=$(stat -f%z dist/bundle.js 2>/dev/null || stat -c%s dist/bundle.js)
[ "$size" -lt 512000 ]