Git Branching Strategy¶
We use a development-based branching model — all feature work branches from development, merges back via PR, and is promoted to production through a separate development → main PR.
Branch overview¶
| Branch | Purpose | Protected |
|---|---|---|
main / master | Production — reflects what is live | ✅ Yes, no direct push |
development | Integration branch — always deployable to staging | ✅ Yes, no direct push |
feature/*, fix/* etc. | Short-lived work branches | No |
main vs master
Depending on the project, the production branch is named either main or master. Check the repo settings when in doubt. The workflow is identical regardless of the name.
Flow¶
gitGraph
commit id: "production"
branch development
checkout development
commit id: "dev base"
branch feature/user-invitations
checkout feature/user-invitations
commit id: "feat: add invitation form"
commit id: "feat: send invite email"
checkout development
merge feature/user-invitations id: "PR: merge feature"
branch fix/token-expiry
checkout fix/token-expiry
commit id: "fix: token expiry"
checkout development
merge fix/token-expiry id: "PR: merge fix"
checkout main
merge development id: "PR: deploy to production" Day-to-day workflow¶
1. Branch from development
2. Commit using Conventional Commits
git commit -m "feat: add invitation form"
git commit -m "test: add unit tests for invitations"
git commit -m "fix: handle duplicate emails"
3. Push and open PR → development
Open a PR targeting development — not main/master.
4. Squash-merge into development
Select "Squash and merge" on the PR. Delete the branch after merge.
5. Deploy to production
When development is ready to ship, open a PR from development → main (or development → master). This is your release PR — describe what is going into production in the PR description.
Branch naming¶
| Type | Pattern | Example |
|---|---|---|
| Feature | feature/<short-description> | feature/user-invitations |
| Bugfix | fix/<short-description> | fix/token-expiry-edge-case |
| Hotfix (prod) | hotfix/<short-description> | hotfix/null-pointer-checkout |
| Chore / deps | chore/<short-description> | chore/bump-php-74 |
| Docs | docs/<short-description> | docs/update-api-reference |
Use kebab-case, keep it short and descriptive (max ~40 characters).
Commit format (Conventional Commits)¶
| Type | Use for |
|---|---|
feat | New functionality |
fix | Bug fix |
docs | Documentation only |
test | Adding or fixing tests |
refactor | Refactoring (no new feature, no bug fix) |
chore | Dependencies, CI, tooling |
perf | Performance improvement |
ci | CI/CD configuration |
style | Formatting, whitespace — no logic change |
Breaking changes
Add ! after the type or BREAKING CHANGE: in the footer:
feat!: remove deprecated auth endpoint
Best practices¶
- Use the imperative mood — "add login form", not "added" or "adds"
- Keep the first line under 50 characters — no period at the end
- Separate the body with a blank line if you need to elaborate
- Be specific — avoid vague messages like
update,fixes,misc changes,final version,stuff,oops
Trailers¶
When a commit relates to a tracked task or fixes a reported error, add trailers to the footer:
feat: allow users to reset password
Adds /forgot-password with a form to submit the user's email.
Includes validation, loading states, and success/error messaging.
TASK: https://app.asana.com/...
FIXES: https://sentry.io/...
Tags and releases¶
Tags and semantic versioning are used for our internal Composer packages. See the New Composer Package guide for details on tagging and versioning package releases.
Post-merge cleanup¶
After your PR is merged, clean up both the local and remote branch: