世界を動かす技術を、日本語で。

コードを読む前に実行するGitコマンド

概要

  • 新しいコードベースを扱う際、まず Git履歴 を確認する手法の重要性
  • 変更頻度・バグ集中・貢献者分布 などの観点でリスク分析
  • 主要な Gitコマンド を用いた短時間診断の流れ
  • コードベースの 健康状態やチーム状況 の可視化
  • 初動調査で得られる 洞察と次の行動指針

新しいコードベースを診断する最初の一時間

  • 新規プロジェクトで最初に行うのは、 コード閲覧 ではなく ターミナルでのGitコマンド実行
  • コミット履歴 から「誰が作ったか」「問題がどこに集中しているか」「チームの活発度」などを把握
  • これにより、 読むべきファイルや注意点 を初動で特定

最も変更されているファイルの特定

  • コマンド例 git log --format=format: --name-only --since="1 year ago" | sort | uniq -c | sort -nr | head -20
    • 過去1年で最も変更されたファイル上位20件 を抽出
  • 最上位のファイルは「 誰も触りたがらない問題児」であることが多い
  • 高頻度変更=悪 とは限らないが、 誰も責任を持ちたがらない高頻度ファイル は要注意
  • Microsoft Researchの調査 でも、変更頻度は複雑度以上に不具合発生を予測
  • 上位5件を次の「バグ集中ファイル」と クロスチェック するのが効果的

誰がこのコードを作ったのか

  • コマンド例 git shortlog -sn --no-merges
    • 貢献者ごとのコミット数ランキング
  • 一人が 60%以上 を占めていれば「 バス係数」が低い
  • 上位貢献者が 最近6ヶ月で活動していない場合 は即クライアントに報告
  • コマンド例 git shortlog -sn --no-merges --since="6 months ago"
  • 貢献者数に対し、実質稼働者が少ない場合 はメンテナンスリスク
  • Squash Merge運用 の場合、実際の作者とコミット者が異なることに注意

バグが集中する場所の把握

  • コマンド例 git log -i -E --grep="fix|bug|broken" --name-only --format='' | sort | uniq -c | sort -nr | head -20
    • バグ修正関連コミット が多いファイルを抽出
  • 変更頻度トップとバグ集中トップ が重なるファイルは最大のリスク
  • コミットメッセージの質 に依存するため、運用状況も加味

プロジェクトの加速か停滞か

  • コマンド例 git log --format='%ad' --date=format:'%Y-%m' | sort | uniq -c
    • 月ごとのコミット数推移
  • 安定したリズム は健全、急減はメンバー離脱などの兆候
  • 6-12ヶ月の漸減 はモメンタム低下、 周期的なスパイク はリリースバッチ型運用
  • チームの歴史と出来事 を可視化するデータ

チームが火消しに追われている頻度

  • コマンド例 git log --oneline --since="1 year ago" | grep -iE 'revert|hotfix|emergency|rollback'
    • RevertやHotfixの頻度 を抽出
  • 年間数回は正常、 頻繁なRevert はデプロイ手順やテストの信頼性不足
  • ゼロ件 も「安定」か「メッセージが雑」かのどちらか
  • 危機パターン は履歴から容易に判読可能

まとめ

  • この5つのコマンド で数分以内に「どこを読むべきか」「どこに問題が潜むか」を特定可能
  • 初動調査 で得られる洞察が、無駄な探索を防ぎ、効率的な読み進めを実現
  • これは コードベース監査の初日1時間 のアプローチ

