← Home🗺 Mind Map☕ Ko-fi💳 Razorpay
// Git Guide · Version Control & GitOps

Git Complete Guide 2026: Branching, Rebase, Reflog, GitOps & Expert Interview Q&A

📅 Updated May 2026⏰ 20 min read 🏷 Git · GitOps · DevOps · Version Control
👨‍💻
Dhanush R — Senior DevOps Engineer
4.5+ years using Git daily in production DevOps workflows — multi-team monorepos, GitOps pipelines with ArgoCD, branch protection enforcement, and recovering production repositories from accidental force pushes.
// Table of Contents
  1. Git Internals — Objects, Refs, and the DAG
  2. Branching Strategies for DevOps Teams
  3. Merge vs Rebase — When to Use Each
  4. Advanced Git Commands
  5. Git Reflog — Recovering Lost Commits
  6. Git Hooks for DevOps Automation
  7. GitOps — Git as Infrastructure Control Plane
  8. GitHub/GitLab Workflow Best Practices
  9. 12 Git Interview Questions with Expert Answers

Git is not just a version control tool — it is the foundation of every modern DevOps workflow. Every CI/CD pipeline is triggered by a Git event. Every GitOps deployment is driven by a Git commit. Every infrastructure change is tracked in Git. I use Git daily in production environments: maintaining branch protection rules, reviewing and approving infrastructure PRs, recovering commits after accidental force pushes, and designing monorepo workflows for platform teams. This guide covers Git at the depth required for senior DevOps and SRE interviews.

Git Internals — Objects, Refs, and the DAG

Understanding Git at the object level removes all mystery from how it works. Git is fundamentally a content-addressable key-value store. Every piece of data is stored as an object identified by its SHA-1 hash. There are four object types:

# Inspect Git objects directly git cat-file -t HEAD # object type of HEAD (commit) git cat-file -p HEAD # content of HEAD commit object git cat-file -p HEAD^{tree} # tree object (root directory listing) git ls-tree HEAD # same but formatted git ls-tree -r HEAD # recursive - all files in repo # Find the blob SHA for a specific file git hash-object src/main.go # SHA without storing git hash-object -w src/main.go # SHA and store in object DB # Refs are just files containing a SHA cat .git/HEAD # ref: refs/heads/main cat .git/refs/heads/main # SHA of latest commit on main git show-ref # all refs and their SHAs

Branching Strategies for DevOps Teams

The branching strategy you choose directly impacts your CI/CD velocity, merge conflict frequency, and deployment safety. The three major strategies in use today:

Trunk-Based Development (Recommended)

All developers commit to a single main branch (the "trunk"), with feature branches lasting at most 1-2 days. Feature flags hide incomplete features from users. This strategy enables true CI (every commit integrates continuously), minimises merge conflicts (small, frequent merges), and aligns with GitOps (the trunk is always the source of truth for production). Used by Google, Meta, and Netflix.

Gitflow

