学習コースLearn

リポジトリマイニング入門

ソフトウェア開発の履歴(コミット・課題・変更)からデータを取り出し、開発の実態を定量的に読み解く技術を、概要から理論、そして実際に動かせるコードまで、順を追って学ぶモジュールです。各ステップは「概要 → 仕組み(座学)→ コードで動かす」の3層で進みます。ページ内には手を動かせるデモを3つ用意しました。

この教材は日本語のみで提供していますThis learning material is available in Japanese only.

はじめにIntroduction

このモジュールの全体像

リポジトリマイニング(MSR: Mining Software Repositories)とは、ソフトウェア開発で日々ためられていく履歴データ、たとえば Git のコミット履歴、課題管理(Issue)、プルリクエスト、コードレビュー、CI のログなどから、開発の実態を掘り出して分析する技術の総称です。「どのファイルがよく壊れるか」「誰と誰が同じ場所を触るか」「どのコミットがバグを埋め込んだか」といった問いに、記録されたデータで答えます。

開発リポジトリは、放っておくと膨大なログの山になります。そのままでは読み解けないので、多くの手法に共通する処理の流れ(パイプライン)に沿って進めます。リポジトリからデータを取り出し、指標や関係へと解析し、洞察や可視化につなげます。

リポジトリマイニングの基本パイプライン

リポジトリ Repository 抽出 Extract 解析 Analyze 洞察・可視化 Insight コミット・diff・メタデータ 指標・共変更・傾向 ホットスポット・可視化

このモジュールは、上のパイプラインを左から順にたどります。各ステップは次の3層で構成します。

  • 概要:その技術が何のためにあるのか、あらましをつかむ。
  • 仕組み(座学):背後にある理論・定義・考え方を、ていねいに理解する。
  • コードで動かす:Python の定番ライブラリや、ページ内デモで実際に動かして確かめる。

手を動かす題材には、実在の公開リポジトリや大規模なデータ基盤(GHTorrent、GH Archive、World of Code など)を念頭に置きつつ、実際にはページ内の小さなサンプルログだけを使います。特別な環境がなくても、読みながら試せる構成です。

1

リポジトリマイニングとは

What is repository mining

概要Overview

ソフトウェア開発では、コードそのもの以外にも、たくさんの記録が自然にたまっていきます。「いつ、誰が、どのファイルを、なぜ変えたのか」が履歴として残り、課題やレビューの議論もテキストとして保存されます。こうした記録の山を分析し、開発の実態を明らかにするのがリポジトリマイニング(MSR)です。おもなデータ源は次のとおりです。

  • コミット履歴:変更の一つひとつ。日時・作者・差分・メッセージが残る。
  • 課題管理(Issue)とプルリクエスト:バグ報告・要望・変更提案とその議論。
  • コードレビュー:変更に対する指摘・承認のやり取り。
  • CI・ビルドのログ:テストやビルドが通ったか、いつ壊れたか。

仕組みHow it works

リポジトリマイニングは、経験的ソフトウェア工学(empirical software engineering)の一分野です。開発について「こうあるべき」と論じるのではなく、実際の開発で残されたデータを集めて、事実にもとづいて調べます。集めたデータから、たとえば次のような問いに答えられます。

  • 欠陥予測:過去によく壊れた部分から、次に注意すべきファイルを見当づける。
  • 開発者の協働:誰と誰が同じコードを触るのか、知識がどこに集中しているのか。
  • ホットスポットの発見:変更が集中し、複雑で壊れやすい「危険地帯」を見つける。

ただし、生のログはそのままでは扱いにくいものです。そこで、まずリポジトリから必要な情報を取り出し(抽出)、それを数えて指標や関係に変え(解析)、最後に人が読み解ける形にまとめます(洞察・可視化)。このモジュールの Step 2 以降は、まさにこの流れを一つずつ体験していきます。

この Step のまとめ

リポジトリマイニングは、コミット・課題・レビュー・CI といった開発の記録から実態を掘り出す技術。「あるべき論」ではなく実データで欠陥予測・協働・ホットスポットを調べる、経験的ソフトウェア工学の営みです。次からは、その最大のデータ源である Git の履歴に踏み込みます。

