Git

Advanced

Git · 9 entries

Working with Multiple Worktrees

syntax
git worktree add <path> <branch>
git worktree list
git worktree remove <path>
example
# Check out main in a separate directory:
git worktree add ../project-main main

# Now you can work on both branches simultaneously
# List all worktrees:
git worktree list
output
/home/user/project         a1b2c3d [feature/payments]
/home/user/project-main    f4e5d6c [main]

Note Worktrees let you have multiple branches checked out at once in different directories, sharing the same .git database. Huge time-saver when you need to quickly fix a bug on main without stashing your feature work.

Submodules

syntax
git submodule add <url> <path>
git submodule update --init --recursive
example
# Add a submodule:
git submodule add https://github.com/lib/shared-utils.git libs/shared

# After cloning a repo with submodules:
git submodule update --init --recursive

# Update submodule to latest:
cd libs/shared && git pull origin main
cd ../.. && git add libs/shared && git commit -m "Update shared-utils submodule"

Note Submodules are notoriously tricky. Each submodule is pinned to a specific commit. Collaborators must run git submodule update --init after cloning or they'll have empty directories. Consider alternatives like package managers when possible.

Sparse Checkout (Partial Clone)

syntax
git sparse-checkout init --cone
git sparse-checkout set <dir1> <dir2>
example
git clone --filter=blob:none --sparse https://github.com/large/monorepo.git
cd monorepo
git sparse-checkout set services/auth packages/shared

Note Only checks out specified directories from a large repo. Extremely useful for monorepos where you only need a small portion. --filter=blob:none avoids downloading file content you don't need.

Rewrite Repository History (git-filter-repo)

syntax
git filter-repo --path <path-to-keep>
git filter-repo --invert-paths --path <path-to-remove>
example
# Remove a file from entire history (e.g., accidentally committed secret):
git filter-repo --invert-paths --path secrets.env

# Extract a subdirectory into its own repo:
git filter-repo --path src/auth/

Note WARNING: This rewrites ALL commit hashes. Every collaborator must re-clone. git-filter-repo is a separate tool (pip install git-filter-repo) that replaces the deprecated git filter-branch. Back up your repo before using this.

Git Hooks

syntax
# Hooks live in .git/hooks/
# Common hooks: pre-commit, pre-push, commit-msg, post-merge
example
# Create a pre-commit hook that runs linting:
# File: .git/hooks/pre-commit
#!/bin/sh
npm run lint
if [ $? -ne 0 ]; then
  echo "Linting failed. Commit aborted."
  exit 1
fi

# Make it executable:
chmod +x .git/hooks/pre-commit

Note Hooks in .git/hooks/ are local and not committed to the repo. Use tools like Husky (Node) or pre-commit (Python) to share hooks via the repo. The hook must be executable and must exit 0 to allow the action to proceed.

Sign Commits with GPG

syntax
git config --global commit.gpgsign true
git config --global user.signingkey <key-id>
git commit -S -m "message"
example
# Find your GPG key:
gpg --list-secret-keys --keyid-format=long

# Configure Git to sign:
git config --global user.signingkey ABC123DEF456
git config --global commit.gpgsign true

# Verify a signed commit:
git log --show-signature -1
output
gpg: Signature made Mon 30 Mar 14:22:01 2026
gpg: Good signature from "Samira Khan <samira@example.com>"

Note Signing proves you actually authored a commit. GitHub shows a 'Verified' badge for signed commits. You can also use SSH keys for signing (Git 2.34+): git config --global gpg.format ssh

Reuse Recorded Resolution (rerere)

syntax
git config --global rerere.enabled true
example
git config --global rerere.enabled true

# Now when you resolve a merge conflict, Git remembers.
# Next time the same conflict appears, Git resolves it automatically.
output
Recorded resolution for 'src/config.js'.
...
Resolved 'src/config.js' using previous resolution.

Note rerere = 'reuse recorded resolution'. Incredibly useful when you repeatedly rebase a long-lived branch and keep hitting the same conflict. Enable it globally — there's no downside.

Ignore Bulk Formatting Commits in Blame

syntax
git blame --ignore-revs-file .git-blame-ignore-revs <file>
example
# Create .git-blame-ignore-revs in the repo root:
# Content: one commit hash per line
# a1b2c3d4e5f6... (bulk prettier formatting)

git config blame.ignoreRevsFile .git-blame-ignore-revs
git blame src/app.js

Note After running a bulk formatting tool (Prettier, Black, etc.), every line shows you as the author in blame. This file tells Git to skip those commits, preserving meaningful blame history. GitHub also respects this file automatically.

Create a Repository Bundle

syntax
git bundle create <file> <refs>
git clone <bundle-file> <dir>
example
# Bundle the entire repo:
git bundle create repo.bundle --all

# Bundle just the last 10 commits on main:
git bundle create recent.bundle -10 main

# Clone from a bundle (offline transfer):
git clone repo.bundle my-project

Note Bundles are single-file snapshots of a repo that can be transferred via USB, email, etc. Useful for air-gapped environments or when network access is limited.