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:
# 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/macOSgit config --global core.autocrlf true # Windows
# Default branch namegit config --global init.defaultBranch main
# Push behaviour (prevents accidental pushes to wrong remote)git config --global push.default currentgit 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=3600Repository-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 -textThe .gitignore file excludes generated and sensitive content:
# Dependenciesnode_modules/vendor/.venv/__pycache__/
# Build outputsdist/build/*.egg-info/
# IDE and editor.idea/.vscode/*.swp*.swo.DS_Store
# Environment and secrets.env.env.local*.pem*.keysecrets/
# Logs and temporary files*.logtmp/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.0Figure 3: Simplified flow with feature branches and release tags
Strategy Selection
| Factor | Trunk-Based | Simplified Flow | GitFlow |
|---|---|---|---|
| Release frequency | Multiple daily | Weekly to bi-weekly | Monthly or longer |
| Team size | 2-8 developers | 3-15 developers | 5+ developers |
| Deployment model | Continuous | Scheduled with flexibility | Fixed release windows |
| Feature flag requirement | Essential | Helpful | Optional |
| Testing maturity | High (automated gates) | Medium (PR-based) | Lower (manual QA phases) |
| Coordination overhead | Low | Medium | High |
| Rollback mechanism | Feature flags or redeploy | Redeploy previous tag | Hotfix 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>| Component | Format | Example |
|---|---|---|
| type | Lowercase category | feature, bugfix, hotfix, chore, docs |
| ticket | Issue tracker reference | PROJ-123, GH-456, #789 |
| description | Kebab-case summary, 2-5 words | add-user-export, fix-login-timeout |
Branch Types
| Type | Purpose | Merges to | Typical lifespan |
|---|---|---|---|
feature | New functionality | main or develop | 1-5 days |
bugfix | Non-urgent defect correction | main or develop | 1-3 days |
hotfix | Urgent production fix | main directly | Hours |
chore | Maintenance without user impact | main or develop | 1-2 days |
docs | Documentation changes only | main or develop | 1-2 days |
release | Release stabilisation (GitFlow) | main and develop | Days to weeks |
Examples
feature/PROJ-234-beneficiary-exportbugfix/PROJ-567-csv-encoding-errorhotfix/PROJ-890-auth-bypass-fixchore/GH-12-update-dependenciesdocs/#45-api-authentication-guiderelease/v2.3.0Prohibited Patterns
These patterns cause automation failures or confusion:
| Pattern | Problem | Correct alternative |
|---|---|---|
| Spaces or special characters | Shell escaping issues | Use hyphens |
| Uppercase letters | Inconsistent across platforms | All lowercase |
| Leading numbers | Some tools misparse | Prefix with type |
| Names matching tags | Ambiguous references | Different naming scheme |
Personal names (john/feature-x) | Unclear ownership after handover | Use 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
| Component | Requirement | Example |
|---|---|---|
| type | Required, lowercase | feat, fix, docs, refactor, test, chore |
| scope | Optional, in parentheses | (api), (auth), (export) |
| separator | Required, colon and space | : |
| subject | Required, imperative mood, no period | add export endpoint for beneficiary data |
| length | Maximum 72 characters total | Enables single-line display in most tools |
Commit Types
| Type | Changelog section | Use when |
|---|---|---|
feat | Features | Adding new functionality visible to users |
fix | Bug Fixes | Correcting defects |
docs | Documentation | Changes to documentation only |
style | (not included) | Formatting, whitespace, no code change |
refactor | (not included) | Code restructuring without behaviour change |
perf | Performance | Improving 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 |
revert | Reverts | Reverting 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 afterauthentication, enabling session fixation attacks. An attackercould set a known session ID in a victim's browser, wait forlogin, then hijack the authenticated session.
This change regenerates session identifiers immediately aftersuccessful authentication. The server invalidates the oldsession and issues a new identifier, preventing fixation.
Tested against OWASP session management verification requirements.Footer
The footer contains structured metadata:
Refs: #123, #456Closes: #789BREAKING CHANGE: API endpoints now require authentication headerCo-authored-by: Name <email@example.org>| Footer type | Format | Purpose |
|---|---|---|
Refs | Refs: #issue, #issue | Related issues for context |
Closes | Closes: #issue | Issues resolved by this commit |
BREAKING CHANGE | BREAKING CHANGE: description | Incompatible API changes |
Co-authored-by | Co-authored-by: Name <email> | Additional contributors |
Examples
Minimal commit (simple change, no body needed):
docs: correct API endpoint URL in READMEStandard commit with body:
feat(export): add CSV export for beneficiary lists
Programme teams requested bulk export functionality for offlinedata reconciliation. This adds a CSV endpoint returning allbeneficiaries matching the current filter criteria.
The export excludes sensitive fields (protection flags, case notes)by default. Authorised users can request full export via queryparameter with audit logging.
Refs: #234Breaking change:
feat(api)!: require authentication for all endpoints
Previously, read-only endpoints allowed anonymous access. Thischange 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: #567Merge 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 branch | Target branch | Method | Rationale |
|---|---|---|---|
| Feature | main/develop | Squash merge | Collapses work-in-progress into single coherent commit |
| Bugfix | main/develop | Squash merge | Same as feature |
| Hotfix | main | Standard merge | Preserves audit trail for emergency changes |
| Release | main | Standard merge | Maintains release history |
| main | Feature (sync) | Rebase | Keeps 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 settingsbranch_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_BODYThe 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:
# From feature branchgit fetch origingit rebase origin/main
# Resolve any conflicts, then continuegit add <resolved-files>git rebase --continue
# Force push (branch history changed)git push --force-with-leaseThe --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:
| Section | Content |
|---|---|
| Title and description | What this repository contains, in one paragraph |
| Status badges | Build status, coverage, version |
| Prerequisites | Runtime versions, system dependencies |
| Installation | Commands to set up development environment |
| Usage | How to run the application or use the library |
| Testing | How to run tests |
| Deployment | How to deploy (or link to deployment docs) |
| Contributing | Link to CONTRIBUTING.md |
| Licence | Link 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-teamProtected Branch Rules
Protected branches prevent direct pushes, enforce reviews, and require status checks before merging.
Configuration by Branch
| Branch | Direct push | Required reviews | Required checks | Force push | Deletion |
|---|---|---|---|---|---|
| main | Blocked | 1 minimum | All CI checks | Blocked | Blocked |
| develop | Blocked | 1 minimum | Build and test | Blocked | Blocked |
| release/* | Blocked | 2 minimum | All CI checks | Blocked | Allowed after merge |
| feature/* | Allowed | None (PR required to main) | None | Allowed | Allowed |
GitHub Branch Protection
# Repository settings for main branchbranch_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: trueGitLab Protected Branch
# .gitlab-ci.yml protection rulesprotected_branches: - name: main push_access_level: no_access merge_access_level: maintainer allow_force_push: false code_owner_approval_required: trueAccess 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
| Level | GitHub role | GitLab role | Capabilities |
|---|---|---|---|
| Read | Read | Guest/Reporter | Clone, view, comment |
| Write | Write | Developer | Push branches, create PRs |
| Maintain | Maintain | Maintainer | Merge, manage releases |
| Admin | Admin | Owner | Full 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 reposSemantic Versioning
Semantic versioning (SemVer) assigns meaning to version numbers, communicating the nature of changes to consumers.
Version Format
MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]| Component | Increment when | Example |
|---|---|---|
| MAJOR | Breaking changes (incompatible API) | 2.0.0 |
| MINOR | New features (backwards compatible) | 1.3.0 |
| PATCH | Bug fixes (backwards compatible) | 1.2.4 |
| PRERELEASE | Pre-release identifier | 2.0.0-beta.1 |
| BUILD | Build metadata | 2.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.0Git Tagging
Create annotated tags for releases:
# Create annotated taggit tag -a v1.2.0 -m "Release 1.2.0: Beneficiary export feature"
# Push tag to remotegit push origin v1.2.0
# Push all tagsgit push origin --tags
# List tagsgit tag -l "v1.*"Pre-Release Workflow
For releases requiring validation before general availability:
# Beta releasegit tag -a v2.0.0-beta.1 -m "Beta 1 for version 2.0.0"
# Release candidategit tag -a v2.0.0-rc.1 -m "Release candidate 1 for version 2.0.0"
# Final releasegit 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:
__version__ = "1.2.0"
# setup.py or pyproject.toml reads from this file{ "version": "1.2.0"}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
# Install Git LFSgit lfs install
# Track file patternsgit lfs track "*.psd"git lfs track "*.ai"git lfs track "*.sketch"git lfs track "*.zip"git lfs track "datasets/*.csv"
# Verify trackingcat .gitattributesThe .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 -textdatasets/*.csv filter=lfs diff=lfs merge=lfs -textStorage Limits
| Provider | Free LFS storage | Free bandwidth/month |
|---|---|---|
| GitHub | 1 GB | 1 GB |
| GitLab | 10 GB (shared) | Unlimited |
| Bitbucket | 1 GB | 5 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 type | Size threshold | Recommendation |
|---|---|---|
| Design files (PSD, AI, Sketch) | Any size | Use LFS |
| Compiled binaries | Any size | Use LFS or artifact storage |
| Documents (PDF, DOCX) | Over 1 MB | Use LFS |
| Images (PNG, JPG) | Over 500 KB | Use LFS for large assets |
| Data files (CSV, JSON) | Over 10 MB | Use LFS or external storage |
| Video/audio | Any size | External storage preferred |
Repository Hygiene
Files to Exclude
These files must never be committed, regardless of .gitignore status:
| Category | Examples | Risk |
|---|---|---|
| Credentials | .env, *.pem, *.key, credentials.json | Credential exposure |
| Personal data | Database dumps, user exports | Privacy violation |
| Build artifacts | node_modules/, dist/, *.pyc | Repository bloat |
| IDE configuration | .idea/, .vscode/ (user-specific) | Developer conflicts |
| OS files | .DS_Store, Thumbs.db | Noise in diffs |
| Logs | *.log, npm-debug.log | Information 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:
# Using BFG (simpler)bfg --delete-files "*.pem" --no-blob-protectiongit reflog expire --expire=now --allgit gc --prune=now --aggressivegit push --force
# Using git filter-repogit filter-repo --path secrets.json --invert-pathsgit push --forceHistory 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:
# Find large files in historygit 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 keywordsgit log --all -p | grep -i -E "(password|secret|api.?key)" | head -20
# Check for merge conflicts markers left in codegit 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
| Factor | Monorepo | Polyrepo |
|---|---|---|
| Cross-project changes | Single commit, atomic | Multiple PRs, coordination |
| Code sharing | Direct imports | Packages, versioning |
| Build complexity | Requires build system aware of dependencies | Standard per-project builds |
| Access control | Path-based (CODEOWNERS) | Repository-based |
| CI performance | Needs selective builds | Independent pipelines |
| Onboarding | Clone once, full context | Clone relevant repos only |
| Repository size | Grows with all projects | Per-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 configurationFor organisations with limited IT capacity, polyrepo provides simpler tooling and clearer boundaries. Monorepo benefits emerge at scale with dedicated build engineering support.