Forge
csharp3407750f
1using AgentForge.Abstractions;
2using AgentForge.Data;
3using AgentForge.Data.Entities;
4using AgentForge.Models;
5using AgentForge.Services;
6
7namespace AgentForge.Plugin.View;
8
9public static class ForgeGitViewHelpers
10{
11 public static string ResolveBranch(GitRepoService git, string slug, string? branch)
12 {
13 if (!string.IsNullOrWhiteSpace(branch) && git.BranchExists(slug, branch.Trim()))
14 return branch.Trim();
15 return git.GetDefaultBranch(slug)
16 ?? throw new ForgeApiException(404, "Repository has no branches.");
17 }
18
19 public static string? ParentPath(string? path)
20 {
21 var normalized = (path ?? "").Trim().Replace('\\', '/').Trim('/');
22 if (normalized.Length == 0)
23 return null;
24 var index = normalized.LastIndexOf('/');
25 return index < 0 ? "" : normalized[..index];
26 }
27
28 public static string CodeViewHref(string name, string branch, string? path, bool isBlob)
29 {
30 var href = new System.Text.StringBuilder("/view/repos/");
31 href.Append(ForgeRepoNames.ToUrlPath(name));
32 href.Append(isBlob ? "/blob" : "/tree");
33 href.Append("?branch=").Append(Uri.EscapeDataString(branch));
34 if (!string.IsNullOrWhiteSpace(path))
35 href.Append("&path=").Append(Uri.EscapeDataString(path.Trim()));
36 return href.ToString();
37 }
38
39 public const string DocsRootPrefix = "docs/";
40
41 public static string? NormalizeDocsPath(string? path)
42 {
43 var normalized = (path ?? "").Trim().Replace('\\', '/').Trim('/');
44 return normalized.Length == 0 ? null : normalized;
45 }
46
47 public static string ToDocsRelativePath(string? repoPath)
48 {
49 var normalized = (repoPath ?? "").Trim().Replace('\\', '/').Trim('/');
50 if (normalized.StartsWith(DocsRootPrefix, StringComparison.OrdinalIgnoreCase))
51 normalized = normalized[DocsRootPrefix.Length..];
52 return normalized;
53 }
54
55 public static string ToDocsRepoPath(string? docsRelativePath)
56 {
57 var normalized = (docsRelativePath ?? "").Trim().Replace('\\', '/').Trim('/');
58 if (normalized.Length == 0)
59 return "docs";
60 if (normalized.StartsWith(DocsRootPrefix, StringComparison.OrdinalIgnoreCase))
61 return normalized;
62 return $"{DocsRootPrefix}{normalized}";
63 }
64
65 public static string NormalizeRepoTab(string? tab, IForgeViewContributorCatalog? catalog)
66 {
67 if (string.IsNullOrWhiteSpace(tab))
68 return "code";
69
70 var normalized = tab.Trim().ToLowerInvariant() switch
71 {
72 "issue" => "issues",
73 "merge-requests" or "merge_requests" or "mrs" or "mr" => "merge-requests",
74 _ => tab.Trim().ToLowerInvariant(),
75 };
76
77 return catalog is not null && catalog.HasTab(normalized) ? normalized : "code";
78 }
79
80 public static ForgeMergeRequestEntity? FindOpenMrForBranch(
81 ForgeRepository repository,
82 string repoId,
83 string branch) =>
84 repository.ListMergeRequests(repoId)
85 .Where(mr => mr.Status == MergeRequestStatus.Open
86 && string.Equals(mr.SourceBranch, branch, StringComparison.OrdinalIgnoreCase))
87 .OrderByDescending(mr => mr.Number)
88 .FirstOrDefault();
89
90 public static IReadOnlyList<string> ListHandoffTargetRepoNames(ForgeRepository repository, string excludeRepo) =>
91 repository.ListRepos()
92 .Where(r => !string.Equals(r.Name, excludeRepo, StringComparison.OrdinalIgnoreCase))
93 .Select(r => r.Name)
94 .OrderBy(n => n, StringComparer.OrdinalIgnoreCase)
95 .ToList();
96}
97
View only · write via MCP/CIDE