我已经编写了一个C#程序来充当Mercurial的预提交钩子,但让它可靠地工作的唯一方法有点复杂,所以我肯定遗漏了一些东西.我在64位的Windows10专业版上运行的是TortoiseHg6.4.5,而该程序的目标是.NET 6.0.
目标是获取已更改文件的列表,并在提交之前处理它们.一开始,最明显的事情似乎奏效了:
IEnumerable<string> GetHgStatus()
{
// never terminates when a large number of files have been changed, and if I
// kill the hung hg.exe process there's nothing in the stdout/stderr streams.
// run exe: hg status -n -m -a
var psi = new ProcessStartInfo
{
// check status:
// -n = don't output leading status indicator, eg. M | A | R
// -m = show only modified files
// -a = show only files that have been added
FileName = config.HgPath,
Arguments = $"status -n -m -a",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
WorkingDirectory = config.RepoPath,
};
var exe = Process.Start(psi);
try
{
exe.WaitForExit(); // never returns
while (!exe.StandardOutput.EndOfStream)
yield return exe.StandardOutput.ReadLine();
}
finally
{
exe.Dispose();
}
}
但是,如果更改的文件列表非常长,则等待永远不会结束.我在任务列表中看到一个hg.exe实例,分配了大约57-60MB,但什么也不做.如果我终止它,则不会向stdout或stderr输出任何结果.
从命令行运行此命令时,我从来没有遇到过问题,所以我try 在cmdshell 中运行它:
IEnumerable<string> GetHgStatus()
{
// never terminates when a large number of files have been changed, and if I
// kill the hung hg.exe process there's nothing in the stdout/stderr streams.
// run exe: hg status -n -m -a
var psi = new ProcessStartInfo
{
// check status:
// -n = don't output leading status indicator, eg. M | A | R
// -m = show only modified files
// -a = show only files that have been added
FileName = "cmd.exe",
Arguments = $"/c \"{config.HgPath}\" status -n -m -a",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
WorkingDirectory = config.RepoPath,
};
var exe = Process.Start(psi);
try
{
exe.WaitForExit(); // never returns
while (!exe.StandardOutput.EndOfStream)
yield return exe.StandardOutput.ReadLine();
}
finally
{
exe.Dispose();
}
}
这与第一个版本具有完全相同的行为.如果我将CreateNoWindow选项切换为False,那么我可以看到HG创建了一个命令shell 窗口,然后它就停在那里.
让它可靠工作的唯一方法是放弃重定向stdout/stderr,并将输出重定向到一个临时文件,然后读入该文件:
IEnumerable<string> GetHgStatus()
{
// have to loop catching file access exceptions until the file is accessible
// run exe: hg status -n -m -a
var output = Path.GetTempFileName();
var psi = new ProcessStartInfo
{
// check status:
// -n = don't output leading status indicator, eg. M | A | R
// -m = show only modified files
// -a = show only files that have been added
FileName = "cmd.exe",
Arguments = $"/c \"{config.HgPath}\" status -n -m -a > {output}",
UseShellExecute = false,
CreateNoWindow = true,
WorkingDirectory = config.RepoPath,
};
var exe = Process.Start(psi);
try
{
exe.WaitForExit();
// have to call Close() then poll until file is accessible
exe.Close();
do
{
try
{
return File.ReadAllLines(output);
}
catch (IOException e) when (e.Message.StartsWith("The process cannot access the file"))
{
continue;
}
} while (true);
}
finally
{
exe.Dispose();
File.Delete(output);
}
}
所以有两个问题:
- 我错过了什么,我不能直接运行hg.exe并重定向它的标准输出,我必须通过一个shell 间接地做到这一点,然后它的输出被重定向到一个文件?
- 有没有更好的方法来等待或知道进程的打开文件句柄已经关闭,这样我就可以避免这种旋转循环?