Part of the Salesforce Admin Git & sf CLI series. Articles are standalone; read in any order. Foundation: Why Version Control · Environment Setup · Connect Your Org · Git Basics
TL;DR
- A merge conflict means two branches changed the same lines of the same file. Git cannot guess which change wins, so it stops and asks you.
- The fix is always the same five moves: merge
maininto your branch, open each conflicted file, keep the right version,git addthe file,git commit. - Salesforce metadata conflicts more than code does. Flows, Profiles, Custom Labels and page layouts are large XML files that several people edit at once.
- Never hand-merge the structure of a Flow. Keep one version, then re-apply the other change in Flow Builder and retrieve it again.
- Validate before you push. A
sf project deploy start --dry-runagainst a sandbox catches a bad resolution before your reviewer does.
What You'll Learn
- What a merge conflict is and why git stops instead of guessing
- How to read the
<<<<<<<,=======and>>>>>>>markers git writes into your file - A step-by-step walkthrough that resolves a real conflict in a Flow file
- Which Salesforce metadata types conflict most and how to handle each one
- The other kind of conflict: when sf CLI says your local file and the org disagree
- How to stop the same conflicts coming back every sprint
The Problem
You followed the workflow. You made your change in a sandbox, retrieved it, committed it on a branch and opened a merge request. Then GitHub shows a grey box that says "This branch has conflicts that must be resolved" and the merge button is disabled. GitLab and Bitbucket show a similar warning.
What happened is ordinary. While you were working, a colleague changed the same Flow, and their change was merged into main first. Your branch and main now disagree about what the same lines of XML should say. Git refuses to pick a winner, because picking wrong would silently throw away someone's work.
This is the moment that makes many admins give up on git. The file now has strange symbols in it, the terminal says "unmerged paths", and nothing in Salesforce Setup prepares you for it. The good news is that resolving a conflict is a short, repeatable procedure, and once you have done it twice it stops being frightening.
This article walks you through that procedure with a real Flow file, then covers the metadata types that cause the most trouble and how to stop them conflicting in the first place.
Common questions this article answers:
- Why does git say my branch has conflicts when I only changed one thing?
- Which version do I keep, mine or the one already in main?
- Can I just edit the Flow XML by hand to combine both changes?
Quick Answer
From your project folder, with your feature branch checked out:
# 1. Get the latest main from the remote
git fetch origin
# 2. Merge main into your branch (this is where the conflict appears)
git merge origin/main
# 3. See which files are in conflict
git status
# 4. Open each file listed under "Unmerged paths", find the
# <<<<<<< ======= >>>>>>> markers, and edit the file so it
# contains only the version you want. Delete the marker lines.
# 5. Tell git the file is resolved
git add force-app/main/default/flows/Case_Escalation.flow-meta.xml
# 6. Finish the merge
git commit --no-edit
# 7. Check the result is valid metadata before anyone else sees it
sf project deploy start --source-dir force-app --target-org mysandbox --dry-run
# 8. Push the resolved branch; the merge request updates automatically
git push
If you get lost at any point before step 6, git merge --abort puts everything back exactly as it was before step 2. Nothing is lost.
What a Merge Conflict Actually Is
Git compares three versions of a file when it merges:
- The common ancestor: the file as it was when your branch split off from
main - Your version: the file on your branch
- Their version: the file on
mainnow
If only one side changed a given line, git takes that change automatically. If both sides changed different lines of the same file, git takes both changes automatically. Most merges succeed this way, and you never notice.
A conflict happens only when both sides changed the same lines (or lines right next to each other) in different ways. Git has no way to know whether "Critical" or "Urgent" is the right picklist value in your escalation Flow. That is a business decision, so git hands it to you.
Two things follow from this:
- Nothing is broken. Git has paused and is waiting for a decision.
- The conflict is in your local copy, not in Salesforce. No org has been touched. You can take your time.
The figure below plays out the example used in the rest of this article. Switch to "How you resolve it" to see the steps in order.
One Flow, two changes
Two admins change the same line of the same Flow. Point at each hop to see what git does at that moment.
Red hops are where git stops and asks you to decide.
Why Salesforce Metadata Conflicts More Than Code
Developers working in Apex run into conflicts too, but admins often hit them sooner, because of the shape of the files they edit.
| Metadata type | Why it conflicts | How bad |
|---|---|---|
| Flow | One XML file holds the whole Flow, including the canvas position (locationX, locationY) of every element. Dragging an element on the canvas changes lines nobody meant to change. |
High |
| Profile | Very large files. A retrieve only includes permissions for the components retrieved alongside it, so two admins retrieving different scopes produce very different files. | High |
| Custom Labels | Every label in the org lives in one file, CustomLabels.labels-meta.xml, sorted by name. Two people adding labels with similar names edit the same spot. |
Medium |
| Page Layout, Lightning Page | One file per layout. Any field moved on the layout rewrites a block of XML. | Medium |
| Permission Set | One file per permission set by default. Two people adding field permissions to the same set often collide. | Medium |
| Custom Object and fields | Low. In source format each field is already its own file (fields/My_Field__c.field-meta.xml), so two admins adding different fields never touch the same file. |
Low |
The pattern: the bigger the file and the more people who edit it, the more conflicts you get. Keep this in mind for the prevention section at the end.
Walkthrough: Resolving a Real Flow Conflict
A Flow called Case_Escalation checks whether a case's priority equals a picklist value.
- You, on branch
feature/escalate-critical, changed the value fromHightoCritical. - A colleague changed the same value from
HightoUrgent, and their merge request was merged intomainfirst.
Substitute your own branch name, file and org alias as you follow along.
Step 1: Make sure you have no unsaved work
git status
You want to see nothing to commit, working tree clean. If you have changes, commit them first:
git add force-app/
git commit -m "feat: escalate critical cases only"
Git will not start a merge on top of uncommitted changes to the same file. If you skip this step you will see the "local changes would be overwritten" error covered in Troubleshooting below.
Step 2: Bring in the latest main
git fetch origin
git merge origin/main
git fetch downloads what is new on the remote without changing your files. git merge origin/main then tries to combine main into your branch. With a conflict, you see:
Auto-merging force-app/main/default/flows/Case_Escalation.flow-meta.xml
CONFLICT (content): Merge conflict in force-app/main/default/flows/Case_Escalation.flow-meta.xml
Automatic merge failed; fix conflicts and then commit the result.
You are now in the middle of a merge. Git stays in this state until you either finish the merge or abort it.
Step 3: List the conflicted files
git status
On branch feature/escalate-critical
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: force-app/main/default/flows/Case_Escalation.flow-meta.xml
Every file under Unmerged paths needs your attention. Files that merged cleanly are already staged and you can ignore them.
Step 4: Read the conflict markers
Open the file in VS Code or any text editor. Git has written both versions into it, wrapped in markers:
<rightValue>
<<<<<<< HEAD
<stringValue>Critical</stringValue>
=======
<stringValue>Urgent</stringValue>
>>>>>>> origin/main
</rightValue>
How to read it:
<<<<<<< HEADstarts your version.HEADmeans "the branch I am on".=======separates the two versions.>>>>>>> origin/mainends their version, the one coming in frommain.
Everything outside the markers merged cleanly and is fine as it is.
Step 5: Decide, then edit
This is the only step that needs judgement, and the judgement usually comes from talking to your colleague. Message them. In this case you agree the business wants Urgent, because that is the value the support team renamed the picklist to.
Edit the file so it contains only the version you want, and delete all three marker lines:
<rightValue>
<stringValue>Urgent</stringValue>
</rightValue>
You have three choices for every conflict:
| Choice | When to use it |
|---|---|
| Keep yours | Your change supersedes theirs |
| Keep theirs | Their change supersedes yours, or yours is no longer needed |
| Keep both | The two changes are independent additions, for example two new Custom Labels |
When the whole file should be one side or the other, you can skip the editing and let git pick:
# Keep your branch's version of the whole file
git checkout --ours force-app/main/default/flows/Case_Escalation.flow-meta.xml
# Keep main's version of the whole file
git checkout --theirs force-app/main/default/flows/Case_Escalation.flow-meta.xml
"Ours" and "theirs" here assume you are merging main into your branch, as in this walkthrough. They swap meaning during a rebase, which is one reason this series sticks to merge.
Step 6: Check you removed every marker
A leftover <<<<<<< line makes the XML invalid and the next deploy fails. Search for them:
git diff --check
If this prints nothing, no markers remain. If it prints lines such as leftover conflict marker, go back and fix those lines.
Step 7: Mark the file resolved and finish the merge
git add force-app/main/default/flows/Case_Escalation.flow-meta.xml
git commit --no-edit
git add on a conflicted file means "I have resolved this one". When every file is added, git commit completes the merge. --no-edit accepts git's default message, Merge remote-tracking branch 'origin/main' into feature/escalate-critical. Without it, git opens a text editor for the message (see Troubleshooting if that happens).
Check your history:
git log --oneline --graph -5
* 0a04e97 Merge remote-tracking branch 'origin/main' into feature/escalate-critical
|\
| * 085cded feat: rename high priority to urgent
* | 36025bf feat: escalate critical cases only
|/
* 699f3ae chore: baseline Case_Escalation flow
The graph shows both lines of work joining at your merge commit.
Step 8: Validate against a sandbox
Git only knows about text. It cannot tell you whether the merged XML is valid Salesforce metadata. A check-only deploy can:
sf project deploy start --source-dir force-app/main/default/flows --target-org mysandbox --dry-run
--dry-run validates everything and saves nothing to the org. If it succeeds, the resolution is sound. If it fails, the error usually points at the exact element. See Deploying Metadata with sf CLI for reading deploy results.
Step 9: Push
git push
Your merge request updates on its own. The conflict warning disappears and the merge button becomes available again.
Resolving Conflicts in VS Code
If you use VS Code (see VS Code as your git merge tool), you do not have to edit the markers by hand. Open the conflicted file and VS Code highlights each conflict with clickable options above it:
- Accept Current Change keeps your version (the
HEADside) - Accept Incoming Change keeps the version from
main - Accept Both Changes keeps both, one after the other
- Compare Changes shows them side by side
VS Code also offers a Resolve in Merge Editor button with a three-pane view: theirs, yours, and the result. Once the file has no conflicts left, save it, then run git add and git commit as above, or use the Source Control panel.
If you prefer a dedicated tool, git mergetool opens whichever merge tool you have configured. git difftool vs mergetool explains the difference, and Configuring Meld on Windows covers a free option.
A Playbook for Each Metadata Type
Clicking "Accept Both" is the right answer for some files and wrong for others. These are the types that cause most of the trouble.
Pick the file git listed under Unmerged paths to get the short answer. The sections after it explain each case.
Which kind of file is in conflict?
Keep mine, keep theirs, or keep both?
Start with the file git lists under Unmerged paths. The panel tells you what to keep and how to check it.
Flows: pick one side, rebuild the other change
Flow XML is generated by Flow Builder, not written by people. Element order, connector references and canvas coordinates all depend on each other. If you combine two versions line by line, you can end up with a connector pointing at an element that no longer exists, or two elements claiming the same name. Sometimes the deploy catches it. Sometimes it deploys and the Flow behaves differently from either version.
The safe procedure:
-
Keep one version of the whole file, usually theirs, since it is already on
mainand reviewed:git checkout --theirs force-app/main/default/flows/Case_Escalation.flow-meta.xml git add force-app/main/default/flows/Case_Escalation.flow-meta.xml git commit --no-edit -
Deploy that version to your sandbox so the sandbox matches
main:sf project deploy start --metadata "Flow:Case_Escalation" --target-org mysandbox -
Open the Flow in Flow Builder in your sandbox and re-apply your change by hand. Save and activate.
-
Retrieve and commit as normal:
sf project retrieve start --metadata "Flow:Case_Escalation" --target-org mysandbox git add force-app/main/default/flows/Case_Escalation.flow-meta.xml git commit -m "feat: re-apply critical escalation on top of urgent rename"
It takes ten minutes longer than editing XML, and the result is a Flow that Flow Builder itself produced.
Exception: a conflict that touches only locationX and locationY lines means someone moved an element on the canvas. It has no effect on behaviour. Take either side.
Profiles: retrieve the same scope, then compare
Profile conflicts are usually noise. A Profile retrieve only includes permissions for the components retrieved alongside it, so if you retrieved the Profile with one object and your colleague retrieved it with ten, the files differ enormously even though nobody changed the Profile.
Before resolving, agree on one retrieve scope for Profiles in your project (a package.xml that lists the Profile together with every object, field and class it should cover). Re-retrieve the Profile with that scope, then compare. Most of the "conflict" usually disappears. Fetching Security Metadata covers Profile retrieval patterns in detail.
Better still, put new access in permission sets and keep Profiles as thin as you can. Small files conflict less.
Custom Labels: usually keep both
Two people adding different labels is the classic "keep both" case. The conflict looks like this:
<<<<<<< HEAD
<labels>
<fullName>Case_Escalated_Message</fullName>
<language>en_US</language>
<protected>false</protected>
<shortDescription>Case Escalated Message</shortDescription>
<value>This case has been escalated.</value>
</labels>
=======
<labels>
<fullName>Case_Closed_Message</fullName>
<language>en_US</language>
<protected>false</protected>
<shortDescription>Case Closed Message</shortDescription>
<value>This case is now closed.</value>
</labels>
>>>>>>> origin/main
Keep both <labels> blocks and delete the markers. Check that the two fullName values are different. If both sides added a label with the same fullName, that is a real conflict about the label's text: pick one and delete the other block, because a label name must be unique.
Page Layouts and Lightning Pages: pick one, re-apply in Setup
Treat these like Flows. Layout XML encodes the position of every field and section, and a line-by-line merge can produce a layout with the same field placed twice. Keep one side, deploy it to your sandbox, re-apply the other change in the layout editor or Lightning App Builder, retrieve and commit.
Permission Sets: usually keep both, check for duplicates
Two people granting different field permissions to the same permission set produce independent <fieldPermissions> blocks. Keep both. If both sides changed the same field's permission (one read-only, one edit), decide which is correct and keep a single <fieldPermissions> block for that field. Keeping both leaves the permission set contradicting itself.
The Other Kind of Conflict: Your Org and Your Project Disagree
sf CLI also uses the word "conflict", and it means something different. This one has nothing to do with git branches.
If source tracking is on for your org (scratch orgs always have it, and Developer and Developer Pro sandboxes have it when an admin enables it in production's Dev Hub settings), sf CLI remembers which components changed in the org and which changed locally since your last retrieve or deploy. If the same component changed in both places, sf CLI stops instead of overwriting one with the other. It retrieves or deploys nothing and lists the conflicting components.
You resolve it by choosing a side:
# The org has the right version: overwrite your local file with it
sf project retrieve start --metadata "Flow:Case_Escalation" --target-org mysandbox --ignore-conflicts
# Your local file has the right version: overwrite the org with it
sf project deploy start --metadata "Flow:Case_Escalation" --target-org mysandbox --ignore-conflicts
--ignore-conflicts does exactly what it says: it overwrites the other side without asking. Before using it, commit your local file to git. If you overwrite it with the org version and change your mind, git diff shows what was lost and git checkout -- <file> brings it back.
| Git merge conflict | sf CLI source tracking conflict | |
|---|---|---|
| Between | Two git branches | Your local project and an org |
| Where you see it | git merge, or your merge request |
sf project retrieve start or deploy start |
| How to resolve | Edit the file, git add, git commit |
Choose a side with --ignore-conflicts |
| Can combine both? | Yes | No, one side overwrites the other |
Preventing Conflicts
You cannot remove conflicts entirely, but you can make them small and rare.
Merge main into your branch often. A branch that lives for three weeks collects three weeks of other people's changes. Run git fetch origin and git merge origin/main every day or two. Each merge is small and the conflicts, if any, are fresh in everyone's memory.
Keep branches small. One change per branch: one Flow fix, one new field and its permissions. Small branches merge quickly, and a branch that merges quickly cannot fall far behind.
Agree who owns busy files this sprint. If two people need to change the same Flow in the same week, decide who goes first. A two-line message in your team channel prevents most Flow conflicts.
Retrieve only what you changed. sf project retrieve start --metadata "Flow:Case_Escalation" touches one file. Retrieving the whole org rewrites hundreds of files, and every one of them is a chance to collide with someone else's work.
Show the original in conflict markers. This git setting adds a third section showing what the lines said before either change, which makes the right answer much easier to see:
git config --global merge.conflictStyle zdiff3
The markers then look like this, with the common ancestor between ||||||| and =======:
<<<<<<< HEAD
<stringValue>Critical</stringValue>
||||||| 699f3ae
<stringValue>High</stringValue>
=======
<stringValue>Urgent</stringValue>
>>>>>>> origin/main
zdiff3 needs git 2.35 or later. Run git --version to check; on older versions use diff3.
Consider decomposing permission sets and custom labels. sf CLI can split a permission set into one small file per object and setting, and split Custom Labels into one file per label, so two people editing different parts no longer touch the same file. This feature is in beta, and it changes the file layout for everyone on the project, so agree it as a team and check that your deployment tool understands the new format first. Try it without changing anything:
sf project convert source-behavior --behavior decomposePermissionSetBeta2 --dry-run
Drop --dry-run to apply it. You run it once per project, and it updates sfdx-project.json for you. Do not edit that setting by hand.
Troubleshooting
"error: Merging is not possible because you have unmerged files."
You are still in the middle of an earlier merge. Either finish it (resolve the files, git add, git commit) or abandon it with git merge --abort. The same cause produces "Pulling is not possible because you have unmerged files" when you run git pull.
"fatal: cannot switch branch while merging"
Same cause. Finish or abort the merge before switching branches.
"error: Your local changes to the following files would be overwritten by merge"
You have uncommitted changes to a file that the merge also changes. Git refuses to start so it cannot destroy your work. Commit your changes first (Step 1), then merge again.
"fatal: refusing to merge unrelated histories"
The two branches do not share a starting commit. This usually means someone ran git init in a fresh folder and pushed it, instead of cloning the existing repository. Do not force the merge. Clone the repository again, copy your changed files into the fresh clone, and commit them there.
A text editor opened when I ran git commit
You ran git commit without --no-edit or -m, so git opened its default editor (usually vim) with a pre-filled merge message. To accept it in vim, type :wq and press Enter. Next time use git commit --no-edit.
I committed a file that still has conflict markers in it
The deploy fails on invalid XML, or a reviewer spots <<<<<<< in the diff. Find every leftover marker in the project:
git grep -n "<<<<<<<"
Fix each file, then commit the fix:
git add force-app/
git commit -m "fix: remove leftover conflict markers"
git push
I resolved it wrong and already pushed
Nothing is lost; every version is in git's history. Find the merge commit with git log --oneline, then look at what each side had with git show <commit-hash>. Restore the correct version of the file from the right commit, commit it as a new change, and push. If you are unsure, ask whoever owns the repository before running anything with reset or --force in it.
Frequently Asked Questions
Q: Which version should I keep, mine or the one in main?
A: Git cannot answer this and neither can a tool. It is a business question. The version on main has usually already been reviewed and may already be in production, so the default is to keep theirs and re-apply your change on top. Keep yours only when you know your change replaces theirs, and tell the colleague whose change you are discarding.
Q: Can I just cancel the merge and pretend it never happened?
A: Yes, as long as you have not committed the merge yet. git merge --abort returns your branch and every file to exactly how they were before you ran git merge. The conflict will still be there the next time you merge, but you can come back to it after talking to your colleague.
Q: Should I use git rebase instead of git merge?
A: Not while you are learning. Rebase rewrites your branch's history, swaps the meaning of "ours" and "theirs", and can ask you to resolve the same conflict several times, once per commit. Merging main into your branch is easier to understand and easier to undo. Many teams squash-merge the final merge request anyway, so the extra merge commits never reach main.
Q: Do DevOps tools like Gearset or Copado remove merge conflicts?
A: They make them easier to resolve, not impossible to get. These tools understand Salesforce metadata, so they can merge some XML (Profiles in particular) more intelligently than plain git. Two people changing the same Flow element to different values is still a conflict in any tool, because only a person can decide which value the business wants.
Q: Why did I get a conflict on a Flow when I only moved one element?
A: Moving an element on the Flow Builder canvas changes its locationX and locationY values in the XML. If a colleague moved the same element, or changed nearby lines, git sees two edits in the same place. Position-only conflicts do not affect how the Flow runs, so take either side.
Key Takeaways
- A merge conflict means two branches changed the same lines and git needs a person to decide; nothing in any org has changed
- The procedure never changes:
git fetch,git merge origin/main, edit each conflicted file,git add,git commit --no-edit, validate with--dry-run,git push git merge --abortis always available until you commit, so you can never get stuck- For Flows, Layouts and Lightning Pages, keep one version and re-apply the other change in Setup; never stitch generated XML together by hand
- Custom Labels and Permission Sets are usually "keep both", as long as you check for duplicate names
- sf CLI's "conflict" is a separate problem between your project and an org, resolved by choosing a side with
--ignore-conflicts - Merging
mainoften, keeping branches small and agreeing who owns busy files prevents most conflicts
What's Next?
This article completes the workflow from the earlier parts of the series:
- Org-Dependent Packages: Edit in a Sandbox, Pull Changes, Create a Merge Request: the branch and merge request workflow that leads to the conflicts covered here
- Deploying Salesforce Metadata with sf CLI: check-only deploys and how to read the results when a resolution does not validate
- Git Basics for Salesforce Admins: a refresher on
status,diff,addandcommitif any of the commands above felt unfamiliar
Action items:
- Run
git config --global merge.conflictStyle zdiff3now, before your next conflict - Practise once on purpose: create two branches that change the same Custom Label, merge one, then merge
maininto the other and resolve it - Agree with your team who owns your busiest Flows for the current sprint
Resources & References
- Basic merge conflicts: Pro Git book: the official git explanation of how conflicts arise and how to resolve them
- git merge documentation: How conflicts are presented: reference for conflict markers and the
diff3andzdiff3styles - Resolve Conflicts Between Your Local Project and Org: Salesforce DX Developer Guide: Salesforce's guidance on source tracking conflicts and
--ignore-conflicts - Decomposed Metadata Types: Salesforce DX Developer Guide: which metadata types are split into smaller files and how to opt in to the beta behaviours
- project convert source-behavior: Salesforce CLI Reference: flags and accepted values for decomposing permission sets and custom labels
- Resolving merge conflicts in VS Code: the inline actions and the three-way merge editor
Responses
Checking your session.
Loading responses.