Skip to content

SE 10 Version Control and Git Workflows

Dr M H B Ariyaratne edited this page Jun 8, 2026 · 1 revision

SE-10: Version Control and Git Workflows

Part of the Software Engineering Principles series


What Is Version Control?

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.


Core Git Concepts

Repository

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.

Commit

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.

Branch

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

Merge

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.

Pull Request (PR)

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

Branching Strategies

Git Flow

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.


GitHub Flow

A simpler model for teams practising continuous delivery:

main ────────────────────────────────────── (always deployable)
  feature/add-drug-allergy-check ───────── (short-lived feature branch)
  1. Branch from main
  2. Add commits
  3. Open a pull request
  4. Review, discuss, and adjust
  5. Merge to main
  6. Deploy

Best for: Web applications with frequent, small deployments.


Trunk-Based Development

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.


Writing Good Commit Messages

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.

The Conventional Commits Standard

<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

What Makes a Good Commit Message

  • 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

The Pull Request Workflow

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

Keeping a Clean History

Atomic Commits

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 Often, Push Deliberately

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.


Conflict Resolution

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:

  1. Understand both changes and their intent
  2. Produce the correct combined result (may be either version, or a synthesis)
  3. Remove the conflict markers
  4. Run tests to confirm the resolved code is correct
  5. Commit the resolution

Tags and Releases

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

Back to Software Engineering Principles

Clone this wiki locally