-
Notifications
You must be signed in to change notification settings - Fork 134
SE 10 Version Control and Git Workflows
Part of the Software Engineering Principles series
Version control is a system that records changes to files over time so that specific versions can be recalled later. It answers the questions: What changed? When? By whom? Why?
Without version control:
- Changes cannot be undone safely
- Two developers working simultaneously will overwrite each other's work
- There is no history to explain why the code is the way it is
Git is the dominant version control system in professional software development. It is distributed, meaning every developer has a full copy of the repository history.
A repository (repo) is a directory tracked by Git. It contains all the files in the project plus the complete history of every change ever made.
A commit is a snapshot of the project at a point in time. Each commit records:
- What changed (the diff)
- Who made the change (author)
- When the change was made (timestamp)
- Why the change was made (commit message)
Commits are linked in a chain — each one points to its parent. This chain forms the project's history.
A branch is a lightweight, movable pointer to a commit. Branches allow parallel streams of work to proceed without interfering with each other.
main: A ── B ── C ── D
\
feature/login: E ── F ── G
Merging integrates changes from one branch into another. Git attempts to combine changes automatically and asks for manual resolution when two branches modified the same lines.
A pull request (PR) is a proposal to merge one branch into another. It provides a structured opportunity for:
- Code review
- Automated CI checks (tests, linting, security scans)
- Discussion before changes land in the main branch
A structured model with distinct branches for different types of work:
main ──────────────────────────────────── (production code)
develop ───────────────────────────────── (integration branch)
feature/patient-merge ──────────────── (feature work)
feature/pharmacy-gst ──────────────── (feature work)
release/2.1.0 ──────────────────────── (release preparation)
hotfix/billing-rounding ────────────── (urgent production fix)
| Branch | Purpose | Merges into |
|---|---|---|
main |
Production-ready code | Never directly |
develop |
Integration of completed features |
main via release branch |
feature/* |
Individual features | develop |
release/* |
Final preparation for a release |
main and develop
|
hotfix/* |
Urgent production fixes |
main and develop
|
Best for: Projects with scheduled releases and a clear separation between development and production.
A simpler model for teams practising continuous delivery:
main ────────────────────────────────────── (always deployable)
feature/add-drug-allergy-check ───────── (short-lived feature branch)
- Branch from
main - Add commits
- Open a pull request
- Review, discuss, and adjust
- Merge to
main - Deploy
Best for: Web applications with frequent, small deployments.
Developers commit directly to main (trunk) or use very short-lived branches (hours, not days). Feature flags control what users see.
Best for: Experienced teams with excellent test coverage and automated deployment.
A commit message is a permanent record of why a change was made. Future readers (including your future self) will rely on it to understand the history.
<type>(<scope>): <short summary>
<optional body>
<optional footer>
Types:
| Type | When to use |
|---|---|
feat |
A new feature |
fix |
A bug fix |
refactor |
Code change that is neither a fix nor a feature |
test |
Adding or correcting tests |
docs |
Documentation only changes |
perf |
Performance improvement |
chore |
Build process, tooling, dependencies |
Examples:
feat(pharmacy): add expiry date validation to stock issue form
Validates that no item past its expiry date is included in an issue.
The check runs on Save and prevents finalisation of expired stock.
Closes #1234
fix(billing): correct GST rounding on split payments
The previous calculation used floating-point arithmetic which accumulated
errors for bills with more than 3 line items. Now uses BigDecimal.
Closes #1289
- Subject line under 72 characters — fits in logs and PR titles
- Present tense imperative — "add" not "added" or "adds"
- Explains WHY, not WHAT — the diff shows what; the message explains why
- References the issue number — creates traceable history
// Bad — describes the what (which the diff shows)
Changed the discount calculation method
// Good — describes the why
fix(billing): prevent discount double-application on split payments
The previous logic applied the discount per payment line rather than
once per bill, causing double-discounting when patients paid in
instalments. Now calculated once on the bill total.
Closes #1543
1. Create branch from the development base
git checkout -b feature/1234-stock-expiry-check origin/development
2. Make changes with small, focused commits
3. Push branch to remote
git push -u origin feature/1234-stock-expiry-check
4. Open a pull request
- Write a clear description of what and why
- Link the issue (Closes #1234)
- Assign reviewers
5. Address review comments
- Push additional commits to the same branch
- Respond to every comment
6. CI passes (build, tests, lint, security scan)
7. Merge when approved
8. Delete the branch
Each commit should represent one complete, coherent change. Do not mix unrelated changes in a single commit.
// Bad — one commit with unrelated changes
fix(billing): correct GST rounding, also rename BillingBean, update UI layout
// Good — separate commits
fix(billing): correct GST rounding on split payments
refactor(billing): rename BillingBean to BillViewController
style(billing): align form fields in bill summary panel
Commit locally after each small step. Push when the work is complete and the branch is in a good state. Frequent local commits give you fine-grained undo capability.
Conflicts occur when two branches modify the same lines differently. Git cannot decide automatically which version is correct — a human must resolve it.
<<<<<<< HEAD (your branch)
return basePrice * 0.8; // staff discount
=======
return basePrice * 0.75; // updated staff discount
>>>>>>> feature/discount-update
Resolution steps:
- Understand both changes and their intent
- Produce the correct combined result (may be either version, or a synthesis)
- Remove the conflict markers
- Run tests to confirm the resolved code is correct
- Commit the resolution
Tags mark specific commits as significant, typically release points.
git tag -a v2.1.0 -m "Release 2.1.0 — GST module"
git push origin v2.1.0
Tags make it easy to check out the exact code that was deployed to production, which is invaluable when investigating production issues.
Previous: SE-09: Testing Principles
Next: SE-11: API Design Principles