メインコンテンツまでスキップ
メインコンテンツまでスキップ

GitHubデータを使用したClickHouseでのクエリの作成

このデータセットには、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 行

データの生成

これは任意です。データは自由に配布されています - データのダウンロードと挿入を参照してください。

これを完了するには、ClickHouseリポジトリに対して約3分(2022年11月8日現在、MacBook Pro 2021で)かかります。

使用可能なオプションの完全なリストは、ツールのネイティブヘルプから取得できます。

このヘルプでは、上記の各テーブルのDDLも提供されています。例えば:

これらのクエリは任意のリポジトリで機能するはずです。自由に探索し、発見を報告してください 実行時間に関するいくつかのガイドライン(2022年11月現在):

  • Linux - ~/clickhouse git-import - 160 分

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

以下のデータは、動作環境を再現するために使用できます。あるいは、このデータセットは play.clickhouse.com で入手可能です - 詳細は クエリ を参照してください。

以下のリポジトリに対する生成ファイルは、下記にあります:

このデータを挿入するためには、以下のクエリを実行してデータベースを準備します。

データを INSERT INTO SELECTs3関数 を使用して挿入します。例えば、以下のようにClickHouseのファイルをそれぞれのテーブルに挿入します:

commits

file_changes

line_changes

クエリ

ツールは、そのヘルプ出力にいくつかのクエリを提案します。私たちはこれらに加えて、興味深い追加の疑問にも答えました。これらのクエリは、ツールの任意の順序に対して複雑さが徐々に増していくものです。

このデータセットは play.clickhouse.comgit_clickhouse データベースにあります。すべてのクエリのためにこの環境へのリンクを提供し、必要に応じてデータベース名を調整します。データ収集の時間の違いにより、プレイの結果はここに示されたものとは異なる場合があることに注意してください。

単一ファイルの履歴

最もシンプルなクエリです。ここでは、StorageReplicatedMergeTree.cpp のすべてのコミットメッセージを見ます。これらはより興味深い可能性があるため、最近のメッセージから最初に並べ替えます。

play

リネームを除外した行の変更も確認できます。つまり、ファイルが異なる名前の下で存在していた時のリネームイベント前の変更は表示しません:

play

ファイルの 行ごとのコミット履歴 を求める、より複雑なクエリもあります。

現在のアクティブファイルを探す

これは、リポジトリ内の現在のファイルのみを考慮する際に重要です。このセットを、名前や削除(その後に再追加/再名前変更)されていないファイルとして見積もります。

dbms, libs, tests/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が報告されます。

したがって、現在のソリューションは現在のファイルの推定値です。

ここでの違いは、いくつかの要因によって引き起こされます:

  • リネームは、ファイルへの他の変更と同時に行われる場合があります。これらはファイル変更において別のイベントとしてリストアップされますが、同じ時間で行われます。argMax関数は、これらを区別する手段がなく、最初の値を選択します。挿入の自然な順序(正しい順序を知る唯一の手段)は、ユニオンを横断して保持されないため、変更されたイベントが選択になる可能性があります。例えば、src/Functions/geometryFromColumn.h ファイルは、src/Functions/geometryConverters.h にリネームされる前に何度も変更されている可能性があります。現在のソリューションでは、Modifyイベントを最新の変更として選択することにより、src/Functions/geometryFromColumn.hが保持されることがあります。

play

  • 破損したコミット履歴 - 削除イベントの欠落。ソースと原因は未定義。

これらの違いは、私たちの分析に意味のある影響を与えないはずです。 このクエリの改善版を歓迎します。

修正回数が最も多いファイルのリスト

現在のファイルに制限し、変更の数を削除と追加の合計として考えます。

play

コミットが通常発生する曜日は?

play

これは、金曜日に生産性が低下することを考慮しており、週末にコードをコミットする人たちを見るのは素晴らしいことです!私たちの貢献者に大きな感謝を!

サブディレクトリ/ファイルの履歴 - 行数、コミット数、時間の経過に伴う貢献者数

未フィルタ状態で示したり視覚化することは非現実的な大きなクエリ結果が生成されます。そのため、以下の例ではファイルやサブディレクトリをフィルタリングできるようにします。ここでは toStartOfWeek 関数を使用して週ごとにグループ化します - それは要求に応じて適応可能です。

play

このデータは視覚化しやすいです。以下にSupersetを使用します。

追加された行数と削除された行数について:

コミットと著者について:

最大数の著者を持つファイルのリスト

現在のファイルのみに制限します。

play

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

現在のファイルのみに制限されています。

play

最も長い履歴を持つファイル

現在のファイルのみに制限されています。

play

私たちのコアデータ構造である Merge Tree は、長い編集履歴を持ちながら継続的に進化しています!

ドキュメントとコードに関する貢献者の分布

データ取得中、docs/フォルダーの変更は非常にコミットの汚れた履歴のためにフィルタされています。したがって、このクエリの結果は正確ではありません。

私たちは特定の月の特定の時期、例えばリリース日付の周囲に、より多くのドキュメントを書いているのでしょうか? countIf 関数を使用して簡単な比率を計算し、結果を bar 関数を使用して視覚化できます。

