Gitリポジトリの簡単なビュワーとクローンツールを作った時のTipsをまとめました。
ローカルサーバーにある中央リポジトリの閲覧・検索をするために作成したものです。
最初はC++とLibGit2の組み合わせで始めたのですが、最終形はWindows/GUIにしたかったので途中でC#とLibGit2Sharpに乗り換えました。
C#初心者です。LINQやらIEnumerableやらTaskやらあって刺激的。C#おもしろい。
この記事では VisualStudio 2013 を使っています。LibGit2Sharp のバージョンは 0.22.0です。
#LibGit2Sharpって何?
LibGit2とはその字面通りGitリポジトリを操作するライブラリ(API)です。C言語用です。*"2"*というからには不出来な兄または姉が居たと想像しています。
後ろに"Sharp"がついたものがC#言語用です。この他、Python,Ruby,Goなどいろいろな言語用のバインディングがあります。
LibGit2のページ
#LibGit2Sharpの取得
VisualStudioのパッケージマネージャーコンソールで次のように打ち込んで NuGet から取得します。
PM>Install-Package LibGit2Sharp -Version 0.22.0
・・・こんな仕組みがあったのね。
#リポジトリを開く
リポジトリを開いて中身を触れるようにします。
LibGit2Sharp.Repository repo = new LibGit2Sharp.Repository(string path);
pathはGitリポジトリのあるパス名です。成功するとrepoにリポジトリオブジェクトが入ります。
指定したpathにリポジトリがない場合はLibGit2Sharp.RepositoryNotFoundException
がthrowされます。
サブディレクトリは探しに行きません。
リポジトリオブジェクトからパス名を取得するのはこれ。
string path = repo.Info.Path;
#ブランチを調べる
リポジトリに含まれるブランチを調べるには次のようにします。
// repoはオープンしたリポジトリオブジェクト
foreach( LibGit2Sharp.Branch branch in repo.Branches )
{
string branch_name = branch.FriendlyName; // ブランチの簡略名
}
正式な名前はCanonicalName
を使います。
#コミットを調べる
ブランチに含まれるコミットを調べるには次のようにします。
// branchはBranchオブジェクト
foreach( LibGit2Sharp.Commit commit in branch.Commits )
{
string auther_name = commit.Author.Name; // 作者
string auther_email = commit.Author.Email; // E-mail
DateTime commit_time = commit.Author.When.DateTime; // コミット日時
string message = commit.Message; // コミットメッセージ
string message_short = commit.MessageShort; // コミットメッセージ省略形
}
英語の質問サイトでこんなやり取りがありました。
質問者:コミットの時刻はどうやって調べるんだ?
回答者:そりゃ、commitの中のAuthorにあるよ。
質問者:本当だ。誰だこんなところに入れた奴は!
質問者に一票。
#コミットの差分を調べる
比較する2つのコミットのTreeをLibGit2Sharp.Repository.DIFF.Copmpare<>()
関数に渡します。
コミットとその親コミットの差分を取る場合は次のようになります。
// commitはCommitオブジェクト
// コミットのツリー
LibGit2Sharp.Tree current_tree = commit.Tree;
// 親コミットのうちの一つ
LibGit2Sharp.Commit parent_commit = commit.Parents.ElementAtOrDefault(0);
// 親コッミトのツリー
LibGit2Sharp.Tree parent_tree = parent_commit == null ? null : parent_commit.Tree;
// 両者を比較して、ファイル差分のコレクションを得る。
// 変数repoはオープンしたリポジトリオブジェクトです。
LibGit2Sharp.TreeChanges changes = repo.Diff.Compare<LibGit2Sharp.TreeChanges>( parent_tree, current_tree );
// 各差分のパスと操作内容を取り出して文字列にし、全差分の文字列を連結する。
// TreeChangesコレクションをSelectで文字列に加工してToArryで配列に変換し、配列をstring.Joinで連結。LINQ使ってみました。
string diff_files = string.Join( "\n", changes.Select( x => x.Path + " " + x.Status ).ToArray() );
ここではLibGit2Sharp.TreeChanges
を返すCompare()を使いました。差のあったパス名と、ファイルに対して行った操作(変更・追加・削除のいづれか)の情報が得られます。
さらに詳細な情報が必要な場合はLibGit2Sharp.Patch
を返すCompare()を使います。
LibGit2Sharp.Patch changes_detail = repo.Diff.Compare<LibGit2Sharp.Patch>( parent_tree, current_tree );
foreach( var diffs in changes_detail )
{
string path = diffs.Path; // 変更されたファイルパス
int add_line = diffs.LinesAdded; // 追加された行数
int del_line = diffs.LinesDeleted; // 削除された行数
string diffs = diffs.Patch; // 追加行、削除行などの差分情報
}
変更されたパス名と削除・追加された行数、追加した行、削除した行、その他差分情報が得られます。
ただ、詳細取得には明らかに処理に時間がかかります。場合によっては秒オーダー。
#Cloneする
中央(リモート)リポジトリからクローンします。
最初にオプションを設定します。ここでは開始時のコールバック、進行状況通知コールバック、チェックアウトするブランチ名を指定しています。(外部サーバーからクローンする場合は、ここにユーザー情報も設定するようです。)
最後にLibGit2Sharp.Repository.Clone()
でクローンを実行します。
// オプション変数
LibGit2Sharp.CloneOptions options = new LibGit2Sharp.CloneOptions();
// オプション:チェックアウトするブランチ名。簡略名(FriendlyName)でもOK
options.BranchName = branch;
// オプション:進行状況のコールバック指定
options.OnCheckoutProgress = (p,n,t)=>checkout_progress_handler(p,n,t);
// オプション:開始時のコールバックを指定
options.RepositoryOperationStarting = (c)=>operation_start_handler(c);
// 入出力のパスとオプションを指定してクローンを実行します。
// string remote_path:クローンするリモートリポジトリのパス
// string local_path:クローンを作成するパス
// CloneOptions options
LibGit2Sharp.Repository.Clone( remote_path, local_path, options );
オプションの OnCheckoutProgress に指定する進行状況コールバックの型は、
delegate void CheckoutProgressHandler(string path, int completedSteps, int totalSteps);
引数のpathは処理したファイルのパス、completedStepsは処理済みのファイル数、totalStepsはファイル総数です。
オプションの RepositoryOperationStarting に指定する処理開始コールバックの型は、
delegate bool RepositoryOperationStarting(RepositoryOperationContext context);
引数のcontextはクローンするリポジトリに関する情報が含まれています。
戻り値trueを返せばクローンが開始されます。falseを返すとClone()は処理中断しLibGit2Sharp.UserCancelledException
をthrowします。
他にも、Clone完了時に呼ばれる RepositoryOperationCompleted
があります。
OnProgress
,OnTransferProgress
といったコールバックも用意されているようですが、Cloneでは動作しませんでした。
#最後に
今回作成したツールで使った機能はこれで全部です。しかしこれはLibGit2のほんの一部です。
LibGit2関連の日本語ドキュメントが少ないので参考になればと思っています。いつもお世話になっているので。
ところで、LibGit2Sharpを使って作ったアプリケーションは、実行時にEXEファイルの他にLibGit2Sharp.dll、ネイティブDLL、合計3個のファイルが必要です。
ファイル3個を配布するのは面倒なので、LibGit2Sharp.dllはILMergeツールを使ってEXEファイルにまとめました(下記リンク)。しかしネイティブDLLをまとめるには簡単な方法が見つからず(あんまりややこしいことはやりたくなかった)結局ファイル2個、ということになりました。
実行ファイルとDLLを一つにまとめる