Verified14 commandsAI-assisted

Git

.md

Verified against git 2.43.0, flags verified via `git <cmd> -h` and tested against a scratch repo in /tmp (worktree, bisect, sparse-checkout, blame, rebase all exercised live; submodule syntax confirmed via `git submodule -h` — the sandboxed environment blocks the `file://` transport needed to fully exercise `submodule add`), 2026-08-21 · official docs

The daily-driver subset of Git — branching, rebasing, stashing, filtering history, diffing, and recovering from mistakes with the reflog. Not the full manpage; just the commands that actually come up while working.

Branching#

Create, switch, rename, and clean up branches.

git branch                          # list local branches
git branch -a                       # list local + remote-tracking branches
git branch --show-current           # print the current branch name
git switch -c feature/new-thing     # create and switch to a new branch
git switch main                     # switch to an existing branch
git branch -m old-name new-name     # rename a branch
git branch -d feature/done          # delete a branch (only if merged)
git branch -D feature/abandoned     # force-delete a branch (even if not merged)

git switch is the modern, safer replacement for git checkout <branch> — it only touches branches, so it can't accidentally discard file changes the way checkout can. Prefer it for branch operations.

Stashing changes#

Shelve work-in-progress without committing it.

git stash                           # stash tracked changes
git stash push -u -m "wip: auth"    # stash including untracked files, with a message
git stash list                      # show all stashes
git stash show -p stash@{0}         # view a stash's diff
git stash pop                       # apply the most recent stash and drop it
git stash apply stash@{1}           # apply a specific stash without dropping it
git stash drop stash@{1}            # delete a specific stash
git stash branch new-branch stash@{0}  # create a branch from a stash (useful after a conflict)

stash pop fails and leaves the stash in place if applying it produces a conflict — resolve the conflict, then git stash drop manually.

Rebasing#

Rewrite a branch's history onto a new base, or clean it up before merging.

git rebase main                     # replay current branch's commits onto main
git rebase -i HEAD~5                # interactively squash/reorder/reword the last 5 commits
git rebase --onto main old-base feature   # move a branch to a different base commit
git rebase --continue               # after resolving a conflict mid-rebase
git rebase --skip                   # skip the commit currently causing a conflict
git rebase --abort                  # bail out and restore the branch to its pre-rebase state

Never rebase a branch other people have already pulled — it rewrites commit hashes, so anyone with the old history will get diverged/duplicate commits on their next pull. Rebase local/unshared branches only.

Viewing history#

Filter and format commit history for what you're actually looking for.

git log --oneline --graph --decorate    # compact visual history
git log --author="jane"                 # commits by a specific author
git log --since="2 weeks ago" --until="yesterday"   # commits in a date range
git log --grep="fix"                    # commits whose message matches a pattern
git log -- path/to/file.py              # history of a single file
git log -p -2                           # full diff for the last 2 commits
git log --stat -1                       # files changed + line counts for the last commit

git log -S"someFunction" (the "pickaxe") finds commits that changed the number of times a string appears — useful for finding when a specific line of code was introduced or removed, which plain --grep (message text only) can't do.

Diffing#

git diff                            # unstaged changes vs the index
git diff --staged                   # staged changes vs HEAD
git diff main..feature              # diff between two branches
git diff HEAD~3 HEAD                # diff between two points in history
git diff --stat                     # summary (files + line counts) instead of full diff
git diff -- path/to/file.py         # diff limited to one file

Cherry-picking#

git cherry-pick <commit-sha>        # apply a single commit onto the current branch
git cherry-pick -n <commit-sha>     # apply the changes but don't auto-commit
git cherry-pick --continue          # after resolving a conflict mid-cherry-pick
git cherry-pick --abort             # bail out of an in-progress cherry-pick

Undoing changes and recovering with the reflog#

git restore --staged path/to/file.py    # unstage a file (keep the changes)
git restore path/to/file.py             # discard unstaged changes to a file
git reset --soft HEAD~1                 # undo the last commit, keep changes staged
git reset --mixed HEAD~1                # undo the last commit, keep changes unstaged (default mode)
git reset --hard HEAD~1                 # undo the last commit and discard the changes entirely
git revert <commit-sha>                 # create a new commit that undoes a prior commit (safe for shared history)
git reflog                              # show a log of everywhere HEAD has pointed, including "lost" commits
git reset --hard HEAD@{2}               # recover to a state from the reflog (e.g. before a bad reset/rebase)

reset --hard is destructive to your working tree, but it is not destructive to the repository — every commit it seems to throw away still exists and is recoverable via git reflog until git's garbage collector eventually prunes unreferenced commits (default ~90 days for reflog entries). If you ever do a reset --hard or a rebase you regret, git reflog is the first thing to check, not a last resort.

For a branch already pushed and pulled by others, use git push --force-with-lease instead of git push --force after a rebase — it aborts the push if the remote has commits you haven't seen yet, preventing you from silently clobbering someone else's work.

Working in multiple branches at once with worktrees#

git worktree add ../repo-hotfix -b hotfix/urgent   # new branch, checked out in a sibling directory
git worktree add ../repo-review existing-branch    # check out an existing branch into a new worktree
git worktree list                                  # show every worktree linked to this repo
git worktree remove ../repo-hotfix                 # remove a worktree (must be clean, or add -f)
git worktree prune                                 # clean up admin files for worktrees deleted by hand

