Skip to content

Portal | Level: L0: Entry | Topics: Git | Domain: DevOps & Tooling

Git Drills

Remember: Git undo safety levels: --soft (safest, keeps everything staged), --mixed (default, unstages but keeps files), --hard (destructive, discards everything). Mnemonic: "Soft keeps Staged, Mixed moves to Modified, Hard destroys." When in doubt, use --soft — you can always unstage later. --hard cannot be undone (except via reflog within 90 days).

Gotcha: git reflog is your emergency parachute. It records every HEAD movement for the last 90 days, even after rebase, reset, or amend. If you accidentally lose commits, git reflog shows where HEAD was before, and git reset --hard <reflog-SHA> recovers them.

Drill 1: Undo Last Commit (Keep Changes)

Difficulty: Easy

Q: You committed too soon. How do you undo the last commit but keep the changes staged?

Answer
git reset --soft HEAD~1
# Changes are back in staging area, ready to re-commit

# If you want changes unstaged:
git reset HEAD~1        # --mixed (default)

# If you want to discard changes entirely:
git reset --hard HEAD~1  # DANGEROUS: changes are gone
| Flag | Commit | Index (staging) | Working dir | |------|--------|----------------|-------------| | `--soft` | Undone | Kept | Kept | | `--mixed` | Undone | Undone | Kept | | `--hard` | Undone | Undone | Undone |

Drill 2: Interactive Rebase

Difficulty: Medium

Q: You have 5 messy commits on a feature branch. Squash them into 2 clean commits before merging.

Answer
git rebase -i HEAD~5
# Or from the branch point:
git rebase -i main

# Editor opens:
pick abc1234 Add user model
squash def5678 Fix typo in user model
squash ghi9012 Add validation
pick jkl3456 Add user API endpoint
squash mno7890 Fix API tests
Commands: - `pick` — keep the commit - `squash` (or `s`) — merge into previous commit - `fixup` (or `f`) — squash but discard the commit message - `reword` (or `r`) — change the commit message - `drop` (or `d`) — delete the commit Result: 2 clean commits instead of 5 messy ones.

Drill 3: Cherry-Pick

Difficulty: Easy

Q: A bugfix on develop needs to go to release immediately. How?

Answer
# Find the commit hash
git log develop --oneline | head -5

# Switch to release branch
git checkout release

# Cherry-pick the specific commit
git cherry-pick abc1234

# If there are conflicts:
# Fix conflicts, then:
git cherry-pick --continue

# Or abort:
git cherry-pick --abort
Cherry-pick creates a new commit with the same changes but a different hash. The original commit stays on its branch.

Drill 4: Stash

Difficulty: Easy

Q: You're mid-feature but need to switch branches for an urgent fix. Save your work without committing.

Answer
# Save current changes
git stash push -m "WIP: user registration form"

# Switch and fix
git checkout main
# ... fix the bug, commit ...

# Come back
git checkout feature/user-reg

# List stashes
git stash list

# Restore
git stash pop           # Apply and remove from stash
# Or:
git stash apply stash@{0}  # Apply but keep in stash

# Include untracked files
git stash push -u -m "Including new files"

Drill 5: Resolve Merge Conflict

Difficulty: Medium

Q: Merge conflict in config.yaml. Walk through the resolution.

Answer
git merge feature-branch
# CONFLICT in config.yaml

# The file now contains:
<<<<<<< HEAD
max_connections: 100
=======
max_connections: 200
>>>>>>> feature-branch

# 1. Edit the file — pick the right value (or combine)
max_connections: 200

# 2. Mark as resolved
git add config.yaml

# 3. Complete the merge
git commit
# (Git provides a default merge commit message)
Tools: `git mergetool` opens a visual diff tool. VS Code highlights conflicts inline. Prevention: rebase feature branches regularly (`git rebase main`) to catch conflicts early.

Drill 6: Bisect

Difficulty: Hard

Q: A bug was introduced somewhere in the last 50 commits. Find the exact commit that broke things.

Answer
# Start bisect
git bisect start

# Mark current (broken) as bad
git bisect bad

# Mark a known good commit
git bisect good v1.0.0   # or a specific hash

# Git checks out a middle commit
# Test it, then mark:
git bisect good   # if this commit works
git bisect bad    # if this commit is broken

# Repeat (binary search: ~6 steps for 50 commits)
# Git finds the exact commit that introduced the bug

# Automated bisect with a test script:
git bisect start
git bisect bad HEAD
git bisect good v1.0.0
git bisect run ./test.sh   # Returns 0 = good, 1 = bad

# Done, reset to original state
git bisect reset
Binary search: `log2(50) ≈ 6` steps to find the bug in 50 commits.

Drill 7: Reflog Recovery

Difficulty: Hard

Q: You accidentally ran git reset --hard and lost commits. How do you recover?

Answer
# Reflog records every HEAD movement
git reflog
# Shows:
# abc1234 HEAD@{0}: reset: moving to HEAD~5
# def5678 HEAD@{1}: commit: Add user API
# ghi9012 HEAD@{2}: commit: Add user model

# Recover to before the reset
git reset --hard def5678
# Or create a branch from the lost commit:
git branch recovery def5678
Reflog is local-only and expires after 90 days (default). It's your safety net for almost any Git disaster. Things reflog can recover from: - `git reset --hard` - Dropped commits from rebase - Deleted branches - Bad merges

Drill 8: Git Hooks

Difficulty: Medium

Q: Set up a pre-commit hook that rejects commits containing TODO or console.log.

Answer
#!/bin/sh
# .git/hooks/pre-commit (must be executable)

# Check for TODO
if git diff --cached --name-only | xargs grep -l 'TODO' 2>/dev/null; then
    echo "ERROR: Remove TODO comments before committing"
    exit 1
fi

# Check for console.log
if git diff --cached --diff-filter=ACM -- '*.js' '*.ts' | grep '+.*console\.log' ; then
    echo "ERROR: Remove console.log statements before committing"
    exit 1
fi

exit 0
chmod +x .git/hooks/pre-commit
For team-wide hooks, use frameworks like **husky** (Node), **pre-commit** (Python), or commit hooks in CI.

Drill 9: Rebase vs Merge

Difficulty: Medium

Q: When do you rebase vs merge? What's the golden rule of rebasing?

Answer **Merge**: Creates a merge commit. Preserves full history.
git checkout main
git merge feature     # Creates a merge commit
**Rebase**: Replays your commits on top of the target. Linear history.
git checkout feature
git rebase main       # Moves feature commits after main's latest
git checkout main
git merge feature     # Fast-forward, no merge commit
**Golden rule**: Never rebase commits that have been pushed and shared with others. Rebase rewrites history — if others have those commits, they'll get conflicts. | Scenario | Use | |----------|-----| | Update feature branch with latest main | Rebase | | Merge feature into main | Merge (or squash merge) | | Shared/public branch | Merge only | | Local-only cleanup | Rebase freely |

Drill 10: Git Blame and Log

Difficulty: Easy

Q: Find out who last changed line 42 of server.py and see all commits that touched that file.

Answer
# Who changed line 42
git blame server.py -L 42,42

# Who changed lines 40-50
git blame server.py -L 40,50

# All commits that touched this file
git log --oneline -- server.py

# See changes per commit for this file
git log -p -- server.py

# Search for when a function was added/removed
git log -S "def process_request" -- server.py

# See changes to a specific function
git log -L :process_request:server.py
`-S` (pickaxe) searches for commits that add/remove a string. Great for finding when code was introduced or deleted.

Wiki Navigation

Prerequisites