◉ANTIGRAVITY LABJP
Articles/Editor View
⟐ Editor View/2026-10-21Advanced

I Wondered Whether to Sort My Diffs by Machine, So I Ran It on 106 Commits. It Mattered for 8, and for One 1,776-File Change

I wrote a small script that orders a git diff by reading priority and ran it on 106 recent commits. It earned its place in 8 of them and one bulk change. Rules, full code, and the silent misclassification traps.

git16diff4code review6Antigravity393editor35indie dev24

✦ Premium Article

Once a pile of agent-made changes builds up, review stalls at the front door. git diff --stat lists files in path order, and nothing tells you which one to open first.

So I wrote a small script that reorders a diff by reading priority. Config and logic come first; prose and generated files go to the back.

Right after finishing it, I couldn't decide whether to use it every time. It looked useful, but most of my diffs are small, and I suspected the cases where reordering helps were fewer than I assumed.

When I'm unsure, I count instead of guessing. I ran the script against the recent history of a website repository I maintain as an indie developer. The short version: it mattered for 8 of 106 commits, plus one change that rewrote 1,776 files at once.

Fix the rules before looking at any numbers

If you tune the rules after seeing results, you end up classifying to fit the outcome. So I fixed the buckets and their order first, and did not move them afterwards.

Read orderBucketWhat goes inWhy it comes first
1secrets-infra.env files, .dev.vars, wrangler config, CI workflows, YAMLFew lines, but they touch secrets and exposure directly
2depspackage.json, requirements, pyprojectA new dependency deserves a look before any prose
3logicts, tsx, js, mjs, py and the likeThe part that changes behavior
4teststests directories, spec and test filesRead after the logic, to compare against intent
5style / data / contentcss, json and csv, MarkdownBreakage here is usually easy to notice
Lastskip / binaryLockfiles, minified files, images and fontsNothing to gain from reading them by eye

I made one judgment call here. Read in order of damage if wrong, not in order of line count.

Line count looks fair, but a three-line config change can be the biggest accident. That ordering is fixed, along with the pitfalls below.

The full script

It uses only the standard library. It sorts git diff --numstat output into buckets and prints them in reading order.

#!/usr/bin/env python3
"""Reorder a git diff into reading order. Usage: diff_triage.py <rev-range> [repo]"""
import re, subprocess, sys
from collections import defaultdict
 
# First matching bucket wins. tests sits before logic so a .py test is not swallowed by the extension rule.
RULES = [
    ("secrets-infra", re.compile(r"(^|/)(\.env[^/]*|\.dev\.vars|wrangler\.(toml|jsonc?)|\.github/workflows/[^/]+|Dockerfile|[^/]*\.ya?ml)$")),
    ("deps",          re.compile(r"(^|/)(package\.json|requirements[^/]*\.txt|pyproject\.toml|go\.mod)$")),
    ("tests",         re.compile(r"(^|/)(tests?|__tests__)/|\.(test|spec)\.[a-z]+$")),
    ("logic",         re.compile(r"\.(tsx?|jsx?|mjs|py|go|rs|swift|kt)$")),
    ("style",         re.compile(r"\.(css|scss)$")),
    ("content",       re.compile(r"\.(mdx?|txt)$")),
    ("data",          re.compile(r"\.(json|csv|tsv)$")),
]
SKIP = re.compile(r"(^|/)(package-lock\.json|pnpm-lock\.yaml|yarn\.lock|.*\.min\.(js|css)|.*\.snap)$")
ORDER = ["secrets-infra", "deps", "logic", "tests", "style", "data", "content", "other"]
 
def run(cmd, repo):
    return subprocess.run(cmd, cwd=repo, capture_output=True, text=True, check=True).stdout
 
def bucket(path):
    if SKIP.search(path):
        return "skip"
    for name, rx in RULES:
        if rx.search(path):
            return name
    return "other"
 
def triage(rev, repo="."):
    rows = []
    for line in run(["git", "-c", "core.quotepath=false", "diff", "--numstat", "-M", rev], repo).splitlines():
        add, dele, path = line.split("\t", 2)
        path = path.strip('"')                # git wraps names with spaces or symbols in quotes
        if "=>" in path:                      # rename looks like 'a/{old => new}.ts'; keep the new side
            path = re.sub(r"\{.*? => (.*?)\}", r"\1", path).replace("//", "/")
        if add == "-":                        # binary files have no line counts
            rows.append(("binary", path, 0, 0)); continue
        rows.append((bucket(path), path, int(add), int(dele)))
    return rows
 
def render(rows):
    by = defaultdict(list)
    for b, p, a, d in rows:
        by[b].append((p, a, d))
    out = []
    for b in ORDER + ["binary", "skip"]:
        files = sorted(by.get(b, []), key=lambda x: -(x[1] + x[2]))
        if not files:
            continue
        total = sum(a + d for _, a, d in files)
        out.append(f"[{b}] {len(files)} files / {total} lines")
        if b in ("skip", "content") and len(files) > 3:
            out.append("   (names only, first 3) " + ", ".join(p for p, _, _ in files[:3]))
        else:
            out += [f"   {a:>5}+ {d:>5}-  {p}" for p, a, d in files[:8]]
    return "\n".join(out)
 
if __name__ == "__main__":
    print(render(triage(sys.argv[1], sys.argv[2] if len(sys.argv) > 2 else ".")))

The rules match the table above, and the order of RULES is the priority. Prose and generated files are folded down to three names, while logic and config show every file sorted by line count.

Call it with a revision range, for example python3 diff_triage.py HEAD~1..HEAD ..

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦You can run a classifier over your own repository history, count how often a diff actually needs reordering, and decide whether the tool deserves daily use
✦You will be able to decide a reading order and fold the noisy buckets before a handful of logic lines get buried under 1,700 content files
✦You can avoid the silent misclassifications a path classifier makes, such as rule order and quoted paths, before they skew your numbers
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

Related Articles

⟐ Editor View2026-10-08
Undo That Rewinds Only the Conversation, and Messaging a Subagent Directly: Three Moments I Hesitated
Antigravity 2.19.1 adds undo for the conversation only, and direct messages to a subagent. Here is what the changelog says, where I draw my own lines, and how to check both safely.
⟐ Editor View2026-09-03
How much of Antigravity's generated commit message I actually keep
Where the Review pane's generated commit message stops being enough, and where I start writing. A prepare-commit-msg hook that adds a Why trailer, plus measured behaviour during rebase, cherry-pick and revert.
⟐ Editor View2026-03-10
VS Code × Antigravity Migration Guide — Seamlessly Transition Your Dev Environment to an AI IDE
A complete guide to migrating from VS Code to Google Antigravity. Learn how to transfer settings, extensions, keybindings, and effectively use both editors together.