メインコンテンツへスキップ
このデータセットには、ClickHouse リポジトリのすべてのコミットと変更が含まれています。これは、ClickHouse に付属するネイティブの git-import ツールを使用して生成できます。 生成されたデータには、次の各テーブルに対応する tsv ファイルが含まれます。
  • commits - 統計情報を含むコミット。
  • file_changes - 各コミットで変更されたファイルと、変更内容および統計情報。
  • line_changes - 各コミットの各変更ファイル内で変更されたすべての行と、その行の完全な情報、およびその行に対する前回の変更に関する情報。
2022年11月8日時点で、各 TSV のサイズと行数はおおよそ次のとおりです。
  • commits - 7.8M - 266,051 行
  • file_changes - 53M - 266,051 行
  • line_changes - 2.7G - 7,535,157 行

データの生成

この手順は任意です。データは無償で提供しています。詳しくは Downloading and inserting the data を参照してください。
ClickHouse リポジトリでは、完了までにおよそ 3 分かかります (2022 年 11 月 8 日時点、MacBook Pro 2021 での実測) 。 利用可能なオプションの一覧は、ツールに組み込まれたヘルプで確認できます。
このヘルプには、上記の各テーブルのDDLも記載されています。例:
これらのクエリは、どのリポジトリでも動作するはずです。自由に調べて、分かったことを報告してください 実行時間の目安は次のとおりです (2022年11月時点) 。
  • Linux - ~/clickhouse git-import - 160分

データのダウンロードと挿入

以下のデータを使用すると、動作環境を再現できます。なお、このデータセットは play.clickhouse.com でも利用できます。詳細は Queries を参照してください。 以下のリポジトリについて生成されたファイルを次に示します。 このデータを挿入するには、次のクエリを実行してデータベースを準備してください。
INSERT INTO SELECTs3 関数 を使用してデータを挿入します。たとえば、以下では ClickHouse の各ファイルを、それぞれ対応するテーブルに挿入します。 commits
file_changes
line_changes

クエリ

このツールは、ヘルプ出力でいくつかのクエリを提示します。ここでは、それらに加えて、関連する補足的な問いにも回答しています。これらのクエリは、ツール上の任意の順序ではなく、おおむね複雑さが増す順に並べています。 このデータセットは、play.clickhouse.comgit_clickhouse データベースで利用できます。すべてのクエリについて、この環境へのリンクを用意しており、必要に応じてデータベース名を調整しています。データ収集時点の違いにより、play の結果はここで示したものと異なる場合がある点に注意してください。

単一ファイルの履歴

最もシンプルなクエリです。ここでは、StorageReplicatedMergeTree.cpp のすべてのコミットメッセージを見ていきます。新しいメッセージのほうが興味深いことが多いため、最新のものが先に来るようにソートしています。 試す
行単位の変更も確認できます。なお、リネームは除外されるため、ファイルが別名だった時点でのリネームイベントより前の変更は表示されません。 play
名前変更を考慮してファイルの行ごとのコミット履歴を求める、より複雑なこのクエリのバリエーションもあります。

現在有効なファイルを特定する

これは、後続の分析でリポジトリ内の現在のファイルだけを対象にしたい場合に重要です。この集合は、リネームも削除もされていないファイル (その後に再追加や再リネームもされていないもの) として推定します。 ただし、dbmslibstests/testflows/ ディレクトリ配下のファイルについては、リネーム時のコミット履歴が壊れているようです。そのため、これらも除外します。 play
これにより、ファイルは名前を変更したあと、さらに元の名前に戻すこともできます。まず、名前変更によって削除されたファイルの一覧に対して、old_path を集約します。次に、これを各 path における最後の操作とユニオンします。最後に、この一覧から最終イベントが Delete ではないものだけを絞り込みます。 play
なお、インポート時には、いくつかのディレクトリを対象外にしています。具体的には、次のとおりです。 --skip-paths 'generated\.cpp|^(contrib|docs?|website|libs/(libcityhash|liblz4|libdivide|libvectorclass|libdouble-conversion|libcpuid|libzstd|libfarmhash|libmetrohash|libpoco|libwidechar_width))/' このパターンを git list-files に適用すると、18155 件となります。
したがって、現在の方法で把握できる現在のファイル数は推定値です ここで違いが生じるのは、いくつかの要因があるためです。
  • リネームは、ファイルに対するほかの変更と同時に発生することがあります。これらは file_changes では別々のイベントとして記録されますが、時刻は同じです。argMax 関数にはこれらを区別する方法がなく、先頭の値を選択します。挿入順 (正しい順序を判断できる唯一の手がかり) はユニオンをまたいで保持されないため、Modify イベントが選ばれてしまうことがあります。たとえば以下では、src/Functions/geometryFromColumn.h ファイルに複数の変更が加えられた後、src/Functions/geometryConverters.h にリネームされています。現在の解決策では、最新の変更として Modify イベントを選んでしまう可能性があり、その結果 src/Functions/geometryFromColumn.h が残ってしまうことがあります。
