Gitへの要求はどこまでも続きそうです。
すみません、Gitのmasterリポジトリをいじる記事ではありません。
Gitの外側から制御して、新規のフック処理を実装するものです。
Gitフックはリモートリポジトリ方向へは手厚いですが、ローカルワークツリー方向へはニーズが無いのか、さっぱりです。
今回は、クライアントサイドフックのcheckout処理前を実装しようと思います。贅沢を言えばきりがないので、checkoutコマンドの一部だけ対応します。
git checkout -- <ファイル> で、過去のコミット分にファイル単位で戻す。
環境
Windows10 64bit home 21H1
VisualStudio 2019 Community 16.11.4 C#
.NET 4.8
Git for Windows 2.33.0.2-64-bit
TortoiseGit 2.12.0.0-64bit
どんな感じでフックを作るのか。
①binフォルダをbin2フォルダにコピー
②binフォルダに今回作るGit.exeを配置(上書きで)
③リポジトリの.git/hooksフォルダにフックスクリプトを配置
Git.exeを使う人は、bin2フォルダは知らないから、binフォルダのGit.exeを呼び出すことになる。そこでコマンド解析し、pre-checkoutフックを呼び出すこととする。
実際の仕事は、bin2フォルダのオリジナルのGit.exeにお願いする。
おまけで、ログファイルも作ってみる。
ラッパー的な実装ということになります。
今回作るもの
.NETのコンソールアプリケーションを作ります。
実は、TortoiseGitを推してまして、TortoiseGitの『このリビジョンに戻す(E)』を選択されたときのフックが欲しくて、こんなことになっています。
Git.exeの処理結果は標準出力で受け取ることができます。なので、子プロセスとしてオリジナルのGit.exeを呼び出し、その標準出力をそのまま今回作るGit.exeの標準出力とする。そうすると、Git.exeを呼び出した人は、変なツールを実行しているとも気が付かずに、Gitを正しく操作できてしまいます。
Git.exe(ログファイル出力対応版)を作る
できました。
コンソールアプリケーション(.NET Framework)で.NET4.8のプロジェクトを作りました。プロジェクト名は『git』で。
Program.cs
using System;
using System.IO;
using System.Text;
namespace git
{
class Program
{
//リポジトリフォルダ
const string REPOSROOT = @"C:\WORK\test\";
//Gitコマンドログファイル
const string LOGFILEPATH = REPOSROOT + @".git\hooks\git.log";
//オリジナルGitコマンドEXE
const string GITCMD = @"C:\Program Files\Git\bin2\git.exe";
static void Main(string[] args)
{
string parm = "";
System.Diagnostics.Process p = null;
string results = "";
//コマンドラインパラメータ連結
parm = string.Join(" ", args);
//ログファイル出力
File.AppendAllText(LOGFILEPATH, $"{DateTime.Now} : git {parm}\n");
//オリジナルGitコマンド実行
p = new System.Diagnostics.Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardInput = false;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.WorkingDirectory = REPOSROOT;
p.StartInfo.FileName = GITCMD;
p.StartInfo.Arguments = parm;
//UTF8を使っている。
p.StartInfo.StandardOutputEncoding = Encoding.UTF8;
p.Start();
results = p.StandardOutput.ReadToEnd();
p.WaitForExit();
p.Close();
//オリジナルGitコマンドの標準出力を、そのまま自分の標準出力とする。
//バイナリを使わないと一部文字化けが発生する。
//『あいうえお.bat』等
byte[] wkBuff = System.Text.Encoding.UTF8.GetBytes(results);
Stream stdout = Console.OpenStandardOutput();
stdout.Write(wkBuff, 0, wkBuff.Length);
stdout.Close();
}
}
}
リポジトリのパス等を固定で持っていますが、configファイルにでも設定するように修正すれば汎用性は上がります。
まずは、このコマンドログファイル出力のみのバージョンで、お目当てのコマンドが取れているか確認が大事です。
動かしてみる
TortoiseGitで、過去のコミット分のファイルの右クリックで、『このリビジョンに戻す』を選択します。
きちんと、『aaa.bat』のcheckoutが取れていますね。
Git.exe(pre-checkoutフック対応版)を作る
Program.cs
using System;
using System.IO;
using System.Text;
namespace git
{
class Program
{
//リポジトリフォルダ
const string REPOSROOT = @"C:\WORK\test\";
//Gitコマンドログファイル
const string LOGFILEPATH = REPOSROOT + @".git\hooks\git.log";
//オリジナルGitコマンドEXE
const string GITCMD = @"C:\Program Files\Git\bin2\git.exe";
//Bashコマンド
const string BASHCMD = @"C:\Program Files\Git\bin\bash.exe";
//フックスクリプト
const string HOOKSH = @".git\hooks\pre-checkout";
static void Main(string[] args)
{
string parm = "";
System.Diagnostics.Process p = null;
string results = "";
//コマンドラインパラメータ連結
parm = string.Join(" ", args);
//ログファイル出力
File.AppendAllText(LOGFILEPATH, $"{DateTime.Now} : git {parm}\n");
//pre-checkoutフック
if(args[0] == "checkout" && args[2] == "--")
{
//ログファイル出力
File.AppendAllText(LOGFILEPATH, $"{DateTime.Now} : pre-checkout\n");
//スクリプト実行コマンド実行
p = new System.Diagnostics.Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.WorkingDirectory = REPOSROOT;
p.StartInfo.FileName = BASHCMD;
p.StartInfo.Arguments = HOOKSH + " " + parm;
p.Start();
p.WaitForExit();
p.Close();
}
//オリジナルGitコマンド実行
p = new System.Diagnostics.Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardInput = false;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.WorkingDirectory = REPOSROOT;
p.StartInfo.FileName = GITCMD;
p.StartInfo.Arguments = parm;
//UTF8を使っている。
p.StartInfo.StandardOutputEncoding = Encoding.UTF8;
p.Start();
results = p.StandardOutput.ReadToEnd();
p.WaitForExit();
p.Close();
//オリジナルGitコマンドの標準出力を、そのまま自分の標準出力とする。
//バイナリを使わないと一部文字化けが発生する。
//『あいうえお.bat』等
byte[] wkBuff = System.Text.Encoding.UTF8.GetBytes(results);
Stream stdout = Console.OpenStandardOutput();
stdout.Write(wkBuff, 0, wkBuff.Length);
stdout.Close();
}
}
}
ログファイル出力の下に少しだけ追加しました。
Bashシェルスクリプトを実行しますが、Bash.exeにお願いしているだけです。
pre-checkoutフックスクリプトを作る
hooksフォルダに配置しました。
pre-checkout
#!/bin/sh
cd `dirname $0`
pwd
echo `date` "pre-checkout start" >> pre-checkout.txt
echo `date` '$1 =' "$1" >> pre-checkout.txt
echo `date` '$2 =' "$2" >> pre-checkout.txt
echo `date` '$3 =' "$3" >> pre-checkout.txt
echo `date` '$4 =' "$4" >> pre-checkout.txt
echo `date` "pre-checkout end" >> pre-checkout.txt
echoだけのダミーです。
動かしてみる
もう一度、同じことをします。
ログファイルに『pre-checkout』が出力されています。
フックスクリプトのデバッグ用テキストにコマンドラインパラメータが渡っていったのが出力されています。
ここで入手した、ハッシュとファイル名を元ネタとして、何かできそうな気がしました?
作ってみて
VisualStudioのようにGit直結でというようなツールばかりではないので、手動での操作が介在するため、ミスはさけられないのです。そこで、素直にGitフックでチェックしたり、なんとかしたりすればOKでは?発想となるのです。
セットものとして管理したくなったものは、いっしょにコミットしたり、チェックアウトしたくなるのです。『元に戻すのなら、2ファイル同時にチェックアウトして、同期をとって!!』的なルールを決めていても、きびしい感じです。
少しいいかげんな作りですが、メンテできる人がいれば、まあまあ使えそうな感じもします。記事に汎用性を持たせるために、pre-checkoutスクリプトはBashシェルスクリプトにしてますが、Git for Windowsなので、PowerShell直接呼出しのほうが、メンテしやすいかもしれません。
本当は、TortoiseGitの機能のフックスクリプトにpre_checkout_hookがあればうれしいです。TortoiseGitのpre_commit_hookフックスクリプトは画面でチェックを付けた管理外のファイル名も参照できるので、楽々です。
フック機能はうまく使えば楽になりますが、やりすぎは厳禁かもしれません。今回のはセーフかな?
↧