Skip to main content

Git Advanced Topics

Master sophisticated Git workflows, internals, and strategies used by professional development teams.

Interactive Rebase​

Interactive rebase is the most powerful Git tool for manipulating history. Use it before submitting pull requests to create clean, logical commits.

Starting Interactive Rebase​

git rebase -i main                 # Rebase and edit commits on main
git rebase -i HEAD~5 # Edit last 5 commits
git rebase -i --root # Edit all commits in repository

Interactive Commands​

When the editor opens, you'll see:

pick a1b2c3d First commit
pick b3c4d5e Second commit
pick c5d6e7f Third commit

# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like squash, but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop = remove commit
# l, label = label current HEAD with a name
# t, reset = reset HEAD to a label
# m, merge = create a merge commit using the original merge message

Common Rebase Scenarios​

Squashing commits:

pick a1b2c3d First feature commit
squash b3c4d5e Bug fix in feature
squash c5d6e7f Another improvement

Result: All three commits merged into one.

Reordering commits:

pick c5d6e7f Third commit
pick a1b2c3d First commit
pick b3c4d5e Second commit

Result: Commits reordered (useful for logical grouping).

Editing commit messages:

pick a1b2c3d First commit
reword b3c4d5e Fix typo in message
pick c5d6e7f Third commit

Editor opens for second commit message only.

Splitting commits:

git rebase -i HEAD~1
# Mark commit with 'edit'
# When rebasing stops at that commit:
git reset HEAD~1 # Undo the commit
git add <partial-files> # Stage part of changes
git commit -m "First part"
git add <remaining-files> # Stage remainder
git commit -m "Second part"
git rebase --continue

Autosquash​

Mark commits with fixup/squash prefix, then use --autosquash:

git commit -m "fixup! Previous commit message"
git commit -m "squash! Another commit message"
git rebase -i --autosquash main
# Automatically moves and marks commits

Cherry-Pick​

Apply specific commits from one branch to another without merging.

Basic Cherry-Pick​

git cherry-pick a1b2c3d            # Apply single commit
git cherry-pick a1b2c3d b3c4d5e # Apply multiple commits
git cherry-pick a1b2c3d..c5d6e7f # Apply range (exclusive of start)
git cherry-pick a1b2c3d^..c5d6e7f # Apply range (inclusive of start)

Cherry-Pick with Modifications​

git cherry-pick -x <commit>        # Add cherry-pick reference
git cherry-pick --no-commit <commit> # Stage changes without committing
git cherry-pick -e <commit> # Edit commit message
git cherry-pick --signoff <commit> # Add Signed-off-by line

Handling Cherry-Pick Conflicts​

git cherry-pick <commit>
# Conflicts occur...
git status # See conflicted files
# Manually resolve conflicts
git add <resolved-files>
git cherry-pick --continue # Complete cherry-pick
git cherry-pick --abort # Cancel cherry-pick

Cherry-Pick Use Cases​

  • Backporting: Apply bug fix to release branch
  • Selective merging: Cherry-pick features without full branch merge
  • Maintenance: Apply patches to multiple branches

Bisect: Finding Bugs​

Binary search through commits to find where a bug was introduced.

Basic Bisect Workflow​

git bisect start                   # Start bisect session
git bisect bad # Current commit is bad (has bug)
git bisect good v1.0 # Known good commit
# Git checks out middle commit
# Test the commit...
git bisect good # Or git bisect bad
# Continue until bug is found
git bisect reset # End bisect, return to original branch

Automated Bisect​

git bisect start
git bisect bad HEAD
git bisect good v1.0
git bisect run npm test # Automatically run test script
# Git bisect runs test on each commit, reports when bug introduced
git bisect reset

Bisect Example​

#!/bin/bash
# test-script.sh - Returns 0 if good, 1 if bad
npm run build || exit 1
npm test | grep "FAILED" && exit 1
exit 0
git bisect start
git bisect bad HEAD
git bisect good v1.0
git bisect run ./test-script.sh

Submodules​

Include external repositories as subdirectories.

Adding Submodules​

git submodule add <url> <path>     # Add submodule
git submodule add -b main <url> <path> # Add specific branch
git add .gitmodules <path>
git commit -m "Add: submodule"

This creates:

  • .gitmodules file tracking submodule info
  • Entry in .git/config
  • Submodule directory with separate .git file

Cloning with Submodules​

git clone --recurse-submodules <url>  # Clone with submodules
git clone <url> # Clone without submodules
git submodule update --init # Initialize submodules after clone
git submodule update --init --recursive # Initialize recursive submodules

Updating Submodules​

git submodule update                # Update to commit in .gitmodules
git submodule update --remote # Update to latest from remote
git submodule update --remote --merge # Update and merge
git submodule foreach git pull # Pull all submodules

