mirror of
https://github.com/renovatebot/renovate.git
synced 2025-01-11 22:46:27 +00:00
feat: stabilityDays (#4372)
This commit is contained in:
parent
27e10b91ee
commit
e6b1d67efb
7 changed files with 128 additions and 1 deletions
|
@ -971,6 +971,13 @@ const options: RenovateOptions[] = [
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'stabilityDays',
|
||||||
|
description:
|
||||||
|
'Number of days required before a new release is considered to be stabilized.',
|
||||||
|
type: 'integer',
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'prCreation',
|
name: 'prCreation',
|
||||||
description: 'When to create the PR for a branch.',
|
description: 'When to create the PR for a branch.',
|
||||||
|
|
|
@ -7,7 +7,7 @@ const { getAdditionalFiles } = require('../../manager/npm/post-update');
|
||||||
const { commitFilesToBranch } = require('./commit');
|
const { commitFilesToBranch } = require('./commit');
|
||||||
const { getParentBranch } = require('./parent');
|
const { getParentBranch } = require('./parent');
|
||||||
const { tryBranchAutomerge } = require('./automerge');
|
const { tryBranchAutomerge } = require('./automerge');
|
||||||
const { setUnpublishable } = require('./status-checks');
|
const { setStability, setUnpublishable } = require('./status-checks');
|
||||||
const { prAlreadyExisted } = require('./check-existing');
|
const { prAlreadyExisted } = require('./check-existing');
|
||||||
const prWorker = require('../pr');
|
const prWorker = require('../pr');
|
||||||
const { appName, appSlug } = require('../../config/app-strings');
|
const { appName, appSlug } = require('../../config/app-strings');
|
||||||
|
@ -188,6 +188,48 @@ async function processBranch(branchConfig, prHourlyLimitReached, packageFiles) {
|
||||||
);
|
);
|
||||||
return 'pending';
|
return 'pending';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
config.upgrades.some(
|
||||||
|
upgrade => upgrade.stabilityDays && upgrade.releaseTimestamp
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// Only set a stability status check if one or more of the updates contain
|
||||||
|
// both a stabilityDays setting and a releaseTimestamp
|
||||||
|
config.stabilityStatus = 'success';
|
||||||
|
// Default to 'success' but set 'pending' if any update is pending
|
||||||
|
const oneDay = 24 * 60 * 60 * 1000;
|
||||||
|
for (const upgrade of config.upgrades) {
|
||||||
|
if (upgrade.stabilityDays && upgrade.releaseTimestamp) {
|
||||||
|
const daysElapsed = Math.floor(
|
||||||
|
(new Date().getTime() -
|
||||||
|
new Date(upgrade.releaseTimestamp).getTime()) /
|
||||||
|
oneDay
|
||||||
|
);
|
||||||
|
if (daysElapsed < upgrade.stabilityDays) {
|
||||||
|
logger.debug(
|
||||||
|
{
|
||||||
|
depName: upgrade.depName,
|
||||||
|
daysElapsed,
|
||||||
|
stabilityDays: upgrade.stabilityDays,
|
||||||
|
},
|
||||||
|
'Update has not passed stability days'
|
||||||
|
);
|
||||||
|
config.stabilityStatus = 'pending';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Don't create a branch if we know it will be status 'pending'
|
||||||
|
if (
|
||||||
|
!branchExists &&
|
||||||
|
config.stabilityStatus === 'pending' &&
|
||||||
|
['not-pending', 'status-success'].includes(config.prCreation)
|
||||||
|
) {
|
||||||
|
logger.info('Skipping branch creation due to stability days not met');
|
||||||
|
return 'pending';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// istanbul ignore if
|
// istanbul ignore if
|
||||||
if (masterIssueCheck === 'rebase' || config.masterIssueRebaseAllOpen) {
|
if (masterIssueCheck === 'rebase' || config.masterIssueRebaseAllOpen) {
|
||||||
logger.info('Manual rebase requested via master issue');
|
logger.info('Manual rebase requested via master issue');
|
||||||
|
@ -269,6 +311,7 @@ async function processBranch(branchConfig, prHourlyLimitReached, packageFiles) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set branch statuses
|
// Set branch statuses
|
||||||
|
await setStability(config);
|
||||||
await setUnpublishable(config);
|
await setUnpublishable(config);
|
||||||
|
|
||||||
// Try to automerge branch and finish if successful, but only if branch already existed before this run
|
// Try to automerge branch and finish if successful, but only if branch already existed before this run
|
||||||
|
|
|
@ -2,6 +2,7 @@ const { logger } = require('../../logger');
|
||||||
const { appSlug, urls } = require('../../config/app-strings');
|
const { appSlug, urls } = require('../../config/app-strings');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
setStability,
|
||||||
setUnpublishable,
|
setUnpublishable,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -25,6 +26,23 @@ async function setStatusCheck(branchName, context, description, state) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function setStability(config) {
|
||||||
|
if (!config.stabilityStatus) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const context = `${appSlug}/stability-days`;
|
||||||
|
const description =
|
||||||
|
config.stabilityStatus === 'success'
|
||||||
|
? 'Updates have met stability days requirement'
|
||||||
|
: 'Updates have not met stability days requirement';
|
||||||
|
await setStatusCheck(
|
||||||
|
config.branchName,
|
||||||
|
context,
|
||||||
|
description,
|
||||||
|
config.stabilityStatus
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async function setUnpublishable(config) {
|
async function setUnpublishable(config) {
|
||||||
if (!config.unpublishSafe) {
|
if (!config.unpublishSafe) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -592,6 +592,11 @@
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
|
"stabilityDays": {
|
||||||
|
"description": "Number of days required before a new release is considered to be stabilized.",
|
||||||
|
"type": "integer",
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
"prCreation": {
|
"prCreation": {
|
||||||
"description": "When to create the PR for a branch.",
|
"description": "When to create the PR for a branch.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|
|
@ -75,6 +75,18 @@ describe('workers/branch', () => {
|
||||||
const res = await branchWorker.processBranch(config);
|
const res = await branchWorker.processBranch(config);
|
||||||
expect(res).toEqual('pending');
|
expect(res).toEqual('pending');
|
||||||
});
|
});
|
||||||
|
it('skips branch if not stabilityDays not met', async () => {
|
||||||
|
schedule.isScheduledNow.mockReturnValueOnce(true);
|
||||||
|
config.prCreation = 'not-pending';
|
||||||
|
config.upgrades = [
|
||||||
|
{
|
||||||
|
releaseTimestamp: '2099-12-31',
|
||||||
|
stabilityDays: 1,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const res = await branchWorker.processBranch(config);
|
||||||
|
expect(res).toEqual('pending');
|
||||||
|
});
|
||||||
it('processes branch if not scheduled but updating out of schedule', async () => {
|
it('processes branch if not scheduled but updating out of schedule', async () => {
|
||||||
schedule.isScheduledNow.mockReturnValueOnce(false);
|
schedule.isScheduledNow.mockReturnValueOnce(false);
|
||||||
config.updateNotScheduled = true;
|
config.updateNotScheduled = true;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
const {
|
const {
|
||||||
|
setStability,
|
||||||
setUnpublishable,
|
setUnpublishable,
|
||||||
} = require('../../../lib/workers/branch/status-checks');
|
} = require('../../../lib/workers/branch/status-checks');
|
||||||
const defaultConfig = require('../../../lib/config/defaults').getConfig();
|
const defaultConfig = require('../../../lib/config/defaults').getConfig();
|
||||||
|
@ -7,6 +8,33 @@ const defaultConfig = require('../../../lib/config/defaults').getConfig();
|
||||||
const platform = global.platform;
|
const platform = global.platform;
|
||||||
|
|
||||||
describe('workers/branch/status-checks', () => {
|
describe('workers/branch/status-checks', () => {
|
||||||
|
describe('setStability', () => {
|
||||||
|
let config;
|
||||||
|
beforeEach(() => {
|
||||||
|
config = {
|
||||||
|
...defaultConfig,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
afterEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
});
|
||||||
|
it('returns if not configured', async () => {
|
||||||
|
await setStability(config);
|
||||||
|
expect(platform.getBranchStatusCheck).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
it('sets status pending', async () => {
|
||||||
|
config.stabilityStatus = 'pending';
|
||||||
|
await setStability(config);
|
||||||
|
expect(platform.getBranchStatusCheck).toHaveBeenCalledTimes(1);
|
||||||
|
expect(platform.setBranchStatus).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
it('sets status success', async () => {
|
||||||
|
config.stabilityStatus = 'success';
|
||||||
|
await setStability(config);
|
||||||
|
expect(platform.getBranchStatusCheck).toHaveBeenCalledTimes(1);
|
||||||
|
expect(platform.setBranchStatus).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
describe('setUnpublishable', () => {
|
describe('setUnpublishable', () => {
|
||||||
let config;
|
let config;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|
|
@ -1083,6 +1083,20 @@ By default, Renovate won't distinguish between "patch" (e.g. 1.0.x) and "minor"
|
||||||
|
|
||||||
Set this to true if you wish to receive one PR for every separate major version upgrade of a dependency. e.g. if you are on webpack@v1 currently then default behaviour is a PR for upgrading to webpack@v3 and not for webpack@v2. If this setting is true then you would get one PR for webpack@v2 and one for webpack@v3.
|
Set this to true if you wish to receive one PR for every separate major version upgrade of a dependency. e.g. if you are on webpack@v1 currently then default behaviour is a PR for upgrading to webpack@v3 and not for webpack@v2. If this setting is true then you would get one PR for webpack@v2 and one for webpack@v3.
|
||||||
|
|
||||||
|
## stabilityDays
|
||||||
|
|
||||||
|
If this is set to a non-zero value, and an update has a release date/timestamp available, then Renovate will check if the configured "stability days" have elapsed. If the days since the release is less than the configured stability days then a "pending" status check will be added to the branch. If enough days have passed then a passing status check will be added.
|
||||||
|
|
||||||
|
There are a couple of uses for this:
|
||||||
|
|
||||||
|
#### Suppress branch/PR creation for X days
|
||||||
|
|
||||||
|
If you combine `stabilityDays=3` and `prCreation="not-pending"` then Renovate will hold back from creating branches until 3 or more days have elapsed since the version was released. It's recommended that you enable `masterIssue=true` so you don't lose visibility of these pending PRs.
|
||||||
|
|
||||||
|
#### Await X days before Automerging
|
||||||
|
|
||||||
|
If you have both `automerge` as well as `stabilityDays` enabled, it means that PRs will be created immediately but automerging will be delayed until X days have passed. This works because Renovate will add a "renovate/stability-days" pending status check to each branch/PR and that pending check will prevent the branch going green to automerge.
|
||||||
|
|
||||||
## statusCheckVerify
|
## statusCheckVerify
|
||||||
|
|
||||||
This feature is added for people migrating from alternative services who are used to seeing a "verify" status check on PRs. If you'd like to use this then go ahead, but otherwise it's more secure to look for Renovate's [GPG Verified Commits](https://github.com/blog/2144-gpg-signature-verification) instead, because those cannot be spoofed by any other person or service (unlike status checks).
|
This feature is added for people migrating from alternative services who are used to seeing a "verify" status check on PRs. If you'd like to use this then go ahead, but otherwise it's more secure to look for Renovate's [GPG Verified Commits](https://github.com/blog/2144-gpg-signature-verification) instead, because those cannot be spoofed by any other person or service (unlike status checks).
|
||||||
|
|
Loading…
Reference in a new issue