62 lines
2.0 KiB
C#
62 lines
2.0 KiB
C#
using System.Diagnostics;
|
|
using System.Text;
|
|
using ClaudeDo.Data.Git;
|
|
|
|
namespace ClaudeDo.Worker.Skills;
|
|
|
|
public sealed class GitRepoCloner : IRepoCloner
|
|
{
|
|
private readonly GitService _git;
|
|
|
|
public GitRepoCloner(GitService git) => _git = git;
|
|
|
|
public async Task<ClonedRepo> CloneAsync(string url, string destDir, CancellationToken ct)
|
|
{
|
|
var parentDir = Path.GetDirectoryName(destDir);
|
|
if (!string.IsNullOrEmpty(parentDir))
|
|
Directory.CreateDirectory(parentDir);
|
|
|
|
var (exitCode, stderr) = await RunGitAsync(["clone", "--depth", "1", url, destDir], ct);
|
|
if (exitCode != 0)
|
|
throw new InvalidOperationException($"git clone '{url}' failed (exit {exitCode}): {stderr}");
|
|
|
|
var pinnedRef = await _git.RevParseHeadAsync(destDir, ct);
|
|
return new ClonedRepo(destDir, pinnedRef);
|
|
}
|
|
|
|
private static async Task<(int ExitCode, string Stderr)> RunGitAsync(IEnumerable<string> args, CancellationToken ct)
|
|
{
|
|
var psi = new ProcessStartInfo
|
|
{
|
|
FileName = "git",
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
UseShellExecute = false,
|
|
CreateNoWindow = true,
|
|
StandardOutputEncoding = Encoding.UTF8,
|
|
StandardErrorEncoding = Encoding.UTF8,
|
|
};
|
|
foreach (var a in args) psi.ArgumentList.Add(a);
|
|
|
|
using var proc = new Process { StartInfo = psi };
|
|
proc.Start();
|
|
|
|
await using var ctr = ct.Register(() =>
|
|
{
|
|
try { proc.Kill(entireProcessTree: true); }
|
|
catch { /* already exited */ }
|
|
});
|
|
|
|
var stdoutTask = proc.StandardOutput.ReadToEndAsync();
|
|
var stderrTask = proc.StandardError.ReadToEndAsync();
|
|
|
|
await proc.WaitForExitAsync(CancellationToken.None);
|
|
await stdoutTask;
|
|
var stderr = await stderrTask;
|
|
|
|
ct.ThrowIfCancellationRequested();
|
|
|
|
return (proc.ExitCode, stderr.TrimEnd());
|
|
}
|
|
}
|