OPC # 0001: Gitea services

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
amadzarak
2026-04-25 19:35:46 -04:00
parent 76962a6af4
commit 65a6b4afaf
13 changed files with 457 additions and 93 deletions
+43 -32
View File
@@ -17,10 +17,13 @@ public static class GitEndpoints
// GET /api/git/log?grep=OPC+%23+0001&limit=50&repo=all
// repo can be "all" (default) or a named key from Git:Repos (e.g. "Clarity", "OPC", "Gateway").
// All responses include a repoKey field so the caller knows which repo each commit came from.
// GET /api/git/log?grep=...&limit=25&page=1&repo=all
// page is 1-based. Each repo contributes up to limit*page commits before merge-sort+skip/take.
private static IResult GetLog(
IConfiguration config,
string? grep = null,
int limit = 50,
int limit = 25,
int page = 1,
string repo = "all")
{
var repos = repo == "all"
@@ -56,12 +59,13 @@ public static class GitEndpoints
if (!string.IsNullOrWhiteSpace(grep))
query = query.Where(c => c.Message.Contains(grep, StringComparison.OrdinalIgnoreCase));
foreach (var c in query.Take(limit))
foreach (var c in query.Take(limit * page))
bucket.Add((c.Author.When, repoKey, ToGitCommit(r, c)));
}
var commits = bucket
.OrderByDescending(x => x.When)
.Skip((page - 1) * limit)
.Take(limit)
.Select(x => new
{
@@ -79,41 +83,48 @@ public static class GitEndpoints
}
// GET /api/git/commits/{hash}
// Searches all registered repos — hash may belong to any of the three repos.
private static IResult GetCommit(string hash, IConfiguration config)
{
var repoPath = ResolveRepo(config);
if (repoPath is null)
return Results.Problem("Could not locate a git repository.");
var repos = ResolveAllRepos(config);
if (repos.Count == 0)
return Results.Problem("Could not locate any git repositories.");
using var repo = new Repository(repoPath);
var commit = repo.Lookup<Commit>(hash);
if (commit is null) return Results.NotFound();
var parentTree = commit.Parents.FirstOrDefault()?.Tree;
var changes = repo.Diff.Compare<TreeChanges>(parentTree, commit.Tree);
var patch = repo.Diff.Compare<Patch>(parentTree, commit.Tree);
var files = changes.Select(c => new
foreach (var (_, repoPath) in repos)
{
path = c.Path,
oldPath = c.OldPath,
status = c.Status.ToString(),
additions = patch[c.Path]?.LinesAdded ?? 0,
deletions = patch[c.Path]?.LinesDeleted ?? 0,
patch = patch[c.Path]?.Patch ?? string.Empty,
}).ToList();
if (!Directory.Exists(repoPath)) continue;
using var repo = new Repository(repoPath);
var commit = repo.Lookup<Commit>(hash);
if (commit is null) continue;
return Results.Ok(new
{
hash = commit.Sha,
shortHash = commit.Sha[..7],
author = commit.Author.Name,
email = commit.Author.Email,
date = commit.Author.When.ToString("yyyy-MM-dd HH:mm:ss zzz"),
subject = commit.MessageShort,
body = commit.Message,
files,
});
var parentTree = commit.Parents.FirstOrDefault()?.Tree;
var changes = repo.Diff.Compare<TreeChanges>(parentTree, commit.Tree);
var patch = repo.Diff.Compare<Patch>(parentTree, commit.Tree);
var files = changes.Select(c => new
{
path = c.Path,
oldPath = c.OldPath,
status = c.Status.ToString(),
additions = patch[c.Path]?.LinesAdded ?? 0,
deletions = patch[c.Path]?.LinesDeleted ?? 0,
patch = patch[c.Path]?.Patch ?? string.Empty,
}).ToList();
return Results.Ok(new
{
hash = commit.Sha,
shortHash = commit.Sha[..7],
author = commit.Author.Name,
email = commit.Author.Email,
date = commit.Author.When.ToString("yyyy-MM-dd HH:mm:ss zzz"),
subject = commit.MessageShort,
body = commit.Message,
files,
});
}
return Results.NotFound();
}
// GET /api/git/branches
+27 -19
View File
@@ -23,14 +23,22 @@ public static class GiteaEndpoints
return app;
}
private static async Task<IResult> GetRepo(GiteaService svc, CancellationToken ct)
private static async Task<IResult> GetRepo(GiteaService svc, string? repo, CancellationToken ct)
{
var repo = await svc.GetRepoAsync(ct);
return repo is null ? Results.StatusCode(503) : Results.Ok(repo);
var result = await svc.GetRepoAsync(repo, ct);
return result is null ? Results.StatusCode(503) : Results.Ok(result);
}
private static async Task<IResult> ListBranches(GiteaService svc, CancellationToken ct) =>
Results.Ok(await svc.ListBranchesAsync(ct));
private static async Task<IResult> ListBranches(GiteaService svc, string? repo, CancellationToken ct)
{
// repo=all returns branches from all registered repos, each tagged with repoKey
if (repo == "all")
{
var all = await svc.ListAllBranchesAsync(ct);
return Results.Ok(all.Select(x => new { repoKey = x.RepoKey, x.Branch.Name, x.Branch.CommitSha, x.Branch.Protected }));
}
return Results.Ok(await svc.ListBranchesAsync(repo, ct));
}
private static async Task<IResult> CreateBranch(
CreateBranchRequest req, GiteaService svc, CancellationToken ct)
@@ -40,40 +48,40 @@ public static class GiteaEndpoints
}
private static async Task<IResult> ListPulls(
GiteaService svc, string state = "open", CancellationToken ct = default) =>
Results.Ok(await svc.ListPullRequestsAsync(state, ct));
GiteaService svc, string state = "open", string? repo = null, CancellationToken ct = default) =>
Results.Ok(await svc.ListPullRequestsAsync(state, repo, ct));
private static async Task<IResult> GetPull(
long number, GiteaService svc, CancellationToken ct)
long number, GiteaService svc, string? repo = null, CancellationToken ct = default)
{
var pr = await svc.GetPullRequestAsync(number, ct);
var pr = await svc.GetPullRequestAsync(number, repo, ct);
return pr is null ? Results.NotFound() : Results.Ok(pr);
}
private static async Task<IResult> CreatePull(
CreatePullRequestRequest req, GiteaService svc, CancellationToken ct)
CreatePullRequestRequest req, GiteaService svc, string? repo = null, CancellationToken ct = default)
{
var pr = await svc.CreatePullRequestAsync(req, ct);
var pr = await svc.CreatePullRequestAsync(req, repo, ct);
return pr is null ? Results.BadRequest("Failed to create PR in Gitea.") : Results.Ok(pr);
}
private static async Task<IResult> ListTags(GiteaService svc, CancellationToken ct) =>
Results.Ok(await svc.ListTagsAsync(ct));
private static async Task<IResult> ListTags(GiteaService svc, string? repo = null, CancellationToken ct = default) =>
Results.Ok(await svc.ListTagsAsync(repo, ct));
private static async Task<IResult> CreateTag(
CreateTagRequest req, GiteaService svc, CancellationToken ct)
CreateTagRequest req, GiteaService svc, string? repo = null, CancellationToken ct = default)
{
var tag = await svc.CreateTagAsync(req, ct);
var tag = await svc.CreateTagAsync(req, repo, ct);
return tag is null ? Results.BadRequest("Failed to create tag in Gitea.") : Results.Ok(tag);
}
private static async Task<IResult> ListWebhooks(GiteaService svc, CancellationToken ct) =>
Results.Ok(await svc.ListWebhooksAsync(ct));
private static async Task<IResult> ListWebhooks(GiteaService svc, string? repo = null, CancellationToken ct = default) =>
Results.Ok(await svc.ListWebhooksAsync(repo, ct));
private static async Task<IResult> RegisterWebhook(
CreateWebhookRequest req, GiteaService svc, CancellationToken ct)
CreateWebhookRequest req, GiteaService svc, string? repo = null, CancellationToken ct = default)
{
var hook = await svc.RegisterWebhookAsync(req, ct);
var hook = await svc.RegisterWebhookAsync(req, repo, ct);
return hook is null ? Results.BadRequest("Failed to register webhook in Gitea.") : Results.Ok(hook);
}
}