From f8cd2d3fc7eb6241b852efdc2e2ff65e52525ae4 Mon Sep 17 00:00:00 2001 From: Mustaque Ahmed Date: Wed, 26 Nov 2025 16:59:32 +0530 Subject: [PATCH] feat: add support for `[[environments]]` block Co-authored-by: Marco Ieni <11428655+marcoieni@users.noreply.github.com> --- docs/toml-schema.md | 14 ++ repos/rust-analyzer/metrics.toml | 4 + .../rust-analyzer.github.io.toml | 4 + repos/rust-lang-nursery/lazy-static.rs.toml | 4 + repos/rust-lang-nursery/rust-cookbook.toml | 4 + .../rust-lang-nursery.github.io.toml | 4 + repos/rust-lang-nursery/rust-toolstate.toml | 4 + repos/rust-lang/a-mir-formality.toml | 4 + repos/rust-lang/api-guidelines.toml | 4 + repos/rust-lang/ar_archive_writer.toml | 4 + repos/rust-lang/areweasyncyet.rs.toml | 4 + repos/rust-lang/arewewebyet.toml | 4 + repos/rust-lang/async-book.toml | 4 + .../async-crashdump-debugging-initiative.toml | 4 + .../async-fundamentals-initiative.toml | 4 + repos/rust-lang/backtrace-rs.toml | 4 + repos/rust-lang/blog.rust-lang.org.toml | 4 + repos/rust-lang/book.toml | 4 + repos/rust-lang/bors-kindergarten.toml | 13 ++ repos/rust-lang/bors.toml | 7 + repos/rust-lang/calendar.toml | 4 + repos/rust-lang/cargo-bisect-rustc.toml | 4 + repos/rust-lang/cargo.toml | 7 + repos/rust-lang/cc-rs.toml | 7 + repos/rust-lang/cfg-if.toml | 4 + repos/rust-lang/chalk.toml | 4 + repos/rust-lang/ci-mirrors.toml | 4 + repos/rust-lang/cmake-rs.toml | 4 + repos/rust-lang/compiler-team.toml | 4 + repos/rust-lang/const-eval.toml | 4 + repos/rust-lang/crates.io.toml | 7 + repos/rust-lang/crates_io_og_image.toml | 4 + .../dyn-upcasting-coercion-initiative.toml | 4 + repos/rust-lang/edition-guide.toml | 4 + repos/rust-lang/enzyme.toml | 4 + repos/rust-lang/flate2-rs.toml | 4 + repos/rust-lang/fls.toml | 4 + repos/rust-lang/futures-rs.toml | 4 + .../generic-associated-types-initiative.toml | 4 + repos/rust-lang/getopts.toml | 4 + repos/rust-lang/gha-self-hosted.toml | 4 + repos/rust-lang/git2-rs.toml | 4 + repos/rust-lang/glob.toml | 4 + repos/rust-lang/hashbrown.toml | 4 + repos/rust-lang/impl-trait-initiative.toml | 4 + repos/rust-lang/initiative-template.toml | 4 + repos/rust-lang/jobserver-rs.toml | 4 + .../keyword-generics-initiative.toml | 4 + repos/rust-lang/lang-team.toml | 4 + repos/rust-lang/libc.toml | 4 + repos/rust-lang/libz-sys.toml | 4 + repos/rust-lang/log.toml | 4 + repos/rust-lang/mdBook.toml | 7 + repos/rust-lang/measureme.toml | 4 + .../rust-lang/negative-impls-initiative.toml | 4 + repos/rust-lang/packed_simd.toml | 4 + repos/rust-lang/pkg-config-rs.toml | 4 + repos/rust-lang/polonius.toml | 4 + repos/rust-lang/pontoon.toml | 4 + repos/rust-lang/portable-simd.toml | 4 + repos/rust-lang/project-const-generics.toml | 4 + .../project-goal-reference-expansion.toml | 4 + repos/rust-lang/project-group-template.toml | 4 + repos/rust-lang/project-stable-mir.toml | 4 + repos/rust-lang/regex.toml | 4 + repos/rust-lang/rfcbot-rs.toml | 4 + repos/rust-lang/rfcs.toml | 4 + repos/rust-lang/rust-analyzer.toml | 4 + repos/rust-lang/rust-bindgen.toml | 4 + repos/rust-lang/rust-by-example.toml | 4 + repos/rust-lang/rust-clippy.toml | 4 + repos/rust-lang/rust-enhanced.toml | 4 + repos/rust-lang/rust-forge.toml | 4 + repos/rust-lang/rust-lang.github.io.toml | 4 + repos/rust-lang/rust-log-analyzer.toml | 4 + repos/rust-lang/rust-project-goals.toml | 4 + repos/rust-lang/rust.toml | 4 + repos/rust-lang/rustc-demangle.toml | 4 + repos/rust-lang/rustc-dev-guide.toml | 4 + repos/rust-lang/rustc-pr-tracking.toml | 4 + repos/rust-lang/rustc-reading-club.toml | 4 + repos/rust-lang/rustfmt.toml | 4 + repos/rust-lang/rustlings.toml | 4 + .../rust-lang/rustup-components-history.toml | 4 + repos/rust-lang/rustup.toml | 4 + repos/rust-lang/socket2.toml | 4 + repos/rust-lang/stacker.toml | 4 + repos/rust-lang/std-dev-guide.toml | 4 + repos/rust-lang/stdarch.toml | 4 + repos/rust-lang/team.toml | 7 + repos/rust-lang/thanks.toml | 4 + repos/rust-lang/thorin.toml | 4 + repos/rust-lang/types-team.toml | 4 + repos/rust-lang/unsafe-code-guidelines.toml | 4 + repos/rust-lang/wg-async.toml | 4 + repos/rust-lang/wg-macros.toml | 4 + repos/rust-lang/www.rust-lang.org.toml | 13 ++ repos/rust-lang/zulip_archive.toml | 4 + rust_team_data/src/v1.rs | 6 + src/main.rs | 15 ++ src/schema.rs | 8 + src/static_api.rs | 7 + src/validate.rs | 43 ++++ sync-team/src/github/api/read.rs | 29 +++ sync-team/src/github/api/write.rs | 36 ++++ sync-team/src/github/mod.rs | 91 +++++++++ sync-team/src/github/tests/mod.rs | 187 +++++++++++++++++- sync-team/src/github/tests/test_utils.rs | 32 ++- tests/static-api/_expected/v1/repos.json | 2 + .../_expected/v1/repos/archived_repo.json | 1 + .../_expected/v1/repos/some_repo.json | 1 + 111 files changed, 893 insertions(+), 3 deletions(-) diff --git a/docs/toml-schema.md b/docs/toml-schema.md index fce4ae222..6985d2e81 100644 --- a/docs/toml-schema.md +++ b/docs/toml-schema.md @@ -431,6 +431,20 @@ allowed-merge-teams = ["awesome-team"] merge-bots = ["homu"] ``` +### Repository environments + +GitHub environments are used to configure deployment protection rules and secrets for GitHub Actions workflows. This repository can manage environment names for repositories. + +```toml +# The environments in this repository (optional) +[[environments]] +# The name of the environment (required) +name = "production" + +[[environments]] +name = "staging" +``` + ### Crates.io trusted publishing Configure crates.io Trusted Publishing for crates published from a given repository from GitHub Actions. diff --git a/repos/rust-analyzer/metrics.toml b/repos/rust-analyzer/metrics.toml index 8e9a6710c..184b730ae 100644 --- a/repos/rust-analyzer/metrics.toml +++ b/repos/rust-analyzer/metrics.toml @@ -6,3 +6,7 @@ bots = [] [access.teams] rust-analyzer = "write" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-analyzer/rust-analyzer.github.io.toml b/repos/rust-analyzer/rust-analyzer.github.io.toml index 88bb2cde6..4fa203d09 100644 --- a/repos/rust-analyzer/rust-analyzer.github.io.toml +++ b/repos/rust-analyzer/rust-analyzer.github.io.toml @@ -6,3 +6,7 @@ bots = [] [access.teams] rust-analyzer = "write" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang-nursery/lazy-static.rs.toml b/repos/rust-lang-nursery/lazy-static.rs.toml index 10b3d546c..8f9b76296 100644 --- a/repos/rust-lang-nursery/lazy-static.rs.toml +++ b/repos/rust-lang-nursery/lazy-static.rs.toml @@ -5,3 +5,7 @@ bots = [] [access.teams] libs = "write" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang-nursery/rust-cookbook.toml b/repos/rust-lang-nursery/rust-cookbook.toml index 8e6a100a3..f07a53f43 100644 --- a/repos/rust-lang-nursery/rust-cookbook.toml +++ b/repos/rust-lang-nursery/rust-cookbook.toml @@ -6,3 +6,7 @@ bots = [] [access.teams] cookbook = "write" lang-docs = "write" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang-nursery/rust-lang-nursery.github.io.toml b/repos/rust-lang-nursery/rust-lang-nursery.github.io.toml index 2f5dd4dc8..940adf1ea 100644 --- a/repos/rust-lang-nursery/rust-lang-nursery.github.io.toml +++ b/repos/rust-lang-nursery/rust-lang-nursery.github.io.toml @@ -5,3 +5,7 @@ bots = [] [access.teams] infra = "write" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang-nursery/rust-toolstate.toml b/repos/rust-lang-nursery/rust-toolstate.toml index b25a9ef55..3458af98a 100644 --- a/repos/rust-lang-nursery/rust-toolstate.toml +++ b/repos/rust-lang-nursery/rust-toolstate.toml @@ -10,3 +10,7 @@ infra = "write" [[branch-protections]] pattern = "master" pr-required = false + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/a-mir-formality.toml b/repos/rust-lang/a-mir-formality.toml index 491a6f842..d71280527 100644 --- a/repos/rust-lang/a-mir-formality.toml +++ b/repos/rust-lang/a-mir-formality.toml @@ -8,3 +8,7 @@ lang = "maintain" lang-ops = "maintain" lang-docs = "maintain" types = "maintain" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/api-guidelines.toml b/repos/rust-lang/api-guidelines.toml index db7f070e8..039a68344 100644 --- a/repos/rust-lang/api-guidelines.toml +++ b/repos/rust-lang/api-guidelines.toml @@ -7,3 +7,7 @@ bots = [] [access.teams] libs-api = "write" libs = "write" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/ar_archive_writer.toml b/repos/rust-lang/ar_archive_writer.toml index 3c6a355db..91a29c976 100644 --- a/repos/rust-lang/ar_archive_writer.toml +++ b/repos/rust-lang/ar_archive_writer.toml @@ -5,3 +5,7 @@ bots = [] [access.teams] compiler = "write" + +[[environments]] +name = "publish" + diff --git a/repos/rust-lang/areweasyncyet.rs.toml b/repos/rust-lang/areweasyncyet.rs.toml index 0c0907877..a9ea6a0b9 100644 --- a/repos/rust-lang/areweasyncyet.rs.toml +++ b/repos/rust-lang/areweasyncyet.rs.toml @@ -9,3 +9,7 @@ wg-async = "write" [access.individuals] upsuper = "write" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/arewewebyet.toml b/repos/rust-lang/arewewebyet.toml index 4635a357b..f7e0764de 100644 --- a/repos/rust-lang/arewewebyet.toml +++ b/repos/rust-lang/arewewebyet.toml @@ -9,3 +9,7 @@ arewewebyet = "maintain" [[branch-protections]] pattern = 'master' + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/async-book.toml b/repos/rust-lang/async-book.toml index cf17669ae..a098ae611 100644 --- a/repos/rust-lang/async-book.toml +++ b/repos/rust-lang/async-book.toml @@ -6,3 +6,7 @@ bots = [] [access.teams] wg-async = "write" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/async-crashdump-debugging-initiative.toml b/repos/rust-lang/async-crashdump-debugging-initiative.toml index e458689e3..076c0c54c 100644 --- a/repos/rust-lang/async-crashdump-debugging-initiative.toml +++ b/repos/rust-lang/async-crashdump-debugging-initiative.toml @@ -5,3 +5,7 @@ bots = [] [access.teams] project-async-crashdump-debugging = "write" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/async-fundamentals-initiative.toml b/repos/rust-lang/async-fundamentals-initiative.toml index 09e61fa4a..eb6bd982a 100644 --- a/repos/rust-lang/async-fundamentals-initiative.toml +++ b/repos/rust-lang/async-fundamentals-initiative.toml @@ -10,3 +10,7 @@ wg-async = "maintain" [[branch-protections]] pattern = "master" required-approvals = 0 + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/backtrace-rs.toml b/repos/rust-lang/backtrace-rs.toml index c4a97a134..1855b5ecc 100644 --- a/repos/rust-lang/backtrace-rs.toml +++ b/repos/rust-lang/backtrace-rs.toml @@ -9,3 +9,7 @@ crate-maintainers = 'maintain' [[branch-protections]] pattern = 'master' + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/blog.rust-lang.org.toml b/repos/rust-lang/blog.rust-lang.org.toml index 048968ce3..a65fb1718 100644 --- a/repos/rust-lang/blog.rust-lang.org.toml +++ b/repos/rust-lang/blog.rust-lang.org.toml @@ -16,3 +16,7 @@ ci-checks = [ "lint", "build", ] + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/book.toml b/repos/rust-lang/book.toml index bb70d7991..536343a6c 100644 --- a/repos/rust-lang/book.toml +++ b/repos/rust-lang/book.toml @@ -11,3 +11,7 @@ book = "maintain" pattern = "main" ci-checks = [] required-approvals = 0 + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/bors-kindergarten.toml b/repos/rust-lang/bors-kindergarten.toml index 7cec5a0d4..6877b66d6 100644 --- a/repos/rust-lang/bors-kindergarten.toml +++ b/repos/rust-lang/bors-kindergarten.toml @@ -6,3 +6,16 @@ bots = ["rustbot"] [access.teams] infra = "write" infra-bors = "write" + +[[environments]] +name = "bors" + +[[environments]] +name = "ci" + +[[environments]] +name = "production" + +[[environments]] +name = "staging" + diff --git a/repos/rust-lang/bors.toml b/repos/rust-lang/bors.toml index a51b30f4f..224f8bde6 100644 --- a/repos/rust-lang/bors.toml +++ b/repos/rust-lang/bors.toml @@ -13,3 +13,10 @@ infra-bors = "write" pattern = "main" ci-checks = ["Test", "Test Docker"] required-approvals = 0 + +[[environments]] +name = "production" + +[[environments]] +name = "staging" + diff --git a/repos/rust-lang/calendar.toml b/repos/rust-lang/calendar.toml index 66dbc89cc..a9034a710 100644 --- a/repos/rust-lang/calendar.toml +++ b/repos/rust-lang/calendar.toml @@ -19,3 +19,7 @@ wg-async = "maintain" [[branch-protections]] pattern = "main" pr-required = false + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/cargo-bisect-rustc.toml b/repos/rust-lang/cargo-bisect-rustc.toml index 9c77ae32e..a6bedb28f 100644 --- a/repos/rust-lang/cargo-bisect-rustc.toml +++ b/repos/rust-lang/cargo-bisect-rustc.toml @@ -13,3 +13,7 @@ ehuss = "write" [[branch-protections]] pattern = "master" required-approvals = 0 + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/cargo.toml b/repos/rust-lang/cargo.toml index 32b740334..4361cdf0d 100644 --- a/repos/rust-lang/cargo.toml +++ b/repos/rust-lang/cargo.toml @@ -14,3 +14,10 @@ ci-checks = ["conclusion"] [[branch-protections]] pattern = "rust-1.*" ci-checks = ["conclusion"] + +[[environments]] +name = "github-pages" + +[[environments]] +name = "release" + diff --git a/repos/rust-lang/cc-rs.toml b/repos/rust-lang/cc-rs.toml index c00109f6d..d768a2f73 100644 --- a/repos/rust-lang/cc-rs.toml +++ b/repos/rust-lang/cc-rs.toml @@ -16,3 +16,10 @@ ci-checks = ['Tests pass'] crates = ["cc", "find-msvc-tools"] workflow-filename = "publish.yml" environment = "publish" + +[[environments]] +name = "github-pages" + +[[environments]] +name = "publish" + diff --git a/repos/rust-lang/cfg-if.toml b/repos/rust-lang/cfg-if.toml index 473e146f0..684996fc2 100644 --- a/repos/rust-lang/cfg-if.toml +++ b/repos/rust-lang/cfg-if.toml @@ -5,3 +5,7 @@ bots = [] [access.teams] crate-maintainers = 'maintain' + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/chalk.toml b/repos/rust-lang/chalk.toml index 64df9d5c5..4c52d532d 100644 --- a/repos/rust-lang/chalk.toml +++ b/repos/rust-lang/chalk.toml @@ -6,3 +6,7 @@ bots = ["rustbot"] [access.teams] types = "write" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/ci-mirrors.toml b/repos/rust-lang/ci-mirrors.toml index 6d44db6d2..a01c93374 100644 --- a/repos/rust-lang/ci-mirrors.toml +++ b/repos/rust-lang/ci-mirrors.toml @@ -11,3 +11,7 @@ infra = "write" pattern = "main" ci-checks = ["Build finished"] dismiss-stale-review = true + +[[environments]] +name = "upload" + diff --git a/repos/rust-lang/cmake-rs.toml b/repos/rust-lang/cmake-rs.toml index dd403813a..9413f46d1 100644 --- a/repos/rust-lang/cmake-rs.toml +++ b/repos/rust-lang/cmake-rs.toml @@ -11,3 +11,7 @@ crate-maintainers = 'maintain' pattern = "master" ci-checks = ['success'] required-approvals = 0 + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/compiler-team.toml b/repos/rust-lang/compiler-team.toml index 4c8c589f1..d77f8cd36 100644 --- a/repos/rust-lang/compiler-team.toml +++ b/repos/rust-lang/compiler-team.toml @@ -12,3 +12,7 @@ types = "write" [[branch-protections]] pattern = "master" required-approvals = 0 + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/const-eval.toml b/repos/rust-lang/const-eval.toml index 308bbc76c..53cf8b769 100644 --- a/repos/rust-lang/const-eval.toml +++ b/repos/rust-lang/const-eval.toml @@ -5,3 +5,7 @@ bots = [] [access.teams] wg-const-eval = "write" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/crates.io.toml b/repos/rust-lang/crates.io.toml index e16cc8594..71f2e37ed 100644 --- a/repos/rust-lang/crates.io.toml +++ b/repos/rust-lang/crates.io.toml @@ -6,3 +6,10 @@ bots = ["renovate", "rustbot", "heroku-deploy-access"] [access.teams] crates-io = "write" + +[[environments]] +name = "crates-io" + +[[environments]] +name = "staging-crates-io" + diff --git a/repos/rust-lang/crates_io_og_image.toml b/repos/rust-lang/crates_io_og_image.toml index 038a2276d..3feec4cf0 100644 --- a/repos/rust-lang/crates_io_og_image.toml +++ b/repos/rust-lang/crates_io_og_image.toml @@ -11,3 +11,7 @@ crates-io = "write" pattern = "main" required-approvals = 0 pr-required = false + +[[environments]] +name = "release" + diff --git a/repos/rust-lang/dyn-upcasting-coercion-initiative.toml b/repos/rust-lang/dyn-upcasting-coercion-initiative.toml index c3a2cb9ca..881670e8b 100644 --- a/repos/rust-lang/dyn-upcasting-coercion-initiative.toml +++ b/repos/rust-lang/dyn-upcasting-coercion-initiative.toml @@ -5,3 +5,7 @@ bots = [] [access.teams] project-dyn-upcasting = "write" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/edition-guide.toml b/repos/rust-lang/edition-guide.toml index a364a5330..1c6845a82 100644 --- a/repos/rust-lang/edition-guide.toml +++ b/repos/rust-lang/edition-guide.toml @@ -11,3 +11,7 @@ edition = "maintain" pattern = "master" ci-checks = ["Success gate"] required-approvals = 0 + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/enzyme.toml b/repos/rust-lang/enzyme.toml index 5cd6a5702..671474be6 100644 --- a/repos/rust-lang/enzyme.toml +++ b/repos/rust-lang/enzyme.toml @@ -20,3 +20,7 @@ pr-required = false [[branch-protections]] pattern = "master" pr-required = false + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/flate2-rs.toml b/repos/rust-lang/flate2-rs.toml index 786d3723a..65489d5dc 100644 --- a/repos/rust-lang/flate2-rs.toml +++ b/repos/rust-lang/flate2-rs.toml @@ -9,3 +9,7 @@ crate-maintainers = 'maintain' [[branch-protections]] pattern = 'main' + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/fls.toml b/repos/rust-lang/fls.toml index 7f22d6236..fb12c637b 100644 --- a/repos/rust-lang/fls.toml +++ b/repos/rust-lang/fls.toml @@ -16,3 +16,7 @@ spec-contributors = "triage" [[branch-protections]] pattern = "main" ci-checks = ["CI finished"] + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/futures-rs.toml b/repos/rust-lang/futures-rs.toml index d3f62f87e..b17eeff43 100644 --- a/repos/rust-lang/futures-rs.toml +++ b/repos/rust-lang/futures-rs.toml @@ -10,3 +10,7 @@ wg-async = "write" [[branch-protections]] pattern = "master" required-approvals = 0 + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/generic-associated-types-initiative.toml b/repos/rust-lang/generic-associated-types-initiative.toml index ce77ff089..b27fb4159 100644 --- a/repos/rust-lang/generic-associated-types-initiative.toml +++ b/repos/rust-lang/generic-associated-types-initiative.toml @@ -6,3 +6,7 @@ bots = [] [access.teams] project-generic-associated-types = "write" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/getopts.toml b/repos/rust-lang/getopts.toml index ddb1956fe..3eaa5dfa5 100644 --- a/repos/rust-lang/getopts.toml +++ b/repos/rust-lang/getopts.toml @@ -6,3 +6,7 @@ bots = [] [access.teams] crate-maintainers = 'maintain' + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/gha-self-hosted.toml b/repos/rust-lang/gha-self-hosted.toml index 5be7d516d..c54d9243d 100644 --- a/repos/rust-lang/gha-self-hosted.toml +++ b/repos/rust-lang/gha-self-hosted.toml @@ -9,3 +9,7 @@ infra = "write" [[branch-protections]] pattern = "main" ci-checks = ["CI finished"] + +[[environments]] +name = "upload" + diff --git a/repos/rust-lang/git2-rs.toml b/repos/rust-lang/git2-rs.toml index b467253ba..cca2dbfe6 100644 --- a/repos/rust-lang/git2-rs.toml +++ b/repos/rust-lang/git2-rs.toml @@ -11,3 +11,7 @@ cargo = "write" pattern = "master" ci-checks = ["Success gate"] required-approvals = 0 + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/glob.toml b/repos/rust-lang/glob.toml index 73e51a483..7d5dbdbb8 100644 --- a/repos/rust-lang/glob.toml +++ b/repos/rust-lang/glob.toml @@ -11,3 +11,7 @@ crate-maintainers = "maintain" pattern = "master" ci-checks = ["success"] required-approvals = 0 + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/hashbrown.toml b/repos/rust-lang/hashbrown.toml index c34cec3f6..1d3981125 100644 --- a/repos/rust-lang/hashbrown.toml +++ b/repos/rust-lang/hashbrown.toml @@ -12,3 +12,7 @@ libs-contributors = "write" pattern = "master" ci-checks = ["conclusion"] required-approvals = 0 + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/impl-trait-initiative.toml b/repos/rust-lang/impl-trait-initiative.toml index 0fe41f697..202be3c60 100644 --- a/repos/rust-lang/impl-trait-initiative.toml +++ b/repos/rust-lang/impl-trait-initiative.toml @@ -6,3 +6,7 @@ bots = [] [access.teams] project-impl-trait = "maintain" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/initiative-template.toml b/repos/rust-lang/initiative-template.toml index 359cbfd0b..dd52fd89b 100644 --- a/repos/rust-lang/initiative-template.toml +++ b/repos/rust-lang/initiative-template.toml @@ -6,3 +6,7 @@ bots = [] [access.teams] lang = "maintain" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/jobserver-rs.toml b/repos/rust-lang/jobserver-rs.toml index 4f1633904..570f27fa6 100644 --- a/repos/rust-lang/jobserver-rs.toml +++ b/repos/rust-lang/jobserver-rs.toml @@ -6,3 +6,7 @@ bots = [] [access.teams] cargo = "write" compiler = "write" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/keyword-generics-initiative.toml b/repos/rust-lang/keyword-generics-initiative.toml index 0d724f4d2..883efc8de 100644 --- a/repos/rust-lang/keyword-generics-initiative.toml +++ b/repos/rust-lang/keyword-generics-initiative.toml @@ -6,3 +6,7 @@ bots = [] [access.teams] project-keyword-generics = "write" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/lang-team.toml b/repos/rust-lang/lang-team.toml index fa44b8848..518d959d2 100644 --- a/repos/rust-lang/lang-team.toml +++ b/repos/rust-lang/lang-team.toml @@ -10,3 +10,7 @@ lang-ops = "maintain" lang-docs = "maintain" types = "maintain" release = "maintain" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/libc.toml b/repos/rust-lang/libc.toml index 8f6c0f21e..c733d8322 100644 --- a/repos/rust-lang/libc.toml +++ b/repos/rust-lang/libc.toml @@ -18,3 +18,7 @@ required-approvals = 0 pattern = 'libc-0.2' ci-checks = ['success'] required-approvals = 0 + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/libz-sys.toml b/repos/rust-lang/libz-sys.toml index 235206997..d53e9b5d4 100644 --- a/repos/rust-lang/libz-sys.toml +++ b/repos/rust-lang/libz-sys.toml @@ -5,3 +5,7 @@ bots = [] [access.teams] crate-maintainers = 'maintain' + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/log.toml b/repos/rust-lang/log.toml index 21b935e81..7a62259fe 100644 --- a/repos/rust-lang/log.toml +++ b/repos/rust-lang/log.toml @@ -9,3 +9,7 @@ crate-maintainers = 'maintain' [access.individuals] Thomasdezeeuw = 'maintain' + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/mdBook.toml b/repos/rust-lang/mdBook.toml index 2fab1e871..9071d9f5f 100644 --- a/repos/rust-lang/mdBook.toml +++ b/repos/rust-lang/mdBook.toml @@ -30,3 +30,10 @@ crates = [ ] workflow-filename = "deploy.yml" environment = "publish" + +[[environments]] +name = "github-pages" + +[[environments]] +name = "publish" + diff --git a/repos/rust-lang/measureme.toml b/repos/rust-lang/measureme.toml index 69c7fa50f..b4be858cc 100644 --- a/repos/rust-lang/measureme.toml +++ b/repos/rust-lang/measureme.toml @@ -18,3 +18,7 @@ ci-checks = ["success"] crates = ["analyzeme", "decodeme", "measureme"] workflow-filename = "publish.yml" environment = "publish" + +[[environments]] +name = "publish" + diff --git a/repos/rust-lang/negative-impls-initiative.toml b/repos/rust-lang/negative-impls-initiative.toml index e18d18fe4..c621b0f70 100644 --- a/repos/rust-lang/negative-impls-initiative.toml +++ b/repos/rust-lang/negative-impls-initiative.toml @@ -5,3 +5,7 @@ bots = [] [access.teams] project-negative-impls = "write" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/packed_simd.toml b/repos/rust-lang/packed_simd.toml index 5afba5c93..f99229a04 100644 --- a/repos/rust-lang/packed_simd.toml +++ b/repos/rust-lang/packed_simd.toml @@ -9,3 +9,7 @@ libs-contributors = "write" libs-api = "write" libs = "write" project-portable-simd = "write" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/pkg-config-rs.toml b/repos/rust-lang/pkg-config-rs.toml index 9e97f4d3d..8ac8c5331 100644 --- a/repos/rust-lang/pkg-config-rs.toml +++ b/repos/rust-lang/pkg-config-rs.toml @@ -9,3 +9,7 @@ crate-maintainers = "maintain" [access.individuals] sdroege = "write" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/polonius.toml b/repos/rust-lang/polonius.toml index 6e26190a6..9c4893e9d 100644 --- a/repos/rust-lang/polonius.toml +++ b/repos/rust-lang/polonius.toml @@ -5,3 +5,7 @@ bots = [] [access.teams] wg-polonius = "write" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/pontoon.toml b/repos/rust-lang/pontoon.toml index 9b8acd49a..77f22c292 100644 --- a/repos/rust-lang/pontoon.toml +++ b/repos/rust-lang/pontoon.toml @@ -5,3 +5,7 @@ bots = [] [access.teams] infra = "write" + +[[environments]] +name = "rust-pontoon" + diff --git a/repos/rust-lang/portable-simd.toml b/repos/rust-lang/portable-simd.toml index 392fc744f..de87f56ea 100644 --- a/repos/rust-lang/portable-simd.toml +++ b/repos/rust-lang/portable-simd.toml @@ -9,3 +9,7 @@ project-portable-simd = "write" [[branch-protections]] pattern = "master" required-approvals = 0 + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/project-const-generics.toml b/repos/rust-lang/project-const-generics.toml index 055570687..e91c35ac4 100644 --- a/repos/rust-lang/project-const-generics.toml +++ b/repos/rust-lang/project-const-generics.toml @@ -6,3 +6,7 @@ bots = [] [access.teams] project-const-generics = "maintain" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/project-goal-reference-expansion.toml b/repos/rust-lang/project-goal-reference-expansion.toml index b97f19dcd..618d1b956 100644 --- a/repos/rust-lang/project-goal-reference-expansion.toml +++ b/repos/rust-lang/project-goal-reference-expansion.toml @@ -6,3 +6,7 @@ bots = [] [access.teams] project-goal-reference-expansion = "write" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/project-group-template.toml b/repos/rust-lang/project-group-template.toml index c0362858d..e26ef4bc2 100644 --- a/repos/rust-lang/project-group-template.toml +++ b/repos/rust-lang/project-group-template.toml @@ -5,3 +5,7 @@ bots = [] [access.teams] leadership-council = "maintain" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/project-stable-mir.toml b/repos/rust-lang/project-stable-mir.toml index d13c95f84..4e977d856 100644 --- a/repos/rust-lang/project-stable-mir.toml +++ b/repos/rust-lang/project-stable-mir.toml @@ -8,3 +8,7 @@ project-stable-mir = "write" [[branch-protections]] pattern = "main" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/regex.toml b/repos/rust-lang/regex.toml index 3ec044072..11fc388ec 100644 --- a/repos/rust-lang/regex.toml +++ b/repos/rust-lang/regex.toml @@ -6,3 +6,7 @@ bots = [] [access.teams] regex = "maintain" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/rfcbot-rs.toml b/repos/rust-lang/rfcbot-rs.toml index ba580d663..ccaea2f46 100644 --- a/repos/rust-lang/rfcbot-rs.toml +++ b/repos/rust-lang/rfcbot-rs.toml @@ -6,3 +6,7 @@ bots = [] [access.teams] infra = "write" + +[[environments]] +name = "rfcbot-rs" + diff --git a/repos/rust-lang/rfcs.toml b/repos/rust-lang/rfcs.toml index c57d7acb3..92fb804e0 100644 --- a/repos/rust-lang/rfcs.toml +++ b/repos/rust-lang/rfcs.toml @@ -10,3 +10,7 @@ all = "write" [[branch-protections]] pattern = "master" required-approvals = 0 + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/rust-analyzer.toml b/repos/rust-lang/rust-analyzer.toml index ca1e38cef..ad4a19911 100644 --- a/repos/rust-lang/rust-analyzer.toml +++ b/repos/rust-lang/rust-analyzer.toml @@ -12,3 +12,7 @@ rust-analyzer-contributors = "triage" pattern = "master" ci-checks = ["conclusion"] required-approvals = 0 + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/rust-bindgen.toml b/repos/rust-lang/rust-bindgen.toml index a99eb5226..a398e0d61 100644 --- a/repos/rust-lang/rust-bindgen.toml +++ b/repos/rust-lang/rust-bindgen.toml @@ -11,3 +11,7 @@ wg-bindgen = "write" pattern = "main" required-approvals = 0 ci-checks = ["success"] + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/rust-by-example.toml b/repos/rust-lang/rust-by-example.toml index 12966b4e6..019bf6186 100644 --- a/repos/rust-lang/rust-by-example.toml +++ b/repos/rust-lang/rust-by-example.toml @@ -6,3 +6,7 @@ bots = ["rustbot"] [access.teams] rust-by-example = "write" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/rust-clippy.toml b/repos/rust-lang/rust-clippy.toml index de1ee62ec..d9ac952a6 100644 --- a/repos/rust-lang/rust-clippy.toml +++ b/repos/rust-lang/rust-clippy.toml @@ -17,3 +17,7 @@ ci-checks = [ "conclusion_changelog", ] required-approvals = 0 + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/rust-enhanced.toml b/repos/rust-lang/rust-enhanced.toml index 99c26cfb0..9f04d3a8c 100644 --- a/repos/rust-lang/rust-enhanced.toml +++ b/repos/rust-lang/rust-enhanced.toml @@ -5,3 +5,7 @@ bots = [] [access.teams] devtools = "write" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/rust-forge.toml b/repos/rust-lang/rust-forge.toml index 9a6c7d450..9c7155514 100644 --- a/repos/rust-lang/rust-forge.toml +++ b/repos/rust-lang/rust-forge.toml @@ -26,3 +26,7 @@ rustc-dev-guide = "maintain" [[branch-protections]] pattern = "master" ci-checks = ["test"] + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/rust-lang.github.io.toml b/repos/rust-lang/rust-lang.github.io.toml index 77e6e1195..d72b7ee50 100644 --- a/repos/rust-lang/rust-lang.github.io.toml +++ b/repos/rust-lang/rust-lang.github.io.toml @@ -5,3 +5,7 @@ bots = [] [access.teams] infra = "write" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/rust-log-analyzer.toml b/repos/rust-lang/rust-log-analyzer.toml index 77fb36b58..d2cd6a8f5 100644 --- a/repos/rust-lang/rust-log-analyzer.toml +++ b/repos/rust-lang/rust-log-analyzer.toml @@ -10,3 +10,7 @@ infra = "write" pattern = "master" ci-checks = ["CI"] required-approvals = 0 + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/rust-project-goals.toml b/repos/rust-lang/rust-project-goals.toml index 7901b4bb1..3d17215dd 100644 --- a/repos/rust-lang/rust-project-goals.toml +++ b/repos/rust-lang/rust-project-goals.toml @@ -16,3 +16,7 @@ libs-api = "maintain" edition = "maintain" goals = "maintain" goal-owners = "maintain" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/rust.toml b/repos/rust-lang/rust.toml index 0328c6629..c32d63f77 100644 --- a/repos/rust-lang/rust.toml +++ b/repos/rust-lang/rust.toml @@ -82,3 +82,7 @@ merge-bots = ["rust-timer"] [[branch-protections]] pattern = "perf-tmp" merge-bots = ["rust-timer"] + +[[environments]] +name = "bors" + diff --git a/repos/rust-lang/rustc-demangle.toml b/repos/rust-lang/rustc-demangle.toml index eadc52a9b..95952a4b7 100644 --- a/repos/rust-lang/rustc-demangle.toml +++ b/repos/rust-lang/rustc-demangle.toml @@ -16,3 +16,7 @@ ci-checks = [ "Rustfmt", "Publish Documentation" ] + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/rustc-dev-guide.toml b/repos/rust-lang/rustc-dev-guide.toml index 1344e873e..1b04b28d7 100644 --- a/repos/rust-lang/rustc-dev-guide.toml +++ b/repos/rust-lang/rustc-dev-guide.toml @@ -28,3 +28,7 @@ required-approvals = 0 # This branch contains the old commit graph, to keep commit references working [[branch-protections]] pattern = "master-old" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/rustc-pr-tracking.toml b/repos/rust-lang/rustc-pr-tracking.toml index f4eeddbf2..927aebf66 100644 --- a/repos/rust-lang/rustc-pr-tracking.toml +++ b/repos/rust-lang/rustc-pr-tracking.toml @@ -6,3 +6,7 @@ bots = ["highfive"] [access.teams] release = "write" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/rustc-reading-club.toml b/repos/rust-lang/rustc-reading-club.toml index 5472cf380..44aa7fa95 100644 --- a/repos/rust-lang/rustc-reading-club.toml +++ b/repos/rust-lang/rustc-reading-club.toml @@ -4,3 +4,7 @@ description = "Rust Code Reading Clubs" bots = [] [access.teams] + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/rustfmt.toml b/repos/rust-lang/rustfmt.toml index c42cb414a..cc982b78d 100644 --- a/repos/rust-lang/rustfmt.toml +++ b/repos/rust-lang/rustfmt.toml @@ -34,3 +34,7 @@ required-approvals = 0 [[branch-protections]] pattern = "rust-1.*" required-approvals = 0 + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/rustlings.toml b/repos/rust-lang/rustlings.toml index e63bae030..d9651810e 100644 --- a/repos/rust-lang/rustlings.toml +++ b/repos/rust-lang/rustlings.toml @@ -6,3 +6,7 @@ bots = [] [access.teams] rustlings = "maintain" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/rustup-components-history.toml b/repos/rust-lang/rustup-components-history.toml index 70d23300c..c2b01ae3f 100644 --- a/repos/rust-lang/rustup-components-history.toml +++ b/repos/rust-lang/rustup-components-history.toml @@ -6,3 +6,7 @@ bots = [] [access.teams] infra = "write" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/rustup.toml b/repos/rust-lang/rustup.toml index 3aa65a2a8..867c85800 100644 --- a/repos/rust-lang/rustup.toml +++ b/repos/rust-lang/rustup.toml @@ -10,3 +10,7 @@ rustup = "maintain" [[branch-protections]] pattern = "main" ci-checks = ["conclusion"] + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/socket2.toml b/repos/rust-lang/socket2.toml index a95476f1b..17eb22014 100644 --- a/repos/rust-lang/socket2.toml +++ b/repos/rust-lang/socket2.toml @@ -24,3 +24,7 @@ ci-checks = [ 'Test (stable)', 'Test (windows)', ] + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/stacker.toml b/repos/rust-lang/stacker.toml index 576185e21..f0f4c7fb5 100644 --- a/repos/rust-lang/stacker.toml +++ b/repos/rust-lang/stacker.toml @@ -5,3 +5,7 @@ bots = [] [access.teams] compiler = "write" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/std-dev-guide.toml b/repos/rust-lang/std-dev-guide.toml index b6b5570e3..214bebd4d 100644 --- a/repos/rust-lang/std-dev-guide.toml +++ b/repos/rust-lang/std-dev-guide.toml @@ -13,3 +13,7 @@ rustc-dev-guide = "write" [[branch-protections]] pattern = "master" ci-checks = ["book-test"] + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/stdarch.toml b/repos/rust-lang/stdarch.toml index ba4a237c7..23433788a 100644 --- a/repos/rust-lang/stdarch.toml +++ b/repos/rust-lang/stdarch.toml @@ -15,3 +15,7 @@ libs-contributors = "write" pattern = "main" ci-checks = ["conclusion"] required-approvals = 0 + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/team.toml b/repos/rust-lang/team.toml index d8cadeaab..e7d06109c 100644 --- a/repos/rust-lang/team.toml +++ b/repos/rust-lang/team.toml @@ -12,3 +12,10 @@ infra = "triage" pattern = "main" ci-checks = ["CI"] dismiss-stale-review = true + +[[environments]] +name = "deploy" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/thanks.toml b/repos/rust-lang/thanks.toml index d74ac49cb..2acc84118 100644 --- a/repos/rust-lang/thanks.toml +++ b/repos/rust-lang/thanks.toml @@ -7,3 +7,7 @@ bots = [] [access.teams] website = "write" infra = "write" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/thorin.toml b/repos/rust-lang/thorin.toml index 31a1a5436..6f8afe0a4 100644 --- a/repos/rust-lang/thorin.toml +++ b/repos/rust-lang/thorin.toml @@ -13,3 +13,7 @@ pattern = "main" crates = ["thorin-dwp", "thorin-dwp-bin"] workflow-filename = "release-plz.yml" environment = "publish" + +[[environments]] +name = "publish" + diff --git a/repos/rust-lang/types-team.toml b/repos/rust-lang/types-team.toml index 39384aa27..5a437e532 100644 --- a/repos/rust-lang/types-team.toml +++ b/repos/rust-lang/types-team.toml @@ -6,3 +6,7 @@ bots = ["rustbot"] [access.teams] types = "write" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/unsafe-code-guidelines.toml b/repos/rust-lang/unsafe-code-guidelines.toml index 0b1741d7b..e2d42fa97 100644 --- a/repos/rust-lang/unsafe-code-guidelines.toml +++ b/repos/rust-lang/unsafe-code-guidelines.toml @@ -6,3 +6,7 @@ bots = ["rustbot", "rfcbot"] [access.teams] opsem = 'maintain' + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/wg-async.toml b/repos/rust-lang/wg-async.toml index 4ff515cf0..b732b696f 100644 --- a/repos/rust-lang/wg-async.toml +++ b/repos/rust-lang/wg-async.toml @@ -10,3 +10,7 @@ wg-async = "write" [[branch-protections]] pattern = "master" required-approvals = 0 + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/wg-macros.toml b/repos/rust-lang/wg-macros.toml index 09d3041b0..97239e6cf 100644 --- a/repos/rust-lang/wg-macros.toml +++ b/repos/rust-lang/wg-macros.toml @@ -8,3 +8,7 @@ wg-macros = "maintain" [[branch-protections]] pattern = "main" + +[[environments]] +name = "github-pages" + diff --git a/repos/rust-lang/www.rust-lang.org.toml b/repos/rust-lang/www.rust-lang.org.toml index 893271590..eaffe6cda 100644 --- a/repos/rust-lang/www.rust-lang.org.toml +++ b/repos/rust-lang/www.rust-lang.org.toml @@ -9,3 +9,16 @@ website = "write" [[branch-protections]] pattern = "main" + +[[environments]] +name = "github-pages" + +[[environments]] +name = "new-rust-www" + +[[environments]] +name = "rust-www-staging" + +[[environments]] +name = "rust-www-try" + diff --git a/repos/rust-lang/zulip_archive.toml b/repos/rust-lang/zulip_archive.toml index 2640be5fe..db42889d2 100644 --- a/repos/rust-lang/zulip_archive.toml +++ b/repos/rust-lang/zulip_archive.toml @@ -6,3 +6,7 @@ bots = [] [access.teams] infra = "write" + +[[environments]] +name = "github-pages" + diff --git a/rust_team_data/src/v1.rs b/rust_team_data/src/v1.rs index 593f52cae..3318e5fac 100644 --- a/rust_team_data/src/v1.rs +++ b/rust_team_data/src/v1.rs @@ -175,6 +175,7 @@ pub struct Repo { pub members: Vec, pub branch_protections: Vec, pub crates: Vec, + pub environments: Vec, pub archived: bool, // This attribute is not synced by sync-team. pub private: bool, @@ -257,6 +258,11 @@ pub struct CratesIoPublishing { pub environment: String, } +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Environment { + pub name: String, +} + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Person { pub name: String, diff --git a/src/main.rs b/src/main.rs index b6bbef7cc..3e09d8b69 100644 --- a/src/main.rs +++ b/src/main.rs @@ -86,6 +86,8 @@ enum Cli { #[arg(long, default_value = "repo")] group_by: DumpIndividualAccessGroupBy, }, + /// Dump all repositories with their environments + DumpEnvironments, /// Encrypt an email address EncryptEmail, /// Decrypt an email address @@ -493,6 +495,19 @@ fn run() -> Result<(), Error> { } } } + Cli::DumpEnvironments => { + for repo in data.all_repos() { + let repo_name = format!("{}/{}", repo.org, repo.name); + if !repo.environments.is_empty() { + println!("{repo_name}:"); + for env in &repo.environments { + println!(" - {}", env.name); + } + } else { + println!("{repo_name}: (no environments)"); + } + } + } Cli::EncryptEmail => { let plain: String = dialoguer::Input::new() .with_prompt("Plaintext address") diff --git a/src/schema.rs b/src/schema.rs index c9e983546..4fcb70391 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -814,6 +814,8 @@ pub(crate) struct Repo { pub branch_protections: Vec, #[serde(default)] pub crates_io_publishing: Vec, + #[serde(default)] + pub environments: Vec, } #[derive(serde_derive::Deserialize, Debug, Clone, PartialEq)] @@ -880,3 +882,9 @@ pub(crate) struct CratesIoPublishing { pub workflow_filename: String, pub environment: String, } + +#[derive(serde_derive::Deserialize, Debug, Clone)] +#[serde(deny_unknown_fields, rename_all = "kebab-case")] +pub(crate) struct Environment { + pub name: String, +} diff --git a/src/static_api.rs b/src/static_api.rs index 45cbef110..e4bdadd1e 100644 --- a/src/static_api.rs +++ b/src/static_api.rs @@ -160,6 +160,13 @@ impl<'a> Generator<'a> { }) }) .collect(), + environments: r + .environments + .iter() + .map(|e| v1::Environment { + name: e.name.clone(), + }) + .collect(), archived, auto_merge_enabled: !managed_by_bors, }; diff --git a/src/validate.rs b/src/validate.rs index 3272042a1..891ded1de 100644 --- a/src/validate.rs +++ b/src/validate.rs @@ -56,6 +56,7 @@ static CHECKS: &[Check)>] = checks![ validate_repos, validate_archived_repos, validate_branch_protections, + validate_environments, validate_trusted_publishing, validate_member_roles, validate_admin_access, @@ -979,6 +980,48 @@ fn validate_archived_repos(data: &Data, errors: &mut Vec) { }); } +/// Validate that environments have valid names (non-empty) +fn validate_environments(data: &Data, errors: &mut Vec) { + wrapper(data.all_repos(), errors, |repo, _| { + for env in &repo.environments { + if env.name.is_empty() { + bail!( + "repo {}/{} has an environment with an empty name", + repo.org, + repo.name + ); + } + // Environment names should not contain special characters that could cause issues + // with GitHub API URLs or shell commands. Forward and backward slashes are particularly + // problematic as they can be interpreted as path separators + //in URLs (/repos/{owner}/{repo}/environments/{name}) and file systems, + //potentially leading to security vulnerabilities or API routing issues. + if env.name.contains('/') || env.name.contains('\\') { + bail!( + "repo {}/{} has an environment '{}' with invalid characters (/, \\)", + repo.org, + repo.name, + env.name + ); + } + } + + // Check for duplicate environment names + let mut seen = HashSet::new(); + for env in &repo.environments { + if !seen.insert(&env.name) { + bail!( + "repo {}/{} has duplicate environment '{}'", + repo.org, + repo.name, + env.name + ); + } + } + Ok(()) + }); +} + /// Validate that branch protections make sense in combination with used bots. fn validate_branch_protections(data: &Data, errors: &mut Vec) { let github_teams = data.github_teams(); diff --git a/sync-team/src/github/api/read.rs b/sync-team/src/github/api/read.rs index 9a4031ec2..5cd314b92 100644 --- a/sync-team/src/github/api/read.rs +++ b/sync-team/src/github/api/read.rs @@ -4,6 +4,7 @@ use crate::github::api::{ }; use anyhow::Context as _; use reqwest::Method; +use rust_team_data::v1::Environment; use std::collections::{HashMap, HashSet}; pub(crate) trait GithubRead { @@ -50,6 +51,10 @@ pub(crate) trait GithubRead { org: &str, repo: &str, ) -> anyhow::Result>; + + /// Get environments for a repository + /// Returns a list of environment names + fn repo_environments(&self, org: &str, repo: &str) -> anyhow::Result>; } pub(crate) struct GitHubApiRead { @@ -435,4 +440,28 @@ impl GithubRead for GitHubApiRead { } Ok(result) } + + fn repo_environments(&self, org: &str, repo: &str) -> anyhow::Result> { + #[derive(serde::Deserialize)] + struct EnvironmentsResponse { + environments: Vec, + } + + let mut environments: Vec = Vec::new(); + + // REST API endpoint for environments + // https://docs.github.com/en/rest/deployments/environments#list-environments + self.client.rest_paginated( + &Method::GET, + &GitHubUrl::repos(org, repo, "environments")?, + |resp: EnvironmentsResponse| { + for env in resp.environments { + environments.push(env); + } + Ok(()) + }, + )?; + + Ok(environments) + } } diff --git a/sync-team/src/github/api/write.rs b/sync-team/src/github/api/write.rs index 439e1eb2b..63b1b3520 100644 --- a/sync-team/src/github/api/write.rs +++ b/sync-team/src/github/api/write.rs @@ -493,4 +493,40 @@ impl GitHubWrite { } Ok(()) } + + /// Create an environment in a repository + pub(crate) fn create_environment( + &self, + org: &str, + repo: &str, + name: &str, + ) -> anyhow::Result<()> { + debug!("Creating environment '{name}' in '{org}/{repo}'"); + if !self.dry_run { + // REST API: PUT /repos/{owner}/{repo}/environments/{environment_name} + // https://docs.github.com/en/rest/deployments/environments#create-or-update-an-environment + let url = GitHubUrl::repos(org, repo, &format!("environments/{}", name))?; + self.client + .send(Method::PUT, &url, &serde_json::json!({}))?; + } + Ok(()) + } + + /// Delete an environment from a repository + pub(crate) fn delete_environment( + &self, + org: &str, + repo: &str, + name: &str, + ) -> anyhow::Result<()> { + debug!("Deleting environment '{name}' from '{org}/{repo}'"); + if !self.dry_run { + // REST API: DELETE /repos/{owner}/{repo}/environments/{environment_name} + // https://docs.github.com/en/rest/deployments/environments#delete-an-environment + let url = GitHubUrl::repos(org, repo, &format!("environments/{}", name))?; + self.client + .send(Method::DELETE, &url, &serde_json::json!({}))?; + } + Ok(()) + } } diff --git a/sync-team/src/github/mod.rs b/sync-team/src/github/mod.rs index 0a0d33f83..cef6eaeef 100644 --- a/sync-team/src/github/mod.rs +++ b/sync-team/src/github/mod.rs @@ -361,12 +361,18 @@ impl SyncGitHub { }, permissions, branch_protections, + environments: expected_repo + .environments + .iter() + .map(|e| e.name.clone()) + .collect(), })); } }; let permission_diffs = self.diff_permissions(expected_repo)?; let branch_protection_diffs = self.diff_branch_protections(&actual_repo, expected_repo)?; + let environment_diffs = self.diff_environments(expected_repo)?; let old_settings = RepoSettings { description: actual_repo.description.clone(), homepage: actual_repo.homepage.clone(), @@ -387,6 +393,7 @@ impl SyncGitHub { settings_diff: (old_settings, new_settings), permission_diffs, branch_protection_diffs, + environment_diffs, })) } @@ -478,6 +485,48 @@ impl SyncGitHub { Ok(branch_protection_diffs) } + fn diff_environments( + &self, + expected_repo: &rust_team_data::v1::Repo, + ) -> anyhow::Result> { + let mut environment_diffs = Vec::new(); + + let actual_environments: HashSet = self + .github + .repo_environments(&expected_repo.org, &expected_repo.name)? + .into_iter() + .map(|e| e.name) + .collect(); + + let expected_environments: HashSet = expected_repo + .environments + .iter() + .map(|e| e.name.clone()) + .collect(); + + // Environments to create (sorted for deterministic output) + let mut to_create: Vec<_> = expected_environments + .difference(&actual_environments) + .cloned() + .collect(); + to_create.sort(); + for env in to_create { + environment_diffs.push(EnvironmentDiff::Create(env)); + } + + // Environments to delete (sorted for deterministic output) + let mut to_delete: Vec<_> = actual_environments + .difference(&expected_environments) + .cloned() + .collect(); + to_delete.sort(); + for env in to_delete { + environment_diffs.push(EnvironmentDiff::Delete(env)); + } + + Ok(environment_diffs) + } + fn expected_role(&self, org: &str, user: u64) -> TeamRole { if let Some(true) = self .org_owners @@ -816,6 +865,7 @@ struct CreateRepoDiff { settings: RepoSettings, permissions: Vec, branch_protections: Vec<(String, api::BranchProtection)>, + environments: Vec, } impl CreateRepoDiff { @@ -834,6 +884,10 @@ impl CreateRepoDiff { .apply(sync, &self.org, &self.name, &repo.node_id)?; } + for env in &self.environments { + sync.create_environment(&self.org, &self.name, env)?; + } + Ok(()) } } @@ -846,6 +900,7 @@ impl std::fmt::Display for CreateRepoDiff { settings, permissions, branch_protections, + environments, } = self; let RepoSettings { @@ -870,6 +925,12 @@ impl std::fmt::Display for CreateRepoDiff { writeln!(&mut f, " {branch_name}")?; log_branch_protection(branch_protection, None, &mut f)?; } + if !environments.is_empty() { + writeln!(f, " Environments:")?; + for env in environments { + writeln!(f, " - {env}")?; + } + } Ok(()) } } @@ -883,6 +944,13 @@ struct UpdateRepoDiff { settings_diff: (RepoSettings, RepoSettings), permission_diffs: Vec, branch_protection_diffs: Vec, + environment_diffs: Vec, +} + +#[derive(Debug)] +enum EnvironmentDiff { + Create(String), + Delete(String), } impl UpdateRepoDiff { @@ -898,11 +966,13 @@ impl UpdateRepoDiff { settings_diff, permission_diffs, branch_protection_diffs, + environment_diffs, } = self; settings_diff.0 == settings_diff.1 && permission_diffs.is_empty() && branch_protection_diffs.is_empty() + && environment_diffs.is_empty() } fn can_be_modified(&self) -> bool { @@ -938,6 +1008,17 @@ impl UpdateRepoDiff { branch_protection.apply(sync, &self.org, &self.name, &self.repo_node_id)?; } + for env_diff in &self.environment_diffs { + match env_diff { + EnvironmentDiff::Create(name) => { + sync.create_environment(&self.org, &self.name, name)?; + } + EnvironmentDiff::Delete(name) => { + sync.delete_environment(&self.org, &self.name, name)?; + } + } + } + if !is_unarchive && self.settings_diff.0 != self.settings_diff.1 { sync.edit_repo(&self.org, &self.name, &self.settings_diff.1)?; } @@ -959,6 +1040,7 @@ impl std::fmt::Display for UpdateRepoDiff { settings_diff, permission_diffs, branch_protection_diffs, + environment_diffs, } = self; writeln!(f, "📝 Editing repo '{org}/{name}':")?; @@ -1006,6 +1088,15 @@ impl std::fmt::Display for UpdateRepoDiff { write!(f, "{branch_protection_diff}")?; } } + if !environment_diffs.is_empty() { + writeln!(f, " Environments:")?; + for env_diff in environment_diffs { + match env_diff { + EnvironmentDiff::Create(name) => writeln!(f, " ➕ Create: {name}")?, + EnvironmentDiff::Delete(name) => writeln!(f, " ❌ Delete: {name}")?, + } + } + } Ok(()) } diff --git a/sync-team/src/github/tests/mod.rs b/sync-team/src/github/tests/mod.rs index c22696329..845c90500 100644 --- a/sync-team/src/github/tests/mod.rs +++ b/sync-team/src/github/tests/mod.rs @@ -1,7 +1,7 @@ use crate::github::tests::test_utils::{ BranchProtectionBuilder, DEFAULT_ORG, DataModel, RepoData, TeamData, }; -use rust_team_data::v1::{BranchProtectionMode, RepoPermission}; +use rust_team_data::v1::{self, BranchProtectionMode, RepoPermission}; mod test_utils; @@ -232,6 +232,7 @@ fn repo_change_description() { ), permission_diffs: [], branch_protection_diffs: [], + environment_diffs: [], }, ), ] @@ -273,6 +274,7 @@ fn repo_change_homepage() { ), permission_diffs: [], branch_protection_diffs: [], + environment_diffs: [], }, ), ] @@ -340,6 +342,7 @@ fn repo_create() { }, ), ], + environments: [], }, ), ] @@ -393,6 +396,7 @@ fn repo_add_member() { }, ], branch_protection_diffs: [], + environment_diffs: [], }, ), ] @@ -446,6 +450,7 @@ fn repo_change_member_permissions() { }, ], branch_protection_diffs: [], + environment_diffs: [], }, ), ] @@ -493,6 +498,7 @@ fn repo_remove_member() { }, ], branch_protection_diffs: [], + environment_diffs: [], }, ), ] @@ -542,6 +548,7 @@ fn repo_add_team() { }, ], branch_protection_diffs: [], + environment_diffs: [], }, ), ] @@ -590,6 +597,7 @@ fn repo_change_team_permissions() { }, ], branch_protection_diffs: [], + environment_diffs: [], }, ), ] @@ -637,6 +645,7 @@ fn repo_remove_team() { }, ], branch_protection_diffs: [], + environment_diffs: [], }, ), ] @@ -675,6 +684,7 @@ fn repo_archive_repo() { ), permission_diffs: [], branch_protection_diffs: [], + environment_diffs: [], }, ), ] @@ -748,6 +758,7 @@ fn repo_add_branch_protection() { ), }, ], + environment_diffs: [], }, ), ] @@ -837,6 +848,7 @@ fn repo_update_branch_protection() { ), }, ], + environment_diffs: [], }, ), ] @@ -889,6 +901,7 @@ fn repo_remove_branch_protection() { ), }, ], + environment_diffs: [], }, ), ] @@ -917,3 +930,175 @@ fn independent_orgs_are_not_synced() { // No members should be removed for independent organizations insta::assert_debug_snapshot!(gh_org_diff, @"[]"); } + +#[test] +fn repo_environment_noop() { + let mut model = DataModel::default(); + model.create_repo( + RepoData::new("repo1") + .environment("production") + .environment("staging"), + ); + let gh = model.gh_model(); + let diff = model.diff_repos(gh); + assert!(diff.is_empty()); +} + +#[test] +fn repo_environment_create() { + let mut model = DataModel::default(); + model.create_repo(RepoData::new("repo1")); + let gh = model.gh_model(); + + model.get_repo("repo1").environments.push(v1::Environment { + name: "production".to_string(), + }); + model.get_repo("repo1").environments.push(v1::Environment { + name: "staging".to_string(), + }); + + let diff = model.diff_repos(gh); + insta::assert_debug_snapshot!(diff, @r#" + [ + Update( + UpdateRepoDiff { + org: "rust-lang", + name: "repo1", + repo_node_id: "0", + settings_diff: ( + RepoSettings { + description: "", + homepage: None, + archived: false, + auto_merge_enabled: false, + }, + RepoSettings { + description: "", + homepage: None, + archived: false, + auto_merge_enabled: false, + }, + ), + permission_diffs: [], + branch_protection_diffs: [], + environment_diffs: [ + Create( + "production", + ), + Create( + "staging", + ), + ], + }, + ), + ] + "#); +} + +#[test] +fn repo_environment_delete() { + let mut model = DataModel::default(); + model.create_repo( + RepoData::new("repo1") + .environment("production") + .environment("staging"), + ); + let gh = model.gh_model(); + + model.get_repo("repo1").environments.clear(); + + let diff = model.diff_repos(gh); + insta::assert_debug_snapshot!(diff, @r#" + [ + Update( + UpdateRepoDiff { + org: "rust-lang", + name: "repo1", + repo_node_id: "0", + settings_diff: ( + RepoSettings { + description: "", + homepage: None, + archived: false, + auto_merge_enabled: false, + }, + RepoSettings { + description: "", + homepage: None, + archived: false, + auto_merge_enabled: false, + }, + ), + permission_diffs: [], + branch_protection_diffs: [], + environment_diffs: [ + Delete( + "production", + ), + Delete( + "staging", + ), + ], + }, + ), + ] + "#); +} + +#[test] +fn repo_environment_update() { + let mut model = DataModel::default(); + model.create_repo( + RepoData::new("repo1") + .environment("production") + .environment("staging"), + ); + let gh = model.gh_model(); + + // Remove staging, keep production, add dev + model.get_repo("repo1").environments = vec![ + v1::Environment { + name: "production".to_string(), + }, + v1::Environment { + name: "dev".to_string(), + }, + ]; + + let diff = model.diff_repos(gh); + insta::assert_debug_snapshot!(diff, @r#" + [ + Update( + UpdateRepoDiff { + org: "rust-lang", + name: "repo1", + repo_node_id: "0", + settings_diff: ( + RepoSettings { + description: "", + homepage: None, + archived: false, + auto_merge_enabled: false, + }, + RepoSettings { + description: "", + homepage: None, + archived: false, + auto_merge_enabled: false, + }, + ), + permission_diffs: [], + branch_protection_diffs: [], + environment_diffs: [ + Create( + "dev", + ), + Delete( + "staging", + ), + ], + }, + ), + ] + "#); +} diff --git a/sync-team/src/github/tests/test_utils.rs b/sync-team/src/github/tests/test_utils.rs index ba7fdee25..95783ee09 100644 --- a/sync-team/src/github/tests/test_utils.rs +++ b/sync-team/src/github/tests/test_utils.rs @@ -2,8 +2,8 @@ use std::collections::{BTreeSet, HashMap, HashSet}; use derive_builder::Builder; use rust_team_data::v1::{ - self, Bot, BranchProtectionMode, GitHubTeam, MergeBot, Person, RepoPermission, TeamGitHub, - TeamKind, + self, Bot, BranchProtectionMode, Environment, GitHubTeam, MergeBot, Person, RepoPermission, + TeamGitHub, TeamKind, }; use crate::Config; @@ -177,6 +177,10 @@ impl DataModel { } org.branch_protections .insert(repo.name.clone(), protections); + + let environments: Vec = repo.environments.clone().into_iter().collect(); + org.repo_environments + .insert(repo.name.clone(), environments); } if orgs.is_empty() { @@ -305,6 +309,8 @@ pub struct RepoData { pub allow_auto_merge: bool, #[builder(default)] pub branch_protections: Vec, + #[builder(default)] + pub environments: Vec, } impl RepoData { @@ -341,6 +347,7 @@ impl From for v1::Repo { archived, allow_auto_merge, branch_protections, + environments, } = value; Self { org, @@ -352,6 +359,7 @@ impl From for v1::Repo { members: members.clone(), branch_protections, crates: vec![], + environments, archived, private: false, auto_merge_enabled: allow_auto_merge, @@ -379,6 +387,15 @@ impl RepoDataBuilder { self.members = Some(members); self } + + pub fn environment(mut self, name: &str) -> Self { + let mut environments = self.environments.clone().unwrap_or_default(); + environments.push(v1::Environment { + name: name.to_string(), + }); + self.environments = Some(environments); + self + } } #[derive(Clone)] @@ -584,6 +601,15 @@ impl GithubRead for GithubMock { Ok(result) } + + fn repo_environments(&self, org: &str, repo: &str) -> anyhow::Result> { + Ok(self + .get_org(org) + .repo_environments + .get(repo) + .cloned() + .unwrap_or_default()) + } } #[derive(Default)] @@ -601,6 +627,8 @@ struct GithubOrg { repo_members: HashMap, // Repo name -> Vec<(protection ID, branch protection)> branch_protections: HashMap>, + // Repo name -> Vec + repo_environments: HashMap>, } #[derive(Clone)] diff --git a/tests/static-api/_expected/v1/repos.json b/tests/static-api/_expected/v1/repos.json index e5ea0a55c..8667c8c93 100644 --- a/tests/static-api/_expected/v1/repos.json +++ b/tests/static-api/_expected/v1/repos.json @@ -25,6 +25,7 @@ } ], "crates": [], + "environments": [], "archived": true, "private": false, "auto_merge_enabled": true @@ -80,6 +81,7 @@ } } ], + "environments": [], "archived": false, "private": false, "auto_merge_enabled": true diff --git a/tests/static-api/_expected/v1/repos/archived_repo.json b/tests/static-api/_expected/v1/repos/archived_repo.json index a73095a4e..3e7c0fdf7 100644 --- a/tests/static-api/_expected/v1/repos/archived_repo.json +++ b/tests/static-api/_expected/v1/repos/archived_repo.json @@ -23,6 +23,7 @@ } ], "crates": [], + "environments": [], "archived": true, "private": false, "auto_merge_enabled": true diff --git a/tests/static-api/_expected/v1/repos/some_repo.json b/tests/static-api/_expected/v1/repos/some_repo.json index 5889a05ab..8c11f1336 100644 --- a/tests/static-api/_expected/v1/repos/some_repo.json +++ b/tests/static-api/_expected/v1/repos/some_repo.json @@ -49,6 +49,7 @@ } } ], + "environments": [], "archived": false, "private": false, "auto_merge_enabled": true