Skip to content

Commit

Permalink
feat: tagPullRequestNumber option to tag release SHA (#5683)
Browse files Browse the repository at this point in the history
(Compared to the previous attempt
#5682, this
version creates the ref in runBranchConfiguration.)

With the following lines in release-please.yml file, the bot now can tag
the release

```
handleGHRelease: true
tagPullRequestNumber: true
```

Test case is in 'should tag pull request number if configured'.
  • Loading branch information
suztomo authored Mar 3, 2025
1 parent c2a38c5 commit ab1873b
Show file tree
Hide file tree
Showing 8 changed files with 220 additions and 12 deletions.
11 changes: 6 additions & 5 deletions packages/release-please/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/release-please/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"@octokit/webhooks": "^10.1.5",
"gcf-utils": "^16.2.1",
"jsonwebtoken": "^9.0.0",
"release-please": "^16.15.0"
"release-please": "^16.18.0"
},
"devDependencies": {
"@types/mocha": "^10.0.0",
Expand Down
1 change: 1 addition & 0 deletions packages/release-please/src/config-constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export interface BranchOptions {
changelogType?: ChangelogNotesType;
initialVersion?: string;
onDemand?: boolean;
tagPullRequestNumber?: boolean;
}

export interface BranchConfiguration extends BranchOptions {
Expand Down
32 changes: 29 additions & 3 deletions packages/release-please/src/release-please.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ async function runBranchConfigurationWithConfigurationHandlingWithoutLock(
repoLanguage,
repoUrl,
branchConfiguration,
octokit,
options
);
} catch (e) {
Expand Down Expand Up @@ -356,6 +357,7 @@ async function runBranchConfiguration(
repoLanguage: string | null,
repoUrl: string,
branchConfiguration: BranchConfiguration,
octokit: Octokit,
options: RunBranchOptions
) {
const logger = options.logger ?? defaultLogger;
Expand Down Expand Up @@ -383,13 +385,37 @@ async function runBranchConfiguration(
plugins
);
try {
const numReleases = await Runner.createReleases(manifest);
logger.info(`Created ${numReleases} releases`);
if (numReleases > 0) {
const releases = await Runner.createReleases(manifest);
logger.info(`Created ${releases.length} releases`);
if (releases.length > 0) {
// we created a release, reload config which may include the latest
// version
manifest = null;
}
if (branchConfiguration.tagPullRequestNumber) {
// Record the pull request number to the commit as
// a tag.

// It's possible for manifest.createReleases() to create
// releases for multiple pull requests. Find the unique
// pair of pull request numbers and their commit SHAs.
const prNumberToSha = new Map<number, string>();
for (const release of releases) {
prNumberToSha.set(release.prNumber, release.sha);
}
for (const [prNumber, sha] of prNumberToSha.entries()) {
// A Git (lightweight) tag is a ref.
const tagName = `release-please-${prNumber}`;
logger.info(`Creating ${tagName} pointing to ${sha}`);
const tagResponse = await Runner.createLightweightTag(
octokit,
github.repository,
tagName,
sha
);
logger.debug('Got tag response: ', tagResponse);
}
}
} catch (e) {
if (e instanceof Errors.DuplicateReleaseError) {
// In the future, this could raise an issue against the
Expand Down
38 changes: 35 additions & 3 deletions packages/release-please/src/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,46 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import {Manifest} from 'release-please';
import {Octokit, RestEndpointMethodTypes} from '@octokit/rest';
import {CreatedRelease, Manifest} from 'release-please';
import {Repository} from 'release-please/build/src/repository';

export class Runner {
static createPullRequests = async (manifest: Manifest) => {
await manifest.createPullRequests();
};
static createReleases = async (manifest: Manifest): Promise<number> => {
static createReleases = async (
manifest: Manifest
): Promise<CreatedRelease[]> => {
const releases = await manifest.createReleases();
return releases.filter(release => !!release).length;
return releases.filter(release => !!release);
};

/**
* Creates a lightweight tag in the GitHub repository.
*
* @param octokit
* @param repository
* @param tagName The tag name to create. It must not have 'refs/tags' prefix.
* @param sha The sha to create tag to.
* @returns
*/
static createLightweightTag = async (
octokit: Octokit,
repository: Repository,
tagName: string,
sha: string
): Promise<RestEndpointMethodTypes['git']['createRef']['response']> => {
// A lightweight tag only requires this create references API call,
// rather than a tag object (/repos/{owner}/{repo}/git/tags).
// https://docs.github.com/en/rest/git/tags?apiVersion=2022-11-28#create-a-tag-object
// https://docs.github.com/en/rest/git/refs?apiVersion=2022-11-28#create-a-reference
const tagRefName = `refs/tags/${tagName}`;
return octokit.git.createRef({
owner: repository.owner,
repo: repository.repo,
ref: tagRefName,
sha: sha,
});
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
primaryBranch: master
manifest: true
handleGHRelease: true
tagPullRequestNumber: true
92 changes: 92 additions & 0 deletions packages/release-please/test/release-please.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ describe('ReleasePleaseBot', () => {
let getConfigStub: sinon.SinonStub;
let createPullRequestsStub: sinon.SinonStub;
let createReleasesStub: sinon.SinonStub;
let createLightweightTagStub: sinon.SinonStub;

beforeEach(() => {
probot = createProbot({
Expand All @@ -62,6 +63,7 @@ describe('ReleasePleaseBot', () => {
getConfigStub = sandbox.stub(botConfigModule, 'getConfig');
createPullRequestsStub = sandbox.stub(Runner, 'createPullRequests');
createReleasesStub = sandbox.stub(Runner, 'createReleases');
createLightweightTagStub = sandbox.stub(Runner, 'createLightweightTag');
sandbox
.stub(gcfUtilsModule, 'getAuthenticatedOctokit')
.resolves(new Octokit({auth: 'faketoken'}));
Expand All @@ -72,6 +74,9 @@ describe('ReleasePleaseBot', () => {
) {
await f();
} as any);

// No release for test cases except explicitly set in each case.
createReleasesStub.resolves([]);
});

afterEach(() => {
Expand Down Expand Up @@ -735,6 +740,93 @@ describe('ReleasePleaseBot', () => {
})
);
});

it('should tag pull request number if configured', async () => {
getConfigStub.resolves(loadConfig('manifest_tag_pr_number.yml'));
// We want the PR number 789 to be in the tag
const exampleRelease = {
id: 'v4.5.6',
path: 'foo',
version: 'v4.5.6',
major: 4,
minor: 5,
patch: 6,
prNumber: 789,
sha: '853ab2395d7777f8f3f8cb2b7106d3a3d17490e9',
};
createReleasesStub.resolves([exampleRelease]);
createLightweightTagStub.resolves({});

await probot.receive(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
{name: 'push', payload: payload as any, id: 'abc123'}
);

sinon.assert.calledOnce(createReleasesStub);
sinon.assert.calledOnceWithExactly(
createLightweightTagStub,
sinon.match.instanceOf(Octokit),
sinon.match
.has('repo', 'google-auth-library-java')
.and(sinon.match.has('owner', 'chingor13')),
'release-please-789',
'853ab2395d7777f8f3f8cb2b7106d3a3d17490e9'
);
// When there's a release, it reloads the manifest.
sinon.assert.calledTwice(fromManifestStub);
sinon.assert.calledOnce(createPullRequestsStub);
});

it('should tag each SHA for pull requests number if configured', async () => {
getConfigStub.resolves(loadConfig('manifest_tag_pr_number.yml'));
// 2 pull requests created 3 releases. First
// two share the same SHA and PR number.
const release1 = {
id: 'foo/v3.0.1',
path: 'foo',
version: 'v3.0.1',
major: 3,
minor: 0,
patch: 1,
prNumber: 789,
sha: '853ab2395d7777f8f3f8cb2b7106d3a3d17490e9',
};
const release2 = {
id: 'bar/v4.0.1',
path: 'bar',
version: 'v4.0.1',
major: 4,
minor: 0,
patch: 1,
// These values below are the same as the
// release1's.
prNumber: 789,
sha: '853ab2395d7777f8f3f8cb2b7106d3a3d17490e9',
};
const release3 = {
id: 'v5.0.2',
path: 'foo',
version: 'v5.0.2',
major: 5,
minor: 0,
patch: 2,
prNumber: 790,
sha: 'f5528f1d94206836a8ceb9bed5eeaa768e002fb4',
};
createReleasesStub.resolves([release1, release2, release3]);
await probot.receive(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
{name: 'push', payload: payload as any, id: 'abc123'}
);

sinon.assert.calledOnce(createReleasesStub);
// Because the first 2 releases share the pull request and SHA,
// there should be 2 new tags.
sinon.assert.calledTwice(createLightweightTagStub);
// When there's a release, it reloads the manifest.
sinon.assert.calledTwice(fromManifestStub);
sinon.assert.calledOnce(createPullRequestsStub);
});
});

it('should handle a misconfigured repository', async () => {
Expand Down
52 changes: 52 additions & 0 deletions packages/release-please/test/runner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import nock from 'nock';
import {Runner} from '../src/runner';
import {Octokit} from '@octokit/rest';
import assert from 'assert';

nock.disableNetConnect();

describe('Release Please Runner', () => {
let octokit: Octokit;
beforeEach(() => {
octokit = new Octokit({auth: 'faketoken'});
});
afterEach(() => {
nock.cleanAll();
});

it('should create a ref for lightweight tag', async () => {
const requests = nock('https://api.github.com')
.post('/repos/owner1/repo1/git/refs', body => {
assert.equal(body['ref'], 'refs/tags/release-please-123');
assert.equal(body['sha'], 'abcdefg');
return true;
})
.reply(200);

await Runner.createLightweightTag(
octokit,
{
owner: 'owner1',
repo: 'repo1',
defaultBranch: 'main',
},
'release-please-123',
'abcdefg'
);
requests.done();
});
});

0 comments on commit ab1873b

Please sign in to comment.