Short answer: one short-lived branch per work item, cut from the branch that represents your highest sandbox, merged back through a pull request, deleted the same week. Git flow, with its permanent develop, its release branches and its hotfix branches, fits badly here and creates the merge conflicts Salesforce metadata handles worst. A branch’s half-life matters more than its name.
Why git flow fits badly
Git flow was designed for software that ships versioned releases to machines you do not control, where several versions are live at once and a hotfix has to be applied to an old one. None of that describes a Salesforce org.
You have one production org running one version of your metadata. There is no artefact, only the org, and you cannot roll back cleanly because deleting a field takes its data with it. The elaborate topology buys you nothing and costs you the thing that hurts: time. A release branch that lives three weeks is three weeks of divergence in files that do not merge.
The shape that works
- A long-lived branch per environment you deploy to automatically. For most teams that is two: integration and production. Three with a UAT org on its own cadence. Not five.
- One branch per work item, named for the ticket, cut from the integration branch.
- Each branch gets an org. A scratch org if your metadata allows it, a developer sandbox if not. Two people sharing a sandbox for two branches is where conflicts start.
- Pull request into integration, with a validation-only deployment as the required check.
- Promote by merging integration into production, not by cherry-picking. Cherry-picking a release out of integration means the integration branch holds work that is not ready, which is a planning problem dressed as a git problem.
The rule that makes it work: a branch open for more than a week is a risk, and one open for a month is a rewrite waiting to happen. Split the work instead.
The metadata conflict problem
Git merges text. Salesforce metadata is generated XML, and a retrieval is not guaranteed to order it the same way twice. Several file types are conflict factories:
- Profiles. A retrieved profile holds every object and field permission in the org, so two people on unrelated features both produce a thousand-line diff. Profiles also deploy as a merge, so a stale file can quietly remove permissions in the target.
- Flows. One file, no structure a three-way merge can use, and element positions stored as coordinates. A conflicted flow is effectively unmergeable.
- Page layouts and Flexipages. Ordered component lists, where two insertions in the middle conflict every time.
- Custom labels and translations. One file holding everything, so every feature touches it.
What actually reduces the pain
Stop source-controlling full profiles. Put permissions in permission sets, which are smaller, feature-scoped, and owned by one branch each. Keep profiles minimal and put the big ones in .forceignore. This single change removes most metadata conflicts in a typical repository.
One owner per flow per release. There is no technical fix. Coordinate it in planning, and if two features need the same flow, sequence them.
Retrieve narrowly. sf project retrieve start --metadata ApexClass:InvoiceService keeps the diff to what the branch changed. Pulling everything source tracking offers is how a branch ends up touching forty files it has no opinion about.
Deploy the delta, not the directory. Tools such as sfdx-git-delta build a manifest from the diff between two commits, so the pipeline deploys changed components rather than the whole folder. Faster, and an accidental revert of someone else’s work becomes much less likely.
Resolve conflicts in the org, not the editor. When two branches conflict on a layout or a flow, take one side, deploy it to a sandbox, reapply the other change through the interface, and retrieve. Hand-merging generated XML produces files that deploy and then behave unexpectedly.
A word on admins
Any strategy requiring every change to arrive as a commit will fail in an org with working admins who configure in Setup. Give them a sandbox, retrieve their changes into a branch on their behalf, and put that through the same pull request. The alternative is production changes nobody records, which is what the branching strategy was supposed to prevent.
Frequently asked
Do we need a branch per environment? Only per environment you deploy to without a human deciding. A sandbox someone deploys to on demand does not need its own branch.
How do we handle a production hotfix? Branch from production, fix, validate, deploy, then merge back into integration immediately. The immediately is the part teams skip.
Should the repository mirror the whole org? Eventually, yes. Starting with everything is how the effort stalls. Begin with Apex, LWC and flows, then widen.