2

バージョン管理とGitの仕組み

Version control & Git

概要Overview

リポジトリマイニングの最大のデータ源は、バージョン管理システム(VCS)が残す履歴です。中でも Git が広く使われています。Git は変更の一つひとつをコミットとして記録し、その積み重ねがプロジェクトのまるごとの歴史になります。まずは、そこにどんな情報が、どんな形で残っているのかをつかみます。

仕組みHow it works

1. リポジトリ・コミット・スナップショット

リポジトリはプロジェクトの全履歴を収めた入れ物です。コミットは「ある時点のプロジェクト全体の状態(スナップショット)」への記録で、ハッシュ値(a1b2c3d... のような 40 桁の識別子)で一意に指し示せます。Git は変更差分だけでなく、その時点のツリー全体を参照する形で状態を保存します。

2. コミットグラフ(DAG)

各コミットは親コミットを指します。ふつうは 1 つですが、ブランチを統合するマージコミットは親を 2 つ以上持ちます。こうしてコミットは、時間をさかのぼる向きにつながった有向非巡回グラフ(DAG: Directed Acyclic Graph)を作ります。循環はしません。この構造をたどることで、どの変更がどの変更の後に来たのかを正確に追えます。

3. diff と blame

diffは 2 つの状態の差分で、どの行が追加・削除されたかを表します。blameは、ファイルの各行が「どのコミットで最後に変更されたか」を対応づけます。あとで出てくる、バグを埋め込んだコミットをさかのぼる分析は、この blame を土台にします。

4. コミットのメタデータ

1 つのコミットには、次のような情報が付きます。これらがマイニングの生の材料です。

  • ハッシュ:コミットを一意に識別する値。
  • author と committer:変更を書いた人と、実際に記録した人(リベース等で異なることがある)。
  • 日時:author 日時と commit 日時。
  • 親コミット:直前の状態への参照(マージなら複数)。
  • メッセージ:変更の意図を書いた説明文。fix・feat などの規約が使われることも多い。
デモ 1Demo 1

コミットログ・パーサ

1 行に 1 コミットを ハッシュ | 著者 | 日付 | メッセージ の形(縦棒区切り)で書くと、各項目に分解して表にします。さらにメッセージ先頭のキーワード(fix / feat / refactor / docs / test など)から変更の種別を推定し、色分けチップで示します。これは git log --pretty=format:"%h|%an|%ad|%s" の出力を読み解く処理の縮小版です。

例

コードで動かすRun the code

まずは Git 自身のコマンドで、履歴とメタデータをのぞいてみます。git log は整形した出力を作れるので、後段の解析に渡しやすい形にできます。

Shellinspect_git.sh
# 直近3件を「ハッシュ|著者|日付|件名」の1行形式で出す
git log -n 3 --pretty=format:"%h|%an|%ad|%s" --date=short

# あるコミットの差分(diff)を見る
git show a1b2c3d

# ファイル各行を最後に変更したコミットを対応づける(blame)
git blame src/parser.py
出力例 / Output
a1b2c3d|Alice|2024-05-01|fix: guard against null commit
b2c3d4e|Bob|2024-05-01|feat: add CSV export option
c3d4e5f|Alice|2024-05-02|refactor: extract parser module

この Step のまとめ

Git はコミットの積み重ねで歴史を残し、コミットは親をたどると DAG を作ります。1 コミットにはハッシュ・作者・日時・親・メッセージが付き、これがマイニングの材料です。次は、この履歴を Python でまとめて取り出します。

3

コミット履歴を抽出する

Extracting commit history

概要Overview

データ源が分かったら、次はそれをプログラムから扱える形で取り出す工程です。手作業でログを眺めるのではなく、全コミットを一つずつたどり、必要な項目(作者・日時・変更ファイル・差分)を集めます。Python なら、専用ライブラリを使うと数行で書けます。

仕組みHow it works

取り出し方は大きく2通り

