From 7c9199636cea64396a8c19713c45c4749f627fce Mon Sep 17 00:00:00 2001 From: Jim Fitzpatrick Date: Sat, 4 Apr 2026 20:02:39 +0100 Subject: [PATCH] ADD: codeberg parsing SSH clone strings from codeberg can now be used. Signed-off-by: Jim Fitzpatrick --- build.zig.zon | 2 +- changelog.d/53.feature.md | 1 + src/root.zig | 65 +++++++++++++++++++++++++++++++++++---- 3 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 changelog.d/53.feature.md diff --git a/build.zig.zon b/build.zig.zon index 2b082cf..dd532dc 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -9,7 +9,7 @@ .name = .grab, // This is a [Semantic Version](https://semver.org/). // In a future version of Zig it will be used for package deduplication. - .version = "0.9.0", + .version = "0.9.0-dev", // Together with name, this represents a globally unique package // identifier. This field is generated by the Zig toolchain when the // package is first created, and then *never changes*. This allows diff --git a/changelog.d/53.feature.md b/changelog.d/53.feature.md new file mode 100644 index 0000000..a568a9d --- /dev/null +++ b/changelog.d/53.feature.md @@ -0,0 +1 @@ +Add parsing to match the CodeBerg ssh clone string format. diff --git a/src/root.zig b/src/root.zig index 975a299..1dc4e9a 100644 --- a/src/root.zig +++ b/src/root.zig @@ -8,20 +8,33 @@ pub const Project = struct { root: ?std.fs.Dir = null, pub fn init(repo: []const u8) !Project { - if (!std.mem.startsWith(u8, repo, "git") or !std.mem.endsWith(u8, repo, "git")) { + if (!std.mem.endsWith(u8, repo, ".git")) { return error.parse; } + var split_on = ":"; + var min: usize = undefined; + var max: usize = undefined; + if (std.mem.startsWith(u8, repo, "git")) { + std.log.debug("possible GitHub repo", .{}); + split_on = ":"; + } else if (std.mem.startsWith(u8, repo, "ssh://git")) { + std.log.debug("possible Codeberg repo", .{}); + split_on = "/"; + } else { + return error.parse; + } + const _clone = repo; - var min = std.mem.indexOf(u8, repo, "@") orelse return error.parse; - var max = std.mem.indexOf(u8, repo, ":") orelse return error.parse; + min = std.mem.indexOf(u8, repo, "@") orelse return error.parse; + max = std.mem.indexOfPos(u8, repo, min, split_on) orelse return error.parse; const _site = repo[min + 1 .. max]; - min = std.mem.indexOf(u8, repo, ":") orelse return error.parse; - max = std.mem.indexOf(u8, repo, "/") orelse return error.parse; + min = max; + max = std.mem.indexOfPos(u8, repo, min + 1, "/") orelse return error.parse; const _owner = repo[min + 1 .. max]; - min = std.mem.indexOf(u8, repo, "/") orelse return error.parse; + min = max; max = std.mem.indexOf(u8, repo, ".git") orelse return error.parse; const _name = repo[min + 1 .. max]; @@ -250,3 +263,43 @@ pub fn fetchOrigin(allocator: std.mem.Allocator, path: std.fs.Dir) !void { allocator.free(result.stderr); } } + +test "input parsing GitHub" { + const input = "git@github.com:Boomatang/git-grab.git"; + const expect = Project{ + .clone = input, + .name = "git-grab", + .owner = "Boomatang", + .site = "github.com", + }; + + const project = try Project.init(input); + + try std.testing.expectEqualStrings(expect.site, project.site); + try std.testing.expectEqualStrings(expect.owner, project.owner); + try std.testing.expectEqualStrings(expect.name, project.name); + try std.testing.expectEqualStrings(expect.clone, project.clone); +} + +test "input parsing codeberg" { + const input = "ssh://git@codeberg.org/boomatang/boomatang.git"; + const expect = Project{ + .clone = input, + .name = "boomatang", + .owner = "boomatang", + .site = "codeberg.org", + }; + + const project = try Project.init(input); + + try std.testing.expectEqualStrings(expect.site, project.site); + try std.testing.expectEqualStrings(expect.owner, project.owner); + try std.testing.expectEqualStrings(expect.name, project.name); + try std.testing.expectEqualStrings(expect.clone, project.clone); +} + +test "input parsing Bad Input" { + const input = "@codeberg.org/boomatang/boomatang.git"; + + try std.testing.expectError(error.parse, Project.init(input)); +}