Gitで変更のかかったファイルを列挙する

アプリケーション開発をする場合、比較的大規模なものになった場合は、プロジェクトディレクトリの外に配置された多数の外部ライブラリを参照する場合が多いです。

現在進行しているプロジェクトの Git は開発時こまめに commit されます。(そりゃそうですよね)

しかし、外部参照したライブラリはコミットしているかどうか怪しいです、複数人が携わるプロジェクトだと他人が変更をかけている場合もあります。ただその他人がちゃんとコミットしてくれていればいいんのですが、変更をかけたけれどもコミットし忘れている。そもそもライブラリとして add してくれていない、というヤバい状況もあったりします。

いやいや Github 使いなさいよ、というのが正しい姿かもしれませんが、考えが古臭い会社に勤めているとそうはいきません。日本ではブラウザと電子メール以外で社外ネットワークに接続するなんて絶対に許さねぇ!という会社がとても多いのです。Github はブラウザで操作できますがコードを社外に置くっていうのがイヤなんでしょう。

かといって社内で Github 的サービスを立ち上げるのもIT管理部みたいな部署にお伺いをたてるのに大騒ぎです。くだらない資料をエクセル方眼紙で作って、スタンプラリーして、ようやくIT管理部までたどり着いたら前例がないのでダメ~、とか、よくあることですよ。

前例がないから申請用書類のフォーマットが無い→フォーマットが無いから申請できない。大企業って社員全体でバカになっています、まぁ会社ごっこですよね。

さて、前置きが長くなりました Github が使えないのなら自分でなんとかするしかありません。下記の2種類の Git のコマンドを /.git の含まれているディレクトリで実行しましょう。

# git add していなくても変更がかかっているファイルの名称を取得する.
git diff --name-only

# git add してコミット対象で変更がかかっているファイルの名称を取得する.
git diff --cached --name-only

フォームに下記のコンポーネントを配置してください。
textBox1 : ライブラリが格納されているトップディレクトリを指定する.
button1: トップディレクトリの配下にあるファイルで変更がかかっているのを調べる.
listBox1: Gitコマンドの戻り値(文字列リスト)を表示する.

下記のソースコードで Form1_Load の dir_top をあなたの環境にあわせて指定してください。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Windows.Forms;
using System.IO;

namespace aaa
{
	public partial class Form1 : Form
	{
		public Form1()
		{
			InitializeComponent();
		}

		private void Form1_Load( object sender, EventArgs e )
		{
            // ここをあなたの環境にあわせて指定してください.
			String dir_top = "c:/develop/my_library";
			textBox1.Text = dir_top;
		}

		private void button1_Click( object sender, EventArgs e )
		{

			String dir_top = textBox1.Text;
			String [] dirs = Directory.GetDirectories( dir_top );

			List<String> list = new List<String>();

			// 配下のディレクトリに .git が含まれているディレクトリをリスト化する.
			for ( int k = 0; k < dirs.Length; k++ )
			{

				// 配下のディレクトリの .git パスを生成する.
				String dir_dot_git = Path.Combine( dirs[k], ".git" );

				// 実際にそのパスがあるか確認する.
				if ( Directory.Exists( dir_dot_git ))
				{
					// 実際に存在すればリストに追加する.
					list.Add( dirs[k] );
				}

			}

			if ( list.Count == 0 )
			{
				MessageBox.Show( ".git ディレクトリが含まれるディレクトリがありませんでした." );
				return; // 脱出する, warning.
			}

			// ここから git コマンド発行で時間がかかるのでウェイトカーソルにする.
			Cursor.Current = Cursors.WaitCursor;

			// A: git add していなくても変更がかかっているファイルの名称を取得する.
			// B: git add してコミット対象で変更がかかっているファイルの名称を取得する.
			const String GIT_ARGS_A = "diff --name-only";
			const String GIT_ARGS_B = "diff --cached --name-only";

			// 出力領域をいったんクリアする.
			listBox1.Items.Clear();

			int counter = 0;

			for ( int k = 0; k < list.Count; k++ )
			{

				// git の実行ディレクトリ.
				String dir_git_working = list[k];

				// git コマンド実行.
				List<String> outA = ExecGitCommand( GIT_ARGS_A, dir_git_working );

				for ( int dk = 0; dk < outA.Count; dk++ )
				{
					listBox1.Items.Add( outA[dk] );
					counter++;
				}

				// git コマンド実行.
				List<String> outB = ExecGitCommand( GIT_ARGS_B, dir_git_working );

				for ( int dk = 0; dk < outB.Count; dk++ )
				{
					listBox1.Items.Add( outB[dk] );
					counter++;
				}

			}

			// カーソルをもとにもどす.
			Cursor.Current = Cursors.Default;

			if ( counter == 0 )
			{
				MessageBox.Show( "変更がかかったファイルはありませんでした." );
				return; // warning.
			}

		}

		private List<string> ExecGitCommand( string arguments, string working_directory )
		{

			ProcessStartInfo psi = new ProcessStartInfo();
			psi.FileName               = "git";
			psi.Arguments              = arguments;
			psi.WorkingDirectory       = working_directory;
			psi.RedirectStandardOutput = true;
			psi.RedirectStandardError  = true;
			psi.StandardOutputEncoding = Encoding.UTF8; // ここを指定しないと日本語は文字化けする.
			psi.StandardErrorEncoding  = Encoding.UTF8; // ここを指定しないと日本語は文字化けする.
			psi.UseShellExecute        = false;
			psi.CreateNoWindow         = true;

			List<String> output = new List<string>();

			try
			{

				using ( Process proc = Process.Start( psi ) )
				{

					while ( !proc.StandardOutput.EndOfStream )
					{
						output.Add( proc.StandardOutput.ReadLine() );
					}

					string err = proc.StandardError.ReadToEnd();
					proc.WaitForExit();

					if ( proc.ExitCode != 0 )
					{
						throw new Exception( err );
					}

				}

			}
			catch ( Exception excp )
			{
				output.Add( excp.Message );
			}

			return output;

		}
	}
}