ひとつは、Git のコマンド出力を整形して読む方法です。git log --pretty=format: で決まった区切り文字の行を出し、標準ライブラリ subprocess で受け取って分解します。依存が少なく、どんな環境でも動くのが利点です。

もうひとつは、専用ライブラリを使う方法です。PyDriller はコミットを 1 件ずつオブジェクトとして返し、変更ファイルや追加・削除行数まで手軽に取れます。GitPython はより低レベルに Git を操作できます。研究や分析では、扱いやすさから PyDriller がよく使われます。

取り出した後の形

集めたデータは、たいてい「1 行 = 1 つの変更(あるコミットの、ある変更ファイル)」という表の形にまとめます。列は「コミット・日時・作者・ファイル名・追加行・削除行」などです。このロング形式の表にしておくと、次の Step で pandas による集計がそのまま使えます。

コードで動かすRun the code

まずは依存の少ない subprocess + git log の方法です。区切り文字を決めて 1 行ずつ受け取り、分解します。

Pythongit_log_subprocess.py
import subprocess

fmt = "%h|%an|%ad|%s"  # ハッシュ|著者|日付|件名
out = subprocess.run(
    ["git", "log", "--pretty=format:" + fmt, "--date=short"],
    cwd="path/to/repo", capture_output=True, text=True).stdout

for line in out.splitlines():
    h, author, date, subject = line.split("|", 3)
    print(date, h, author, "::", subject)
出力例 / Output
2024-05-04 f6a7b8c Alice :: test: add cases for the parser
2024-05-03 e5f6a7b Bob :: fix typo in error message
2024-05-03 d4e5f6a Carol :: docs: update README usage
2024-05-02 c3d4e5f Alice :: refactor: extract parser module

次は PyDriller です。Repository(path).traverse_commits() でコミットを 1 件ずつたどり、コミットの属性や変更ファイルにアクセスします。traverse_commits は生成器なので、大きなリポジトリでも 1 件ずつ処理できます。

Pythonpydriller_traverse.py
# pip install pydriller
from pydriller import Repository

for commit in Repository("path/to/repo").traverse_commits():
    print(commit.hash[:7], commit.author.name, "::", commit.msg.splitlines()[0])
    for mf in commit.modified_files:
        print("    ", mf.change_type.name, mf.filename,
              "+" + str(mf.added_lines), "-" + str(mf.deleted_lines))
出力例 / Output
c3d4e5f Alice :: refactor: extract parser module
     MODIFY src/main.py +8 -22
     ADD src/parser.py +40 -0
b2c3d4e Bob :: feat: add CSV export option
     MODIFY src/cli.py +25 -3
     MODIFY README.md +6 -0

PyDriller のコミットからは、ほかにも commit.author_date(日時)、commit.parents(親ハッシュ)、commit.merge(マージか)などが取れます。変更ファイル側では added_lines / deleted_lines(追加・削除行数)や change_type(ADD・MODIFY・DELETE・RENAME)が使えます。これらを並べれば、次の Step の指標づくりにそのまま渡せます。

この Step のまとめ

履歴の抽出は「subprocess + git log」か「PyDriller」で。前者は依存が少なく、後者は変更ファイルや行数まで手軽に取れます。取り出したデータは「1行 = 1変更」の表にまとめておくと、次の集計がスムーズです。

4

変更履歴から指標を作る

Metrics from change history

概要Overview

抽出した履歴を、そのまま眺めても全体像はつかめません。そこで、たくさんの変更を数えて指標に変え、比べられるようにします。ここで作る指標は、後の欠陥予測やホットスポット分析の土台になります。

仕組みHow it works

コードチャーン(code churn)

あるファイルが、ある期間にどれだけ書き換えられたかの量です。もっとも素朴には追加行数と削除行数の合計で測ります。よく変わるファイルは、それだけ手が入り、壊れる機会も多いと考えられます。

churn(f) = Σ ( added(f) + deleted(f) ) added(f): ファイル f に加わった行数 deleted(f): 削られた行数 Σ: 対象期間の全コミットにわたる合計

変更頻度とホットスポット

