◉ANTIGRAVITY LABEN
記事一覧/Editor View
⟐ Editor View/2026-10-21上級

差分を読む順番を機械で並べ替えるか迷い、106 コミットに当てました。効いたのは 8 件と、1,776 ファイルの 1 回でした

差分の読む順番を機械で決める小さなスクリプトを書き、直近 106 コミットに当てました。常用する価値があったのは 8 件と大量変更の 1 回だけです。判定基準、全コード、誤分類の落とし穴をまとめます。

git16diff3コードレビュー11Antigravity393editor32個人開発115

✦ プレミアム記事

エージェントに任せた変更が溜まってくると、レビューの入口で手が止まります。git diff --stat を開くと、ファイルがパス順に並んでいるだけで、どれから読めばよいのか分かりません。

そこで、差分を「読む優先度」で並べ替える小さなスクリプトを書きました。設定ファイルや実装を先に、本文や生成物を後ろに回すだけの道具です。

書き終えてすぐ、常用するかどうかで迷いました。便利そうに見える一方で、私の普段の差分は小さいものが大半で、並べ替えが効く場面は思ったほど多くないような気もしたのです。

迷ったときは、勘ではなく自分の履歴に当てて数えます。個人開発で運営している Web サイトのリポジトリに残っている、直近のコミットで試しました。結論を先にお伝えすると、効いたのは 106 コミット中 8 件と、1,776 ファイルを一度に書き換えた 1 回でした。並べ替えが効かない日のほうが多いのかもしれない、という最初の予感は、おおむね当たっていました。

並べ替えの規則は、先に決めて固定します

規則を数字を見てから調整すると、結果に合わせた分類になってしまいます。そのため、先に次の 8 区分と順番を決め、あとで動かさないことにしました。

読む順区分入るもの先に読む理由
1secrets-infra.env 系、.dev.vars、wrangler 設定、CI のワークフロー、YAML行数は少なくても、秘密情報や公開範囲に直結します
2depspackage.json、requirements、pyproject依存の追加は、本文より先に確認したいものです
3logicts、tsx、js、mjs、py など動作を変える本体です
4teststests ディレクトリ、spec と test のつくファイルロジックを読んだあとに、意図と照らします
5style / data / contentcss、json や csv、Markdown見た目と本文は、壊れても気づきやすい部類です
末尾skip / binaryロックファイル、min 化したファイル、画像やフォント人の目で読んでも得るものがありません

ここで 1 つだけ、自分の判断を入れています。行数の多い順ではなく、壊したときの影響が大きい順に読む、という線引きです。

行数順は一見公平ですが、数行の設定変更が一番大きな事故になることがあります。後述する落とし穴の対策も含め、この順番を固定しました。

完成したスクリプトの全文です

標準ライブラリだけで動きます。git diff --numstat の出力を区分に振り分け、読む順に並べ直します。

#!/usr/bin/env python3
"""git の差分を『読む順番』に並べ替える。使い方: diff_triage.py <rev-range> [repo]"""
import re, subprocess, sys
from collections import defaultdict
 
# 上から順に最初に当たった区分を採用する。tests は logic より前に置く(拡張子 .py が先に当たるのを防ぐ)
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 が引用符で包む
        if "=>" in path:                      # 改名は 'a/{old => new}.ts' の新しい側を採る
            path = re.sub(r"\{.*? => (.*?)\}", r"\1", path).replace("//", "/")
        if add == "-":                        # バイナリは行数が取れない
            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("   (一覧のみ・先頭 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 ".")))

分類の規則は上の表と同じで、RULES の並びがそのまま優先順位です。本文と生成物は 3 件だけ名前を出して畳み、実装と設定は全件を行数順に見せます。

使うときは python3 diff_triage.py HEAD~1..HEAD . のように、見たいリビジョンの範囲を渡します。

✦

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
✦自分のリポジトリの履歴に分類スクリプトを当てて、並べ替えが要る差分の割合を数え、道具を常用するか見送るかを判断できるようになる
✦数行のロジックが 1,700 超の本文ファイルに埋もれて見落とされる前に、区分ごとに折りたたんで読む順番を決められるようになる
✦拡張子の判定順や引用符つきのパスなど、分類スクリプトが黙って誤分類する落とし穴を、書く前に潰せるようになる
Stripe による安全な決済 · いつでもキャンセル可能
✦

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

⟐ Editor View2026-09-03
Antigravity の自動コミットメッセージを、どこまで受け入れるか決めました
Review ペインの自動コミットメッセージを、どこまで受け入れてどこから自分で書くか。prepare-commit-msg に Why 行を足す実装と、リベースやリバートで手が止まった実測を書き残します。
⟐ Editor View2026-10-16
納期の前日にアップデート通知が届いたとき、上げるか先送りするかを決める三つの問い
納期前夜に Antigravity のアップデート通知が出たら、上げるか先送りか。変更履歴の読み方、戻せるかの確認、5分でできる退避の手順まで、個人開発の判断基準をまとめました。
⟐ Editor View2026-10-08
会話だけ戻す undo とサブエージェントへの直送、使い分けに迷った3つの場面
Antigravity 2.19.1 で加わった「undo で会話だけを戻す」と「サブエージェントへ直接送る」。公式の記述と私の線引きを分けて、迷いやすい3つの場面を整理します。