From 2bb8d133efc211f4e463d0069499ba738c81c664 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 24 Jun 2026 02:32:34 +0000
Subject: [PATCH 1/2] Initial plan
From 363a116c7dd6b854fc9091345f7b45fcbc213cb7 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 24 Jun 2026 02:39:40 +0000
Subject: [PATCH 2/2] Add release immutability support to repository plugin
---
.../github-settings/1. repository-settings.md | 24 +++++++++
docs/sample-settings/settings.yml | 5 ++
lib/plugins/repository.js | 39 ++++++++++++++-
test/unit/lib/plugins/repository.test.js | 49 +++++++++++++++++--
4 files changed, 113 insertions(+), 4 deletions(-)
diff --git a/docs/github-settings/1. repository-settings.md b/docs/github-settings/1. repository-settings.md
index 46eaa64b9..4b3b92922 100644
--- a/docs/github-settings/1. repository-settings.md
+++ b/docs/github-settings/1. repository-settings.md
@@ -44,6 +44,8 @@ repository:
security:
enableVulnerabilityAlerts: true
enableAutomatedSecurityFixes: true
+ releases:
+ immutable: true
```
## Repository API Spec
@@ -414,5 +416,27 @@ repository:
...
```
+
+
|
+ releases object
+Settings for releases in this repository.
+
+Properties of releases
+
+
+ immutable boolean
+ Either true to enable release immutability (disallow assets and tags from being modified once a release is published), or false to disable it.
+ See Enable immutable releases for more information.
+
+
+ |
+
+```yaml
+repository:
+ releases:
+ immutable: true
+...
+```
+
|
diff --git a/docs/sample-settings/settings.yml b/docs/sample-settings/settings.yml
index 1ede6a079..80b605f8e 100644
--- a/docs/sample-settings/settings.yml
+++ b/docs/sample-settings/settings.yml
@@ -24,6 +24,11 @@ repository:
enableVulnerabilityAlerts: true
enableAutomatedSecurityFixes: true
+ # Settings for release immutability
+ # See https://docs.github.com/en/rest/repos/repos#enable-immutable-releases
+ releases:
+ immutable: true
+
# Either `true` to make the repository private, or `false` to make it public.
# If this value is changed and if org members cannot change the visibility of repos
# it would result in an error when updating a repo
diff --git a/lib/plugins/repository.js b/lib/plugins/repository.js
index 8f1a3f5dc..55f3732f5 100644
--- a/lib/plugins/repository.js
+++ b/lib/plugins/repository.js
@@ -37,7 +37,8 @@ const ignorableFields = [
'force_create',
'auto_init',
'repo',
- 'archived'
+ 'archived',
+ 'releases'
]
module.exports = class Repository extends ErrorStash {
@@ -48,6 +49,7 @@ module.exports = class Repository extends ErrorStash {
this.settings = Object.assign({ mediaType: { previews: ['nebula-preview'] } }, settings, repo)
this.topics = this.settings.topics
this.security = this.settings.security
+ this.releases = this.settings.releases
this.repo = repo
this.log = log
this.nop = nop
@@ -56,6 +58,7 @@ module.exports = class Repository extends ErrorStash {
delete this.settings.topics
delete this.settings.force
delete this.settings.template
+ delete this.settings.releases
}
sync () {
@@ -109,8 +112,12 @@ module.exports = class Repository extends ErrorStash {
promises.push(updateRepoPromise.then(() => {
return this.updateAutomatedSecurityFixes(resp.data, resArray)
}))
+ promises.push(updateRepoPromise.then(() => {
+ return this.updateReleaseImmutability(resp.data, resArray)
+ }))
} else {
promises.push(this.updateSecurity(resp.data, resArray))
+ promises.push(this.updateReleaseImmutability(resp.data, resArray))
}
if (this.nop) {
return Promise.resolve(resArray)
@@ -315,4 +322,34 @@ module.exports = class Repository extends ErrorStash {
}
}
}
+
+ updateReleaseImmutability (repoData, resArray) {
+ if (this.releases?.immutable === true || this.releases?.immutable === false) {
+ const parms = {
+ owner: repoData.owner.login,
+ repo: repoData.name
+ }
+ if (this.releases.immutable === true) {
+ this.log.debug(`Enabling release immutability for owner: ${repoData.owner.login} and repo ${repoData.name}`)
+ if (this.nop) {
+ resArray.push(new NopCommand(this.constructor.name, this.repo, this.github.request.endpoint('PUT /repos/{owner}/{repo}/releases/immutability', parms), 'Enabling release immutability'))
+ return Promise.resolve(resArray)
+ }
+ return this.github.request('PUT /repos/{owner}/{repo}/releases/immutability', parms)
+ } else {
+ this.log.debug(`Disabling release immutability for owner: ${repoData.owner.login} and repo ${repoData.name}`)
+ if (this.nop) {
+ resArray.push(new NopCommand(this.constructor.name, this.repo, this.github.request.endpoint('DELETE /repos/{owner}/{repo}/releases/immutability', parms), 'Disabling release immutability'))
+ return Promise.resolve(resArray)
+ }
+ return this.github.request('DELETE /repos/{owner}/{repo}/releases/immutability', parms)
+ }
+ } else {
+ this.log.debug(`no need to update release immutability for ${repoData.name}`)
+ if (this.nop) {
+ return Promise.resolve([])
+ }
+ return Promise.resolve()
+ }
+ }
}
diff --git a/test/unit/lib/plugins/repository.test.js b/test/unit/lib/plugins/repository.test.js
index 75b1199fc..5d64a910a 100644
--- a/test/unit/lib/plugins/repository.test.js
+++ b/test/unit/lib/plugins/repository.test.js
@@ -6,14 +6,18 @@ describe('Repository', () => {
repos: {
get: jest.fn().mockResolvedValue({
data: {
- topics: []
+ topics: [],
+ owner: { login: 'bkeepers' },
+ name: 'test'
}
}),
update: jest.fn().mockResolvedValue(),
replaceAllTopics: jest.fn().mockResolvedValue()
}
- }
+ },
+ request: jest.fn().mockResolvedValue()
}
+ github.request.endpoint = jest.fn().mockReturnValue({})
const log = jest.fn()
log.debug = jest.fn()
log.error = jest.fn()
@@ -60,7 +64,7 @@ describe('Repository', () => {
})
})
- it.only('syncs topics', () => {
+ it('syncs topics', () => {
const plugin = configure({
topics: ['foo', 'bar']
})
@@ -76,5 +80,44 @@ describe('Repository', () => {
})
})
})
+
+ it('enables release immutability', () => {
+ const plugin = configure({
+ releases: { immutable: true }
+ })
+
+ return plugin.sync().then(() => {
+ expect(github.request).toHaveBeenCalledWith(
+ 'PUT /repos/{owner}/{repo}/releases/immutability',
+ { owner: 'bkeepers', repo: 'test' }
+ )
+ })
+ })
+
+ it('disables release immutability', () => {
+ const plugin = configure({
+ releases: { immutable: false }
+ })
+
+ return plugin.sync().then(() => {
+ expect(github.request).toHaveBeenCalledWith(
+ 'DELETE /repos/{owner}/{repo}/releases/immutability',
+ { owner: 'bkeepers', repo: 'test' }
+ )
+ })
+ })
+
+ it('does not call release immutability API when releases setting is absent', () => {
+ const plugin = configure({
+ name: 'test'
+ })
+
+ return plugin.sync().then(() => {
+ expect(github.request).not.toHaveBeenCalledWith(
+ expect.stringMatching(/releases\/immutability/),
+ expect.anything()
+ )
+ })
+ })
})
})