Skip to content

Commit

Permalink
feat: support custom pull request title (googleapis#784)
Browse files Browse the repository at this point in the history
  • Loading branch information
yi-Xu-0100 committed Feb 24, 2021
1 parent 24d5b8b commit d34e069
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 28 deletions.
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
[![codecov](https://img.shields.io/codecov/c/github/googleapis/release-please/master.svg?style=flat)](https://codecov.io/gh/googleapis/release-please)

Release Please automates CHANGELOG generation, the creation of GitHub releases,
and version bumps for your projects.
and version bumps for your projects.

It does so by parsing your
git history, looking for [Conventional Commit messages](https://www.conventionalcommits.org/),
Expand All @@ -28,7 +28,7 @@ Release Please assumes you are using [Conventional Commit messages](https://www.

The most important prefixes you should have in mind are:

* `fix:` which represents bug fixes, and correlates to a [SemVer](https://semver.org/)
* `fix:` which represents bug fixes, and correlates to a [SemVer](https://semver.org/)
patch.
* `feat:` which represents a new feature, and correlates to a SemVer minor.
* `feat!:`, or `fix!:`, `refactor!:`, etc., which represent a breaking change
Expand All @@ -39,7 +39,7 @@ The most important prefixes you should have in mind are:
Release Please allows you to represent multiple changes in a single commit,
using footers:

```
```txt
feat: adds v4 UUID to crypto
This adds support for v4 UUIDs to the library.
Expand All @@ -63,18 +63,18 @@ The above commit message will contain:

## How do I change the version number?

When a commit to the main branch has `Release-As: x.x.x` in the **commit body**, Release Please will open a new pull request for the specified version.
When a commit to the main branch has `Release-As: x.x.x`(case insensitive) in the **commit body**, Release Please will open a new pull request for the specified version.

**Empty commit example:**

`git commit --allow-empty -m "chore: release 2.0.0" -m "Release-As: 2.0.0"` results in the following commit message:
```

```txt
chore: release 2.0.0
Release-As: 2.0.0
```


## Release types supported

Release Please automates releases for the following flavors of repositories:
Expand All @@ -88,6 +88,7 @@ Release Please automates releases for the following flavors of repositories:
| ocaml | [An OCaml repository, containing 1 or more opam or esy files and a CHANGELOG.md](https://github.com/grain-lang/binaryen.ml) |
| simple | [A repository with a version.txt and a CHANGELOG.md](https://github.com/googleapis/gapic-generator) |
| helm | A repository with a Chart.yaml and a CHANGELOG.md |

## Adding additional release types

To add a new release type, simply use the existing [releasers](https://github.com/googleapis/release-please/tree/master/src/releasers) and [updaters](https://github.com/googleapis/release-please/tree/master/src/updaters)
Expand Down Expand Up @@ -198,6 +199,7 @@ release-please release-pr --package-name=@google-cloud/firestore" \
| `--default-branch`| branch to open pull release PR against (detected by default). |
| `--path` | create a release from a path other than the repository's root |
| `--monorepo-tags` | add prefix to tags and branches, allowing multiple libraries to be released from the same repository. |
| `--pull-request-title-pattern` | add title pattern to make release PR, defaults to using `chore${scope}: release${component} ${version}`. |

### Creating a release on GitHub

Expand Down
4 changes: 4 additions & 0 deletions src/bin/release-please.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,10 @@ export const parser = yargs
type: 'boolean',
default: false,
})
.option('pull-request-title-pattern', {
describe: 'Title pattern to make release PR',
type: 'string',
})
.demandCommand(1)
.strict(true);

Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ interface ReleasePROptions {
// for Ruby: TODO refactor to find version.rb like Python finds version.py
// and then remove this property
versionFile?: string;
pullRequestTitlePattern?: string;
}

// GitHub Constructor options
Expand Down
15 changes: 12 additions & 3 deletions src/release-pr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export class ReleasePR {
lastPackageVersion?: string;
changelogSections?: ChangelogSection[];
changelogPath = 'CHANGELOG.md';
pullRequestTitlePattern?: string;

constructor(options: ReleasePRConstructorOptions) {
this.bumpMinorPreMajor = options.bumpMinorPreMajor || false;
Expand All @@ -100,6 +101,7 @@ export class ReleasePR {

this.changelogSections = options.changelogSections;
this.changelogPath = options.changelogPath ?? this.changelogPath;
this.pullRequestTitlePattern = options.pullRequestTitlePattern;
}

// A releaser can override this method to automatically detect the
Expand Down Expand Up @@ -258,15 +260,22 @@ export class ReleasePR {
): Promise<string> {
const packageName = await this.getPackageName();
const pullRequestTitle = includePackageName
? PullRequestTitle.ofComponentVersion(packageName.name, version)
: PullRequestTitle.ofVersion(version);
? PullRequestTitle.ofComponentVersion(
packageName.name,
version,
this.pullRequestTitlePattern
)
: PullRequestTitle.ofVersion(version, this.pullRequestTitlePattern);
return pullRequestTitle.toString();
}

// Override this method to detect the release version from code (if it cannot be
// inferred from the release PR head branch)
protected detectReleaseVersionFromTitle(title: string): string | undefined {
const pullRequestTitle = PullRequestTitle.parse(title);
const pullRequestTitle = PullRequestTitle.parse(
title,
this.pullRequestTitlePattern
);
if (pullRequestTitle) {
return pullRequestTitle.getVersion();
}
Expand Down
85 changes: 68 additions & 17 deletions src/util/pull-request-title.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,59 +16,106 @@
// at the script level are undefined, they are only defined inside function
// or instance methods/properties.

const DEFAULT_PATTERN = /^chore(\((?<branch>[\w-.]+)\))?: release ?(?<component>[\w-.]*)? v?(?<version>[0-9].*)$/;
const DEFAULT_PR_TITLE_PATTERN =
'chore${scope}: release${component} ${version}';
export function generateMatchPattern(pullRequestTitlePattern?: string): RegExp {
if (
pullRequestTitlePattern &&
pullRequestTitlePattern.search(/\$\{scope\}/) === -1
)
throw Error("pullRequestTitlePattern miss the part of '${scope}'");
if (
pullRequestTitlePattern &&
pullRequestTitlePattern.search(/\$\{component\}/) === -1
)
throw Error("pullRequestTitlePattern miss the part of '${component}'");
if (
pullRequestTitlePattern &&
pullRequestTitlePattern.search(/\$\{version\}/) === -1
)
throw Error("pullRequestTitlePattern miss the part of '${version}'");
return new RegExp(
`^${(pullRequestTitlePattern || DEFAULT_PR_TITLE_PATTERN)
.replace('${scope}', '(\\((?<branch>[\\w-.]+)\\))?')
.replace('${component}', ' ?(?<component>[\\w-.]*)?')
.replace('${version}', 'v?(?<version>[0-9].*)')}$`
);
}

export class PullRequestTitle {
component?: string;
targetBranch?: string;
version: string;
pullRequestTitlePattern: string;
matchPattern: RegExp;

private constructor(opts: {
version: string;
component?: string;
targetBranch?: string;
pullRequestTitlePattern?: string;
}) {
this.version = opts.version;
this.component = opts.component;
this.targetBranch = opts.targetBranch;
this.pullRequestTitlePattern =
opts.pullRequestTitlePattern || DEFAULT_PR_TITLE_PATTERN;
this.matchPattern = generateMatchPattern(this.pullRequestTitlePattern);
}

static parse(title: string): PullRequestTitle | undefined {
const match = title.match(DEFAULT_PATTERN);
static parse(
title: string,
pullRequestTitlePattern?: string
): PullRequestTitle | undefined {
const matchPattern = generateMatchPattern(pullRequestTitlePattern);
const match = title.match(matchPattern);
if (match?.groups) {
return new PullRequestTitle({
version: match.groups['version'],
component: match.groups['component'],
targetBranch: match.groups['branch'],
pullRequestTitlePattern: pullRequestTitlePattern,
});
}
return undefined;
}

static create(_title: string): PullRequestTitle | undefined {
return undefined;
}

static ofComponentVersion(
component: string,
version: string
version: string,
pullRequestTitlePattern?: string
): PullRequestTitle {
return new PullRequestTitle({version, component});
return new PullRequestTitle({version, component, pullRequestTitlePattern});
}
static ofVersion(version: string): PullRequestTitle {
return new PullRequestTitle({version});
static ofVersion(
version: string,
pullRequestTitlePattern?: string
): PullRequestTitle {
return new PullRequestTitle({version, pullRequestTitlePattern});
}
static ofTargetBranchVersion(
targetBranch: string,
version: string
version: string,
pullRequestTitlePattern?: string
): PullRequestTitle {
return new PullRequestTitle({version, targetBranch});
return new PullRequestTitle({
version,
targetBranch,
pullRequestTitlePattern,
});
}
static ofComponentTargetBranchVersion(
component: string,
targetBranch: string,
version: string
version: string,
pullRequestTitlePattern?: string
): PullRequestTitle {
return new PullRequestTitle({version, component, targetBranch});
return new PullRequestTitle({
version,
component,
targetBranch,
pullRequestTitlePattern,
});
}

getTargetBranch(): string | undefined {
Expand All @@ -77,12 +124,16 @@ export class PullRequestTitle {
getComponent(): string | undefined {
return this.component;
}
getVersion(): string | undefined {
getVersion(): string {
return this.version;
}

toString(): string {
const scope = this.targetBranch ? `(${this.targetBranch})` : '';
const component = this.component ? ` ${this.component}` : '';
return `chore${scope}: release${component} ${this.version}`;
return this.pullRequestTitlePattern
.replace('${scope}', scope)
.replace('${component}', component)
.replace('${version}', this.getVersion());
}
}
9 changes: 8 additions & 1 deletion test/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,21 @@ describe('CLI', () => {
it('instantiates release PR based on command line arguments', () => {
sandbox.replace(factory, 'call', callStub);
parser.parse(
'release-pr --repo-url=googleapis/release-please-cli --package-name=cli-package'
'release-pr ' +
'--repo-url=googleapis/release-please-cli ' +
'--package-name=cli-package ' +
"--pull-request-title-pattern='chore${scope}: release${component} ${version}'"
);
assert.ok(instanceToRun! instanceof ReleasePR);
assert.strictEqual(instanceToRun.gh.owner, 'googleapis');
assert.strictEqual(instanceToRun.gh.repo, 'release-please-cli');
assert.strictEqual(instanceToRun.packageName, 'cli-package');
// Defaults to Node.js release type:
assert.strictEqual(instanceToRun.constructor.name, 'Node');
assert.strictEqual(
instanceToRun.pullRequestTitlePattern,
'chore${scope}: release${component} ${version}'
);
});
it('validates releaseType choices', done => {
sandbox.stub(factory, 'call').resolves(undefined);
Expand Down
Loading

0 comments on commit d34e069

Please sign in to comment.