Skip to main content

Version Control

Version control standards define how source code, configuration, and documentation are stored, branched, merged, and released. This reference provides lookup information for Git configuration, branching conventions, commit formatting, and repository management.

Git Configuration

Git behaviour depends on configuration at three levels: system (all users on a machine), global (single user across repositories), and local (single repository). Organisation standards apply at global and local levels.

Required Global Configuration

Every developer workstation requires these settings before contributing to organisation repositories:

Terminal window
# Identity (required for commit attribution)
git config --global user.name "Full Name"
git config --global user.email "name@example.org"
# Line endings (prevents cross-platform issues)
git config --global core.autocrlf input # Linux/macOS
git config --global core.autocrlf true # Windows
# Default branch name
git config --global init.defaultBranch main
# Push behaviour (prevents accidental pushes to wrong remote)
git config --global push.default current
git config --global push.autoSetupRemote true
# Pull behaviour (prevents merge commits on pull)
git config --global pull.rebase true
# Credential caching (reduces authentication prompts)
git config --global credential.helper cache --timeout=3600

Repository-Level Configuration

Each repository contains a .gitattributes file defining file handling:

# Default behaviour
* text=auto
# Force specific line endings
*.sh text eol=lf
*.bat text eol=crlf
*.ps1 text eol=crlf
# Binary files (no line ending conversion)
*.png binary
*.jpg binary
*.pdf binary
*.zip binary
# Large files (Git LFS)
*.psd filter=lfs diff=lfs merge=lfs -text
*.ai filter=lfs diff=lfs merge=lfs -text
*.sketch filter=lfs diff=lfs merge=lfs -text

The .gitignore file excludes generated and sensitive content:

# Dependencies
node_modules/
vendor/
.venv/
__pycache__/
# Build outputs
dist/
build/
*.egg-info/
# IDE and editor
.idea/
.vscode/
*.swp
*.swo
.DS_Store
# Environment and secrets
.env
.env.local
*.pem
*.key
secrets/
# Logs and temporary files
*.log
tmp/
cache/

Branching Strategies

A branching strategy determines how parallel development occurs and how changes reach production. The choice depends on release frequency, team size, and deployment model.

Trunk-Based Development

Trunk-based development maintains a single long-lived branch (main or trunk) where all developers integrate changes at least daily. Short-lived feature branches exist for hours or days, never weeks. This strategy suits teams practising continuous deployment where changes reach production within hours of commit.

main: ──●──●──●──●──●──●──●──●──●──●──●──●──▶
\ / \ / \ /
feature-a: ●─● │ │ │ │
feature-b: ●─● │ │
feature-c: ●───●

Figure 1: Trunk-based development with short-lived feature branches

Trunk-based development requires:

  • Feature flags to hide incomplete work in production
  • Comprehensive automated testing (changes merge without staging periods)
  • Small, incremental changes (large changes cannot integrate quickly)
  • CI pipeline execution under 10 minutes

GitFlow

GitFlow uses multiple long-lived branches to manage releases on a fixed schedule. A develop branch accumulates features, release branches stabilise versions, and hotfix branches patch production without disrupting ongoing development. This strategy suits products with versioned releases shipped on cycles of weeks or months.

main: ──●────────────────●────────────────●──────────▶
\ / \ /
\ / \ /
hotfix: \ / \ ●────●
\ / \ / \
release: \ ●─●─● ● \
\ / \ / \
develop: ──●──●──●──●──●──●──●──●──●──●──●──●──●──▶
\ / \ / \ /
feature-a: ●─● \ / ●─●
feature-b: ●───●

Figure 2: GitFlow with develop, release, and hotfix branches

GitFlow requires:

  • Release manager role to coordinate branch merges
  • Scheduled release windows
  • Longer stabilisation periods (release branches exist for days or weeks)
  • Formal versioning scheme

Simplified Flow

Simplified flow provides a middle ground: main represents production, and feature branches merge through pull requests. No develop branch exists; features integrate directly to main after review. Release tags mark production deployments. This strategy suits teams deploying weekly or bi-weekly without formal release cycles.

main: ──●──●──●──●──●──●──●──●──●──●──▶
\ / \ / \ /
\ / \ / \ /
feature-a: ● \ / feature-c:
feature-b: ●───●
Tags: v1.2.0 v1.3.0 v1.4.0

Figure 3: Simplified flow with feature branches and release tags

Strategy Selection

