Understanding conflict resolution in detail
Git conflicts are no reason to panic once you actually understand PhpStorm's three-way merge dialog. Base, Local and Remote side by side turn a confusing diff into a controllable, line-by-line decision.
Table of Contents
- 1. Why a merge tool does more than an editor with conflict markers
- 2. Reading Base, Local and Remote correctly
- 3. Accepting conflicts partially instead of all-or-nothing
- 4. Resolving composer.lock conflicts sensibly
- 5. package-lock.json and other generated files
- 6. Navigating between multiple conflicts in a project
- 7. Differences in the dialog between merge and interactive rebase
- 8. Preventing conflicts with smaller commits and branch hygiene
- 9. Practical workflow: from conflict to a clean commit
- 10. Summary
- 11. FAQ
1. Why a merge tool does more than an editor with conflict markers
Anyone who resolves conflicts directly in a file using the markers <<<<<<<, ======= and >>>>>>> is fighting two problems at once: understanding the conflict content and cleanly removing the text markers afterward without accidentally leaving a stray bracket or semicolon behind. This is exactly where PhpStorm's three-way merge dialog steps in, since it fully separates the content decision from the mechanical cleanup work.
The dialog shows three columns at once: Local on the left, Base in the middle, and Remote on the right, plus a fourth, editable result column where the final outcome is assembled. This spatial separation makes it immediately visible which side introduced which change, instead of fighting through nested text markers. For PHP projects with several developers working in parallel, that is the decisive difference between a merge finished in two minutes and one that ends with a syntax error in production.
2. Reading Base, Local and Remote correctly
Base is the common ancestor, meaning the state of the file before the two branches diverged. Local is the current state in your own working copy, meaning your own not-yet-merged changes. Remote is the state of the branch currently being merged or rebased in, for example main after a git pull, or a feature branch during a rebase. This mapping sounds trivial but gets mixed up regularly in practice, especially during a rebase, where Local and Remote can appear swapped compared to a normal merge.
PhpStorm highlights changes against Base in both outer columns in color, so it is immediately visible which lines are even affected by the conflict and which stayed unchanged. A common beginner mistake is ignoring the Base column because it is not editable. Yet it is exactly this column that provides the context needed to tell whether a change is a genuine content collision or whether both sides independently made the same sensible fix.
3. Accepting conflicts partially instead of all-or-nothing
The big advantage over a simple Accept Yours or Accept Theirs is that PhpStorm does not treat a conflict as a single block but splits it into individual change chunks. Each chunk gets its own arrow buttons, letting you pull exactly that excerpt from Local or Remote into the result column without affecting the other chunks in the file. That way a method from your own branch can be kept while a bug fix from the other branch is adopted, even though both live in the same file.
The result column itself is a normal editor: after a chunk is automatically accepted, you can keep typing freely there, for example to manually merge two conflicting parameter lists into a new, correct signature. This is the point where a real merge tool separates itself from a pure command-line solution, since nobody has to open the finished file separately and rework it afterward, everything happens in a single step.
// Example: conflict in a repository class
// Local added a new method, Remote extended the constructor.
// Both should be kept -- merge them manually in the result panel:
final class ProductAvailabilityChecker
{
public function __construct(
private readonly StockRegistryInterface $stockRegistry,
private readonly LoggerInterface $logger, // taken from Remote
) {
}
// Taken from Local, kept unchanged
public function isAvailable(int $productId): bool
{
$stockItem = $this->stockRegistry->getStockItem($productId);
return $stockItem->getIsInStock();
}
}
4. Resolving composer.lock conflicts sensibly
For composer.lock, the three-way merge dialog is almost always the wrong level, since the file is a generated artifact with hash checksums that cannot be meaningfully edited by hand. The reliable path is to resolve the conflict in composer.json manually, correctly merging both sides' version requirements, and then regenerate composer.lock completely instead of resolving the conflict markers in the lock file itself.
In PhpStorm, composer.lock can therefore be resolved fastest by using Accept Theirs or Accept Yours as a placeholder to formally close the conflict, followed by a composer update or composer install in the terminal that regenerates the file from the correct composer.json. Anyone who instead tries to manually merge individual package hashes almost always ends up with a lock file that no longer matches the actual composer.json and fails validation on the next CI run.
5. package-lock.json and other generated files
The same logic applies to package-lock.json in frontend build toolchains: the merge dialog is useful for quickly picking one side as an intermediate state, but the actual source of truth lives in package.json. After resolving the conflict there, npm install or the equivalent command of whichever package manager is in use should run so the lock file is consistent with the versions actually installed.
A practical trick in PhpStorm is to treat such generated files as 'binary', or route them through a merge=ours or merge=theirs driver via .gitattributes, so they never even show up in the interactive three-way dialog. That keeps the merge dialog reserved for files where a content decision is actually needed, and no time is wasted clicking through hash lines that will be regenerated anyway.
# .gitattributes: keep generated lock files out of the interactive merge dialog
composer.lock merge=ours
package-lock.json merge=ours
# enable locally (once per repository):
git config merge.ours.driver true
# after formally closing the conflict, regenerate the lock file:
composer update --lock
npm install --package-lock-only
6. Navigating between multiple conflicts in a project
In larger merges, conflicts rarely affect just one file. PhpStorm's merge view lists all conflicting files in an overview before the actual three-way dialog for the individual file is opened. From this list it is immediately visible whether a file contains just a single small conflict or a dozen scattered changes, which helps prioritize which file to tackle first.
Within a single file with multiple conflict chunks, the dialog offers navigation arrows to jump straight to the next or previous unresolved conflict without manually scrolling through the whole file. That matters especially for classes with hundreds of lines, for example generated GraphQL schema files or large configuration classes, where a single overlooked conflict chunk would otherwise slip unnoticed into the commit.
7. Differences in the dialog between merge and interactive rebase
In a classic git merge, Local corresponds to the current branch and Remote to the branch being merged in, which intuitively matches the usual reading direction. During an interactive rebase, this mapping effectively flips: since each commit is reapplied individually onto the target branch, Local often represents the target branch while Remote represents your own commit currently being replayed. Anyone who ignores this can easily accept the wrong side during a rebase, mistaking it for their own change.
PhpStorm shows additional context in the dialog's title bar during a rebase conflict, such as the hash and commit message of the commit currently being applied. It is worth reading this header deliberately before hastily clicking one of the two accept buttons, especially with longer rebase chains involving many individual commits, where the meaning of Local and Remote can even change from commit to commit.
8. Preventing conflicts with smaller commits and branch hygiene
Even the best merge dialog cannot replace good branch discipline. Long-running feature branches that go unsynchronized with main for weeks almost inevitably produce large, hard-to-resolve conflict volumes, since both sides have kept evolving independently in the meantime. Regularly rebasing or merging the target branch into your own feature branch keeps individual conflict chunks small and therefore quick to handle in the three-way dialog.
Team formatting settings also play an underestimated role: when two developers use different PHP-CS-Fixer or code style configurations, conflicts often arise not from content changes but purely from differing indentation or line breaks. A project-wide, unified PhpStorm code style scheme, anchored via .editorconfig or shared IDE configuration in the repository, noticeably reduces such purely cosmetic conflicts.
9. Practical workflow: from conflict to a clean commit
In practice, the following order works well: first check the overview of all conflicting files and mentally set generated files like lock files aside. Then open the three-way dialog file by file, read Base as the reference point, decide chunk by chunk, and manually rework in the result panel where needed. Only once all content files are resolved do the generated lock files follow, via regeneration.
Before the final commit, it is worth glancing at PhpStorm's local history or running a quick test to make sure the manual merge did not accidentally leave one of the two original changes incomplete. Especially with strictly typed PHP classes, a forgotten parameter or a missing use statement often only surfaces at the next PHPStan run, which is why a short analysis pass right after the merge should be part of the routine.
| Situation | Recommended action | Use merge dialog? | Follow-up |
|---|---|---|---|
| Conflict in a PHP class | Review chunk by chunk and merge in the result panel | Yes, fully | Run PHPStan/tests |
| composer.lock | Resolve the conflict formally with Accept Theirs/Yours | Formal only | composer update --lock |
| package-lock.json | Resolve formally, then regenerate the lock file | Formal only | npm install --package-lock-only |
| Long-running feature branch | Rebase regularly instead of merging once at the end | Yes, in small chunks | Test briefly after each rebase |
| Purely formatting conflicts | Unify .editorconfig/code style across the team | Rarely needed | Add a code style check to CI |
Mironsoft
PhpStorm setup, Docker integration, and team productivity
PhpStorm that actually runs optimally for Magento and PHP projects?
We review existing PhpStorm setups for slow indexing, unused Docker integration, and missing team conventions, then set up a configuration that is productive from the first second.
Setup Review
Optimizing indexing, interpreter, and memory settings for large Magento projects.
Docker Integration
Cleanly connecting Xdebug, PHPUnit, and database tools to the Docker setup.
Team Conventions
Standardizing inspection profiles, code style, and live templates project-wide.
10. Summary
Three-Way Merge in PhpStorm: The Essentials at a Glance
Three columns
Base, Local and Remote are shown side by side, plus an editable result column for the final outcome.
Chunk-level control
Each conflict excerpt can be accepted individually from Local or Remote, no all-or-nothing.
Treat lock files differently
Resolve composer.lock and package-lock.json formally, then let them be regenerated automatically.
Watch rebases
During an interactive rebase, Local and Remote can be swapped compared to a normal merge.