Managing Submodules​

git submodule status               # Show submodule status
git submodule deinit <path> # Deregister submodule
git rm <path> # Remove submodule
git config --file=.gitmodules --remove-section submodule.<name> # Cleanup

Subtrees​

Alternative to submodules: merge external repo as subdirectory.

Adding Subtrees​

git subtree add --prefix=<path> <url> <branch>  # Add subtree
git subtree add --prefix=vendor/lib https://github.com/lib/repo main

Updating Subtrees​

git subtree pull --prefix=<path> <url> <branch>  # Pull updates
git subtree push --prefix=<path> <url> <branch> # Push changes back

Subtree vs Submodule​

FeatureSubtreeSubmodule
Learning CurveSteeperGentler
CloningAutomaticRequires init
Sharing ChangesPush back to originalCommit submodule pointer
Directory TrackingFiles in main repoSeparate .git file
Recommended ForExtracting external codeUsing external library

Git Hooks​

Automate actions at specific Git events.

Hook Types​

Pre-commit: Before committing

#!/bin/bash
# .git/hooks/pre-commit
npm run lint || exit 1
npm run format || exit 1

Pre-push: Before pushing

#!/bin/bash
# .git/hooks/pre-push
npm run test || exit 1

Commit-msg: Validate commit message

#!/bin/bash
# .git/hooks/commit-msg
if ! head -1 $1 | grep -qE "^(Add|Fix|Update|Remove):"; then
echo "Commit message must start with: Add, Fix, Update, or Remove"
exit 1
fi

Post-merge: After merging

#!/bin/bash
# .git/hooks/post-merge
npm install || exit 1

Setting Up Hooks​

# Manual setup (one-time)
chmod +x .git/hooks/pre-commit

# Using Husky (NPM projects)
npm install husky --save-dev
npx husky install
npx husky add .husky/pre-commit "npm run lint"
npx husky add .husky/pre-push "npm run test"

# Commit hooks to repo
git add .husky .gitignore
git commit -m "Add: git hooks"

Common Hook Patterns​

#!/bin/bash
# Prevent accidental commits to main
BRANCH=$(git rev-parse --abbrev-ref HEAD)
if [ "$BRANCH" = "main" ]; then
echo "Cannot commit directly to main. Use a feature branch."
exit 1
fi

# Prevent large files
MAX_SIZE=5242880 # 5MB
STAGED_FILES=$(git diff --cached --name-only)
for FILE in $STAGED_FILES; do
SIZE=$(stat -f%z "$FILE" 2>/dev/null || stat -c%s "$FILE" 2>/dev/null)
if [ $SIZE -gt $MAX_SIZE ]; then
echo "File $FILE is too large"
exit 1
fi
done

Git Internals​

Understanding the plumbing beneath Git's porcelain.

Objects​

Git stores four object types:

Blob: File contents

git hash-object <file>             # Calculate blob hash
git cat-file -p <hash> # View blob contents

Tree: Directory structure

git cat-file -p <tree-hash>        # View tree
# Shows: [mode] [type] [hash] [name]

Commit: Snapshot with metadata

git cat-file -p <commit-hash>      # View commit
# tree: <tree-hash>
# parent: <parent-hash>
# author: name <email> timestamp
# committer: name <email> timestamp
# message

Tag: Reference to commit

git cat-file -p <tag-hash>         # View tag

References​

Branches and tags are simple references to commits:

cat .git/HEAD                      # Shows current branch
cat .git/refs/heads/main # Shows main branch commit
cat .git/refs/tags/v1.0 # Shows tag commit
git symbolic-ref HEAD # Show symbolic reference

Repository Structure​

.git/
HEAD # Current branch reference
refs/
heads/ # Local branch references
remotes/ # Remote branch references
tags/ # Tag references
objects/
[hash]/ # Loose objects (blobs, trees, commits)
pack/ # Packfiles (compressed objects)
config # Repository configuration
hooks/ # Git hooks
index # Staging area
logs/ # Reflog

Signed Commits​

Cryptographically sign commits with GPG.

Setup GPG​

gpg --list-secret-keys --keyid-format long  # List keys
gpg --gen-key # Generate key
git config --global user.signingKey <KEY-ID> # Set default key
git config --global commit.gpgsign true # Always sign

Signing Commits​

git commit -S                      # Sign commit
git commit -S -m "message" # Sign with message
git commit --gpg-sign=<key-id> # Sign with specific key

Verifying Signatures​

git log --show-signature           # Show signatures in log
git verify-commit <commit> # Verify specific commit
git tag -v <tag> # Verify signed tag

Git LFS (Large File Storage)​

Handle large files efficiently without bloating repository.

Setup​

