はじめに
これはpydriller公式ドキュメントに沿ってしながら和訳したものです。できる限り公式の形式をそのままでDeepLを使い、訳したつもりです。間違いがありましたらコメントなどをよろしくお願いします。バージョンは現在の最新版で2.4.1です。
現在は途中までです。
概要/インストール
PyDriller は、ソフトウェアリポジトリのマイニングを行う開発者を支援する Python フレームワークです。PyDriller を使用すると、コミット、開発者、変更、差分、ソースコードなどの情報を Git リポジトリから簡単に抽出し、CSV ファイルを素早くエクスポートできます。
要件
PyDriller のインストール
PyDriller のインストールはpipを使って簡単に行うことができます。pipがインストールされている前提の元、コマンドラインから以下を実行するだけです。
$ pip install pydriller
このコマンドは、Python Package Indexから最新版のGitPythonをダウンロードし、システムにインストールします。また、必要な依存関係もインストールされます。
ソースコード
PyDrillerのgit repoはGitHubで公開されており、閲覧することが可能です。
クローンは
$ git clone https://github.com/ishepard/pydriller
$ cd pydriller
オプション(推奨)で、virtualenvを使用する。
$ virtualenv -p python3 venv
$ source venv/bin/activate
必要なもののインストール
$ pip install -r requirements.txt
$ pip install -r test-requirements.txt
$ unzip test-repos.zip
Pydrillerの引用方法
@inbook{PyDriller,
title = "PyDriller: Python Framework for Mining Software Repositories",
abstract = "Software repositories contain historical and valuable information about the overall development of software systems. Mining software repositories (MSR) is nowadays considered one of the most interesting growing fields within software engineering. MSR focuses on extracting and analyzing data available in software repositories to uncover interesting, useful, and actionable information about the system. Even though MSR plays an important role in software engineering research, few tools have been created and made public to support developers in extracting information from Git repository. In this paper, we present PyDriller, a Python Framework that eases the process of mining Git. We compare our tool against the state-of-the-art Python Framework GitPython, demonstrating that PyDriller can achieve the same results with, on average, 50% less LOC and significantly lower complexity.URL: https://github.com/ishepard/pydrillerMaterials: https://doi.org/10.5281/zenodo.1327363Pre-print: https://doi.org/10.5281/zenodo.1327411",
author = "Spadini, Davide and Aniche, Maurício and Bacchelli, Alberto",
year = "2018",
doi = "10.1145/3236024.3264598",
booktitle = "The 26th ACM Joint European Software Engineering Conference and Symposium on the Foundations of Software Engineering (ESEC/FSE)",
}
入門編
PyDriller の使用方法は非常に簡単で、Repositoryクラスを作成するのみです。このクラスはリポジトリへのパスを入力として受け取り、コミットを反復処理するジェネレータを返します。例えば、以下のようになります。
for commit in Repository('path/to/the/repo').traverse_commits():
print('Hash {}, author {}'.format(commit.hash, commit.author.name))
このソースコードでは、各コミットの開発者の名前を表示します。
Repositoryの中では、どのプロジェクトを分析するか、どのコミットを分析するか、どの日付を分析するかなどを設定する必要があります。どのような設定が可能かについては、Repositoryを参照してください。
リポジトリ(ローカルとリモートの両方)のリストを渡すこともでき、PyDriller は順次解析します。リモートリポジトリの場合、PyDriller は一時フォルダにクローンを作成し、その後削除します。例は以下のコードです。
urls = ["repos/repo1", "repos/repo2", "https://github.com/ishepard/pydriller.git", "repos/repo3", "https://github.com/apache/hadoop.git"]
for commit in Repository(path_to_repo=urls).traverse_commits():
print("Project {}, commit {}, date {}".format(
commit.project_path, commit.hash, commit.author_date))
別の例として、コミットごとに変更されたファイルをすべて表示することにしましょう。これは魔法をかけるようなものです。
for commit in Repository('path/to/the/repo').traverse_commits():
for file in commit.modified_files:
print('Author {} modified {} in commit {}'.format(commit.author.name, file.filename, commit.hash))
これで大丈夫です。
処理において、PyDriller が Git リポジトリを開き、必要な情報をすべて抽出します。その後、フレームワークはコミットを反復処理できるジェネレータを返します。
さらに、PyDriller はコミットで変更されたすべてのファイルの構造指標を計算することができます。これらの指標を計算するために、Pydriller は Lizardに依存しています。Lizard は、多くの異なるプログラミング言語のソースコードをクラスとメソッドの両方のレベルで分析できる強力なツールです!
for commit in Repository('path/to/the/repo').traverse_commits():
for file in commit.modified_files:
print('{} has complexity of {}, and it contains {} methods'.format(
file.filename, file.complexity, len(file.methods)))
Repository
Repository は Pydriller のメインクラスであり、必要なコミットのリストを返す役割を担っています。PyDriller を使用してソフトウェアリポジトリをマイニングする主な利点の 1 つは、高度に設定可能であることです。ここでは、Repository に渡すことができるすべてのオプションを見ていきます。
簡単な説明
Pydrillerの "Hello World "(最初の一歩)です。
for commit in Repository("/Users/dspadini/myrepo").traverse_commits():
print(commit.hash)
Repositoryの関数traverse_commits()は、選択されたコミット(この単純なケースではすべてのコミット)を返します。では、Repositoryをどのようにカスタマイズするか見てみましょう。
分析対象となるプロジェクトの選定
Repositoryの唯一の必須パラメータはpath_to_repoで、分析するリポジトリを指定します。str型またはList[str]型でなければならず、1つのリポジトリだけを分析するか、複数のリポジトリを分析するかを指定します。
さらに、PyDriller はローカルとリモートの両方のリポジトリをサポートしています。URLを渡すと、PyDriller は自動的に一時フォルダを作成し、リポジトリをクローンして研究を実行し、最後に一時フォルダを削除します。
例えば、Repositoryの入力として考えられるのは、以下のようなものです。
# ローカルリポジトリを1つだけ分析
url = "repos/pydriller/"
# ローカルリポジトリを2つ分析
url = ["repos/pydriller/", "repos/anotherrepo/"]
# ローカルとリモートの両方を分析
url = ["repos/pydriller/", "https://github.com/apache/hadoop.git", "repos/anotherrepo"]
# 1つのリモートリポジトリを分析
url = "https://github.com/apache/hadoop.git"
PyDriller がどのプロジェクトを解析しているかを把握するために、Commit オブジェクトには project_name というプロパティがあります。
コミット範囲を選択する
デフォルトでは、PyDriller はリポジトリ内のすべてのコミットを分析します。しかし、特定のコミットのみを取り出すために Repository にフィルタを適用することができます。
- single (str): コミットの単一のハッシュです。このコミットに対してのみ呼び出されます。
FROM:
- since (datetime): この日付以降のコミットのみが分析対象となります。
- from_commit (str): このコミットハッシュ以降のコミットのみ解析されます。
- from_tag (str): このコミットタグ以降のコミットのみが分析対象となります。
TO:
- to (datetime): この日付までのコミットのみが分析されます。
- to_commit (str): このコミットハッシュまでのコミットのみが解析されます。
- to_tag (str): このコミットタグまでのコミットのみが解析されます。
ORDER:
- order (str): 'date-order', 'author-date-order', 'topo-order', 'reverse' のいずれか (詳しくはこちらを参照)。注意: デフォルトでは、PyDriller はコミットを古いものから新しいものへと返します。その逆(新しいものから古いものへ)が必要な場合は、"order='reverse'" を使用してください。
例:
# 単一コミットの解析
Repository('path/to/the/repo', single='6411e3096dd2070438a17b225f44475136e54e3a').traverse_commits()
# 2016/10/8から
Repository('path/to/the/repo', since=datetime(2016, 10, 8, 17, 0, 0)).traverse_commits()
# 2日間
dt1 = datetime(2016, 10, 8, 17, 0, 0)
dt2 = datetime(2016, 10, 8, 17, 59, 0)
Repository('path/to/the/repo', since=dt1, to=dt2).traverse_commits()
# tagの間
from_tag = 'tag1'
to_tag = 'tag2'
Repository('path/to/the/repo', from_tag=from_tag, to_tag=to_tag).traverse_commits()
# ある日付まで
dt1 = datetime(2016, 10, 8, 17, 0, 0, tzinfo=to_zone)
Repository('path/to/the/repo', to=dt1).traverse_commits()
# !!!!! エラー !!!!! これは実行できません
Repository('path/to/the/repo', from_tag=from_tag, from_commit=from_commit).traverse_commits()
重要:同じカテゴリのフィルターを複数設定することはできません(例えば、複数のfrom)。また、単一のフィルタを他のフィルタと一緒にすることもできません!
コミットのフィルタリング
PyDriller には、適用可能な一般的なコミットフィルタのセットが付属しています。
- only_in_branch (str): このブランチに属するコミットのみを分析します。
- only_no_merge (bool): マージコミットでないコミットのみを分析します。
- only_authors (List[str]): この著者によって作成されたコミットのみを分析します。メールではなく、ユーザー名でチェックされます。
- only_commits (List[str]): これらのコミットのみが分析されます。
- only_releases (bool): タグ付けされたコミットのみを分析します(「リリース」はGitHubの用語で、実際にはGitには存在しません)。
- filepath (str): このファイルを変更したコミットのみが分析されます。
- only_modifications_with_file_types (List[str]): そのファイルタイプで少なくとも一つの変更が行われたコミットのみを分析します。例えば、".java "を渡した場合、少なくとも一つのJavaファイルが変更されたコミットのみを訪問します。明らかに、他のコミット(例えば、Javaファイルを変更しなかったコミット)はスキップします。
例
# ブランチ1でのコミットのみ
Repository('path/to/the/repo', only_in_branch='branch1').traverse_commits()
# ブランチ1でのコミットのみで、マージは行わない # ブランチ1でのコミットのみで、マージは行わない
Repository('path/to/the/repo', only_in_branch='branch1', only_no_merge=True).traverse_commits()
# 作者「ishepard」(そう、私(Pydrillerの作者)です)のコミットのみ
Repository('path/to/the/repo', only_authors=['ishepard']).traverse_commits()
# この3つのコミットのみ
Repository('path/to/the/repo', only_commits=['hash1', 'hash2', 'hash3']).traverse_commits()
# "Matricula.javax "を変更したコミットのみです。
Repository('path/to/the/repo', filepath='Matricula.javax').traverse_commits()
# java ファイルを変更したコミットのみ
Repository('path/to/the/repo', only_modifications_with_file_types=['.java']).traverse_commits()
構成
Pydriller では、コミットのフィルタリングや日付範囲の定義以外に、以下のような設定もサポートしています。
- include_refs (bool): コミット解析に refs と HEAD を含めるかどうか (--all フラグの追加と同等)。
- include_remotes (bool): リモートコミットを解析に含めるかどうか(--remotes フラグの追加と同等)。
- clone_repo_to (str): リポジトリが URL の場合、Pydriller はこのディレクトリにリポジトリをクローンします。
- num_workers (int): ワーカー (つまりスレッド) の数です。num_workers > 1 の場合、コミット順序は維持されないので注意してください。
- histogram (bool): 通常の git の代わりに git diff --histogram を使用します。Git Diff Algorithms を参照ください。
- skip_whitespaces (bool): diff を要求する際に "-w" オプションを追加します。
Git Diffのアルゴリズム
Gitでは、git diffに4種類のアルゴリズムが用意されています:
- マイヤーズ(デフォルト)
- ミニマム(改良型マイヤーズ)
- 忍耐(文脈的な差異を与えるようにする)。
- ヒストグラム(忍耐力の強化のようなもの)
Nugroho, et al (2019)の研究においてMyersとHistogramの比較から、git diffコマンドの様々なdiffアルゴリズムが不均等なdiff出力を生成した。パッチ解析の結果から、変更操作の回復が期待できるコードの変更点を示すには、Histogramの方がMyersよりも優れていることがわかったそうです。そこで、本ツールでは、ソースコードの差異を考慮したヒストグラムdiffアルゴリズムを実装する。
Commit
Commit オブジェクトは、Git のコミットに関するすべての情報と、それ以上の情報を持っています。より具体的には
- hash (str): コミットのハッシュ。
- msg (str): コミットメッセージ
- author (Developer): commit author (名前、メールアドレス)
- コミッター(開発者):コミッター(名前、メール)をコミットする
- author_date (datetime):執筆された日付
- author_timezone (int):作者のタイムゾーン(エポックからの秒数で表現)。
- committer_date (datetime): コミットの日付
- committer_timezone (int): コミットのタイムゾーン (エポックからの秒数で表現)
- branches (List[str]): このコミットを含むブランチのリスト
- in_main_branch (Bool): コミットがメインブランチにある場合は真
- merge (Bool): コミットがマージコミットである場合は真
- modified_files (List[ModifiedFile]): コミットで変更されたファイルのリスト (ModifiedFile を参照)
- parents (List[str]): コミットペアレンツのリスト
- project_name (str): プロジェクト名
- project_path (str): プロジェクトパス
- deletions (int): コミットで削除された行の数 (-shortstat から得られる)。
- insertions (int): コミットで追加された行数(-shortstatから表示される)。
- lines (int): コミットに追加された行と削除された行の合計数(-shortstatから表示)。
- files (int): コミットで変更されたファイルの数(-shortstatから表示されます)。
- dmm_unit_size (float): ユニットサイズプロパティのDMMメトリック値。
- dmm_unit_complexity (float): ユニットの複雑さに関するDMMのメトリック値です。
- dmm_unit_interfacing (float): ユニットインターフェーシングのDMMメトリックス値。
例:
for commit in Repository('path/to/the/repo').traverse_commits():
print(
'The commit {} has been modified by {}, '
'committed by {} in date {}'.format(
commit.hash,
commit.author.name,
commit.committer.name,
commit.committer_date
)
)
ModifiedFile
各コミットから、変更されたファイルのリストとその差分、現在のソースコードを取得することができます。すべてのModificationは、ModifiedFileオブジェクトを反復処理することで取得することができます。各修正オブジェクトは、修正されたファイルを参照し、以下のフィールドを持っています:
- old_path: ファイルの旧パス (ファイルが追加された場合は None を指定可能)
- new_path: ファイルの新しいパス (ファイルが削除された場合は None を指定できる)
- filename: ファイル名のみを返す(例えば、"/Users/dspadini/pydriller/myfile.py "のようなパスに似た文字列を指定すると "myfile.py "を返す)。
- change_type:変更の種類。Add、Deleted、Modified、Renamedのいずれかを指定します。
- diff: Gitが提示するファイルの差分(例えば、@@ xx,xx @@で始まるもの)。
- diff_parsed: 追加された行と削除された行を含む辞書で解析された差分。この辞書には2つのキーがあります: 「added」と「deleted」の2つのキーがあり、それぞれ(ファイルの行数、実際の行数)に対応するタプル(int, str)のリストが含まれています。
- added_lines: 追加された行数
- deleted_lines: 削除された行数
- source_code: ファイルのソースコード (ファイルが削除されたり、リネームされただけの場合は None を指定できます)
- source_code_before: 変更前のファイルのソースコード (ファイルの追加や名前の変更のみの場合は None を指定可能)
- methods: そのファイルのメソッドのリスト。プログラミング言語がサポートされていない場合、またはファイルがソースコードファイルでない場合、リストは空になることがあります。これらは、変更後のメソッドです。
- methods_before: 変更前のファイルのメソッドのリスト (例: コミット前。)
- changed_methods: _methods_のサブセットで、変更されたメソッドのみを含む。
- nloc: ファイルのコード行数(LOC)
- complexity: ファイルのサイクロマティック複雑度
- token_count: ファイルのトークンの数
注意:コミットがマージコミットである場合、変更点のリストが空になることがあります。これについては、この投稿を参照してください。
例:
for commit in Repository('path/to/the/repo').traverse_commits():
for m in commit.modified_files:
print(
"Author {}".format(commit.author.name),
" modified {}".format(m.filename),
" with a change type of {}".format(m.change_type.name),
" and the complexity is {}".format(m.complexity)
)
Git
Gitは、チェックアウトやリセットといったGitの最も一般的なユーティリティをラップしたものです。例えば、特定のコミットやブランチをチェックアウトする場合などです:
gr = Git('test-repos/git-1/')
gr.checkout('a7053a4dcd627f5f4f213dc9aa002eb1caf926f8')
しかし、注意してください!Git checkout はハードディスク上のリポジトリの状態を変更するため、他のプロセス(スレッドや複数のリポジトリマイニングなど)が同じリポジトリから読み込んでいる場合は、このコマンドを使用すべきではありません。
さらに、Gitを使うことで、リポジトリからさまざまな情報を得ることができます:
gr = Git('test-repos/test1')
gr.get_list_commits() # 全てのコミットのリストを取得する。
gr.get_commit('cc5b002') # 特定のコミットを取得する。
gr.files() # 現在のコミットでレポに存在するファイルのリストを取得する。
gr.total_commits() # コミットの総数を取得する。
gr.get_commit_from_tag('v1.15') # タグ v1.15 のコミットを取得する
もう一つ、非常に便利なAPI(特に研究者向け ;)として、コミットを指定すると、ファイルの変更行に最後に「触れた」コミットをすべて取得できるものがあります(バグ修正のコミットを渡すと、バグを誘発したコミットを取得します)。
PS: PyDriller 1.9 以降、この機能をカスタマイズして "git hyper-blame" を使用することができます(詳しくはこちらをご覧ください)。git hyper blame は特定のコミット(コードをリファクタリングするコミットなど)をスキップするように指示することができます。
例を見てみましょう:
# commit abc ファイルAの1行目を変更した。
# commit def ファイルAの2行目を修正しました。
# commit ghi ファイルAの3行目を変更した。
# commit lmn ファイルAの1行目と2行目を削除しました。
gr = Git('test-repos/test5')
commit = gr.get_commit('lmn')
buggy_commits = gr.get_commits_last_modified_lines(commit)
print(buggy_commits) # 結果:(abc, def)
コミット lmn では 2 行(1 行目と 2 行目)が削除されたので、PyDriller はこれらの行が最後に変更されたコミット(この例では、コミット abc と def)を取得することができます。
利用可能な関数の完全なリストについては、このクラスの API リファレンスをチェックしてください。
Delta Maintainability
背景
コミットの保守性の意味を評価するために、PyDriller は Open Source Delta Maintainability Model (OS-DMM) の実装を提供しています。基礎となる Delta Maintainability Model は、もともと TechDebt 2019 で発表された論文 [DiBiase2019] で説明されました。きめ細かい分析で100以上の異なる言語をサポートする商用利用可能な実装は、Software Improvement Group (SIG)によって提供されています。
PyDrillerに含まれるオープンソースの実装は、PyDrillerがすでにサポートしている一般的なプログラミング言語で書かれたシステムの研究実験や計測に適した部分的な実装を提供しています。PyDriller の git 機能は言語に依存しませんが、PyDriller がサポートするメトリクス(メソッドサイズやサイクロマティック複雑度など)は言語固有の実装が必要で、そのために PyDriller は Lizard に依存しています。
OS-DMM の実装では、PyDriller のメトリクスを拡張し、サイズ、複雑さ、インターフェースのリスクに関連する 3 つのコミットレベルのメトリクスを追加しています。
定義
一言で言えば、デルタ保守性の指標は、コミットに含まれる低リスクの変更の割合です。結果の値は、0.0(すべての変更が危険)から1.0(すべての変更が低リスク)までの範囲です。この指標は、方法を良くすることに報酬を与え、物事を悪くすることにペナルティを与えます。
DMMの出発点は、リスクプロファイルである[Heitlager2007]。伝統的に、リスクプロファイルは、手法(またはより一般的にはユニットとも呼ばれる)を低リスク、中リスク、高リスク、超高リスクの4つのビンに分類している。そして、クラスのリスクプロファイルは、4つのカテゴリのそれぞれに含まれるコードの量(行数)を表す4タプル(l、m、h、v)である。
DMMの文脈では、簡略化のため、低リスクと低リスク以外(中、高、超高)の2つのビンのみを使用する。リスクプロファイルをファイル(またはシステム)レベルからコミットレベルに移すために、デルタリスクプロファイルを検討します。これは(dl, dh)のペアであり、dlは低リスクコードの増加(または減少)、dhは高リスクコードの増加(または減少)である。
そして、デルタリスクプロファイルは、良い変更と悪い変更を判断するために使用することができます: