An intelligent Git merge driver specifically designed for Salesforce metadata files. Eliminates hours of manual merge conflicts resolution.
- Saves time: No more manual XML conflict resolution
- Zero-config: Works immediately after installation
- Reliable: Understands Salesforce metadata structure
- Transparent: Seamless Git workflow integration
sequenceDiagram
participant Dev
participant Git
participant MergeDriver
Dev->>Git: git merge
Git->>MergeDriver: Detects XML conflict
MergeDriver->>MergeDriver: Analyzes metadata structure
MergeDriver->>MergeDriver: Smart merging
MergeDriver->>Git: Returns merged result
Git->>Dev: Clean commit (no conflicts)
This merge driver follows the zdiff3 conflict style philosophy:
- Shows the most compact diff possible: Only the specific conflicting elements are marked, not entire file sections
- Includes ancestor context: Conflicts display the base (ancestor) version alongside local and remote changes
- Respects Git configuration: Conflict marker size and labels are configurable via Git's standard parameters (
-L,-S,-X,-Yflags)
Example conflict output:
<<<<<<< ours
<field>localValue</field>
||||||| base
<field>originalValue</field>
=======
<field>remoteValue</field>
>>>>>>> theirsThis approach helps you understand:
- What the original value was (
base) - What your branch changed it to (
ours) - What the other branch changed it to (
theirs)
For metadata types where element order matters (like picklist values), the merge driver preserves ordering while intelligently merging changes:
- Disjoint reorderings merge automatically: If one branch reorders elements A↔B and another reorders C↔D, both changes are preserved
- Conflicting reorderings trigger conflict: If both branches move the same element to different positions, a conflict is raised for manual resolution
- Additions respect position: New elements are inserted at their intended position
Supported ordered metadata:
GlobalValueSet/StandardValueSet→ picklist valuesCustomField→ value set entriesRecordType→ picklist value assignments
This ensures picklist value ordering in your org matches what you expect after a merge.
# Install plugin (one time, global)
sf plugins install sf-git-merge-driver
# Configure merge driver in your project (one time, local per project)
cd my/sf/project
sf git merge driver installUpgrading from a previous version: re-run
sf git merge driver installin each repository after upgrading. Recent releases route conflicts through a bundled standalone binary (~80 ms per file instead of ~600 ms via the oclif command) — a 200-file rebase drops from ~120 s to ~16 s of merge-driver overhead. Previously-installed.git/configentries keep working via the retained oclif command, but do not receive the speedup until reinstall.Re-installing also wires git's
%Splaceholder into conflict markers: the ancestor-marker label now reflects git's own label (typically a short SHA) instead of the staticbasestring. See the CHANGELOG for the exact before/after.Safe install on upgrade. If another merge driver is already configured on one of our metadata globs (e.g. a different Salesforce tool added its own
.git/info/attributesline), install now aborts with a conflict report instead of silently stacking up. Use--on-conflict=skipto leave those globs alone,--on-conflict=overwrite(or--force) to take them over — in which case uninstall will restore the originals from an annotation comment. Add--dry-runto preview any install/uninstall plan before writing.
SFDX-Hardis VS Code extension has integrated the sf-git-merge-driver in its default dependencies, and offers a one click activation of the merge driver from the status bar, as shown below:
The merge driver activates automatically for conflicts on:
No additional steps required! Works during normal Git operations:
git pull # Conflicts resolved automatically
git merge # Same hereConfigured for these metadata files by default:
*.profile-meta.xml merge=salesforce-source
*.permissionset-meta.xml merge=salesforce-source
*.labels-meta.xml merge=salesforce-source
*.label-meta.xml merge=salesforce-source
*.applicationVisibility-meta.xml merge=salesforce-source
*.classAccess-meta.xml merge=salesforce-source
*.customMetadataTypeAccess-meta.xml merge=salesforce-source
*.customPermission-meta.xml merge=salesforce-source
*.customSettingAccess-meta.xml merge=salesforce-source
*.externalCredentialPrincipalAccess-meta.xml merge=salesforce-source
*.externalDataSourceAccess-meta.xml merge=salesforce-source
*.fieldPermission-meta.xml merge=salesforce-source
*.flowAccess-meta.xml merge=salesforce-source
*.objectPermission-meta.xml merge=salesforce-source
*.pageAccess-meta.xml merge=salesforce-source
*.recordTypeVisibility-meta.xml merge=salesforce-source
*.tabSetting-meta.xml merge=salesforce-source
*.userPermission-meta.xml merge=salesforce-source
*.objectSettings-meta.xml merge=salesforce-source
*.permissionsetgroup-meta.xml merge=salesforce-source
*.permissionSetLicenseDefinition-meta.xml merge=salesforce-source
*.mutingpermissionset-meta.xml merge=salesforce-source
*.sharingRules-meta.xml merge=salesforce-source
*.sharingCriteriaRule-meta.xml merge=salesforce-source
*.sharingGuestRule-meta.xml merge=salesforce-source
*.sharingOwnerRule-meta.xml merge=salesforce-source
*.sharingTerritoryRule-meta.xml merge=salesforce-source
*.workflow-meta.xml merge=salesforce-source
*.workflowAlert-meta.xml merge=salesforce-source
*.workflowFieldUpdate-meta.xml merge=salesforce-source
*.workflowFlowAction-meta.xml merge=salesforce-source
*.workflowKnowledgePublish-meta.xml merge=salesforce-source
*.workflowOutboundMessage-meta.xml merge=salesforce-source
*.workflowRule-meta.xml merge=salesforce-source
*.workflowSend-meta.xml merge=salesforce-source
*.workflowTask-meta.xml merge=salesforce-source
*.assignmentRules-meta.xml merge=salesforce-source
*.autoResponseRules-meta.xml merge=salesforce-source
*.escalationRules-meta.xml merge=salesforce-source
*.marketingappextension-meta.xml merge=salesforce-source
*.matchingRule-meta.xml merge=salesforce-source
*.globalValueSet-meta.xml merge=salesforce-source
*.standardValueSet-meta.xml merge=salesforce-source
*.globalValueSetTranslation-meta.xml merge=salesforce-source
*.standardValueSetTranslation-meta.xml merge=salesforce-source
*.translation-meta.xml merge=salesforce-source
*.objectTranslation-meta.xml merge=salesforce-sourceWhen you don't want to use the merge driver for a specific merge, just backup the .git/info/attributes file and put it back after the merge.
mv .git/info/attributes .git/info/attributes.bak
git merge <branch>
mv .git/info/attributes.bak .git/info/attributesIf you want to disable the merge driver for a specific file, just comment the merge driver configuration from the .git/info/attributes file.
# *.profile-meta.xml merge=salesforce-sourceIf you want to disable it for all the project, just uninstall the driver:
sf git merge driver uninstallYou can check if the merge driver is installed by running the following command:
git config --show-origin --get-regexp '^merge.salesforce-source(\..*)?'You can check if the merge driver is enabled by running the following command:
grep "merge=salesforce-source" .git/info/attributesThe plugin writes to $GIT_COMMON_DIR/info/attributes. The installer creates the info/ directory and the attributes file on demand if either is missing (some git init implementations — notably Apple Git ≥ 2.50 — do not create info/ on a fresh repository), so the only precondition is a real git repository: ensure the repository is cloned / checked out / git init'd in your CI/CD context before running install.
The installer resolves the attributes file via git rev-parse --git-common-dir, so a single install applies correctly across all of these layouts:
| Layout | Where attributes are written | Notes |
|---|---|---|
| Standard repo | .git/info/attributes |
Default case. |
Linked worktrees (git worktree add) |
<main>/.git/info/attributes |
Single install applies to every worktree of the repository — install once, no need to re-run in each worktree. |
| Submodules | <super>/.git/modules/<sub>/info/attributes |
Run install from inside the submodule; it registers the driver against that submodule's git dir. |
| Bare repos | <bare-repo>/info/attributes |
Works without a working tree (useful for server-side merges or scripted recovery). |
Custom GIT_DIR (env var) |
$GIT_DIR/info/attributes |
Honoured. Path is canonicalised, so traversal components like GIT_DIR=../.. cannot escape the repository. |
Most users never invoke the driver directly — git does. But for scripted recovery
(e.g. replaying a merge after a botched rebase, batch-resolving conflicts in CI,
or diagnosing a specific file pair) you can call the bundled binary directly. It
has no sf CLI / oclif startup cost (~37 ms cold vs ~500 ms via sf git merge driver run,
which is why git itself also bypasses the sf CLI once sf git merge driver install
has been run).
Locate the binary from the sf-installed plugin (no separate npm install -g needed —
this is the same copy git uses, so there is no version-drift risk):
# Unix/Linux version — Resolve the plugin root from sf's plugin registry (user-installed plugins)
PLUGIN_ROOT=$(dirname "$(sf plugins inspect sf-git-merge-driver --json | jq -r '.[0].options.root')")/node_modules/sf-git-merge-driver
DRIVER="$PLUGIN_ROOT/bin/merge-driver.cjs"
# Powershell version — Resolve the plugin root from sf's plugin registry (user-installed plugins)
$DRIVER = Join-Path (Join-Path (Split-Path ((sf plugins inspect sf-git-merge-driver --json | ConvertFrom-Json)[0].options.root) -Parent) "node_modules/sf-git-merge-driver") "bin/merge-driver.cjs"
# Execution
# POSIX — the binary has a shebang and +x, so it's directly executable:
"$DRIVER" -O base.xml -A local.xml -B other.xml -P merged.xml
# Windows / anywhere shebangs aren't honoured, invoke via node explicitly:
node "$DRIVER" -O base.xml -A local.xml -B other.xml -P merged.xmlFlags (git's merge-driver %O %A %B %P convention):
-Oancestor file,-Alocal file,-Bother file,-Poutput file — the merged result is written back over-A(matching git's merge-driver contract);-Pis accepted for compatibility but not used as the output target.-Lconflict marker size (1–100, default 7).-S/-X/-Yconflict tag labels for ancestor / local / other.
Exit codes (scripts should branch on these, not on stderr content):
0— clean merge, no conflict markers written.1— merge completed with conflict markers in the output.2— usage error (missing/unreadable file, bad flag, unsupported Node version).
Example — batch-resolve all profile conflicts in the working tree:
PLUGIN_ROOT=$(dirname "$(sf plugins inspect sf-git-merge-driver --json | jq -r '.[0].options.root')")/node_modules/sf-git-merge-driver
DRIVER="$PLUGIN_ROOT/bin/merge-driver.cjs"
for f in $(git diff --name-only --diff-filter=U | grep '\.profile-meta\.xml$'); do
git show :1:"$f" > /tmp/base.xml # :1: = common ancestor
git show :2:"$f" > /tmp/local.xml # :2: = our side
git show :3:"$f" > /tmp/other.xml # :3: = their side
node "$DRIVER" -O /tmp/base.xml -A /tmp/local.xml -B /tmp/other.xml -P /tmp/merged.xml || true
cp /tmp/local.xml "$f" # -A receives the merged (or conflict-marked) result
done
sf git merge driver runis a thin oclif wrapper kept only for backward compatibility with.git/configentries generated by sf-git-merge-driver ≤ 1.5. It is deprecated and scheduled for removal in the next major release. Use the binary directly for scripts.
If the merge driver encounters a list of elements (like fields in a profile, or permissions in a permission set) but does not know which field acts as the "key" (unique identifier) for that type, it will fallback to standard conflict behavior. This means you might see a conflict block containing the entire array instead of a smart merge of individual elements.
If you encounter this behavior for a Salesforce metadata type the driver is supposed to handle, please open an issue! We can add the missing key definition to support smart merging for that type.
If you encounter this behavior for a Salesforce metadata type the driver does not already handle, please open an issue! We can evaluate how to support smart merging for that type.
The plugin uses the Salesforce CLI logging system to log information.
You can control the logging level by setting the SF_LOG_LEVEL environment variable.
You can redirect the logging in the terminal using DEBUG=sf-git-merge-driver.
You can also use GIT_TRACE=1 to get more information about git operations.
You can also use GIT_MERGE_VERBOSITY=5 to get more information about the merge process.
Git environment variables are detailed here.
Example:
DEBUG=sf-git-merge-driver
SF_LOG_LEVEL=trace # can be error | warn | info | debug | trace, default: warn
GIT_MERGE_VERBOSITY=5 # can be 0 to 5
GIT_TRACE=true
git merge ...sf git merge driver disablesf git merge driver enablesf git merge driver installsf git merge driver runsf git merge driver uninstall
Uninstalls the local git merge driver from the current project.
USAGE
$ sf git merge driver disable [--json] [--flags-dir <value>] [--dry-run]
FLAGS
--dry-run Plan the uninstall without touching git config or .git/info/attributes. Exits 0; shows what would be
removed.
GLOBAL FLAGS
--flags-dir=<value> Import flag values from a directory.
--json Format output as json.
DESCRIPTION
Uninstalls the local git merge driver from the current project.
Removes the `merge.salesforce-source` section from `.git/config` and strips the driver's rules from
`.git/info/attributes`. Lines that combined the driver with user attributes (e.g. `*.profile-meta.xml text=auto
merge=salesforce-source`) are rewritten to keep the user attributes; only the `merge=` token is removed. If a previous
install used `--on-conflict=overwrite`, the original driver rule is restored from the annotation comment written at
install time. `--dry-run` previews the plan without writing.
ALIASES
$ sf git merge driver disable
EXAMPLES
Uninstall the driver for a given project:
$ sf git merge driver disable
Preview the changes that would be written:
$ sf git merge driver disable --dry-run
Installs a local git merge driver for Salesforce metadata in the current project.
USAGE
$ sf git merge driver enable [--json] [--flags-dir <value>] [--dry-run] [--on-conflict abort|skip|overwrite] [--force]
FLAGS
--dry-run Plan the install without writing to git config or .git/info/attributes. Exits 0; shows the
list of rules that would be added/skipped/conflict.
--force Alias for --on-conflict=overwrite. Non-interactive shortcut for CI.
--on-conflict=<option> [default: abort] How to handle patterns already owned by another merge driver in
.git/info/attributes. Default: abort (refuse to change anything).
<options: abort|skip|overwrite>
GLOBAL FLAGS
--flags-dir=<value> Import flag values from a directory.
--json Format output as json.
DESCRIPTION
Installs a local git merge driver for Salesforce metadata in the current project.
Registers the driver in `.git/config` and adds one merge rule per Salesforce metadata glob to `.git/info/attributes`.
Safe to re-run: install is idempotent, preserves any user attributes already on the globs, and dedupes legacy
duplicate rules silently.
If another merge driver is already configured on one of our globs, install aborts by default and lists the conflicts.
Pass `--on-conflict=skip` to leave those globs to the other driver, `--on-conflict=overwrite` (or `--force`) to take
them over (uninstall restores the originals), or run the command from a TTY to be prompted interactively. `--dry-run`
previews the plan without writing.
ALIASES
$ sf git merge driver enable
EXAMPLES
Install the driver for a given project:
$ sf git merge driver enable
Preview the changes that would be written:
$ sf git merge driver enable --dry-run
Take over conflicting globs non-interactively (for CI):
$ sf git merge driver enable --force
Installs a local git merge driver for Salesforce metadata in the current project.
USAGE
$ sf git merge driver install [--json] [--flags-dir <value>] [--dry-run] [--on-conflict abort|skip|overwrite]
[--force]
FLAGS
--dry-run Plan the install without writing to git config or .git/info/attributes. Exits 0; shows the
list of rules that would be added/skipped/conflict.
--force Alias for --on-conflict=overwrite. Non-interactive shortcut for CI.
--on-conflict=<option> [default: abort] How to handle patterns already owned by another merge driver in
.git/info/attributes. Default: abort (refuse to change anything).
<options: abort|skip|overwrite>
GLOBAL FLAGS
--flags-dir=<value> Import flag values from a directory.
--json Format output as json.
DESCRIPTION
Installs a local git merge driver for Salesforce metadata in the current project.
Registers the driver in `.git/config` and adds one merge rule per Salesforce metadata glob to `.git/info/attributes`.
Safe to re-run: install is idempotent, preserves any user attributes already on the globs, and dedupes legacy
duplicate rules silently.
If another merge driver is already configured on one of our globs, install aborts by default and lists the conflicts.
Pass `--on-conflict=skip` to leave those globs to the other driver, `--on-conflict=overwrite` (or `--force`) to take
them over (uninstall restores the originals), or run the command from a TTY to be prompted interactively. `--dry-run`
previews the plan without writing.
ALIASES
$ sf git merge driver enable
EXAMPLES
Install the driver for a given project:
$ sf git merge driver install
Preview the changes that would be written:
$ sf git merge driver install --dry-run
Take over conflicting globs non-interactively (for CI):
$ sf git merge driver install --force
See code: src/commands/git/merge/driver/install.ts
Runs the merge driver for the specified files.
USAGE
$ sf git merge driver run -O <value> -A <value> -B <value> -P <value> [--json] [--flags-dir <value>] [-L <value>] [-S
<value>] [-X <value>] [-Y <value>]
FLAGS
-A, --local-file=<value> (required) path to our version of the file
-B, --other-file=<value> (required) path to their version of the file
-L, --conflict-marker-size=<value> [default: 7] number of characters to show for conflict markers
-O, --ancestor-file=<value> (required) path to the common ancestor version of the file
-P, --output-file=<value> (required) path to the file where the merged content will be written
-S, --ancestor-conflict-tag=<value> [default: base] string used to tag ancestor version in conflicts
-X, --local-conflict-tag=<value> [default: ours] string used to tag local version in conflicts
-Y, --other-conflict-tag=<value> [default: theirs] string used to tag other version in conflicts
GLOBAL FLAGS
--flags-dir=<value> Import flag values from a directory.
--json Format output as json.
DESCRIPTION
Runs the merge driver for the specified files.
DEPRECATED — will be removed in the next major release. Run `sf git merge driver install` in the repository to upgrade
to the faster standalone binary driver.
Runs the merge driver for the specified files, handling the merge conflict resolution using Salesforce-specific merge
strategies. This command is typically called automatically by Git when a merge conflict is detected.
EXAMPLES
Run the merge driver for conflicting files:
$ sf git merge driver run --ancestor-file=<value> --local-file=<value> --other-file=<value> \
--output-file=<value>
Where:
- ancestor-file is the path to the common ancestor version of the file
- local-file is the path to our version of the file
- other-file is the path to their version of the file
- output-file is the path to the file where the merged content will be written
See code: src/commands/git/merge/driver/run.ts
Uninstalls the local git merge driver from the current project.
USAGE
$ sf git merge driver uninstall [--json] [--flags-dir <value>] [--dry-run]
FLAGS
--dry-run Plan the uninstall without touching git config or .git/info/attributes. Exits 0; shows what would be
removed.
GLOBAL FLAGS
--flags-dir=<value> Import flag values from a directory.
--json Format output as json.
DESCRIPTION
Uninstalls the local git merge driver from the current project.
Removes the `merge.salesforce-source` section from `.git/config` and strips the driver's rules from
`.git/info/attributes`. Lines that combined the driver with user attributes (e.g. `*.profile-meta.xml text=auto
merge=salesforce-source`) are rewritten to keep the user attributes; only the `merge=` token is removed. If a previous
install used `--on-conflict=overwrite`, the original driver rule is restored from the annotation comment written at
install time. `--dry-run` previews the plan without writing.
ALIASES
$ sf git merge driver disable
EXAMPLES
Uninstall the driver for a given project:
$ sf git merge driver uninstall
Preview the changes that would be written:
$ sf git merge driver uninstall --dry-run
See code: src/commands/git/merge/driver/uninstall.ts
changelog.md is available for consultation.
Versioning follows SemVer specification.
Contributions are what make the trailblazer community such an amazing place. We regard this component as a way to inspire and learn from others. Any contributions you make are appreciated.
See contributing.md for sgd contribution principles.
This project license is MIT - see the LICENSE.md file for details


