Git Branching & Collaboration
Success criteria
0 of 4
The scenario#
You are contributing to a repository other people are also changing. Main has moved since you branched, your history has a conflict in it, and at some point you will reset something you did not mean to.
All three are normal. None of them should cost you work.
The daily loop#
git switch -c feat/add-healthcheck # branch from main
# ... edit, then ...
git add -p # stage hunks, not whole files
git commit -m "Add /healthz endpoint"
git fetch origin
git rebase origin/main # replay your work on current main
git push -u origin feat/add-healthcheckgit add -p walks you through each hunk. It is slower than git add . and it
is the reason your commits end up saying one thing each — which is what makes
them reviewable and revertible.
Rebase daily, not at the end. Replaying two commits onto a main that moved this morning is a small conflict you still remember the context for. Replaying three weeks of work is every conflict at once, in a hurry.
Resolving a conflict properly#
git rebase origin/main
# CONFLICT (content): Merge conflict in src/app.pyOpen the file. You will see both sides:
<<<<<<< HEAD (what is on main)
timeout = 30
======= (what you wrote)
timeout = 60
>>>>>>> feat/add-healthcheckThe mistake is picking a side because it is quicker. Read both — someone raised that timeout on main for a reason, and your change may need to accommodate it rather than replace it.
# after editing to the correct combined result
git add src/app.py
git rebase --continuegit rebase --abort puts everything back exactly as it was. Nothing is lost by
trying.
The recovery everybody needs eventually#
git reset --hard HEAD~3 # three commits, apparently gone
git reflog # every position HEAD has held
# a1b2c3d HEAD@{1}: commit: Add /healthz endpoint
git reset --hard HEAD@{1} # back, intactgit reflog is the undo history for the repository itself. A commit is
reachable for ~90 days even after every branch pointing at it is gone. Almost
nothing done locally in git is actually destructive, and knowing that changes
how confidently you work.
A secret in history#
git rm --cached .env
echo ".env" >> .gitignore
git commit -m "Remove .env from tracking"That stops tracking it going forward, and the value is still in history and in every clone. The order that matters:
- Rotate the credential. Assume it is compromised — this is the only step that actually protects anything.
- Then rewrite history (
git filter-repo, or the GitHub secret-scanning flow). - Then force-push, and tell anyone with a clone.
Doing step 2 without step 1 is theatre.
When it goes wrong#
The failure is where the learning is. These are the ones that actually happen:
git push is rejected as non-fast-forward
Main moved. git fetch origin && git rebase origin/main, then push. Do not --force onto a shared branch.
The rebase conflicts in files you never touched
You branched from an old main. Abort, fetch, and rebase onto the current one.
You reset and lost commits
git reflog, find the hash, git reset --hard <hash>. It is almost certainly still there.
A force-push erased a colleague's commits
Use --force-with-lease instead — it refuses when the remote has moved since you last fetched.