変更履歴(VCS)メトリクス

big_code_analysis.vcs サブモジュールは、変更履歴リスク — ソースの AST ではなくバージョン管理の履歴から導かれるシグナル — によってファイルをランク付けし、コミットをスコアリングします。これは bca vcs CLI コマンドの Python 版であり、両者は同じ Rust エンジンに支えられているため、返される dict は CLI の構造化出力とフィールド単位で一致します。

from big_code_analysis import vcs

report = vcs.rank("path/to/repo", top=20)
trend = vcs.trend("path/to/repo", points=6)
commit = vcs.commit("path/to/repo", commit="HEAD")
diff = vcs.score_diff(unified_diff_text)

4 つのエントリポイントは bca vcs のサブコマンドに対応します。vcs.rank はファイルをランク付けし(bca vcs)、vcs.trend はそのランキングを時間軸に沿ってサンプリングし(bca vcs trend)、vcs.commit は 1 つのコミットをスコアリングし(bca vcs commit)、vcs.score_diff は素の unified diff をスコアリングします(bca vcs commit --diff)。シグナル、複合リスクスコア、その背後にある欠陥予測の文献については CLI の章を参照してください。このページでは Python 側のインターフェースを扱います。

これは、単一ファイルのメトリクスに vcs ブロックを付加する analyze(..., vcs=True) とは別物です。vcs サブモジュールはリポジトリ全体の履歴を一度だけ走査するため、ランク付けにはこちらを優先してください。analyze のキーワード引数は、ファイルの AST メトリクスと並べて変更履歴の数値が欲しい場合にのみ使います。リポジトリ内のファイル全体で履歴走査を償却する analyze_batch(..., vcs=True) の経路についてはバッチ処理を参照してください。

ファイルのランク付け

vcs.rank(repo_path, *, options=None, top=None, no_cache=False, cache_dir=None) は、対象範囲内のすべてのファイルをリスク降順でランク付けし、VcsReportDict を返します。呼び出しごとに変わるキーワード専用の設定は rank にあり、trendcommit と共有される履歴走査の設定は共通の Options オブジェクト(後述)にあります。

from big_code_analysis import vcs

report = vcs.rank("path/to/repo", top=20)

print(f"long window:  {report['long_window_days']} days")
print(f"recent window: {report['recent_window_days']} days")

for ranked in report["files"]:
    block = ranked["vcs"]
    print(f"{block['risk_score']:6.2f}  {ranked['path']}")

top はランキングに残すファイル数の上限を指定します。0 または None はすべてを残します。レポートは、解決済みのウィンドウ長と risk_score_version / vcs_schema_version スタンプを(各ファイルの vcs ブロックではなく)トップレベルに一度だけ持ちます。files リストは vcs.risk_score の降順で並び、計算された場合はリポジトリのバスファクターサマリーを vcs_aggregate キーが保持します。

aggregate = report.get("vcs_aggregate")
if aggregate is not None:
    bus = aggregate["bus_factor"]
    print(f"repo bus factor: {bus['repo']['bus_factor']}")

コミットのスコアリング

vcs.commit(repo_path, *, commit="HEAD", options=None) は、単一のコミットを第一親に対する just-in-time(コミットレベル)リスクの観点でスコアリングし、JitCommitReportDict を返します。commit 引数には任意の git リビジョン表記("HEAD""HEAD~3"、ブランチ、タグ、SHA)を指定できます。

from big_code_analysis import vcs

report = vcs.commit("path/to/repo", commit="HEAD")

print(f"risk score: {report['risk_score']}")
print(f"is merge:   {report['commit']['is_merge']}")

size = report["features"]["size"]
print(f"+{size['lines_added']} -{size['lines_deleted']} "
      f"across {size['files_touched']} files")

# 各特徴グループが順序スコアに与える符号付きの寄与。
for group, value in report["contributions"].items():
    print(f"  {group:<11} {value}")

このスコアは順序尺度です。コミット同士のランク付けや、リポジトリ自身の分布との比較には使えますが、その大きさを確率として読んではいけません。contributions ブロックは各特徴グループの符号付き寄与を報告するため、コミットが 「なぜ」 その順位になったのかを利用側が確認できます。

任意の diff のスコアリング

vcs.score_diff(diff) は、まだコミットされていない git 形式の unified diff — pre-commit フックやコードレビューボットが扱う形 — をスコアリングし、JitDiffReportDict を返します。

import subprocess
from big_code_analysis import vcs

staged = subprocess.run(
    ["git", "diff", "--cached"],
    capture_output=True, text=True, check=True,
).stdout

report = vcs.score_diff(staged)
print(f"partial risk: {report['partial_risk_score']}")

素の diff には作者・親コミット・ファイル履歴の情報が含まれないため、source はリテラルの "diff" になり、計算可能なのは sizediffusion のグループだけで、partial_risk_score はコミットの risk_score比較できません。history、experience、purpose の各グループはゼロではなく、欠落しています。

トレンドのサンプリング

vcs.trend(repo_path, *, options=None, points=12, span=None, top=None, top_deltas=None) は、ファイルのランキングを複数の時点でサンプリングし、VcsTrendDict を返します。各時点はその瞬間のメインラインの先頭に改めてアンカーされるため、この系列は現在のランキングを過去に投影し直したものではなく、真の歴史的スナップショットの並びになります。

from big_code_analysis import vcs