FactorTrunk-BasedSimplified FlowGitFlow
Release frequencyMultiple dailyWeekly to bi-weeklyMonthly or longer
Team size2-8 developers3-15 developers5+ developers
Deployment modelContinuousScheduled with flexibilityFixed release windows
Feature flag requirementEssentialHelpfulOptional
Testing maturityHigh (automated gates)Medium (PR-based)Lower (manual QA phases)
Coordination overheadLowMediumHigh
Rollback mechanismFeature flags or redeployRedeploy previous tagHotfix branch

For organisations with limited IT capacity managing 1-3 applications, simplified flow provides adequate control without GitFlow’s coordination overhead. Teams practising continuous deployment for web applications should adopt trunk-based development. GitFlow remains appropriate for packaged software, mobile applications with app store review cycles, or environments requiring formal release approval.

Branch Naming

Branch names follow a structured format enabling automation, filtering, and identification at a glance.

Format Specification

<type>/<ticket>-<description>
ComponentFormatExample
typeLowercase categoryfeature, bugfix, hotfix, chore, docs
ticketIssue tracker referencePROJ-123, GH-456, #789
descriptionKebab-case summary, 2-5 wordsadd-user-export, fix-login-timeout

Branch Types

TypePurposeMerges toTypical lifespan
featureNew functionalitymain or develop1-5 days
bugfixNon-urgent defect correctionmain or develop1-3 days
hotfixUrgent production fixmain directlyHours
choreMaintenance without user impactmain or develop1-2 days
docsDocumentation changes onlymain or develop1-2 days
releaseRelease stabilisation (GitFlow)main and developDays to weeks

Examples

feature/PROJ-234-beneficiary-export
bugfix/PROJ-567-csv-encoding-error
hotfix/PROJ-890-auth-bypass-fix
chore/GH-12-update-dependencies
docs/#45-api-authentication-guide
release/v2.3.0

Prohibited Patterns

These patterns cause automation failures or confusion:

PatternProblemCorrect alternative
Spaces or special charactersShell escaping issuesUse hyphens
Uppercase lettersInconsistent across platformsAll lowercase
Leading numbersSome tools misparsePrefix with type
Names matching tagsAmbiguous referencesDifferent naming scheme
Personal names (john/feature-x)Unclear ownership after handoverUse type prefix

Commit Messages

Commit messages serve three audiences: reviewers examining changes, maintainers investigating bugs, and future developers understanding history. A well-structured message enables git log, git blame, and automated changelog generation.

Format Specification

<type>(<scope>): <subject>
<body>
<footer>

The subject line contains a type, optional scope, and imperative description under 72 characters. The body explains what changed and why, wrapped at 72 characters per line. The footer contains references to issues, breaking change notices, and co-author credits.

Subject Line

ComponentRequirementExample
typeRequired, lowercasefeat, fix, docs, refactor, test, chore
scopeOptional, in parentheses(api), (auth), (export)
separatorRequired, colon and space:
subjectRequired, imperative mood, no periodadd export endpoint for beneficiary data
lengthMaximum 72 characters totalEnables single-line display in most tools

Commit Types

TypeChangelog sectionUse when
featFeaturesAdding new functionality visible to users
fixBug FixesCorrecting defects
docsDocumentationChanges to documentation only
style(not included)Formatting, whitespace, no code change
refactor(not included)Code restructuring without behaviour change
perfPerformanceImproving performance
test(not included)Adding or correcting tests
build(not included)Build system or dependency changes
ci(not included)CI configuration changes
chore(not included)Maintenance tasks
revertRevertsReverting previous commit

Body Guidelines

The body explains context that the diff cannot convey: why the change was necessary, what alternatives were considered, and what side effects exist. Wrap lines at 72 characters for readability in terminal tools.

fix(auth): prevent session fixation on login
The previous implementation reused session identifiers after
authentication, enabling session fixation attacks. An attacker
could set a known session ID in a victim's browser, wait for
login, then hijack the authenticated session.
This change regenerates session identifiers immediately after
successful authentication. The server invalidates the old
session and issues a new identifier, preventing fixation.
Tested against OWASP session management verification requirements.

The footer contains structured metadata:

Refs: #123, #456
Closes: #789
BREAKING CHANGE: API endpoints now require authentication header
Co-authored-by: Name <email@example.org>
Footer typeFormatPurpose
RefsRefs: #issue, #issueRelated issues for context
ClosesCloses: #issueIssues resolved by this commit
BREAKING CHANGEBREAKING CHANGE: descriptionIncompatible API changes
Co-authored-byCo-authored-by: Name <email>Additional contributors

Examples

Minimal commit (simple change, no body needed):

docs: correct API endpoint URL in README

Standard commit with body:

