Skip to content

Commit

Permalink
Perf Tests: Refactor use of ENV and git in test setup
Browse files Browse the repository at this point in the history
In this patch we're refactoring how the performance tests are
configured, relying on the ENV values set by Github Actions and adding
new semantic terms for existing implicit conditions.

Additionally, the custom `git` abstraction has been removed and
replaced with `SimpleGit` for a more concrete represetnation of
what is being performed during the test setup. This change will
enable more complicated uses of `git` for optimizing the run script,
and by moving out of the abstraction it will minimize any changes
based on that kind of `git` usage.

There should be no functional changes in this patch and only the
code surrounding test setup should change. The term "branch" is
replaced in a few places with "ref" since this script is designed
to accept any `git` ref pointing to commits, and in fact has been
in daily use with a mix of tags, commit SHAs, and branch names.
  • Loading branch information
dmsnell committed Nov 10, 2022
1 parent 3086d49 commit e112e26
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 343 deletions.
60 changes: 6 additions & 54 deletions bin/plugin/commands/common.js
Original file line number Diff line number Diff line change
@@ -1,72 +1,26 @@
/**
* External dependencies
*/
const fs = require( 'fs' );
const rimraf = require( 'rimraf' );
const semver = require( 'semver' );
const SimpleGit = require( 'simple-git' );

/**
* Internal dependencies
*/
const { log, formats } = require( '../lib/logger' );
const { runStep, readJSONFile } = require( '../lib/utils' );
const git = require( '../lib/git' );
const config = require( '../config' );

/**
* Clone the repository and returns the working directory.
*
* @param {string} abortMessage Abort message.
*
* @return {Promise<string>} Repository local path.
*/
async function runGitRepositoryCloneStep( abortMessage ) {
// Cloning the repository.
let gitWorkingDirectoryPath;
await runStep( 'Cloning the Git repository', abortMessage, async () => {
log( '>> Cloning the Git repository' );
gitWorkingDirectoryPath = await git.clone( config.gitRepositoryURL );
log(
'>> The Git repository has been successfully cloned in the following temporary folder: ' +
formats.success( gitWorkingDirectoryPath )
);
} );

return gitWorkingDirectoryPath;
}

/**
* Clean the working directories.
*
* @param {string[]} folders Folders to clean.
* @param {string} abortMessage Abort message.
*/
async function runCleanLocalFoldersStep( folders, abortMessage ) {
await runStep( 'Cleaning the temporary folders', abortMessage, async () => {
await Promise.all(
folders.map( async ( directoryPath ) => {
if ( fs.existsSync( directoryPath ) ) {
await rimraf( directoryPath, ( err ) => {
if ( err ) {
throw err;
}
} );
}
} )
);
} );
}
const { readJSONFile } = require( '../lib/utils' );

