forgejo refactor
This commit is contained in:
parent
e2199c2636
commit
00eacdf8fc
7 changed files with 142 additions and 57 deletions
10
README.md
10
README.md
|
@ -37,7 +37,13 @@ Create a `config.json` file with your API tokens:
|
||||||
{
|
{
|
||||||
"github_token": "your_github_token",
|
"github_token": "your_github_token",
|
||||||
"gitlab_token": "your_gitlab_token",
|
"gitlab_token": "your_gitlab_token",
|
||||||
"codeberg_token": "your_codeberg_token",
|
"forgejo": [
|
||||||
|
{
|
||||||
|
"name": "codeberg",
|
||||||
|
"base_url": "https://codeberg.org",
|
||||||
|
"token": "your_codeberg_access_token_here"
|
||||||
|
}
|
||||||
|
],
|
||||||
"sourcehut": {
|
"sourcehut": {
|
||||||
"repositories": [
|
"repositories": [
|
||||||
"~sircmpwn/aerc",
|
"~sircmpwn/aerc",
|
||||||
|
@ -52,7 +58,7 @@ Create a `config.json` file with your API tokens:
|
||||||
|
|
||||||
- **GitHub**: Create a Personal Access Token with and `user:read` scope. Classic is preferred (see note)
|
- **GitHub**: Create a Personal Access Token with and `user:read` scope. Classic is preferred (see note)
|
||||||
- **GitLab**: Create a Personal Access Token with `read_api` scope
|
- **GitLab**: Create a Personal Access Token with `read_api` scope
|
||||||
- **Codeberg**: Create an Access Token in your account settings
|
- **Codeberg**: Create an Access Token in your account settings. read:repository and read:user
|
||||||
- **SourceHut**: No token required for public repositories. Specify repositories to track in the configuration.
|
- **SourceHut**: No token required for public repositories. Specify repositories to track in the configuration.
|
||||||
|
|
||||||
Note on GitHub PATs. Some GitHub orgs will place additional restrictions on
|
Note on GitHub PATs. Some GitHub orgs will place additional restrictions on
|
||||||
|
|
20
build.zig
20
build.zig
|
@ -4,7 +4,7 @@ pub fn build(b: *std.Build) void {
|
||||||
const target = b.standardTargetOptions(.{});
|
const target = b.standardTargetOptions(.{});
|
||||||
const optimize = b.standardOptimizeOption(.{});
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
const integration = b.option(bool, "integration", "Run integration tests") orelse false;
|
const integration = b.option(bool, "integration", "Run integration tests") orelse false;
|
||||||
const provider = b.option([]const u8, "provider", "Test specific provider (github, gitlab, codeberg, sourcehut)");
|
const provider = b.option([]const u8, "provider", "Test specific provider (github, gitlab, forgejo, sourcehut)");
|
||||||
const test_debug = b.option(bool, "test-debug", "Enable debug output in tests") orelse false;
|
const test_debug = b.option(bool, "test-debug", "Enable debug output in tests") orelse false;
|
||||||
|
|
||||||
// Add Zeit dependency
|
// Add Zeit dependency
|
||||||
|
@ -79,7 +79,7 @@ pub fn build(b: *std.Build) void {
|
||||||
// Individual provider test steps
|
// Individual provider test steps
|
||||||
const github_step = b.step("test-github", "Test GitHub provider only");
|
const github_step = b.step("test-github", "Test GitHub provider only");
|
||||||
const gitlab_step = b.step("test-gitlab", "Test GitLab provider only");
|
const gitlab_step = b.step("test-gitlab", "Test GitLab provider only");
|
||||||
const codeberg_step = b.step("test-codeberg", "Test Codeberg provider only");
|
const forgejo_step = b.step("test-forgejo", "Test Forgejo provider only");
|
||||||
const sourcehut_step = b.step("test-sourcehut", "Test SourceHut provider only");
|
const sourcehut_step = b.step("test-sourcehut", "Test SourceHut provider only");
|
||||||
|
|
||||||
const github_tests = b.addTest(.{
|
const github_tests = b.addTest(.{
|
||||||
|
@ -106,17 +106,17 @@ pub fn build(b: *std.Build) void {
|
||||||
gitlab_test_debug_option.addOption(bool, "test_debug", test_debug);
|
gitlab_test_debug_option.addOption(bool, "test_debug", test_debug);
|
||||||
gitlab_tests.root_module.addOptions("build_options", gitlab_test_debug_option);
|
gitlab_tests.root_module.addOptions("build_options", gitlab_test_debug_option);
|
||||||
|
|
||||||
const codeberg_tests = b.addTest(.{
|
const forgejo_tests = b.addTest(.{
|
||||||
.name = "codeberg-tests",
|
.name = "forgejo-tests",
|
||||||
.root_source_file = b.path("src/integration_tests.zig"),
|
.root_source_file = b.path("src/integration_tests.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
.filters = &[_][]const u8{"Codeberg provider"},
|
.filters = &[_][]const u8{"Forgejo provider"},
|
||||||
});
|
});
|
||||||
codeberg_tests.root_module.addImport("zeit", zeit_dep.module("zeit"));
|
forgejo_tests.root_module.addImport("zeit", zeit_dep.module("zeit"));
|
||||||
const codeberg_test_debug_option = b.addOptions();
|
const forgejo_test_debug_option = b.addOptions();
|
||||||
codeberg_test_debug_option.addOption(bool, "test_debug", test_debug);
|
forgejo_test_debug_option.addOption(bool, "test_debug", test_debug);
|
||||||
codeberg_tests.root_module.addOptions("build_options", codeberg_test_debug_option);
|
forgejo_tests.root_module.addOptions("build_options", forgejo_test_debug_option);
|
||||||
|
|
||||||
const sourcehut_tests = b.addTest(.{
|
const sourcehut_tests = b.addTest(.{
|
||||||
.name = "sourcehut-tests",
|
.name = "sourcehut-tests",
|
||||||
|
@ -132,6 +132,6 @@ pub fn build(b: *std.Build) void {
|
||||||
|
|
||||||
github_step.dependOn(&b.addRunArtifact(github_tests).step);
|
github_step.dependOn(&b.addRunArtifact(github_tests).step);
|
||||||
gitlab_step.dependOn(&b.addRunArtifact(gitlab_tests).step);
|
gitlab_step.dependOn(&b.addRunArtifact(gitlab_tests).step);
|
||||||
codeberg_step.dependOn(&b.addRunArtifact(codeberg_tests).step);
|
forgejo_step.dependOn(&b.addRunArtifact(forgejo_tests).step);
|
||||||
sourcehut_step.dependOn(&b.addRunArtifact(sourcehut_tests).step);
|
sourcehut_step.dependOn(&b.addRunArtifact(sourcehut_tests).step);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
{
|
{
|
||||||
"github_token": "ghp_your_github_personal_access_token_here",
|
"github_token": "ghp_your_github_personal_access_token_here",
|
||||||
"gitlab_token": "glpat-your_gitlab_personal_access_token_here",
|
"gitlab_token": "glpat-your_gitlab_personal_access_token_here",
|
||||||
"codeberg_token": "your_codeberg_access_token_here",
|
"forgejo": [
|
||||||
|
{
|
||||||
|
"name": "codeberg",
|
||||||
|
"base_url": "https://codeberg.org",
|
||||||
|
"token": "your_codeberg_access_token_here"
|
||||||
|
}
|
||||||
|
],
|
||||||
"sourcehut": {
|
"sourcehut": {
|
||||||
"token": "your_sourcehut_token_here",
|
"token": "your_sourcehut_token_here",
|
||||||
"repositories": [
|
"repositories": [
|
||||||
|
|
|
@ -2,6 +2,31 @@ const std = @import("std");
|
||||||
const json = std.json;
|
const json = std.json;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
pub const ForgejoInstance = struct {
|
||||||
|
name: []const u8,
|
||||||
|
base_url: []const u8,
|
||||||
|
token: []const u8,
|
||||||
|
allocator: Allocator,
|
||||||
|
|
||||||
|
pub fn deinit(self: *const ForgejoInstance) void {
|
||||||
|
self.allocator.free(self.name);
|
||||||
|
self.allocator.free(self.base_url);
|
||||||
|
self.allocator.free(self.token);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ForgejoConfig = struct {
|
||||||
|
instances: []ForgejoInstance,
|
||||||
|
allocator: Allocator,
|
||||||
|
|
||||||
|
pub fn deinit(self: *const ForgejoConfig) void {
|
||||||
|
for (self.instances) |*instance| {
|
||||||
|
instance.deinit();
|
||||||
|
}
|
||||||
|
self.allocator.free(self.instances);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
pub const SourceHutConfig = struct {
|
pub const SourceHutConfig = struct {
|
||||||
token: ?[]const u8 = null,
|
token: ?[]const u8 = null,
|
||||||
repositories: [][]const u8,
|
repositories: [][]const u8,
|
||||||
|
@ -19,7 +44,8 @@ pub const SourceHutConfig = struct {
|
||||||
pub const Config = struct {
|
pub const Config = struct {
|
||||||
github_token: ?[]const u8 = null,
|
github_token: ?[]const u8 = null,
|
||||||
gitlab_token: ?[]const u8 = null,
|
gitlab_token: ?[]const u8 = null,
|
||||||
codeberg_token: ?[]const u8 = null,
|
codeberg_token: ?[]const u8 = null, // Legacy support
|
||||||
|
forgejo: ?ForgejoConfig = null,
|
||||||
sourcehut: ?SourceHutConfig = null,
|
sourcehut: ?SourceHutConfig = null,
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
|
|
||||||
|
@ -27,6 +53,7 @@ pub const Config = struct {
|
||||||
if (self.github_token) |token| self.allocator.free(token);
|
if (self.github_token) |token| self.allocator.free(token);
|
||||||
if (self.gitlab_token) |token| self.allocator.free(token);
|
if (self.gitlab_token) |token| self.allocator.free(token);
|
||||||
if (self.codeberg_token) |token| self.allocator.free(token);
|
if (self.codeberg_token) |token| self.allocator.free(token);
|
||||||
|
if (self.forgejo) |*forgejo_config| forgejo_config.deinit();
|
||||||
if (self.sourcehut) |*sh_config| sh_config.deinit();
|
if (self.sourcehut) |*sh_config| sh_config.deinit();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -72,6 +99,28 @@ pub fn parseConfigFromJson(allocator: Allocator, json_content: []const u8) !Conf
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse forgejo instances
|
||||||
|
var forgejo_config: ?ForgejoConfig = null;
|
||||||
|
if (root.get("forgejo")) |forgejo_obj| {
|
||||||
|
const forgejo_array = forgejo_obj.array;
|
||||||
|
var instances = try allocator.alloc(ForgejoInstance, forgejo_array.items.len);
|
||||||
|
|
||||||
|
for (forgejo_array.items, 0..) |instance_obj, i| {
|
||||||
|
const instance = instance_obj.object;
|
||||||
|
instances[i] = ForgejoInstance{
|
||||||
|
.name = try allocator.dupe(u8, instance.get("name").?.string),
|
||||||
|
.base_url = try allocator.dupe(u8, instance.get("base_url").?.string),
|
||||||
|
.token = try allocator.dupe(u8, instance.get("token").?.string),
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
forgejo_config = ForgejoConfig{
|
||||||
|
.instances = instances,
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return Config{
|
return Config{
|
||||||
.github_token = if (root.get("github_token")) |v| switch (v) {
|
.github_token = if (root.get("github_token")) |v| switch (v) {
|
||||||
.string => |s| if (s.len > 0) try allocator.dupe(u8, s) else null,
|
.string => |s| if (s.len > 0) try allocator.dupe(u8, s) else null,
|
||||||
|
@ -88,6 +137,7 @@ pub fn parseConfigFromJson(allocator: Allocator, json_content: []const u8) !Conf
|
||||||
.null => null,
|
.null => null,
|
||||||
else => null,
|
else => null,
|
||||||
} else null,
|
} else null,
|
||||||
|
.forgejo = forgejo_config,
|
||||||
.sourcehut = sourcehut_config,
|
.sourcehut = sourcehut_config,
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,7 +6,7 @@ const atom = @import("atom.zig");
|
||||||
const Release = @import("main.zig").Release;
|
const Release = @import("main.zig").Release;
|
||||||
const GitHub = @import("providers/GitHub.zig");
|
const GitHub = @import("providers/GitHub.zig");
|
||||||
const GitLab = @import("providers/GitLab.zig");
|
const GitLab = @import("providers/GitLab.zig");
|
||||||
const Codeberg = @import("providers/Codeberg.zig");
|
const Forgejo = @import("providers/Forgejo.zig");
|
||||||
const SourceHut = @import("providers/SourceHut.zig");
|
const SourceHut = @import("providers/SourceHut.zig");
|
||||||
const config = @import("config.zig");
|
const config = @import("config.zig");
|
||||||
|
|
||||||
|
@ -436,24 +436,24 @@ test "GitLab provider integration" {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test "Codeberg provider integration" {
|
test "Forgejo provider integration" {
|
||||||
const allocator = testing.allocator;
|
const allocator = testing.allocator;
|
||||||
|
|
||||||
// Load config to get token
|
// Load config to get token
|
||||||
const app_config = config.loadConfig(allocator, "config.json") catch |err| {
|
const app_config = config.loadConfig(allocator, "config.json") catch |err| {
|
||||||
testPrint("Skipping Codeberg test - config not available: {}\n", .{err});
|
testPrint("Skipping Forgejo test - config not available: {}\n", .{err});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
defer app_config.deinit();
|
defer app_config.deinit();
|
||||||
|
|
||||||
if (app_config.codeberg_token == null) {
|
if (app_config.codeberg_token == null) {
|
||||||
testPrint("Skipping Codeberg test - no token configured\n", .{});
|
testPrint("Skipping Forgejo test - no token configured\n", .{});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var provider = Codeberg.init(app_config.codeberg_token.?);
|
var provider = Forgejo.init("codeberg", "https://codeberg.org", app_config.codeberg_token.?);
|
||||||
const releases = provider.fetchReleases(allocator) catch |err| {
|
const releases = provider.fetchReleases(allocator) catch |err| {
|
||||||
testPrint("Codeberg provider error: {}\n", .{err});
|
testPrint("Forgejo provider error: {}\n", .{err});
|
||||||
return; // Skip test if provider fails
|
return; // Skip test if provider fails
|
||||||
};
|
};
|
||||||
defer {
|
defer {
|
||||||
|
@ -463,7 +463,7 @@ test "Codeberg provider integration" {
|
||||||
releases.deinit();
|
releases.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
testPrint("Codeberg: Found {} releases\n", .{releases.items.len});
|
testPrint("Forgejo: Found {} releases\n", .{releases.items.len});
|
||||||
|
|
||||||
// Verify releases have required fields
|
// Verify releases have required fields
|
||||||
for (releases.items) |release| {
|
for (releases.items) |release| {
|
||||||
|
|
30
src/main.zig
30
src/main.zig
|
@ -6,8 +6,8 @@ const Thread = std.Thread;
|
||||||
|
|
||||||
const GitHub = @import("providers/GitHub.zig");
|
const GitHub = @import("providers/GitHub.zig");
|
||||||
const GitLab = @import("providers/GitLab.zig");
|
const GitLab = @import("providers/GitLab.zig");
|
||||||
const Codeberg = @import("providers/Codeberg.zig");
|
|
||||||
const SourceHut = @import("providers/SourceHut.zig");
|
const SourceHut = @import("providers/SourceHut.zig");
|
||||||
|
const ForgejoRegistry = @import("ForgejoRegistry.zig");
|
||||||
const atom = @import("atom.zig");
|
const atom = @import("atom.zig");
|
||||||
const config = @import("config.zig");
|
const config = @import("config.zig");
|
||||||
const zeit = @import("zeit");
|
const zeit = @import("zeit");
|
||||||
|
@ -136,7 +136,7 @@ pub fn main() !u8 {
|
||||||
// Initialize providers with their tokens (need to persist for the lifetime of the program)
|
// Initialize providers with their tokens (need to persist for the lifetime of the program)
|
||||||
var github_provider: ?GitHub = null;
|
var github_provider: ?GitHub = null;
|
||||||
var gitlab_provider: ?GitLab = null;
|
var gitlab_provider: ?GitLab = null;
|
||||||
var codeberg_provider: ?Codeberg = null;
|
var forgejo_registry: ?ForgejoRegistry = null;
|
||||||
var sourcehut_provider: ?SourceHut = null;
|
var sourcehut_provider: ?SourceHut = null;
|
||||||
|
|
||||||
if (app_config.github_token) |token| {
|
if (app_config.github_token) |token| {
|
||||||
|
@ -147,15 +147,32 @@ pub fn main() !u8 {
|
||||||
gitlab_provider = GitLab.init(token);
|
gitlab_provider = GitLab.init(token);
|
||||||
try providers.append(gitlab_provider.?.provider());
|
try providers.append(gitlab_provider.?.provider());
|
||||||
}
|
}
|
||||||
if (app_config.codeberg_token) |token| {
|
|
||||||
codeberg_provider = Codeberg.init(token);
|
// Handle Forgejo instances (including legacy codeberg_token)
|
||||||
try providers.append(codeberg_provider.?.provider());
|
forgejo_registry = ForgejoRegistry.init(allocator, &app_config) catch |err| {
|
||||||
|
const stderr = std.io.getStdErr().writer();
|
||||||
|
stderr.print("Error initializing Forgejo registry: {}\n", .{err}) catch {};
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
if (forgejo_registry) |*registry| {
|
||||||
|
const forgejo_providers = try registry.providers();
|
||||||
|
defer registry.deinitProviders(forgejo_providers);
|
||||||
|
|
||||||
|
for (forgejo_providers) |provider| {
|
||||||
|
try providers.append(provider);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (app_config.sourcehut) |sh_config| if (sh_config.repositories.len > 0 and sh_config.token != null) {
|
if (app_config.sourcehut) |sh_config| if (sh_config.repositories.len > 0 and sh_config.token != null) {
|
||||||
sourcehut_provider = SourceHut.init(sh_config.token.?, sh_config.repositories);
|
sourcehut_provider = SourceHut.init(sh_config.token.?, sh_config.repositories);
|
||||||
try providers.append(sourcehut_provider.?.provider());
|
try providers.append(sourcehut_provider.?.provider());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Cleanup forgejo registry when done
|
||||||
|
defer if (forgejo_registry) |*registry| {
|
||||||
|
registry.deinit();
|
||||||
|
};
|
||||||
|
|
||||||
// Fetch releases from all providers concurrently using thread pool
|
// Fetch releases from all providers concurrently using thread pool
|
||||||
const provider_results = try fetchReleasesFromAllProviders(allocator, providers.items);
|
const provider_results = try fetchReleasesFromAllProviders(allocator, providers.items);
|
||||||
defer {
|
defer {
|
||||||
|
@ -567,5 +584,6 @@ test {
|
||||||
std.testing.refAllDecls(@import("providers/GitHub.zig"));
|
std.testing.refAllDecls(@import("providers/GitHub.zig"));
|
||||||
std.testing.refAllDecls(@import("providers/GitLab.zig"));
|
std.testing.refAllDecls(@import("providers/GitLab.zig"));
|
||||||
std.testing.refAllDecls(@import("providers/SourceHut.zig"));
|
std.testing.refAllDecls(@import("providers/SourceHut.zig"));
|
||||||
std.testing.refAllDecls(@import("providers/Codeberg.zig"));
|
std.testing.refAllDecls(@import("providers/Forgejo.zig"));
|
||||||
|
std.testing.refAllDecls(@import("ForgejoRegistry.zig"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,12 +9,18 @@ const tag_filter = @import("../tag_filter.zig");
|
||||||
const Release = @import("../main.zig").Release;
|
const Release = @import("../main.zig").Release;
|
||||||
const Provider = @import("../Provider.zig");
|
const Provider = @import("../Provider.zig");
|
||||||
|
|
||||||
|
name: []const u8,
|
||||||
|
base_url: []const u8,
|
||||||
token: []const u8,
|
token: []const u8,
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
pub fn init(token: []const u8) Self {
|
pub fn init(name: []const u8, base_url: []const u8, token: []const u8) Self {
|
||||||
return Self{ .token = token };
|
return Self{
|
||||||
|
.name = name,
|
||||||
|
.base_url = base_url,
|
||||||
|
.token = token,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn provider(self: *Self) Provider {
|
pub fn provider(self: *Self) Provider {
|
||||||
|
@ -27,8 +33,8 @@ pub fn fetchReleases(self: *Self, allocator: Allocator) !ArrayList(Release) {
|
||||||
|
|
||||||
var releases = ArrayList(Release).init(allocator);
|
var releases = ArrayList(Release).init(allocator);
|
||||||
|
|
||||||
// Get starred repositories (Codeberg uses Gitea API)
|
// Get starred repositories (uses Forgejo/Gitea API)
|
||||||
const starred_repos = try getStarredRepos(allocator, &client, self.token);
|
const starred_repos = try getStarredRepos(allocator, &client, self.base_url, self.token);
|
||||||
defer {
|
defer {
|
||||||
for (starred_repos.items) |repo| {
|
for (starred_repos.items) |repo| {
|
||||||
allocator.free(repo);
|
allocator.free(repo);
|
||||||
|
@ -39,9 +45,9 @@ pub fn fetchReleases(self: *Self, allocator: Allocator) !ArrayList(Release) {
|
||||||
// Get releases for each repo
|
// Get releases for each repo
|
||||||
for (starred_repos.items) |repo| {
|
for (starred_repos.items) |repo| {
|
||||||
// TODO: Investigate the tags/releases situation similar to GitHub
|
// TODO: Investigate the tags/releases situation similar to GitHub
|
||||||
const repo_releases = getRepoReleases(allocator, &client, self.token, repo) catch |err| {
|
const repo_releases = getRepoReleases(allocator, &client, self.base_url, self.token, self.name, repo) catch |err| {
|
||||||
const stderr = std.io.getStdErr().writer();
|
const stderr = std.io.getStdErr().writer();
|
||||||
stderr.print("Error fetching Codeberg releases for {s}: {}\n", .{ repo, err }) catch {};
|
stderr.print("Error fetching {s} releases for {s}: {}\n", .{ self.name, repo, err }) catch {};
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
defer repo_releases.deinit();
|
defer repo_releases.deinit();
|
||||||
|
@ -56,11 +62,10 @@ pub fn fetchReleases(self: *Self, allocator: Allocator) !ArrayList(Release) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getName(self: *Self) []const u8 {
|
pub fn getName(self: *Self) []const u8 {
|
||||||
_ = self;
|
return self.name;
|
||||||
return "codeberg";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getStarredRepos(allocator: Allocator, client: *http.Client, token: []const u8) !ArrayList([]const u8) {
|
fn getStarredRepos(allocator: Allocator, client: *http.Client, base_url: []const u8, token: []const u8) !ArrayList([]const u8) {
|
||||||
var repos = ArrayList([]const u8).init(allocator);
|
var repos = ArrayList([]const u8).init(allocator);
|
||||||
errdefer {
|
errdefer {
|
||||||
// Clean up any allocated repo names if we fail
|
// Clean up any allocated repo names if we fail
|
||||||
|
@ -78,7 +83,7 @@ fn getStarredRepos(allocator: Allocator, client: *http.Client, token: []const u8
|
||||||
const per_page: u32 = 100;
|
const per_page: u32 = 100;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const url = try std.fmt.allocPrint(allocator, "https://codeberg.org/api/v1/user/starred?limit={d}&page={d}", .{ per_page, page });
|
const url = try std.fmt.allocPrint(allocator, "{s}/api/v1/user/starred?limit={d}&page={d}", .{ base_url, per_page, page });
|
||||||
defer allocator.free(url);
|
defer allocator.free(url);
|
||||||
|
|
||||||
const uri = try std.Uri.parse(url);
|
const uri = try std.Uri.parse(url);
|
||||||
|
@ -99,15 +104,15 @@ fn getStarredRepos(allocator: Allocator, client: *http.Client, token: []const u8
|
||||||
if (req.response.status != .ok) {
|
if (req.response.status != .ok) {
|
||||||
if (req.response.status == .unauthorized) {
|
if (req.response.status == .unauthorized) {
|
||||||
const stderr = std.io.getStdErr().writer();
|
const stderr = std.io.getStdErr().writer();
|
||||||
stderr.print("Codeberg API: Unauthorized - check your token and scopes\n", .{}) catch {};
|
stderr.print("Forgejo API: Unauthorized - check your token and scopes\n", .{}) catch {};
|
||||||
return error.Unauthorized;
|
return error.Unauthorized;
|
||||||
} else if (req.response.status == .forbidden) {
|
} else if (req.response.status == .forbidden) {
|
||||||
const stderr = std.io.getStdErr().writer();
|
const stderr = std.io.getStdErr().writer();
|
||||||
stderr.print("Codeberg API: Forbidden - token may lack required scopes (read:repository)\n", .{}) catch {};
|
stderr.print("Forgejo API: Forbidden - token may lack required scopes (read:repository)\n", .{}) catch {};
|
||||||
return error.Forbidden;
|
return error.Forbidden;
|
||||||
}
|
}
|
||||||
const stderr = std.io.getStdErr().writer();
|
const stderr = std.io.getStdErr().writer();
|
||||||
stderr.print("Codeberg API request failed with status: {}\n", .{req.response.status}) catch {};
|
stderr.print("Forgejo API request failed with status: {}\n", .{req.response.status}) catch {};
|
||||||
return error.HttpRequestFailed;
|
return error.HttpRequestFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,7 +121,7 @@ fn getStarredRepos(allocator: Allocator, client: *http.Client, token: []const u8
|
||||||
|
|
||||||
const parsed = json.parseFromSlice(json.Value, allocator, body, .{}) catch |err| {
|
const parsed = json.parseFromSlice(json.Value, allocator, body, .{}) catch |err| {
|
||||||
const stderr = std.io.getStdErr().writer();
|
const stderr = std.io.getStdErr().writer();
|
||||||
stderr.print("Error parsing Codeberg starred repos JSON (page {d}): {}\n", .{ page, err }) catch {};
|
stderr.print("Error parsing Forgejo starred repos JSON (page {d}): {}\n", .{ page, err }) catch {};
|
||||||
return error.JsonParseError;
|
return error.JsonParseError;
|
||||||
};
|
};
|
||||||
defer parsed.deinit();
|
defer parsed.deinit();
|
||||||
|
@ -152,7 +157,7 @@ fn getStarredRepos(allocator: Allocator, client: *http.Client, token: []const u8
|
||||||
return repos;
|
return repos;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getRepoReleases(allocator: Allocator, client: *http.Client, token: []const u8, repo: []const u8) !ArrayList(Release) {
|
fn getRepoReleases(allocator: Allocator, client: *http.Client, base_url: []const u8, token: []const u8, provider_name: []const u8, repo: []const u8) !ArrayList(Release) {
|
||||||
var releases = ArrayList(Release).init(allocator);
|
var releases = ArrayList(Release).init(allocator);
|
||||||
errdefer {
|
errdefer {
|
||||||
// Clean up any allocated releases if we fail
|
// Clean up any allocated releases if we fail
|
||||||
|
@ -162,7 +167,7 @@ fn getRepoReleases(allocator: Allocator, client: *http.Client, token: []const u8
|
||||||
releases.deinit();
|
releases.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = try std.fmt.allocPrint(allocator, "https://codeberg.org/api/v1/repos/{s}/releases", .{repo});
|
const url = try std.fmt.allocPrint(allocator, "{s}/api/v1/repos/{s}/releases", .{ base_url, repo });
|
||||||
defer allocator.free(url);
|
defer allocator.free(url);
|
||||||
|
|
||||||
const uri = try std.Uri.parse(url);
|
const uri = try std.Uri.parse(url);
|
||||||
|
@ -186,19 +191,19 @@ fn getRepoReleases(allocator: Allocator, client: *http.Client, token: []const u8
|
||||||
if (req.response.status != .ok) {
|
if (req.response.status != .ok) {
|
||||||
if (req.response.status == .unauthorized) {
|
if (req.response.status == .unauthorized) {
|
||||||
const stderr = std.io.getStdErr().writer();
|
const stderr = std.io.getStdErr().writer();
|
||||||
stderr.print("Codeberg API: Unauthorized for repo {s} - check your token and scopes\n", .{repo}) catch {};
|
stderr.print("Forgejo API: Unauthorized for repo {s} - check your token and scopes\n", .{repo}) catch {};
|
||||||
return error.Unauthorized;
|
return error.Unauthorized;
|
||||||
} else if (req.response.status == .forbidden) {
|
} else if (req.response.status == .forbidden) {
|
||||||
const stderr = std.io.getStdErr().writer();
|
const stderr = std.io.getStdErr().writer();
|
||||||
stderr.print("Codeberg API: Forbidden for repo {s} - token may lack required scopes\n", .{repo}) catch {};
|
stderr.print("Forgejo API: Forbidden for repo {s} - token may lack required scopes\n", .{repo}) catch {};
|
||||||
return error.Forbidden;
|
return error.Forbidden;
|
||||||
} else if (req.response.status == .not_found) {
|
} else if (req.response.status == .not_found) {
|
||||||
const stderr = std.io.getStdErr().writer();
|
const stderr = std.io.getStdErr().writer();
|
||||||
stderr.print("Codeberg API: Repository {s} not found or no releases\n", .{repo}) catch {};
|
stderr.print("Forgejo API: Repository {s} not found or no releases\n", .{repo}) catch {};
|
||||||
return error.NotFound;
|
return error.NotFound;
|
||||||
}
|
}
|
||||||
const stderr = std.io.getStdErr().writer();
|
const stderr = std.io.getStdErr().writer();
|
||||||
stderr.print("Codeberg API request failed for repo {s} with status: {}\n", .{ repo, req.response.status }) catch {};
|
stderr.print("Forgejo API request failed for repo {s} with status: {}\n", .{ repo, req.response.status }) catch {};
|
||||||
return error.HttpRequestFailed;
|
return error.HttpRequestFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,7 +212,7 @@ fn getRepoReleases(allocator: Allocator, client: *http.Client, token: []const u8
|
||||||
|
|
||||||
const parsed = json.parseFromSlice(json.Value, allocator, body, .{}) catch |err| {
|
const parsed = json.parseFromSlice(json.Value, allocator, body, .{}) catch |err| {
|
||||||
const stderr = std.io.getStdErr().writer();
|
const stderr = std.io.getStdErr().writer();
|
||||||
stderr.print("Error parsing Codeberg releases JSON for {s}: {}\n", .{ repo, err }) catch {};
|
stderr.print("Error parsing Forgejo releases JSON for {s}: {}\n", .{ repo, err }) catch {};
|
||||||
return error.JsonParseError;
|
return error.JsonParseError;
|
||||||
};
|
};
|
||||||
defer parsed.deinit();
|
defer parsed.deinit();
|
||||||
|
@ -247,7 +252,7 @@ fn getRepoReleases(allocator: Allocator, client: *http.Client, token: []const u8
|
||||||
.published_at = try utils.parseReleaseTimestamp(published_at_value.string),
|
.published_at = try utils.parseReleaseTimestamp(published_at_value.string),
|
||||||
.html_url = try allocator.dupe(u8, html_url_value.string),
|
.html_url = try allocator.dupe(u8, html_url_value.string),
|
||||||
.description = try allocator.dupe(u8, body_str),
|
.description = try allocator.dupe(u8, body_str),
|
||||||
.provider = try allocator.dupe(u8, "codeberg"),
|
.provider = try allocator.dupe(u8, provider_name),
|
||||||
.is_tag = false,
|
.is_tag = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -264,15 +269,15 @@ fn getRepoReleases(allocator: Allocator, client: *http.Client, token: []const u8
|
||||||
return releases;
|
return releases;
|
||||||
}
|
}
|
||||||
|
|
||||||
test "codeberg provider name" {
|
test "forgejo provider name" {
|
||||||
const allocator = std.testing.allocator;
|
const allocator = std.testing.allocator;
|
||||||
_ = allocator;
|
_ = allocator;
|
||||||
|
|
||||||
var codeberg_provider = init("dummy_token");
|
var forgejo_provider = init("codeberg", "https://codeberg.org", "dummy_token");
|
||||||
try std.testing.expectEqualStrings("codeberg", codeberg_provider.getName());
|
try std.testing.expectEqualStrings("codeberg", forgejo_provider.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
test "codeberg release parsing with live data snapshot" {
|
test "forgejo release parsing with live data snapshot" {
|
||||||
const allocator = std.testing.allocator;
|
const allocator = std.testing.allocator;
|
||||||
|
|
||||||
// Sample Codeberg API response for releases (captured from real API)
|
// Sample Codeberg API response for releases (captured from real API)
|
||||||
|
@ -326,7 +331,7 @@ test "codeberg release parsing with live data snapshot" {
|
||||||
.published_at = try utils.parseReleaseTimestamp(published_at_value.string),
|
.published_at = try utils.parseReleaseTimestamp(published_at_value.string),
|
||||||
.html_url = try allocator.dupe(u8, html_url_value.string),
|
.html_url = try allocator.dupe(u8, html_url_value.string),
|
||||||
.description = try allocator.dupe(u8, body_str),
|
.description = try allocator.dupe(u8, body_str),
|
||||||
.provider = try allocator.dupe(u8, "codeberg"),
|
.provider = try allocator.dupe(u8, "test-forgejo"),
|
||||||
.is_tag = false,
|
.is_tag = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -348,13 +353,13 @@ test "codeberg release parsing with live data snapshot" {
|
||||||
))),
|
))),
|
||||||
releases.items[0].published_at,
|
releases.items[0].published_at,
|
||||||
);
|
);
|
||||||
try std.testing.expectEqualStrings("codeberg", releases.items[0].provider);
|
try std.testing.expectEqualStrings("test-forgejo", releases.items[0].provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "Codeberg tag filtering" {
|
test "Forgejo tag filtering" {
|
||||||
const allocator = std.testing.allocator;
|
const allocator = std.testing.allocator;
|
||||||
|
|
||||||
// Test that Codeberg now uses the same filtering as other providers
|
// Test that Forgejo now uses the same filtering as other providers
|
||||||
const problematic_tags = [_][]const u8{
|
const problematic_tags = [_][]const u8{
|
||||||
"nightly", "prerelease", "latest", "edge", "canary", "dev-branch",
|
"nightly", "prerelease", "latest", "edge", "canary", "dev-branch",
|
||||||
};
|
};
|
Loading…
Add table
Reference in a new issue