Skip to content

Commit

Permalink
refactor(backmerge): try endlessly to retrieve the missing commit fro…
Browse files Browse the repository at this point in the history
…m @semantic-release/git
  • Loading branch information
kilianpaquier committed May 4, 2024
1 parent b594c41 commit f5e8a35
Show file tree
Hide file tree
Showing 11 changed files with 162 additions and 90 deletions.
Binary file modified bun.lockb
Binary file not shown.
33 changes: 7 additions & 26 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
import { SuccessContext, VerifyConditionsContext } from 'semantic-release'
import { ensureDefault, verifyConfig } from "./lib/verify-config"
import { executeBackmerge, getBranches } from "./lib/backmerge"
import parse, { GitUrl } from "git-url-parse"

import SemanticReleaseError from "@semantic-release/error"

import { BackmergeConfig } from "./lib/models/config"

/**
* Config represents an extension of BackmergeConfig with additional fields not to validate
* and given as input by semantic-release directly.
*/
interface Config extends BackmergeConfig {
repositoryUrl: string
}

/**
* verifyConditions is the exported function for semantic-release for verifyConditions lifecycle.
* It verifies the input plugin configuration and throws an error if it's not valid.
Expand All @@ -24,35 +15,25 @@ interface Config extends BackmergeConfig {
*
* @returns both the parsed repository url as GitUrl and validated configuration.
*/
export const verifyConditions = (globalConfig: Config, context: VerifyConditionsContext): [Config, parse.GitUrl] => {
const parseURL = (): GitUrl => {
try {
return parse(globalConfig.repositoryUrl)
} catch (error) {
throw new SemanticReleaseError("Failed to parse repository url", "EINVALIDREPOSITORYURL", String(error))
}
}
const info = parseURL()

export const verifyConditions = (globalConfig: BackmergeConfig, context: VerifyConditionsContext) => {
const config = ensureDefault(globalConfig, context.env)
verifyConfig(config)

return [{ ...config, repositoryUrl: globalConfig.repositoryUrl }, info]
return config
}

/**
* success is the function for semantic-release success lifecycle.
* It executes the backmerge to all appropriate branches as the release was successfull.
*
* @param globalConfig the semantic-release-backmerge plugin configuration (revalidated).
* @param globalConfig the semantic-release-backmerge plugin configuration.
* @param context the semantic-release context.
*/
export const success = async (globalConfig: Config, context: SuccessContext) => {
const [config, info] = verifyConditions(globalConfig, context)
export const success = async (globalConfig: BackmergeConfig, context: SuccessContext) => {
const config = verifyConditions(globalConfig, context)

try {
const branches = await getBranches(context, config.repositoryUrl, config.targets)
await executeBackmerge(context, config, info, branches)
const branches = await getBranches(context, config)
await executeBackmerge(context, config, branches)
} catch (error) {
throw new SemanticReleaseError("Failed to backmerge branches.", "EBACKMERGE", String(error))
}
Expand Down
32 changes: 17 additions & 15 deletions lib/backmerge.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { BackmergeConfig, Target } from "./models/config"
import { BackmergeConfig } from "./models/config"

import AggregateError from "aggregate-error"
import SemanticReleaseError from "@semantic-release/error"
import parse from "git-url-parse"
import semver from "semver"

import { Git } from "./git"
import { GitUrl } from "git-url-parse"
import { authModificator } from "./auth-modificator"
import { createPR } from "./pull-request"
import { template } from "lodash"
Expand All @@ -29,17 +29,16 @@ export interface Context {
* and their semver version value related to the appropriate target (for instance, a branch v1.0 won't be returned if target.from is v1.1).
*
* @param context with logger, released branch, current directory and environment.
* @param remote to fetch all the branches from.
* @param targets the configuration of which branches are to be backmerged into the others.
* @param config the semantic-release-backmerge plugin configuration.
*
* @throws an error in case the input remote can't be fetched or the branches can be retrieved with git.
*
* @returns the slice of branches where the context.branch.name must be backmerged into.
*/
export const getBranches = async (context: Context, remote: string, targets: Target[]) => {
export const getBranches = async (context: Context, config: BackmergeConfig) => {
const releaseBranch = context.branch.name

const appropriates = targets.filter(branch => releaseBranch.match(branch.from))
const appropriates = config.targets.filter(branch => releaseBranch.match(branch.from))
if (appropriates.length === 0) {
context.logger.log(`Current branch '${releaseBranch}' doesn't match any configured backmerge targets.`)
return []
Expand All @@ -48,9 +47,9 @@ export const getBranches = async (context: Context, remote: string, targets: Tar

const git = new Git(context.cwd, context.env)
await git.fetchAllRemotes()
await git.fetch(remote)
await git.fetch(config.repositoryUrl)

const branches = (await git.ls(remote)).
const branches = (await git.ls(config.repositoryUrl)).
// don't keep the released branch
filter(branch => releaseBranch !== branch).

Expand Down Expand Up @@ -103,17 +102,20 @@ export const getBranches = async (context: Context, remote: string, targets: Tar
* If a merge fails, it tries to create a pull request.
*
* @param context input context with the logger, released branch, etc.
* @param remote url to fetch and push merged branches.
* @param config the semantic-release-backmerge plugin configuration.
* @param branches slice of branches to be backmerged with released branch commits.
*
* @throws AggregateError of SemanticReleaseError(s) for each branch that couldn't be backmerged.
*/
export const executeBackmerge = async (context: Context, config: BackmergeConfig, info: GitUrl, branches: string[]) => {
export const executeBackmerge = async (context: Context, config: BackmergeConfig, branches: string[]) => {
const releaseBranch = context.branch.name
const git = new Git(context.cwd, context.env)
const remote = authModificator(info, config.platform, config.token)

await git.fetch(remote)
const url = parse(config.repositoryUrl)
const authRemote = authModificator(url, config.platform, config.token)

const git = new Git(context.cwd, context.env)
// await git.fetchAllRemotes()
await git.fetch(config.repositoryUrl)
await git.checkout(releaseBranch)

const commit = template(config.commit)
Expand All @@ -126,7 +128,7 @@ export const executeBackmerge = async (context: Context, config: BackmergeConfig
if (config.dryRun) {
context.logger.log(`Running with --dry-run, push to '${branch}' will not update remote state.`)
}
await git.push(remote, branch, config.dryRun)
await git.push(authRemote, branch, config.dryRun)
} catch (error) {
context.logger.error(`Failed to backmerge '${releaseBranch}' into '${branch}', opening pull request.`, error)

Expand All @@ -136,7 +138,7 @@ export const executeBackmerge = async (context: Context, config: BackmergeConfig
}

try {
await createPR(config, info, releaseBranch, branch)
await createPR(config, url, releaseBranch, branch)
} catch (prError) {
errors.push(new SemanticReleaseError(`Failed to create pull request from '${releaseBranch}' to '${branch}'.`, "EPULLREQUEST", String(prError)))
}
Expand Down
4 changes: 4 additions & 0 deletions lib/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ const configErrors: { [k in keyof BackmergeConfig]: (value?: any) => ConfigError
details: `[Platform](${linkify("shared-configuration")}) must be one of 'bitbucket', 'bitbucket-cloud', 'gitea', 'github', 'gitlab'. Provided value is ${JSON.stringify(value)}.`,
message: `Invalid 'platform' configuration.`,
}),
// shouldn't happen since it comes from semantic-release config
repositoryUrl: () => ({
message: "Invalid 'repositoryUrl' configuration (coming from semantic-release options).",
}),
targets: (value: any) => ({
details: `[Targets](${linkify("shared-configuration")}) must be a valid array of targets ({ from: "...", to: "..." }). Provided value is ${JSON.stringify(value)}.`,
message: `Invalid 'targets' configuration.`,
Expand Down
6 changes: 1 addition & 5 deletions lib/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,7 @@ export class Git {
* @throws an error if the checkout cannot be done.
*/
public async checkout(branch: string) {
try {
await this.exec(["checkout", branch])
} catch (error) {
await this.exec(["checkout", "-b", branch])
}
await this.exec(["checkout", "-B", branch])
}

/**
Expand Down
1 change: 1 addition & 0 deletions lib/models/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface BackmergeConfig {
debug: boolean // comes from semantic-release config
dryRun: boolean // comes from semantic-release config
platform: Platform
repositoryUrl: string // comes from semantic-release config
targets: Target[]
title: string
token: string
Expand Down
2 changes: 2 additions & 0 deletions lib/verify-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export const ensureDefault = (config: Partial<BackmergeConfig>, env?: Record<str
debug: config.debug ?? false, // shouldn't happen since it comes from semantic-release config
dryRun: config.dryRun ?? false, // shouldn't happen since it comes from semantic-release config
platform: config.platform ?? platform,
repositoryUrl: config.repositoryUrl ?? "",
targets: config.targets ?? [],
title: config.title ?? defaultTitle,
// checking all environment variables since it doesn't matter which is valued whatever the platform could be
Expand All @@ -112,6 +113,7 @@ export const verifyConfig = (config: BackmergeConfig) => {
debug: [isBoolean], // shouldn't happen since it comes from semantic-release config
dryRun: [isBoolean], // shouldn't happen since it comes from semantic-release config
platform: [isString, validatePlatform],
repositoryUrl: [isString, stringNotEmpty], // shouldn't happen since it comes from semantic-release config
targets: [isArray, validateTargets],
title: [isString, stringNotEmpty],
token: [isString, stringNotEmpty]
Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,19 @@
],
"scripts": {
"build": "bun build ./index.ts --outdir dist --target node",
"clean": "git clean -Xf ./*",
"lint": "eslint",
"test": "bun test --coverage"
},
"devDependencies": {
"@eslint/js": "^9.1.1",
"@eslint/js": "^9.2.0",
"@types/bun": "latest",
"@types/eslint__js": "^8.42.3",
"@types/git-url-parse": "^9.0.3",
"@types/lodash": "^4.17.0",
"@types/lodash": "^4.17.1",
"@types/semantic-release__error": "^3.0.3",
"@types/semver": "^7.5.8",
"eslint": "^9.1.1",
"eslint": "^9.2.0",
"globals": "^15.1.0",
"npm-check-updates": "^16.14.20",
"typescript-eslint": "^7.8.0"
Expand All @@ -50,7 +51,7 @@
"git-url-parse": "^14.0.0",
"lodash": "^4.17.21",
"node-fetch": "^3.3.2",
"octokit": "^3.2.0",
"octokit": "^3.2.1",
"parse-url": "^9.2.0",
"semantic-release": "^23.0.8",
"semver": "^7.6.0"
Expand Down
Loading

0 comments on commit f5e8a35

Please sign in to comment.