play

月末に少し多くなっているかもしれませんが、全体としては良い均等分布を保っています。再度、これが信頼できないのは、データ挿入中にドキュメントフィルタがフィルタリングされたためです。

最も多様な影響を与えた著者

ここでの多様性の考慮は、著者が貢献したユニークなファイルの数です。

play

最近の作業において、最も多様なコミットを持っているのは誰か見てみましょう。日付で制限するのではなく、著者の最後の N コミットに制限します(この場合、3 を使用していますが、変更しても構いません)。

play

著者の好みのファイル

ここでは、私たちの創始者である Alexey Milovidov を選択し、分析を現在のファイルに制限します。

play

これは、Alexeyがチェンジログの維持を担当しているため、納得がいきます。しかし、ファイルの基本名を使用して彼の人気のあるファイルを特定するとどうでしょうか。これはリネームを許可し、コードの貢献に焦点を当てる必要があります。

play

これは、彼の興味のある分野をより正確に反映しているかもしれません。

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

これを行うために、まず最大ファイルを特定する必要があります。コミットの履歴からすべてのファイルを完全に再構築して推定するのは非常に高コストです!

推定するために、現在のファイルに制限すると仮定し、行追加を合計し、削除を減算します。そして、著者の数に対する長さの比率を計算できます。

play

テキスト辞書はリアルさがないかもしれないので、ファイル拡張子フィルタを使用してコードのみに制限しましょう!

play

これは少し最近の偏りがあります - 新しいファイルはコミットの機会が少なくなります。1 年以上経過したファイルに制限するとどうなるでしょうか?

play

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

この分析では、週の各曜日に追加および削除された行数を解釈します。この場合、Functions ディレクトリ に焦点を当てます。

実行

また、時間帯別にも分析します。

実行

この分布は、ほとんどの開発チームがアムステルダムにいることを考えると理にかなっています。bar 関数を利用してこれらの分布を可視化することができます。

実行

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

sign = -1 はコードの削除を示します。句読点や空行の挿入は除外します。

実行

Sankey チャート (SuperSet) を利用して視覚化できます。視覚的なバラエティを持たせるために、LIMIT BY を 3 に増やして各著者のトップ 3 コード削除者を取得します。

Alexey は他人のコードを削除するのが好きなようです。もう少しバランスのとれた視点を得るために、彼を除外します。

曜日別に最高の寄与率を持つのは誰か?

コミット数だけを考慮する場合:

実行

さて、ここで長い寄与者—私たちの創業者 Alexey に有利な点があるかもしれません。分析を昨年に限定しましょう。

実行

これでもまだシンプルすぎて、人物の働きぶりを反映していません。

より良い指標としては、過去一年間の総作業に対する割合として各日のトップ寄与者を考慮することができます。削除と追加のコードを同等と見なします。

実行

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

分析は現在のファイルに制限します。簡潔さのために、結果を深さ2、ルートフォルダごとに5ファイルに制限します。必要に応じて調整してください。

実行

著者のコードの何パーセントが他の著者によって削除されたか

この質問のためには、著者によって書かれた行数を、他の貢献者によって削除された合計行数で割る必要があります。

実行

最も多く書き直されたファイルをリストする

この質問に対する最もシンプルなアプローチは、パスごとの行の変更を単純にカウントすることかもしれません(現在のファイルに制限)。

ただし、これにより「書き直し」の概念を捉えることはできません。書き直しは、大部分のファイルがコミットの際に変更される場合を指します。これは、より複雑なクエリが必要です。書き直しを、ファイルの 50% 以上が削除され、50% 以上が追加された場合とみなします。このクエリを自分の解釈に合わせて調整できます。

現在のファイルのみに制限され、このクエリはファイルの変更をグループ化して pathcommit_hash に基づいて、追加および削除された行の数を返します。ウィンドウ関数を使用して、ファイルの合計サイズを推定し、追加された行数 - 削除された行数としてファイルサイズへの影響を推定します。この統計を使用して、各変更について追加または削除されたファイルのパーセンテージを計算できます。最後に、書き直しと見なされるファイル変更の数をカウントします (percent_add >= 0.5) AND (percent_delete >= 0.5) AND current_size > 50。ファイルが 50 行以上である必要があるため、ファイルへの初期寄与が書き直しとしてカウントされることを避けます。また、非常に小さなファイルにバイアスがかかるのを避けます。

実行

どの曜日にコードがリポジトリに残る可能性が最も高いか?

これには、コードの行を一意に特定する必要があります。同じ行がファイル内で複数回表示される可能性があるため、パスと行の内容を使用して推定します。

追加された行をクエリし、削除された行と結合します。後者が前者よりも最近発生している場合にフィルターを掛けます。これにより、削除された行から、これら二つのイベントの間の時間を計算できます。

最後に、データセットを集約して、曜日ごとのリポジトリ内に行が残る平均日数を計算します。

実行

コードの平均年齢でソートされたファイル