play
  • 破損したコミット履歴 - 削除イベントが欠落しています。発生元と原因は未特定です。
これらの違いが分析に大きな影響を及ぼすことはないはずです。このクエリの改善案を歓迎します

変更回数が最も多いファイルを一覧表示

現時点のファイルのみに絞ると、変更回数は削除数と追加数の合計として扱います。 play

コミットは通常、何曜日に行われますか?

play
金曜日には多少生産性が落ちることを考えると、これは納得できます。週末にもコードがコミットされているのはすばらしいですね!コントリビューターの皆さんに心より感謝します!

サブディレクトリ/ファイルの履歴 - 行数、コミット数、コントリビューター数の推移

このままだと、フィルタリングなしでは表示や可視化が現実的でないほど大きなクエリ結果になります。そのため、次の例ではファイルまたはサブディレクトリで絞り込めるようにしています。ここでは toStartOfWeek 関数を使って週単位でグループ化しています。必要に応じて調整してください。 play
このデータは可視化しやすいため、以下では Superset を使用します。 追加・削除された行数: コミット数と著者数:

著者数が最も多いファイルを一覧表示する

現時点で存在するファイルのみに絞ります。 試す

リポジトリ内で最も古いコードの行

現在のファイルのみが対象です。 試す

履歴が最も長いファイル

現在のファイルのみが対象です。 試す
中核となるデータ構造である Merge Tree は、長い改良の歴史の中で、当然ながら絶えず進化を続けてきました!

1 か月を通した docs とコードに対するコントリビューターの分布

データ収集時に、docs/ フォルダー内の変更はコミット 履歴が非常に煩雑なため除外されています。そのため、このクエリの結果は正確ではありません。 月の特定の時期、たとえば release 日の前後に docs が多く書かれているのでしょうか。countIf 関数を使って単純な比率を算出し、その結果を bar 関数で可視化できます。 試す
月末にかけてやや増えるかもしれませんが、全体としてはおおむね均等に分布しています。繰り返しになりますが、これはデータ挿入時に docs filter でフィルタリングされるため、あまり信頼できません。

最も幅広く影響している著者

ここでいう多様性とは、著者が貢献した重複のないファイル数を指します。 play
最近の作業で、誰が最も多様なコミットをしているかを見てみましょう。日付で絞り込む代わりに、著者ごとの直近 N 件のコミットに限定します (この例では 3 を使っていますが、必要に応じて変更してください) 。 試す

ある著者がよく扱うファイル

ここでは創業者の Alexey Milovidov を選び、分析対象を現時点のファイルに限定します。 試す
これは理にかなっています。というのも、Alexey は Change log の保守を担当してきたからです。では、人気のあるファイルを特定する際にファイルのベース名を使ったらどうでしょうか。これならリネームにも対応でき、コードへの貢献により焦点を当てられます。 play
これは、彼の関心のある分野をより反映しているのかもしれません。

著者数が少ない大きなファイル

このためには、まず最も大きなファイルを特定する必要があります。各ファイルについて、コミット の履歴から完全にファイルを復元してサイズを見積もるのは、非常にコストがかかります。 そこで概算として、現在存在するファイルのみに絞り、追加された行数の合計から削除された行数を差し引きます。そのうえで、ファイルの長さと著者数の比率を計算できます。 試す
テキストの Dictionaries はあまり現実的ではないかもしれないので、ファイル拡張子で絞り込んでコードのみにしましょう! 試す
これにはやや新しさによるバイアスがあります。新しいファイルほど、コミットされる機会が少ないためです。では、少なくとも1年以上前のファイルに限定するとどうなるでしょうか? 試す

時間別・曜日別・著者別のコミット数とコード行数の分布;特定のサブディレクトリ

ここでは、これを曜日ごとの追加・削除行数として見ます。この例では、Functionsディレクトリに着目します。 試す
時間帯別では、 試す
開発チームの大半がアムステルダムにいることを考えると、この分布は納得できます。bar 関数を使うと、こうした分布を視覚化できます。 試す

どの著者が他の著者のコードを書き換える傾向にあるかを示す著者マトリックス

