From b0ecc5855fa441316690f47d33f76f2496c4b1e4 Mon Sep 17 00:00:00 2001 From: Mustaque Ahmed Date: Tue, 2 Dec 2025 23:43:12 +0530 Subject: [PATCH 1/7] feat: configure branches in environments --- docs/toml-schema.md | 20 +- repos/rust-analyzer/metrics.toml | 3 +- .../rust-analyzer.github.io.toml | 3 +- repos/rust-lang-nursery/lazy-static.rs.toml | 3 +- repos/rust-lang-nursery/rust-cookbook.toml | 3 +- .../rust-lang-nursery.github.io.toml | 3 +- repos/rust-lang-nursery/rust-toolstate.toml | 3 +- repos/rust-lang/a-mir-formality.toml | 3 +- repos/rust-lang/annotate-snippets-rs.toml | 3 +- repos/rust-lang/api-guidelines.toml | 3 +- repos/rust-lang/ar_archive_writer.toml | 3 +- repos/rust-lang/areweasyncyet.rs.toml | 3 +- repos/rust-lang/arewewebyet.toml | 3 +- repos/rust-lang/async-book.toml | 3 +- .../async-crashdump-debugging-initiative.toml | 3 +- .../async-fundamentals-initiative.toml | 3 +- repos/rust-lang/backtrace-rs.toml | 3 +- repos/rust-lang/blog.rust-lang.org.toml | 3 +- repos/rust-lang/book.toml | 3 +- repos/rust-lang/bors-kindergarten.toml | 12 +- repos/rust-lang/bors.toml | 7 +- repos/rust-lang/calendar.toml | 3 +- repos/rust-lang/cargo-bisect-rustc.toml | 3 +- repos/rust-lang/cargo.toml | 6 +- repos/rust-lang/cc-rs.toml | 6 +- repos/rust-lang/cfg-if.toml | 3 +- repos/rust-lang/chalk.toml | 3 +- repos/rust-lang/ci-mirrors.toml | 3 +- repos/rust-lang/cmake-rs.toml | 3 +- repos/rust-lang/compiler-team.toml | 3 +- repos/rust-lang/const-eval.toml | 3 +- repos/rust-lang/crates.io.toml | 6 +- repos/rust-lang/crates_io_og_image.toml | 3 +- .../dyn-upcasting-coercion-initiative.toml | 3 +- repos/rust-lang/edition-guide.toml | 3 +- repos/rust-lang/enzyme.toml | 3 +- repos/rust-lang/flate2-rs.toml | 3 +- repos/rust-lang/fls.toml | 3 +- repos/rust-lang/futures-rs.toml | 3 +- .../generic-associated-types-initiative.toml | 3 +- repos/rust-lang/getopts.toml | 3 +- repos/rust-lang/gha-self-hosted.toml | 3 +- repos/rust-lang/git2-rs.toml | 3 +- repos/rust-lang/glob.toml | 3 +- repos/rust-lang/hashbrown.toml | 3 +- repos/rust-lang/impl-trait-initiative.toml | 3 +- repos/rust-lang/initiative-template.toml | 3 +- repos/rust-lang/jobserver-rs.toml | 3 +- .../keyword-generics-initiative.toml | 3 +- repos/rust-lang/lang-team.toml | 3 +- repos/rust-lang/libc.toml | 3 +- repos/rust-lang/libz-sys.toml | 3 +- repos/rust-lang/log.toml | 3 +- repos/rust-lang/mdBook.toml | 6 +- repos/rust-lang/measureme.toml | 3 +- .../rust-lang/negative-impls-initiative.toml | 3 +- repos/rust-lang/packed_simd.toml | 3 +- repos/rust-lang/pkg-config-rs.toml | 3 +- repos/rust-lang/polonius.toml | 3 +- repos/rust-lang/pontoon.toml | 3 +- repos/rust-lang/portable-simd.toml | 3 +- repos/rust-lang/project-const-generics.toml | 3 +- .../project-goal-reference-expansion.toml | 3 +- repos/rust-lang/project-group-template.toml | 3 +- repos/rust-lang/project-stable-mir.toml | 3 +- repos/rust-lang/regex.toml | 3 +- repos/rust-lang/rfcbot-rs.toml | 3 +- repos/rust-lang/rfcs.toml | 3 +- repos/rust-lang/rust-analyzer.toml | 3 +- repos/rust-lang/rust-bindgen.toml | 3 +- repos/rust-lang/rust-by-example.toml | 3 +- repos/rust-lang/rust-clippy.toml | 3 +- repos/rust-lang/rust-enhanced.toml | 3 +- repos/rust-lang/rust-forge.toml | 3 +- repos/rust-lang/rust-lang.github.io.toml | 3 +- repos/rust-lang/rust-log-analyzer.toml | 3 +- repos/rust-lang/rust-project-goals.toml | 3 +- repos/rust-lang/rust.toml | 3 +- repos/rust-lang/rustc-demangle.toml | 3 +- repos/rust-lang/rustc-dev-guide.toml | 3 +- repos/rust-lang/rustc-pr-tracking.toml | 3 +- repos/rust-lang/rustc-reading-club.toml | 3 +- repos/rust-lang/rustfmt.toml | 3 +- repos/rust-lang/rustlings.toml | 3 +- .../rust-lang/rustup-components-history.toml | 3 +- repos/rust-lang/rustup.toml | 3 +- repos/rust-lang/socket2.toml | 3 +- repos/rust-lang/stacker.toml | 3 +- repos/rust-lang/std-dev-guide.toml | 3 +- repos/rust-lang/stdarch.toml | 3 +- repos/rust-lang/team.toml | 6 +- repos/rust-lang/thanks.toml | 3 +- repos/rust-lang/thorin.toml | 3 +- repos/rust-lang/types-team.toml | 3 +- repos/rust-lang/unsafe-code-guidelines.toml | 3 +- repos/rust-lang/wg-async.toml | 3 +- repos/rust-lang/wg-macros.toml | 3 +- repos/rust-lang/www.rust-lang.org.toml | 12 +- repos/rust-lang/zulip_archive.toml | 3 +- rust_team_data/src/v1.rs | 5 +- src/main.rs | 8 +- src/schema.rs | 7 +- src/static_api.rs | 23 ++- src/validate.rs | 43 +++-- sync-team/src/github/api/read.rs | 82 +++++++-- sync-team/src/github/api/write.rs | 171 +++++++++++++++++- sync-team/src/github/mod.rs | 110 ++++++++--- sync-team/src/github/tests/mod.rs | 93 ++++++++-- sync-team/src/github/tests/test_utils.rs | 31 +++- tests/static-api/_expected/v1/repos.json | 4 +- .../_expected/v1/repos/archived_repo.json | 2 +- .../_expected/v1/repos/some_repo.json | 2 +- 112 files changed, 605 insertions(+), 327 deletions(-) diff --git a/docs/toml-schema.md b/docs/toml-schema.md index 59b8b865d..b1f35ebfc 100644 --- a/docs/toml-schema.md +++ b/docs/toml-schema.md @@ -433,16 +433,22 @@ 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. +GitHub environments are used to configure deployment protection rules and secrets for GitHub Actions workflows. This repository can manage environment names and deployment branch policies for repositories. ```toml # The environments in this repository (optional) -[[environments]] -# The name of the environment (required) -name = "production" - -[[environments]] -name = "staging" +# Use table-of-tables syntax where the key is the environment name +[environments.production] +# List of branch names that can deploy to this environment (optional) +# If empty or omitted, any branch can deploy +branches = ["main", "release/*"] + +[environments.staging] +# Empty branches list means any branch can deploy +branches = [] + +[environments.development] +# No branches specified - any branch can deploy ``` ### Crates.io trusted publishing diff --git a/repos/rust-analyzer/metrics.toml b/repos/rust-analyzer/metrics.toml index 184b730ae..4f8bd8f77 100644 --- a/repos/rust-analyzer/metrics.toml +++ b/repos/rust-analyzer/metrics.toml @@ -7,6 +7,5 @@ bots = [] [access.teams] rust-analyzer = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-analyzer/rust-analyzer.github.io.toml b/repos/rust-analyzer/rust-analyzer.github.io.toml index 4fa203d09..2a82b89ee 100644 --- a/repos/rust-analyzer/rust-analyzer.github.io.toml +++ b/repos/rust-analyzer/rust-analyzer.github.io.toml @@ -7,6 +7,5 @@ bots = [] [access.teams] rust-analyzer = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang-nursery/lazy-static.rs.toml b/repos/rust-lang-nursery/lazy-static.rs.toml index 8f9b76296..d3325e2f6 100644 --- a/repos/rust-lang-nursery/lazy-static.rs.toml +++ b/repos/rust-lang-nursery/lazy-static.rs.toml @@ -6,6 +6,5 @@ bots = [] [access.teams] libs = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang-nursery/rust-cookbook.toml b/repos/rust-lang-nursery/rust-cookbook.toml index f07a53f43..2e2287602 100644 --- a/repos/rust-lang-nursery/rust-cookbook.toml +++ b/repos/rust-lang-nursery/rust-cookbook.toml @@ -7,6 +7,5 @@ bots = [] cookbook = "write" lang-docs = "write" -[[environments]] -name = "github-pages" +[environments.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 940adf1ea..11f33ead2 100644 --- a/repos/rust-lang-nursery/rust-lang-nursery.github.io.toml +++ b/repos/rust-lang-nursery/rust-lang-nursery.github.io.toml @@ -6,6 +6,5 @@ bots = [] [access.teams] infra = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang-nursery/rust-toolstate.toml b/repos/rust-lang-nursery/rust-toolstate.toml index 3458af98a..3f5ab5fc1 100644 --- a/repos/rust-lang-nursery/rust-toolstate.toml +++ b/repos/rust-lang-nursery/rust-toolstate.toml @@ -11,6 +11,5 @@ infra = "write" pattern = "master" pr-required = false -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/a-mir-formality.toml b/repos/rust-lang/a-mir-formality.toml index d71280527..b1d3b187e 100644 --- a/repos/rust-lang/a-mir-formality.toml +++ b/repos/rust-lang/a-mir-formality.toml @@ -9,6 +9,5 @@ lang-ops = "maintain" lang-docs = "maintain" types = "maintain" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/annotate-snippets-rs.toml b/repos/rust-lang/annotate-snippets-rs.toml index f1b23d337..fa9842f57 100644 --- a/repos/rust-lang/annotate-snippets-rs.toml +++ b/repos/rust-lang/annotate-snippets-rs.toml @@ -17,5 +17,4 @@ crates = ["annotate-snippets"] workflow-filename = "release.yml" environment = "publish" -[[environments]] -name = "publish" +[environments.publish] diff --git a/repos/rust-lang/api-guidelines.toml b/repos/rust-lang/api-guidelines.toml index 039a68344..290737714 100644 --- a/repos/rust-lang/api-guidelines.toml +++ b/repos/rust-lang/api-guidelines.toml @@ -8,6 +8,5 @@ bots = [] libs-api = "write" libs = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/ar_archive_writer.toml b/repos/rust-lang/ar_archive_writer.toml index 91a29c976..c700f2721 100644 --- a/repos/rust-lang/ar_archive_writer.toml +++ b/repos/rust-lang/ar_archive_writer.toml @@ -6,6 +6,5 @@ bots = [] [access.teams] compiler = "write" -[[environments]] -name = "publish" +[environments.publish] diff --git a/repos/rust-lang/areweasyncyet.rs.toml b/repos/rust-lang/areweasyncyet.rs.toml index a9ea6a0b9..e649f3cdb 100644 --- a/repos/rust-lang/areweasyncyet.rs.toml +++ b/repos/rust-lang/areweasyncyet.rs.toml @@ -10,6 +10,5 @@ wg-async = "write" [access.individuals] upsuper = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/arewewebyet.toml b/repos/rust-lang/arewewebyet.toml index f7e0764de..3d5c134e8 100644 --- a/repos/rust-lang/arewewebyet.toml +++ b/repos/rust-lang/arewewebyet.toml @@ -10,6 +10,5 @@ arewewebyet = "maintain" [[branch-protections]] pattern = 'master' -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/async-book.toml b/repos/rust-lang/async-book.toml index a098ae611..98f497ec6 100644 --- a/repos/rust-lang/async-book.toml +++ b/repos/rust-lang/async-book.toml @@ -7,6 +7,5 @@ bots = [] [access.teams] wg-async = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/async-crashdump-debugging-initiative.toml b/repos/rust-lang/async-crashdump-debugging-initiative.toml index 076c0c54c..d3b1be8a3 100644 --- a/repos/rust-lang/async-crashdump-debugging-initiative.toml +++ b/repos/rust-lang/async-crashdump-debugging-initiative.toml @@ -6,6 +6,5 @@ bots = [] [access.teams] project-async-crashdump-debugging = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/async-fundamentals-initiative.toml b/repos/rust-lang/async-fundamentals-initiative.toml index eb6bd982a..416c20b97 100644 --- a/repos/rust-lang/async-fundamentals-initiative.toml +++ b/repos/rust-lang/async-fundamentals-initiative.toml @@ -11,6 +11,5 @@ wg-async = "maintain" pattern = "master" required-approvals = 0 -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/backtrace-rs.toml b/repos/rust-lang/backtrace-rs.toml index 1855b5ecc..d38441d6b 100644 --- a/repos/rust-lang/backtrace-rs.toml +++ b/repos/rust-lang/backtrace-rs.toml @@ -10,6 +10,5 @@ crate-maintainers = 'maintain' [[branch-protections]] pattern = 'master' -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/blog.rust-lang.org.toml b/repos/rust-lang/blog.rust-lang.org.toml index a65fb1718..f19e8152b 100644 --- a/repos/rust-lang/blog.rust-lang.org.toml +++ b/repos/rust-lang/blog.rust-lang.org.toml @@ -17,6 +17,5 @@ ci-checks = [ "build", ] -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/book.toml b/repos/rust-lang/book.toml index 536343a6c..17261691c 100644 --- a/repos/rust-lang/book.toml +++ b/repos/rust-lang/book.toml @@ -12,6 +12,5 @@ pattern = "main" ci-checks = [] required-approvals = 0 -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/bors-kindergarten.toml b/repos/rust-lang/bors-kindergarten.toml index 6877b66d6..54f04a35b 100644 --- a/repos/rust-lang/bors-kindergarten.toml +++ b/repos/rust-lang/bors-kindergarten.toml @@ -7,15 +7,11 @@ bots = ["rustbot"] infra = "write" infra-bors = "write" -[[environments]] -name = "bors" +[environments.bors] -[[environments]] -name = "ci" +[environments.ci] -[[environments]] -name = "production" +[environments.production] -[[environments]] -name = "staging" +[environments.staging] diff --git a/repos/rust-lang/bors.toml b/repos/rust-lang/bors.toml index 224f8bde6..20d2db75b 100644 --- a/repos/rust-lang/bors.toml +++ b/repos/rust-lang/bors.toml @@ -14,9 +14,8 @@ pattern = "main" ci-checks = ["Test", "Test Docker"] required-approvals = 0 -[[environments]] -name = "production" +[environments.production] +branches = ["main"] -[[environments]] -name = "staging" +[environments.staging] diff --git a/repos/rust-lang/calendar.toml b/repos/rust-lang/calendar.toml index a9034a710..388e8f608 100644 --- a/repos/rust-lang/calendar.toml +++ b/repos/rust-lang/calendar.toml @@ -20,6 +20,5 @@ wg-async = "maintain" pattern = "main" pr-required = false -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/cargo-bisect-rustc.toml b/repos/rust-lang/cargo-bisect-rustc.toml index a6bedb28f..9b01b15f1 100644 --- a/repos/rust-lang/cargo-bisect-rustc.toml +++ b/repos/rust-lang/cargo-bisect-rustc.toml @@ -14,6 +14,5 @@ ehuss = "write" pattern = "master" required-approvals = 0 -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/cargo.toml b/repos/rust-lang/cargo.toml index 4361cdf0d..37d368e75 100644 --- a/repos/rust-lang/cargo.toml +++ b/repos/rust-lang/cargo.toml @@ -15,9 +15,7 @@ ci-checks = ["conclusion"] pattern = "rust-1.*" ci-checks = ["conclusion"] -[[environments]] -name = "github-pages" +[environments.github-pages] -[[environments]] -name = "release" +[environments.release] diff --git a/repos/rust-lang/cc-rs.toml b/repos/rust-lang/cc-rs.toml index d768a2f73..ab79e0e92 100644 --- a/repos/rust-lang/cc-rs.toml +++ b/repos/rust-lang/cc-rs.toml @@ -17,9 +17,7 @@ crates = ["cc", "find-msvc-tools"] workflow-filename = "publish.yml" environment = "publish" -[[environments]] -name = "github-pages" +[environments.github-pages] -[[environments]] -name = "publish" +[environments.publish] diff --git a/repos/rust-lang/cfg-if.toml b/repos/rust-lang/cfg-if.toml index 684996fc2..09f4b4ef7 100644 --- a/repos/rust-lang/cfg-if.toml +++ b/repos/rust-lang/cfg-if.toml @@ -6,6 +6,5 @@ bots = [] [access.teams] crate-maintainers = 'maintain' -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/chalk.toml b/repos/rust-lang/chalk.toml index 4c52d532d..a67c287dc 100644 --- a/repos/rust-lang/chalk.toml +++ b/repos/rust-lang/chalk.toml @@ -7,6 +7,5 @@ bots = ["rustbot"] [access.teams] types = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/ci-mirrors.toml b/repos/rust-lang/ci-mirrors.toml index a01c93374..80f0df3d0 100644 --- a/repos/rust-lang/ci-mirrors.toml +++ b/repos/rust-lang/ci-mirrors.toml @@ -12,6 +12,5 @@ pattern = "main" ci-checks = ["Build finished"] dismiss-stale-review = true -[[environments]] -name = "upload" +[environments.upload] diff --git a/repos/rust-lang/cmake-rs.toml b/repos/rust-lang/cmake-rs.toml index 9413f46d1..957c92a9e 100644 --- a/repos/rust-lang/cmake-rs.toml +++ b/repos/rust-lang/cmake-rs.toml @@ -12,6 +12,5 @@ pattern = "master" ci-checks = ['success'] required-approvals = 0 -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/compiler-team.toml b/repos/rust-lang/compiler-team.toml index d77f8cd36..69295ee2c 100644 --- a/repos/rust-lang/compiler-team.toml +++ b/repos/rust-lang/compiler-team.toml @@ -13,6 +13,5 @@ types = "write" pattern = "master" required-approvals = 0 -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/const-eval.toml b/repos/rust-lang/const-eval.toml index 53cf8b769..7dd4a4004 100644 --- a/repos/rust-lang/const-eval.toml +++ b/repos/rust-lang/const-eval.toml @@ -6,6 +6,5 @@ bots = [] [access.teams] wg-const-eval = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/crates.io.toml b/repos/rust-lang/crates.io.toml index 71f2e37ed..492d78c1e 100644 --- a/repos/rust-lang/crates.io.toml +++ b/repos/rust-lang/crates.io.toml @@ -7,9 +7,7 @@ bots = ["renovate", "rustbot", "heroku-deploy-access"] [access.teams] crates-io = "write" -[[environments]] -name = "crates-io" +[environments.crates-io] -[[environments]] -name = "staging-crates-io" +[environments.staging-crates-io] diff --git a/repos/rust-lang/crates_io_og_image.toml b/repos/rust-lang/crates_io_og_image.toml index 3feec4cf0..68e429ba9 100644 --- a/repos/rust-lang/crates_io_og_image.toml +++ b/repos/rust-lang/crates_io_og_image.toml @@ -12,6 +12,5 @@ pattern = "main" required-approvals = 0 pr-required = false -[[environments]] -name = "release" +[environments.release] diff --git a/repos/rust-lang/dyn-upcasting-coercion-initiative.toml b/repos/rust-lang/dyn-upcasting-coercion-initiative.toml index 881670e8b..21b3fe0a4 100644 --- a/repos/rust-lang/dyn-upcasting-coercion-initiative.toml +++ b/repos/rust-lang/dyn-upcasting-coercion-initiative.toml @@ -6,6 +6,5 @@ bots = [] [access.teams] project-dyn-upcasting = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/edition-guide.toml b/repos/rust-lang/edition-guide.toml index 1c6845a82..8d29417c6 100644 --- a/repos/rust-lang/edition-guide.toml +++ b/repos/rust-lang/edition-guide.toml @@ -12,6 +12,5 @@ pattern = "master" ci-checks = ["Success gate"] required-approvals = 0 -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/enzyme.toml b/repos/rust-lang/enzyme.toml index 671474be6..1a2088b4d 100644 --- a/repos/rust-lang/enzyme.toml +++ b/repos/rust-lang/enzyme.toml @@ -21,6 +21,5 @@ pr-required = false pattern = "master" pr-required = false -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/flate2-rs.toml b/repos/rust-lang/flate2-rs.toml index 65489d5dc..638dd6dee 100644 --- a/repos/rust-lang/flate2-rs.toml +++ b/repos/rust-lang/flate2-rs.toml @@ -10,6 +10,5 @@ crate-maintainers = 'maintain' [[branch-protections]] pattern = 'main' -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/fls.toml b/repos/rust-lang/fls.toml index fb12c637b..26e005f68 100644 --- a/repos/rust-lang/fls.toml +++ b/repos/rust-lang/fls.toml @@ -17,6 +17,5 @@ spec-contributors = "triage" pattern = "main" ci-checks = ["CI finished"] -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/futures-rs.toml b/repos/rust-lang/futures-rs.toml index b17eeff43..36aae1a70 100644 --- a/repos/rust-lang/futures-rs.toml +++ b/repos/rust-lang/futures-rs.toml @@ -11,6 +11,5 @@ wg-async = "write" pattern = "master" required-approvals = 0 -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/generic-associated-types-initiative.toml b/repos/rust-lang/generic-associated-types-initiative.toml index b27fb4159..45f75a737 100644 --- a/repos/rust-lang/generic-associated-types-initiative.toml +++ b/repos/rust-lang/generic-associated-types-initiative.toml @@ -7,6 +7,5 @@ bots = [] [access.teams] project-generic-associated-types = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/getopts.toml b/repos/rust-lang/getopts.toml index 3eaa5dfa5..1d48841d0 100644 --- a/repos/rust-lang/getopts.toml +++ b/repos/rust-lang/getopts.toml @@ -7,6 +7,5 @@ bots = [] [access.teams] crate-maintainers = 'maintain' -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/gha-self-hosted.toml b/repos/rust-lang/gha-self-hosted.toml index c54d9243d..5aae39989 100644 --- a/repos/rust-lang/gha-self-hosted.toml +++ b/repos/rust-lang/gha-self-hosted.toml @@ -10,6 +10,5 @@ infra = "write" pattern = "main" ci-checks = ["CI finished"] -[[environments]] -name = "upload" +[environments.upload] diff --git a/repos/rust-lang/git2-rs.toml b/repos/rust-lang/git2-rs.toml index cca2dbfe6..e5ed5d209 100644 --- a/repos/rust-lang/git2-rs.toml +++ b/repos/rust-lang/git2-rs.toml @@ -12,6 +12,5 @@ pattern = "master" ci-checks = ["Success gate"] required-approvals = 0 -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/glob.toml b/repos/rust-lang/glob.toml index 7d5dbdbb8..0d15908ed 100644 --- a/repos/rust-lang/glob.toml +++ b/repos/rust-lang/glob.toml @@ -12,6 +12,5 @@ pattern = "master" ci-checks = ["success"] required-approvals = 0 -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/hashbrown.toml b/repos/rust-lang/hashbrown.toml index 1d3981125..601cf06ad 100644 --- a/repos/rust-lang/hashbrown.toml +++ b/repos/rust-lang/hashbrown.toml @@ -13,6 +13,5 @@ pattern = "master" ci-checks = ["conclusion"] required-approvals = 0 -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/impl-trait-initiative.toml b/repos/rust-lang/impl-trait-initiative.toml index 202be3c60..11350948c 100644 --- a/repos/rust-lang/impl-trait-initiative.toml +++ b/repos/rust-lang/impl-trait-initiative.toml @@ -7,6 +7,5 @@ bots = [] [access.teams] project-impl-trait = "maintain" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/initiative-template.toml b/repos/rust-lang/initiative-template.toml index dd52fd89b..80fe3ef6c 100644 --- a/repos/rust-lang/initiative-template.toml +++ b/repos/rust-lang/initiative-template.toml @@ -7,6 +7,5 @@ bots = [] [access.teams] lang = "maintain" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/jobserver-rs.toml b/repos/rust-lang/jobserver-rs.toml index 570f27fa6..ab5071b76 100644 --- a/repos/rust-lang/jobserver-rs.toml +++ b/repos/rust-lang/jobserver-rs.toml @@ -7,6 +7,5 @@ bots = [] cargo = "write" compiler = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/keyword-generics-initiative.toml b/repos/rust-lang/keyword-generics-initiative.toml index 883efc8de..46b6ce910 100644 --- a/repos/rust-lang/keyword-generics-initiative.toml +++ b/repos/rust-lang/keyword-generics-initiative.toml @@ -7,6 +7,5 @@ bots = [] [access.teams] project-keyword-generics = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/lang-team.toml b/repos/rust-lang/lang-team.toml index 518d959d2..af6f18d83 100644 --- a/repos/rust-lang/lang-team.toml +++ b/repos/rust-lang/lang-team.toml @@ -11,6 +11,5 @@ lang-docs = "maintain" types = "maintain" release = "maintain" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/libc.toml b/repos/rust-lang/libc.toml index c733d8322..116668fb8 100644 --- a/repos/rust-lang/libc.toml +++ b/repos/rust-lang/libc.toml @@ -19,6 +19,5 @@ pattern = 'libc-0.2' ci-checks = ['success'] required-approvals = 0 -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/libz-sys.toml b/repos/rust-lang/libz-sys.toml index d53e9b5d4..563e76964 100644 --- a/repos/rust-lang/libz-sys.toml +++ b/repos/rust-lang/libz-sys.toml @@ -6,6 +6,5 @@ bots = [] [access.teams] crate-maintainers = 'maintain' -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/log.toml b/repos/rust-lang/log.toml index 7a62259fe..18362fb98 100644 --- a/repos/rust-lang/log.toml +++ b/repos/rust-lang/log.toml @@ -10,6 +10,5 @@ crate-maintainers = 'maintain' [access.individuals] Thomasdezeeuw = 'maintain' -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/mdBook.toml b/repos/rust-lang/mdBook.toml index 9071d9f5f..39ec4ea9f 100644 --- a/repos/rust-lang/mdBook.toml +++ b/repos/rust-lang/mdBook.toml @@ -31,9 +31,7 @@ crates = [ workflow-filename = "deploy.yml" environment = "publish" -[[environments]] -name = "github-pages" +[environments.github-pages] -[[environments]] -name = "publish" +[environments.publish] diff --git a/repos/rust-lang/measureme.toml b/repos/rust-lang/measureme.toml index b4be858cc..926c89b3c 100644 --- a/repos/rust-lang/measureme.toml +++ b/repos/rust-lang/measureme.toml @@ -19,6 +19,5 @@ crates = ["analyzeme", "decodeme", "measureme"] workflow-filename = "publish.yml" environment = "publish" -[[environments]] -name = "publish" +[environments.publish] diff --git a/repos/rust-lang/negative-impls-initiative.toml b/repos/rust-lang/negative-impls-initiative.toml index c621b0f70..c27092204 100644 --- a/repos/rust-lang/negative-impls-initiative.toml +++ b/repos/rust-lang/negative-impls-initiative.toml @@ -6,6 +6,5 @@ bots = [] [access.teams] project-negative-impls = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/packed_simd.toml b/repos/rust-lang/packed_simd.toml index f99229a04..34f354a09 100644 --- a/repos/rust-lang/packed_simd.toml +++ b/repos/rust-lang/packed_simd.toml @@ -10,6 +10,5 @@ libs-api = "write" libs = "write" project-portable-simd = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/pkg-config-rs.toml b/repos/rust-lang/pkg-config-rs.toml index 8ac8c5331..05f7253a5 100644 --- a/repos/rust-lang/pkg-config-rs.toml +++ b/repos/rust-lang/pkg-config-rs.toml @@ -10,6 +10,5 @@ crate-maintainers = "maintain" [access.individuals] sdroege = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/polonius.toml b/repos/rust-lang/polonius.toml index 9c4893e9d..5aa4b54f1 100644 --- a/repos/rust-lang/polonius.toml +++ b/repos/rust-lang/polonius.toml @@ -6,6 +6,5 @@ bots = [] [access.teams] wg-polonius = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/pontoon.toml b/repos/rust-lang/pontoon.toml index 77f22c292..ff44674c2 100644 --- a/repos/rust-lang/pontoon.toml +++ b/repos/rust-lang/pontoon.toml @@ -6,6 +6,5 @@ bots = [] [access.teams] infra = "write" -[[environments]] -name = "rust-pontoon" +[environments.rust-pontoon] diff --git a/repos/rust-lang/portable-simd.toml b/repos/rust-lang/portable-simd.toml index de87f56ea..2a54bde19 100644 --- a/repos/rust-lang/portable-simd.toml +++ b/repos/rust-lang/portable-simd.toml @@ -10,6 +10,5 @@ project-portable-simd = "write" pattern = "master" required-approvals = 0 -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/project-const-generics.toml b/repos/rust-lang/project-const-generics.toml index e91c35ac4..48f9aa5c3 100644 --- a/repos/rust-lang/project-const-generics.toml +++ b/repos/rust-lang/project-const-generics.toml @@ -7,6 +7,5 @@ bots = [] [access.teams] project-const-generics = "maintain" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/project-goal-reference-expansion.toml b/repos/rust-lang/project-goal-reference-expansion.toml index 618d1b956..0d02ce266 100644 --- a/repos/rust-lang/project-goal-reference-expansion.toml +++ b/repos/rust-lang/project-goal-reference-expansion.toml @@ -7,6 +7,5 @@ bots = [] [access.teams] project-goal-reference-expansion = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/project-group-template.toml b/repos/rust-lang/project-group-template.toml index e26ef4bc2..87038ac1c 100644 --- a/repos/rust-lang/project-group-template.toml +++ b/repos/rust-lang/project-group-template.toml @@ -6,6 +6,5 @@ bots = [] [access.teams] leadership-council = "maintain" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/project-stable-mir.toml b/repos/rust-lang/project-stable-mir.toml index 4e977d856..d924603bf 100644 --- a/repos/rust-lang/project-stable-mir.toml +++ b/repos/rust-lang/project-stable-mir.toml @@ -9,6 +9,5 @@ project-stable-mir = "write" [[branch-protections]] pattern = "main" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/regex.toml b/repos/rust-lang/regex.toml index 11fc388ec..dd0d7cb69 100644 --- a/repos/rust-lang/regex.toml +++ b/repos/rust-lang/regex.toml @@ -7,6 +7,5 @@ bots = [] [access.teams] regex = "maintain" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/rfcbot-rs.toml b/repos/rust-lang/rfcbot-rs.toml index ccaea2f46..5c9bc0e79 100644 --- a/repos/rust-lang/rfcbot-rs.toml +++ b/repos/rust-lang/rfcbot-rs.toml @@ -7,6 +7,5 @@ bots = [] [access.teams] infra = "write" -[[environments]] -name = "rfcbot-rs" +[environments.rfcbot-rs] diff --git a/repos/rust-lang/rfcs.toml b/repos/rust-lang/rfcs.toml index 92fb804e0..93e698a48 100644 --- a/repos/rust-lang/rfcs.toml +++ b/repos/rust-lang/rfcs.toml @@ -11,6 +11,5 @@ all = "write" pattern = "master" required-approvals = 0 -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/rust-analyzer.toml b/repos/rust-lang/rust-analyzer.toml index ad4a19911..dae9d243e 100644 --- a/repos/rust-lang/rust-analyzer.toml +++ b/repos/rust-lang/rust-analyzer.toml @@ -13,6 +13,5 @@ pattern = "master" ci-checks = ["conclusion"] required-approvals = 0 -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/rust-bindgen.toml b/repos/rust-lang/rust-bindgen.toml index a398e0d61..9b56947f2 100644 --- a/repos/rust-lang/rust-bindgen.toml +++ b/repos/rust-lang/rust-bindgen.toml @@ -12,6 +12,5 @@ pattern = "main" required-approvals = 0 ci-checks = ["success"] -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/rust-by-example.toml b/repos/rust-lang/rust-by-example.toml index 019bf6186..21dfca1e2 100644 --- a/repos/rust-lang/rust-by-example.toml +++ b/repos/rust-lang/rust-by-example.toml @@ -7,6 +7,5 @@ bots = ["rustbot"] [access.teams] rust-by-example = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/rust-clippy.toml b/repos/rust-lang/rust-clippy.toml index d9ac952a6..8a7fb2b17 100644 --- a/repos/rust-lang/rust-clippy.toml +++ b/repos/rust-lang/rust-clippy.toml @@ -18,6 +18,5 @@ ci-checks = [ ] required-approvals = 0 -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/rust-enhanced.toml b/repos/rust-lang/rust-enhanced.toml index 9f04d3a8c..b6d929ce2 100644 --- a/repos/rust-lang/rust-enhanced.toml +++ b/repos/rust-lang/rust-enhanced.toml @@ -6,6 +6,5 @@ bots = [] [access.teams] devtools = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/rust-forge.toml b/repos/rust-lang/rust-forge.toml index 9c7155514..d42ebf9a5 100644 --- a/repos/rust-lang/rust-forge.toml +++ b/repos/rust-lang/rust-forge.toml @@ -27,6 +27,5 @@ rustc-dev-guide = "maintain" pattern = "master" ci-checks = ["test"] -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/rust-lang.github.io.toml b/repos/rust-lang/rust-lang.github.io.toml index d72b7ee50..3008e6131 100644 --- a/repos/rust-lang/rust-lang.github.io.toml +++ b/repos/rust-lang/rust-lang.github.io.toml @@ -6,6 +6,5 @@ bots = [] [access.teams] infra = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/rust-log-analyzer.toml b/repos/rust-lang/rust-log-analyzer.toml index d2cd6a8f5..99f0cd5a7 100644 --- a/repos/rust-lang/rust-log-analyzer.toml +++ b/repos/rust-lang/rust-log-analyzer.toml @@ -11,6 +11,5 @@ pattern = "master" ci-checks = ["CI"] required-approvals = 0 -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/rust-project-goals.toml b/repos/rust-lang/rust-project-goals.toml index 3d17215dd..56cae94d1 100644 --- a/repos/rust-lang/rust-project-goals.toml +++ b/repos/rust-lang/rust-project-goals.toml @@ -17,6 +17,5 @@ edition = "maintain" goals = "maintain" goal-owners = "maintain" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/rust.toml b/repos/rust-lang/rust.toml index c32d63f77..b096fca6e 100644 --- a/repos/rust-lang/rust.toml +++ b/repos/rust-lang/rust.toml @@ -83,6 +83,5 @@ merge-bots = ["rust-timer"] pattern = "perf-tmp" merge-bots = ["rust-timer"] -[[environments]] -name = "bors" +[environments.bors] diff --git a/repos/rust-lang/rustc-demangle.toml b/repos/rust-lang/rustc-demangle.toml index 95952a4b7..30d03dc73 100644 --- a/repos/rust-lang/rustc-demangle.toml +++ b/repos/rust-lang/rustc-demangle.toml @@ -17,6 +17,5 @@ ci-checks = [ "Publish Documentation" ] -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/rustc-dev-guide.toml b/repos/rust-lang/rustc-dev-guide.toml index 1b04b28d7..f003498cf 100644 --- a/repos/rust-lang/rustc-dev-guide.toml +++ b/repos/rust-lang/rustc-dev-guide.toml @@ -29,6 +29,5 @@ required-approvals = 0 [[branch-protections]] pattern = "master-old" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/rustc-pr-tracking.toml b/repos/rust-lang/rustc-pr-tracking.toml index 927aebf66..fdbfdedaa 100644 --- a/repos/rust-lang/rustc-pr-tracking.toml +++ b/repos/rust-lang/rustc-pr-tracking.toml @@ -7,6 +7,5 @@ bots = ["highfive"] [access.teams] release = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/rustc-reading-club.toml b/repos/rust-lang/rustc-reading-club.toml index 44aa7fa95..6424fc2a8 100644 --- a/repos/rust-lang/rustc-reading-club.toml +++ b/repos/rust-lang/rustc-reading-club.toml @@ -5,6 +5,5 @@ bots = [] [access.teams] -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/rustfmt.toml b/repos/rust-lang/rustfmt.toml index cc982b78d..151b808f1 100644 --- a/repos/rust-lang/rustfmt.toml +++ b/repos/rust-lang/rustfmt.toml @@ -35,6 +35,5 @@ required-approvals = 0 pattern = "rust-1.*" required-approvals = 0 -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/rustlings.toml b/repos/rust-lang/rustlings.toml index d9651810e..e1c8afeed 100644 --- a/repos/rust-lang/rustlings.toml +++ b/repos/rust-lang/rustlings.toml @@ -7,6 +7,5 @@ bots = [] [access.teams] rustlings = "maintain" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/rustup-components-history.toml b/repos/rust-lang/rustup-components-history.toml index c2b01ae3f..6cfc5eb8a 100644 --- a/repos/rust-lang/rustup-components-history.toml +++ b/repos/rust-lang/rustup-components-history.toml @@ -7,6 +7,5 @@ bots = [] [access.teams] infra = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/rustup.toml b/repos/rust-lang/rustup.toml index 867c85800..77d23958d 100644 --- a/repos/rust-lang/rustup.toml +++ b/repos/rust-lang/rustup.toml @@ -11,6 +11,5 @@ rustup = "maintain" pattern = "main" ci-checks = ["conclusion"] -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/socket2.toml b/repos/rust-lang/socket2.toml index 17eb22014..3dba53231 100644 --- a/repos/rust-lang/socket2.toml +++ b/repos/rust-lang/socket2.toml @@ -25,6 +25,5 @@ ci-checks = [ 'Test (windows)', ] -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/stacker.toml b/repos/rust-lang/stacker.toml index f0f4c7fb5..970bb50af 100644 --- a/repos/rust-lang/stacker.toml +++ b/repos/rust-lang/stacker.toml @@ -6,6 +6,5 @@ bots = [] [access.teams] compiler = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/std-dev-guide.toml b/repos/rust-lang/std-dev-guide.toml index 214bebd4d..b01ddf372 100644 --- a/repos/rust-lang/std-dev-guide.toml +++ b/repos/rust-lang/std-dev-guide.toml @@ -14,6 +14,5 @@ rustc-dev-guide = "write" pattern = "master" ci-checks = ["book-test"] -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/stdarch.toml b/repos/rust-lang/stdarch.toml index 23433788a..ca29909b7 100644 --- a/repos/rust-lang/stdarch.toml +++ b/repos/rust-lang/stdarch.toml @@ -16,6 +16,5 @@ pattern = "main" ci-checks = ["conclusion"] required-approvals = 0 -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/team.toml b/repos/rust-lang/team.toml index e7d06109c..e8fdef25a 100644 --- a/repos/rust-lang/team.toml +++ b/repos/rust-lang/team.toml @@ -13,9 +13,7 @@ pattern = "main" ci-checks = ["CI"] dismiss-stale-review = true -[[environments]] -name = "deploy" +[environments.deploy] -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/thanks.toml b/repos/rust-lang/thanks.toml index 2acc84118..a021e9aca 100644 --- a/repos/rust-lang/thanks.toml +++ b/repos/rust-lang/thanks.toml @@ -8,6 +8,5 @@ bots = [] website = "write" infra = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/thorin.toml b/repos/rust-lang/thorin.toml index 6f8afe0a4..b78340c08 100644 --- a/repos/rust-lang/thorin.toml +++ b/repos/rust-lang/thorin.toml @@ -14,6 +14,5 @@ crates = ["thorin-dwp", "thorin-dwp-bin"] workflow-filename = "release-plz.yml" environment = "publish" -[[environments]] -name = "publish" +[environments.publish] diff --git a/repos/rust-lang/types-team.toml b/repos/rust-lang/types-team.toml index 5a437e532..ea7ab575f 100644 --- a/repos/rust-lang/types-team.toml +++ b/repos/rust-lang/types-team.toml @@ -7,6 +7,5 @@ bots = ["rustbot"] [access.teams] types = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/unsafe-code-guidelines.toml b/repos/rust-lang/unsafe-code-guidelines.toml index e2d42fa97..1444145ea 100644 --- a/repos/rust-lang/unsafe-code-guidelines.toml +++ b/repos/rust-lang/unsafe-code-guidelines.toml @@ -7,6 +7,5 @@ bots = ["rustbot", "rfcbot"] [access.teams] opsem = 'maintain' -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/wg-async.toml b/repos/rust-lang/wg-async.toml index b732b696f..f2c11560e 100644 --- a/repos/rust-lang/wg-async.toml +++ b/repos/rust-lang/wg-async.toml @@ -11,6 +11,5 @@ wg-async = "write" pattern = "master" required-approvals = 0 -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/wg-macros.toml b/repos/rust-lang/wg-macros.toml index 97239e6cf..20bdbd9ca 100644 --- a/repos/rust-lang/wg-macros.toml +++ b/repos/rust-lang/wg-macros.toml @@ -9,6 +9,5 @@ wg-macros = "maintain" [[branch-protections]] pattern = "main" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/repos/rust-lang/www.rust-lang.org.toml b/repos/rust-lang/www.rust-lang.org.toml index eaffe6cda..4a92e2688 100644 --- a/repos/rust-lang/www.rust-lang.org.toml +++ b/repos/rust-lang/www.rust-lang.org.toml @@ -10,15 +10,11 @@ website = "write" [[branch-protections]] pattern = "main" -[[environments]] -name = "github-pages" +[environments.github-pages] -[[environments]] -name = "new-rust-www" +[environments.new-rust-www] -[[environments]] -name = "rust-www-staging" +[environments.rust-www-staging] -[[environments]] -name = "rust-www-try" +[environments.rust-www-try] diff --git a/repos/rust-lang/zulip_archive.toml b/repos/rust-lang/zulip_archive.toml index db42889d2..b1e513738 100644 --- a/repos/rust-lang/zulip_archive.toml +++ b/repos/rust-lang/zulip_archive.toml @@ -7,6 +7,5 @@ bots = [] [access.teams] infra = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] diff --git a/rust_team_data/src/v1.rs b/rust_team_data/src/v1.rs index b50eeeb52..2bc4b5e58 100644 --- a/rust_team_data/src/v1.rs +++ b/rust_team_data/src/v1.rs @@ -175,7 +175,7 @@ pub struct Repo { pub members: Vec, pub branch_protections: Vec, pub crates: Vec, - pub environments: Vec, + pub environments: IndexMap, pub archived: bool, // This attribute is not synced by sync-team. pub private: bool, @@ -261,7 +261,8 @@ pub struct CratesIoPublishing { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Environment { - pub name: String, + #[serde(default)] + pub branches: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] diff --git a/src/main.rs b/src/main.rs index 3e09d8b69..7afaa143a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -500,8 +500,12 @@ fn run() -> Result<(), Error> { let repo_name = format!("{}/{}", repo.org, repo.name); if !repo.environments.is_empty() { println!("{repo_name}:"); - for env in &repo.environments { - println!(" - {}", env.name); + for (env_name, env) in &repo.environments { + print!(" - {env_name}"); + if !env.branches.is_empty() { + print!(" (branches: {})", env.branches.join(", ")); + } + println!(); } } else { println!("{repo_name}: (no environments)"); diff --git a/src/schema.rs b/src/schema.rs index fbed3397b..d0fc0b1f1 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -3,7 +3,7 @@ pub(crate) use crate::permissions::Permissions; use anyhow::{bail, format_err, Error}; use serde::de::{Deserialize, Deserializer}; use serde_untagged::UntaggedEnumVisitor; -use std::collections::{BTreeSet, HashMap, HashSet}; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; #[derive(serde_derive::Deserialize, Debug)] #[serde(deny_unknown_fields, rename_all = "kebab-case")] @@ -814,7 +814,7 @@ pub(crate) struct Repo { #[serde(default)] pub crates_io_publishing: Vec, #[serde(default)] - pub environments: Vec, + pub environments: BTreeMap, } #[derive(serde_derive::Deserialize, Debug, Clone, PartialEq)] @@ -887,5 +887,6 @@ pub(crate) struct CratesIoPublishing { #[derive(serde_derive::Deserialize, Debug, Clone)] #[serde(deny_unknown_fields, rename_all = "kebab-case")] pub(crate) struct Environment { - pub name: String, + #[serde(default)] + pub branches: Vec, } diff --git a/src/static_api.rs b/src/static_api.rs index 53e896ec1..1ba8ae315 100644 --- a/src/static_api.rs +++ b/src/static_api.rs @@ -161,13 +161,22 @@ impl<'a> Generator<'a> { }) }) .collect(), - environments: r - .environments - .iter() - .map(|e| v1::Environment { - name: e.name.clone(), - }) - .collect(), + environments: { + let mut envs: Vec<_> = r + .environments + .iter() + .map(|(name, env)| { + ( + name.clone(), + v1::Environment { + branches: env.branches.clone(), + }, + ) + }) + .collect(); + envs.sort_by(|a, b| a.0.cmp(&b.0)); + envs.into_iter().collect() + }, archived, auto_merge_enabled: !managed_by_bors, }; diff --git a/src/validate.rs b/src/validate.rs index 534232d3f..8de26b56f 100644 --- a/src/validate.rs +++ b/src/validate.rs @@ -1042,11 +1042,11 @@ fn validate_archived_repos(data: &Data, errors: &mut Vec) { }); } -/// Validate that environments have valid names (non-empty) +/// Validate that environments have valid names (non-empty) and branches fn validate_environments(data: &Data, errors: &mut Vec) { wrapper(data.all_repos(), errors, |repo, _| { - for env in &repo.environments { - if env.name.is_empty() { + for (env_name, env) in &repo.environments { + if env_name.is_empty() { bail!( "repo {}/{} has an environment with an empty name", repo.org, @@ -1058,28 +1058,39 @@ fn validate_environments(data: &Data, errors: &mut Vec) { // 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('\\') { + if env_name.contains('/') || env_name.contains('\\') { bail!( "repo {}/{} has an environment '{}' with invalid characters (/, \\)", repo.org, repo.name, - env.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 - ); + // Validate branch names are not empty and not duplicated + let mut seen_branches = HashSet::new(); + for branch in &env.branches { + if branch.is_empty() { + bail!( + "repo {}/{} environment '{}' has an empty branch name", + repo.org, + repo.name, + env_name + ); + } + if !seen_branches.insert(branch) { + bail!( + "repo {}/{} environment '{}' has duplicate branch name '{}'", + repo.org, + repo.name, + env_name, + branch + ); + } } } + + // No need to check for duplicate environment names since HashMap keys are unique Ok(()) }); } diff --git a/sync-team/src/github/api/read.rs b/sync-team/src/github/api/read.rs index 5cd314b92..d4aeadb0e 100644 --- a/sync-team/src/github/api/read.rs +++ b/sync-team/src/github/api/read.rs @@ -53,8 +53,12 @@ pub(crate) trait GithubRead { ) -> anyhow::Result>; /// Get environments for a repository - /// Returns a list of environment names - fn repo_environments(&self, org: &str, repo: &str) -> anyhow::Result>; + /// Returns a map of environment names to their Environment data + fn repo_environments( + &self, + org: &str, + repo: &str, + ) -> anyhow::Result>; } pub(crate) struct GitHubApiRead { @@ -441,27 +445,83 @@ impl GithubRead for GitHubApiRead { Ok(result) } - fn repo_environments(&self, org: &str, repo: &str) -> anyhow::Result> { + fn repo_environments( + &self, + org: &str, + repo: &str, + ) -> anyhow::Result> { + #[derive(serde::Deserialize)] + struct ProtectionRule { + #[serde(rename = "type")] + rule_type: String, + } + + #[derive(serde::Deserialize)] + struct GitHubEnvironment { + name: String, + protection_rules: Vec, + } + #[derive(serde::Deserialize)] struct EnvironmentsResponse { - environments: Vec, + environments: Vec, + } + + #[derive(serde::Deserialize)] + struct BranchPolicy { + name: String, } - let mut environments: Vec = Vec::new(); + #[derive(serde::Deserialize)] + struct BranchPoliciesResponse { + branch_policies: Vec, + } + + let mut env_infos = Vec::new(); - // REST API endpoint for environments - // https://docs.github.com/en/rest/deployments/environments#list-environments + // Fetch all environments with their protection_rules metadata + // REST API: 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); - } + env_infos.extend(resp.environments); Ok(()) }, )?; - Ok(environments) + // For each environment, fetch deployment branch policies if they exist + // REST API: https://docs.github.com/en/rest/deployments/branch-policies#list-deployment-branch-policies + env_infos + .into_iter() + .map(|env_info| { + // Check if branch policies exist by looking at protection_rules metadata + let has_branch_policies = env_info + .protection_rules + .iter() + .any(|rule| rule.rule_type == "branch_policy"); + + let branches = if has_branch_policies { + let mut branches = Vec::new(); + self.client.rest_paginated( + &Method::GET, + &GitHubUrl::repos( + org, + repo, + &format!("environments/{}/deployment-branch-policies", env_info.name), + )?, + |resp: BranchPoliciesResponse| { + branches.extend(resp.branch_policies.into_iter().map(|p| p.name)); + Ok(()) + }, + )?; + branches + } else { + Vec::new() + }; + + Ok((env_info.name, Environment { branches })) + }) + .collect() } } diff --git a/sync-team/src/github/api/write.rs b/sync-team/src/github/api/write.rs index 63b1b3520..7091efa54 100644 --- a/sync-team/src/github/api/write.rs +++ b/sync-team/src/github/api/write.rs @@ -1,5 +1,6 @@ use log::debug; use reqwest::Method; +use std::collections::HashSet; use crate::github::api::url::GitHubUrl; use crate::github::api::{ @@ -9,6 +10,12 @@ use crate::github::api::{ }; use crate::utils::ResponseExt; +#[derive(Debug)] +struct BranchPolicyInfo { + id: u64, + name: String, +} + pub(crate) struct GitHubWrite { client: HttpClient, dry_run: bool, @@ -500,14 +507,172 @@ impl GitHubWrite { org: &str, repo: &str, name: &str, + branches: &[String], + ) -> anyhow::Result<()> { + debug!( + "Creating environment '{name}' in '{org}/{repo}' with branches: {:?}", + branches + ); + self.upsert_environment(org, repo, name, branches) + } + + /// Update an environment in a repository + pub(crate) fn update_environment( + &self, + org: &str, + repo: &str, + name: &str, + branches: &[String], + ) -> anyhow::Result<()> { + debug!( + "Updating environment '{name}' in '{org}/{repo}' with branches: {:?}", + branches + ); + self.upsert_environment(org, repo, name, branches) + } + + /// Internal helper to create or update an environment + fn upsert_environment( + &self, + org: &str, + repo: &str, + name: &str, + branches: &[String], ) -> 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!({}))?; + + let body = if branches.is_empty() { + serde_json::json!({ + "deployment_branch_policy": null + }) + } else { + serde_json::json!({ + "deployment_branch_policy": { + "protected_branches": false, + "custom_branch_policies": true + } + }) + }; + + self.client.send(Method::PUT, &url, &body)?; + + // Always sync branch policies to ensure cleanup of old policies + self.set_environment_branch_policies(org, repo, name, branches)?; + } + Ok(()) + } + + /// Get existing branch policies for an environment + fn get_environment_branch_policies( + &self, + org: &str, + repo: &str, + environment: &str, + ) -> anyhow::Result> { + #[derive(serde::Deserialize)] + struct BranchPolicy { + id: u64, + name: String, + } + + #[derive(serde::Deserialize)] + struct BranchPoliciesResponse { + branch_policies: Vec, + } + + let url = GitHubUrl::repos( + org, + repo, + &format!("environments/{}/deployment-branch-policies", environment), + )?; + + let response: BranchPoliciesResponse = + self.client.req(Method::GET, &url)?.send()?.json()?; + + Ok(response + .branch_policies + .into_iter() + .map(|bp| BranchPolicyInfo { + id: bp.id, + name: bp.name, + }) + .collect()) + } + + /// Delete a specific branch policy by ID + fn delete_environment_branch_policy( + &self, + org: &str, + repo: &str, + environment: &str, + policy_id: u64, + ) -> anyhow::Result<()> { + let url = GitHubUrl::repos( + org, + repo, + &format!( + "environments/{}/deployment-branch-policies/{}", + environment, policy_id + ), + )?; + self.client + .send(Method::DELETE, &url, &serde_json::json!({}))?; + Ok(()) + } + + /// Set custom branch policies for an environment + /// This method properly handles updates by: + /// 1. Fetching all existing policies + /// 2. Deleting policies that are no longer needed + /// 3. Adding new policies that don't exist + fn set_environment_branch_policies( + &self, + org: &str, + repo: &str, + environment: &str, + branches: &[String], + ) -> anyhow::Result<()> { + // 1. Fetch existing policies + let existing_policies = self.get_environment_branch_policies(org, repo, environment)?; + + let existing_branches: HashSet = + existing_policies.iter().map(|p| p.name.clone()).collect(); + let new_branches: HashSet = branches.iter().cloned().collect(); + + // 2. Delete policies that are no longer needed + for policy in &existing_policies { + if !new_branches.contains(&policy.name) { + debug!( + "Deleting branch policy '{}' (id: {}) from environment '{}' in '{}/{}'", + policy.name, policy.id, environment, org, repo + ); + self.delete_environment_branch_policy(org, repo, environment, policy.id)?; + } + } + + // 3. Add new policies that don't exist yet + for branch in branches { + if !existing_branches.contains(branch) { + debug!( + "Adding branch policy '{}' to environment '{}' in '{}/{}'", + branch, environment, org, repo + ); + let url = GitHubUrl::repos( + org, + repo, + &format!("environments/{}/deployment-branch-policies", environment), + )?; + self.client.send( + Method::POST, + &url, + &serde_json::json!({ + "name": branch + }), + )?; + } } Ok(()) } diff --git a/sync-team/src/github/mod.rs b/sync-team/src/github/mod.rs index cef6eaeef..a0740c496 100644 --- a/sync-team/src/github/mod.rs +++ b/sync-team/src/github/mod.rs @@ -364,7 +364,7 @@ impl SyncGitHub { environments: expected_repo .environments .iter() - .map(|e| e.name.clone()) + .map(|(name, env)| (name.clone(), env.branches.clone())) .collect(), })); } @@ -491,32 +491,46 @@ impl SyncGitHub { ) -> anyhow::Result> { let mut environment_diffs = Vec::new(); - let actual_environments: HashSet = self + let actual_environments = self .github - .repo_environments(&expected_repo.org, &expected_repo.name)? - .into_iter() - .map(|e| e.name) - .collect(); + .repo_environments(&expected_repo.org, &expected_repo.name)?; - let expected_environments: HashSet = expected_repo - .environments - .iter() - .map(|e| e.name.clone()) - .collect(); + let expected_env_names: HashSet = + expected_repo.environments.keys().cloned().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)); + let actual_env_names: HashSet = actual_environments.keys().cloned().collect(); + + // Environments to create or update (sorted for deterministic output) + let mut to_create_or_update: Vec<_> = expected_repo.environments.iter().collect(); + to_create_or_update.sort_by_key(|(name, _)| name.as_str()); + + for (env_name, expected_env) in to_create_or_update { + match actual_environments.get(env_name) { + Some(actual_env) if actual_env.branches == expected_env.branches => { + // No change needed + continue; + } + Some(actual_env) => { + // Environment exists but branches differ - update it + environment_diffs.push(EnvironmentDiff::Update( + env_name.clone(), + actual_env.branches.clone(), + expected_env.branches.clone(), + )); + } + None => { + // Environment doesn't exist - create it + environment_diffs.push(EnvironmentDiff::Create( + env_name.clone(), + expected_env.branches.clone(), + )); + } + } } // Environments to delete (sorted for deterministic output) - let mut to_delete: Vec<_> = actual_environments - .difference(&expected_environments) + let mut to_delete: Vec<_> = actual_env_names + .difference(&expected_env_names) .cloned() .collect(); to_delete.sort(); @@ -865,7 +879,7 @@ struct CreateRepoDiff { settings: RepoSettings, permissions: Vec, branch_protections: Vec<(String, api::BranchProtection)>, - environments: Vec, + environments: Vec<(String, Vec)>, } impl CreateRepoDiff { @@ -884,8 +898,8 @@ impl CreateRepoDiff { .apply(sync, &self.org, &self.name, &repo.node_id)?; } - for env in &self.environments { - sync.create_environment(&self.org, &self.name, env)?; + for (env_name, branches) in &self.environments { + sync.create_environment(&self.org, &self.name, env_name, branches)?; } Ok(()) @@ -927,8 +941,11 @@ impl std::fmt::Display for CreateRepoDiff { } if !environments.is_empty() { writeln!(f, " Environments:")?; - for env in environments { - writeln!(f, " - {env}")?; + for (env_name, branches) in environments { + writeln!(f, " - {env_name}")?; + if !branches.is_empty() { + writeln!(f, " Branches: {}", branches.join(", "))?; + } } } Ok(()) @@ -949,7 +966,8 @@ struct UpdateRepoDiff { #[derive(Debug)] enum EnvironmentDiff { - Create(String), + Create(String, Vec), + Update(String, Vec, Vec), Delete(String), } @@ -1010,8 +1028,11 @@ impl UpdateRepoDiff { for env_diff in &self.environment_diffs { match env_diff { - EnvironmentDiff::Create(name) => { - sync.create_environment(&self.org, &self.name, name)?; + EnvironmentDiff::Create(name, branches) => { + sync.create_environment(&self.org, &self.name, name, branches)?; + } + EnvironmentDiff::Update(name, _old_branches, new_branches) => { + sync.update_environment(&self.org, &self.name, name, new_branches)?; } EnvironmentDiff::Delete(name) => { sync.delete_environment(&self.org, &self.name, name)?; @@ -1092,7 +1113,36 @@ impl std::fmt::Display for UpdateRepoDiff { writeln!(f, " Environments:")?; for env_diff in environment_diffs { match env_diff { - EnvironmentDiff::Create(name) => writeln!(f, " ➕ Create: {name}")?, + EnvironmentDiff::Create(name, branches) => { + writeln!(f, " ➕ Create: {name}")?; + if !branches.is_empty() { + writeln!(f, " Branches: {}", branches.join(", "))?; + } + } + EnvironmentDiff::Update(name, old_branches, new_branches) => { + writeln!(f, " 🔄 Update: {name}")?; + let old_set: HashSet<_> = old_branches.iter().collect(); + let new_set: HashSet<_> = new_branches.iter().collect(); + + let mut added: Vec<_> = + new_set.difference(&old_set).map(|s| s.as_str()).collect(); + let mut removed: Vec<_> = + old_set.difference(&new_set).map(|s| s.as_str()).collect(); + + // Sort for deterministic output + added.sort(); + removed.sort(); + + if !added.is_empty() { + writeln!(f, " Adding branches: {}", added.join(", "))?; + } + if !removed.is_empty() { + writeln!(f, " Removing branches: {}", removed.join(", "))?; + } + if added.is_empty() && removed.is_empty() { + writeln!(f, " No branch changes")?; + } + } EnvironmentDiff::Delete(name) => writeln!(f, " ❌ Delete: {name}")?, } } diff --git a/sync-team/src/github/tests/mod.rs b/sync-team/src/github/tests/mod.rs index 845c90500..6a132c318 100644 --- a/sync-team/src/github/tests/mod.rs +++ b/sync-team/src/github/tests/mod.rs @@ -950,12 +950,14 @@ fn repo_environment_create() { 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(), - }); + model.get_repo("repo1").environments.insert( + "production".to_string(), + v1::Environment { branches: vec![] }, + ); + model + .get_repo("repo1") + .environments + .insert("staging".to_string(), v1::Environment { branches: vec![] }); let diff = model.diff_repos(gh); insta::assert_debug_snapshot!(diff, @r#" @@ -984,9 +986,11 @@ fn repo_environment_create() { environment_diffs: [ Create( "production", + [], ), Create( "staging", + [], ), ], }, @@ -1056,14 +1060,15 @@ fn repo_environment_update() { 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(), - }, - ]; + model.get_repo("repo1").environments.clear(); + model.get_repo("repo1").environments.insert( + "production".to_string(), + v1::Environment { branches: vec![] }, + ); + model + .get_repo("repo1") + .environments + .insert("dev".to_string(), v1::Environment { branches: vec![] }); let diff = model.diff_repos(gh); insta::assert_debug_snapshot!(diff, @r#" @@ -1092,6 +1097,7 @@ fn repo_environment_update() { environment_diffs: [ Create( "dev", + [], ), Delete( "staging", @@ -1102,3 +1108,62 @@ fn repo_environment_update() { ] "#); } + +#[test] +fn repo_environment_update_branches() { + let mut model = DataModel::default(); + model.create_repo( + RepoData::new("repo1").environment_with_branches("production", &["main", "release/*"]), + ); + let gh = model.gh_model(); + + // Update branches for production environment + model.get_repo("repo1").environments.insert( + "production".to_string(), + v1::Environment { + branches: vec!["main".to_string(), "stable".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: [ + Update( + "production", + [ + "main", + "release/*", + ], + [ + "main", + "stable", + ], + ), + ], + }, + ), + ] + "#); +} diff --git a/sync-team/src/github/tests/test_utils.rs b/sync-team/src/github/tests/test_utils.rs index 95783ee09..fe6f8ad5c 100644 --- a/sync-team/src/github/tests/test_utils.rs +++ b/sync-team/src/github/tests/test_utils.rs @@ -178,7 +178,8 @@ impl DataModel { org.branch_protections .insert(repo.name.clone(), protections); - let environments: Vec = repo.environments.clone().into_iter().collect(); + let environments: HashMap = + repo.environments.clone().into_iter().collect(); org.repo_environments .insert(repo.name.clone(), environments); } @@ -310,7 +311,7 @@ pub struct RepoData { #[builder(default)] pub branch_protections: Vec, #[builder(default)] - pub environments: Vec, + pub environments: indexmap::IndexMap, } impl RepoData { @@ -390,9 +391,19 @@ impl RepoDataBuilder { 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(), - }); + environments.insert(name.to_string(), v1::Environment { branches: vec![] }); + self.environments = Some(environments); + self + } + + pub fn environment_with_branches(mut self, name: &str, branches: &[&str]) -> Self { + let mut environments = self.environments.clone().unwrap_or_default(); + environments.insert( + name.to_string(), + v1::Environment { + branches: branches.iter().map(|s| s.to_string()).collect(), + }, + ); self.environments = Some(environments); self } @@ -602,7 +613,11 @@ impl GithubRead for GithubMock { Ok(result) } - fn repo_environments(&self, org: &str, repo: &str) -> anyhow::Result> { + fn repo_environments( + &self, + org: &str, + repo: &str, + ) -> anyhow::Result> { Ok(self .get_org(org) .repo_environments @@ -627,8 +642,8 @@ struct GithubOrg { repo_members: HashMap, // Repo name -> Vec<(protection ID, branch protection)> branch_protections: HashMap>, - // Repo name -> Vec - repo_environments: HashMap>, + // Repo name -> HashMap + repo_environments: HashMap>, } #[derive(Clone)] diff --git a/tests/static-api/_expected/v1/repos.json b/tests/static-api/_expected/v1/repos.json index 4dee8934c..4cffac8a9 100644 --- a/tests/static-api/_expected/v1/repos.json +++ b/tests/static-api/_expected/v1/repos.json @@ -25,7 +25,7 @@ } ], "crates": [], - "environments": [], + "environments": {}, "archived": true, "private": false, "auto_merge_enabled": true @@ -83,7 +83,7 @@ "trusted_publishing_only": true } ], - "environments": [], + "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 3e7c0fdf7..6c00ae967 100644 --- a/tests/static-api/_expected/v1/repos/archived_repo.json +++ b/tests/static-api/_expected/v1/repos/archived_repo.json @@ -23,7 +23,7 @@ } ], "crates": [], - "environments": [], + "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 603e136d7..7202e9423 100644 --- a/tests/static-api/_expected/v1/repos/some_repo.json +++ b/tests/static-api/_expected/v1/repos/some_repo.json @@ -51,7 +51,7 @@ "trusted_publishing_only": true } ], - "environments": [], + "environments": {}, "archived": false, "private": false, "auto_merge_enabled": true From 752e1730047767be721da03a8fb21e227f099afe Mon Sep 17 00:00:00 2001 From: Mustaque Ahmed Date: Wed, 10 Dec 2025 09:13:28 +0530 Subject: [PATCH 2/7] feat: update `branches` array in repos toml files --- repos/rust-lang-nursery/rust-cookbook.toml | 1 + repos/rust-lang/a-mir-formality.toml | 1 + repos/rust-lang/ar_archive_writer.toml | 1 + repos/rust-lang/areweasyncyet.rs.toml | 1 + repos/rust-lang/async-book.toml | 1 + repos/rust-lang/async-fundamentals-initiative.toml | 1 + repos/rust-lang/blog.rust-lang.org.toml | 1 + repos/rust-lang/book.toml | 1 + repos/rust-lang/bors.toml | 1 + repos/rust-lang/calendar.toml | 1 + repos/rust-lang/cargo-bisect-rustc.toml | 1 + repos/rust-lang/cargo.toml | 1 + repos/rust-lang/cc-rs.toml | 1 + repos/rust-lang/fls.toml | 1 + repos/rust-lang/lang-team.toml | 1 + repos/rust-lang/libc.toml | 1 + repos/rust-lang/mdBook.toml | 1 + repos/rust-lang/measureme.toml | 1 + repos/rust-lang/polonius.toml | 1 + repos/rust-lang/rfcs.toml | 1 + repos/rust-lang/rust-forge.toml | 1 + repos/rust-lang/rust-project-goals.toml | 1 + repos/rust-lang/rust.toml | 1 + repos/rust-lang/rustfmt.toml | 1 + repos/rust-lang/rustlings.toml | 1 + repos/rust-lang/team.toml | 2 ++ repos/rust-lang/thanks.toml | 1 + repos/rust-lang/thorin.toml | 1 + repos/rust-lang/unsafe-code-guidelines.toml | 1 + repos/rust-lang/wg-async.toml | 1 + repos/rust-lang/wg-macros.toml | 1 + repos/rust-lang/www.rust-lang.org.toml | 1 + 32 files changed, 33 insertions(+) diff --git a/repos/rust-lang-nursery/rust-cookbook.toml b/repos/rust-lang-nursery/rust-cookbook.toml index 2e2287602..a5e5ab79a 100644 --- a/repos/rust-lang-nursery/rust-cookbook.toml +++ b/repos/rust-lang-nursery/rust-cookbook.toml @@ -8,4 +8,5 @@ cookbook = "write" lang-docs = "write" [environments.github-pages] +branches = ["gh-pages"] diff --git a/repos/rust-lang/a-mir-formality.toml b/repos/rust-lang/a-mir-formality.toml index b1d3b187e..cd6a81bf8 100644 --- a/repos/rust-lang/a-mir-formality.toml +++ b/repos/rust-lang/a-mir-formality.toml @@ -10,4 +10,5 @@ lang-docs = "maintain" types = "maintain" [environments.github-pages] +branches = ["main"] diff --git a/repos/rust-lang/ar_archive_writer.toml b/repos/rust-lang/ar_archive_writer.toml index c700f2721..42cfce85c 100644 --- a/repos/rust-lang/ar_archive_writer.toml +++ b/repos/rust-lang/ar_archive_writer.toml @@ -7,4 +7,5 @@ bots = [] compiler = "write" [environments.publish] +branches = ["*", "master"] diff --git a/repos/rust-lang/areweasyncyet.rs.toml b/repos/rust-lang/areweasyncyet.rs.toml index e649f3cdb..fbe15deb8 100644 --- a/repos/rust-lang/areweasyncyet.rs.toml +++ b/repos/rust-lang/areweasyncyet.rs.toml @@ -11,4 +11,5 @@ wg-async = "write" upsuper = "write" [environments.github-pages] +branches = ["master"] diff --git a/repos/rust-lang/async-book.toml b/repos/rust-lang/async-book.toml index 98f497ec6..0c19383f7 100644 --- a/repos/rust-lang/async-book.toml +++ b/repos/rust-lang/async-book.toml @@ -8,4 +8,5 @@ bots = [] wg-async = "write" [environments.github-pages] +branches = ["master"] diff --git a/repos/rust-lang/async-fundamentals-initiative.toml b/repos/rust-lang/async-fundamentals-initiative.toml index 416c20b97..7e03382e1 100644 --- a/repos/rust-lang/async-fundamentals-initiative.toml +++ b/repos/rust-lang/async-fundamentals-initiative.toml @@ -12,4 +12,5 @@ pattern = "master" required-approvals = 0 [environments.github-pages] +branches = ["master"] diff --git a/repos/rust-lang/blog.rust-lang.org.toml b/repos/rust-lang/blog.rust-lang.org.toml index f19e8152b..113e7da1e 100644 --- a/repos/rust-lang/blog.rust-lang.org.toml +++ b/repos/rust-lang/blog.rust-lang.org.toml @@ -18,4 +18,5 @@ ci-checks = [ ] [environments.github-pages] +branches = ["main"] diff --git a/repos/rust-lang/book.toml b/repos/rust-lang/book.toml index 17261691c..b9e44af46 100644 --- a/repos/rust-lang/book.toml +++ b/repos/rust-lang/book.toml @@ -13,4 +13,5 @@ ci-checks = [] required-approvals = 0 [environments.github-pages] +branches = ["gh-pages"] diff --git a/repos/rust-lang/bors.toml b/repos/rust-lang/bors.toml index 20d2db75b..5abddaa69 100644 --- a/repos/rust-lang/bors.toml +++ b/repos/rust-lang/bors.toml @@ -18,4 +18,5 @@ required-approvals = 0 branches = ["main"] [environments.staging] +branches = ["main"] diff --git a/repos/rust-lang/calendar.toml b/repos/rust-lang/calendar.toml index 388e8f608..4da311b2b 100644 --- a/repos/rust-lang/calendar.toml +++ b/repos/rust-lang/calendar.toml @@ -21,4 +21,5 @@ pattern = "main" pr-required = false [environments.github-pages] +branches = ["main"] diff --git a/repos/rust-lang/cargo-bisect-rustc.toml b/repos/rust-lang/cargo-bisect-rustc.toml index 9b01b15f1..e81034d69 100644 --- a/repos/rust-lang/cargo-bisect-rustc.toml +++ b/repos/rust-lang/cargo-bisect-rustc.toml @@ -15,4 +15,5 @@ pattern = "master" required-approvals = 0 [environments.github-pages] +branches = ["gh-pages", "master"] diff --git a/repos/rust-lang/cargo.toml b/repos/rust-lang/cargo.toml index 37d368e75..c3bbba033 100644 --- a/repos/rust-lang/cargo.toml +++ b/repos/rust-lang/cargo.toml @@ -18,4 +18,5 @@ ci-checks = ["conclusion"] [environments.github-pages] [environments.release] +branches = ["*"] diff --git a/repos/rust-lang/cc-rs.toml b/repos/rust-lang/cc-rs.toml index ab79e0e92..d1ed4f1f3 100644 --- a/repos/rust-lang/cc-rs.toml +++ b/repos/rust-lang/cc-rs.toml @@ -20,4 +20,5 @@ environment = "publish" [environments.github-pages] [environments.publish] +branches = ["main"] diff --git a/repos/rust-lang/fls.toml b/repos/rust-lang/fls.toml index 26e005f68..504d3992e 100644 --- a/repos/rust-lang/fls.toml +++ b/repos/rust-lang/fls.toml @@ -18,4 +18,5 @@ pattern = "main" ci-checks = ["CI finished"] [environments.github-pages] +branches = ["gh-readonly-queue/main/*", "main"] diff --git a/repos/rust-lang/lang-team.toml b/repos/rust-lang/lang-team.toml index af6f18d83..d30f52776 100644 --- a/repos/rust-lang/lang-team.toml +++ b/repos/rust-lang/lang-team.toml @@ -12,4 +12,5 @@ types = "maintain" release = "maintain" [environments.github-pages] +branches = ["gh-pages", "master"] diff --git a/repos/rust-lang/libc.toml b/repos/rust-lang/libc.toml index 116668fb8..cb3a24966 100644 --- a/repos/rust-lang/libc.toml +++ b/repos/rust-lang/libc.toml @@ -20,4 +20,5 @@ ci-checks = ['success'] required-approvals = 0 [environments.github-pages] +branches = ["gh-pages", "main", "master"] diff --git a/repos/rust-lang/mdBook.toml b/repos/rust-lang/mdBook.toml index 39ec4ea9f..ee2c47256 100644 --- a/repos/rust-lang/mdBook.toml +++ b/repos/rust-lang/mdBook.toml @@ -34,4 +34,5 @@ environment = "publish" [environments.github-pages] [environments.publish] +branches = ["master"] diff --git a/repos/rust-lang/measureme.toml b/repos/rust-lang/measureme.toml index 926c89b3c..bfd800b82 100644 --- a/repos/rust-lang/measureme.toml +++ b/repos/rust-lang/measureme.toml @@ -20,4 +20,5 @@ workflow-filename = "publish.yml" environment = "publish" [environments.publish] +branches = ["*", "master"] diff --git a/repos/rust-lang/polonius.toml b/repos/rust-lang/polonius.toml index 5aa4b54f1..7ccc5e95b 100644 --- a/repos/rust-lang/polonius.toml +++ b/repos/rust-lang/polonius.toml @@ -7,4 +7,5 @@ bots = [] wg-polonius = "write" [environments.github-pages] +branches = ["master"] diff --git a/repos/rust-lang/rfcs.toml b/repos/rust-lang/rfcs.toml index 93e698a48..e4e07a12f 100644 --- a/repos/rust-lang/rfcs.toml +++ b/repos/rust-lang/rfcs.toml @@ -12,4 +12,5 @@ pattern = "master" required-approvals = 0 [environments.github-pages] +branches = ["master"] diff --git a/repos/rust-lang/rust-forge.toml b/repos/rust-lang/rust-forge.toml index d42ebf9a5..874690390 100644 --- a/repos/rust-lang/rust-forge.toml +++ b/repos/rust-lang/rust-forge.toml @@ -28,4 +28,5 @@ pattern = "master" ci-checks = ["test"] [environments.github-pages] +branches = ["master"] diff --git a/repos/rust-lang/rust-project-goals.toml b/repos/rust-lang/rust-project-goals.toml index 56cae94d1..4cfd0e8f0 100644 --- a/repos/rust-lang/rust-project-goals.toml +++ b/repos/rust-lang/rust-project-goals.toml @@ -18,4 +18,5 @@ goals = "maintain" goal-owners = "maintain" [environments.github-pages] +branches = ["main"] diff --git a/repos/rust-lang/rust.toml b/repos/rust-lang/rust.toml index b096fca6e..897d00a99 100644 --- a/repos/rust-lang/rust.toml +++ b/repos/rust-lang/rust.toml @@ -84,4 +84,5 @@ pattern = "perf-tmp" merge-bots = ["rust-timer"] [environments.bors] +branches = ["auto", "automation/bors/try", "try", "try-perf"] diff --git a/repos/rust-lang/rustfmt.toml b/repos/rust-lang/rustfmt.toml index 151b808f1..d151318bc 100644 --- a/repos/rust-lang/rustfmt.toml +++ b/repos/rust-lang/rustfmt.toml @@ -36,4 +36,5 @@ pattern = "rust-1.*" required-approvals = 0 [environments.github-pages] +branches = ["main"] diff --git a/repos/rust-lang/rustlings.toml b/repos/rust-lang/rustlings.toml index e1c8afeed..b46000763 100644 --- a/repos/rust-lang/rustlings.toml +++ b/repos/rust-lang/rustlings.toml @@ -8,4 +8,5 @@ bots = [] rustlings = "maintain" [environments.github-pages] +branches = ["gh-pages", "main"] diff --git a/repos/rust-lang/team.toml b/repos/rust-lang/team.toml index e8fdef25a..39491462a 100644 --- a/repos/rust-lang/team.toml +++ b/repos/rust-lang/team.toml @@ -14,6 +14,8 @@ ci-checks = ["CI"] dismiss-stale-review = true [environments.deploy] +branches = ["gh-readonly-queue/main/*", "main"] [environments.github-pages] +branches = ["main"] diff --git a/repos/rust-lang/thanks.toml b/repos/rust-lang/thanks.toml index a021e9aca..7a57d2848 100644 --- a/repos/rust-lang/thanks.toml +++ b/repos/rust-lang/thanks.toml @@ -9,4 +9,5 @@ website = "write" infra = "write" [environments.github-pages] +branches = ["master"] diff --git a/repos/rust-lang/thorin.toml b/repos/rust-lang/thorin.toml index b78340c08..02f51e944 100644 --- a/repos/rust-lang/thorin.toml +++ b/repos/rust-lang/thorin.toml @@ -15,4 +15,5 @@ workflow-filename = "release-plz.yml" environment = "publish" [environments.publish] +branches = ["main"] diff --git a/repos/rust-lang/unsafe-code-guidelines.toml b/repos/rust-lang/unsafe-code-guidelines.toml index 1444145ea..c6133e54c 100644 --- a/repos/rust-lang/unsafe-code-guidelines.toml +++ b/repos/rust-lang/unsafe-code-guidelines.toml @@ -8,4 +8,5 @@ bots = ["rustbot", "rfcbot"] opsem = 'maintain' [environments.github-pages] +branches = ["master"] diff --git a/repos/rust-lang/wg-async.toml b/repos/rust-lang/wg-async.toml index f2c11560e..6c5b84920 100644 --- a/repos/rust-lang/wg-async.toml +++ b/repos/rust-lang/wg-async.toml @@ -12,4 +12,5 @@ pattern = "master" required-approvals = 0 [environments.github-pages] +branches = ["master"] diff --git a/repos/rust-lang/wg-macros.toml b/repos/rust-lang/wg-macros.toml index 20bdbd9ca..908c3aa67 100644 --- a/repos/rust-lang/wg-macros.toml +++ b/repos/rust-lang/wg-macros.toml @@ -10,4 +10,5 @@ wg-macros = "maintain" pattern = "main" [environments.github-pages] +branches = ["main"] diff --git a/repos/rust-lang/www.rust-lang.org.toml b/repos/rust-lang/www.rust-lang.org.toml index 4a92e2688..0392aafb4 100644 --- a/repos/rust-lang/www.rust-lang.org.toml +++ b/repos/rust-lang/www.rust-lang.org.toml @@ -11,6 +11,7 @@ website = "write" pattern = "main" [environments.github-pages] +branches = ["main"] [environments.new-rust-www] From d626fd41c52666adddaa23e9e4d33125f3280468 Mon Sep 17 00:00:00 2001 From: Mustaque Ahmed Date: Wed, 10 Dec 2025 19:25:00 +0530 Subject: [PATCH 3/7] fix: ordering issue --- sync-team/src/github/mod.rs | 40 +++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/sync-team/src/github/mod.rs b/sync-team/src/github/mod.rs index a0740c496..eefbc82b5 100644 --- a/sync-team/src/github/mod.rs +++ b/sync-team/src/github/mod.rs @@ -489,28 +489,28 @@ impl SyncGitHub { &self, expected_repo: &rust_team_data::v1::Repo, ) -> anyhow::Result> { - let mut environment_diffs = Vec::new(); - let actual_environments = self .github .repo_environments(&expected_repo.org, &expected_repo.name)?; let expected_env_names: HashSet = expected_repo.environments.keys().cloned().collect(); - let actual_env_names: HashSet = actual_environments.keys().cloned().collect(); - // Environments to create or update (sorted for deterministic output) - let mut to_create_or_update: Vec<_> = expected_repo.environments.iter().collect(); - to_create_or_update.sort_by_key(|(name, _)| name.as_str()); + let mut environment_diffs = Vec::new(); + + // Process environments to create or update (sorted for deterministic output) + let mut environments_to_process: Vec<_> = expected_repo.environments.iter().collect(); + environments_to_process.sort_by_key(|(name, _)| name.as_str()); - for (env_name, expected_env) in to_create_or_update { + for (env_name, expected_env) in environments_to_process { match actual_environments.get(env_name) { - Some(actual_env) if actual_env.branches == expected_env.branches => { - // No change needed - continue; - } Some(actual_env) => { + // Skip if branches are identical (order-independent comparison) + if branches_equal(&actual_env.branches, &expected_env.branches) { + continue; + } + // Environment exists but branches differ - update it environment_diffs.push(EnvironmentDiff::Update( env_name.clone(), @@ -528,13 +528,13 @@ impl SyncGitHub { } } - // Environments to delete (sorted for deterministic output) - let mut to_delete: Vec<_> = actual_env_names + // Process environments to delete (sorted for deterministic output) + let mut envs_to_delete: Vec<_> = actual_env_names .difference(&expected_env_names) .cloned() .collect(); - to_delete.sort(); - for env in to_delete { + envs_to_delete.sort(); + for env in envs_to_delete { environment_diffs.push(EnvironmentDiff::Delete(env)); } @@ -554,6 +554,16 @@ impl SyncGitHub { } } +/// Compare two branch lists for equality, ignoring order +fn branches_equal(a: &[String], b: &[String]) -> bool { + if a.len() != b.len() { + return false; + } + let a_set: HashSet<&String> = a.iter().collect(); + let b_set: HashSet<&String> = b.iter().collect(); + a_set == b_set +} + fn calculate_permission_diffs( expected_repo: &rust_team_data::v1::Repo, mut actual_teams: HashMap, From 73ac937ca5542fdf227cc6d57438c7d506fe0e73 Mon Sep 17 00:00:00 2001 From: Mustaque Ahmed Date: Thu, 11 Dec 2025 17:53:23 +0530 Subject: [PATCH 4/7] refactor: address review comments --- sync-team/src/github/mod.rs | 103 +++++++++++++++++++----------- sync-team/src/github/tests/mod.rs | 56 ++++++++-------- 2 files changed, 95 insertions(+), 64 deletions(-) diff --git a/sync-team/src/github/mod.rs b/sync-team/src/github/mod.rs index eefbc82b5..af6ff990f 100644 --- a/sync-team/src/github/mod.rs +++ b/sync-team/src/github/mod.rs @@ -511,19 +511,36 @@ impl SyncGitHub { continue; } - // Environment exists but branches differ - update it - environment_diffs.push(EnvironmentDiff::Update( - env_name.clone(), - actual_env.branches.clone(), - expected_env.branches.clone(), - )); + // Environment exists but branches differ - compute what to add/remove + let old_set: HashSet<_> = actual_env.branches.iter().collect(); + let new_set: HashSet<_> = expected_env.branches.iter().collect(); + + let mut add_branches: Vec<_> = new_set + .difference(&old_set) + .map(|s| s.to_string()) + .collect(); + let mut remove_branches: Vec<_> = old_set + .difference(&new_set) + .map(|s| s.to_string()) + .collect(); + + // Sort for deterministic output + add_branches.sort(); + remove_branches.sort(); + + environment_diffs.push(EnvironmentDiff::Update { + name: env_name.clone(), + add_branches, + remove_branches, + new_branches: expected_env.branches.clone(), + }); } None => { // Environment doesn't exist - create it - environment_diffs.push(EnvironmentDiff::Create( - env_name.clone(), - expected_env.branches.clone(), - )); + environment_diffs.push(EnvironmentDiff::Create { + name: env_name.clone(), + branches: expected_env.branches.clone(), + }); } } } @@ -535,7 +552,7 @@ impl SyncGitHub { .collect(); envs_to_delete.sort(); for env in envs_to_delete { - environment_diffs.push(EnvironmentDiff::Delete(env)); + environment_diffs.push(EnvironmentDiff::Delete { name: env }); } Ok(environment_diffs) @@ -976,9 +993,19 @@ struct UpdateRepoDiff { #[derive(Debug)] enum EnvironmentDiff { - Create(String, Vec), - Update(String, Vec, Vec), - Delete(String), + Create { + name: String, + branches: Vec, + }, + Update { + name: String, + add_branches: Vec, + remove_branches: Vec, + new_branches: Vec, + }, + Delete { + name: String, + }, } impl UpdateRepoDiff { @@ -1038,13 +1065,18 @@ impl UpdateRepoDiff { for env_diff in &self.environment_diffs { match env_diff { - EnvironmentDiff::Create(name, branches) => { + EnvironmentDiff::Create { name, branches } => { sync.create_environment(&self.org, &self.name, name, branches)?; } - EnvironmentDiff::Update(name, _old_branches, new_branches) => { + EnvironmentDiff::Update { + name, + add_branches: _, + remove_branches: _, + new_branches, + } => { sync.update_environment(&self.org, &self.name, name, new_branches)?; } - EnvironmentDiff::Delete(name) => { + EnvironmentDiff::Delete { name } => { sync.delete_environment(&self.org, &self.name, name)?; } } @@ -1123,37 +1155,34 @@ impl std::fmt::Display for UpdateRepoDiff { writeln!(f, " Environments:")?; for env_diff in environment_diffs { match env_diff { - EnvironmentDiff::Create(name, branches) => { + EnvironmentDiff::Create { name, branches } => { writeln!(f, " ➕ Create: {name}")?; if !branches.is_empty() { writeln!(f, " Branches: {}", branches.join(", "))?; } } - EnvironmentDiff::Update(name, old_branches, new_branches) => { + EnvironmentDiff::Update { + name, + add_branches, + remove_branches, + new_branches: _, + } => { writeln!(f, " 🔄 Update: {name}")?; - let old_set: HashSet<_> = old_branches.iter().collect(); - let new_set: HashSet<_> = new_branches.iter().collect(); - - let mut added: Vec<_> = - new_set.difference(&old_set).map(|s| s.as_str()).collect(); - let mut removed: Vec<_> = - old_set.difference(&new_set).map(|s| s.as_str()).collect(); - - // Sort for deterministic output - added.sort(); - removed.sort(); - - if !added.is_empty() { - writeln!(f, " Adding branches: {}", added.join(", "))?; + if !add_branches.is_empty() { + writeln!(f, " Adding branches: {}", add_branches.join(", "))?; } - if !removed.is_empty() { - writeln!(f, " Removing branches: {}", removed.join(", "))?; + if !remove_branches.is_empty() { + writeln!( + f, + " Removing branches: {}", + remove_branches.join(", ") + )?; } - if added.is_empty() && removed.is_empty() { + if add_branches.is_empty() && remove_branches.is_empty() { writeln!(f, " No branch changes")?; } } - EnvironmentDiff::Delete(name) => writeln!(f, " ❌ Delete: {name}")?, + EnvironmentDiff::Delete { name } => writeln!(f, " ❌ Delete: {name}")?, } } } diff --git a/sync-team/src/github/tests/mod.rs b/sync-team/src/github/tests/mod.rs index 6a132c318..3ccad04ab 100644 --- a/sync-team/src/github/tests/mod.rs +++ b/sync-team/src/github/tests/mod.rs @@ -984,14 +984,14 @@ fn repo_environment_create() { permission_diffs: [], branch_protection_diffs: [], environment_diffs: [ - Create( - "production", - [], - ), - Create( - "staging", - [], - ), + Create { + name: "production", + branches: [], + }, + Create { + name: "staging", + branches: [], + }, ], }, ), @@ -1036,12 +1036,12 @@ fn repo_environment_delete() { permission_diffs: [], branch_protection_diffs: [], environment_diffs: [ - Delete( - "production", - ), - Delete( - "staging", - ), + Delete { + name: "production", + }, + Delete { + name: "staging", + }, ], }, ), @@ -1095,13 +1095,13 @@ fn repo_environment_update() { permission_diffs: [], branch_protection_diffs: [], environment_diffs: [ - Create( - "dev", - [], - ), - Delete( - "staging", - ), + Create { + name: "dev", + branches: [], + }, + Delete { + name: "staging", + }, ], }, ), @@ -1150,17 +1150,19 @@ fn repo_environment_update_branches() { permission_diffs: [], branch_protection_diffs: [], environment_diffs: [ - Update( - "production", - [ - "main", + Update { + name: "production", + add_branches: [ + "stable", + ], + remove_branches: [ "release/*", ], - [ + new_branches: [ "main", "stable", ], - ), + }, ], }, ), From cf58411534e989192ae732cb1817e6e029e759d2 Mon Sep 17 00:00:00 2001 From: Mustaque Ahmed Date: Fri, 12 Dec 2025 14:26:47 +0530 Subject: [PATCH 5/7] feat: add support for tags --- docs/toml-schema.md | 27 ++++-- rust_team_data/src/v1.rs | 2 + src/main.rs | 29 +++++- src/schema.rs | 18 +++- src/static_api.rs | 38 ++++++-- src/validate.rs | 86 ++++++++++++++++- sync-team/src/github/api/read.rs | 22 ++++- sync-team/src/github/api/write.rs | 114 ++++++++++++++++++----- sync-team/src/github/mod.rs | 103 ++++++++++++++------ sync-team/src/github/tests/mod.rs | 39 ++++++-- sync-team/src/github/tests/test_utils.rs | 9 +- 11 files changed, 404 insertions(+), 83 deletions(-) diff --git a/docs/toml-schema.md b/docs/toml-schema.md index b1f35ebfc..0b57fc247 100644 --- a/docs/toml-schema.md +++ b/docs/toml-schema.md @@ -433,22 +433,37 @@ 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 and deployment branch policies for repositories. +GitHub environments are used to configure deployment protection rules and secrets for GitHub Actions workflows. This repository can manage environment names and deployment branch and tag policies for repositories. ```toml # The environments in this repository (optional) # Use table-of-tables syntax where the key is the environment name [environments.production] -# List of branch names that can deploy to this environment (optional) +# List of branch patterns that can deploy to this environment (optional) # If empty or omitted, any branch can deploy -branches = ["main", "release/*"] +branch = ["main", "release/*"] +# List of tag patterns that can deploy to this environment (optional) +tag = ["v*", "release-*"] [environments.staging] -# Empty branches list means any branch can deploy -branches = [] +# Only specific branches can deploy to staging +branch = ["develop", "staging"] +# No tag patterns specified - no tags can deploy [environments.development] -# No branches specified - any branch can deploy +# No branch or tag patterns specified - any branch or tag can deploy + +## Legacy syntax (still supported for backwards compatibility): +# The old "branches" and "deployment-patterns" fields are still supported +# and will be automatically merged with the new "branch" and "tag" fields +[environments.legacy-example] +branches = ["main", "stable"] # Old style - treated as branch patterns + +[environments.legacy-patterns] +deployment-patterns = [ + { name = "main", type = "branch" }, + { name = "v*", type = "tag" } +] ``` ### Crates.io trusted publishing diff --git a/rust_team_data/src/v1.rs b/rust_team_data/src/v1.rs index 2bc4b5e58..b910ebfbc 100644 --- a/rust_team_data/src/v1.rs +++ b/rust_team_data/src/v1.rs @@ -263,6 +263,8 @@ pub struct CratesIoPublishing { pub struct Environment { #[serde(default)] pub branches: Vec, + #[serde(default)] + pub tags: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] diff --git a/src/main.rs b/src/main.rs index 7afaa143a..8d75f55d6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -502,9 +502,34 @@ fn run() -> Result<(), Error> { println!("{repo_name}:"); for (env_name, env) in &repo.environments { print!(" - {env_name}"); - if !env.branches.is_empty() { - print!(" (branches: {})", env.branches.join(", ")); + + // Show branches if present + if !env.branch.is_empty() { + print!(" (branches: {})", env.branch.join(", ")); + } + + // Show tags if present + if !env.tag.is_empty() { + print!(" (tags: {})", env.tag.join(", ")); } + + // Fallback to legacy fields for backwards compatibility + if env.branch.is_empty() && env.tag.is_empty() { + if let Some(patterns) = &env.deployment_patterns { + if !patterns.is_empty() { + let patterns_str: Vec = patterns + .iter() + .map(|p| format!("{} ({})", p.name, p.pattern_type)) + .collect(); + print!(" (legacy patterns: {})", patterns_str.join(", ")); + } + } else if let Some(branches) = &env.branches { + if !branches.is_empty() { + print!(" (legacy branches: {})", branches.join(", ")); + } + } + } + println!(); } } else { diff --git a/src/schema.rs b/src/schema.rs index d0fc0b1f1..a61d22741 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -888,5 +888,21 @@ pub(crate) struct CratesIoPublishing { #[serde(deny_unknown_fields, rename_all = "kebab-case")] pub(crate) struct Environment { #[serde(default)] - pub branches: Vec, + pub branch: Vec, + #[serde(default)] + pub tag: Vec, + /// Legacy field for backwards compatibility (old "branches" field) + #[serde(default, skip_serializing_if = "Option::is_none")] + pub branches: Option>, + /// Legacy field for backwards compatibility (old deployment-patterns) + #[serde(default, skip_serializing_if = "Option::is_none")] + pub deployment_patterns: Option>, +} + +#[derive(serde_derive::Deserialize, Debug, Clone)] +#[serde(deny_unknown_fields, rename_all = "kebab-case")] +pub(crate) struct LegacyDeploymentPattern { + pub name: String, + #[serde(rename = "type")] + pub pattern_type: String, } diff --git a/src/static_api.rs b/src/static_api.rs index 1ba8ae315..ea2065c90 100644 --- a/src/static_api.rs +++ b/src/static_api.rs @@ -166,12 +166,38 @@ impl<'a> Generator<'a> { .environments .iter() .map(|(name, env)| { - ( - name.clone(), - v1::Environment { - branches: env.branches.clone(), - }, - ) + let mut branches = env.branch.clone(); + let mut tags = env.tag.clone(); + + // Legacy support: merge old "branches" field into branch array + if let Some(legacy_branches) = &env.branches { + for b in legacy_branches { + if !branches.contains(b) { + branches.push(b.clone()); + } + } + } + + // Legacy support: convert old deployment-patterns into branch/tag arrays + if let Some(patterns) = &env.deployment_patterns { + for p in patterns { + match p.pattern_type.as_str() { + "branch" => { + if !branches.contains(&p.name) { + branches.push(p.name.clone()); + } + } + "tag" => { + if !tags.contains(&p.name) { + tags.push(p.name.clone()); + } + } + _ => {} + } + } + } + + (name.clone(), v1::Environment { branches, tags }) }) .collect(); envs.sort_by(|a, b| a.0.cmp(&b.0)); diff --git a/src/validate.rs b/src/validate.rs index 8de26b56f..83d55980d 100644 --- a/src/validate.rs +++ b/src/validate.rs @@ -1042,7 +1042,7 @@ fn validate_archived_repos(data: &Data, errors: &mut Vec) { }); } -/// Validate that environments have valid names (non-empty) and branches +/// Validate that environments have valid names (non-empty) and deployment patterns fn validate_environments(data: &Data, errors: &mut Vec) { wrapper(data.all_repos(), errors, |repo, _| { for (env_name, env) in &repo.environments { @@ -1067,12 +1067,15 @@ fn validate_environments(data: &Data, errors: &mut Vec) { ); } - // Validate branch names are not empty and not duplicated + // Validate branch and tag patterns are not empty and not duplicated let mut seen_branches = HashSet::new(); - for branch in &env.branches { + let mut seen_tags = HashSet::new(); + + // Validate branch patterns (new field) + for branch in &env.branch { if branch.is_empty() { bail!( - "repo {}/{} environment '{}' has an empty branch name", + "repo {}/{} environment '{}' has an empty branch pattern", repo.org, repo.name, env_name @@ -1080,7 +1083,7 @@ fn validate_environments(data: &Data, errors: &mut Vec) { } if !seen_branches.insert(branch) { bail!( - "repo {}/{} environment '{}' has duplicate branch name '{}'", + "repo {}/{} environment '{}' has duplicate branch pattern '{}'", repo.org, repo.name, env_name, @@ -1088,6 +1091,79 @@ fn validate_environments(data: &Data, errors: &mut Vec) { ); } } + + // Validate tag patterns (new field) + for tag in &env.tag { + if tag.is_empty() { + bail!( + "repo {}/{} environment '{}' has an empty tag pattern", + repo.org, + repo.name, + env_name + ); + } + if !seen_tags.insert(tag) { + bail!( + "repo {}/{} environment '{}' has duplicate tag pattern '{}'", + repo.org, + repo.name, + env_name, + tag + ); + } + } + + // Handle legacy fields for backwards compatibility + if let Some(branches) = &env.branches { + for branch in branches { + if branch.is_empty() { + bail!( + "repo {}/{} environment '{}' has an empty branch name (legacy field)", + repo.org, + repo.name, + env_name + ); + } + if !seen_branches.insert(branch) { + bail!( + "repo {}/{} environment '{}' has duplicate branch name '{}' (legacy field)", + repo.org, + repo.name, + env_name, + branch + ); + } + } + } + + if let Some(patterns) = &env.deployment_patterns { + for pattern in patterns { + if pattern.name.is_empty() { + bail!( + "repo {}/{} environment '{}' has an empty deployment pattern name (legacy field)", + repo.org, + repo.name, + env_name + ); + } + // Legacy patterns use string type, decide which set to check + let seen_set = match pattern.pattern_type.as_str() { + "branch" => &mut seen_branches, + "tag" => &mut seen_tags, + _ => continue, // Skip unknown types + }; + if !seen_set.insert(&pattern.name) { + bail!( + "repo {}/{} environment '{}' has duplicate deployment pattern '{}' (type: {}, legacy field)", + repo.org, + repo.name, + env_name, + pattern.name, + pattern.pattern_type + ); + } + } + } } // No need to check for duplicate environment names since HashMap keys are unique diff --git a/sync-team/src/github/api/read.rs b/sync-team/src/github/api/read.rs index d4aeadb0e..7deb6f37f 100644 --- a/sync-team/src/github/api/read.rs +++ b/sync-team/src/github/api/read.rs @@ -470,6 +470,12 @@ impl GithubRead for GitHubApiRead { #[derive(serde::Deserialize)] struct BranchPolicy { name: String, + #[serde(rename = "type", default = "default_branch_type")] + pattern_type: String, + } + + fn default_branch_type() -> String { + "branch".to_string() } #[derive(serde::Deserialize)] @@ -501,8 +507,9 @@ impl GithubRead for GitHubApiRead { .iter() .any(|rule| rule.rule_type == "branch_policy"); - let branches = if has_branch_policies { + let (branches, tags) = if has_branch_policies { let mut branches = Vec::new(); + let mut tags = Vec::new(); self.client.rest_paginated( &Method::GET, &GitHubUrl::repos( @@ -511,16 +518,21 @@ impl GithubRead for GitHubApiRead { &format!("environments/{}/deployment-branch-policies", env_info.name), )?, |resp: BranchPoliciesResponse| { - branches.extend(resp.branch_policies.into_iter().map(|p| p.name)); + for p in resp.branch_policies { + match p.pattern_type.as_str() { + "tag" => tags.push(p.name), + _ => branches.push(p.name), + } + } Ok(()) }, )?; - branches + (branches, tags) } else { - Vec::new() + (Vec::new(), Vec::new()) }; - Ok((env_info.name, Environment { branches })) + Ok((env_info.name, Environment { branches, tags })) }) .collect() } diff --git a/sync-team/src/github/api/write.rs b/sync-team/src/github/api/write.rs index 7091efa54..df1bd8649 100644 --- a/sync-team/src/github/api/write.rs +++ b/sync-team/src/github/api/write.rs @@ -14,6 +14,7 @@ use crate::utils::ResponseExt; struct BranchPolicyInfo { id: u64, name: String, + pattern_type: String, } pub(crate) struct GitHubWrite { @@ -508,12 +509,13 @@ impl GitHubWrite { repo: &str, name: &str, branches: &[String], + tags: &[String], ) -> anyhow::Result<()> { debug!( - "Creating environment '{name}' in '{org}/{repo}' with branches: {:?}", - branches + "Creating environment '{name}' in '{org}/{repo}' with branches: {:?}, tags: {:?}", + branches, tags ); - self.upsert_environment(org, repo, name, branches) + self.upsert_environment(org, repo, name, branches, tags) } /// Update an environment in a repository @@ -523,12 +525,13 @@ impl GitHubWrite { repo: &str, name: &str, branches: &[String], + tags: &[String], ) -> anyhow::Result<()> { debug!( - "Updating environment '{name}' in '{org}/{repo}' with branches: {:?}", - branches + "Updating environment '{name}' in '{org}/{repo}' with branches: {:?}, tags: {:?}", + branches, tags ); - self.upsert_environment(org, repo, name, branches) + self.upsert_environment(org, repo, name, branches, tags) } /// Internal helper to create or update an environment @@ -538,13 +541,14 @@ impl GitHubWrite { repo: &str, name: &str, branches: &[String], + tags: &[String], ) -> anyhow::Result<()> { 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))?; - let body = if branches.is_empty() { + let body = if branches.is_empty() && tags.is_empty() { serde_json::json!({ "deployment_branch_policy": null }) @@ -559,8 +563,8 @@ impl GitHubWrite { self.client.send(Method::PUT, &url, &body)?; - // Always sync branch policies to ensure cleanup of old policies - self.set_environment_branch_policies(org, repo, name, branches)?; + // Always sync branch/tag policies to ensure cleanup of old policies + self.set_environment_deployment_patterns(org, repo, name, branches, tags)?; } Ok(()) } @@ -576,6 +580,12 @@ impl GitHubWrite { struct BranchPolicy { id: u64, name: String, + #[serde(rename = "type", default = "default_branch_type")] + pattern_type: String, + } + + fn default_branch_type() -> String { + "branch".to_string() } #[derive(serde::Deserialize)] @@ -598,6 +608,7 @@ impl GitHubWrite { .map(|bp| BranchPolicyInfo { id: bp.id, name: bp.name, + pattern_type: bp.pattern_type, }) .collect()) } @@ -623,41 +634,74 @@ impl GitHubWrite { Ok(()) } - /// Set custom branch policies for an environment + /// Set custom deployment patterns (branch/tag policies) for an environment /// This method properly handles updates by: /// 1. Fetching all existing policies /// 2. Deleting policies that are no longer needed /// 3. Adding new policies that don't exist - fn set_environment_branch_policies( + fn set_environment_deployment_patterns( &self, org: &str, repo: &str, environment: &str, branches: &[String], + tags: &[String], ) -> anyhow::Result<()> { // 1. Fetch existing policies let existing_policies = self.get_environment_branch_policies(org, repo, environment)?; - let existing_branches: HashSet = - existing_policies.iter().map(|p| p.name.clone()).collect(); - let new_branches: HashSet = branches.iter().cloned().collect(); + #[derive(Hash, Eq, PartialEq)] + struct PatternKey { + name: String, + pattern_type: String, + } + + let existing_patterns: HashSet = existing_policies + .iter() + .map(|p| PatternKey { + name: p.name.clone(), + pattern_type: p.pattern_type.clone(), + }) + .collect(); + + let mut new_patterns = HashSet::new(); + for branch in branches { + new_patterns.insert(PatternKey { + name: branch.clone(), + pattern_type: "branch".to_string(), + }); + } + for tag in tags { + new_patterns.insert(PatternKey { + name: tag.clone(), + pattern_type: "tag".to_string(), + }); + } // 2. Delete policies that are no longer needed for policy in &existing_policies { - if !new_branches.contains(&policy.name) { + let key = PatternKey { + name: policy.name.clone(), + pattern_type: policy.pattern_type.clone(), + }; + if !new_patterns.contains(&key) { debug!( - "Deleting branch policy '{}' (id: {}) from environment '{}' in '{}/{}'", - policy.name, policy.id, environment, org, repo + "Deleting deployment policy '{}' (type: {}, id: {}) from environment '{}' in '{}/{}'", + policy.name, policy.pattern_type, policy.id, environment, org, repo ); self.delete_environment_branch_policy(org, repo, environment, policy.id)?; } } - // 3. Add new policies that don't exist yet + // 3. Add new branch policies that don't exist yet for branch in branches { - if !existing_branches.contains(branch) { + let key = PatternKey { + name: branch.clone(), + pattern_type: "branch".to_string(), + }; + if !existing_patterns.contains(&key) { debug!( - "Adding branch policy '{}' to environment '{}' in '{}/{}'", + "Adding branch pattern '{}' to environment '{}' in '{}/{}'", branch, environment, org, repo ); let url = GitHubUrl::repos( @@ -669,7 +713,35 @@ impl GitHubWrite { Method::POST, &url, &serde_json::json!({ - "name": branch + "name": branch, + "type": "branch" + }), + )?; + } + } + + // 4. Add new tag policies that don't exist yet + for tag in tags { + let key = PatternKey { + name: tag.clone(), + pattern_type: "tag".to_string(), + }; + if !existing_patterns.contains(&key) { + debug!( + "Adding tag pattern '{}' to environment '{}' in '{}/{}'", + tag, environment, org, repo + ); + let url = GitHubUrl::repos( + org, + repo, + &format!("environments/{}/deployment-branch-policies", environment), + )?; + self.client.send( + Method::POST, + &url, + &serde_json::json!({ + "name": tag, + "type": "tag" }), )?; } diff --git a/sync-team/src/github/mod.rs b/sync-team/src/github/mod.rs index af6ff990f..f23f11598 100644 --- a/sync-team/src/github/mod.rs +++ b/sync-team/src/github/mod.rs @@ -364,7 +364,7 @@ impl SyncGitHub { environments: expected_repo .environments .iter() - .map(|(name, env)| (name.clone(), env.branches.clone())) + .map(|(name, env)| (name.clone(), env.clone())) .collect(), })); } @@ -506,33 +506,49 @@ impl SyncGitHub { for (env_name, expected_env) in environments_to_process { match actual_environments.get(env_name) { Some(actual_env) => { - // Skip if branches are identical (order-independent comparison) - if branches_equal(&actual_env.branches, &expected_env.branches) { + // Skip if both branches and tags are identical (order-independent comparison) + if patterns_equal(&actual_env.branches, &expected_env.branches) + && patterns_equal(&actual_env.tags, &expected_env.tags) + { continue; } - // Environment exists but branches differ - compute what to add/remove - let old_set: HashSet<_> = actual_env.branches.iter().collect(); - let new_set: HashSet<_> = expected_env.branches.iter().collect(); + // Environment exists but patterns differ - compute what to add/remove for branches + let old_branches: HashSet<_> = actual_env.branches.iter().collect(); + let new_branches: HashSet<_> = expected_env.branches.iter().collect(); - let mut add_branches: Vec<_> = new_set - .difference(&old_set) - .map(|s| s.to_string()) + let mut add_branches: Vec<_> = new_branches + .difference(&old_branches) + .map(|&s| s.clone()) .collect(); - let mut remove_branches: Vec<_> = old_set - .difference(&new_set) - .map(|s| s.to_string()) + let mut remove_branches: Vec<_> = old_branches + .difference(&new_branches) + .map(|&s| s.clone()) .collect(); + // Compute what to add/remove for tags + let old_tags: HashSet<_> = actual_env.tags.iter().collect(); + let new_tags: HashSet<_> = expected_env.tags.iter().collect(); + + let mut add_tags: Vec<_> = + new_tags.difference(&old_tags).map(|&s| s.clone()).collect(); + let mut remove_tags: Vec<_> = + old_tags.difference(&new_tags).map(|&s| s.clone()).collect(); + // Sort for deterministic output add_branches.sort(); remove_branches.sort(); + add_tags.sort(); + remove_tags.sort(); environment_diffs.push(EnvironmentDiff::Update { name: env_name.clone(), add_branches, remove_branches, + add_tags, + remove_tags, new_branches: expected_env.branches.clone(), + new_tags: expected_env.tags.clone(), }); } None => { @@ -540,6 +556,7 @@ impl SyncGitHub { environment_diffs.push(EnvironmentDiff::Create { name: env_name.clone(), branches: expected_env.branches.clone(), + tags: expected_env.tags.clone(), }); } } @@ -571,8 +588,8 @@ impl SyncGitHub { } } -/// Compare two branch lists for equality, ignoring order -fn branches_equal(a: &[String], b: &[String]) -> bool { +/// Compare two string lists for equality, ignoring order +fn patterns_equal(a: &[String], b: &[String]) -> bool { if a.len() != b.len() { return false; } @@ -906,7 +923,7 @@ struct CreateRepoDiff { settings: RepoSettings, permissions: Vec, branch_protections: Vec<(String, api::BranchProtection)>, - environments: Vec<(String, Vec)>, + environments: Vec<(String, rust_team_data::v1::Environment)>, } impl CreateRepoDiff { @@ -925,8 +942,8 @@ impl CreateRepoDiff { .apply(sync, &self.org, &self.name, &repo.node_id)?; } - for (env_name, branches) in &self.environments { - sync.create_environment(&self.org, &self.name, env_name, branches)?; + for (env_name, env) in &self.environments { + sync.create_environment(&self.org, &self.name, env_name, &env.branches, &env.tags)?; } Ok(()) @@ -968,10 +985,13 @@ impl std::fmt::Display for CreateRepoDiff { } if !environments.is_empty() { writeln!(f, " Environments:")?; - for (env_name, branches) in environments { + for (env_name, env) in environments { writeln!(f, " - {env_name}")?; - if !branches.is_empty() { - writeln!(f, " Branches: {}", branches.join(", "))?; + if !env.branches.is_empty() { + writeln!(f, " Branches: {}", env.branches.join(", "))?; + } + if !env.tags.is_empty() { + writeln!(f, " Tags: {}", env.tags.join(", "))?; } } } @@ -996,12 +1016,16 @@ enum EnvironmentDiff { Create { name: String, branches: Vec, + tags: Vec, }, Update { name: String, add_branches: Vec, remove_branches: Vec, + add_tags: Vec, + remove_tags: Vec, new_branches: Vec, + new_tags: Vec, }, Delete { name: String, @@ -1065,16 +1089,23 @@ impl UpdateRepoDiff { for env_diff in &self.environment_diffs { match env_diff { - EnvironmentDiff::Create { name, branches } => { - sync.create_environment(&self.org, &self.name, name, branches)?; + EnvironmentDiff::Create { + name, + branches, + tags, + } => { + sync.create_environment(&self.org, &self.name, name, branches, tags)?; } EnvironmentDiff::Update { name, add_branches: _, remove_branches: _, + add_tags: _, + remove_tags: _, new_branches, + new_tags, } => { - sync.update_environment(&self.org, &self.name, name, new_branches)?; + sync.update_environment(&self.org, &self.name, name, new_branches, new_tags)?; } EnvironmentDiff::Delete { name } => { sync.delete_environment(&self.org, &self.name, name)?; @@ -1155,17 +1186,27 @@ impl std::fmt::Display for UpdateRepoDiff { writeln!(f, " Environments:")?; for env_diff in environment_diffs { match env_diff { - EnvironmentDiff::Create { name, branches } => { + EnvironmentDiff::Create { + name, + branches, + tags, + } => { writeln!(f, " ➕ Create: {name}")?; if !branches.is_empty() { writeln!(f, " Branches: {}", branches.join(", "))?; } + if !tags.is_empty() { + writeln!(f, " Tags: {}", tags.join(", "))?; + } } EnvironmentDiff::Update { name, add_branches, remove_branches, + add_tags, + remove_tags, new_branches: _, + new_tags: _, } => { writeln!(f, " 🔄 Update: {name}")?; if !add_branches.is_empty() { @@ -1178,8 +1219,18 @@ impl std::fmt::Display for UpdateRepoDiff { remove_branches.join(", ") )?; } - if add_branches.is_empty() && remove_branches.is_empty() { - writeln!(f, " No branch changes")?; + if !add_tags.is_empty() { + writeln!(f, " Adding tags: {}", add_tags.join(", "))?; + } + if !remove_tags.is_empty() { + writeln!(f, " Removing tags: {}", remove_tags.join(", "))?; + } + if add_branches.is_empty() + && remove_branches.is_empty() + && add_tags.is_empty() + && remove_tags.is_empty() + { + writeln!(f, " No pattern changes")?; } } EnvironmentDiff::Delete { name } => writeln!(f, " ❌ Delete: {name}")?, diff --git a/sync-team/src/github/tests/mod.rs b/sync-team/src/github/tests/mod.rs index 3ccad04ab..35e6e19f3 100644 --- a/sync-team/src/github/tests/mod.rs +++ b/sync-team/src/github/tests/mod.rs @@ -952,12 +952,18 @@ fn repo_environment_create() { model.get_repo("repo1").environments.insert( "production".to_string(), - v1::Environment { branches: vec![] }, + v1::Environment { + branches: vec![], + tags: vec![], + }, + ); + model.get_repo("repo1").environments.insert( + "staging".to_string(), + v1::Environment { + branches: vec![], + tags: vec![], + }, ); - model - .get_repo("repo1") - .environments - .insert("staging".to_string(), v1::Environment { branches: vec![] }); let diff = model.diff_repos(gh); insta::assert_debug_snapshot!(diff, @r#" @@ -987,10 +993,12 @@ fn repo_environment_create() { Create { name: "production", branches: [], + tags: [], }, Create { name: "staging", branches: [], + tags: [], }, ], }, @@ -1063,12 +1071,18 @@ fn repo_environment_update() { model.get_repo("repo1").environments.clear(); model.get_repo("repo1").environments.insert( "production".to_string(), - v1::Environment { branches: vec![] }, + v1::Environment { + branches: vec![], + tags: vec![], + }, + ); + model.get_repo("repo1").environments.insert( + "dev".to_string(), + v1::Environment { + branches: vec![], + tags: vec![], + }, ); - model - .get_repo("repo1") - .environments - .insert("dev".to_string(), v1::Environment { branches: vec![] }); let diff = model.diff_repos(gh); insta::assert_debug_snapshot!(diff, @r#" @@ -1098,6 +1112,7 @@ fn repo_environment_update() { Create { name: "dev", branches: [], + tags: [], }, Delete { name: "staging", @@ -1122,6 +1137,7 @@ fn repo_environment_update_branches() { "production".to_string(), v1::Environment { branches: vec!["main".to_string(), "stable".to_string()], + tags: vec![], }, ); @@ -1158,10 +1174,13 @@ fn repo_environment_update_branches() { remove_branches: [ "release/*", ], + add_tags: [], + remove_tags: [], new_branches: [ "main", "stable", ], + new_tags: [], }, ], }, diff --git a/sync-team/src/github/tests/test_utils.rs b/sync-team/src/github/tests/test_utils.rs index fe6f8ad5c..6e59b77c0 100644 --- a/sync-team/src/github/tests/test_utils.rs +++ b/sync-team/src/github/tests/test_utils.rs @@ -391,7 +391,13 @@ impl RepoDataBuilder { pub fn environment(mut self, name: &str) -> Self { let mut environments = self.environments.clone().unwrap_or_default(); - environments.insert(name.to_string(), v1::Environment { branches: vec![] }); + environments.insert( + name.to_string(), + v1::Environment { + branches: vec![], + tags: vec![], + }, + ); self.environments = Some(environments); self } @@ -402,6 +408,7 @@ impl RepoDataBuilder { name.to_string(), v1::Environment { branches: branches.iter().map(|s| s.to_string()).collect(), + tags: vec![], }, ); self.environments = Some(environments); From 1b94e4a8095e3de5729bc2fb4e4adfdab80e0d52 Mon Sep 17 00:00:00 2001 From: Mustaque Ahmed Date: Fri, 12 Dec 2025 18:05:24 +0530 Subject: [PATCH 6/7] chore: branches and tags --- docs/toml-schema.md | 12 ++++-------- src/main.rs | 16 ++++++---------- src/schema.rs | 9 ++++----- src/static_api.rs | 13 ++----------- src/validate.rs | 28 +++------------------------- 5 files changed, 19 insertions(+), 59 deletions(-) diff --git a/docs/toml-schema.md b/docs/toml-schema.md index 0b57fc247..b5c7a2fa3 100644 --- a/docs/toml-schema.md +++ b/docs/toml-schema.md @@ -441,24 +441,20 @@ GitHub environments are used to configure deployment protection rules and secret [environments.production] # List of branch patterns that can deploy to this environment (optional) # If empty or omitted, any branch can deploy -branch = ["main", "release/*"] +branches = ["main", "release/*"] # List of tag patterns that can deploy to this environment (optional) -tag = ["v*", "release-*"] +tags = ["v*", "release-*"] [environments.staging] # Only specific branches can deploy to staging -branch = ["develop", "staging"] +branches = ["develop", "staging"] # No tag patterns specified - no tags can deploy [environments.development] # No branch or tag patterns specified - any branch or tag can deploy ## Legacy syntax (still supported for backwards compatibility): -# The old "branches" and "deployment-patterns" fields are still supported -# and will be automatically merged with the new "branch" and "tag" fields -[environments.legacy-example] -branches = ["main", "stable"] # Old style - treated as branch patterns - +# The old "deployment-patterns" field is still supported [environments.legacy-patterns] deployment-patterns = [ { name = "main", type = "branch" }, diff --git a/src/main.rs b/src/main.rs index 8d75f55d6..e7f75dd58 100644 --- a/src/main.rs +++ b/src/main.rs @@ -504,17 +504,17 @@ fn run() -> Result<(), Error> { print!(" - {env_name}"); // Show branches if present - if !env.branch.is_empty() { - print!(" (branches: {})", env.branch.join(", ")); + if !env.branches.is_empty() { + print!(" (branches: {})", env.branches.join(", ")); } // Show tags if present - if !env.tag.is_empty() { - print!(" (tags: {})", env.tag.join(", ")); + if !env.tags.is_empty() { + print!(" (tags: {})", env.tags.join(", ")); } - // Fallback to legacy fields for backwards compatibility - if env.branch.is_empty() && env.tag.is_empty() { + // Fallback to legacy deployment-patterns field for backwards compatibility + if env.branches.is_empty() && env.tags.is_empty() { if let Some(patterns) = &env.deployment_patterns { if !patterns.is_empty() { let patterns_str: Vec = patterns @@ -523,10 +523,6 @@ fn run() -> Result<(), Error> { .collect(); print!(" (legacy patterns: {})", patterns_str.join(", ")); } - } else if let Some(branches) = &env.branches { - if !branches.is_empty() { - print!(" (legacy branches: {})", branches.join(", ")); - } } } diff --git a/src/schema.rs b/src/schema.rs index a61d22741..cbec4c8ef 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -887,13 +887,12 @@ pub(crate) struct CratesIoPublishing { #[derive(serde_derive::Deserialize, Debug, Clone)] #[serde(deny_unknown_fields, rename_all = "kebab-case")] pub(crate) struct Environment { + /// Branch patterns that can deploy to this environment #[serde(default)] - pub branch: Vec, + pub branches: Vec, + /// Tag patterns that can deploy to this environment #[serde(default)] - pub tag: Vec, - /// Legacy field for backwards compatibility (old "branches" field) - #[serde(default, skip_serializing_if = "Option::is_none")] - pub branches: Option>, + pub tags: Vec, /// Legacy field for backwards compatibility (old deployment-patterns) #[serde(default, skip_serializing_if = "Option::is_none")] pub deployment_patterns: Option>, diff --git a/src/static_api.rs b/src/static_api.rs index ea2065c90..85bb09193 100644 --- a/src/static_api.rs +++ b/src/static_api.rs @@ -166,17 +166,8 @@ impl<'a> Generator<'a> { .environments .iter() .map(|(name, env)| { - let mut branches = env.branch.clone(); - let mut tags = env.tag.clone(); - - // Legacy support: merge old "branches" field into branch array - if let Some(legacy_branches) = &env.branches { - for b in legacy_branches { - if !branches.contains(b) { - branches.push(b.clone()); - } - } - } + let mut branches = env.branches.clone(); + let mut tags = env.tags.clone(); // Legacy support: convert old deployment-patterns into branch/tag arrays if let Some(patterns) = &env.deployment_patterns { diff --git a/src/validate.rs b/src/validate.rs index 83d55980d..a9302b7a8 100644 --- a/src/validate.rs +++ b/src/validate.rs @@ -1072,7 +1072,7 @@ fn validate_environments(data: &Data, errors: &mut Vec) { let mut seen_tags = HashSet::new(); // Validate branch patterns (new field) - for branch in &env.branch { + for branch in &env.branches { if branch.is_empty() { bail!( "repo {}/{} environment '{}' has an empty branch pattern", @@ -1093,7 +1093,7 @@ fn validate_environments(data: &Data, errors: &mut Vec) { } // Validate tag patterns (new field) - for tag in &env.tag { + for tag in &env.tags { if tag.is_empty() { bail!( "repo {}/{} environment '{}' has an empty tag pattern", @@ -1113,29 +1113,7 @@ fn validate_environments(data: &Data, errors: &mut Vec) { } } - // Handle legacy fields for backwards compatibility - if let Some(branches) = &env.branches { - for branch in branches { - if branch.is_empty() { - bail!( - "repo {}/{} environment '{}' has an empty branch name (legacy field)", - repo.org, - repo.name, - env_name - ); - } - if !seen_branches.insert(branch) { - bail!( - "repo {}/{} environment '{}' has duplicate branch name '{}' (legacy field)", - repo.org, - repo.name, - env_name, - branch - ); - } - } - } - + // Handle legacy deployment-patterns field for backwards compatibility if let Some(patterns) = &env.deployment_patterns { for pattern in patterns { if pattern.name.is_empty() { From 609d6cf993cb61e4be2a031280ee5db9238939f2 Mon Sep 17 00:00:00 2001 From: Mustaque Ahmed Date: Fri, 12 Dec 2025 18:07:15 +0530 Subject: [PATCH 7/7] feat: add tags in repos dir toml files --- repos/rust-lang/ar_archive_writer.toml | 1 + repos/rust-lang/cargo.toml | 1 + repos/rust-lang/mdBook.toml | 1 + repos/rust-lang/measureme.toml | 1 + 4 files changed, 4 insertions(+) diff --git a/repos/rust-lang/ar_archive_writer.toml b/repos/rust-lang/ar_archive_writer.toml index 42cfce85c..8592add24 100644 --- a/repos/rust-lang/ar_archive_writer.toml +++ b/repos/rust-lang/ar_archive_writer.toml @@ -8,4 +8,5 @@ compiler = "write" [environments.publish] branches = ["*", "master"] +tags = ["*"] diff --git a/repos/rust-lang/cargo.toml b/repos/rust-lang/cargo.toml index c3bbba033..de89cae8e 100644 --- a/repos/rust-lang/cargo.toml +++ b/repos/rust-lang/cargo.toml @@ -19,4 +19,5 @@ ci-checks = ["conclusion"] [environments.release] branches = ["*"] +tags = ["*"] diff --git a/repos/rust-lang/mdBook.toml b/repos/rust-lang/mdBook.toml index ee2c47256..0dde9dddc 100644 --- a/repos/rust-lang/mdBook.toml +++ b/repos/rust-lang/mdBook.toml @@ -35,4 +35,5 @@ environment = "publish" [environments.publish] branches = ["master"] +tags = ["*"] diff --git a/repos/rust-lang/measureme.toml b/repos/rust-lang/measureme.toml index bfd800b82..db0baee76 100644 --- a/repos/rust-lang/measureme.toml +++ b/repos/rust-lang/measureme.toml @@ -21,4 +21,5 @@ environment = "publish" [environments.publish] branches = ["*", "master"] +tags = ["*"]