trend = vcs.trend("path/to/repo", points=6, span="6mo", top_deltas=10)

# サンプリング時刻。古い順(Unix 秒)。
print("sampled at:", trend["as_of_points"])

# 期間中に最も悪化したファイル。
for delta in trend["deltas"]["regressed"]:
    print(f"  +{delta['delta']:.2f}  {delta['path']}")

points 個(2 以上)のサンプルが span(デフォルト 12mo)の期間に分布し、options.as_of で終わります。files マップは各ファイルの系列を as_of_points と 1:1 で揃え、そのファイルがまだ存在しなかった箇所は None 要素になります。deltas サマリーはファイルを improvedregressed のリストに分け、top_deltas が各リストを切り詰め、top が残すファイル数の上限を決めます。

共通オプション

リポジトリを走査する 3 つのエントリポイント — ranktrendcommit — は同じ vcs.Options オブジェクトを受け取るため、共通の設定項目を繰り返すことなく、1 つの構成でランク付けとトレンドの両方のパスを実行できます。すべてのフィールドはキーワード専用かつ省略可能で、デフォルト値は bca vcs CLI のデフォルトを再現するため、Options() はデフォルトのランキングと一致します。

from datetime import datetime, timezone
from big_code_analysis import vcs

options = vcs.Options(
    long_window="2y",
    recent_window="60d",
    risk_formula="percentile",
    file_types=["rs", "py"],
    as_of=datetime(2026, 1, 1, tzinfo=timezone.utc),
)

report = vcs.rank("path/to/repo", options=options, top=20)

拡張されたオプションのキーワード引数(issue #619)は、それぞれ単なる文字列以外の値も受け付けます:

  • file_types はランク付けの対象ファイルを選択します。"metrics"(デフォルト — bca がメトリクスを計算するファイルのみ)、"all"(追跡対象のすべてのテキストファイル)、カンマ区切りの拡張子許可リスト("rs,py")、または拡張子の Sequence[str]["rs", "py"])を指定できます。
  • as_of は再現可能なスナップショットのために基準となる「現在」を固定します。datetime、または文字列(RFC 3339@unix、git の日付形式)で指定します。as_of を固定すると実行が再現可能になります。ランキングは実時間に対してではなく、その時点での状態として計算されます。
  • cache_dirOptions ではなく rank のパラメーター)は str または任意の os.PathLike を受け付けます — pathlib.Path はそのまま渡せます。

履歴走査のトグルは CLI フラグを反映しています。full_historyinclude_mergesfollow_renames(デフォルト True)、exclude_bots(デフォルト True)、そしてボット作者の正規表現を上書きする bot_pattern です。bus_factor_threshold はバスファクターのフラグに用いるカバレッジ割合を設定し(デフォルト 0.5)、emit_author_details は SHA-256 でハッシュ化された正規化済み作者 ID を含めます。author_hash_keyemit_author_details が必要)はこれらのダイジェストを鍵付き HMAC-SHA256 に強化します。これは 作者詳細のプライバシー で説明されているものと同じオプトインです。

キャッシュ

vcs.rank は各履歴走査の永続キャッシュをデフォルトで有効にして保持します。キャッシュヒットは新規の走査とビット単位で同一であり、時間ウィンドウは実行のたびに現在時刻に対して再計算されるため、キャッシュ済みの結果が古くなることはありません。

from big_code_analysis import vcs

# 最初の呼び出しがキャッシュを準備し、2 回目はそれを再生します。
vcs.rank("path/to/repo")
vcs.rank("path/to/repo")  # 前回の走査を再利用

vcs.rank("path/to/repo", no_cache=True)            # キャッシュを無視
vcs.rank("path/to/repo", cache_dir="/tmp/bca")     # ディレクトリを上書き

デフォルトでは、キャッシュはプラットフォームのキャッシュディレクトリ配下に置かれます。作者 ID は SHA-256 ダイジェストとしてのみ保存され、平文で保存されることはありません。ハッシュ化は匿名化ではなく仮名化である点に注意してください。ダイジェストは候補となるメールアドレス集合に対して復元可能です — 作者詳細のプライバシーを参照してください。

GIL の解放

リポジトリを走査する呼び出し(vcs.rankvcs.trend、および vcs.commit のコミットスコア)は、履歴走査の間 GIL を解放します(issue #620)。そのため ThreadPoolExecutor を使えば、インタープリターのロックで直列化されることなく、複数のリポジトリを並列にランク付けできます:

from concurrent.futures import ThreadPoolExecutor
from big_code_analysis import vcs

repos = ["service-a", "service-b", "service-c"]

with ThreadPoolExecutor() as pool:
    reports = list(pool.map(lambda r: vcs.rank(r, top=20), repos))

これは 非同期パターン のページがファイル単位の analyze 呼び出しに適用しているのと同じパターンです。

エラー

vcs の各関数は、bca.VcsError(それ自体が ValueError)を頂点とする型付き例外階層を送出します。完全な分類と、どの呼び出しがどの型を送出するかについてはエラー処理を参照してください。

関連項目

  • 変更履歴(VCS)メトリクス — CLI の章。シグナルの定義、複合リスクスコアの計算式、およびその背景にある欠陥予測の文献を扱います。
  • バッチ処理analyze_batch(..., vcs=True) は、リポジトリごとに 1 つの履歴インデックスを共有しながら、ファイルごとの vcs ブロックを付加します。
  • エラー処理 — VCS の例外分類。