Skip to content

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

git switch development
git pull --rebase origin development
git switch -c feature/my-feature

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

git push -u origin feature/my-feature

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>(<scope>): <description>

[optional body]

[optional footer: BREAKING CHANGE: ..., Closes #123]
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:

git switch development
git pull --rebase origin development
git branch -d feature/my-feature
git push origin --delete feature/my-feature