From 84ab4c4ce90e687e2a9e87820a2f39205419fdd8 Mon Sep 17 00:00:00 2001 From: Bryan Reymander <30937320+breymander@users.noreply.github.com> Date: Tue, 13 Oct 2026 15:57:54 -0400 Subject: [PATCH] Sync settings on repository.transferred A repo transferred into the org fired no handler, so org, suborg and repo-level settings were not applied until the next cron sync or a human edit. Treat repository.transferred like repository.created. Fixes #1107 --- README.md | 2 ++ index.js | 10 ++++++++ test/integration/common.js | 14 +++++++++++ .../triggers/repository-transferred.test.js | 24 +++++++++++++++++++ 4 files changed, 50 insertions(+) create mode 100644 test/integration/triggers/repository-transferred.test.js diff --git a/README.md b/README.md index 16d09c3d5..d3e6597a3 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,8 @@ The App listens to the following webhook events: - **repository.created**: If a repository is created in the org, the settings for the repo - the default settings for the org, overlaid with settings for the suborg that the repo belongs to, overlaid with the settings for that specific repo - is applied. +- **repository.transferred**: If a repository is transferred into the org from another org or user account, the settings for the repo are applied the same way as for **repository.created**. + - **branch_protection_rule**: If a branch protection rule is modified or deleted, `safe-settings` will `sync` the settings to prevent any unauthorized changes. - **repository.edited**: For e.g. If the default branch is renamed, or if topics change, `safe-settings` will `sync` the settings, to prevent any unauthorized changes. diff --git a/index.js b/index.js index 4ed6c2398..afaa93887 100644 --- a/index.js +++ b/index.js @@ -894,6 +894,16 @@ module.exports = (robot, { getRouter }, Settings = require('./lib/settings')) => return syncSettings(false, context) }) + // A repo transferred into the org gets the same treatment as a newly created one. + // GitHub delivers repository.transferred to the receiving account, so this is the + // only webhook that fires when a repo arrives from another org or user. + robot.on('repository.transferred', async context => { + const { payload } = context + const { sender } = payload + robot.log.debug('repository.transferred payload from ', JSON.stringify(sender)) + return syncSettings(false, context) + }) + robot.on('repository.archived', async context => { const { payload } = context const { sender } = payload diff --git a/test/integration/common.js b/test/integration/common.js index 47fbc1c9c..8eb08f0f4 100644 --- a/test/integration/common.js +++ b/test/integration/common.js @@ -220,6 +220,19 @@ function buildRepositoryCreatedEvent () { } } +function buildRepositoryTransferredEvent () { + return { + name: 'repository.transferred', + payload: { + changes: { owner: { from: { organization: { login: any.word() } } } }, + repository, + organization, + sender, + installation + } + } +} + function buildTriggerEvent () { return buildRepositoryCreatedEvent() } @@ -235,5 +248,6 @@ module.exports = { buildTriggerEvent, buildRepositoryCreatedEvent, buildRepositoryEditedEvent, + buildRepositoryTransferredEvent, repository } diff --git a/test/integration/triggers/repository-transferred.test.js b/test/integration/triggers/repository-transferred.test.js new file mode 100644 index 000000000..0baa2814f --- /dev/null +++ b/test/integration/triggers/repository-transferred.test.js @@ -0,0 +1,24 @@ +const { describe, it, beforeEach, afterEach } = require('node:test') +const { buildRepositoryTransferredEvent, initializeNock, loadInstance, mockAdminRepository, repository, teardownNock } = require('../common') + +describe('repository.transferred trigger', function () { + let probot, githubScope + + beforeEach(async () => { + githubScope = initializeNock() + probot = await loadInstance() + }) + + afterEach(() => { + teardownNock(githubScope) + }) + + it('does not apply configuration when the repository does not have a settings.yml', async () => { + mockAdminRepository(githubScope) + githubScope + .get(`/repos/${repository.owner.login}/${repository.name}`) + .reply(200, { ...repository, archived: false }) + + await probot.receive(buildRepositoryTransferredEvent()) + }) +})