OPC # 0006: OPC Git Trunk-Based management

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
amadzarak
2026-04-26 13:45:05 -04:00
parent 571f0bf2a4
commit 5e969a2b3e
+38 -16
View File
@@ -52,6 +52,38 @@ public class PromotionService(IConfiguration config, ILogger<PromotionService> l
private static Signature MakeSig() => private static Signature MakeSig() =>
new("OPC Control Plane", "opc@clarity.internal", DateTimeOffset.UtcNow); new("OPC Control Plane", "opc@clarity.internal", DateTimeOffset.UtcNow);
// ── Remote URL (config-driven, never reads .git/config URL) ──────────────
/// <summary>
/// Builds the HTTPS remote URL for a named repo entirely from Gitea config.
/// The local clone's .git/config remote URL is irrelevant — this is the authority.
/// </summary>
private string GetRemoteUrl(string repoName)
{
var baseUrl = (config["Gitea:BaseUrl"]
?? throw new InvalidOperationException("Gitea:BaseUrl is not configured.")).TrimEnd('/');
var owner = config[$"Gitea:Repos:{repoName}:Owner"] ?? config["Gitea:Owner"]
?? throw new InvalidOperationException($"Gitea owner not configured for '{repoName}'.");
var repoSlug = config[$"Gitea:Repos:{repoName}:Repo"] ?? repoName;
return $"{baseUrl}/{owner}/{repoSlug}.git";
}
/// <summary>
/// Returns the 'origin' remote after normalising its URL to the config-driven HTTPS URL.
/// If the clone was checked out with SSH (e.g. on a dev machine), this corrects it silently
/// so that LibGit2Sharp — which has no SSH support — always uses HTTPS.
/// </summary>
private Remote EnsureRemote(Repository repo, string repoName)
{
var url = GetRemoteUrl(repoName);
var remote = repo.Network.Remotes["origin"];
if (remote is null)
return repo.Network.Remotes.Add("origin", url);
if (remote.Url != url)
repo.Network.Remotes.Update("origin", r => r.Url = url);
return repo.Network.Remotes["origin"]!;
}
// ── Branch status ──────────────────────────────────────────────────────── // ── Branch status ────────────────────────────────────────────────────────
/// <summary> /// <summary>
@@ -72,13 +104,10 @@ public class PromotionService(IConfiguration config, ILogger<PromotionService> l
// Fetch to get up-to-date remote refs; swallow network errors so status still works offline. // Fetch to get up-to-date remote refs; swallow network errors so status still works offline.
try try
{ {
var remote = repo.Network.Remotes["origin"]; var remote = EnsureRemote(repo, repoName);
if (remote is not null)
{
var refSpecs = remote.FetchRefSpecs.Select(r => r.Specification).ToList(); var refSpecs = remote.FetchRefSpecs.Select(r => r.Specification).ToList();
repo.Network.Fetch(remote.Name, refSpecs, MakeFetchOptions()); repo.Network.Fetch(remote.Name, refSpecs, MakeFetchOptions());
} }
}
catch (Exception ex) catch (Exception ex)
{ {
logger.LogWarning(ex, "Fetch during ladder status failed — continuing with cached refs"); logger.LogWarning(ex, "Fetch during ladder status failed — continuing with cached refs");
@@ -217,8 +246,7 @@ public class PromotionService(IConfiguration config, ILogger<PromotionService> l
// 1. Fetch latest remote state for all branches // 1. Fetch latest remote state for all branches
Log(" Fetching origin..."); Log(" Fetching origin...");
var remote = repo.Network.Remotes["origin"] var remote = EnsureRemote(repo, repoName);
?? throw new InvalidOperationException("No 'origin' remote configured.");
var refSpecs = remote.FetchRefSpecs.Select(r => r.Specification).ToList(); var refSpecs = remote.FetchRefSpecs.Select(r => r.Specification).ToList();
repo.Network.Fetch(remote.Name, refSpecs, MakeFetchOptions()); repo.Network.Fetch(remote.Name, refSpecs, MakeFetchOptions());
@@ -332,8 +360,7 @@ public class PromotionService(IConfiguration config, ILogger<PromotionService> l
try try
{ {
var remote = repo.Network.Remotes["origin"] var remote = EnsureRemote(repo, repoName);
?? throw new InvalidOperationException("No 'origin' remote.");
// Force push — "+" prefix overrides remote reflog // Force push — "+" prefix overrides remote reflog
repo.Network.Push(remote, $"+refs/heads/{branchName}:refs/heads/{branchName}", MakePushOptions()); repo.Network.Push(remote, $"+refs/heads/{branchName}:refs/heads/{branchName}", MakePushOptions());
} }
@@ -424,8 +451,7 @@ public class PromotionService(IConfiguration config, ILogger<PromotionService> l
// 1. Fetch // 1. Fetch
Log(" Fetching origin..."); Log(" Fetching origin...");
var remote = repo.Network.Remotes["origin"] var remote = EnsureRemote(repo, repoName);
?? throw new InvalidOperationException("No 'origin' remote configured.");
var refSpecs = remote.FetchRefSpecs.Select(r => r.Specification).ToList(); var refSpecs = remote.FetchRefSpecs.Select(r => r.Specification).ToList();
repo.Network.Fetch(remote.Name, refSpecs, MakeFetchOptions()); repo.Network.Fetch(remote.Name, refSpecs, MakeFetchOptions());
@@ -553,13 +579,10 @@ public class PromotionService(IConfiguration config, ILogger<PromotionService> l
// Fetch latest remote refs — swallow network errors so status still works offline. // Fetch latest remote refs — swallow network errors so status still works offline.
try try
{ {
var remote = repo.Network.Remotes["origin"]; var remote = EnsureRemote(repo, repoName);
if (remote is not null)
{
var refSpecs = remote.FetchRefSpecs.Select(r => r.Specification).ToList(); var refSpecs = remote.FetchRefSpecs.Select(r => r.Specification).ToList();
repo.Network.Fetch(remote.Name, refSpecs, MakeFetchOptions()); repo.Network.Fetch(remote.Name, refSpecs, MakeFetchOptions());
} }
}
catch (Exception ex) catch (Exception ex)
{ {
logger.LogWarning(ex, "Fetch during conformance check failed — continuing with cached refs"); logger.LogWarning(ex, "Fetch during conformance check failed — continuing with cached refs");
@@ -672,8 +695,7 @@ public class PromotionService(IConfiguration config, ILogger<PromotionService> l
repo.Refs.Add($"refs/heads/{branchName}", commit.Sha); repo.Refs.Add($"refs/heads/{branchName}", commit.Sha);
var remote = repo.Network.Remotes["origin"] var remote = EnsureRemote(repo, repoName);
?? throw new InvalidOperationException("No 'origin' remote configured.");
repo.Network.Push(remote, $"refs/heads/{branchName}:refs/heads/{branchName}", MakePushOptions()); repo.Network.Push(remote, $"refs/heads/{branchName}:refs/heads/{branchName}", MakePushOptions());