sign = -1 はコードの削除を示します。句読点と空行の挿入は除外しています。 play
Sankey chart (SuperSet) を使うと、これを見やすく可視化できます。ビジュアルの幅を広げるため、LIMIT BY を 3 に増やし、各著者についてコードを最も多く削除した上位 3 人を取得しています。 Alexey は明らかに他の人のコードを削除するのが好きなようです。コード削除の傾向をよりバランスよく見るため、彼を除外しましょう。

各曜日で、コミットに占める割合が最も高いコントリビューターは誰ですか?

コミット数だけで見ると: 試す
ここでは、最も長く貢献してきたコントリビューターである創業者の Alexey が有利になる可能性があります。分析対象は直近1年に限定しましょう。 play
これはまだ少し単純すぎて、人々の実際の作業を反映できていません。 よりよい指標としては、過去1年間の総作業量に占める割合で見たときに、各日の最上位のコントリビューターが誰か、というものが考えられます。なお、コードの削除と追加は同等に扱います。 play

リポジトリ内のコード年齢の分布

分析対象は現在のファイルに限定しています。簡潔にするため、結果は深さ 2、各ルートフォルダーあたり 5 ファイルまでに絞っています。必要に応じて調整してください。 play

著者が書いたコードのうち、他の著者に削除された割合はどれくらいですか?

この問いに答えるには、ある著者が書いた行数を、別のコントリビューターによって削除されたその著者の総行数で割ります。 試す

最も頻繁に書き換えられたファイルを一覧表示するには?

この問いに対する最もシンプルな方法は、各 path について行の変更回数を数えることです (現在存在するファイルに限定) 。例:
ただし、これは、どのコミットでもファイルの大部分が変更される「re-write」の概念までは捉えていません。これには、より複雑なクエリが必要です。ここでは、ファイルの 50% 超が削除され、かつ 50% が追加された場合を rewrite とみなします。何を rewrite と見なすかについては、この解釈に合わせてクエリを調整できます。 このクエリは、現在存在するファイルのみに限定されています。pathcommit_hash でグループ化して追加行数と削除行数を返すことで、すべてのファイル変更を一覧化します。ウィンドウ関数を使って累積和を計算し、各変更によるファイルサイズへの影響を lines added - lines removed と見積もることで、任意の時点におけるファイルの総サイズを推定します。この統計量を使うと、変更ごとにファイルの何パーセントが追加または削除されたかを計算できます。最後に、ファイルごとに rewrite に該当する変更回数を数えます。つまり、(percent_add >= 0.5) AND (percent_delete >= 0.5) AND current_size > 50 です。なお、ファイルへの初期の変更が rewrite として数えられないようにするため、対象は 50 行を超えるファイルに限定しています。これにより、rewrite されやすい可能性がある非常に小さなファイルへの偏りも避けられます。 play

コードがリポジトリに最も残りやすいのは何曜日ですか?

このためには、コードの各行を一意に特定する必要があります。同じ行が1つのファイル内に複数回現れることがあるため、ここではパスと行の内容を使ってそれを近似的に識別します。 追加された行を対象にクエリを実行し、それを削除された行と join したうえで、後者の発生時刻が前者より新しいケースに絞り込みます。これにより、2つのイベントの間の時間を計算できる削除済みの行が得られます。 最後に、このデータセット全体を集計し、行がリポジトリに残る平均日数を曜日ごとに算出します。 play

平均コード年齢でソートしたファイル

このクエリは、パスと行の内容を使ってコードの各行を一意に識別するという、コードがリポジトリに残る可能性が最も高い曜日はいつですか と同じ考え方に基づいています。 これにより、ある行が追加されてから削除されるまでの時間を特定できます。ただし、ここでは現在存在するファイル内の現行コードのみに絞り込み、行ごとの期間をファイル単位で平均しています。 play

テスト / CPP コード / コメントをより多く書く傾向があるのは誰でしょうか?

この問いには、いくつかのアプローチがあります。コードとテストの比率に着目すると、このクエリは比較的シンプルで、tests を含むフォルダーへのコントリビューション数を数え、それが総コントリビューション数に占める比率を算出します。 ここでは、継続的に commit しているユーザーに絞り、一度限りのコントリビューションによる偏りを避けるため、変更回数が 20 回を超えるユーザーに限定しています。 play
この分布をヒストグラムとして表示できます。 試す
ほとんどのコントリビューターは、予想どおり、テストよりもコードを書く量のほうが多いです。 では、コードをコントリビュートする際に最も多くのコメントを追加するのは誰でしょうか? play
コードへの貢献度でソートしている点に注意してください。主要な貢献者はいずれもこの割合が驚くほど高く、それが私たちのコードの読みやすさの一因となっています。