このクエリは、リポジトリにコードが最も長く残る曜日は何かと同じ原理を使用しています - パスと行の内容を使用してコードの行を一意に特定することを目的としています。 これにより、行が追加されてから削除されるまでの時間を特定することができます。ただし、現在のファイルとコードのみにフィルタリングし、各ファイルの行の平均時間を計算します。

play

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

この質問に対するアプローチはいくつかあります。コードとテストの比率に焦点を当てると、このクエリは比較的シンプルです - testsを含むフォルダへの貢献の数をカウントし、総貢献に対する比率を計算します。

なお、20回以上の変更を行ったユーザーに制限して、定期的なコミッターに焦点を当て、一度きりの貢献に偏りが出ないようにしています。

play

この分布をヒストグラムとしてプロットできます。

play

ほとんどの貢献者はテストよりも多くのコードを書く傾向があり、予想通りです。

コードに貢献する際に最も多くコメントを追加するのは誰かについてはどうでしょうか?

play

なお、コードの貢献によってソートしています。驚くべきことに、全ての最大の貢献者にかなり高い%があり、これが我々のコードを非常に読みやすくしている要因の一部です。

作成者のコミットは時間とともにコード/コメントの割合に関してどのように変化するか?

作成者ごとにこれを計算するのは簡単です。

しかし、理想的には、全ての作成者についての集計を見たいところであり、彼らがコミットを始めた最初の日からの基準値に対するコメントの割合の変化を見たいわけです。彼らは徐々に書くコメントの数を減らすのでしょうか?

これを計算するために、まず各作成者の時間におけるコメント比率を求めます - 誰がより多くのテスト / CPP コード / コメントを書く傾向があるか?のような方法です。これを各作成者の開始日と結合することで、週のオフセットごとにコメントの割合を計算できます。

各作成者の週に対する平均を計算した後、これらの結果をサンプリングして、10週ごとに選択します。

play

励みになることに、我々のコメント%はかなり安定しており、作成者の貢献が長くなるにつれて低下しないことが分かります。

コードが再書きされるまでの平均時間と中央値(コードの decay の半減期)は何か?

最も多く書き直されたファイルのリストと同様の原則を使用してリライトを特定できますが、すべてのファイルを考慮します。ウィンドウ関数を使用して、各ファイルの書き直しの間の時間を計算します。これをもとに、すべてのファイルで平均と中央値を計算できます。

play

どの時間帯にコードが書かれると、最も書き直される可能性が高いか?

コードが再書きされるまでの平均時間と中央値(コードの decay の半減期)は何か?及び最も多く書き直されたファイルのリストに類似しており、ただし、週の日数で集計します。必要に応じて調整します(例:年の月)。

play

どの作成者のコードが最も「粘着性」があるか?

「粘着性」とは、作成者のコードが再び書き直されるまでの期間を定義します。前の質問コードが再書きされるまでの平均時間と中央値(コードの decay の半減期)は何か?と同様に、リライトのための同じメトリックを使用します。すなわち、ファイルに対する50%の追加と50%の削除。作成者ごとに平均リライト時間を計算し、2ファイル以上の貢献者のみを考慮します。

play

作成者別の連続コミット日数

このクエリは、まず作成者がコミットした日数を計算する必要があります。ウィンドウ関数を使用して作成者ごとにパーティションを区切り、コミット間の日数を計算します。各コミットに対して、前回のコミットからの経過時間が1日であれば連続日とマークし(1)、そうでなければ0とし、この結果を consecutive_day に格納します。

その後、各作成者の連続した1の最長シーケンスを計算します。まず、groupArray 関数を使用して、作成者ごとのすべての consecutive_day 値を集計します。この1と0の配列を0の値で分割してサブ配列にし、最後に最も長いサブ配列を計算します。

play

行ごとのコミット履歴

ファイルは名前を変更できます。これが発生すると、path カラムはファイルの新しいパスに設定され、old_path は以前の場所を表します。例えば:

play

これにより、ファイルの完全な履歴を見ることが難しくなります。なぜなら、行またはファイルの変更を接続する単一の値がないからです。

これに対処するために、ユーザー定義関数 (UDF) を使用することができます。現在、これらは再帰的ではないため、ファイルの履歴を識別するには、相互に明示的に呼び出す一連の UDF を定義する必要があります。

これは、最大深さまでの名前変更のみを追跡できることを意味します。以下の例は5段階の深さです。この深さ以上にファイルが名前を変更されることは考えにくいため、現時点ではこれで十分です。

file_path_history('src/Storages/StorageReplicatedMergeTree.cpp') を呼び出すことで、名前変更の履歴を再帰的にたどります。各関数は old_path を用いて次のレベルを呼び出します。結果は arrayConcat を使用して結合されます。

例えば:

この機能を使用して、ファイルの完全な履歴のためにコミットを組み立てることができます。この例では、各パス値に対して1つのコミットを示しています。

未解決の質問

Git blame

これは、現在、配列関数内で状態を保持することができないため、正確な結果を得るのが特に難しいです。これは、arrayFold または arrayReduce によって可能になるでしょう。これにより、各イテレーションで状態を保持することができます。

おおよその解決策は、高レベルの分析に十分なもので、このようになります:

ここで正確で改善された解決策を歓迎します。