Branching, history and collaboration workflow.
42 items · all topics
Detached HEAD, and how work seems to vanish
HEAD normally points at a branch, and the branch points at a commit. Check out a raw SHA and HEAD points straight at the commit instead, so anything you commit there is reachable from nothing, and switching branches appears to delete it.
Gitflow, watched one branch at a time
Gitflow keeps two branches alive forever (main for what is live, develop for what is next) and adds three short-lived branch types around them: feature, release and hotfix. Every branch has one place it starts from and one or two places it must merge back into. Get those arrows wrong and fixes go missing.
Merge vs rebase, watched one commit at a time
Merge joins two histories with a new commit and keeps the fork visible forever. Rebase replays your commits onto a new base and leaves a straight line, but every replayed commit is a brand new object with a new SHA.
Real Git situations: the ones that actually happen at work
Git problems on a real team are rarely about the command: they are about who else has already seen the history, and whether Git's idea of 'clean' matches what is actually running. Twenty-five situations, grouped by what they have in common, with the fix and the part everyone gets wrong.
Reset vs revert: two ways to undo, one of them destructive
Reset moves the branch pointer backwards and leaves the commit orphaned. Revert adds a new commit that applies the opposite change. One rewrites history and needs a force push; the other only ever grows it and is safe on a shared branch.
You accidentally committed a .env file containing API keys. What do you do?
Rotate the credentials first. That's the only step that actually makes you safe, since the secret is already in every clone, fork and CI cache that pulled before you noticed. Cleaning up Git history is a second, separate job that comes after.
Your branch is 5 commits behind main and you have uncommitted changes. How do you update safely?
Save the uncommitted work first with a stash or a WIP commit, then fetch and rebase onto origin/main, then bring it back. The real point is knowing what git pull would do to your branch before you run it.
A developer says they pulled the latest code but their branch doesn't match the remote. How do you investigate?
It's almost always a tracking problem, not corruption. They pulled a different branch than they think, or their branch tracks something other than what they assume. `git branch -vv` and `git log HEAD..origin/main` usually answer it in two commands.
Code works on the developer's machine but fails in Jenkins. How could Git be involved?
Stop guessing about the code and compare commit SHAs first. CI often builds a different commit than the developer tested: a merge commit, a stale workspace, a shallow clone, missing submodules, or a file that's gitignored locally but needed at build time. Only once the SHAs match is it worth looking at the code.
You made a commit but forgot to include one file, and it isn't pushed yet. What do you do?
Stage the file and run `git commit --amend`. Amend doesn't edit the previous commit, it replaces it with a new one that has a new SHA, which is free while the commit is still local and a problem the moment someone else has it.
A developer started a feature branch from an outdated main. How would you fix it?
Fetch, then rebase the branch onto origin/main so the work replays on current code. Rebase is right while the branch is private; if it's been pushed and others use it, merge main in instead so you don't rewrite shared SHAs.
A Git repository has become huge and cloning takes 20 minutes. What would you investigate?
Measure before you guess. Find the biggest objects in history, because size usually comes from binaries and build artifacts committed long ago. Deleting them today does nothing: the old blobs stay in history until you rewrite it or route developers around downloading them in the first place.
What is the difference between git fetch and git pull?
Fetch downloads remote commits and updates your remote-tracking branches, but never touches your working tree. Pull is fetch plus merge or rebase, so it changes your branch. Fetch is the safer move when you're debugging or scripting.
The deploy says it shipped main, but production doesn't have the latest commit. How do you debug it?
Walk the chain, commit, CI checkout, build, artifact, deploy, running pod, and compare the SHA at each step. main is a moving pointer, so the usual cause is that something in the chain resolved it at a different moment, or shipped a cached artifact instead of a fresh one.
Your CI uses git clone --depth=1 and a deploy script that needs history suddenly fails. Why?
A shallow clone downloads the current tree and exactly one commit, no parents, usually no tags. Anything that reads history breaks: `git describe`, changelogs, `git diff HEAD~10`, commit counts, `merge-base`. Fetch the depth you actually need instead of defaulting to depth 1 everywhere.
A developer wants to git reset --hard and force-push a shared branch to undo a bad commit. Do you allow it?
On a shared branch, no. Use git revert, which undoes the change with a new commit and leaves history intact. Reset plus force-push rewrites history that other people, CI and deployment records already depend on.
A bad feature was merged into production. How would you undo it?
Roll back the running deployment first, that's faster than any Git fix, then fix Git properly. Reverting a merge needs git revert -m 1 <merge-commit>, and the catch nobody mentions upfront is that you have to revert the revert later or the feature will never merge back in.
A production bug was introduced somewhere in the last 50 commits. How do you find the exact commit?
`git bisect` does a binary search over the range, so 50 commits take about 6 tests instead of 50. The hard part isn't the commands, it's having a reliable test that says good or bad, which is what lets you automate the whole thing with `git bisect run`.
You need one bug fix from a branch that contains 20 other commits. What do you do?
Cherry-pick copies just that one commit onto your branch. It's the right tool for hotfixes and release backports, but it duplicates the change, so use it deliberately and keep fixes isolated in their own commits in the first place so they're easy to lift out later.
Two engineers changed the same Terraform file and Git reports a conflict. How do you resolve it?
Resolve the text conflict, then prove the result is actually correct with terraform validate and a plan. A clean Git merge only means the file parses in a human's head, it says nothing about whether the merged config destroys a database.
Your CI pipeline runs twice for every pull request. How do you investigate?
Almost always two triggers firing on one action, usually push and pull_request both matching the same branch. Read the event that started each run, then make the trigger config deliberate instead of deleting jobs until the noise stops.
A developer says their commit has disappeared. How do you investigate and get it back?
Commits are rarely deleted, they usually just lose their branch reference. git reflog records every move of HEAD locally, so a bad reset, rebase or checkout is almost always recoverable, and git fsck --lost-found catches most of the rest.
Your deployment system needs to know exactly which Git commit is running in production. How do you design that?
Stamp the commit SHA into the artifact at build time and expose it at runtime. Build once per commit, tag and deploy by digest, and serve a /version endpoint, so the answer to "what's live?" comes from the running process itself, not from a pipeline log someone has to go dig up.
A deploy of commit A is still running when commit B lands on main. What can go wrong?
If the pipeline resolves `main` at each step instead of pinning one commit early, later stages can pick up B while earlier ones tested A. You get mixed versions, out-of-order deploys, and a rollback target that no longer means anything. The fix is pinning the SHA once plus serializing production deploys.
A secret was committed six months ago and exists in hundreds of commits. What do you do?
Treat it as a security incident, not a Git cleanup task. Rotate first, then work out the blast radius, then decide honestly whether rewriting history is worth the cost. The rewrite is the most visible part of the response and the least important one.
Your 5 GB monorepo has code, binaries, Terraform and Helm charts, and clones are slow. What do you do?
Measure what the 5 GB actually is: big blobs, deep history, and a wide tree are three different problems with three different fixes. Work through them in order of cost, artifacts out of Git, binaries into LFS, partial clone and sparse checkout for developers, and treat splitting the repo as the last resort rather than the first idea.
Your organization wants signed commits for production code. How would you implement it?
git config user.email is free text, not identity, so signing is what turns authorship into a cryptographic claim. Roll out SSH signing, enforce it with branch protection and a trusted-key list, and plan for the parts that actually break rollouts: bots, squash merges, and key rotation.
Someone force-pushed a branch and deleted important commits. How do you recover them?
The commits almost certainly still exist, they just have nothing pointing at them anymore. Find the old SHA from any clone, CI workspace, PR page or provider event log, then create a branch on it. Act the same day, because garbage collection is the real deadline.
Two CI pipelines try to create the same Git release tag at the same time. What happens?
The remote accepts one push and rejects the other, because ref updates are atomic. The danger isn't the collision, it's a pipeline that "fixes" the rejection with --force, which silently moves an existing release tag onto a different commit. Serialize releases and protect tags server-side.
A critical production bug needs an emergency fix, but your PR process takes two hours. What do you do?
Mitigate first: rollback or a feature flag beats writing code under pressure. If code is genuinely needed, use a documented hotfix lane: branch from the production tag, minimal fix, fast tests, one reviewer, deploy, then merge back. Emergency means a faster controlled process, not no process.
You use GitOps and someone changes Kubernetes manually. Git still has the old config. What happens?
You get configuration drift: Git says one thing, the cluster says another. What happens next depends on whether the controller self-heals or just reports it. Either way the manual change is temporary, and the fix is to put the intended change into Git, not to argue with the controller.
Argo CD keeps reverting your emergency production change. Why, and what should you do?
Self-heal is doing exactly what it was configured to do: pulling the cluster back to what Git says. During an incident, mitigate through something the controller doesn't manage, then get the real change into Git fast rather than fighting reconciliation head on.
Someone moved a production Git tag to a different commit. Why is that dangerous?
A release tag is a promise that a version name means one exact, unchanging set of code. Moving it breaks that promise everywhere at once: rollbacks, audits, incident timelines, and any pipeline that deploys by tag now point somewhere different from what people believe, and some clones won't even notice the change.
Your repository has hundreds of branches, many already merged into main. What would you change?
Deleting merged branches is easy and worth automating, but it's the symptom. The real disease is branches living long enough to diverge, which shows up as painful merges and untested integration risk. Fix PR size and review speed first, and watch for squash-merged branches that `--merged` can't even see.
Your organization wants production deployments to be reproducible six months later. How does Git help?
Git pins the source exactly and that's all it pins. Real reproducibility also needs locked dependencies, versioned infrastructure, immutable artifacts, and a stored record linking a deployment to its digest, plus keeping the actual artifact, because rebuilding it later is rarely byte-identical.
Deployment says SUCCESS but production is running an older commit. You have 10 minutes. What do you check?
Ask the running process what it actually is, then walk backwards through the chain until the SHA stops matching. A green pipeline only proves each step exited zero, not that anything actually changed in production, and "unchanged" is a success message that means nothing happened at all.
Git is the source of truth, but production differs from Git. How do you prove where the drift happened?
Compare state at each stage, Git, rendered manifests, what was applied, and the live cluster, and use Kubernetes' own metadata to name the culprit. managedFields records which controller last wrote each field, which usually answers the question outright without any guessing.
Someone force-pushed main at 2 AM. How do you investigate?
Preserve evidence first, then answer four questions: what was main before, what is it now, who did it, and was anything deployed from the rewritten history. Treat it as potentially malicious until the audit log says otherwise, because a force-push at 2 AM is an unusual enough event to earn that default.
An attacker steals a developer's Git credentials and pushes malicious code. How do you defend against this?
Assume one credential will eventually be stolen and design so that alone isn't enough to ship code. Layer identity, branch protection, review, signing, pipeline isolation and detection, and remember the attacker's real target is usually the CI workflow, not the application code itself.
Nobody knows which Git branch or tag corresponds to production. How would you fix the release process?
Establish one unbroken chain of identity from commit to running process, then make the pipeline the only way anything reaches production. Start by discovering what's actually deployed today, you can't design a release process around a system you can't describe.