Git — Beyond Add, Commit, Push
Branches, rebases, conflict resolution, and the workflows real teams use.
The Mental Model
Git is a distributed version control system. The single most important thing to internalize: Git tracks SNAPSHOTS, not diffs.
When you commit, Git takes a complete snapshot of every tracked file. Each commit has:
• A unique SHA-1 hash that identifies it
• A pointer to its parent commit (or commits, for merges)
• A snapshot of the entire working tree
• An author, committer, timestamp, and message
A repository's history is a directed graph of commits. Branches are just named pointers to specific commits. HEAD is a pointer to your current location in this graph.
(main)
↓
A ──── B ──── C ──── D
\
E ──── F
↑
(feature)
When you switch branches, Git just moves a pointer and updates files. When you merge, Git creates a new commit with two parents. When you rebase, Git rewrites history by replaying commits on a different base.
This model is why Git operations are fast — most are just pointer manipulation. And it's why understanding the model makes the commands click.
We assume basic familiarity (git add, git commit, git push). This lesson covers what makes someone genuinely effective with Git.
Branching Strategies
Creating a branch is essentially free:
git checkout -b feature-login # create AND switch
git switch -c feature-login # newer alternative
git branch -d feature-login # delete (after merge)
git branch -D feature-login # force delete (lose unmerged work)
The two dominant workflows in 2026:
GitHub Flow (recommended for most teams):
1. main is always deployable
2. Create a branch off main for any new work
3. Open a Pull Request when ready
4. Discuss / review / iterate
5. Merge to main → automatically deploy
Trunk-Based Development (high-throughput teams):
1. main is the only long-lived branch
2. Feature branches are short — under 1 day
3. Multiple deploys per day from main
4. Feature flags hide incomplete features in production
Avoid Git Flow (with develop, release/, hotfix/ branches) unless you have scheduled releases. It's complexity that modern continuous-deploy teams don't need.
For this series, focus on GitHub Flow. It's what most cloud-native teams use.
Merging vs Rebasing
When your feature branch is done and main has moved on, you need to integrate them. Two ways:
Merge:
git checkout main
git merge feature-login
Creates a merge commit with two parents. Preserves history exactly as it happened.
Rebase:
git checkout feature-login
git rebase main
Replays your commits ON TOP OF main, creating new commits with new SHAs. Linear history.
Before: After rebase:
A — B — C — D (main) A — B — C — D (main)
\ \
E — F (feature) E' — F' (feature)
The Golden Rule: NEVER rebase a branch that others have based work on. Rebasing rewrites commits with new SHAs. If someone else's commits depend on the old SHAs, their history breaks.
Safe: your local feature branch.
Unsafe: a shared develop branch.
A clean workflow combining both:
# Keep your feature branch up to date with main
git checkout feature-login
git fetch origin
git rebase origin/main # safe — only your branch is rewritten
# When done, merge to main via PR
# (use "Squash and merge" in GitHub for a clean main history)
"Squash and merge" combines all feature-branch commits into one clean commit on main. Best of both worlds: clean main history, full review history in the PR.
Resolving Conflicts
Conflicts happen when Git can't automatically combine changes — both branches changed the same lines.
$ git merge feature-login
CONFLICT (content): Merge conflict in src/auth.js
When you open the file:
<<<<<<< HEAD
const TIMEOUT = 5000;
=======
const TIMEOUT = 10000;
>>>>>>> feature-login
Edit to resolve — keep what you want, delete the markers. Then:
git add src/auth.js # mark resolved
git status # check for more
git commit # complete the merge
git merge --abort # or abort the whole thing
Tips for fewer conflicts:
• Merge / rebase main into your branch frequently
• Keep PRs small (smaller diffs = less chance of conflict)
• Communicate when refactoring shared files
• Use feature flags for incomplete work, avoiding long-lived branches
Daily Commands That Pay Off
Inspecting:
git log --oneline # one line per commit
git log --graph --oneline --all # visual graph of all branches
git log -p src/auth.js # patches that touched this file
git blame src/auth.js # who wrote each line
git diff main..feature # what's different between branches
Undoing:
git restore file.txt # undo unstaged changes
git restore --staged file.txt # unstage (keep changes)
git reset --soft HEAD~1 # undo last commit, keep changes staged
git reset --hard HEAD~1 # undo last commit, discard changes (DANGER)
git commit --amend # add to your previous commit (don't push if shared!)
Stashing — temporarily set aside changes:
git stash # save uncommitted work
git stash pop # restore it
git stash list # see all stashes
Pushing safely:
git push # current branch
git push --force-with-lease # safer than --force (refuses if remote moved)
Cherry-pick — copy a single commit from elsewhere:
git cherry-pick abc123 # apply that commit to current branch
Useful for porting a fix from main to a release branch.
Bisect — find which commit introduced a bug:
git bisect start
git bisect bad # current state is broken
git bisect good v1.0 # this version was fine
# Git checks out a midpoint; test it and mark good or bad
# Repeat until Git pinpoints the offending commit
git bisect reset
Reflog — your safety net:
git reflog # every HEAD movement of the last 90 days
git reset --hard HEAD@{2} # back to where HEAD was 2 movements ago
Even commits you "lost" via bad reset/rebase are usually still in the reflog. This has saved more careers than I can count.
Pull Requests
A Pull Request is a code review workflow on top of Git. The author proposes a change, reviewers comment, the author iterates, and eventually it merges.
What makes a good PR:
• Small — fewer than ~400 lines of diff. Big PRs get cursory reviews.
• Single-purpose — one logical change. Don't mix refactoring with features.
• Descriptive title — what changed. "Fix login redirect on mobile" not "fix bug".
• Body explains WHY — link to the issue, describe the approach, note alternatives.
• Tests included.
• CI green before requesting review.
What reviewers check:
• Does it solve the problem correctly?
• Will it break anything in production?
• Is the code readable in 6 months?
• Edge cases or security issues?
• Tests adequate?
Review automation that helps:
• CI runs tests automatically — failed tests = no merge
• Required reviewers (CODEOWNERS file maps paths to required reviewers)
• Branch protection — main can't be pushed to directly, only via merged PRs
• Required status checks before merge
Secrets & .gitignore
Some files should never be committed: build artifacts, dependencies, secrets, logs, IDE configs.
Standard .gitignore:
# Dependencies
node_modules/
__pycache__/
*.pyc
# Build output
dist/
build/
# Logs
*.log
# OS / Editor
.DS_Store
.idea/
.vscode/
# Secrets — NEVER commit these
.env
.env.local
*.pem
*.key
GitHub publishes useful templates per language at github.com/github/gitignore.
Critical: secrets in Git are forever
If you commit a secret and push it, the commit is in the history of every clone. Tools like git-secrets, truffleHog, and gitleaks scan for them.
Recovery if you committed a secret:
1. ROTATE THE SECRET IMMEDIATELY — the value is compromised
2. Use git filter-repo or BFG Repo-Cleaner to remove from history
3. Force-push and notify everyone with a clone to re-clone
Better: prevent it. The pre-commit framework runs scanners before each commit:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: trailing-whitespace
- id: check-merge-conflict
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
Cheap insurance.
The next lesson covers the workflow that ties Git into deployment: continuous integration. Once you have Git discipline AND CI, the rest of DevOps automation falls into place.
⁂ Back to all modules