Long-lived branches: main (production-ready), develop (integration), feature/*, release/*, hotfix/*. Provides clear structure for scheduled releases but creates large, painful merges and slows down CI. Best suited for software with explicit version releases (embedded systems, packaged software). Not recommended for web services deploying continuously.

GitHub Flow

Simplified: main is always deployable. Feature branches branch from main, get a PR reviewed, merge to main, deploy immediately. Simpler than Gitflow, better than Gitflow for web services, but lacking the short-lived branch discipline of true trunk-based development.

Merge vs Rebase — When to Use Each

This is the most common Git question in DevOps interviews. The choice between merge and rebase affects commit history readability and the ability to bisect bugs.

# Merge: creates a merge commit preserving all branch history git checkout main git merge feature/api-v2 # creates a merge commit M # History: A---B---C---M (main) # \---D---E---/ (feature, preserved as-is) # Rebase: replays feature commits on top of main (linear history) git checkout feature/api-v2 git rebase main # replays D, E as D', E' on top of C git checkout main git merge feature/api-v2 # fast-forward, no merge commit # History: A---B---C---D'---E' (linear, clean) # Interactive rebase: rewrite history before merging (squash, fixup, edit) git rebase -i HEAD~4 # interactive rebase of last 4 commits # pick a1b2c3 Add API endpoint # squash d4e5f6 Fix typo in API endpoint # squash 789abc WIP: API endpoint # pick def123 Add tests for API endpoint # GOLDEN RULE: Never rebase commits that have been pushed to a shared branch # Rebasing rewrites SHAs. If others have based work on those commits, they diverge.
When to use merge: Merging feature branches into main in a team environment (merge commits preserve the context of what was developed together). When the branch history is meaningful and should be preserved. When using a merge-based PR workflow (GitHub PRs default to merge commits).

When to use rebase: Updating a local feature branch with upstream changes before opening a PR (git pull --rebase). Cleaning up WIP commits before sharing with the team (interactive rebase to squash). When you want a clean, linear history that is easy to read and git bisect.

Advanced Git Commands

# cherry-pick: apply a specific commit to the current branch git cherry-pick a1b2c3d4 # apply commit by SHA git cherry-pick main~3 # apply the commit 3 before main's tip git cherry-pick v1.0..v1.2 # apply a range of commits # Use case: apply a hotfix commit from main to a release branch # stash: save uncommitted changes temporarily git stash # stash tracked file changes git stash push -u -m "WIP: API refactor" # include untracked, add message git stash list git stash pop # apply latest stash and remove it git stash apply stash@{2} # apply specific stash, keep in list git stash drop stash@{0} # remove specific stash # bisect: binary search for the commit that introduced a bug git bisect start git bisect bad # current commit is broken git bisect good v1.4.0 # last known good state # Git checks out midpoint commit. Test it, then: git bisect good # or: git bisect bad # Repeat until git identifies the first bad commit git bisect reset # return to original HEAD # Automated bisect with a test script (exit 0=good, non-zero=bad): git bisect run npm test # blame: find who last changed each line of a file git blame -L 45,60 src/api.go # show only lines 45-60 git blame --since=3.weeks src/api.go git log -S "function authenticate" --oneline # pickaxe: find commits touching this string # worktree: check out multiple branches simultaneously in separate directories git worktree add ../feature-branch feature/api-v2 git worktree list git worktree remove ../feature-branch

Git Reflog — Recovering Lost Commits

The reflog is Git's local safety net. Every time HEAD moves (commit, checkout, rebase, reset, merge), Git records where HEAD was. The reflog is local and never pushed — it is your personal undo history for the last 90 days (configurable).

# Recovering from an accidental git reset --hard git reflog # see all recent HEAD positions # Output: a1b2c3d HEAD@{0}: reset: moving to HEAD~3 # d4e5f6g HEAD@{1}: commit: Add authentication middleware # h7i8j9k HEAD@{2}: commit: Add user model git checkout HEAD@{1} # go back to before the reset git checkout -b recovery-branch # save it as a branch # Recover a deleted branch git reflog | grep "checkout: moving from" # find the last commit on deleted branch git checkout -b recovered-branch a1b2c3d # recreate branch at that SHA # Recover a dropped stash (stash drop / stash clear) git fsck --lost-found | grep commit # find dangling objects git show SHA_OF_STASH # inspect it git stash apply SHA_OF_STASH # restore it

Git Hooks for DevOps Automation

Git hooks are scripts that run automatically at specific points in the Git workflow. They live in .git/hooks/ and are not committed to the repository by default. Use a tool like Husky (Node.js) or pre-commit (Python) to share hooks across the team via the repository itself.

# .git/hooks/pre-commit — run before committing (exit non-zero to abort) #!/bin/bash set -e echo "Running pre-commit checks..." go fmt ./... # format Go code go vet ./... # static analysis golangci-lint run # linting go test ./... -short # fast unit tests only # Scan for secrets before committing gitleaks detect --source . --no-git echo "Pre-commit checks passed" # .git/hooks/commit-msg — enforce conventional commits format #!/bin/bash COMMIT_MSG=$(cat "$1") PATTERN="^(feat|fix|docs|style|refactor|test|chore|ci|build)(\(.+\))?: .{1,72}" if ! echo "$COMMIT_MSG" | grep -qE "$PATTERN"; then echo "ERROR: Commit message must follow Conventional Commits format" echo "Example: feat(api): add OAuth2 authentication endpoint" exit 1 fi # .git/hooks/pre-push — run full test suite before pushing to main #!/bin/bash PROTECTED_BRANCH="main" CURRENT_BRANCH=$(git symbolic-ref HEAD | sed 's!refs/heads/!!') if [ "$CURRENT_BRANCH" = "$PROTECTED_BRANCH" ]; then echo "Running full test suite before pushing to main..." make test-all || exit 1 fi

GitOps — Git as Infrastructure Control Plane

GitOps is the practice of using Git as the single source of truth for both application code and infrastructure desired state. Every Kubernetes manifest, Terraform configuration, and Helm values file is stored in Git. Changes are made through pull requests with review and approval. A GitOps operator (ArgoCD, Flux) continuously reconciles the live environment to match the Git state. This gives: complete audit trail of every infrastructure change, instant rollback (git revert), enforced review process, and disaster recovery from Git.

# GitOps repository structure (mono-repo approach) gitops-repo/ apps/ # ArgoCD Application definitions production/ api-service/ deployment.yaml service.yaml ingress.yaml kustomization.yaml staging/ api-service/ kustomization.yaml # patches prod manifests for staging infrastructure/ # Terraform configs eks-cluster/ vpc/ rds/ charts/ # Helm chart values per environment api-service/ values-production.yaml values-staging.yaml # CI pipeline updates image tag in GitOps repo (triggers ArgoCD sync) # cd to gitops-repo, update image, commit, push sed -i "s|image:.*|image: registry/api:${GIT_SHA}|" apps/production/api-service/deployment.yaml git add -A git commit -m "ci: update api image to ${GIT_SHA} [skip ci]" git push

GitHub/GitLab Workflow Best Practices

12 Git Interview Questions with Expert Answers

Q1: What is the difference between git merge and git rebase?
Merge combines two branches by creating a new merge commit that has two parents, preserving the full history of both branches exactly as they happened. The resulting history is accurate but non-linear — when many feature branches are merged, the history becomes a complex graph of merge commits. Rebase re-applies the commits from one branch on top of another, rewriting their SHAs and creating a linear history as if the feature was developed after the base branch commits. The golden rule: never rebase commits that have already been pushed to a shared branch. Rewriting SHAs breaks the history for anyone who has based work on those commits. In practice: use rebase to update local feature branches (git pull --rebase) and clean up WIP commits before a PR. Use merge for integrating approved PRs into main in a team environment.
Q2: How do you recover from git reset --hard?
Git's reflog records every position HEAD has been at for the last 90 days (local only, never pushed). Run git reflog to see all recent HEAD positions with their SHA and a description of what moved HEAD there. Find the entry just before the reset (e.g., HEAD@{1}) and check it out: git checkout HEAD@{1} or git checkout <SHA>. Create a new branch to preserve it: git checkout -b recovery-branch. Then merge or cherry-pick the recovered commits back into your main branch. The reflog is your safety net for almost every "I destroyed my work" scenario in Git — accidental reset, deleted branch, dropped stash. The only case it cannot help with is if the reflog entries have expired (90 days) or if you ran git gc aggressively. This is why git reset --hard should be treated as a dangerous operation, especially on shared branches.
Q3: What is a detached HEAD state and how does it happen?
In normal operation, HEAD is a symbolic reference pointing to a branch name (e.g., refs/heads/main), which in turn points to a commit. Detached HEAD means HEAD points directly to a commit SHA rather than to a branch. It happens when you: git checkout <commit-sha>, git checkout v1.0.0 (a tag), or git bisect is running. In detached HEAD, any commits you make are not on any branch — they are dangling commits that will be garbage collected eventually. To save work done in detached HEAD: git checkout -b my-new-branch creates a branch at the current detached HEAD position, saving all commits you made. To return to a branch: git checkout main. If you accidentally committed in detached HEAD and switched away, use git reflog to find the SHA and recover it.
Q4: Explain git cherry-pick and when you would use it in production.
Cherry-pick applies the changes introduced by a specific commit to the current branch, creating a new commit with the same changes but a different SHA (different parent, different timestamp). Production use cases: (1) Hotfix propagation — a critical security fix is committed to main and needs to be applied to a release branch without merging all of main's recent development commits. git cherry-pick <fix-sha> applies just the fix to the release branch. (2) Partial feature extraction — a large feature branch has one commit that is needed urgently while the rest of the feature is still in review. (3) Reverting a revert — if a feature was reverted from main but should be re-applied after a blocking issue was fixed. Cherry-pick is not a substitute for proper branching strategy — if you are cherry-picking frequently between branches, it is a signal that the branching model needs review.
Q5: What does git fetch vs git pull do?
Git fetch downloads all changes (commits, branches, tags) from the remote repository and updates the remote-tracking branches (origin/main, origin/feature-x) in your local repository. It never touches your working directory or local branches — it is always safe to run. Git pull is effectively git fetch followed by git merge (or git rebase if configured with pull.rebase=true). It fetches and then integrates the fetched changes into your current branch. The DevOps best practice: configure git config --global pull.rebase true so git pull does git fetch + git rebase instead of creating a merge commit for every upstream sync. This keeps your local branch history clean. Never use git pull on shared branches in a CI/CD context — always use git fetch and then explicitly merge or rebase.
Q6: How do you find a bug using git bisect?
Git bisect performs a binary search through commit history to find the exact commit that introduced a bug. Start with git bisect start, mark the current broken state as bad (git bisect bad), and mark a known-good commit or tag (git bisect good v1.4.0). Git checks out the midpoint commit between good and bad. You test it and run git bisect good or git bisect bad based on the result. Git halves the search space each time. After 10 iterations, you have identified 1 commit out of 1,024 candidates. For automated bisect, provide a test script: git bisect run npm test — Git automatically marks commits good (exit 0) or bad (non-zero exit). When complete, Git prints the first bad commit. Run git bisect reset to return to the original state. This technique finds bugs in large codebases in minutes that manual investigation would take hours.
Q7: What is the difference between git reset and git revert?
Git reset moves the branch pointer backward in history to an earlier commit, effectively removing commits from the branch tip. Three modes: --soft (moves branch pointer, keeps changes staged), --mixed (default: moves pointer, unstages changes, keeps in working directory), --hard (moves pointer, discards all changes permanently). Git reset rewrites history — safe for local, unpushed commits; dangerous for shared branches because it creates a divergence that requires force push. Git revert creates a new commit that undoes the changes of a previous commit, without removing any history. It is the safe way to undo changes on shared branches: the original commit remains in history (for audit trail), and the revert commit shows what was undone and why. Rule: use git reset for local history cleanup. Use git revert for shared/published commits that need to be undone.
Q8: How do you squash commits and why is it valuable?
Squashing combines multiple commits into a single commit. Use interactive rebase: git rebase -i HEAD~5 to see the last 5 commits. Change "pick" to "squash" (or "s") for commits to fold into the one above them. Git opens an editor to combine the commit messages. Squashing is valuable because: (1) Development history contains noise — "WIP", "fix typo", "address PR comment", "forgot to add file". These are useful during development but pollute the main branch history with meaningless entries. A clean history with one meaningful commit per logical change makes git log readable and git bisect effective. (2) Squash merge (GitHub's merge strategy) automatically squashes all commits in a PR into one commit on main, named after the PR title — producing a clean linear history without any manual rebasing required from developers. This is the recommended strategy for most teams using GitHub Flow.
Q9: How do you handle large binary files in Git?
Git is not designed for large binary files. Each binary file change is stored as a complete new object (no delta compression for binaries), causing the repository to grow rapidly and clone/fetch times to increase. Git LFS (Large File Storage) solves this by storing large files outside the Git repository (in a separate LFS server or cloud storage) and replacing them with small pointer files in the Git object database. Configure: git lfs install, git lfs track "*.psd" "*.zip" "model/*.bin", and commit the resulting .gitattributes file. When anyone clones or pulls, Git LFS automatically downloads the large files as needed. For DevOps artefacts (large Docker images, compiled binaries, ML models), the better practice is to never commit them to Git at all — store them in S3, ECR, or an artefact repository (Nexus, JFrog Artifactory) and reference them by URL or hash in your pipeline configuration.
Q10: What is a Git hook and how do you share hooks across a team?
Git hooks are executable scripts in .git/hooks/ that run automatically at specific workflow points. Pre-commit: runs before a commit is created (can abort the commit by exiting non-zero). Commit-msg: validates or modifies the commit message. Pre-push: runs before a push to a remote. Post-receive: runs on the server after receiving a push (used for automatic deployment triggers). The challenge: .git/hooks/ is not part of the repository and is not pushed. To share hooks across a team: use the pre-commit framework (pip install pre-commit) with a .pre-commit-config.yaml committed to the repository. Run pre-commit install once after cloning to install the hooks locally. Alternatively, use Husky for Node.js projects (npm install husky) which automatically installs hooks when developers run npm install. Hooks should enforce: code formatting, linting, commit message convention (Conventional Commits), secret scanning (Gitleaks), and fast unit tests.
Q11: How do you manage monorepos with Git for a large DevOps team?
Monorepos (all services in one repository) have advantages (atomic cross-service commits, single source of truth, easier dependency management) but require specific tooling at scale. Key patterns: (1) Sparse checkout — git sparse-checkout init lets developers check out only the directories they need, reducing local clone size for large repos. (2) CODEOWNERS — automatic review assignment by directory ensures the right team reviews each service's changes. (3) Path-filtered CI — CI pipelines run only the tests and builds for the services that changed. GitHub Actions: on.push.paths filter. GitLab CI: changes keyword. (4) Merge queues — prevent parallel merges from breaking main by queuing and testing PRs sequentially or in batches before merging. (5) Nx, Bazel, or Turborepo for monorepo build tooling — they provide affected-build analysis (only build/test what changed) and remote caching for drastically faster CI.
Q12: What is GitOps and how does it differ from traditional CI/CD?
GitOps uses Git as the single source of truth for both application code and desired infrastructure/Kubernetes state. In traditional CI/CD, the pipeline runs kubectl apply or helm upgrade imperatively — the pipeline has cluster credentials and makes changes directly. In GitOps, the CI pipeline only updates image tags in a manifest repository (no cluster credentials in CI). A GitOps operator (ArgoCD, Flux) running inside the cluster continuously watches the manifest repository and reconciles the cluster to match Git. Key differences: (1) Security — cluster credentials never leave the cluster. (2) Audit trail — every infrastructure change is a Git commit with an author, timestamp, and PR review. (3) Self-healing — any manual kubectl change is automatically reverted by the operator to match Git. (4) Disaster recovery — any cluster can be rebuilt from Git state alone. (5) Rollback — git revert triggers automatic rollback through the operator. GitOps is the recommended approach for Kubernetes deployments at any scale above a single team.

🌿 Explore Git on the Interactive Mind Map

See how Git connects to CI/CD pipelines, ArgoCD GitOps, GitHub Actions, and the full DevOps toolchain.

Open Interactive Mind Map ⚙ CI/CD Guide →
// More Guides
📖 DevOps⎈ Kubernetes🐳 Docker☁ AWS📀 Terraform⚙ CI/CD📊 Prometheus🐧 Linux🌿 Git
Advertisement
☕ Support Master DevOps

All guides are 100% free. If this helped you learn or land a job, your support keeps the project alive.

☕ Ko-fi💳 Razorpay
🌿
Written by Dhanush R
Senior DevOps Engineer · 4.5+ Years · Bengaluru, India

4.5+ years using Git daily in production DevOps workflows — multi-team monorepos, GitOps pipelines with ArgoCD, and recovering production repositories from accidental force pushes.

📷 Instagram▶ YouTube💼 LinkedIn
🌙