変更頻度は、そのファイルが何回のコミットで触られたかです。チャーンや複雑度と組み合わせて、「よく変わり、かつ複雑で危ない場所」をホットスポットと呼びます。限られたレビューやテストの労力を、どこに集中させるべきかの手がかりになります。

共変更(co-change)

2 つのファイルが同じコミットで一緒に変更される傾向を共変更と呼びます。一緒に変わりやすいファイルは、見えない依存関係でつながっている可能性があります。共変更の強さは、頻出パターン分析と同じく支持度(support)と確信度(confidence)で表せます。

support(A, B) = A と B を共に含むコミット数
confidence(A → B) = support(A, B) / support(A) confidence(A→B) は「A を変えたとき、同時に B も変えた割合」。0 から 1 の値をとります。

作者数とバス係数

ファイルやモジュールに何人の作者が関わったかも重要な指標です。関連してバス係数(bus factor)という考え方があります。「何人が突然いなくなると、プロジェクトが立ち行かなくなるか」を表す目安で、知識が特定の人に偏っているリスクを示します。値が小さいほど危うい状態です。

デモ 2Demo 2

コードチャーン計算機

1 行に ファイル名 +追加 -削除 の形で変更を書きます(同じファイルが複数行あれば合計します)。ファイルごとにチャーン(追加+削除)を求め、降順に並べてバーで強調します。変更回数(何回のコミットで触られたか)も数えます。

例
デモ 3Demo 3

共変更マトリクス

1 行に、あるコミットで一緒に変更されたファイルを空白区切りで書きます(1行=1コミット)。すべてのファイル対について、同時に変更されたコミット数(支持度)と、確信度(片方を変えたとき、もう片方も変えた割合)を計算し、支持度の高い順に表にします。

例

コードで動かすRun the code

PyDriller で集めた変更を「1行=1変更」の表にすれば、あとは pandas の集計だけです。ファイルごとにチャーンを合計し、降順に並べます。

Pythonchurn_pandas.py
import pandas as pd
from pydriller import Repository

records = []
for commit in Repository("path/to/repo").traverse_commits():
    for mf in commit.modified_files:
        records.append({"file": mf.filename,
                        "added": mf.added_lines,
                        "deleted": mf.deleted_lines})

df = pd.DataFrame(records)
df["churn"] = df["added"] + df["deleted"]
hot = df.groupby("file").agg(
    commits=("file", "size"),
    churn=("churn", "sum"),
).sort_values("churn", ascending=False)
print(hot.head())
出力例 / Output
                      commits  churn
file
src/parser.py               8    173
tests/test_parser.py        5     65
src/utils.py                4     52
src/cli.py                  6     50
README.md                   3     10

この Step のまとめ

変更履歴は数えて指標に変えます。チャーン(追加+削除)でよく変わる場所を、共変更(支持度・確信度)でつながりを、作者数やバス係数で知識の偏りを測ります。集計は pandas の groupby ひとつで書けます。

5

課題とバグを追跡する

Issues & bug tracking

概要Overview

コミット履歴だけでなく、課題管理(Issue トラッカー)もマイニングの宝庫です。バグ報告・機能要望・議論が、いつ・誰によって・どう解決されたかまで残ります。ここでは、コミットと課題をどう結びつけ、どのコミットがバグを直したのか、さらにバグを埋め込んだのはどのコミットかを、どうやって推定するかを見ます。

仕組みHow it works

Issue トラッカーとコミットの紐付け

GitHub Issues や JIRA などの課題管理では、それぞれの課題に番号が振られます。開発者はコミットメッセージに fix #123 や closes #142 のように課題番号を書く慣習があり、これを手がかりにコミットと課題を機械的に結びつけられます。番号の書き方には流儀があるので、正規表現でひろうのが定番です。

バグ修正コミットの特定

どのコミットが「バグを直したか」を知りたい場面はよくあります。素朴には、メッセージに fix・bug・patch といった語が含まれるかで判定します。より確実には、バグとして分類された課題(Issue)に紐づくコミットを、バグ修正コミットとみなします。ただしメッセージだけの判定はノイズが多く、過大にも過小にも数えうる点に注意します。

