diff --git a/docs/toml-schema.md b/docs/toml-schema.md index 59b8b865d..b5c7a2fa3 100644 --- a/docs/toml-schema.md +++ b/docs/toml-schema.md @@ -433,16 +433,33 @@ 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 and tag 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 patterns that can deploy to this environment (optional) +# If empty or omitted, any branch can deploy +branches = ["main", "release/*"] +# List of tag patterns that can deploy to this environment (optional) +tags = ["v*", "release-*"] + +[environments.staging] +# Only specific branches can deploy to 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 "deployment-patterns" field is still supported +[environments.legacy-patterns] +deployment-patterns = [ + { name = "main", type = "branch" }, + { name = "v*", type = "tag" } +] ``` ### 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..a5e5ab79a 100644 --- a/repos/rust-lang-nursery/rust-cookbook.toml +++ b/repos/rust-lang-nursery/rust-cookbook.toml @@ -7,6 +7,6 @@ bots = [] cookbook = "write" lang-docs = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] +branches = ["gh-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..cd6a81bf8 100644 --- a/repos/rust-lang/a-mir-formality.toml +++ b/repos/rust-lang/a-mir-formality.toml @@ -9,6 +9,6 @@ lang-ops = "maintain" lang-docs = "maintain" types = "maintain" -[[environments]] -name = "github-pages" +[environments.github-pages] +branches = ["main"] 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..8592add24 100644 --- a/repos/rust-lang/ar_archive_writer.toml +++ b/repos/rust-lang/ar_archive_writer.toml @@ -6,6 +6,7 @@ bots = [] [access.teams] compiler = "write" -[[environments]] -name = "publish" +[environments.publish] +branches = ["*", "master"] +tags = ["*"] diff --git a/repos/rust-lang/areweasyncyet.rs.toml b/repos/rust-lang/areweasyncyet.rs.toml index a9ea6a0b9..fbe15deb8 100644 --- a/repos/rust-lang/areweasyncyet.rs.toml +++ b/repos/rust-lang/areweasyncyet.rs.toml @@ -10,6 +10,6 @@ wg-async = "write" [access.individuals] upsuper = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] +branches = ["master"] 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..0c19383f7 100644 --- a/repos/rust-lang/async-book.toml +++ b/repos/rust-lang/async-book.toml @@ -7,6 +7,6 @@ bots = [] [access.teams] wg-async = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] +branches = ["master"] 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..7e03382e1 100644 --- a/repos/rust-lang/async-fundamentals-initiative.toml +++ b/repos/rust-lang/async-fundamentals-initiative.toml @@ -11,6 +11,6 @@ wg-async = "maintain" pattern = "master" required-approvals = 0 -[[environments]] -name = "github-pages" +[environments.github-pages] +branches = ["master"] 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..113e7da1e 100644 --- a/repos/rust-lang/blog.rust-lang.org.toml +++ b/repos/rust-lang/blog.rust-lang.org.toml @@ -17,6 +17,6 @@ ci-checks = [ "build", ] -[[environments]] -name = "github-pages" +[environments.github-pages] +branches = ["main"] diff --git a/repos/rust-lang/book.toml b/repos/rust-lang/book.toml index 536343a6c..b9e44af46 100644 --- a/repos/rust-lang/book.toml +++ b/repos/rust-lang/book.toml @@ -12,6 +12,6 @@ pattern = "main" ci-checks = [] required-approvals = 0 -[[environments]] -name = "github-pages" +[environments.github-pages] +branches = ["gh-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..5abddaa69 100644 --- a/repos/rust-lang/bors.toml +++ b/repos/rust-lang/bors.toml @@ -14,9 +14,9 @@ pattern = "main" ci-checks = ["Test", "Test Docker"] required-approvals = 0 -[[environments]] -name = "production" +[environments.production] +branches = ["main"] -[[environments]] -name = "staging" +[environments.staging] +branches = ["main"] diff --git a/repos/rust-lang/calendar.toml b/repos/rust-lang/calendar.toml index a9034a710..4da311b2b 100644 --- a/repos/rust-lang/calendar.toml +++ b/repos/rust-lang/calendar.toml @@ -20,6 +20,6 @@ wg-async = "maintain" pattern = "main" pr-required = false -[[environments]] -name = "github-pages" +[environments.github-pages] +branches = ["main"] diff --git a/repos/rust-lang/cargo-bisect-rustc.toml b/repos/rust-lang/cargo-bisect-rustc.toml index a6bedb28f..e81034d69 100644 --- a/repos/rust-lang/cargo-bisect-rustc.toml +++ b/repos/rust-lang/cargo-bisect-rustc.toml @@ -14,6 +14,6 @@ ehuss = "write" pattern = "master" required-approvals = 0 -[[environments]] -name = "github-pages" +[environments.github-pages] +branches = ["gh-pages", "master"] diff --git a/repos/rust-lang/cargo.toml b/repos/rust-lang/cargo.toml index 4361cdf0d..de89cae8e 100644 --- a/repos/rust-lang/cargo.toml +++ b/repos/rust-lang/cargo.toml @@ -15,9 +15,9 @@ ci-checks = ["conclusion"] pattern = "rust-1.*" ci-checks = ["conclusion"] -[[environments]] -name = "github-pages" +[environments.github-pages] -[[environments]] -name = "release" +[environments.release] +branches = ["*"] +tags = ["*"] diff --git a/repos/rust-lang/cc-rs.toml b/repos/rust-lang/cc-rs.toml index d768a2f73..d1ed4f1f3 100644 --- a/repos/rust-lang/cc-rs.toml +++ b/repos/rust-lang/cc-rs.toml @@ -17,9 +17,8 @@ crates = ["cc", "find-msvc-tools"] workflow-filename = "publish.yml" environment = "publish" -[[environments]] -name = "github-pages" +[environments.github-pages] -[[environments]] -name = "publish" +[environments.publish] +branches = ["main"] 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..504d3992e 100644 --- a/repos/rust-lang/fls.toml +++ b/repos/rust-lang/fls.toml @@ -17,6 +17,6 @@ spec-contributors = "triage" pattern = "main" ci-checks = ["CI finished"] -[[environments]] -name = "github-pages" +[environments.github-pages] +branches = ["gh-readonly-queue/main/*", "main"] 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..d30f52776 100644 --- a/repos/rust-lang/lang-team.toml +++ b/repos/rust-lang/lang-team.toml @@ -11,6 +11,6 @@ lang-docs = "maintain" types = "maintain" release = "maintain" -[[environments]] -name = "github-pages" +[environments.github-pages] +branches = ["gh-pages", "master"] diff --git a/repos/rust-lang/libc.toml b/repos/rust-lang/libc.toml index c733d8322..cb3a24966 100644 --- a/repos/rust-lang/libc.toml +++ b/repos/rust-lang/libc.toml @@ -19,6 +19,6 @@ pattern = 'libc-0.2' ci-checks = ['success'] required-approvals = 0 -[[environments]] -name = "github-pages" +[environments.github-pages] +branches = ["gh-pages", "main", "master"] 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..0dde9dddc 100644 --- a/repos/rust-lang/mdBook.toml +++ b/repos/rust-lang/mdBook.toml @@ -31,9 +31,9 @@ crates = [ workflow-filename = "deploy.yml" environment = "publish" -[[environments]] -name = "github-pages" +[environments.github-pages] -[[environments]] -name = "publish" +[environments.publish] +branches = ["master"] +tags = ["*"] diff --git a/repos/rust-lang/measureme.toml b/repos/rust-lang/measureme.toml index b4be858cc..db0baee76 100644 --- a/repos/rust-lang/measureme.toml +++ b/repos/rust-lang/measureme.toml @@ -19,6 +19,7 @@ crates = ["analyzeme", "decodeme", "measureme"] workflow-filename = "publish.yml" environment = "publish" -[[environments]] -name = "publish" +[environments.publish] +branches = ["*", "master"] +tags = ["*"] 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..7ccc5e95b 100644 --- a/repos/rust-lang/polonius.toml +++ b/repos/rust-lang/polonius.toml @@ -6,6 +6,6 @@ bots = [] [access.teams] wg-polonius = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] +branches = ["master"] 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..e4e07a12f 100644 --- a/repos/rust-lang/rfcs.toml +++ b/repos/rust-lang/rfcs.toml @@ -11,6 +11,6 @@ all = "write" pattern = "master" required-approvals = 0 -[[environments]] -name = "github-pages" +[environments.github-pages] +branches = ["master"] 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..874690390 100644 --- a/repos/rust-lang/rust-forge.toml +++ b/repos/rust-lang/rust-forge.toml @@ -27,6 +27,6 @@ rustc-dev-guide = "maintain" pattern = "master" ci-checks = ["test"] -[[environments]] -name = "github-pages" +[environments.github-pages] +branches = ["master"] 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..4cfd0e8f0 100644 --- a/repos/rust-lang/rust-project-goals.toml +++ b/repos/rust-lang/rust-project-goals.toml @@ -17,6 +17,6 @@ edition = "maintain" goals = "maintain" goal-owners = "maintain" -[[environments]] -name = "github-pages" +[environments.github-pages] +branches = ["main"] diff --git a/repos/rust-lang/rust.toml b/repos/rust-lang/rust.toml index c32d63f77..897d00a99 100644 --- a/repos/rust-lang/rust.toml +++ b/repos/rust-lang/rust.toml @@ -83,6 +83,6 @@ merge-bots = ["rust-timer"] pattern = "perf-tmp" merge-bots = ["rust-timer"] -[[environments]] -name = "bors" +[environments.bors] +branches = ["auto", "automation/bors/try", "try", "try-perf"] 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..d151318bc 100644 --- a/repos/rust-lang/rustfmt.toml +++ b/repos/rust-lang/rustfmt.toml @@ -35,6 +35,6 @@ required-approvals = 0 pattern = "rust-1.*" required-approvals = 0 -[[environments]] -name = "github-pages" +[environments.github-pages] +branches = ["main"] diff --git a/repos/rust-lang/rustlings.toml b/repos/rust-lang/rustlings.toml index d9651810e..b46000763 100644 --- a/repos/rust-lang/rustlings.toml +++ b/repos/rust-lang/rustlings.toml @@ -7,6 +7,6 @@ bots = [] [access.teams] rustlings = "maintain" -[[environments]] -name = "github-pages" +[environments.github-pages] +branches = ["gh-pages", "main"] 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..39491462a 100644 --- a/repos/rust-lang/team.toml +++ b/repos/rust-lang/team.toml @@ -13,9 +13,9 @@ pattern = "main" ci-checks = ["CI"] dismiss-stale-review = true -[[environments]] -name = "deploy" +[environments.deploy] +branches = ["gh-readonly-queue/main/*", "main"] -[[environments]] -name = "github-pages" +[environments.github-pages] +branches = ["main"] diff --git a/repos/rust-lang/thanks.toml b/repos/rust-lang/thanks.toml index 2acc84118..7a57d2848 100644 --- a/repos/rust-lang/thanks.toml +++ b/repos/rust-lang/thanks.toml @@ -8,6 +8,6 @@ bots = [] website = "write" infra = "write" -[[environments]] -name = "github-pages" +[environments.github-pages] +branches = ["master"] diff --git a/repos/rust-lang/thorin.toml b/repos/rust-lang/thorin.toml index 6f8afe0a4..02f51e944 100644 --- a/repos/rust-lang/thorin.toml +++ b/repos/rust-lang/thorin.toml @@ -14,6 +14,6 @@ crates = ["thorin-dwp", "thorin-dwp-bin"] workflow-filename = "release-plz.yml" environment = "publish" -[[environments]] -name = "publish" +[environments.publish] +branches = ["main"] 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..c6133e54c 100644 --- a/repos/rust-lang/unsafe-code-guidelines.toml +++ b/repos/rust-lang/unsafe-code-guidelines.toml @@ -7,6 +7,6 @@ bots = ["rustbot", "rfcbot"] [access.teams] opsem = 'maintain' -[[environments]] -name = "github-pages" +[environments.github-pages] +branches = ["master"] diff --git a/repos/rust-lang/wg-async.toml b/repos/rust-lang/wg-async.toml index b732b696f..6c5b84920 100644 --- a/repos/rust-lang/wg-async.toml +++ b/repos/rust-lang/wg-async.toml @@ -11,6 +11,6 @@ wg-async = "write" pattern = "master" required-approvals = 0 -[[environments]] -name = "github-pages" +[environments.github-pages] +branches = ["master"] diff --git a/repos/rust-lang/wg-macros.toml b/repos/rust-lang/wg-macros.toml index 97239e6cf..908c3aa67 100644 --- a/repos/rust-lang/wg-macros.toml +++ b/repos/rust-lang/wg-macros.toml @@ -9,6 +9,6 @@ wg-macros = "maintain" [[branch-protections]] pattern = "main" -[[environments]] -name = "github-pages" +[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 eaffe6cda..0392aafb4 100644 --- a/repos/rust-lang/www.rust-lang.org.toml +++ b/repos/rust-lang/www.rust-lang.org.toml @@ -10,15 +10,12 @@ website = "write" [[branch-protections]] pattern = "main" -[[environments]] -name = "github-pages" +[environments.github-pages] +branches = ["main"] -[[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..b910ebfbc 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,10 @@ pub struct CratesIoPublishing { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Environment { - pub name: String, + #[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 3e09d8b69..e7f75dd58 100644 --- a/src/main.rs +++ b/src/main.rs @@ -500,8 +500,33 @@ 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}"); + + // Show branches if present + if !env.branches.is_empty() { + print!(" (branches: {})", env.branches.join(", ")); + } + + // Show tags if present + if !env.tags.is_empty() { + print!(" (tags: {})", env.tags.join(", ")); + } + + // 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 + .iter() + .map(|p| format!("{} ({})", p.name, p.pattern_type)) + .collect(); + print!(" (legacy patterns: {})", patterns_str.join(", ")); + } + } + } + + println!(); } } else { println!("{repo_name}: (no environments)"); diff --git a/src/schema.rs b/src/schema.rs index fbed3397b..cbec4c8ef 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,21 @@ 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 branches: Vec, + /// Tag patterns that can deploy to this environment + #[serde(default)] + pub tags: Vec, + /// 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 53e896ec1..85bb09193 100644 --- a/src/static_api.rs +++ b/src/static_api.rs @@ -161,13 +161,39 @@ 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)| { + 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 { + 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)); + envs.into_iter().collect() + }, archived, auto_merge_enabled: !managed_by_bors, }; diff --git a/src/validate.rs b/src/validate.rs index 534232d3f..a9302b7a8 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 deployment patterns 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,93 @@ 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 and tag patterns are not empty and not duplicated + let mut seen_branches = HashSet::new(); + let mut seen_tags = HashSet::new(); + + // Validate branch patterns (new field) + for branch in &env.branches { + if branch.is_empty() { + bail!( + "repo {}/{} environment '{}' has an empty branch pattern", + repo.org, + repo.name, + env_name + ); + } + if !seen_branches.insert(branch) { + bail!( + "repo {}/{} environment '{}' has duplicate branch pattern '{}'", + repo.org, + repo.name, + env_name, + branch + ); + } + } + + // Validate tag patterns (new field) + for tag in &env.tags { + 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 deployment-patterns field for backwards compatibility + 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 Ok(()) }); } diff --git a/sync-team/src/github/api/read.rs b/sync-team/src/github/api/read.rs index 5cd314b92..7deb6f37f 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,95 @@ 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, } - let mut environments: Vec = Vec::new(); + #[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)] + 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, tags) = if has_branch_policies { + let mut branches = Vec::new(); + let mut tags = Vec::new(); + self.client.rest_paginated( + &Method::GET, + &GitHubUrl::repos( + org, + repo, + &format!("environments/{}/deployment-branch-policies", env_info.name), + )?, + |resp: BranchPoliciesResponse| { + for p in resp.branch_policies { + match p.pattern_type.as_str() { + "tag" => tags.push(p.name), + _ => branches.push(p.name), + } + } + Ok(()) + }, + )?; + (branches, tags) + } else { + (Vec::new(), Vec::new()) + }; + + 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 63b1b3520..df1bd8649 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,13 @@ use crate::github::api::{ }; use crate::utils::ResponseExt; +#[derive(Debug)] +struct BranchPolicyInfo { + id: u64, + name: String, + pattern_type: String, +} + pub(crate) struct GitHubWrite { client: HttpClient, dry_run: bool, @@ -500,14 +508,243 @@ impl GitHubWrite { org: &str, repo: &str, name: &str, + branches: &[String], + tags: &[String], + ) -> anyhow::Result<()> { + debug!( + "Creating environment '{name}' in '{org}/{repo}' with branches: {:?}, tags: {:?}", + branches, tags + ); + self.upsert_environment(org, repo, name, branches, tags) + } + + /// Update an environment in a repository + pub(crate) fn update_environment( + &self, + org: &str, + repo: &str, + name: &str, + branches: &[String], + tags: &[String], + ) -> anyhow::Result<()> { + debug!( + "Updating environment '{name}' in '{org}/{repo}' with branches: {:?}, tags: {:?}", + branches, tags + ); + self.upsert_environment(org, repo, name, branches, tags) + } + + /// Internal helper to create or update an environment + fn upsert_environment( + &self, + org: &str, + repo: &str, + name: &str, + branches: &[String], + tags: &[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() && tags.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/tag policies to ensure cleanup of old policies + self.set_environment_deployment_patterns(org, repo, name, branches, tags)?; + } + 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, + #[serde(rename = "type", default = "default_branch_type")] + pattern_type: String, + } + + fn default_branch_type() -> String { + "branch".to_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, + pattern_type: bp.pattern_type, + }) + .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 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_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)?; + + #[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 { + let key = PatternKey { + name: policy.name.clone(), + pattern_type: policy.pattern_type.clone(), + }; + if !new_patterns.contains(&key) { + debug!( + "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 branch policies that don't exist yet + for branch in branches { + let key = PatternKey { + name: branch.clone(), + pattern_type: "branch".to_string(), + }; + if !existing_patterns.contains(&key) { + debug!( + "Adding branch pattern '{}' 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, + "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" + }), + )?; + } } Ok(()) } diff --git a/sync-team/src/github/mod.rs b/sync-team/src/github/mod.rs index cef6eaeef..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(|e| e.name.clone()) + .map(|(name, env)| (name.clone(), env.clone())) .collect(), })); } @@ -489,39 +489,87 @@ impl SyncGitHub { &self, expected_repo: &rust_team_data::v1::Repo, ) -> 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(); + let actual_env_names: HashSet = actual_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 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 environments_to_process { + match actual_environments.get(env_name) { + Some(actual_env) => { + // 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 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_branches + .difference(&old_branches) + .map(|&s| s.clone()) + .collect(); + 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 => { + // Environment doesn't exist - create it + environment_diffs.push(EnvironmentDiff::Create { + name: env_name.clone(), + branches: expected_env.branches.clone(), + tags: expected_env.tags.clone(), + }); + } + } } - // Environments to delete (sorted for deterministic output) - let mut to_delete: Vec<_> = actual_environments - .difference(&expected_environments) + // 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 { - environment_diffs.push(EnvironmentDiff::Delete(env)); + envs_to_delete.sort(); + for env in envs_to_delete { + environment_diffs.push(EnvironmentDiff::Delete { name: env }); } Ok(environment_diffs) @@ -540,6 +588,16 @@ impl SyncGitHub { } } +/// Compare two string lists for equality, ignoring order +fn patterns_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, @@ -865,7 +923,7 @@ struct CreateRepoDiff { settings: RepoSettings, permissions: Vec, branch_protections: Vec<(String, api::BranchProtection)>, - environments: Vec, + environments: Vec<(String, rust_team_data::v1::Environment)>, } impl CreateRepoDiff { @@ -884,8 +942,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, env) in &self.environments { + sync.create_environment(&self.org, &self.name, env_name, &env.branches, &env.tags)?; } Ok(()) @@ -927,8 +985,14 @@ impl std::fmt::Display for CreateRepoDiff { } if !environments.is_empty() { writeln!(f, " Environments:")?; - for env in environments { - writeln!(f, " - {env}")?; + for (env_name, env) in environments { + writeln!(f, " - {env_name}")?; + if !env.branches.is_empty() { + writeln!(f, " Branches: {}", env.branches.join(", "))?; + } + if !env.tags.is_empty() { + writeln!(f, " Tags: {}", env.tags.join(", "))?; + } } } Ok(()) @@ -949,8 +1013,23 @@ struct UpdateRepoDiff { #[derive(Debug)] enum EnvironmentDiff { - Create(String), - Delete(String), + 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, + }, } impl UpdateRepoDiff { @@ -1010,10 +1089,25 @@ 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, + tags, + } => { + sync.create_environment(&self.org, &self.name, name, branches, tags)?; } - EnvironmentDiff::Delete(name) => { + 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, new_tags)?; + } + EnvironmentDiff::Delete { name } => { sync.delete_environment(&self.org, &self.name, name)?; } } @@ -1092,8 +1186,54 @@ 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::Delete(name) => writeln!(f, " ❌ Delete: {name}")?, + 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() { + writeln!(f, " Adding branches: {}", add_branches.join(", "))?; + } + if !remove_branches.is_empty() { + writeln!( + f, + " Removing branches: {}", + remove_branches.join(", ") + )?; + } + 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 845c90500..35e6e19f3 100644 --- a/sync-team/src/github/tests/mod.rs +++ b/sync-team/src/github/tests/mod.rs @@ -950,12 +950,20 @@ 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![], + tags: vec![], + }, + ); + model.get_repo("repo1").environments.insert( + "staging".to_string(), + v1::Environment { + branches: vec![], + tags: vec![], + }, + ); let diff = model.diff_repos(gh); insta::assert_debug_snapshot!(diff, @r#" @@ -982,12 +990,16 @@ fn repo_environment_create() { permission_diffs: [], branch_protection_diffs: [], environment_diffs: [ - Create( - "production", - ), - Create( - "staging", - ), + Create { + name: "production", + branches: [], + tags: [], + }, + Create { + name: "staging", + branches: [], + tags: [], + }, ], }, ), @@ -1032,12 +1044,12 @@ fn repo_environment_delete() { permission_diffs: [], branch_protection_diffs: [], environment_diffs: [ - Delete( - "production", - ), - Delete( - "staging", - ), + Delete { + name: "production", + }, + Delete { + name: "staging", + }, ], }, ), @@ -1056,14 +1068,21 @@ fn repo_environment_update() { let gh = model.gh_model(); // Remove staging, keep production, add dev - model.get_repo("repo1").environments = vec![ + model.get_repo("repo1").environments.clear(); + model.get_repo("repo1").environments.insert( + "production".to_string(), v1::Environment { - name: "production".to_string(), + branches: vec![], + tags: vec![], }, + ); + model.get_repo("repo1").environments.insert( + "dev".to_string(), v1::Environment { - name: "dev".to_string(), + branches: vec![], + tags: vec![], }, - ]; + ); let diff = model.diff_repos(gh); insta::assert_debug_snapshot!(diff, @r#" @@ -1090,12 +1109,79 @@ fn repo_environment_update() { permission_diffs: [], branch_protection_diffs: [], environment_diffs: [ - Create( - "dev", - ), - Delete( - "staging", - ), + Create { + name: "dev", + branches: [], + tags: [], + }, + Delete { + name: "staging", + }, + ], + }, + ), + ] + "#); +} + +#[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()], + tags: vec![], + }, + ); + + 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 { + name: "production", + add_branches: [ + "stable", + ], + 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 95783ee09..6e59b77c0 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,26 @@ 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![], + tags: 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(), + tags: vec![], + }, + ); self.environments = Some(environments); self } @@ -602,7 +620,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 +649,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