Senior-Level Git & Version Control Interview Guide

Basics

1. Git Internals & Data Structures

  • Question: Explain the exact lifecycle of a file commit in Git, starting from the working directory through the staging area (.git/index), to the object database (.git/objects). What data types are written to disk, and how does Git compute structural identity?

    • What interviewers look for: Look for a clear explanation of blobs (file contents hashed via SHA-1 based on content, not filename), trees (directory structures mapping filenames to blobs/sub-trees), and commit objects (metadata pointing to a root tree and parent commit hashes). Candidates should mention that .git/index is a binary cache mapping file paths to staged staging hashes and modification timestamps.
  • Question: In a large microservices monorepo with over 500,000 files, git status or git add can become a major performance bottleneck. How does Git optimize filesystem tracking using the index, and how do tools like git commit-graph or Git’s sparse-checkout feature mitigate scaling issues?

    • What interviewers look for: Candidates should discuss how the index avoids scanning every file on disk by checking file modification times (mtime) and inodes against cached index records. For massive repos, they should mention core.untrackedCache (fsmonitor), sparse-checkouts (checking out only specific sub-directories), and commit-graph files for accelerating reachability queries.

2. Scenario-Based & Distributed Workflow Questions

  • Question (The Rebase vs. Merge Trade-off in a Microservices CI/CD Pipeline): Your team manages a core Java SDK library consumed by 30 downstream microservices. Two senior engineers push conflicting feature branches to main within minutes of each other. Team policy mandates clean, linear history for release auditing, but developers frequently run into broken builds during rebases.

    • How would you architect the branch protection rules and merge strategy on GitHub/GitLab to balance clean history with zero-broken-build deployments?

    • What interviewers look for: Avoid blindly saying “always rebase” or “always merge”. A senior answer weighs linear history (via git rebase or squash-merging) against losing context on merge commits. The ideal solution involves strict branch protection, mandatory pre-merge CI checks (GitHub Actions / GitLab CI testing the merge commit in isolation before merging), and enforcing squash-and-merge or protected merge queues.

  • Question (Accidental Secret Exposure & History Rewriting): A developer accidentally committed a production database password and a private RSA key into the third commit of a repository containing 4,500 subsequent commits across 15 active branches. A simple git reset won’t work because it’s already pushed.

    • Walk through the exact remediation steps to purge the secret from history, handle shared remote state, and prevent future occurrences.

    • What interviewers look for:

      1. Immediate revocation/rotation of the compromised credentials (the human fix comes first).

      2. Using git filter-repo (the modern successor to git filter-branch) or BFG Repo-Cleaner to strip the file from all historical object trees and refs.

      3. Force pushing the cleaned history (git push origin --force --all) and coordinating with the team to drop and re-clone (since local histories are poisoned).

      4. Implementing pre-commit hooks (e.g., git-secrets or Trufflehog) in the CI/CD pipeline to block future leakage.

  • Question (Detached HEAD & Reflog Rescue): A junior developer ran git reset --hard origin/main while working on an uncommitted, unpushed hotfix feature branch with 3 days of local work, completely wiping out their local working directory and branch pointer.

    • How can you rescue these “lost” commits using Git’s underlying mechanics?

    • What interviewers look for: Immediate invocation of git reflog (Reference Log). Candidates should explain that Git doesn’t immediately delete unreferenced object data; it keeps dangling commits in the reflog for a default expiration period (90 days). The developer can inspect the reflog hash history and instantly restore their work via git checkout -b rescue-branch <SHA>.

3. Workflow Architecture Visualization

Code snippet