関連記事リスト

  • Why Your Engineering Team Is Slow (It's the Codebase, Not the People)
  • How to Close a Tab in Vim
  • How I Audit a Legacy Rails Codebase in the First Week
  • How to Open a New Tab in Vim
  • Rails default_scope: Why You Should Never Use It

Hackerたちの意見

jujutsuの使い方について興味がある人のために、以下のコマンドを紹介するね。 「最も変更されたファイル」 jj log --no-graph -r 'ancestors(trunk()) & committer_date(after:"1 year ago")' \ -T 'self.diff().files().map(|f| f.path() ++ "\n").join("")' \ | sort | uniq -c | sort -nr | head -20 「このプロジェクトを作ったのは誰?」 jj log --no-graph -r 'ancestors(trunk()) & ~merges()' \ -T 'self.author().name() ++ "\n"' \ | sort | uniq -c | sort -nr 「バグはどこに集まる?」 jj log --no-graph -r 'ancestors(trunk()) & description(regex:"(?i)fix|bug|broken")' \ -T 'self.diff().files().map(|f| f.path() ++ "\n").join("")' \ | sort | uniq -c | sort -nr | head -20 「このプロジェクトは加速しているのか、それとも死にかけているのか?」 jj log --no-graph -r 'ancestors(trunk())' \ -T 'self.committer().timestamp().format("%Y-%m") ++ "\n"' \ | sort | uniq -c 「チームはどれくらい頻繁に火消しをしているのか?」 jj log --no-graph \ -r 'ancestors(trunk()) & committer_date(after:"1 year ago") & description(regex:"(?i)revert|hotfix|emergency|rollback")' もっと詳しくて、シェルスクリプトよりプログラミングに近い感じ。でも、覚えるフラグは少ないよ。

私にとって、jujutsuはVCSの中でNixみたいに見える。誰かを傷つけるつもりはないけど、Nixはクールだけど複雑さを増すよね。ちなみに、私はjujutsuを数ヶ月使ったけど、結局gitに戻った。主にgitが手に馴染んでるし、どこにでもあるから。jujutsuができることの例は素敵だけど、私が使ってた数ヶ月の間には全然必要なかったから、過剰に感じた。

これ全部覚えてられないな。CLIに特化したLLMモデルでローカルで動かせるやつ知ってる人いる?

過去1年で最も変更された20のファイル。 一番上のファイルは、ほぼいつもみんなが警告してくるやつだ。「ああ、そのファイルね。みんな触るのを怖がってる。」 最も変更されたファイルって、みんなが触るのを怖がってるやつなの?

そうだよ。恐れは必要性に支えられてるから。ファイルを編集しなきゃいけないし、みんなも同じで、それが混乱の元になる。こういうファイルを何年も振り返ることができる。大体は、理解するのが難しいキロ行のコードが詰まってる。

これを試してみたけど、最も触られたファイルは、私のテストでは最も関係ないかつ退屈なファイル(自動生成されたものやサービスのエントリーポイントなど)だった。

よく編集されるファイルは、壊れる機会が多かったのかもしれないし、最もランダムな人たちによって編集されてたのかも。

まるで、あまりにも混雑してて誰も行かなくなった場所みたいだね。

自分の経験では全然違うけどね。最も変わったのは変更ログ、バージョン番号のファイル、リードミーだよ。誰もそれを最新の状態に保つのを怖がってるとは思わないな。

このコマンドには警告が必要だね。このコマンドを使って、特に初心者のうちはあまり多くの結論を引き出すと、チームメイトの前で恥をかくことになるよ。自分が開いてるリポジトリで実行したけど、コードファイル以外をフィルタリングしたら、実際には去年取り組んだ機能しか教えてくれなかった。バグや「チャン」に関することよりも、機能をどのように分けたかについての方が多く語ってる。

いいアイデアだけど、正規表現には単語の境界を含めるべきだね。例えば、 git log -i -E --grep="\b(fix|fixed|fixes|bug|broken)\b" --name-only --format='' | sort | uniq -c | sort -nr | head -20 「debugger」って大きなパッケージを持ってるプロジェクトがあるんだけど、「debugger」の中に「bug」が含まれてると、元のコマンドが狂っちゃう。

似たようなことをするサマリーエイリアスを持ってるよ。# summary: 一部の典型的なメトリクスの役立つサマリーを表示するやつ。

summary = "!f() {
  printf \"このブランチのサマリー...\n\";
  printf \"%s\n\" \$(git rev-parse --abbrev-ref HEAD);
  printf \"%s 最初のコミットタイムスタンプ\n\" \$(git log --date-order --format=%cI | tail -1);
  printf \"%s 最新のコミットタイムスタンプ\n\" \$(git log -1 --date-order --format=%cI);
  printf \"%d コミット数\n\" \$(git rev-list --count HEAD);
  printf \"%d 日付数\n\" \$(git log --format=oneline --format=\"%ad\" --date=format:\"%Y-%m-%d\" | awk '{a[\$0]=1}END{for(i in a){n++;} print n}');
  printf \"%d タグ数\n\" \$(git tag | wc -l);
  printf \"%d 著者数\n\" \$(git log --format=oneline --format=\"%aE\" | awk '{a[\$0]=1}END{for(i in a){n++;} print n}');
  printf \"%d コミッター数\n\" \$(git log --format=oneline --format=\"%cE\" | awk '{a[\$0]=1}END{for(i in a){n++;} print n}');
  printf \"%d ローカルブランチ数\n\" \$(git branch | grep -v \" -> \" | wc -l);
  printf \"%d リモートブランチ数\n\" \$(git branch -r | grep -v \" -> \" | wc -l);
  printf \"\nこのディレクトリのサマリー...\n\";
  printf \"%s\n\" \$(pwd);
  printf \"%d ファイル数 (git ls-files)\n\" \$(git ls-files | wc -l);
  printf \"%d ファイル数 (findコマンド)\n\" \$(find . | wc -l);
  printf \"%d ディスク使用量\n\" \$(du -s | awk '{print \$1}');
  printf \"\n最もアクティブな著者、コミット数と%%...\n\";
  git log-of-count-and-email | head -7;
  printf \"\n最もアクティブな日付、コミット数と%%...\n\";
  git log-of-count-and-day | head -7;
  printf \"\n最もアクティブなファイル、チャン数\n\";
  git churn | head -7;
}; f"

