Skip to content

Advanced Git Commands

This document covers Git commands and features that are powerful but less frequently used in Day-to-day workflows. These tools solve specific problems around metadata, history manipulation, Repository integrity, and multi-tree management.

Power tools for Git experts: Advanced Git commands are like power tools in a workshop — you do not need them every day, but when you do, they save hours of manual work. Commands like git bisect, git stash, and git worktree solve specific problems that basic Git cannot handle efficiently.

Why it matters: These commands solve real-world problems — git bisect finds the exact commit that introduced a bug in minutes instead of hours, git stash lets you context-switch without losing work, and git worktree lets you work on multiple branches simultaneously.

The key insight: git reflog is your safety net — it records every movement of HEAD, so even if you accidentally reset or rebase, you can always recover your previous state.

git replace allows you to tell Git to use one object in place of another without rewriting the Object database. When Git encounters the original object, it transparently substitutes the Replacement. This is a non-destructive mechanism for altering how history appears.

Definition. A replacement ref is a reference stored under refs/replace/ that maps an original Object hash to a replacement object hash. Git resolves these references during object lookups, Presenting the replacement as if it were the original.

Terminal window
## Replace one object with another
git replace <original-object> <replacement-object>
## Replace using an edited version of the original
git replace --edit <object>
# Graft: make a commit appear to have a different parent
git replace --graft <commit> [<parent>...]
# List all replacements
git replace -l
# Delete a specific replacement
git replace -d <object>
# Delete all replacements
git replace -d $(git replace -l)

When you run git replace A BGit creates a ref at refs/replace/A pointing to B. During any Object lookup, Git checks whether the requested object has an entry under refs/replace/. If it Does, Git returns the replacement instead.

refs/replace/
a3f2b1c0... -> d4e5f6a7... (commit replacement)
b7c8d9e0... -> e1f2a3b4... (blob replacement)

This means:

  • The original object still exists in the object store, unchanged.
  • The replacement object must already exist in the object store.
  • The replacement is local by default; it is not pushed unless you explicitly push refs/replace/.
Terminal window
# List all replacement refs
$ git replace -l
# Show what an object is replaced with
$ git cat-file -p $(git replace -l | head -1)
# Show a replaced commit as it appears after replacement
$ git log --no-replace-objects -1 <original-commit>
# vs.
$ git log -1 <original-commit>

The --no-replace-objects flag disables replacement resolution, letting you see the raw original Object.

Grafting re-parents a commit, making it appear as if it has different parents. This is useful for Stitching together unrelated histories.

Terminal window
# Make commit C appear as if root-commit is its parent (joining two histories)
git replace --graft <commit-C> <root-commit>
# Make a commit appear as a root commit (no parents)
git replace --graft <commit>
# Make a commit appear to have two parents (octopus merge)
git replace --graft <commit> <parent1> <parent2>
### Basic Usage
Terminal window
# Send the last commit as a patch
$ git format-patch -1 --stdout | git send-email --to maintainer@example.com \
--cc reviewer@example.com
# Send patches from files
$ git format-patch -3
$ git send-email --to maintainer@example.com 0001-*.patch 0002-*.patch 0003-*.patch
# Send all patches in a directory
$ git send-email --to maintainer@example.com --to-list patches/
# Send with a cover letter
$ git format-patch --cover-letter -5
$ git send-email --to maintainer@example.com 0000-*.patch 0001-*.patch
# Dry run (print what would be sent without sending)
$ git send-email --to maintainer@example.com --dry-run 0001-*.patch
# Send with In-Reply-To (for threading)
$ git send-email --to maintainer@example.com --in-reply-to="<message-id@example.com>" 0001-*.patch
Terminal window
# Add Cc recipients
$ git send-email --cc reviewer1@example.com --cc reviewer2@example.com 0001-*.patch
# Cc everyone who authored the patches
$ git send-email --cc-cmd "./scripts/get-maintainer.pl" 0001-*.patch
# Set the In-Reply-To header for proper threading
$ git send-email --in-reply-to="<20260407103000.12345@example.com>" 0001-*.patch
# Suppress Cc from the patch body (Signed-off-by, etc.)
$ git send-email --suppress-cc=cc 0001-*.patch
# Suppress Cc from body, sob, and misc-cmds
$ git send-email --suppress-cc=body,sob,misc-cmd 0001-*.patch
FlagSuppresses Cc from
--suppress-cc=authorPatch author
--suppress-cc=sobSigned-off-by trailers
--suppress-cc=ccCc lines in patch body
--suppress-cc=bodyccCc, Acked-by, Reviewed-by trailers
--suppress-cc=bodyEntire patch body
--suppress-cc=misc-cmdOutput of —cc-cmd
--suppress-cc=allAll of the above
Terminal window
# 1. Create patches
$ git format-patch --cover-letter -5 -o outgoing/
# 2. Review the patches
$ ls outgoing/
0000-cover-letter.patch
0001-fix-memory-leak-in-parser.patch
0002-add-bounds-checking.patch
0003-refactor-tokenizer.patch
0004-update-tests.patch
0005-update-documentation.patch
# 3. Edit the cover letter
$ vim outgoing/0000-cover-letter.patch
# 4. Dry run
$ git send-email --to maintainer@project.org \
--cc list@project.org \
--dry-run outgoing/*.patch
# 5. Send
$ git send-email --to maintainer@project.org \
--cc list@project.org \
outgoing/*.patch
Config KeyDescription
sendemail.fromDefault From address
sendemail.toDefault To address
sendemail.smtpServerSMTP server hostname
sendemail.smtpServerPortSMTP server port
sendemail.smtpEncryptionssl``tlsOr none
sendemail.smtpUserSMTP username
sendemail.smtpPassSMTP password (or use credential helper)
sendemail.chainReplyToChain emails as replies to cover letter
sendemail.threadEnable threading
sendemail.confirmauto``always``never``cc``compose