Ordering lists and imports to minimise conflicts Jump to heading
Many merge conflicts are not real disagreements. Two branches each add one import at the end of the same import block, or one dependency at the bottom of the same list, or one enum value after the same last entry. Git sees two different changes to the same lines and stops. Nothing in the code conflicts; only the formatting forced the additions into one spot. A few formatting rules — sorted entries, one item per line, trailing commas — spread additions across a file so that independent changes land in different hunks and merge cleanly. Enforced by a formatter, they cost nothing to follow. This page covers the rules and how to enforce them, within conflict prevention by design.
When to use this approach Jump to heading
- Conflicts often involve import blocks, dependency lists, enum or constant lists, or array literals.
- Two branches adding unrelated entries to the same list regularly need manual resolution.
- Your languages have formatters or linters that can sort and wrap lists.
- You already use a formatter on staged files, as in running Prettier and ESLint only on staged files.
Step 1 — See why appending conflicts and sorting does not Jump to heading
When both branches append to the end of a list, both changes touch the same location — the line after the last entry — so Git cannot apply both. When entries are sorted, additions land wherever their name sorts, which is usually a different place for each branch.
# Appended — both branches change the same spot
import config
import logging
import billing # branch A
import search # branch B → conflict
# Sorted — additions land in different places
import billing # branch A
import config
import logging
import search # branch B → clean merge Step 2 — Put one item per line with trailing commas Jump to heading
A list written on one line conflicts on any change to any item. A multi-line list without trailing commas makes every append modify the previous last line too — to add its comma — so two appends conflict on that line. One item per line with a trailing comma on every item means adding an entry touches only the new line.
# Fragile: adding 'export' edits the 'search' line to add a comma
FEATURES = [
"billing",
"search"
]
# Robust: adding an entry touches only its own line
FEATURES = [
"billing",
"search",
] Step 3 — Enforce the rules with formatters Jump to heading
Rules that depend on people remembering them do not survive. Configure the formatter for each language to sort imports and keep trailing commas, and run it on commit and in CI.
# pyproject.toml — Python: ruff sorts imports and the formatter keeps magic trailing commas
[tool.ruff.lint]
extend-select = ["I"] # isort-compatible import sorting
[tool.ruff.format]
skip-magic-trailing-comma = false // .prettierrc — JavaScript/TypeScript: trailing commas everywhere
{ "trailingComma": "all" } # Go sorts imports with gofmt/goimports; check in CI
gofmt -l . | tee /dev/stderr | (! read) Sorting dependency manifests often needs a separate tool — sort-package-json for npm, taplo for TOML. Add whichever fits your stack to the same hook.
Step 4 — Apply the format once, as its own commit Jump to heading
Turning on sorting reorders existing lists, which touches many files. Do it in one dedicated commit, then add that commit to .git-blame-ignore-revs so blame skips it.
ruff check --select I --fix . && ruff format .
npx prettier --write .
git commit -am "Apply import sorting and trailing commas (formatting only)"
git rev-parse HEAD >> .git-blame-ignore-revs
git config blame.ignoreRevsFile .git-blame-ignore-revs
git add .git-blame-ignore-revs && git commit -m "Ignore formatting commit in blame" Open branches will conflict with the reformat once. The easiest fix is for each to run the same formatter on their branch before merging, which makes most of the conflicts disappear on their own.
# On an open branch, before merging main
git merge origin/main || { ruff format . && ruff check --select I --fix . && git add -A && git commit --no-edit; } Step 5 — Measure the effect on conflict hunks Jump to heading
Check that the change paid off by comparing conflicts on list-heavy files before and after.
# Re-run recent merges and count conflict hunks in import-heavy files
for m in $(git rev-list --merges --since=3.months main | head -50); do
git merge-tree --write-tree --name-only "$m^1" "$m^2" 2>/dev/null | tail -n +2
done | grep -E '\.(py|ts)$' | sort | uniq -c | sort -rn | head Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
What about lists where order matters? Jump to heading
Do not sort them. Middleware chains, migration lists and precedence-ordered rules must keep their order. Mark them with a comment so formatters and people leave them alone, and consider splitting them into fragments instead, as in splitting shared config into fragments.
Does sorting cause more conflicts when two branches add neighbouring entries? Jump to heading
Occasionally — two additions that sort next to each other still touch adjacent lines. That is far rarer than every addition colliding at the end of a list.
Is a custom merge driver a better solution? Jump to heading
For specific file types, such as lockfiles, a driver can resolve conflicts automatically. For code, formatting rules are simpler and work with every tool.
Related Jump to heading
- Conflict Prevention by Design — the parent topic.
- Keeping Generated Files Out of Merge Conflicts — another category of avoidable conflict.
- Formatting Only Changed Lines in a Legacy Codebase — when a one-off reformat is not possible.
- Resolving Whitespace Conflicts with Strategy Options — formatting conflicts that do happen.