gitGraph
    commit id: "Init Core"
    branch feature/payment-gateway
    checkout feature/payment-gateway
    commit id: "Add Stripe SDK"
    commit id: "Implement Webhook"
    checkout main
    commit id: "Hotfix Auth"
    checkout feature/payment-gateway
    merge main id: "Merge main into feature"
    commit id: "Finalize Gateway"
    checkout main
    merge feature/payment-gateway tag: "v2.0-Release" type: HIGHLIGHT
  • Question on the diagram above: In an enterprise release pipeline, what happens to the object database and parent pointer arrays when Merge main into feature is executed versus a git rebase main? Which approach alters SHA hashes upstream, and why does that matter for distributed teams?

    • What interviewers look for: Merging creates a new merge commit object with two parent pointers (main and feature), preserving historical timeline accuracy without altering existing hashes. Rebasing rewrites history by generating entirely new commit SHAs for every commit on the feature branch, which breaks downstream tracking for any developer who already pulled the old branch state.

Answers

  • Step 1: Content-Addressable Hashing (git add)

    When you modify a file and run git add, Git reads the file contents, prepends a header (blob <size>\0), computes a SHA-1 hash, and compresses the stream using zlib. It writes this file into .git/objects/ using the first two characters of the hash as a directory prefix and the remaining 38 characters as the filename (blob object).

  • Step 2: Updating the Index Manifest (.git/index)

    Concurrently, Git updates the binary staging manifest (.git/index). It records the file path, stage mode, file modification timestamps (mtime/ctime), inode data, and the newly calculated 20-byte blob SHA-1 hash. This file acts as the transactional snapshot for your next commit.

  • Step 3: Building Tree Structures (git commit)

    When you execute git commit, Git crawls the .git/index and constructs a hierarchical network of Tree objects. Each tree object maps directory names and filenames to their corresponding blob hashes or sub-tree hashes.

  • Step 4: Writing the Commit Object & Advancing Refs

    Finally, Git writes a Commit object containing the root tree hash, parent commit hash(es), author/committer metadata, and the commit message. It then updates the current branch pointer file (e.g., .git/refs/heads/main) to point directly to this new commit SHA.

  • Index Optimization & mtime Caching: Git avoids reading file contents or traversing disk trees during git status by comparing the file modification time (mtime), size, and inode metadata stored in the binary .git/index against the filesystem stats. If mtime hasn’t changed, Git trusts the cache.

  • Untracked Cache (core.untrackedCache): In massive repos, scanning the filesystem for untracked files is expensive. Enabling the untracked cache records directory mtimes in the index, allowing Git to skip scanning entire directory subtrees if the directory modification timestamp hasn’t changed.

  • Filesystem Monitor (core.fsmonitor): Integrates with OS-level file system hooks (like FSEvents on macOS or inotify on Linux) to track background modifications, eliminating the need for Git to poll the entire file tree.

  • Sparse-Checkout & Cone Mode: For monorepos, developers rarely need all 500,000 files. Sparse-checkout allows cloning or checking out only specific subdirectories (e.g., a single microservice and shared SDKs), drastically shrinking the working directory footprint and index size.

  • Commit-graph File: Speeds up reachability queries (like traversal during git log or merge-base calculations) by storing commit parent topologies in a separate binary file rather than parsing raw commit objects across disk blocks.

  • The Trade-off: Blindly enforcing git rebase creates a clean linear history, but rewriting history on shared branches causes downstream breakage. Traditional merges preserve exact timelines with merge commits, but can introduce integration bugs that unit tests miss when evaluated in isolation.

  • Architectural Solution:

    1. Enforce Branch Protection: Restrict direct pushes to main. Require all changes to go through Pull Requests.

    2. Automated Merge Queues (e.g., GitHub Merge Queues / GitLab Merge Trains): Instead of merging feature branches directly when approved, queue them. The system automatically creates a temporary prospective merge commit against the latest main, spins up an isolated CI pipeline to run full integration tests on that merged state, and only pushes to main if the build succeeds.

    3. Squash and Merge Policy: Configure GitHub/GitLab to enforce “Squash and Merge” for feature branches into main. This maintains a pristine, single-commit history per feature while allowing developers to work with rich non-linear commit histories locally without messy rebase conflicts.

  • Step 1: Immediate Credential Revocation (Human Fix First): Code scrubbing tools only remove data from git history; scrapers or fork mirrors may have already captured it. Immediately rotate the database credentials and revoke the compromised SSH key in infrastructure settings.

  • Step 2: Purge History via git filter-repo: Avoid the deprecated git filter-branch. Use the modern, high-performance git filter-repo tool (built on Python) to strip the file entirely from all commit objects, trees, and reflogs across the entire repository history:

    Bash

    git filter-repo --path path/to/sensitive/file.pem --invert-path
    
  • Step 3: Force Push Cleaned State: Force push all branches and tags to the remote server to overwrite the polluted history:

    Bash

    git push origin --force --all
    git push origin --force --tags
    
  • Step 4: Coordinate Team Remediation: Because history was rewritten, local clones held by other engineers are poisoned. They cannot simply pull; they must discard local changes and fresh clone or hard reset against the cleaned remote (git fetch origin && git reset --hard origin/main).

  • Step 5: Preventive Guardrails: Install client-side pre-commit hooks or server-side secret scanning tools (such as Trufflehog or GitHub Secret Scanning) to intercept secrets before they touch the remote object database.

  • The Mechanism (git reflog): When commits or branch pointers are abandoned, overwritten, or reset, Git does not immediately delete their objects from disk. Instead, the local reference log (.git/logs/HEAD) records every single state transition of HEAD for a default retention period of 90 days.

  • Recovery Steps:

    1. Run git reflog to output a chronological list of recent HEAD positions with their SHAs:

      Bash

      git reflog
      
    2. Identify the SHA of the commit right before the destructive reset --hard command was executed (e.g., a1b2c3d).

    3. Recreate and switch to a rescue branch pointing directly to that lost commit:

      Bash

      git checkout -b hotfix-rescue a1b2c3d
      
  • Caveat on Uncommitted Changes: If changes were completely untracked (never staged via git add), Git’s reflog cannot recover them because no blob objects were written to .git/objects/. Reflog only tracks reference movements (commits and branch shifts).