SZZ アルゴリズムの考え方

バグを直したコミットが分かると、次は「そのバグを埋め込んだのはどのコミットか」を知りたくなります。これを推定する古典的な手法が SZZ アルゴリズムです。考え方はシンプルです。

  • バグ修正コミットの diff を見て、修正で変更・削除された行を特定する。
  • それらの行を git blame でさかのぼり、その行を最後に変更したコミットを突き止める。
  • そのコミットをバグ混入の候補とみなす。

実際には、空行やコメントだけの変更を除く、修正日より後のコミットを候補から外す、といった補正を加えて精度を上げます。SZZ は欠陥予測のデータ作り(どのコミットが「バグ入り」かのラベル付け)に広く使われます。

コードで動かすRun the code

まずは基本の一歩、コミットメッセージから課題番号を正規表現で取り出します。標準ライブラリ re だけで書けます。

Pythonlink_issues.py
import re

messages = [
    "fix login crash (fixes #142)",
    "Merge pull request #56 from feature/api",
    "refactor: tidy up parser, see #142 and #143",
    "update docs",
]
issue = re.compile(r"#(\d+)")
fixkw = re.compile(r"\b(fix|fixes|fixed|close|closes|resolve|resolves)\b", re.I)

for msg in messages:
    ids = issue.findall(msg)
    is_fix = bool(fixkw.search(msg))
    print(ids, "fix" if is_fix else "-", "::", msg)