/**
* Finds the name of the current plugin release branch based on the version in
* the package.json file.
* the package.json file and the latest `trunk` branch in `git`.
*
* @param {string} gitWorkingDirectoryPath Path to the project's working directory.
*
* @return {string} Name of the plugin release branch.
*/
async function findPluginReleaseBranchName( gitWorkingDirectoryPath ) {
await git.checkoutRemoteBranch( gitWorkingDirectoryPath, 'trunk' );
await SimpleGit( gitWorkingDirectoryPath )
.fetch( 'origin', 'trunk' )
.checkout( 'trunk' );

const packageJsonPath = gitWorkingDirectoryPath + '/package.json';
const mainPackageJson = readJSONFile( packageJsonPath );
Expand Down Expand Up @@ -141,6 +95,4 @@ function calculateVersionBumpFromChangelog(
module.exports = {
calculateVersionBumpFromChangelog,
findPluginReleaseBranchName,
runGitRepositoryCloneStep,
runCleanLocalFoldersStep,
};
148 changes: 104 additions & 44 deletions bin/plugin/commands/packages.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,24 @@ const path = require( 'path' );
const glob = require( 'fast-glob' );
const fs = require( 'fs' );
const { inc: semverInc } = require( 'semver' );
const rimraf = require( 'rimraf' );
const readline = require( 'readline' );
const SimpleGit = require( 'simple-git' );

/**
* Internal dependencies
*/
const { log, formats } = require( '../lib/logger' );
const { askForConfirmation, runStep, readJSONFile } = require( '../lib/utils' );
const {
askForConfirmation,
runStep,
readJSONFile,
getRandomTemporaryPath,
} = require( '../lib/utils' );
const {
calculateVersionBumpFromChangelog,
findPluginReleaseBranchName,
runGitRepositoryCloneStep,
runCleanLocalFoldersStep,
} = require( './common' );
const git = require( '../lib/git' );
const { join } = require( 'path' );

/**
Expand Down Expand Up @@ -55,6 +59,17 @@ const { join } = require( 'path' );
* @property {ReleaseType} releaseType The selected release type.
*/

/**
* Throws if given an error in the node.js callback style.
*
* @param {any|null} error If callback failed, this will hold a value.
*/
const rethrow = ( error ) => {
if ( error ) {
throw error;
}
};

/**
* Checks out the npm release branch.
*
Expand All @@ -64,9 +79,28 @@ async function checkoutNpmReleaseBranch( {
gitWorkingDirectoryPath,
npmReleaseBranch,
} ) {
// Creating the release branch.
await git.checkoutRemoteBranch( gitWorkingDirectoryPath, npmReleaseBranch );
await git.fetch( gitWorkingDirectoryPath, [ '--depth=100' ] );
/*
* Create the release branch.
*
* Note that we are grabbing an arbitrary depth of commits
* during the fetch. When `lerna` attempts to determine if
* a package needs an update, it looks at `git` history,
* and if we have pruned that history it will pre-emptively
* publish when it doesn't need to.
*
* We could set a different arbitrary depth if this isn't
* long enough or if it's excessive. We could also try and
* find a way to more specifically fetch what we expect to
* change. For example, if we knew we'll be performing
* updates every two weeks, we might be conservative and
* use `--shallow-since=4.weeks.ago`.
*
* At the time of writing, a depth of 100 pulls in all
* `trunk` commits from within the past week.
*/
await SimpleGit( gitWorkingDirectoryPath )
.fetch( npmReleaseBranch, [ '--depth=100' ] )
.checkout( npmReleaseBranch );
log(
'>> The local npm release branch ' +
formats.success( npmReleaseBranch ) +
Expand Down Expand Up @@ -105,13 +139,19 @@ async function runNpmReleaseBranchSyncStep( pluginReleaseBranch, config ) {
`>> Syncing the latest plugin release to "${ pluginReleaseBranch }".`
);

await git.replaceContentFromRemoteBranch(
gitWorkingDirectoryPath,
pluginReleaseBranch
);
const repo = SimpleGit( gitWorkingDirectoryPath );

/*
* Replace content from remote branch.
*
* @TODO: What is our goal here? Could `git reset --hard origin/${pluginReleaseBranch}` work?
* Why are we manually removing and then adding files back in?
*/
await repo
.raw( 'rm', '-r', '.' )
.raw( 'checkout', `origin/${ pluginReleaseBranch }`, '--', '.' );

const commitHash = await git.commit(
gitWorkingDirectoryPath,
const { commit: commitHash } = await repo.commit(
`Merge changes published in the Gutenberg plugin "${ pluginReleaseBranch }" branch`
);

Expand Down Expand Up @@ -223,6 +263,7 @@ async function updatePackages( config ) {
'>> Recommended version bumps based on the changes detected in CHANGELOG files:'
);

// e.g. "2022-11-01T00:13:26.102Z" -> "2022-11-01"
const publishDate = new Date().toISOString().split( 'T' )[ 0 ];
await Promise.all(
packagesToUpdate.map(
Expand All @@ -234,11 +275,8 @@ async function updatePackages( config ) {
version,
} ) => {
// Update changelog.
const content = await fs.promises.readFile(
changelogPath,
'utf8'
);
await fs.promises.writeFile(
const content = fs.readFileSync( changelogPath, 'utf8' );
fs.writeFileSync(
changelogPath,
content.replace(
'## Unreleased',
Expand Down Expand Up @@ -280,11 +318,10 @@ async function updatePackages( config ) {
);
}

const commitHash = await git.commit(
gitWorkingDirectoryPath,
'Update changelog files',
[ './*' ]
);
const { commit: commitHash } = await SimpleGit( gitWorkingDirectoryPath )
.add( [ './*' ] )
.commit( 'Update changelog files' );

if ( commitHash ) {
await runPushGitChangesStep( config );
}
Expand Down Expand Up @@ -313,8 +350,8 @@ async function runPushGitChangesStep( {
abortMessage
);
}
await git.pushBranchToOrigin(
gitWorkingDirectoryPath,
await SimpleGit( gitWorkingDirectoryPath ).push(
'origin',
npmReleaseBranch
);
} );
Expand Down Expand Up @@ -345,9 +382,9 @@ async function publishPackagesToNpm( {
stdio: 'inherit',
} );

const beforeCommitHash = await git.getLastCommitHash(
const beforeCommitHash = await SimpleGit(
gitWorkingDirectoryPath
);
).revparse( [ '--short', 'HEAD' ] );

const yesFlag = interactive ? '' : '--yes';
const noVerifyAccessFlag = interactive ? '' : '--no-verify-access';
Expand Down Expand Up @@ -403,8 +440,8 @@ async function publishPackagesToNpm( {
);
}

const afterCommitHash = await git.getLastCommitHash(
gitWorkingDirectoryPath
const afterCommitHash = await SimpleGit( gitWorkingDirectoryPath ).revparse(
[ '--short', 'HEAD' ]
);
if ( afterCommitHash === beforeCommitHash ) {
return;
Expand Down Expand Up @@ -439,18 +476,23 @@ async function backportCommitsToBranch(

log( `>> Backporting commits to "${ branchName }".` );

await git.resetLocalBranchAgainstOrigin(
gitWorkingDirectoryPath,
branchName
);
const repo = SimpleGit( gitWorkingDirectoryPath );

/*
* Reset any local changes and replace them with the origin branch's copy.
*
* Perform an additional fetch to ensure that when we push our changes that
* it's very unlikely that new commits could have appeared at the origin
* HEAD between when we started running this script and now when we're
* pushing our changes back upstream.
*/
await repo.fetch().checkout( branchName ).pull( 'origin', branchName );

for ( const commitHash of commits ) {
await git.cherrypickCommitIntoBranch(
gitWorkingDirectoryPath,
branchName,
commitHash
);
await repo.raw( 'cherry-pick', commitHash );
}
await git.pushBranchToOrigin( gitWorkingDirectoryPath, branchName );

await repo.push( 'origin', branchName );

log( `>> Backporting successfully finished.` );
}
Expand Down Expand Up @@ -478,11 +520,20 @@ async function runPackagesRelease( config, customMessages ) {

const temporaryFolders = [];
if ( ! config.gitWorkingDirectoryPath ) {
// Cloning the Git repository.
config.gitWorkingDirectoryPath = await runGitRepositoryCloneStep(
config.abortMessage
const gitPath = getRandomTemporaryPath();
config.gitWorkingDirectoryPath = gitPath;
fs.mkdirSync( gitPath, { recursive: true } );
temporaryFolders.push( gitPath );

await runStep(
'Cloning the Git repository',
config.abortMessage,
async () => {
log( '>> Cloning the Git repository' );
await SimpleGit( gitPath ).clone( config.gitRepositoryURL );
log( ` >> successfully clone into: ${ gitPath }` );
}
);
temporaryFolders.push( config.gitWorkingDirectoryPath );
}

let pluginReleaseBranch;
Expand Down Expand Up @@ -518,7 +569,16 @@ async function runPackagesRelease( config, customMessages ) {
}
}

await runCleanLocalFoldersStep( temporaryFolders, 'Cleaning failed.' );
await runStep(
'Cleaning the temporary folders',
'Cleaning failed',
async () =>
await Promise.all(
temporaryFolders
.filter( ( tempDir ) => fs.existsSync( tempDir ) )
.map( ( tempDir ) => rimraf( tempDir, rethrow ) )
)
);

log(
'\n>> 🎉 WordPress packages are now published!\n\n',
Expand Down
Loading

0 comments on commit e112e26

Please sign in to comment.