Merge branch 'claudedo/340b4a2df0f14235a34fe0d02b2dc06f'
This commit is contained in:
@@ -3,7 +3,10 @@ using System.Text;
|
||||
|
||||
namespace ClaudeDo.Data.Git;
|
||||
|
||||
public sealed record MergePreview(bool Supported, bool Clean, IReadOnlyList<string> ConflictFiles);
|
||||
// TreeOid is only populated when Clean — the tree `merge-tree --write-tree` would produce,
|
||||
// usable to materialize the merge result into a scratch worktree without touching the real
|
||||
// working tree, index, or refs (see GitService.CommitTreeAsync / WorktreeAddDetachedAsync).
|
||||
public sealed record MergePreview(bool Supported, bool Clean, IReadOnlyList<string> ConflictFiles, string? TreeOid = null);
|
||||
|
||||
public sealed class GitService
|
||||
{
|
||||
@@ -373,7 +376,8 @@ public sealed class GitService
|
||||
["merge-tree", "--write-tree", "--name-only", targetBranch, sourceBranch], ct);
|
||||
|
||||
if (exitCode == 0)
|
||||
return new MergePreview(true, true, Array.Empty<string>());
|
||||
// stdout is just the written tree's oid on a clean merge.
|
||||
return new MergePreview(true, true, Array.Empty<string>(), stdout.Trim());
|
||||
|
||||
if (exitCode == 1)
|
||||
{
|
||||
@@ -393,6 +397,62 @@ public sealed class GitService
|
||||
return new MergePreview(false, false, Array.Empty<string>());
|
||||
}
|
||||
|
||||
/// <summary>Resolves <paramref name="revision"/> (a branch, tag, or SHA) to a full commit SHA.</summary>
|
||||
public async Task<string> RevParseAsync(string repoDir, string revision, CancellationToken ct = default)
|
||||
{
|
||||
var (exitCode, stdout, stderr) = await RunGitAsync(repoDir, ["rev-parse", revision], ct);
|
||||
if (exitCode != 0)
|
||||
throw new InvalidOperationException($"git rev-parse '{revision}' failed (exit {exitCode}): {stderr}");
|
||||
return stdout.Trim();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new commit object wrapping <paramref name="treeOid"/> with <paramref name="parentSha"/>
|
||||
/// as its single parent, writing only a loose object — no ref is created or moved.
|
||||
/// </summary>
|
||||
public async Task<string> CommitTreeAsync(
|
||||
string repoDir, string treeOid, string parentSha, string message, CancellationToken ct = default)
|
||||
{
|
||||
var (exitCode, stdout, stderr) = await RunGitAsync(repoDir,
|
||||
["commit-tree", treeOid, "-p", parentSha, "-m", message], ct);
|
||||
if (exitCode != 0)
|
||||
throw new InvalidOperationException($"git commit-tree failed (exit {exitCode}): {stderr}");
|
||||
return stdout.Trim();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Materializes <paramref name="commitish"/> into a new, branchless worktree at
|
||||
/// <paramref name="worktreePath"/> (detached HEAD) — used to build/verify a merge-tree
|
||||
/// result without ever creating a branch or touching the real working tree.
|
||||
/// </summary>
|
||||
public async Task WorktreeAddDetachedAsync(
|
||||
string repoDir, string worktreePath, string commitish, CancellationToken ct = default)
|
||||
{
|
||||
await WorktreeAddGate.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
const int maxAttempts = 3;
|
||||
for (var attempt = 1; ; attempt++)
|
||||
{
|
||||
var (exitCode, _, stderr) = await RunGitAsync(repoDir,
|
||||
["worktree", "add", "--detach", worktreePath, commitish], ct);
|
||||
if (exitCode == 0)
|
||||
return;
|
||||
|
||||
var transient = stderr.Contains("commondir", StringComparison.OrdinalIgnoreCase)
|
||||
|| stderr.Contains("failed to read", StringComparison.OrdinalIgnoreCase);
|
||||
if (!transient || attempt >= maxAttempts)
|
||||
throw new InvalidOperationException($"git worktree add --detach failed (exit {exitCode}): {stderr}");
|
||||
|
||||
await Task.Delay(150 * attempt, ct);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
WorktreeAddGate.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Count of files that differ on <paramref name="sourceBranch"/> since its merge base with the target.</summary>
|
||||
public async Task<int> CountChangedFilesAsync(
|
||||
string repoDir, string targetBranch, string sourceBranch, CancellationToken ct = default)
|
||||
|
||||
Reference in New Issue
Block a user