git lfs install                    # Initialize LFS
git lfs track "*.psd" # Track file type
git add .gitattributes
git commit -m "Add: LFS tracking"

Usage​

git add large-file.zip             # Added to LFS automatically
git push origin main # LFS file uploaded
git lfs ls-files # List tracked files
git lfs prune # Remove local LFS objects

Monorepo Strategies​

Managing multiple projects in single repository.

Monorepo Structure​

my-monorepo/
packages/
api/
package.json
src/
web/
package.json
src/
shared/
src/
.git
package.json (root)

Advantages​

  • Atomic commits across projects
  • Shared dependencies
  • Simplified tooling
  • Easier refactoring

Challenges​

  • Larger clone/fetch
  • Builds slower
  • Complex CI/CD

Tools for Monorepos​

  • npm/yarn workspaces: Node.js packages
  • Lerna: Package management
  • Nx: Build system
  • Bazel: Google's build system
  • Turborepo: Fast monorepo build system

Git Workflows​

Gitflow​

Complex workflow with release and hotfix branches:

main (production)
├── hotfix-* (emergency fixes)
│ └── merge back to main and develop
│
develop (staging)
├── release-* (preparing release)
│ └── merge to main and develop
│
├── feature-* (feature development)
│ └── merge back to develop
└── bugfix-* (bug fixes)
└── merge back to develop

Best for: Teams with scheduled releases

Trunk-Based Development​

Developers commit directly to main (or short-lived branches):

git checkout -b feature-x
git commit -m "Add feature"
git push origin feature-x
# PR review
git merge feature-x
  • Branches live days, not weeks
  • Frequent merges to main
  • CI/CD catches issues quickly

Best for: High-performing teams, DevOps

GitHub Flow​

Simple workflow: main always deployable

main (always deployable)
├── feature-1 (short-lived)
├── feature-2 (short-lived)
└── hotfix-1 (short-lived)
git checkout -b feature-name
# Make changes
git push origin feature-name
# Create PR, review, merge

Best for: Continuous deployment, web apps

GitLab Flow​

GitHub Flow plus environment branches:

main (staging)
├── production (merge main when ready)
├── feature-1
└── feature-2

Best for: Teams needing pre-production testing

Exercises​

Exercise 1: Interactive Rebase and Squashing​

# Create feature with multiple commits
git switch -c feature-squash
echo "feature 1" > f1.txt
git add f1.txt
git commit -m "Add feature part 1"

echo "feature 2" > f2.txt
git add f2.txt
git commit -m "Add feature part 2"

echo "bug fix" >> f1.txt
git add f1.txt
git commit -m "Fix bug in part 1"

# Interactive rebase to squash
git rebase -i main
# Change last two commits to 'squash'
# Save and edit final message

# Now feature has one clean commit
git log --oneline

Exercise 2: Cherry-Pick Across Branches​

# Create commits on feature branch
git switch -c cherry-source
echo "commit A" > a.txt
git add a.txt
git commit -m "A"

echo "commit B" > b.txt
git add b.txt
git commit -m "B"

echo "commit C" > c.txt
git add c.txt
git commit -m "C"

# Cherry-pick only B to main
git switch main
git cherry-pick <hash-of-B>

# B is now on main, A and C not needed
git log --oneline

Exercise 3: Bisect a Bug​

# Create history with bug
git switch -c buggy-history
for i in {1..10}; do
echo "version $i" > version.txt
git add version.txt
git commit -m "Version $i"
# Introduce bug in version 6
if [ $i -eq 6 ]; then
echo "BUG" >> version.txt
git add version.txt
git commit --amend -m "Version $i (buggy)"
fi
done

# Use bisect to find it
git bisect start
git bisect bad HEAD
git bisect good HEAD~10
# Test each commit, marking good/bad
# Git narrows down to buggy commit
git bisect reset

Exercise 4: Implement Pre-commit Hook​

# Create hook
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/bash
CHANGES=$(git diff --cached --name-only)
for FILE in $CHANGES; do
if [[ $FILE == *.js ]]; then
echo "Linting $FILE..."
# Would run: npx eslint "$FILE" || exit 1
fi
done
exit 0
EOF

chmod +x .git/hooks/pre-commit

# Test it
echo "console.log('test')" > test.js
git add test.js
git commit -m "Add test" # Hook runs

Summary​

Advanced Git mastery includes:

  • Interactive rebase: Clean, logical commit history
  • Cherry-pick: Selective commit application
  • Bisect: Debugging through binary search
  • Submodules/Subtrees: Managing external code
  • Hooks: Automating Git workflows
  • Internals: Understanding objects and references
  • Signed commits: Cryptographic verification
  • LFS: Handling large files
  • Workflows: Team collaboration patterns

These tools enable professional development practices and team workflows at scale.