Version Control¶
Version control records changes as reviewable history. It enables collaboration, rollback, investigation, and reproducible releases. This chapter uses Git, a distributed version control system.
Git's Model¶
Keep four places distinct:
- working tree — files you are editing;
- index — the proposed contents of the next commit;
- local repository — commits and named references such as branches;
- remote repository — another repository reached through a named remote.
A commit stores a snapshot, metadata, and parent commit identifiers. A branch is a movable name pointing to a commit. HEAD identifies the current branch or, when detached, a commit directly.
working tree --add--> index --commit--> local history --push--> remote
^ │
└---- restore -----┘
The Daily Workflow¶
git status
git diff
git add path/to/file
git diff --staged
git commit -m "Explain the completed change"
git fetch origin
git push
Stage deliberately. git add -p selects individual changes. Before committing, inspect both unstaged and staged diffs and run the relevant checks.
Good commits are coherent, buildable where practical, and explain why the change exists. Do not mix generated files, formatting, refactoring, and behavior unless they are inseparable.
Branches and Integration¶
git switch -c fix/session-expiry
git switch main
git merge fix/session-expiry
Keep branches short-lived and integrate frequently. Protect the shared branch with review and automated checks.
A merge preserves both histories and creates a merge commit when needed. A rebase copies commits onto a new base, producing new commit identities and a linear history:
git fetch origin
git rebase origin/main
Rebase unpublished work freely. Avoid rebasing history other people may have based work on. If rewriting your own published branch is agreed, prefer git push --force-with-lease over unguarded force.
Inspecting History¶
git log --oneline --graph --decorate --all
git show <commit>
git diff <base>...<branch>
git blame path/to/file
git log -S 'removed_text' -- path/to/file
git blame identifies the last change to lines, not the person responsible for a defect. Follow the commit and surrounding history for context.
Use binary search to locate a regression:
git bisect start
git bisect bad
git bisect good <known-good-commit>
git bisect run ./small-reproduction.sh
git bisect reset
Undoing Safely¶
The official Git documentation distinguishes three commonly confused commands:
git restorechanges working-tree or staged file content;git resetmoves a branch or changes the index and can rewrite local history;git revertcreates a new commit that reverses an earlier commit.
Examples:
git restore path/to/file # discard unstaged changes
git restore --staged path/to/file # unstage; keep working changes
git commit --amend # replace the latest local commit
git revert <commit> # safe reversal in shared history
Inspect before discarding. git reset --hard, git clean, and forced pushes can destroy uncommitted or shared work.
The reflog records recent local reference movements and often recovers commits after an accidental reset or rebase:
git reflog
git branch recovery <commit-from-reflog>
Reflogs are local and expire; they are not a backup.
Conflicts¶
When an integration stops:
- read
git status; - understand both intended changes;
- edit the files to the correct combined result;
- remove conflict markers;
- stage resolved files and run tests;
- continue or abort the operation.
git merge --continue # or git rebase --continue
git merge --abort # or git rebase --abort
Do not resolve by blindly taking “ours” or “theirs”; those labels also vary by operation.
Remotes and Collaboration¶
git fetch downloads objects and updates remote-tracking references without changing the working tree. git pull fetches and immediately integrates; use it only when the configured integration behavior is understood.
Review requests should be small, explain the problem and trade-offs, include verification evidence, and separate required changes from optional suggestions. Delete merged branches; commits remain reachable through the target history.
Ignore, Attributes, and Large Files¶
.gitignore prevents untracked files from being considered for addition; it does not remove already tracked files. Ignore local build output, caches, and secrets—but commit reproducibility inputs such as lockfiles when the ecosystem expects them.
.gitattributes controls repository-level text normalization and diff/merge behavior. Use Git LFS or artifact storage for large binary assets that genuinely need versioning; avoid committing generated archives and build products.
Tags, Releases, and Authenticity¶
Tags give stable names to commits. Annotated tags carry metadata and can be signed. A release should map to an immutable commit and reproducible artifact.
Commit or tag signatures prove control of a configured key, not that code is safe or reviewed. Protect keys, verify contributor identity through the hosting workflow, and secure branch and release permissions.
Repository Safety¶
- Never commit secrets; rotate an exposed secret even if history is rewritten.
- Review executable hooks, CI configuration, and dependency changes as code.
- Limit who can bypass protected branches or publish releases.
- Back up the authoritative remote and test restoration where loss matters.
- Prefer reviewed history rewriting tools for removing sensitive data.
Useful Advanced Tools¶
git stashtemporarily records a dirty working state; a small WIP commit is often easier to understand.git worktreechecks out multiple branches into separate directories.git cherry-pickcopies selected commits; repeated use may signal poorly aligned branches.- submodules pin another repository commit but add coordination work; use them only when separate ownership and history are essential.
- sparse checkout and partial clone reduce working data for very large repositories.
Checklist¶
- Is the staged diff exactly the intended change?
- Does the commit explain why and pass relevant checks?
- Is shared history preserved unless rewriting was agreed?
- Are generated output, binaries, and secrets excluded?
- Can a release be traced to an immutable commit?
- Are destructive commands preceded by inspection and a recovery plan?
Version control is most valuable when history is trustworthy enough to review, diagnose, and restore the software.