Code snippet

gitGraph
    commit id: "C1"
    branch feature
    checkout feature
    commit id: "C2"
    checkout main
    commit id: "C3"
    checkout feature
    merge main id: "Merge Commit"
    checkout main
    merge feature id: "Fast-Forward / Integration"
  • Merge Behavior: Creates a new commit object with two parent pointers pointing to both branches. Existing commit hashes (C1, C2, C3) remain completely untouched and immutable. History accurately reflects parallel development timelines.

  • Rebase Behavior: Reads the patch differences of your feature branch, temporarily stores them, resets your branch tip to the latest target commit, and replays each patch one by one. This process generates entirely new commit SHAs for every rebased commit because the parent timestamps and parent pointers have changed.

  • Why it matters for distributed teams: Rewriting history via rebase changes commit hashes upstream. If other developers have already pulled those commits, their local histories will diverge, leading to severe sync conflicts during subsequent pushes.

4. Fetch vs Merge

  • Git merge and Git fetch→Git fetch retrieves new commits from a remote repository without modifying your working directory, while git merge incorporates changes from another branch into the current branch, updating the working tree.

  • Git fetch and git merge are complementary steps in the typical workflow of integrating remote work.

  • Git Fetch

    • Git contacts the remote repository, downloads any new commits and updates the remote‑tracking branches (e.g., origin/main) in your local .git database. Nothing in your current checkout is changed: your working directory, index, and HEAD stay exactly as they were, so you can inspect the new commits safely before deciding what to do with them.
  • Git merge

    • It takes commits from another branch (often a remote‑tracking branch that you just fetched) and creates a new merge commit on your current branch, replaying the changes into your working tree. This updates the files on disk, may produce conflicts that you must resolve, and moves HEAD forward. In practice you usually fetch first to bring the remote history up‑to‑date, then merge (or rebase) the fetched branch into your own. This separation lets you review incoming changes, test them on a separate branch, or choose a different integration strategy without unintentionally altering your current work.

0 items under this folder.