Skip to content

Commit

Permalink
fix(execa): use await execa instead of execaSync since it produce…
Browse files Browse the repository at this point in the history
…s unexpected errors when spawning git commands - fixes #17

Co-authored-by: syphernl <syphernl@users.noreply.github.com>

Signed-off-by: kilianpaquier <kilian@kilianpaquier.com>
  • Loading branch information
kilianpaquier committed Oct 24, 2024
1 parent 8901c80 commit f42bfef
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 88 deletions.
4 changes: 2 additions & 2 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const verifyConditions = (globalConfig: BackmergeConfig, context: VerifyC
/**
* success is the function for semantic-release success lifecycle.
*
* It executes the backmerge to all appropriate branches as the release was successfull.
* It executes the backmerge to all appropriate branches as the release was successful.
*
* @param globalConfig the semantic-release-backmerge plugin configuration.
* @param context the semantic-release context.
Expand All @@ -40,7 +40,7 @@ export const success = async (globalConfig: BackmergeConfig, context: SuccessCon
const config = verifyConditions(globalConfig, context)

try {
const branches = getBranches(context, config)
const branches = await getBranches(context, config)
await executeBackmerge(context, config, branches)
} catch (error) {
if (error instanceof AggregateError || error instanceof SemanticReleaseError) {
Expand Down
2 changes: 1 addition & 1 deletion lib/auth-modificator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Platform } from "./models/config"
import { getConfigError } from "./error"

/**
* getUser returns the appropriate string user for the given input platform.
* getUser returns the appropriate string user for the given input platform.
* It's specifically used in AuthModificator function for the auth user token.
*
* @see https://github.com/semantic-release/semantic-release/blob/master/lib/get-git-auth-url.js#L64
Expand Down
55 changes: 33 additions & 22 deletions lib/backmerge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { authModificator } from "./auth-modificator"
import { template } from "lodash"

/**
* Context is a subinterface of semantic-release Context (specifically VerifyConditionContext)
* Context is a subinterface of semantic-release Context (specifically VerifyConditionContext).
*/
export interface Context {
branch: { name: string }
Expand All @@ -27,7 +27,8 @@ export interface Context {
}

/**
* getBranches returns the slice of branches that can be backmerged.
* getBranches returns the slice of branches that can be backmerged.
*
* To retrieve them, it takes into account their existence in remote repository, their presence in input targets,
* 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).
*
Expand All @@ -38,7 +39,7 @@ export interface Context {
*
* @returns the slice of branches where the context.branch.name must be backmerged into.
*/
export const getBranches = (context: Context, config: BackmergeConfig) => {
export const getBranches = async (context: Context, config: BackmergeConfig) => {
const releaseBranch = context.branch.name

const appropriates = config.targets.filter(branch => releaseBranch.match(branch.from))
Expand All @@ -51,12 +52,19 @@ export const getBranches = (context: Context, config: BackmergeConfig) => {
const url = parse(config.repositoryUrl)
const authRemote = authModificator(url, config.platform, config.token)

// ensure at any time and any moment that the fetch'ed remote url is the same as there
// https://github.com/semantic-release/git/blob/master/lib/prepare.js#L69
// it's to ensure that the commit done during @semantic-release/git is backmerged alongside the other commits
fetch(authRemote, context.cwd, context.env)
let branches: string[] = [] // eslint-disable-line no-useless-assignment
try {
// ensure at any time and any moment that the fetch'ed remote url is the same as there
// https://github.com/semantic-release/git/blob/master/lib/prepare.js#L69
// it's to ensure that the commit done during @semantic-release/git is backmerged alongside the other commits
await fetch(authRemote, context.cwd, context.env)

branches = await ls(config.repositoryUrl, context.cwd, context.env)
} catch (error) {
throw new SemanticReleaseError("Failed to fetch git remote or list all branches.", "ELSREMOTE", String(error))
}

const branches = ls(config.repositoryUrl, context.cwd, context.env).
const filteredBranches = branches.
// don't keep the released branch
filter(branch => releaseBranch !== branch).

Expand All @@ -82,24 +90,24 @@ export const getBranches = (context: Context, config: BackmergeConfig) => {

// don't merge into older versions
if (semver.lt(branchMaintenance, releaseMaintenance)) {
context.logger.log(`Not backmerging into '${branch}' since the semver version is before '${releaseBranch}'.`)
context.logger.log(`Not backmerging into '${branch}' since semver version is before '${releaseBranch}'.`)
return false
}

// don't merge minor versions into next majors versions
if (semver.gte(branchMaintenance, nextMajor!)) {
context.logger.log(`Not backmerging into '${branch}' since the semver major version is after '${releaseBranch}'.`)
context.logger.log(`Not backmerging into '${branch}' since semver major version is after '${releaseBranch}'.`)
return false
}
}
return true
})
if (branches.length === 0) {
if (filteredBranches.length === 0) {
context.logger.log("No configured target is present in remote origin, no backmerge to be done.")
return []
}
context.logger.log(`Retrieved following branches present in remote origin: '${JSON.stringify(branches)}'`)
return branches
context.logger.log(`Retrieved branches present in remote origin: '${JSON.stringify(filteredBranches)}'`)
return filteredBranches
}

/**
Expand All @@ -120,13 +128,17 @@ export const executeBackmerge = async (context: Context, config: BackmergeConfig
const url = parse(config.repositoryUrl)
const authRemote = authModificator(url, config.platform, config.token)

// ensure at any time and any moment that the fetch'ed remote url is the same as there
// https://github.com/semantic-release/git/blob/master/lib/prepare.js#L69
// it's to ensure that the commit done during @semantic-release/git is backmerged alongside the other commits
fetch(authRemote, context.cwd, context.env)
try {
// ensure at any time and any moment that the fetch'ed remote url is the same as there
// https://github.com/semantic-release/git/blob/master/lib/prepare.js#L69
// it's to ensure that the commit done during @semantic-release/git is backmerged alongside the other commits
await fetch(authRemote, context.cwd, context.env)

// checkout to ensure released branch is up to date with last fetch'ed remote url
checkout(releaseBranch, context.cwd, context.env)
// checkout to ensure released branch is up to date with last fetch'ed remote url
await checkout(releaseBranch, context.cwd, context.env)
} catch (error) {
throw new SemanticReleaseError(`Failed to fetch or checkout released branch '${releaseBranch}'.`, "ECHECKOUT", String(error))
}

const errors: SemanticReleaseError[] = []
for (const branch of branches) { // keep await in loop since git actions aren't thread safe
Expand All @@ -138,12 +150,11 @@ export const executeBackmerge = async (context: Context, config: BackmergeConfig
}

try {
merge(releaseBranch, branch, template(config.commit)(templateData), context.cwd, context.env)

await merge(releaseBranch, branch, template(config.commit)(templateData), context.cwd, context.env)
if (config.dryRun) {
context.logger.log(`Running with --dry-run, push to '${branch}' will not update remote state.`)
}
push(authRemote, branch, config.dryRun, context.cwd, context.env)
await push(authRemote, branch, config.dryRun, context.cwd, context.env)
} catch (error) {
context.logger.error(`Failed to backmerge '${releaseBranch}' into '${branch}', opening pull request.`, error)

Expand Down
34 changes: 18 additions & 16 deletions lib/git.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { execaSync } from "execa"
import { execa } from "execa"

/**
* ls returns the slice of all branches present in remote origin.
* ls returns the slice of all branches present in remote origin.
* It removes 'refs/heads/' from the branches name.
*
* @returns the slice of branches.
*
* @throws an error is the git ls-remote cannot be done.
* @throws an error if the git ls-remote cannot be done.
*/
export const ls = (remote: string, cwd?: string, env?: Record<string, string>) => {
const { stdout } = execaSync("git", ["ls-remote", "--heads", remote], { cwd, env })
export const ls = async (remote: string, cwd?: string, env?: Record<string, string>) => {
const { stdout } = await execa("git", ["ls-remote", "--heads", remote], { cwd, env })
const branches = stdout.
split("\n").
map(branch => branch.split("\t")).
Expand All @@ -20,15 +20,16 @@ export const ls = (remote: string, cwd?: string, env?: Record<string, string>) =
}

/**
* checkout executes a simple checkout of input branch.
* checkout executes a simple checkout of input branch.
*
* The checkout is strict with remote state, meaning all local changes are removed.
*
* @param branch the input branch to checkout.
*
* @throws an error if the checkout cannot be done.
*/
export const checkout = (branch: string, cwd?: string, env?: Record<string, string>) => {
execaSync("git", ["checkout", "-B", branch], { cwd, env })
export const checkout = async (branch: string, cwd?: string, env?: Record<string, string>) => {

Check warning on line 31 in lib/git.ts

View check run for this annotation

Codecov / codecov/patch

lib/git.ts#L31

Added line #L31 was not covered by tests
await execa("git", ["checkout", "-B", branch], { cwd, env })
}

/**
Expand All @@ -38,12 +39,13 @@ export const checkout = (branch: string, cwd?: string, env?: Record<string, stri
*
* @throws an error if the fetch cannot be done.
*/
export const fetch = (remote: string, cwd?: string, env?: Record<string, string>) => {
execaSync("git", ["fetch", remote], { cwd, env })
export const fetch = async (remote: string, cwd?: string, env?: Record<string, string>) => {

Check warning on line 42 in lib/git.ts

View check run for this annotation

Codecov / codecov/patch

lib/git.ts#L42

Added line #L42 was not covered by tests
await execa("git", ["fetch", remote], { cwd, env })
}

/**
* merge executes a checkout of input 'to' branch, and merges input 'from' branch into 'to'.
*
* If a merge commit must be done (by default --ff is used), then the merge commit is the input commit.
*
* @param from the branch to merge into 'to'.
Expand All @@ -52,13 +54,13 @@ export const fetch = (remote: string, cwd?: string, env?: Record<string, string>
*
* @throws an error if the merge fails (in case of conflicts, etc.).
*/
export const merge = (from: string, to: string, commit: string, cwd?: string, env?: Record<string, string>) => {
checkout(to)
export const merge = async (from: string, to: string, commit: string, cwd?: string, env?: Record<string, string>) => {
await checkout(to)

Check warning on line 58 in lib/git.ts

View check run for this annotation

Codecov / codecov/patch

lib/git.ts#L57-L58

Added lines #L57 - L58 were not covered by tests

try {
execaSync("git", ["merge", `${from}`, "--ff", "-m", commit], { cwd, env })
await execa("git", ["merge", `${from}`, "--ff", "-m", commit], { cwd, env })

Check warning on line 61 in lib/git.ts

View check run for this annotation

Codecov / codecov/patch

lib/git.ts#L61

Added line #L61 was not covered by tests
} catch (error) {
execaSync("git", ["merge", "--abort"], { cwd, env })
await execa("git", ["merge", "--abort"], { cwd, env })

Check warning on line 63 in lib/git.ts

View check run for this annotation

Codecov / codecov/patch

lib/git.ts#L63

Added line #L63 was not covered by tests
throw error
}
}
Expand All @@ -71,10 +73,10 @@ export const merge = (from: string, to: string, commit: string, cwd?: string, en
*
* @throws an error if the push cannot be executed.
*/
export const push = (remote: string, branch: string, dryRun?: boolean, cwd?: string, env?: Record<string, string>) => {
export const push = async (remote: string, branch: string, dryRun?: boolean, cwd?: string, env?: Record<string, string>) => {

Check warning on line 76 in lib/git.ts

View check run for this annotation

Codecov / codecov/patch

lib/git.ts#L76

Added line #L76 was not covered by tests
const args = ["push", remote, `HEAD:${branch}`]
if (dryRun) {
args.push("--dry-run")
}
execaSync("git", args, { cwd, env })
await execa("git", args, { cwd, env })
}
Loading

0 comments on commit f42bfef

Please sign in to comment.