出力例 / Output
['142'] fix :: fix login crash (fixes #142)
['56'] - :: Merge pull request #56 from feature/api
['142', '143'] - :: refactor: tidy up parser, see #142 and #143
[] - :: update docs

この「番号の抽出」と「fix 系キーワードの判定」を組み合わせると、バグ修正コミットと課題の対応表が作れます。そこから SZZ の考え方で git blame をたどれば、バグ混入コミットの候補まで手が届きます。PyDriller には SZZ を助ける機能もあり、修正コミットから変更行の最終更新コミットを取り出せます。

この Step のまとめ

コミットメッセージの #123 や fix 系キーワードで、コミットと課題・バグ修正を結びつけます。バグを埋め込んだコミットは、修正の変更行を blame でさかのぼる SZZ の発想で推定します。これが欠陥予測のラベル作りの土台です。

6

分析・可視化とこれから

Analysis, visualization & caveats

概要Overview

指標がそろったら、最後は集計して人に見せる工程です。数字の表をグラフにすると、傾向や外れ値が一目で分かります。ただし、掘り出したデータには落とし穴も多く、結論を急ぐと簡単に誤ります。ここでは可視化の流れと、注意すべき点、そして再現性の大切さを見ます。

仕組みHow it works

集計とグラフ化

ここまでで作った「1行=1変更」の表は、pandas の groupby でファイル別・作者別・月別に集計できます。集計した結果を matplotlib で棒グラフや折れ線にすれば、ホットスポットの順位、時間による変更量の推移、作者ごとの貢献などが見えてきます。流れとしては「PyDriller で抽出 → pandas で集計 → matplotlib で作図」の 3 段が定番です。

マイニングの落とし穴

リポジトリのデータは、そのままではノイズだらけです。代表的な注意点を挙げます。

  • tangled commit(もつれたコミット):1 つのコミットに、バグ修正と機能追加とリファクタが混ざっていること。指標の対応づけを狂わせます。
  • ノイズと表記ゆれ:同じ人が別名のメールでコミットする、自動整形やファイル移動で大量の行が動く、といった見かけ上の変更が紛れます。
  • 生存者バイアス:今も残っているファイルやプロジェクトだけを見ると、途中で消えたものを見落とし、結論が偏ります。

再現性

マイニングの結論は、抽出条件や前処理の設計に強く左右されます。だからこそ、対象リポジトリの版(コミットハッシュ)、使ったツールの版、除外したファイルの条件などを記録し、他人が同じ手順をたどれるようにすることが欠かせません。再現できない分析は、主張の根拠になりません。

この Step のまとめ

「抽出 → 集計(pandas)→ 作図(matplotlib)」で傾向を見せます。ただし tangled commit・ノイズ・生存者バイアスといった落とし穴があり、抽出条件やツールの版を記録して再現性を保つことが、正しい結論への近道です。

まとめWrap-up

学びの振り返りと、さらに学ぶために

このモジュールでは、開発の履歴からデータを掘り出す道具を、パイプラインに沿って順に手に入れました。

  • MSRとは:コミット・課題・レビュー・CI から実態を掘り出す、経験的ソフトウェア工学の営み。
  • Gitの仕組み:コミットの DAG、diff・blame、メタデータという材料を理解する。
  • 履歴の抽出:subprocess + git log、または PyDriller でプログラムから取り出す。
  • 変更指標:チャーン・変更頻度・共変更・作者数で、開発の傾向を数値にする。
  • 課題とバグ:課題番号の紐付け、バグ修正コミットの特定、SZZ でバグ混入をさかのぼる。
  • 分析・可視化:pandas と matplotlib で見せ、落とし穴を避け、再現性を保つ。

次に進むなら、下の道具と資料が役に立ちます。

公開データセット・基盤

  • 公開 GitHub リポジトリ:任意のオープンソースを clone すれば、それ自体がマイニング対象。
  • GH Archive:GitHub の公開イベントを時系列で集めたアーカイブ。
  • GHTorrent:GitHub の API データを収集したデータセット(歴史的資料)。
  • World of Code:巨大な OSS 横断のバージョン管理データ基盤。

ライブラリ

  • PyDriller:コミット・変更ファイルを手軽にたどる。
  • GitPython:Git を低レベルに操作する。
  • pandas:抽出データの集計・整形。
  • PyGithub:GitHub API 経由で Issue や PR を取得。

参考書

  • Bird, Menzies, Zimmermann『The Art and Science of Analyzing Software Data』
  • Menzies, Williams, Zimmermann『Perspectives on Data Science for Software Engineering』
  • MSR(Mining Software Repositories)分野の解説・チュートリアル

次のモジュールへ

おつかれさまでした

ここで学んだ「リポジトリから取り出す → 指標に変える → 見せる」という流れは、開発データに限らず、記録が残るあらゆる現象を定量的に読み解くときに共通の考え方です。手元のサンプルを書き換えて、3 つのデモをもう一度動かしてみてください。値がどう変わるかを確かめると、理解がぐっと深まります。

発展トピックBeyond this module

このコースでは扱わない、関係する発展トピック

このモジュールでは、リポジトリからデータを取り出し、指標に変えて読み解く「基礎の流れ」を扱いました。いっぽうで、実際の研究や実務では、より進んだ手法や大規模なデータ基盤が使われています。ここでは、このコースでは踏み込まないものの、知っておくと視野が広がる発展的なトピックを紹介します。深掘りは各自の興味に合わせてどうぞ。

欠陥予測とプロセス指標

  • SZZ と欠陥予測:バグ混入コミットを推定してラベルを作り、どこが壊れやすいかを予測する。
  • プロセス指標 対 コード指標:変更の履歴から作る指標(プロセス指標)と、コードそのものの指標(複雑度など)の、予測力の比較。
  • バグ局在化(fault localization):バグ報告から、原因になりそうなファイルや箇所を絞り込む。

社会技術ネットワーク

  • 開発者協働ネットワーク:誰と誰が同じコードを触るかをグラフにし、中心人物や分業構造を見る。
  • コードレビュー分析:レビューの速さ・指摘の量・承認の経路から、品質やボトルネックを読み解く。

大規模な鉱脈

  • World of Code:OSS 全体を横断する巨大なバージョン管理データ基盤。
  • GH Archive:GitHub の公開イベントを時系列に集めた大規模アーカイブ。
  • 大規模横断分析:多数のプロジェクトをまたいで傾向を調べる、生態系レベルの研究。

コードへの機械学習

  • コード表現学習:ソースコードやコミットをベクトルにして機械学習に載せる(code2vec・CodeBERT など)。
  • 変更のパターン学習:過去の変更から、次に必要な変更や共変更を学習して支援する。