Git Interview & Scenarios: The Complete DevOps Interview Preparation Guide
Master Git interview questions and real-world troubleshooting scenarios that separate junior from senior DevOps engineers.
📅 Published: Feb 2026
⏱️ Estimated Reading Time: 25 minutes
🏷️ Tags: Git Interview, DevOps Interview, Git Scenarios, Version Control, Troubleshooting
🎯 Common Git Interview Questions: From Basics to Expert
Level 1: Foundation Questions
These questions test whether you understand Git fundamentals. Interviewers ask these to establish baseline knowledge.
Q1: What's the difference between Git and GitHub/GitLab?
Why interviewers ask this: Many beginners confuse Git (the tool) with GitHub (the platform). This tests your fundamental understanding.
Answer:
Git is a version control system that runs locally on your computer. It tracks changes to files and manages your repository history. GitHub, GitLab, and Bitbucket are web-based platforms that host Git repositories and add collaboration features like pull requests, issue tracking, and CI/CD.
Think of it like email:
Git = The email protocol (SMTP/IMAP) - the underlying technology
GitHub = Gmail/Hotmail - the service that makes it accessible
# Git works offline - no internet needed git commit -m "Work on plane" # Works perfectly # GitHub requires internet to push/pull git push origin main # Needs internet connection
Q2: Explain the difference between git pull and git fetch.
Why interviewers ask this: This reveals whether you understand how Git synchronizes with remote repositories.
Answer:git fetch downloads changes from the remote repository but does not integrate them into your working files. It's like checking what's new without bringing it into your workspace.
git pull downloads and immediately integrates changes. It's actually git fetch followed by git merge (or git rebase if configured).
# Fetch - safe, no changes to working directory git fetch origin git log origin/main # See what's new git diff main origin/main # Compare before merging # Pull - downloads AND merges git pull origin main # Fetch + merge # or git pull --rebase origin main # Fetch + rebase
Real-world scenario:
You're on a feature branch and want to see if teammates have pushed changes without disrupting your work-in-progress:
git fetch origin git log main..origin/main # View commits not in your local main # Now decide when to merge
Q3: What's the difference between git merge and git rebase?
Why interviewers ask this: This is the classic Git interview question. Your answer reveals how you think about history management.
Answer:
Both commands integrate changes from one branch into another, but they create different commit histories:
git merge creates a merge commit that joins two branches. It preserves the exact history of when branches diverged and were rejoined. It's non-destructive - you never lose context.
git rebase rewrites history by reapplying commits on top of another branch. It creates a linear, cleaner history but changes commit SHAs.
# Merge preserves context git checkout feature git merge main # Creates: ... A --- B --- C --- D (feature) # \ / # X --- Y --- Z (main) # Rebase creates linear history git checkout feature git rebase main # Creates: ... X --- Y --- Z (main) --- A' --- B' --- C' (feature)
When to use each:
Merge: Public branches, preserving historical truth, when rebase would be disruptive
Rebase: Local feature branches before PR, creating clean, readable history
Golden rule: Never rebase commits that have been pushed to a shared repository.
Q4: How do you undo changes in Git? Explain reset, revert, and checkout.
Why interviewers ask this: Recovery skills separate beginners from professionals. Everyone makes mistakes; professionals know how to fix them.
Answer:
Each undo command serves a different purpose:
git reset moves the branch pointer backward, effectively "erasing" commits. Use it for local, unpushed changes only.
# Soft reset - keep changes staged git reset --soft HEAD~1 # Undo commit, keep changes ready to commit # Mixed reset - keep changes working (default) git reset HEAD~1 # Undo commit, unstage changes # Hard reset - discard changes completely git reset --hard HEAD~1 # Undo commit, DELETE changes (DANGER!)
git revert creates a new commit that undoes a previous commit. It's safe for public branches because it doesn't rewrite history.
git revert HEAD # Create new commit that undoes last commit git revert abc123 # Revert specific commit
git checkout restores files or switches branches.
# Discard changes in working directory git checkout -- file.txt # Restore file to last committed state # Switch to previous commit (detached HEAD) git checkout abc123
Decision tree for undoing changes:
Not committed yet? →
git checkout -- fileorgit restore fileCommitted but not pushed? →
git reset --soft/hard HEAD~1Already pushed to shared branch? →
git revert
Q5: What is a detached HEAD state and how do you recover from it?
Why interviewers ask this: This tests whether you understand Git's internal pointer system.
Answer:
Detached HEAD means your HEAD pointer is attached directly to a commit instead of a branch. You're no longer on a named branch.
Why it happens:
Checking out a specific commit:
git checkout abc123Checking out a tag:
git checkout v1.0.0During interactive rebase
Why it's dangerous: Any commits made in detached HEAD can be lost when you switch branches (they'll be garbage collected after 30 days).
Recovery methods:
# 1. Create a branch immediately (SAFEST) git checkout -b saved-work # 2. Cherry-pick the commit to an existing branch git checkout main git cherry-pick abc123 # 3. Find lost commits in reflog git reflog git checkout -b recovered-branch HEAD@{2}
Level 2: Intermediate Questions
These questions test practical experience and problem-solving ability.
Q6: How do you find a bug using Git bisect?
Why interviewers ask this: This tests whether you know Git's debugging tools, not just basic commands.
Answer:git bisect performs a binary search through your commit history to find which commit introduced a bug.
# Step 1: Start bisect git bisect start # Step 2: Mark current version as bad git bisect bad # Step 3: Mark known good commit (where bug didn't exist) git bisect good v1.0.0 # Step 4: Git checks out middle commit - test it # If bug exists: git bisect bad # If bug doesn't exist: git bisect good # Step 5: Repeat until Git identifies the culprit # Output: abc1234 is the first bad commit # Step 6: End bisect git bisect reset
Real-world scenario:
# Automate with test script git bisect start git bisect bad HEAD git bisect good v1.0.0 git bisect run npm test # Automatically test each commit
Pro tip: Create a script that returns 0 for good, 1 for bad, and let Git run it automatically.
Q7: How do you recover a deleted branch or lost commit?
Why interviewers ask this: This tests your knowledge of Git's safety mechanisms and recovery tools.
Answer:
Git never immediately deletes commits—they linger in the reflog and garbage collection. Use git reflog as your recovery tool.
# 1. Find the lost commit git reflog # abc1234 HEAD@{0}: commit: Critical feature implementation # def5678 HEAD@{1}: checkout: moving from feature to main # 2. Recreate the branch git checkout -b recovered-feature abc1234 # 3. Or cherry-pick the commit git checkout main git cherry-pick abc1234 # 4. If you know the commit hash but it's not in reflog git fsck --lost-found # Find dangling commits git show .git/lost-found/commit/abc1234
Prevention: Push feature branches frequently, even work-in-progress branches.
Q8: How do you clean up a messy commit history before a pull request?
Why interviewers ask this: This tests your ability to present clean, reviewable work.
Answer:
Use interactive rebase to squash, reorder, and rewrite commits.
# Before cleanup (messy history) git log --oneline # a1b2c3d Fix typo # b2c3d4e Actually fix the bug # c3d4e5f Oops, forgot semicolon # d4e5f6g Add feature implementation # e5f6g7h Initial commit # Interactive rebase on the last 5 commits git rebase -i HEAD~5 # In editor, change 'pick' to: # pick e5f6g7h Initial commit # squash d4e5f6g Add feature implementation # fixup c3d4e5f Oops, forgot semicolon # fixup b2c3d4e Actually fix the bug # fixup a1b2c3d Fix typo # After cleanup (clean history) git log --oneline # x1y2z3a Add feature implementation with tests # e5f6g7h Initial commit
Best practice script:
#!/bin/bash # cleanup-pr.sh echo "=== Cleaning up PR branch ===" # Rebase on latest main git fetch origin main git rebase origin/main # Squash fixup commits git rebase -i origin/main # Verify commit messages follow convention git log --oneline origin/main..HEAD # Force push (since history changed) git push --force-with-lease origin HEAD echo "✅ Branch ready for PR review"
Q9: How do you handle large files in Git?
Why interviewers ask this: This tests experience with real-world repository management issues.
Answer:
Git struggles with large files (>50MB) because it stores the entire history of every file. Solutions:
1. Prevent large files from being committed:
# .gitignore *.iso *.zip *.tar.gz node_modules/ # Pre-commit hook find . -type f -size +5M ! -path ".git/*" | while read file; do echo "❌ Large file detected: $file" exit 1 done
2. Remove large files from history (BFG Repo-Cleaner):
# Download BFG java -jar bfg.jar --strip-blobs-bigger-than 50M git reflog expire --expire=now --all git gc --prune=now --aggressive
3. Use Git LFS (Large File Storage) for binaries:
# Install Git LFS git lfs install # Track file types git lfs track "*.psd" git lfs track "*.zip" git add .gitattributes # Commit as normal git add file.psd git commit -m "feat: add design assets" git push
Q10: What's your Git branching strategy and why?
Why interviewers ask this: This tests your ability to design and justify workflows, not just follow patterns.
Answer:
"I adapt my branching strategy to the team's needs, but my preferred approach is GitHub Flow with these adaptations:"
## Branch Strategy **main** - Always deployable, protected branch - Direct commits prohibited - Requires PR with 1-2 reviewers - Must pass all CI checks - Deployed automatically **feat/** - New features - Branched from main - Deleted after merge - Naming: `feat/JIRA-123-description` - Squash merge to main **fix/** - Bug fixes - Same rules as feat/ - Includes regression tests - Cherry-picked to release branches if needed **hotfix/** - Production emergencies - Branched from tagged release - Urgent review process - Merged to main AND develop - Tagged with hotfix version **release/** - Version preparation - Branched from develop - Only bug fixes, no new features - Final testing and version bumps - Merged to main and tagged
Why this works:
Simple - Easy for new team members to understand
Safe - Protected branches and required reviews
Fast - Short-lived branches, continuous integration
Traceable - JIRA tickets in branch names connect code to requirements
Level 3: Expert Questions
These questions test deep understanding of Git internals and complex troubleshooting.
Q11: Explain how Git stores data internally.
Why interviewers ask this: This separates users from masters. Understanding internals helps you debug and recover from any situation.
Answer:
Git is fundamentally a content-addressable filesystem with a VCS interface. It stores data as key-value pairs where the key is a SHA-1 hash of the content.
Four object types:
# 1. Blob - File contents git hash-object -w file.txt # Creates blob # 2. Tree - Directory structure git cat-file -p HEAD^{tree} # View tree # 3. Commit - Snapshot with metadata git cat-file -p HEAD # View commit object # 4. Tag - Named reference to commit git cat-file -p v1.0.0 # View tag object
Storage mechanics:
# View Git's internal storage ls -la .git/objects/ # 12/ 34/ 56/ # First 2 chars = directory, rest = filename # View object type and content git cat-file -t abc1234 # blob, tree, commit, tag git cat-file -p abc1234 # Pretty-print content # Git's compression git gc # Garbage collect, pack objects ls -la .git/objects/pack/ # Packed files are delta-compressed
The magic of SHA-1:
Content + header = hash
Same content = same hash anywhere in the universe
This is how Git detects changes and duplicates
Q12: How do you recover from a force-push that overwrote your branch?
Why interviewers ask this: This tests your ability to handle catastrophic team scenarios.
Answer:
This is every team's nightmare. Here's a systematic recovery approach:
# 1. STAY CALM - Don't push anything else # 2. Find the original commit using reflog (on ANY machine that had the branch) git reflog show origin/branch-name # If you have remote reflog git reflog show branch-name # Local reflog # 3. If you have the commit hash, recreate the branch git checkout -b recovered-branch abc1234 git push origin recovered-branch # 4. If no one else has the hash, check other developers' machines # They can push the branch back git push origin local-branch:remote-branch # 5. Use Git's data recovery tools git fsck --full --no-reflogs --unreachable git show unreachable-commit-hash # 6. As a last resort, reconstruct from PR (GitHub/GitLab) # GitHub stores all PR commits permanently # Find the PR, look for "merged commit" hash # 7. Prevention: Configure branch protection git push --force-with-lease # NEVER use --force
Team recovery protocol:
#!/bin/bash # emergency-branch-recovery.sh echo "🚨 EMERGENCY: Branch overwritten" echo "Time: $(date)" echo "Branch: $1" echo # 1. Identify affected developers echo "1. Identifying affected work..." git reflog | grep $1 | head -20 # 2. Create recovery branch echo "2. Creating recovery branch..." git checkout -b $1-recovery $2 git push origin $1-recovery # 3. Notify team echo "3. Sending team alert..." echo "Subject: Git Recovery Needed for $1" echo "Recovery branch: $1-recovery" echo "Please cherry-pick your commits to new feature branch" # 4. Post-mortem echo "4. Root cause analysis required"
Q13: How do you implement Git hooks for compliance in regulated industries?
Why interviewers ask this: This tests experience with enterprise Git, compliance, and automation.
Answer:
In regulated industries (finance, healthcare, government), you need enforceable, auditable Git workflows:
#!/bin/bash # compliance-hooks.sh # pre-commit - Prevent secrets and compliance violations cat > .git/hooks/pre-commit << 'EOF' #!/bin/bash echo "🔒 Compliance Check - $(date)" >> .git/compliance.log # 1. Check for hardcoded secrets SECRET_PATTERNS="password|secret|key|token|credential|PRIVATE" if git diff --cached | grep -iE "$SECRET_PATTERNS"; then echo "❌ SECURITY VIOLATION: Hardcoded secrets detected" exit 1 fi # 2. Check for TODO/FIXME in production code if git diff --cached | grep -iE "\+.*todo|\+.*fixme"; then echo "⚠️ COMPLIANCE WARNING: TODO/FIXME in committed code" echo "Add JIRA ticket reference: // TODO(JIRA-123)" fi # 3. License headers (required by legal) for file in $(git diff --cached --name-only); do if ! grep -q "Copyright $(date +%Y) Company Name" "$file"; then echo "❌ LEGAL VIOLATION: Missing copyright header in $file" exit 1 fi done EOF # commit-msg - Enforce regulatory ticket references cat > .git/hooks/commit-msg << 'EOF' #!/bin/bash commit_msg=$(cat "$1") # REGULATORY-123: Description if ! echo "$commit_msg" | grep -qE "^(feat|fix|docs|chore)\([a-z]+\): \[[A-Z]+-[0-9]+\]"; then echo "❌ COMPLIANCE: Commit must include JIRA ticket in format [PROJECT-123]" exit 1 fi EOF # pre-push - Required signatures and approvals cat > .git/hooks/pre-push << 'EOF' #!/bin/bash # HIPAA/SOX compliance requires signed commits if ! git log -1 --format=%B | grep -q "Signed-off-by"; then echo "❌ COMPLIANCE: Commits must be signed off (git commit -s)" exit 1 fi # Verify GPG signature if ! git verify-commit HEAD; then echo "❌ SECURITY: Commits must be GPG signed" exit 1 fi EOF
Audit trail requirements:
# Every action must be logged git config --global alias.commit '!f() { \ echo "[$(date)] commit: $@" >> ~/.git-audit.log; \ git -c commit.gpgsign=true commit -s -S "$@"; \ }; f'
Q14: How do you optimize Git performance for monorepos?
Why interviewers ask this: This tests experience with large-scale Git repositories.
Answer:
Monorepos (single repository for entire company) create unique Git challenges:
# 1. Partial clone (Git 2.19+) - Download only what you need git clone --filter=blob:none <url> # No file contents git clone --filter=tree:0 <url> # No trees git sparse-checkout set /service-a # Only checkout specific directories # 2. Shallow clone for CI/CD git clone --depth 1 <url> # Only last commit git fetch --depth 1 origin <commit-sha> # Fetch specific commit # 3. Git GC optimization git gc --aggressive --prune=now git repack -a -d --depth=250 --window=250 # 4. Configure Git for large repos git config core.preloadindex true # Faster git status git config core.fscache true # Filesystem cache (Windows) git config gc.auto 256 # Less frequent GC git config pack.threads 8 # Parallel compression # 5. Use Git worktrees for parallel work git worktree add ../project-hotfix hotfix git worktree add ../project-feature feature # Each worktree shares .git folder but has separate working directory # 6. Monorepo management tools # - git-submodules (built-in) # - git-subtree (built-in) # - Google's repo tool # - Facebook's EdenFS
Enterprise monorepo strategy:
# .gitattributes - Path-specific settings /service-a/** linguist-generated=true /service-b/** -diff *.pbxproj merge=union # .gitignore patterns **/node_modules/ **/.env **/dist/
🔧 Real-world Git Issues: Case Studies
Case Study 1: The Deleted Production Branch
Scenario: A junior developer accidentally deleted the main branch on GitHub. Your team is panicking, and deployment is blocked.
Root cause: Branch protection wasn't enabled. The developer had admin rights and clicked "Delete branch" after merging a PR.
Recovery steps:
# 1. CHECK REFLOG ON ANY LOCAL COPY git reflog show origin/main # If you find the last commit hash, recreate branch # 2. IF NO REFLOG, CHECK PULL REQUESTS # GitHub stores all PR commits permanently # Find the last merged PR, copy commit SHA # 3. RECREATE BRANCH LOCALLY git checkout -b main abc1234 git push origin main # 4. ENABLE BRANCH PROTECTION # Settings → Branches → Add rule # ☑ Require pull request reviews # ☑ Dismiss stale reviews # ☑ Require status checks # ☑ Require signed commits # ☑ Do not allow bypassing settings # 5. POST-MORTEM # Document incident, adjust permissions # Remove admin rights from developer accounts # Implement backup strategy for critical branches
Prevention script:
#!/bin/bash # protect-critical-branches.sh # Enable branch protection via GitHub API REPO="company/production" BRANCHES=("main" "develop" "staging" "production") for BRANCH in "${BRANCHES[@]}"; do curl -X PUT \ -H "Authorization: token $GITHUB_TOKEN" \ -H "Accept: application/vnd.github.v3+json" \ "https://api.github.com/repos/$REPO/branches/$BRANCH/protection" \ -d '{ "required_status_checks": { "strict": true, "contexts": ["continuous-integration"] }, "enforce_admins": true, "required_pull_request_reviews": { "required_approving_review_count": 2, "dismiss_stale_reviews": true }, "restrictions": null }' done
Case Study 2: The 10GB Repository
Scenario: Your repository has grown to 10GB. git clone takes 30 minutes, git status takes 10 seconds, and developers are complaining.
Root cause: Multiple issues:
Accidentally committed 500MB video files (removed but still in history)
5 years of binary build artifacts
No Git LFS strategy
Every build artifact committed "just in case"
Diagnosis:
# 1. Find largest files in history git rev-list --objects --all | \ git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | \ sed -n 's/^blob //p' | \ sort --numeric-sort --key=2 | \ tail -10 | \ cut -c 1-12,41- | \ $(command -v gnumfmt || echo numfmt) --field=2 --to=iec-i --suffix=B --padding=7 --round=nearest # 2. Check pack file size du -sh .git/objects/pack/ # 3. Count objects git count-objects -vH
Recovery strategy:
# Phase 1: Emergency cleanup # Remove large files from history using BFG java -jar bfg.jar --delete-files "*.mp4" --delete-files "*.zip" java -jar bfg.jar --strip-blobs-bigger-than 50M # Phase 2: Garbage collection git reflog expire --expire=now --all git gc --prune=now --aggressive # Phase 3: Force push (COORDINATE WITH TEAM!) git push --force --all git push --force --tags # Phase 4: Implement Git LFS git lfs track "*.psd" git lfs track "*.mp4" git lfs track "*.zip" git add .gitattributes git commit -m "chore: add Git LFS tracking for binary files" git push # Phase 5: Migrate existing binaries to LFS git lfs migrate import --include="*.psd,*.mp4,*.zip" --everything
Prevention policy:
# .github/workflows/validate-size.yml name: Validate File Sizes on: [pull_request] jobs: check-files: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Check file sizes run: | LARGE_FILES=$(find . -type f -size +5M -not -path "./.git/*") if [ -n "$LARGE_FILES" ]; then echo "❌ Large files detected:" echo "$LARGE_FILES" exit 1 fi
Case Study 3: The Divergent Histories Disaster
Scenario: Two teams worked on the same repository for 6 months without syncing. Now you have two completely divergent main branches that Git refuses to merge.
Root cause: Team A used rebase workflow, Team B used merge workflow. No one communicated. Now there are 5,000 commits with overlapping changes, and git merge produces 2,000 conflicts.
Recovery strategy:
# Phase 1: Assessment # Create backup branches git branch backup/team-a origin/team-a/main git branch backup/team-b origin/team-b/main # Analyze differences git log --oneline backup/team-a ^backup/team-b | wc -l # Commits only in A git log --oneline backup/team-b ^backup/team-a | wc -l # Commits only in B # Phase 2: Create merge base # Find common ancestor BASE_COMMIT=$(git merge-base backup/team-a backup/team-b) git checkout -b integration $BASE_COMMIT # Phase 3: Strategic merge (not tactical) # Don't try to merge everything at once - use "strategy merge" # First merge team A with ours strategy (keep all our changes) git merge -s ours backup/team-a --no-commit git commit -m "chore: integrate team A history" # Then merge team B with recursive and patience algorithm git merge backup/team-b -X patience -X rename-threshold=30 # Phase 4: Break into phases # Create integration branches for each module git checkout -b integration/auth $BASE_COMMIT git merge --squash backup/team-a -- src/auth/ git commit -m "feat(auth): integrate team A auth module" git merge --squash backup/team-b -- src/auth/ # Resolve conflicts for just this module git commit -m "fix(auth): resolve integration conflicts" # Repeat for each module # Phase 5: Final integration git checkout -b final-integration main for module in auth api frontend; do git merge integration/$module --no-ff -m "feat: integrate $module" done
Lessons learned document:
# Post-Mortem: Repository Divergence Incident ## Timeline - Month 1: Teams started working independently - Month 3: First signs of divergence (ignored) - Month 6: Deployment blocked, unable to merge ## Root Causes 1. No shared branch strategy 2. No regular integration meetings 3. No CI on feature branches 4. Different rebase/merge preferences ## Prevention Measures ✅ Weekly "integration sync" meetings ✅ CI required on all branches ✅ Branch protection rules enforced ✅ Shared Git strategy documented ✅ Monthly "rebase day" for long-running branches ## Recovery Status ✅ All modules successfully integrated ✅ 95% automatic merge, 5% manual resolution ✅ 2 weeks of focused effort ✅ All tests passing
Case Study 4: The CI/CD Pipeline That Deleted History
Scenario: Your CI/CD pipeline runs git push --force after successful builds. One day, a build succeeds but the force push overwrites the wrong branch, deleting 3 days of work from the release branch.
Root cause: CI script used git push --force instead of git push --force-with-lease. The script also didn't validate which branch it was pushing.
The dangerous script:
#!/bin/bash # BAD - DO NOT USE git checkout $BRANCH git merge --no-ff feature/$FEATURE git push --force origin $BRANCH # DANGER!
Recovery:
# 1. IMMEDIATELY: Identify who has the lost commits # Check all developers' local repositories # Use the reflog on any machine that had the branch git reflog show origin/release | head -20 # 2. RECOVER: Restore from local copy git checkout -b release-recovery abc1234 git push origin release-recovery # 3. NOTIFY: Team to cherry-pick from recovery branch echo "Release branch recovered at release-recovery" echo "Please cherry-pick your commits to new release branch" # 4. FIX: Update CI/CD pipeline cat > .github/workflows/deploy.yml << 'EOF' name: Safe Deploy on: [push] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: fetch-depth: 0 # Full history needed for safety check - name: Validate branch run: | if [[ "$GITHUB_REF" != "refs/heads/release"* ]] && \ [[ "$GITHUB_REF" != "refs/heads/hotfix"* ]]; then echo "❌ Cannot force push to $GITHUB_REF" exit 1 fi - name: Push with lease run: | git push --force-with-lease origin HEAD:$GITHUB_REF EOF
Prevention checklist:
## CI/CD Git Safety Checklist - [ ] Never use `git push --force` - [ ] Always use `git push --force-with-lease` - [ ] Validate branch names before force push - [ ] Only allow force push to feature branches - [ ] Enable branch protection on main/develop/release - [ ] Require linear history on protected branches - [ ] Log all force push operations - [ ] Alert team when force push occurs
📝 Hands-on Practice Tasks
Task 1: Git Bisect Detective
Objective: Find which commit introduced a specific bug using binary search.
# Setup mkdir git-detective && cd git-detective git init # Create buggy script cat > app.js << 'EOF' function calculateTotal(price, tax) { // Bug introduced somewhere in history return price + tax; // Should be price * (1 + tax) } EOF git add app.js git commit -m "feat: add tax calculation" # Create 20 commits, introducing bug at commit 10 for i in {1..20}; do if [ $i -eq 10 ]; then # Bug version cat > app.js << 'EOF' function calculateTotal(price, tax) { return price + tax; // WRONG! Should multiply } EOF else # Correct version cat > app.js << 'EOF' function calculateTotal(price, tax) { return price * (1 + tax); // CORRECT } EOF fi git add app.js git commit -m "chore: update iteration $i" done # TASK: Find commit 10 using git bisect
Solution:
# Start bisect git bisect start # Mark current as bad git bisect bad # Mark first commit as good git bisect good $(git rev-list --max-parents=0 HEAD) # Git will checkout middle commits - test each # If function returns price + tax (bad): git bisect bad # If function returns price * (1+tax) (good): git bisect good # Automate with test script cat > test.sh << 'EOF' #!/bin/bash if grep -q "price + tax" app.js; then exit 1 # Bad else exit 0 # Good fi EOF chmod +x test.sh git bisect run ./test.sh
Task 2: Rewriting History Safely
Objective: Clean up a messy feature branch without losing work.
# Setup mkdir git-cleanup && cd git-cleanup git init # Create messy history touch file1.txt && git add . && git commit -m "wip" touch file2.txt && git add . && git commit -m "fix" touch file3.txt && git add . && git commit -m "actually fix" touch file4.txt && git add . && git commit -m "add feature" touch file5.txt && git add . && git commit -m "oops" touch file6.txt && git add . && git commit -m "add tests" touch file7.txt && git add . && git commit -m "fix tests" touch file8.txt && git add . && git commit -m "documentation" touch file9.txt && git add . && git commit -m "style" touch file10.txt && git add . && git commit -m "chore" # TASK: Clean to 3 meaningful commits: # 1. "feat: add core feature implementation" # 2. "test: add test suite" # 3. "docs: update documentation"
Solution:
# 1. Create backup branch (SAFETY FIRST!) git branch backup/messy-history # 2. Interactive rebase git rebase -i --root # In editor, reorganize commits: # pick 1st commit - "feat: add core feature implementation" # squash 2-4 into it # fixup 5-7 into it # pick "add tests" - "test: add test suite" # squash/fixup test-related commits # pick "documentation" - "docs: update documentation" # squash/fixup remaining # 3. Force push with lease (to your feature branch only!) git push --force-with-lease origin feature-branch
Task 3: Git Surgery - Splitting a Commit
Objective: Split one large commit into multiple focused commits.
# Setup mkdir git-surgery && cd git-surgery git init # Create one large commit cat > auth.js << 'EOF' function login(username, password) { // authentication logic } EOF cat > profile.js << 'EOF' function getProfile(userId) { // profile retrieval } EOF cat > utils.js << 'EOF' function formatDate(date) { return date.toISOString().split('T')[0]; } EOF git add . git commit -m "feat: add authentication, profiles, and utilities" # TASK: Split this commit into 3 logical commits
Solution:
# 1. Start interactive rebase git rebase -i HEAD~1 # Change 'pick' to 'edit' # 2. Reset to previous commit, keeping changes git reset HEAD^ # 3. Commit each file separately git add auth.js git commit -m "feat(auth): add user authentication" git add profile.js git commit -m "feat(profile): add user profile retrieval" git add utils.js git commit -m "feat(utils): add date formatting utilities" # 4. Continue rebase git rebase --continue # 5. Verify git log --oneline # Should show 3 commits instead of 1
Task 4: Merge Conflict Master
Objective: Resolve a complex merge conflict involving multiple files.
# Setup - Person A mkdir git-conflict-a && cd git-conflict-a git init cat > config.json << 'EOF' { "version": "1.0.0", "features": { "auth": true, "profile": false, "admin": false } } EOF git add config.json git commit -m "chore: initial config" git checkout -b feature/person-a cat > config.json << 'EOF' { "version": "1.1.0", "features": { "auth": true, "profile": true, // Added "admin": false, "notifications": true // New feature } } EOF git commit -am "feat: add profile and notifications" # Setup - Person B (separate directory) mkdir ../git-conflict-b && cd ../git-conflict-b git init cp ../git-conflict-a/config.json . git add config.json git commit -m "chore: initial config" git checkout -b feature/person-b cat > config.json << 'EOF' { "version": "1.2.0", "features": { "auth": true, "profile": false, "admin": true, // Added "analytics": true // New feature }, "database": { "host": "localhost", "port": 5432 } } EOF git commit -am "feat: add admin and analytics, add database config" # TASK: Merge both branches and resolve conflicts intelligently
Solution:
# 1. Create integration branch git checkout main git checkout -b merge/integration # 2. Merge first feature branch git merge feature/person-a --no-ff # Should succeed # 3. Merge second feature branch (CONFLICT!) git merge feature/person-b # 4. Resolve conflict in config.json # Open config.json, create merged version: { "version": "2.0.0", // Bump major version for multiple new features "features": { "auth": true, "profile": true, // From Person A "admin": true, // From Person B "notifications": true, // From Person A "analytics": true // From Person B }, "database": { // From Person B "host": "localhost", "port": 5432 } } # 5. Mark as resolved git add config.json git commit -m "merge: resolve config.json conflicts - Combined features from both branches - Bumped version to 2.0.0 - Added database configuration" # 6. Verify cat config.json | jq '.features' # Should have all 5 features
Task 5: Git Hook Implementation
Objective: Create a comprehensive pre-commit hook that enforces team standards.
# Setup mkdir git-hook-practice && cd git-hook-practice git init # TASK: Create pre-commit hook that: # 1. Prevents committing .env files # 2. Enforces file size limit (1MB) # 3. Checks for TODO without ticket reference # 4. Runs linter on staged files # 5. Validates branch name format
Solution:
cat > .git/hooks/pre-commit << 'EOF' #!/bin/bash echo "🔍 Running pre-commit checks..." FAILED=0 # 1. Branch name validation BRANCH_NAME=$(git symbolic-ref --short HEAD 2>/dev/null) if [[ ! "$BRANCH_NAME" =~ ^(feat|fix|hotfix|chore|docs)/[a-z0-9-]+$ ]]; then echo "❌ Branch name must match: type/description" echo " Examples: feat/login-form, fix/null-pointer" FAILED=1 fi # 2. Check for .env files ENV_FILES=$(git diff --cached --name-only | grep -E "\.env$|\.env\.") if [ -n "$ENV_FILES" ]; then echo "❌ Environment files detected:" echo "$ENV_FILES" echo " Use .env.example instead" FAILED=1 fi # 3. File size check git diff --cached --name-only | while read file; do if [ -f "$file" ]; then SIZE=$(stat -c%s "$file") if [ $SIZE -gt 1048576 ]; then # 1MB echo "❌ File too large: $file (${SIZE} bytes)" echo " Max size: 1MB" FAILED=1 fi fi done # 4. TODO without ticket reference TODO_LINES=$(git diff --cached | grep -E "^\+.*TODO" | grep -vE "TODO\([A-Z]+-[0-9]+\)") if [ -n "$TODO_LINES" ]; then echo "⚠️ TODO missing ticket reference:" echo "$TODO_LINES" echo " Format: TODO(JIRA-123)" # Warning only, don't fail fi # 5. Run linter on staged JavaScript files STAGED_JS=$(git diff --cached --name-only --diff-filter=ACM | grep "\.js$") if [ -n "$STAGED_JS" ] && [ -f "node_modules/.bin/eslint" ]; then echo "Running ESLint..." npx eslint $STAGED_JS if [ $? -ne 0 ]; then echo "❌ ESLint failed" FAILED=1 fi fi # 6. Check for debugging statements if git diff --cached | grep -E "^\+.*console\.log|^\+.*debugger|^\+.*print\("; then echo "⚠️ Debugging statements detected" echo " Consider removing before final commit" # Warning only fi if [ $FAILED -eq 1 ]; then echo "❌ Pre-commit checks failed" exit 1 else echo "✅ All pre-commit checks passed" fi EOF chmod +x .git/hooks/pre-commit # Test the hook echo "console.log('debug');" > test.js git add test.js git commit -m "test" # Should warn about console.log echo "SECRET_KEY=123" > .env git add .env git commit -m "test" # Should FAIL
Task 6: Git Team Simulation
Objective: Practice team collaboration with branches, PRs, and merge conflicts.
#!/bin/bash # team-simulation.sh echo "=== Git Team Simulation ===" echo "Role: Team Lead / DevOps Engineer" echo # Setup shared repository mkdir team-repo && cd team-repo git init --bare repo.git cd .. git clone team-repo/repo.git project cd project # Initialize project cat > README.md << 'EOF' # Team Project Welcome to the team! EOF git add README.md git commit -m "chore: initial commit" git push origin main # Person A - Feature implementation git checkout -b feat/user-auth cat > auth.js << 'EOF' function login(username, password) { // TODO: implement JWT return "token"; } EOF git add auth.js git commit -m "feat(auth): add login function stub" git push origin feat/user-auth # Person B - Different feature (simulate in real team) git checkout main git checkout -b feat/user-profile cat > profile.js << 'EOF' function getProfile(userId) { // TODO: fetch from database return { name: "John" }; } EOF git add profile.js git commit -m "feat(profile): add getProfile stub" git push origin feat/user-profile # Person A continues working... git checkout feat/user-auth echo "function validateEmail(email) { return true; }" >> auth.js git commit -am "feat(auth): add email validation" git push # TASK: As team lead, review and merge both PRs echo -e "\n🎯 YOUR TASK:" echo "1. Review both feature branches" echo "2. Test functionality" echo "3. Merge with main" echo "4. Resolve any conflicts" echo "5. Tag release v1.0.0"
Solution steps:
# 1. Review branches git fetch --all git checkout origin/feat/user-auth git diff main git log --oneline main..feat/user-auth # 2. Test changes locally git checkout -b test/user-auth origin/feat/user-auth # Run tests, verify functionality # 3. Merge to main with PR strategy git checkout main git pull origin main git merge --no-ff feat/user-auth -m "merge: PR #123 - user authentication" git push origin main git merge --no-ff feat/user-profile -m "merge: PR #124 - user profile" git push origin main # 4. Create release git tag -a v1.0.0 -m "Release 1.0.0 - Auth + Profile features" git push origin v1.0.0 # 5. Clean up branches git push origin --delete feat/user-auth git push origin --delete feat/user-profile git branch -d feat/user-auth feat/user-profile
📋 Git Interview Preparation Checklist
# Git Interview Readiness Checklist ## ✅ Fundamentals - [ ] Explain difference between Git and GitHub - [ ] Describe Git workflow (add, commit, push, pull) - [ ] Differentiate merge vs rebase - [ ] Understand detached HEAD state - [ ] Know reset, revert, checkout differences ## ✅ Branching & Merging - [ ] Create feature branches with naming conventions - [ ] Resolve merge conflicts confidently - [ ] Squash commits with interactive rebase - [ ] Cherry-pick specific commits - [ ] Stash and apply changes ## ✅ Collaboration - [ ] Fork and clone repositories - [ ] Open and review pull requests - [ ] Implement branch protection rules - [ ] Handle upstream changes with rebase - [ ] Manage multiple remotes ## ✅ Troubleshooting - [ ] Recover lost commits with reflog - [ ] Find bugs with git bisect - [ ] Remove large files from history - [ ] Recover from force push disaster - [ ] Clean up local/remote branches ## ✅ Advanced - [ ] Implement Git hooks for automation - [ ] Configure Git LFS for binaries - [ ] Optimize monorepo performance - [ ] Sign commits with GPG - [ ] Custom Git aliases ## ✅ Best Practices - [ ] Write conventional commits - [ ] Maintain clean linear history - [ ] Use .gitignore effectively - [ ] Document branching strategy - [ ] Automate with CI/CD ## 🎯 Common Interview Questions - [ ] "How do you undo a public commit?" - [ ] "What's your branching strategy and why?" - [ ] "How do you resolve merge conflicts?" - [ ] "How do you handle hotfixes in production?" - [ ] "How do you onboard new developers to Git?"
🚀 Final Practice Challenge: Git Escape Room
Objective: Solve 5 Git problems in under 30 minutes.
#!/bin/bash # git-escape-room.sh echo "=== GIT ESCAPE ROOM ===" echo "You have 30 minutes to solve 5 challenges" echo # Challenge 1: Detached HEAD Recovery git checkout v1.0.0 echo "hotfix" > urgent.txt git add urgent.txt git commit -m "fix: urgent production hotfix" # 💡 Your commit is in detached HEAD! # TASK: Save this commit to a branch # Challenge 2: Lost Commit Recovery git checkout main git branch -D feature/in-progress # Oops, deleted! # TASK: Recover the branch using reflog # Challenge 3: Merge Conflict Resolution git merge release/v2.0.0 # CONFLICT in config.json! # TASK: Resolve intelligently, keep both features # Challenge 4: History Rewriting git log --oneline # 5 messy commits about the same feature # TASK: Squash into 1 clean commit # Challenge 5: Git Hook Implementation # TASK: Create pre-commit hook that: # - Prevents commit if contains "password" # - Runs unit tests # - Validates commit message format echo -e "\n🎉 ESCAPE COMPLETE!" echo "Rate your confidence (1-5):"
Solutions available upon request in comments! 💬
🔗 Master Git with Hands-on Interview Prep
Git interview questions are designed to test not just what you know, but how you think about version control problems. The best preparation is hands-on practice with real scenarios.
👉 Practice Git interview questions and real-world scenarios in our interactive labs at:
https://devops.trainwithsky.com/
Our platform provides:
50+ common Git interview questions with solutions
Real-time Git simulation environment
Merge conflict resolution challenges
Team collaboration scenarios
Emergency recovery drills
Personalized feedback on your Git workflow
Frequently Asked Interview Questions
Q: "I've used Git for years but always struggle with rebase. Is that okay?"
A: Honesty is better than bluffing. Say: "I primarily use merge in my daily work because I prefer preserving history context. I understand rebase conceptually and use it for cleaning up feature branches before PRs, but I'm more comfortable with merge for collaborative work." This shows self-awareness and strategic thinking.
Q: "What's the worst Git mistake you've made?"
A: Everyone has made mistakes. A good answer shows growth: "I once force-pushed to the main branch, overwriting a teammate's commits. Since then, I always use --force-with-lease, enable branch protection, and never force push to shared branches. I also wrote a team guide about Git safety."
Q: "How do you stay updated with Git?"
A: "I follow Git's release notes, subscribe to GitHub's changelog, and practice new features in side projects. I also participate in code reviews where I see how other developers solve Git problems."
Q: "Should we use GitFlow or GitHub Flow?"
A: "It depends on your deployment cadence. For continuous deployment, GitHub Flow is simpler and faster. For scheduled releases with multiple versions in production, GitFlow's release branches provide better isolation. I've implemented both and would recommend GitHub Flow for most modern teams."
Q: "How do you handle Git in a regulated environment?"
A: "I implement mandatory code reviews, signed commits, and comprehensive Git hooks for compliance checks. All merges to protected branches require passing CI, ticket references, and sign-offs. Every action is logged for audit trails."
Have a Git interview coming up? Facing a specific Git challenge? Share your scenario in the comments below, and our community will help you prepare! 💬
Comments
Post a Comment