Git Advanced Guide
Git Submodules
Git submodules allow you to keep a Git repository as a subdirectory of another Git repository. This is useful for managing dependencies or shared libraries.
Why Use Submodules?
- Manage external dependencies: Include third-party libraries
- Share code across projects: Reuse common components
- Version control dependencies: Lock dependencies to specific versions
- Separate concerns: Keep related but independent codebases together
Adding Submodules
# Add a submodulegit submodule add https://github.com/username/library.git libs/library
# Add submodule to specific pathgit submodule add https://github.com/username/ui-components.git src/components/shared
# Commit the submodulegit add .gitmodules libs/librarygit commit -m "Add library submodule"git push origin mainWhat happens:
- Creates a
.gitmodulesfile tracking submodule configuration - Clones the submodule repository into specified path
- Records the specific commit SHA of the submodule
Cloning a Repository with Submodules
# Clone and initialize submodules in one commandgit clone --recursive https://github.com/username/project.git
# Or clone first, then initialize submodulesgit clone https://github.com/username/project.gitcd projectgit submodule initgit submodule update
# Shorthand for init + updategit submodule update --init --recursiveUpdating Submodules
# Update all submodules to latest commitgit submodule update --remote
# Update specific submodulegit submodule update --remote libs/library
# Pull latest changes in submodulecd libs/librarygit pull origin maincd ../..git add libs/librarygit commit -m "Update library submodule to latest version"Working Inside Submodules
# Navigate to submodulecd libs/library
# Check current commitgit log -1
# Switch to specific branchgit checkout main
# Make changes (if you have write access)git add .git commit -m "Fix bug in library"git push origin main
# Return to parent repositorycd ../..
# Update parent to track new commitgit add libs/librarygit commit -m "Update library submodule with bug fix"git push origin mainRemoving Submodules
# 1. Deinitialize the submodulegit submodule deinit -f libs/library
# 2. Remove from .git/modulesrm -rf .git/modules/libs/library
# 3. Remove from working treegit rm -f libs/library
# 4. Commit the removalgit commit -m "Remove library submodule"Submodule Best Practices
# Always specify branch to trackgit submodule add -b main https://github.com/username/library.git libs/library
# Update to track branch automaticallygit config -f .gitmodules submodule.libs/library.branch main
# Check submodule statusgit submodule status
# View .gitmodules configurationcat .gitmodulesCommon Submodule Issues
Issue: Submodule is empty after clone
git submodule update --init --recursiveIssue: Submodule detached HEAD
cd libs/librarygit checkout maincd ../..Issue: Update all submodules to latest
git submodule update --remote --mergeInteractive Rebase
Interactive rebase allows you to modify commit history before pushing.
Basic Interactive Rebase
# Rebase last 3 commitsgit rebase -i HEAD~3
# Rebase from specific commitgit rebase -i abc1234Interactive rebase editor opens:
pick a1b2c3d Add login featurepick e4f5g6h Fix typo in loginpick i7j8k9l Update documentation
# Commands:# p, pick = use commit# r, reword = use commit, but edit message# e, edit = use commit, but stop for amending# s, squash = use commit, but meld into previous commit# f, fixup = like squash, but discard commit message# d, drop = remove commitSquashing Commits
Combine multiple commits into one:
# Change to:pick a1b2c3d Add login featuresquash e4f5g6h Fix typo in loginsquash i7j8k9l Update documentation
# Save and exit, then edit the combined commit messageRewording Commits
# Change to:reword a1b2c3d Add login featurepick e4f5g6h Fix typo in login
# Save and exit, editor opens to change messageEditing Commits
# Change to:edit a1b2c3d Add login featurepick e4f5g6h Fix typo in login
# Save and exit, make changes, then:git add .git rebase --continuePractical Rebase Example
# Clean up messy commit history before pushgit log --oneline -5# Output:# a1b2c3d WIP# e4f5g6h Fix bug# i7j8k9l Add feature# m0n1o2p Fix typo# q3r4s5t Update feature
git rebase -i HEAD~5
# Squash all into one clean commitpick i7j8k9l Add featuresquash q3r4s5t Update featuresquash e4f5g6h Fix bugsquash m0n1o2p Fix typosquash a1b2c3d WIP
# Results in one commit: "Add feature with bug fixes"Cherry-Picking
Apply specific commits from one branch to another.
Basic Cherry-Pick
# Switch to target branchgit checkout main
# Cherry-pick specific commitgit cherry-pick abc1234
# Cherry-pick multiple commitsgit cherry-pick abc1234 def5678
# Cherry-pick range of commitsgit cherry-pick abc1234..def5678Cherry-Pick with Edit
# Cherry-pick and edit commit messagegit cherry-pick -e abc1234
# Cherry-pick without committing (stage changes only)git cherry-pick -n abc1234Resolving Cherry-Pick Conflicts
# If conflicts occurgit cherry-pick abc1234
# Fix conflicts in filesgit add .git cherry-pick --continue
# Or abortgit cherry-pick --abortPractical Cherry-Pick Example
# Feature branch has a bug fix we need in maingit checkout feature/new-uigit log --oneline# def5678 Fix critical security vulnerability# abc1234 Add new UI components
# Cherry-pick just the bug fixgit checkout maingit cherry-pick def5678git push origin mainGit Hooks
Automate tasks at specific points in Git workflow.
Types of Hooks
Client-side hooks:
pre-commit: Run before commitprepare-commit-msg: Edit default messagecommit-msg: Validate commit messagepost-commit: Run after commitpre-push: Run before push
Server-side hooks:
pre-receive: Run before accepting pushupdate: Run once per branch being pushedpost-receive: Run after push completes
Creating a Pre-Commit Hook
# Navigate to hooks directorycd .git/hooks
# Create pre-commit hookcat > pre-commit << 'EOF'#!/bin/sh
# Run tests before commitnpm test
# Check for console.log statementsif git diff --cached | grep -i "console.log"; then echo "Error: Remove console.log statements before committing" exit 1fi
echo "Pre-commit checks passed!"exit 0EOF
# Make executablechmod +x pre-commitCommit Message Hook
# Create commit-msg hookcat > .git/hooks/commit-msg << 'EOF'#!/bin/sh
commit_msg_file=$1commit_msg=$(cat "$commit_msg_file")
# Ensure commit message starts with capital letterif ! echo "$commit_msg" | grep -q '^[A-Z]'; then echo "Error: Commit message must start with a capital letter" exit 1fi
# Ensure commit message is at least 10 charactersif [ ${#commit_msg} -lt 10 ]; then echo "Error: Commit message too short (minimum 10 characters)" exit 1fi
exit 0EOF
chmod +x .git/hooks/commit-msgPre-Push Hook
# Create pre-push hookcat > .git/hooks/pre-push << 'EOF'#!/bin/sh
protected_branch='main'current_branch=$(git symbolic-ref HEAD | sed -e 's,.*/\(.*\),\1,')
if [ "$current_branch" = "$protected_branch" ]; then read -p "You're about to push to main. Are you sure? [y/n] " -n 1 -r < /dev/tty echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 1 fifi
exit 0EOF
chmod +x .git/hooks/pre-pushBypassing Hooks
# Skip pre-commit hookgit commit --no-verify -m "Emergency fix"
# Skip pre-push hookgit push --no-verify origin mainAdvanced Workflows
GitFlow Workflow
Popular branching model for larger projects.
# Main branches# - main: Production code# - develop: Integration branch
# Create develop branchgit checkout -b develop main
# Feature developmentgit checkout -b feature/user-auth develop# ... work on feature ...git checkout developgit merge --no-ff feature/user-authgit branch -d feature/user-authgit push origin develop
# Release preparationgit checkout -b release/1.0.0 develop# ... bug fixes, version bumps ...git checkout maingit merge --no-ff release/1.0.0git tag -a v1.0.0 -m "Release version 1.0.0"git checkout developgit merge --no-ff release/1.0.0git branch -d release/1.0.0
# Hotfixgit checkout -b hotfix/1.0.1 main# ... fix critical bug ...git checkout maingit merge --no-ff hotfix/1.0.1git tag -a v1.0.1 -m "Hotfix version 1.0.1"git checkout developgit merge --no-ff hotfix/1.0.1git branch -d hotfix/1.0.1Trunk-Based Development
Simple workflow with short-lived branches.
# All work happens on main or short-lived branchesgit checkout -b feature/quick-fix main
# Make small changesgit add .git commit -m "Fix navigation bug"
# Merge quickly (within a day)git checkout maingit merge feature/quick-fixgit push origin maingit branch -d feature/quick-fixAdvanced Git Commands
Bisect (Find Bug-Introducing Commit)
# Start bisectinggit bisect start
# Mark current commit as badgit bisect bad
# Mark a known good commitgit bisect good abc1234
# Git checks out a commit in between# Test it, then mark as good or badgit bisect good # or git bisect bad
# Repeat until bug is found# Git will identify the problematic commit
# End bisectinggit bisect resetReflog (Recover Lost Commits)
# View reference loggit reflog
# Output:# abc1234 HEAD@{0}: commit: Add feature# def5678 HEAD@{1}: commit: Fix bug# ghi9012 HEAD@{2}: reset: moving to HEAD~1
# Recover lost commitgit reset --hard HEAD@{1}
# Or create branch from lost commitgit branch recovered-work HEAD@{2}Worktrees (Multiple Working Directories)
# List worktreesgit worktree list
# Create new worktreegit worktree add ../project-feature feature/new-feature
# Work in both simultaneouslycd ../project-feature# ... make changes ...
# Remove worktreegit worktree remove ../project-featureClean Up Repository
# Remove untracked files (dry run)git clean -n
# Remove untracked filesgit clean -f
# Remove untracked files and directoriesgit clean -fd
# Remove ignored files toogit clean -fdx
# Prune remote branchesgit remote prune origin
# Optimize repositorygit gc --aggressivePerformance Optimization
Shallow Clone (Faster for Large Repos)
# Clone only recent historygit clone --depth 1 https://github.com/username/large-repo.git
# Fetch full history later if neededgit fetch --unshallowSparse Checkout (Clone Specific Directories)
# Initialize sparse checkoutgit clone --no-checkout https://github.com/username/repo.gitcd repogit sparse-checkout init --cone
# Checkout specific directoriesgit sparse-checkout set src/components src/utils
# Checkout filesgit checkout mainPartial Clone (Filter Objects)
# Clone without blobs (download on demand)git clone --filter=blob:none https://github.com/username/repo.git
# Clone without large filesgit clone --filter=blob:limit=1m https://github.com/username/repo.gitQuick Reference
| Command | Description |
|---|---|
git submodule add <url> <path> | Add submodule |
git submodule update --init | Initialize submodules |
git submodule update --remote | Update all submodules |
git rebase -i HEAD~N | Interactive rebase |
git cherry-pick <commit> | Apply specific commit |
git bisect start | Find bug-introducing commit |
git reflog | View reference history |
git clean -fd | Remove untracked files |
git worktree add <path> <branch> | Create worktree |
Best Practices
- Use Submodules Wisely: Only when truly needed, consider alternatives like package managers
- Rebase Private Branches: Keep feature branches clean before merging
- Never Rebase Public History: Don’t rebase commits already pushed to shared branches
- Write Custom Hooks: Automate repetitive checks and tasks
- Use GitFlow for Releases: Great for projects with scheduled releases
- Trunk-Based for CI/CD: Better for continuous deployment workflows
- Regular Maintenance: Clean up old branches, optimize repo periodically
Troubleshooting
Undo a Bad Rebase
git refloggit reset --hard HEAD@{before-rebase}Fix Submodule Issues
git submodule deinit -f .git submodule update --init --recursiveResolve Detached HEAD
git checkout -b temp-branchgit checkout maingit merge temp-branchConclusion
You’ve now mastered advanced Git techniques! These tools will help you:
- Manage complex projects with submodules
- Maintain clean commit history
- Automate workflows with hooks
- Choose appropriate strategies for your team
- Recover from mistakes confidently
Keep practicing these techniques and adapt them to your specific workflow needs!