feat(export): add CSV export for beneficiary lists
Programme teams requested bulk export functionality for offline
data reconciliation. This adds a CSV endpoint returning all
beneficiaries matching the current filter criteria.
The export excludes sensitive fields (protection flags, case notes)
by default. Authorised users can request full export via query
parameter with audit logging.
Refs: #234

Breaking change:

feat(api)!: require authentication for all endpoints
Previously, read-only endpoints allowed anonymous access. This
change requires valid authentication tokens for all API requests,
including list and detail views.
Clients must include Authorization header with bearer token.
Unauthenticated requests receive 401 response.
BREAKING CHANGE: All API endpoints now require authentication.
Closes: #567

Merge and Rebase Policies

Merging and rebasing both integrate changes between branches but produce different histories. The choice affects how history reads and how conflicts are resolved.

Merge commits preserve the complete branching history, showing when branches diverged and rejoined. Each merge creates a commit with two parents, enabling tools to identify which commits belonged to which branch.

Rebase rewrites commits to appear as if they were made sequentially on the target branch. The resulting history is linear but loses information about parallel development.

Policy by Branch Type

Source branchTarget branchMethodRationale
Featuremain/developSquash mergeCollapses work-in-progress into single coherent commit
Bugfixmain/developSquash mergeSame as feature
HotfixmainStandard mergePreserves audit trail for emergency changes
ReleasemainStandard mergeMaintains release history
mainFeature (sync)RebaseKeeps feature branch current without merge commits

Squash Merge Configuration

Squash merging collapses all commits from a feature branch into a single commit on the target. Configure repository settings to enforce:

# GitHub repository settings
branch_protection:
main:
required_pull_request_reviews:
required_approving_review_count: 1
allow_squash_merge: true
allow_merge_commit: false
allow_rebase_merge: false
squash_merge_commit_title: PR_TITLE
squash_merge_commit_message: PR_BODY

The squash commit message should follow the commit message format, summarising the entire feature rather than listing individual commits.

Rebase for Branch Synchronisation

When a feature branch falls behind main, rebase to incorporate upstream changes:

Terminal window
# From feature branch
git fetch origin
git rebase origin/main
# Resolve any conflicts, then continue
git add <resolved-files>
git rebase --continue
# Force push (branch history changed)
git push --force-with-lease

The --force-with-lease flag prevents overwriting changes pushed by others, protecting against accidental data loss in shared branches.

Never rebase shared branches

Rebasing rewrites commit history. If others have based work on commits that get rewritten, their branches become incompatible. Only rebase branches where you are the sole contributor.

Repository Structure

Consistent repository structure enables developers to navigate unfamiliar codebases and automation to locate expected files.

Root Directory Layout

repository/
├── .github/ # GitHub-specific configuration
│ ├── workflows/ # CI/CD pipeline definitions
│ ├── CODEOWNERS # Required reviewers by path
│ ├── pull_request_template.md
│ └── ISSUE_TEMPLATE/
├── docs/ # Documentation
├── src/ # Source code (or app/, lib/)
├── tests/ # Test files (or __tests__/, spec/)
├── scripts/ # Build and utility scripts
├── .gitignore # Ignored files
├── .gitattributes # File handling rules
├── README.md # Repository overview
├── CHANGELOG.md # Version history
├── CONTRIBUTING.md # Contribution guidelines
├── LICENSE # Licence terms
└── <language-specific> # package.json, requirements.txt, etc.

README Requirements

Every repository requires a README.md containing:

SectionContent
Title and descriptionWhat this repository contains, in one paragraph
Status badgesBuild status, coverage, version
PrerequisitesRuntime versions, system dependencies
InstallationCommands to set up development environment
UsageHow to run the application or use the library
TestingHow to run tests
DeploymentHow to deploy (or link to deployment docs)
ContributingLink to CONTRIBUTING.md
LicenceLink to LICENSE file

CODEOWNERS

The CODEOWNERS file designates required reviewers for paths within the repository:

# Default owners for everything
* @org/development-team
# Security-sensitive paths require security review
/src/auth/ @org/security-team
/src/crypto/ @org/security-team
*.pem @org/security-team
# Infrastructure requires ops review
/infrastructure/ @org/operations-team
/.github/workflows/ @org/operations-team
# Documentation
/docs/ @org/documentation-team

Protected Branch Rules

Protected branches prevent direct pushes, enforce reviews, and require status checks before merging.

Configuration by Branch