コード/コメントの割合に対して、著者のコミットは時系列でどのように変化するでしょうか?

これを著者ごとに計算するのは簡単です。
ただし理想的には、すべての著者について、最初にコミットした日を起点に、これが全体としてどのように変化するかを見たいところです。著者が書くコメントの数は、時間とともに徐々に減っていくのでしょうか。 これをコンピュートするには、まず各著者のコメント比率の推移を求めます。これは Who tends to write more tests / CPP code / comments? と同様です。これを各著者の開始日と join することで、週ごとのオフセット別にコメント比率を計算できるようにします。 その後、すべての著者について週オフセットごとの平均を計算し、10週ごとに抽出して結果をサンプルします。 play
喜ばしいことに、コメント率はほぼ一定で、著者の貢献期間が長くなっても低下しません。

コードが書き換えられるまでの平均時間と中央値 (コード劣化の半減期) はどれくらいですか?

書き換えを特定するには、最も多く書き換えられた、または最も多くの著者によって書き換えられたファイルを一覧表示する と同じ原理を使いますが、ここではすべてのファイルを対象にします。各ファイルについて書き換えの間隔を算出するために、ウィンドウ関数 を使用します。これにより、すべてのファイルにわたる平均値と中央値を計算できます。 play

コードが最も書き直されやすいのは、いつ書かれた場合ですか?

コードが書き直されるまでの平均時間と中央値 (コード劣化の半減期) はどれくらいですか? および 最も多く書き直された、または最も多くの著者によって書き直されたファイルを一覧表示する と同様ですが、ここでは曜日ごとに集計します。必要に応じて、たとえば月ごとに集計するよう調整してください。 play

どの著者のコードが最も長く残るのでしょうか?

ここでいう “sticky” とは、著者のコードが書き換えられるまでどれくらい維持されるかを指します。前の質問 コードが書き換えられるまでの平均時間と中央値 (コード劣化の半減期) はどれくらいですか? と同様に、書き換えの指標には同じ基準、つまりファイルに対して追加が50%、削除が50%発生した時点を用います。著者ごとの平均書き換え時間を算出し、対象は3つ以上のファイルに関与した contributors に限定します。 play

著者ごとの最長連続コミット日数

このクエリでは、まず著者がコミットした日を計算する必要があります。著者ごとにパーティション化したウィンドウ関数を使うことで、各コミット間の日数を計算できます。各コミットについて、前回のコミットから 1 日しか経過していなければ連続 (1)、そうでなければ 0 とし、この結果を consecutive_day に格納します。 続いて、配列関数を使って各著者の連続する 1 の最長の数列を計算します。まず、groupArray 関数を使って、各著者の consecutive_day の値をすべてまとめます。次に、この 1 と 0 の配列を 0 を区切りとして部分配列に分割します。最後に、最も長い部分配列を求めます。 play

ファイルの行単位のコミット履歴

ファイル名は変更できます。この場合、名前変更イベントが記録され、path カラムにはファイルの新しいパスが設定され、old_path には以前の場所が入ります。例: 実行
そのため、すべての行変更やファイル変更を結び付ける単一の値がなく、ファイルの完全な履歴を追うのは困難です。 これに対処するために、ユーザー定義関数 (UDF) を使用できます。現時点では再帰的には使えないため、ファイルの履歴を特定するには、互いを明示的に呼び出す一連の UDF を定義する必要があります。 つまり、追跡できるリネームの深さには上限があり、以下の例では 5 段階までです。ファイル名がこれ以上変更されることはあまり考えられないため、現時点ではこれで十分です。
file_path_history('src/Storages/StorageReplicatedMergeTree.cpp') を呼び出すと、リネームの履歴を再帰的にたどり、各関数呼び出しで old_path を使って次のレベルを呼び出します。結果は arrayConcat で結合されます。 たとえば、
この機能を使うことで、ファイルの履歴全体にわたるコミットを組み立てられるようになります。この例では、path の各値ごとに1つのコミットを示します。

未解決の課題

Git blame

これは、現時点では array functions で状態を保持できないため、正確な結果を得るのが特に難しいからです。これを可能にするのが arrayFoldarrayReduce で、各反復で状態を保持できるようになります。 高レベルな分析には十分な近似的な解決策としては、たとえば次のようなものが考えられます。
ここでは、より正確な解決策や改善案を歓迎します。
最終更新日 2026年6月19日