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.

Appending versus sorted insertionWhen new entries are always appended, two branches both change the line after the last entry and conflict. When entries are kept sorted, one branch inserts near the top and the other near the bottom, so the changes are in different hunks and merge automatically.Append at endKeep sortedbranch A addsafter last lineunder 'b…'branch B addsafter last lineunder 's…'merge resultconflictcleansorting does not prevent all conflicts — neighbours still collide — but it removes the systematic ones
# 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",
]
Three formatting rules that remove false conflictsSorting entries spreads additions across the list. One item per line keeps each change to its own line. Trailing commas mean appending never edits the previous line. Together they leave only genuine conflicts, where both branches change the same entry.Sortedadditions spread outOne per linechanges stay localTrailing commasappend touches 1 linenone of these changes behaviour — they only change where diffs land

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
Conflict hunks in import blocksAn illustrative Python and TypeScript codebase. Before formatting rules, import blocks accounted for a large share of conflict hunks. After sorted imports and trailing commas were enforced, they almost disappeared, leaving conflicts concentrated in real code.conflict hunks in import blocks per 50 merges (illustrative)before46after4the remaining few are genuine: both branches changed the same import

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.