OPC # 0006: OPC Git Trunk-Based management
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -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());
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user