Skip to content

Commit

Permalink
feat: use version from PR title when labelled as 'autorelease: user e…
Browse files Browse the repository at this point in the history
…dited'

That makes it possible for an end user to control the version number number
without having to rely on a commit with the footnote RELEASE-AS.

The label can be easily added by a GitHub Action like this one:

```yaml
name: Handle release PR edits
on:
  pull_request:
    types:
      - edited

jobs:
  update_pr_content:
    name: Update pull request content
    if: |
      github.event.pull_request.state == 'open' &&
      github.event.changes.title.from != github.event.pull_request.title &&
      github.event.sender.login != 'stainless-bot' # this is important to avoid infinite loops
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - uses: actions/github-script@v6
        with:
          script: |
            github.rest.issues.addLabels({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              labels: ['autorelease: user edited'] # add the label
            })

      - name: Then here run release-please
      - run: ...
```
  • Loading branch information
dgellow committed Sep 4, 2023
1 parent 7d77f3f commit 8260dc8
Show file tree
Hide file tree
Showing 10 changed files with 140 additions and 102 deletions.
4 changes: 2 additions & 2 deletions src/bin/release-please.ts
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ const createReleasePullRequestCommand: yargs.CommandModule<
}
if (argv.dryRun) {
const pullRequests = await manifest.buildPullRequests();
const pullRequests = await manifest.buildPullRequests([], []);
logger.debug(`Would open ${pullRequests.length} pull requests`);
logger.debug('fork:', manifest.fork);
logger.debug('changes branch:', manifest.changesBranch);
Expand Down Expand Up @@ -632,7 +632,7 @@ const createManifestPullRequestCommand: yargs.CommandModule<
);
if (argv.dryRun) {
const pullRequests = await manifest.buildPullRequests();
const pullRequests = await manifest.buildPullRequests([], []);
logger.debug(`Would open ${pullRequests.length} pull requests`);
logger.debug('fork:', manifest.fork);
for (const pullRequest of pullRequests) {
Expand Down
3 changes: 2 additions & 1 deletion src/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1495,6 +1495,7 @@ export class GitHub {
release: Release,
options: ReleaseOptions = {}
): Promise<GitHubRelease> => {
this.logger.debug({tag: release.tag});
const resp = await this.octokit.repos.createRelease({
name: release.name,
owner: this.repository.owner,
Expand Down Expand Up @@ -1751,7 +1752,7 @@ export class GitHub {
branchName: string,
branchSha: string
): Promise<string> {
this.logger.debug(`Creating new branch: '${branchName}' at $'{branchSha}'`);
this.logger.debug(`Creating new branch: '${branchName}' at '${branchSha}'`);
const {
data: {
object: {sha},
Expand Down
47 changes: 39 additions & 8 deletions src/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ export const DEFAULT_RELEASE_LABELS = ['autorelease: tagged'];
export const DEFAULT_SNAPSHOT_LABELS = ['autorelease: snapshot'];
export const SNOOZE_LABEL = 'autorelease: snooze';
export const DEFAULT_PRERELEASE_LABELS = ['autorelease: pre-release'];
export const DEFAULT_USEREDITED_LABEL = 'autorelease: user edited';
const DEFAULT_RELEASE_SEARCH_DEPTH = 400;
const DEFAULT_COMMIT_SEARCH_DEPTH = 500;

Expand Down Expand Up @@ -505,7 +506,10 @@ export class Manifest {
*
* @returns {ReleasePullRequest[]} The candidate pull requests to open or update.
*/
async buildPullRequests(): Promise<ReleasePullRequest[]> {
async buildPullRequests(
openPullRequests: PullRequest[],
snoozedPullRequests: PullRequest[]
): Promise<ReleasePullRequest[]> {
this.logger.info('Building pull requests');
const pathsByComponent = await this.getPathsByComponent();
const strategiesByPath = await this.getStrategiesByPath();
Expand Down Expand Up @@ -733,11 +737,18 @@ export class Manifest {

const strategy = strategies[path];
const latestRelease = releasesByPath[path];

const branchName = (await strategy.getBranchName()).toString();
const existingPR =
openPullRequests.find(pr => pr.headBranchName === branchName) ||
snoozedPullRequests.find(pr => pr.headBranchName === branchName);

const releasePullRequest = await strategy.buildReleasePullRequest(
pathCommits,
latestRelease,
config.draftPullRequest ?? this.draftPullRequest,
this.labels
this.labels,
existingPR
);
this.logger.debug(`path: ${path}`);
this.logger.debug(
Expand Down Expand Up @@ -840,11 +851,6 @@ export class Manifest {
// register all possible labels to make them available to users through GitHub label dropdown
await this.registerLabels();

const candidatePullRequests = await this.buildPullRequests();
if (candidatePullRequests.length === 0) {
return [];
}

// if there are any merged, pending release pull requests, don't open
// any new release PRs
const mergedPullRequestsGenerator = this.findMergedReleasePullRequests();
Expand All @@ -859,6 +865,15 @@ export class Manifest {
const openPullRequests = await this.findOpenReleasePullRequests();
const snoozedPullRequests = await this.findSnoozedReleasePullRequests();

// prepare releases pull requests
const candidatePullRequests = await this.buildPullRequests(
openPullRequests,
snoozedPullRequests
);
if (candidatePullRequests.length === 0) {
return [];
}

if (this.sequentialCalls) {
const pullRequests: PullRequest[] = [];
for (const pullRequest of candidatePullRequests) {
Expand Down Expand Up @@ -1019,13 +1034,23 @@ export class Manifest {
existing: PullRequest,
pullRequest: ReleasePullRequest
): Promise<PullRequest | undefined> {
const userEditedExistingPR = existing.labels.find(
label => label === DEFAULT_USEREDITED_LABEL
);

this.logger.debug({userEditedExistingPR, existing});

// If unchanged, no need to push updates
if (existing.body === pullRequest.body.toString()) {
if (
!userEditedExistingPR &&
existing.body === pullRequest.body.toString()
) {
this.logger.info(
`PR https://github.com/${this.repository.owner}/${this.repository.repo}/pull/${existing.number} remained the same`
);
return undefined;
}

const updatedPullRequest = await this.github.updatePullRequest(
existing.number,
pullRequest,
Expand All @@ -1037,6 +1062,12 @@ export class Manifest {
pullRequestOverflowHandler: this.pullRequestOverflowHandler,
}
);

await this.github.removeIssueLabels(
[DEFAULT_USEREDITED_LABEL],
existing.number
);

return updatedPullRequest;
}

Expand Down
2 changes: 1 addition & 1 deletion src/release-pull-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {PullRequestTitle} from './util/pull-request-title';
export interface ReleasePullRequest {
readonly title: PullRequestTitle;
readonly body: PullRequestBody;
readonly labels: string[];
labels: string[];
readonly headRefName: string;
readonly version?: Version;
readonly draft: boolean;
Expand Down
97 changes: 49 additions & 48 deletions src/strategies/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,17 @@ export abstract class BaseStrategy implements Strategy {
this.initialVersion = options.initialVersion;
this.extraLabels = options.extraLabels || [];
}
async getBranchName(): Promise<BranchName> {
const branchComponent = await this.getBranchComponent();
const branchName = branchComponent
? BranchName.ofComponentTargetBranch(
branchComponent,
this.targetBranch,
this.changesBranch
)
: BranchName.ofTargetBranch(this.targetBranch, this.changesBranch);
return branchName;
}

/**
* Specify the files necessary to update in a release pull request.
Expand Down Expand Up @@ -258,7 +269,8 @@ export abstract class BaseStrategy implements Strategy {
commits: ConventionalCommit[],
latestRelease?: Release,
draft?: boolean,
labels: string[] = []
labels: string[] = [],
existingPullRequest?: PullRequest
): Promise<ReleasePullRequest | undefined> {
const conventionalCommits = await this.postProcessCommits(commits);
this.logger.info(`Considering: ${conventionalCommits.length} commits`);
Expand All @@ -267,10 +279,41 @@ export abstract class BaseStrategy implements Strategy {
return undefined;
}

const newVersion = await this.buildNewVersion(
conventionalCommits,
latestRelease
const releaseAsCommit = conventionalCommits.find(conventionalCommit =>
conventionalCommit.notes.find(note => note.title === 'RELEASE AS')
);
const releaseAsNote = releaseAsCommit?.notes.find(
note => note.title === 'RELEASE AS'
);

const existingPRTitleVersion =
existingPullRequest &&
PullRequestTitle.parse(existingPullRequest.title)?.getVersion();
const userEditedExistingPR =
existingPullRequest &&
existingPullRequest.labels.find(
label => label === 'autorelease: user edited'
);

let newVersion: Version;
if (existingPRTitleVersion && userEditedExistingPR) {
newVersion = existingPRTitleVersion;
} else if (this.releaseAs) {
this.logger.warn(
`Setting version for ${this.path} from release-as configuration`
);
newVersion = Version.parse(this.releaseAs);
} else if (releaseAsNote) {
newVersion = Version.parse(releaseAsNote.text);
} else if (latestRelease) {
newVersion = await this.versioningStrategy.bump(
latestRelease.tag.version,
conventionalCommits
);
} else {
newVersion = this.initialReleaseVersion();
}

const versionsMap = await this.updateVersionsMap(
await this.buildVersionsMap(conventionalCommits),
conventionalCommits,
Expand Down Expand Up @@ -298,15 +341,6 @@ export abstract class BaseStrategy implements Strategy {
this.pullRequestTitlePattern
);

const branchComponent = await this.getBranchComponent();
const branchName = branchComponent
? BranchName.ofComponentTargetBranch(
branchComponent,
this.targetBranch,
this.changesBranch
)
: BranchName.ofTargetBranch(this.targetBranch, this.changesBranch);

const releaseNotesBody = await this.buildReleaseNotes(
conventionalCommits,
newVersion,
Expand Down Expand Up @@ -346,7 +380,7 @@ export abstract class BaseStrategy implements Strategy {
body: pullRequestBody,
updates: updatesWithExtras,
labels: [...labels, ...this.extraLabels],
headRefName: branchName.toString(),
headRefName: (await this.getBranchName()).toString(),
version: newVersion,
draft: draft ?? false,
};
Expand Down Expand Up @@ -463,45 +497,12 @@ export abstract class BaseStrategy implements Strategy {
for (const [component, version] of versionsMap.entries()) {
versionsMap.set(
component,
await this.versioningStrategy.bump(version, conventionalCommits)
this.versioningStrategy.bump(version, conventionalCommits)
);
}
return versionsMap;
}

protected async buildNewVersion(
conventionalCommits: ConventionalCommit[],
latestRelease?: Release
): Promise<Version> {
if (this.releaseAs) {
this.logger.warn(
`Setting version for ${this.path} from release-as configuration`
);
return Version.parse(this.releaseAs);
}

const releaseAsCommit = conventionalCommits.find(conventionalCommit =>
conventionalCommit.notes.find(note => note.title === 'RELEASE AS')
);
if (releaseAsCommit) {
const note = releaseAsCommit.notes.find(
note => note.title === 'RELEASE AS'
);
if (note) {
return Version.parse(note.text);
}
}

if (latestRelease) {
return await this.versioningStrategy.bump(
latestRelease.tag.version,
conventionalCommits
);
}

return this.initialReleaseVersion();
}

protected async buildVersionsMap(
_conventionalCommits: ConventionalCommit[]
): Promise<VersionsMap> {
Expand Down
13 changes: 9 additions & 4 deletions src/strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {Commit} from './commit';
import {VersioningStrategy} from './versioning-strategy';
import {ChangelogNotes} from './changelog-notes';
import {Version} from './version';
import {BranchName} from './util/branch-name';

export interface BuildReleaseOptions {
groupPullRequestTitlePattern?: string;
Expand All @@ -39,15 +40,17 @@ export interface Strategy {
* component if available.
* @param {boolean} draft Optional. Whether or not to create the pull
* request as a draft. Defaults to `false`.
* @returns {ReleasePullRequest | undefined} The release pull request to
* open for this path/component. Returns undefined if we should not
* open a pull request.
* @param {string[]} labels Optional.
* @param {PullRequest} existingPullRequest Optional. A pull request already existing for this branch.
* @returns {ReleasePullRequest | undefined} The release pull request to open for this path/component. Returns
* undefined if we should not open a pull request.
*/
buildReleasePullRequest(
commits: Commit[],
latestRelease?: Release,
draft?: boolean,
labels?: string[]
labels?: string[],
existingPullRequest?: PullRequest
): Promise<ReleasePullRequest | undefined>;

/**
Expand Down Expand Up @@ -84,6 +87,8 @@ export interface Strategy {
*/
getBranchComponent(): Promise<string | undefined>;

getBranchName(): Promise<BranchName>;

/**
* Validate whether version is a valid release.
* @param version Released version.
Expand Down
Loading

0 comments on commit 8260dc8

Please sign in to comment.