BranchDirect pushRequired reviewsRequired checksForce pushDeletion
mainBlocked1 minimumAll CI checksBlockedBlocked
developBlocked1 minimumBuild and testBlockedBlocked
release/*Blocked2 minimumAll CI checksBlockedAllowed after merge
feature/*AllowedNone (PR required to main)NoneAllowedAllowed

GitHub Branch Protection

# Repository settings for main branch
branch_protection:
main:
required_pull_request_reviews:
required_approving_review_count: 1
dismiss_stale_reviews: true
require_code_owner_reviews: true
require_last_push_approval: true
required_status_checks:
strict: true
contexts:
- "build"
- "test"
- "security-scan"
enforce_admins: true
required_linear_history: true
allow_force_pushes: false
allow_deletions: false
required_conversation_resolution: true

GitLab Protected Branch

# .gitlab-ci.yml protection rules
protected_branches:
- name: main
push_access_level: no_access
merge_access_level: maintainer
allow_force_push: false
code_owner_approval_required: true

Access Control Model

Repository access follows the principle of least privilege, granting minimum permissions needed for each role.

+------------------------------------------------------------------+
| REPOSITORY ACCESS MODEL |
+------------------------------------------------------------------+
| |
| +----------------------+ |
| | Organisation | |
| | Administrators | |
| | | |
| | - Create repos | |
| | - Manage members | |
| | - Configure billing | |
| +----------+-----------+ |
| | |
| v |
| +----------+-----------+ +----------------------+ |
| | Repository | | Repository | |
| | Administrators | | Maintainers | |
| | | | | |
| | - Manage settings | | - Merge to main | |
| | - Branch protection | | - Manage releases | |
| | - Manage access | | - Review/approve | |
| +----------+-----------+ +----------+-----------+ |
| | | |
| +-------------+--------------+ |
| | |
| v |
| +-------------+--------------+ |
| | Contributors | |
| | | |
| | - Push to feature branches| |
| | - Create pull requests | |
| | - Comment and review | |
| +-------------+--------------+ |
| | |
| v |
| +-------------+--------------+ |
| | Read-Only Access | |
| | | |
| | - Clone and pull | |
| | - View issues and PRs | |
| | - Fork (if permitted) | |
| +----------------------------+ |
| |
+------------------------------------------------------------------+

Figure 4: Repository permission hierarchy

Permission Levels

LevelGitHub roleGitLab roleCapabilities
ReadReadGuest/ReporterClone, view, comment
WriteWriteDeveloperPush branches, create PRs
MaintainMaintainMaintainerMerge, manage releases
AdminAdminOwnerFull repository control

Team-Based Access

Assign permissions to teams rather than individuals. When staff leave or change roles, updating team membership automatically adjusts repository access.

org/
├── development-team → Write access to application repos
├── security-team → Read access to all, Write to security repos
├── operations-team → Maintain access to infrastructure repos
├── documentation-team → Write access to docs repos
└── read-only-auditors → Read access to all repos

Semantic Versioning

Semantic versioning (SemVer) assigns meaning to version numbers, communicating the nature of changes to consumers.

Version Format

MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]
ComponentIncrement whenExample
MAJORBreaking changes (incompatible API)2.0.0
MINORNew features (backwards compatible)1.3.0
PATCHBug fixes (backwards compatible)1.2.4
PRERELEASEPre-release identifier2.0.0-beta.1
BUILDBuild metadata2.0.0+20240115

Version Precedence

Versions sort by comparing major, then minor, then patch numerically. Pre-release versions have lower precedence than the associated release:

1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-beta < 1.0.0 < 1.0.1 < 1.1.0 < 2.0.0

Git Tagging

Create annotated tags for releases:

Terminal window
# Create annotated tag
git tag -a v1.2.0 -m "Release 1.2.0: Beneficiary export feature"
# Push tag to remote
git push origin v1.2.0
# Push all tags
git push origin --tags
# List tags
git tag -l "v1.*"

Pre-Release Workflow

For releases requiring validation before general availability:

Terminal window
# Beta release
git tag -a v2.0.0-beta.1 -m "Beta 1 for version 2.0.0"
# Release candidate
git tag -a v2.0.0-rc.1 -m "Release candidate 1 for version 2.0.0"
# Final release
git tag -a v2.0.0 -m "Release 2.0.0"

Version in Code

Store version in a single source of truth, referenced by packaging and runtime:

src/__version__.py
__version__ = "1.2.0"
# setup.py or pyproject.toml reads from this file
package.json
{
"version": "1.2.0"
}
version.go
package main
var Version = "1.2.0"

Large File Handling

Git stores complete copies of every file version, making repositories with large binary files unwieldy. Git Large File Storage (LFS) replaces large files with text pointers while storing file contents on a separate server.

LFS Configuration

Terminal window
# Install Git LFS
git lfs install
# Track file patterns
git lfs track "*.psd"
git lfs track "*.ai"
git lfs track "*.sketch"
git lfs track "*.zip"
git lfs track "datasets/*.csv"
# Verify tracking
cat .gitattributes

The .gitattributes file records tracked patterns:

*.psd filter=lfs diff=lfs merge=lfs -text
*.ai filter=lfs diff=lfs merge=lfs -text
*.sketch filter=lfs diff=lfs merge=lfs -text
*.zip filter=lfs diff=lfs merge=lfs -text
datasets/*.csv filter=lfs diff=lfs merge=lfs -text

Storage Limits

ProviderFree LFS storageFree bandwidth/month
GitHub1 GB1 GB
GitLab10 GB (shared)Unlimited
Bitbucket1 GB5 GB

For repositories exceeding free tiers, evaluate whether large files belong in version control or should use dedicated artifact storage.

When to Use LFS

File typeSize thresholdRecommendation
Design files (PSD, AI, Sketch)Any sizeUse LFS
Compiled binariesAny sizeUse LFS or artifact storage
Documents (PDF, DOCX)Over 1 MBUse LFS
Images (PNG, JPG)Over 500 KBUse LFS for large assets
Data files (CSV, JSON)Over 10 MBUse LFS or external storage
Video/audioAny sizeExternal storage preferred

Repository Hygiene

Files to Exclude

These files must never be committed, regardless of .gitignore status:

CategoryExamplesRisk
Credentials.env, *.pem, *.key, credentials.jsonCredential exposure
Personal dataDatabase dumps, user exportsPrivacy violation
Build artifactsnode_modules/, dist/, *.pycRepository bloat
IDE configuration.idea/, .vscode/ (user-specific)Developer conflicts
OS files.DS_Store, Thumbs.dbNoise in diffs
Logs*.log, npm-debug.logInformation leakage

Removing Sensitive Data

If sensitive data is committed accidentally, removing it from the current branch does not remove it from history. Use BFG Repo-Cleaner or git filter-repo:

Terminal window
# Using BFG (simpler)
bfg --delete-files "*.pem" --no-blob-protection
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push --force
# Using git filter-repo
git filter-repo --path secrets.json --invert-paths
git push --force

History rewriting affects all collaborators

After rewriting history, all collaborators must re-clone or reset their local repositories. Coordinate this operation and notify all contributors before executing.

Repository Health Checks

Run periodically to identify issues:

Terminal window
# Find large files in history
git rev-list --objects --all | \
git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | \
awk '/^blob/ {print $3, $4}' | \
sort -rn | head -20
# Find commits with sensitive keywords
git log --all -p | grep -i -E "(password|secret|api.?key)" | head -20
# Check for merge conflicts markers left in code
git grep -E "^(<{7}|>{7}|={7})" -- "*.js" "*.py" "*.ts"

Monorepo and Polyrepo

A monorepo contains multiple projects in a single repository. A polyrepo approach uses separate repositories for each project.

Comparison

FactorMonorepoPolyrepo
Cross-project changesSingle commit, atomicMultiple PRs, coordination
Code sharingDirect importsPackages, versioning
Build complexityRequires build system aware of dependenciesStandard per-project builds
Access controlPath-based (CODEOWNERS)Repository-based
CI performanceNeeds selective buildsIndependent pipelines
OnboardingClone once, full contextClone relevant repos only
Repository sizeGrows with all projectsPer-project sizing

Selection Criteria

Choose monorepo when:

  • Multiple projects share significant code
  • Cross-project changes happen frequently
  • Team size allows shared ownership
  • Build tooling supports selective execution

Choose polyrepo when:

  • Projects are genuinely independent
  • Teams own specific projects exclusively
  • Access control must differ between projects
  • Projects use incompatible toolchains

Monorepo Structure

monorepo/
├── apps/
│ ├── web-app/
│ │ ├── package.json
│ │ └── src/
│ ├── mobile-app/
│ │ ├── package.json
│ │ └── src/
│ └── api-service/
│ ├── package.json
│ └── src/
├── packages/
│ ├── shared-ui/
│ │ ├── package.json
│ │ └── src/
│ └── common-utils/
│ ├── package.json
│ └── src/
├── tools/
│ └── scripts/
├── package.json # Root workspace configuration
├── turbo.json # Build orchestration (Turborepo)
└── nx.json # Alternative: Nx configuration

For organisations with limited IT capacity, polyrepo provides simpler tooling and clearer boundaries. Monorepo benefits emerge at scale with dedicated build engineering support.

See also