EDIT: https://github.com/GitAlias/gitalias に感謝。

気になるんだけど、.gitconfigに関数として書く理由は何なの?パスにgit-summaryスクリプトを置けばいいのに。なんか余計なエスケープや引用符が多くて面倒くさそう。

いい感じだね。ただ、log-of-count-and-email、log-of-count-and-day、churnがないのが残念。

各コマンドが何をするかをふわっと説明するためにLLMを使うより、著者はその出力を見せるべきだったね(必要なら切り詰めて)。

特定のキーワードを含むメッセージを信頼するのは楽観的すぎると思う。自分は「emergency」や「hotfix」を使ったことないし。「Revert」は時々自動で作成されるツールもあるし(例えばPRをアンマージするとか)。

ずいぶん前に、Googleがコミットメッセージを使ってコードのホットスポットを特定するアルゴリズムを発表したんだよね。 https://github.com/niedbalski/python-bugspots

うちのワークフローだと、Gitのコミットが意味をなさなくなっちゃってる。'bugfix'や'feature'はブランチとしてラベル付けされてて、実際に重要なのはマージコミットだけなんだ。マージコミットは自動生成されて、著者、ブランチ名、承認者リストだけが含まれてるから、コミットメッセージを見るには遡らなきゃいけない。Gitでは全然できるんだけど、'関連情報'をPRの本文に入れなきゃいけないから(それがマージコミットメッセージには入らないし…)、誰もコミットメッセージを使わないんだよね。'wip'や'lates't、たまにJIRAのタスク番号があるくらい。そうなるまでの話だけど、Claudeが登場してからは、Claudeがほぼ小説みたいなコミットメッセージを生成するようになった。ビジネスの人たちのClaude以外、誰も読んでないけどね。Claudeが自分にメッセージを書いて読んでる感じ。コミットの数も、1PRあたり約4から12に増えちゃったよ。

ずいぶん前に、Googleがコミットメッセージを使ってホットスポットを検出するアルゴリズムを書いたんだ。 https://github.com/niedbalski/python-bugspots

ありがとう、これ役立つね!