diff --git a/README.md b/README.md index 8524dac..fba7fb3 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Table of content ## Who is this tool for? -`git-backporting` is a tool that provides capabilities to *backport* pull requests (on *GitHub*) and merge requests (on *GitLab*) in an automated way. +`git-backporting` is a fully configurable tool that provides capabilities to *backport* pull requests (on *GitHub*) and merge requests (on *GitLab*) in an automated way. > *What is backporting?* - backporting is an action aiming to move a change (usually a commit) from a branch (usually the main one) to another one, which is generally referring to a still maintained release branch. Keeping it simple: it is about to move a specific change or a set of them from one branch to another. @@ -139,6 +139,8 @@ Right now **Git Backporting** supports the following git management services: * ***GITLAB***: This has been introduced since version `3.0.0`, it works for both public and private *GitLab* servers. The interaction with this service is performed using plain [*axios*](https://axios-http.com) requests. The *gitlab* api version that is used to make requests is `v4`, at the moment there is no possibility to override it. + * ***CODEBERG***: Introduced since version `4.4.0`, it works for public [codeberg.org](https://codeberg.org/) platform. Thanks to the api compatibility with GitHub, the interaction with this service is performed using using [*octokit*](https://octokit.github.io/rest.js) client library. + > **NOTE**: by default, all gitlab requests are performed setting `rejectUnauthorized=false`, planning to make this configurable too. ## GitHub action diff --git a/dist/cli/index.js b/dist/cli/index.js index aab00ba..83a0c2c 100755 --- a/dist/cli/index.js +++ b/dist/cli/index.js @@ -566,6 +566,9 @@ class GitClientFactory { case git_types_1.GitClientType.GITLAB: GitClientFactory.instance = new gitlab_client_1.default(authToken, apiUrl); break; + case git_types_1.GitClientType.CODEBERG: + GitClientFactory.instance = new github_client_1.default(authToken, apiUrl); + break; default: throw new Error(`Invalid git service type received: ${type}`); } @@ -607,6 +610,9 @@ const inferGitClient = (prUrl) => { else if (stdPrUrl.includes(git_types_1.GitClientType.GITLAB.toString())) { return git_types_1.GitClientType.GITLAB; } + else if (stdPrUrl.includes(git_types_1.GitClientType.CODEBERG.toString())) { + return git_types_1.GitClientType.CODEBERG; + } throw new Error(`Remote git service not recognized from pr url: ${prUrl}`); }; exports.inferGitClient = inferGitClient; @@ -640,6 +646,7 @@ var GitClientType; (function (GitClientType) { GitClientType["GITHUB"] = "github"; GitClientType["GITLAB"] = "gitlab"; + GitClientType["CODEBERG"] = "codeberg"; })(GitClientType = exports.GitClientType || (exports.GitClientType = {})); var GitRepoState; (function (GitRepoState) { @@ -661,6 +668,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); +const git_types_1 = __nccwpck_require__(750); const github_mapper_1 = __importDefault(__nccwpck_require__(5764)); const octokit_factory_1 = __importDefault(__nccwpck_require__(4257)); const logger_service_factory_1 = __importDefault(__nccwpck_require__(8936)); @@ -673,7 +681,7 @@ class GitHubClient { } // READ getDefaultGitUser() { - return "GitHub"; + return this.apiUrl.includes(git_types_1.GitClientType.CODEBERG.toString()) ? "Codeberg" : "GitHub"; } getDefaultGitEmail() { return "noreply@github.com"; @@ -806,9 +814,9 @@ class GitHubMapper { state: this.mapGitState(pr.state), merged: pr.merged ?? false, mergedBy: pr.merged_by?.login, - reviewers: pr.requested_reviewers.filter(r => "login" in r).map((r => r?.login)), - assignees: pr.assignees.filter(r => "login" in r).map(r => r.login), - labels: pr.labels.map(l => l.name), + reviewers: pr.requested_reviewers?.filter(r => "login" in r).map((r => r?.login)) ?? [], + assignees: pr.assignees?.filter(r => "login" in r).map(r => r.login) ?? [], + labels: pr.labels?.map(l => l.name) ?? [], sourceRepo: await this.mapSourceRepo(pr), targetRepo: await this.mapTargetRepo(pr), nCommits: pr.commits, @@ -1260,8 +1268,8 @@ class Runner { } // 2. init git service const gitClientType = (0, git_util_1.inferGitClient)(args.pullRequest); - // right now the apiVersion is set to v4 - const apiUrl = (0, git_util_1.inferGitApiUrl)(args.pullRequest); + // the api version is ignored in case of github + const apiUrl = (0, git_util_1.inferGitApiUrl)(args.pullRequest, gitClientType === git_types_1.GitClientType.CODEBERG ? "v1" : undefined); const gitApi = git_client_factory_1.default.getOrCreate(gitClientType, args.auth, apiUrl); // 3. parse configs this.logger.debug("Parsing configs.."); @@ -1302,7 +1310,7 @@ class Runner { if (configs.originalPullRequest.sourceRepo.owner !== configs.originalPullRequest.targetRepo.owner || configs.originalPullRequest.state === "open") { this.logger.debug("Fetching pull request remote.."); - const prefix = git.gitClientType === git_types_1.GitClientType.GITHUB ? "pull" : "merge-requests"; // default is for gitlab + const prefix = git.gitClientType === git_types_1.GitClientType.GITLAB ? "merge-requests" : "pull"; // default is for gitlab await git.gitCli.fetch(configs.folder, `${prefix}/${configs.originalPullRequest.number}/head:pr/${configs.originalPullRequest.number}`); } // 7. apply all changes to the new branch diff --git a/dist/gha/index.js b/dist/gha/index.js index f8f564c..29dd2b5 100755 --- a/dist/gha/index.js +++ b/dist/gha/index.js @@ -536,6 +536,9 @@ class GitClientFactory { case git_types_1.GitClientType.GITLAB: GitClientFactory.instance = new gitlab_client_1.default(authToken, apiUrl); break; + case git_types_1.GitClientType.CODEBERG: + GitClientFactory.instance = new github_client_1.default(authToken, apiUrl); + break; default: throw new Error(`Invalid git service type received: ${type}`); } @@ -577,6 +580,9 @@ const inferGitClient = (prUrl) => { else if (stdPrUrl.includes(git_types_1.GitClientType.GITLAB.toString())) { return git_types_1.GitClientType.GITLAB; } + else if (stdPrUrl.includes(git_types_1.GitClientType.CODEBERG.toString())) { + return git_types_1.GitClientType.CODEBERG; + } throw new Error(`Remote git service not recognized from pr url: ${prUrl}`); }; exports.inferGitClient = inferGitClient; @@ -610,6 +616,7 @@ var GitClientType; (function (GitClientType) { GitClientType["GITHUB"] = "github"; GitClientType["GITLAB"] = "gitlab"; + GitClientType["CODEBERG"] = "codeberg"; })(GitClientType = exports.GitClientType || (exports.GitClientType = {})); var GitRepoState; (function (GitRepoState) { @@ -631,6 +638,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); +const git_types_1 = __nccwpck_require__(750); const github_mapper_1 = __importDefault(__nccwpck_require__(5764)); const octokit_factory_1 = __importDefault(__nccwpck_require__(4257)); const logger_service_factory_1 = __importDefault(__nccwpck_require__(8936)); @@ -643,7 +651,7 @@ class GitHubClient { } // READ getDefaultGitUser() { - return "GitHub"; + return this.apiUrl.includes(git_types_1.GitClientType.CODEBERG.toString()) ? "Codeberg" : "GitHub"; } getDefaultGitEmail() { return "noreply@github.com"; @@ -776,9 +784,9 @@ class GitHubMapper { state: this.mapGitState(pr.state), merged: pr.merged ?? false, mergedBy: pr.merged_by?.login, - reviewers: pr.requested_reviewers.filter(r => "login" in r).map((r => r?.login)), - assignees: pr.assignees.filter(r => "login" in r).map(r => r.login), - labels: pr.labels.map(l => l.name), + reviewers: pr.requested_reviewers?.filter(r => "login" in r).map((r => r?.login)) ?? [], + assignees: pr.assignees?.filter(r => "login" in r).map(r => r.login) ?? [], + labels: pr.labels?.map(l => l.name) ?? [], sourceRepo: await this.mapSourceRepo(pr), targetRepo: await this.mapTargetRepo(pr), nCommits: pr.commits, @@ -1230,8 +1238,8 @@ class Runner { } // 2. init git service const gitClientType = (0, git_util_1.inferGitClient)(args.pullRequest); - // right now the apiVersion is set to v4 - const apiUrl = (0, git_util_1.inferGitApiUrl)(args.pullRequest); + // the api version is ignored in case of github + const apiUrl = (0, git_util_1.inferGitApiUrl)(args.pullRequest, gitClientType === git_types_1.GitClientType.CODEBERG ? "v1" : undefined); const gitApi = git_client_factory_1.default.getOrCreate(gitClientType, args.auth, apiUrl); // 3. parse configs this.logger.debug("Parsing configs.."); @@ -1272,7 +1280,7 @@ class Runner { if (configs.originalPullRequest.sourceRepo.owner !== configs.originalPullRequest.targetRepo.owner || configs.originalPullRequest.state === "open") { this.logger.debug("Fetching pull request remote.."); - const prefix = git.gitClientType === git_types_1.GitClientType.GITHUB ? "pull" : "merge-requests"; // default is for gitlab + const prefix = git.gitClientType === git_types_1.GitClientType.GITLAB ? "merge-requests" : "pull"; // default is for gitlab await git.gitCli.fetch(configs.folder, `${prefix}/${configs.originalPullRequest.number}/head:pr/${configs.originalPullRequest.number}`); } // 7. apply all changes to the new branch diff --git a/src/service/git/git-client-factory.ts b/src/service/git/git-client-factory.ts index fa63173..ed66a75 100644 --- a/src/service/git/git-client-factory.ts +++ b/src/service/git/git-client-factory.ts @@ -43,6 +43,9 @@ export default class GitClientFactory { case GitClientType.GITLAB: GitClientFactory.instance = new GitLabClient(authToken, apiUrl); break; + case GitClientType.CODEBERG: + GitClientFactory.instance = new GitHubService(authToken, apiUrl); + break; default: throw new Error(`Invalid git service type received: ${type}`); } diff --git a/src/service/git/git-util.ts b/src/service/git/git-util.ts index 6d459b2..2ac1f2b 100644 --- a/src/service/git/git-util.ts +++ b/src/service/git/git-util.ts @@ -16,6 +16,8 @@ export const inferGitClient = (prUrl: string): GitClientType => { return GitClientType.GITHUB; } else if (stdPrUrl.includes(GitClientType.GITLAB.toString())) { return GitClientType.GITLAB; + } else if (stdPrUrl.includes(GitClientType.CODEBERG.toString())) { + return GitClientType.CODEBERG; } throw new Error(`Remote git service not recognized from pr url: ${prUrl}`); diff --git a/src/service/git/git.types.ts b/src/service/git/git.types.ts index 6370eee..696b29d 100644 --- a/src/service/git/git.types.ts +++ b/src/service/git/git.types.ts @@ -41,6 +41,7 @@ export interface BackportPullRequest { export enum GitClientType { GITHUB = "github", GITLAB = "gitlab", + CODEBERG = "codeberg", } export enum GitRepoState { diff --git a/src/service/git/github/github-client.ts b/src/service/git/github/github-client.ts index 297052d..f83f3e2 100644 --- a/src/service/git/github/github-client.ts +++ b/src/service/git/github/github-client.ts @@ -1,5 +1,5 @@ import GitClient from "@bp/service/git/git-client"; -import { BackportPullRequest, GitPullRequest } from "@bp/service/git/git.types"; +import { BackportPullRequest, GitClientType, GitPullRequest } from "@bp/service/git/git.types"; import GitHubMapper from "@bp/service/git/github/github-mapper"; import OctokitFactory from "@bp/service/git/github/octokit-factory"; import LoggerService from "@bp/service/logger/logger-service"; @@ -24,7 +24,7 @@ export default class GitHubClient implements GitClient { // READ getDefaultGitUser(): string { - return "GitHub"; + return this.apiUrl.includes(GitClientType.CODEBERG.toString()) ? "Codeberg" : "GitHub"; } getDefaultGitEmail(): string { diff --git a/src/service/git/github/github-mapper.ts b/src/service/git/github/github-mapper.ts index a79cb3d..3ae0ca4 100644 --- a/src/service/git/github/github-mapper.ts +++ b/src/service/git/github/github-mapper.ts @@ -24,9 +24,9 @@ export default class GitHubMapper implements GitResponseMapper "login" in r).map((r => (r as User)?.login)), - assignees: pr.assignees.filter(r => "login" in r).map(r => r.login), - labels: pr.labels.map(l => l.name), + reviewers: pr.requested_reviewers?.filter(r => "login" in r).map((r => (r as User)?.login)) ?? [], + assignees: pr.assignees?.filter(r => "login" in r).map(r => r.login) ?? [], + labels: pr.labels?.map(l => l.name) ?? [], sourceRepo: await this.mapSourceRepo(pr), targetRepo: await this.mapTargetRepo(pr), nCommits: pr.commits, diff --git a/src/service/runner/runner.ts b/src/service/runner/runner.ts index 6f256b4..ce6d543 100644 --- a/src/service/runner/runner.ts +++ b/src/service/runner/runner.ts @@ -61,8 +61,8 @@ export default class Runner { // 2. init git service const gitClientType: GitClientType = inferGitClient(args.pullRequest); - // right now the apiVersion is set to v4 - const apiUrl = inferGitApiUrl(args.pullRequest); + // the api version is ignored in case of github + const apiUrl = inferGitApiUrl(args.pullRequest, gitClientType === GitClientType.CODEBERG ? "v1" : undefined); const gitApi: GitClient = GitClientFactory.getOrCreate(gitClientType, args.auth, apiUrl); // 3. parse configs @@ -112,7 +112,7 @@ export default class Runner { if (configs.originalPullRequest.sourceRepo.owner !== configs.originalPullRequest.targetRepo.owner || configs.originalPullRequest.state === "open") { this.logger.debug("Fetching pull request remote.."); - const prefix = git.gitClientType === GitClientType.GITHUB ? "pull" : "merge-requests"; // default is for gitlab + const prefix = git.gitClientType === GitClientType.GITLAB ? "merge-requests" : "pull" ; // default is for gitlab await git.gitCli.fetch(configs.folder, `${prefix}/${configs.originalPullRequest.number}/head:pr/${configs.originalPullRequest.number}`); } diff --git a/test/service/git/git-client-factory.test.ts b/test/service/git/git-client-factory.test.ts index 0d05ea3..df64e40 100644 --- a/test/service/git/git-client-factory.test.ts +++ b/test/service/git/git-client-factory.test.ts @@ -20,6 +20,11 @@ describe("git client factory test", () => { expect(client).toBeInstanceOf(GitLabClient); }); + test("correctly create codeberg client", () => { + const client = GitClientFactory.getOrCreate(GitClientType.CODEBERG, "auth", "apiurl"); + expect(client).toBeInstanceOf(GitHubClient); + }); + test("check get service github", () => { const create = GitClientFactory.getOrCreate(GitClientType.GITHUB, "auth", "apiurl"); const get = GitClientFactory.getClient(); @@ -31,4 +36,10 @@ describe("git client factory test", () => { const get = GitClientFactory.getClient(); expect(create).toStrictEqual(get); }); + + test("check get service codeberg", () => { + const create = GitClientFactory.getOrCreate(GitClientType.CODEBERG, "auth", "apiurl"); + const get = GitClientFactory.getClient(); + expect(create).toStrictEqual(get); + }); }); \ No newline at end of file diff --git a/test/service/git/git-util.test.ts b/test/service/git/git-util.test.ts index cf083c6..5e202e6 100644 --- a/test/service/git/git-util.test.ts +++ b/test/service/git/git-util.test.ts @@ -23,6 +23,18 @@ describe("check git utilities", () => { expect(inferGitApiUrl("http://github.acme-inc.com/superuser/backporting-example/pull/4", "v3")).toStrictEqual("http://github.acme-inc.com/api/v3"); }); + test("check infer github api from github api url", ()=> { + expect(inferGitApiUrl("https://api.github.com/repos/owner/repo/pulls/1")).toStrictEqual("https://api.github.com"); + }); + + test("check infer codeberg api", ()=> { + expect(inferGitApiUrl("https://codeberg.org/lampajr/backporting-example/pulls/1", "v1")).toStrictEqual("https://codeberg.org/api/v1"); + }); + + test("check infer codeberg api", ()=> { + expect(inferGitApiUrl("https://codeberg.org/lampajr/backporting-example/pulls/1", undefined)).toStrictEqual("https://codeberg.org/api/v4"); + }); + test("check infer github client", ()=> { expect(inferGitClient("https://github.com/superuser/backporting-example/pull/4")).toStrictEqual(GitClientType.GITHUB); }); @@ -39,7 +51,7 @@ describe("check git utilities", () => { expect(inferGitClient("https://api.github.com/repos/owner/repo/pulls/1")).toStrictEqual(GitClientType.GITHUB); }); - test("check infer github api from github api url", ()=> { - expect(inferGitApiUrl("https://api.github.com/repos/owner/repo/pulls/1")).toStrictEqual("https://api.github.com"); + test("check infer codeberg client", ()=> { + expect(inferGitClient("https://codeberg.org/lampajr/backporting-example/pulls/1")).toStrictEqual(GitClientType.CODEBERG); }); }); \ No newline at end of file