Quick-reference cheat-sheet. Validated against git 2.54.0.
Modern commands (switch, restore) are preferred over the older checkout
for branch/file operations — since git 2.23 they split checkout's two jobs
apart, and git 2.54 updated its own advice messages to point at them.
- https://docs.freebsd.org/en/articles/committers-guide/#git-primer
- https://blog.gitbutler.com/how-git-core-devs-configure-git/
# Get a repo
git clone <url>
git remote add upstream <url>
# Inspect
git status
git log
git diff
git reflog
# Stage + commit
git add <path> # or git add .
git commit -m "msg"
git commit -am "msg" # stage tracked + commit
git commit --amend # fix last commit
# Sync
git fetch <remote>
git pull --ff-only
git push
git push -u origin <branch>
# Branches
git switch <branch>
git switch -c <branch> # create + switch
git branch -D <branch>
# Rewrite / integrate
git rebase <base>
git rebase -i HEAD~<N>
git cherry-pick <hash>
# Undo / rescue
git restore <path> # discard unstaged changes to file
git restore --staged <path> # unstage
git reset --hard <ref>
git stash / git stash pop / git stash list
# Debug
git bisect
Terminology:
- origin = your fork (or the repo you cloned)
- upstream = the canonical repo you track
- main = the primary branch (was
master)
merge — record upstream changes into your fork as a merge commit. Adds a noisy commit; prefer rebase for personal branches.
rebase — reapply your commits on top of an updated base. Linear history.
git remote add upstream <url>
git fetch upstream
git switch main
git rebase upstream/main
git switch working
git rebase main
One canonical recipe — works for any fork (GitHub PR flow, FreeBSD ports, BSDRP, etc.):
git switch main
git pull --rebase upstream main # fetch + rebase in one step
git push origin main # update your fork's main
git switch <working-branch>
git rebase main
git push --force-with-lease # safer than -f: refuses if remote moved
Use --force-with-lease instead of -f — it aborts the push if someone
else pushed in the meantime.
git diff # unstaged only
git diff --staged # staged only
git diff HEAD # staged + unstaged <- use this one
git diff HEAD^ HEAD # last commit
git diff <hash>^! # a specific commit
git diff origin/main origin/feat # between two branches
git diff alone shows only tracked, unstaged changes. It silently
omits staged changes and never shows new files, so it can under-report
what your patch really is. Two commands, always:
git diff HEAD # everything tracked, staged or not
git status --short # catch ?? files that belong in the patch
Reading git status --short — two columns, staged then unstaged:
M path # modified, nothing staged (git diff shows it)
M path # modified and staged (git diff shows NOTHING)
MM path # staged, then modified again
?? path # untracked: git diff never shows this
So a repo with scratch files in its root can look clean to git diff
while arc diff or git format-patch pick up something unintended.
Keep junk out of the tree, or list it in .git/info/exclude
(local-only, unlike .gitignore).
Save a reviewable patch and prove it applies to a pristine tree:
git diff HEAD > /tmp/fix.diff
git stash && git apply --check /tmp/fix.diff && git stash pop
Create:
git format-patch HEAD^! # last commit
git format-patch <hash>^! # specific commit
git format-patch -N # last N commits
Apply (preserves author + date):
git am file.patch
git commit --amend # only if you need to edit the message
git log --oneline -N # confirm what you're squashing
git rebase -i HEAD~N
In the editor, keep pick on the first line, change the rest to s
(squash). Save; edit the combined message; save.
git push --force-with-lease origin <branch>
Discard unstaged changes in one file:
git restore <path>
Discard all unstaged changes:
git restore .
Unstage a file (keep the edits):
git restore --staged <path>
Undo the last local commit, keep changes staged:
git reset --soft HEAD~1
Undo the last local commit, discard changes:
git reset --hard HEAD~1
Reset a local branch to match its upstream (@{u} = upstream shorthand):
git reset --hard @{u}
Revert a bad rebase (still known via reflog):
git reflog
git reset --hard HEAD@{N}
Force-overwrite a pushed working branch (never on main):
git reset --hard <good-hash>
git push --force-with-lease
Create a branch from a tag:
git switch -c newbranch tags/v1.0
Copy one file from another branch into the current one:
git restore --source=<other-branch> path/to/file
Delete a branch, locally and remotely:
git branch -d <branch> # -D to force
git push origin -d <branch>
Tag a release:
git tag -a v1.992 -m "Release v1.992"
git push origin v1.992
git remote add someone <their-url>
git fetch someone
git switch <target-branch>
git cherry-pick <hash>
git push
When was a branch created off main?
git show --summary "$(git merge-base <branch> main)"
When was a file deleted?
git log --all -1 -- path/to/file
Which patchlevel is /usr/src at?
git -C /usr/src rev-list --count --first-parent HEAD
git bundle create repo.bundle --all
git clone repo.bundle <newrepo>
Git records two identities per commit:
- Author — who wrote the change.
- Committer — who applied it. Always you, taken from
user.name/user.email.
git log prints only the author. Use git log --format=fuller to see both.
git apply patch.diff # or: patch -p1 < patch.diff
git add -A
git commit --author="Jane Doe <jane@example.org>" -m "msg"
If the author is already in the repo history, a search term is enough — git resolves it against existing commits:
git commit --author="jane" -m "msg"
Prefer git am: it takes the author, date, and commit message from the mail
headers, so no --author flag is needed.
git am patch.mbox
git am --3way patch.mbox # fall back to 3-way merge if it does not apply
git commit --amend --author="Jane Doe <jane@example.org>" --no-edit
--author sets the name and email only. The author date stays "now". To take
the original date as well:
git commit --author="Jane Doe <jane@example.org>" \
--date="2026-09-14T10:32:00+02:00" -m "msg"
For a shared change, keep yourself as author and add a trailer. GitHub and GitLab render it as a second contributor.
git commit -m "msg
Co-authored-by: Jane Doe <jane@example.org>"
Real example — forking both FreeBSD ports and src. Fork each via the GitHub web UI first, then:
# freebsd-ports
git clone git@github.com:<you>/freebsd-ports.git
cd freebsd-ports
git remote add upstream git@github.com:freebsd/freebsd-ports
cd ..
# freebsd-src
git clone git@github.com:<you>/freebsd-src.git
cd freebsd-src
git remote add upstream git@github.com:freebsd/freebsd-src
After this, each clone has two remotes:
origin→ your GitHub fork (wheregit pushlands)upstream→ the official FreeBSD repo (fetch-only in practice)
Keep either fork current — see section 3.