A worktree lets you have two branches checked out simultaneously from the same repository — e.g. keep main building in one directory while you work on a feature in another — without the stash/switch/stash pop dance. All worktrees share the same .git object store, so commits, branches, and tags are visible across all of them immediately.

Finding the commit that introduced a bug with bisect#

git bisect start                    # begin a bisect session
git bisect bad HEAD                 # mark the current commit as broken
git bisect good v1.4.0              # mark a known-good commit/tag
# git checks out a commit halfway between good and bad — test it, then:
git bisect good                     # this commit is fine, keep searching later commits
git bisect bad                      # this commit is broken, keep searching earlier commits
git bisect reset                    # done — return to the branch/commit you started from

git bisect does a binary search across the commit range, so a range of ~1000 commits takes about 10 steps, not 1000. For a bug with an automatable repro (a failing test, a script that exits non-zero), skip the manual good/bad loop entirely with git bisect run ./test-script.sh — it drives the whole search for you and stops on the first bad commit.

Working with submodules#

git submodule add https://github.com/org/lib.git vendor/lib   # add a submodule at a path
git submodule status                                            # show each submodule's checked-out commit
git clone --recurse-submodules <repo-url>                       # clone a repo and its submodules together
git submodule update --init --recursive                         # populate submodules after a plain clone
git submodule update --remote vendor/lib                        # pull the submodule's latest tracked-branch commit
git submodule foreach 'git status'                               # run a command inside every submodule

A submodule pins the parent repo to one exact commit of the child repo, not a branch — git submodule update alone checks that commit back out even if the child repo has moved on. --remote is what actually advances the pin to the latest upstream commit; you still need to git add and commit the resulting pointer change in the parent repo afterward.

Interactive rebase in depth#

git rebase -i HEAD~5                # open the last 5 commits in an editor as a todo list
git rebase -i --autosquash HEAD~5   # auto-reorder fixup!/squash! commits next to their targets
git commit --fixup <commit-sha>     # create a fixup commit, paired with --autosquash above
git rebase --exec "make test" -i HEAD~5   # run a command after each commit as it's replayed
GIT_SEQUENCE_EDITOR=true git rebase -i HEAD~5   # non-interactively accept the default todo (scripting)

The interactive todo list supports more than pick/squash/reword/drop: edit pauses on that commit so you can amend it or split it into several with git reset HEAD^ + re-committing, and exec runs an arbitrary shell command between commits (useful for making sure every intermediate commit still builds). --autosquash combined with git commit --fixup is the standard workflow for "amend an earlier commit" without hand-editing the todo list — create the fixup commit, then let autosquash reorder and squash it into place.

Blaming a file to find who/when changed a line#

git blame path/to/file.py                    # annotate every line with its introducing commit
git blame -L 40,60 path/to/file.py            # limit to a line range
git blame -L :funcName path/to/file.py        # limit to a specific function's lines
git blame -w path/to/file.py                  # ignore whitespace-only changes when attributing lines
git blame --ignore-rev <commit-sha> path/to/file.py   # skip a noisy commit (e.g. a mass reformat)

A large reformat or auto-fix commit ruins blame's usefulness for every line it touches. Fix this permanently by adding the reformat commit's SHA to a .git-blame-ignore-revs file and configuring git config blame.ignoreRevsFile .git-blame-ignore-revsblame then skips straight past it to the real authorial commit, and GitHub/GitLab respect the same file in their web blame views.

Checking out only part of a large repo with sparse-checkout#

git clone --filter=blob:none --sparse <repo-url>   # clone without downloading file contents yet
cd <repo> && git sparse-checkout init --cone         # enable cone mode (fast, directory-based)
git sparse-checkout set services/api services/web    # only these directories are checked out to disk
git sparse-checkout add services/shared              # add another directory to the working set
git sparse-checkout disable                          # go back to a full checkout

Cone mode (the default since Git 2.25+) restricts sparse-checkout to whole directories, which is both faster and far less error-prone than the old pattern-based mode — use it unless you have a specific need for file-level glob patterns. Pairing --filter=blob:none on the clone with sparse-checkout is what actually saves bandwidth and disk: the filter skips downloading file contents outside your sparse set, not just skipping them from the working tree.

Hooks basics#

ls .git/hooks/                       # every hook Git supports ships here as a *.sample file
mv .git/hooks/pre-commit.sample .git/hooks/pre-commit && chmod +x .git/hooks/pre-commit
git config core.hooksPath .githooks  # point Git at a repo-tracked hooks directory instead
git commit --no-verify                # skip commit-time hooks (pre-commit, commit-msg) for one commit

Hooks in .git/hooks/ are local-only and never cloned with the repo — every teammate has to install them by hand, which is why real projects instead commit a hooks directory (e.g. .githooks/) and point Git at it with core.hooksPath, or use a wrapper tool like pre-commit or husky that manages installation. The most commonly used hooks are pre-commit (runs before a commit is created — linting, formatting checks), commit-msg (validates the message itself — e.g. enforcing Conventional Commits), and pre-push (runs before git push — a last gate, like running the test suite).