Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .ado/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,30 @@ CI and PR share all build/test/pack logic through `build-template.yml`; the
`buildEnvironment` parameter (`Continuous` vs `PullRequest`) gates the
environment-specific steps.

## Supported branches

CI, PR, Release, and the prepare-release bot all run against the same set of
branches, all driven from YAML. Keep the branch lists in `ci-pipeline.yml`,
`pr-pipeline.yml`, `release-pipeline.yml`, and `prepare-release-bot.yml` (its
`trigger` and the `targetBranch` dropdown) in sync with this table.

| Branch | Status |
|--------|--------|
| `main` | Active development |
| `0.81-stable` | LTS — last release supporting both Fabric and Paper UI |
| `0.83-stable` | Latest release − 1 |
| `0.84-stable` | Current release |
| `0.85-stable` | Preview |
| `0.86-stable` | In upstream React Native, not yet integrated |
| `0.87-stable` | In upstream React Native, not yet integrated |

`0.82-stable` is deprecated and removed from all pipelines. `0.80-stable` and
earlier are not built.

Feed warm-up (`warm-feed-pipeline.yml`) is branch-agnostic (scheduled on `main`).
Branch *creation* is governed separately by the ruleset in
[`repo-rules/`](./repo-rules/README.md).

## Template-repository wiring (per-entry `PipelineTemplates` alias)

CI runs in the internal **ISS** project and extends the **Office** 1ES templates.
Expand Down
2 changes: 1 addition & 1 deletion .ado/ci-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ name: 0.0.$(Date:yyMM.d)$(Rev:rrr)
trigger:
branches:
include:
# Supported branches — keep in sync with .ado/README.md
- main
- '0.81-stable'
- '0.82-stable'
- '0.83-stable'
- '0.84-stable'
- '0.85-stable'
Expand Down
6 changes: 3 additions & 3 deletions .ado/integrate-rn.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:

- template: templates/prepare-js-env.yml

- script: git checkout -b integrate-${{ parameters.reactNativeVersion }} origin/main
- script: git checkout -b integrate/${{ parameters.reactNativeVersion }} origin/main
displayName: Create a new branch

- script: yarn integrate-rn ${{ parameters.reactNativeVersion }} --reportPath "$(Agent.TempDirectory)\integration-report.md"
Expand Down Expand Up @@ -90,11 +90,11 @@ jobs:
git commit --amend -F "$(Agent.TempDirectory)\commit-message.md"
displayName: Slip changefiles into original commit

- script: git push origin integrate-${{ parameters.reactNativeVersion }}
- script: git push origin integrate/${{ parameters.reactNativeVersion }}
displayName: Push changes

- pwsh: |
$headers = @{'Accept'='application/vnd.github.v3+json'; 'Authorization'='Token $(githubAuthToken)'}
$body = '{"head": "integrate-${{ parameters.reactNativeVersion }}", "base": "main", "title": "${{ parameters.commitTitle}}"}'
$body = '{"head": "integrate/${{ parameters.reactNativeVersion }}", "base": "main", "title": "${{ parameters.commitTitle}}"}'
Invoke-WebRequest -Method Post -Headers $headers -Body $body https://api.github.com/repos/microsoft/react-native-windows/pulls
displayName: Create PR
9 changes: 7 additions & 2 deletions .ado/pr-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ name: $(Date:yyyyMMdd).$(Rev:r)
trigger: none

pr:
# Supported branches — keep in sync with .ado/README.md
- main
- master
- "*-stable"
- '0.81-stable'
- '0.83-stable'
- '0.84-stable'
- '0.85-stable'
- '0.86-stable'
- '0.87-stable'

variables:
- group: platform-override-zero-permission-token
Expand Down
26 changes: 18 additions & 8 deletions .ado/prepare-release-bot.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
name: $(Date:yyyyMMdd).$(Rev:r)

# Triggers are configured in the ADO pipeline UI:
# - CI triggers on pushes to main and *-stable branches
# - Scheduled triggers for daily runs
# - Manual runs with optional branch override
trigger: none
# CI trigger on the supported branches; also queued manually (targetBranch parameter).
trigger:
branches:
include:
# Supported branches — keep in sync with .ado/README.md
- main
- '0.81-stable'
- '0.83-stable'
- '0.84-stable'
- '0.85-stable'
- '0.86-stable'
- '0.87-stable'
pr: none

parameters:
Expand All @@ -13,12 +20,15 @@ parameters:
type: string
default: (source branch)
values:
# Supported branches — keep in sync with .ado/README.md
- (source branch)
- main
- 0.82-stable
- 0.81-stable
- 0.80-stable
- 0.74-stable
- 0.83-stable
- 0.84-stable
- 0.85-stable
- 0.86-stable
- 0.87-stable

jobs:
- job: PrepareRelease
Expand Down
2 changes: 1 addition & 1 deletion .ado/release-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ resources:
trigger:
branches:
include:
# Supported branches — keep in sync with .ado/README.md
- main
- '0.81-stable'
- '0.82-stable'
- '0.83-stable'
- '0.84-stable'
- '0.85-stable'
Expand Down
148 changes: 148 additions & 0 deletions .ado/repo-rules/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# Repository rules (rulesets)

Infrastructure-as-code for the GitHub **repository rulesets** applied to
`microsoft/react-native-windows`. Each `*.json` file in this folder is the exact
request body for GitHub's create/update ruleset REST API, so the live policy
always has a reviewable history in git. Edit the JSON, get it reviewed, then
apply it with the commands below.

## Files

| File | Ruleset name | Purpose |
|------|--------------|---------|
| [`branch-namespace-ruleset.json`](./branch-namespace-ruleset.json) | Require namespaced branches | Stops new **top-level** branches. New branches must live under an approved namespace (personal work goes under `user/<alias>/…`). |

## What `branch-namespace-ruleset.json` does

- Targets **all branches** (`~ALL`) *except* the approved patterns under
`conditions.ref_name.exclude`.
- Applies the **`creation`** rule ("Restrict creations"): a branch whose name is
not in the allow list cannot be created by anyone without bypass permission.
- **Creation only.** Existing branches and pushes to them are untouched — this
refuses only *new* non-conforming branch names. The ~90 legacy top-level
branches keep working; nobody can add more like them.
- `bypass_actors` is empty on purpose: **no one is exempt**, not even repo
admins. (An admin can still edit or delete the ruleset itself.)

### Approved branch namespaces

| Branch pattern | Who / what uses it |
|----------------|--------------------|
| `user/<alias>/…` | Humans — the required home for personal and PR branches |
| `users/merlinbot/…` | 1ES PT auto-baselining bot |
| `dependabot/…` | Dependabot |
| `copilot/…` | Copilot coding agent |
| `chore/weekly-lock-refresh-*` | `compliance-yarn-lock.yml` weekly bot |
| `*-stable` | Release branches (e.g. `0.85-stable`) |
| `preview-*-test` | Preview test branches (e.g. `preview-0.84-test`) |
| `prepare-release/…` | Release preparation |
| `release/…`, `hotfix/…`, `archive/…` | Reserved by the [branch lifecycle policy](../../docs/branch-lifecycle-policy.md) (`archive/` is the documented way to preserve a branch) |
| `integrate/…` | React Native nightly integration (`integrate/nightly-*`) |
| `cherry-pick/…` | Cherry-pick branches |
| `revert-*` | GitHub "Revert" button on merged PRs |
| `gh-readonly-queue/…` | Merge queue (only if enabled) |
| `gh-pages` | Docs site publishing |

### Pattern syntax & gotchas

GitHub matches ref patterns with Ruby's `File.fnmatch` under the
`File::FNM_PATHNAME` flag. It has sharp edges — each point below was verified
against that engine:

- **A single `*` never crosses `/`.** `foo/*` matches exactly one level
(`foo/bar`), never `foo/a/b`. Inside a single segment `*` is greedy, so
`*-stable` matches both `0.85-stable` and `0.58-vnext-stable`, and
`preview-*-test` requires the literal `-test` suffix (plain `preview-0.84` is
blocked).
- **A bare trailing `**` does *not* cross `/` either.** `foo/**` behaves like
`foo/*` and silently misses nested branches — never use it to mean "everything
under `foo/`".
- **Use `foo/**/*` for a whole namespace.** The `**/` piece matches *zero or
more* directories, so a single `foo/**/*` matches both single-level (`foo/bar`)
and deep (`foo/a/b/c`). That is why each folder namespace needs only this one
entry.
- **`~ALL` — not `refs/heads/**` — means "all branches".** The `include` uses the
special `~ALL` token; a trailing `**` cannot express it (see above).
- **Every `exclude` entry keeps the `refs/heads/` prefix**; patterns match the
full ref path, not the short branch name.
- **Reverts can contain slashes.** GitHub's "Revert" button creates
`revert-<PR#>-<original-branch>` and preserves slashes when the original was
namespaced (e.g. `revert-15633-user/WatsonFix/0.81`). That is why revert takes
two entries: `revert-*` (flat) and `revert-*/**/*` (nested).
- **Matching is case-sensitive.** `fix-*` does not match `Fix-lint`.
- **Unsupported syntax:** no negated character classes (`[^…]`), no brace/extglob
expansion (`{a,b}`, `File::FNM_EXTGLOB`), and backslash is not an escape
character.
- **Creation-only.** The rule blocks new non-conforming branch *names*; it never
touches existing branches or pushes to them.

## Prerequisites

1. **Admin on the repo.** Rulesets are an admin-only setting. Elevate through
just-in-time (JIT) admin access to get the **Admin** role on
`microsoft/react-native-windows`, and drop it when you're done.
2. **GitHub CLI**, authenticated as the elevated account:
```powershell
gh --version
gh auth login # or set $env:GH_TOKEN
```

## Apply (first time)

```powershell
cd .ado/repo-rules
gh api --method POST repos/microsoft/react-native-windows/rulesets --input branch-namespace-ruleset.json
```

## Update an existing ruleset

Look up the ruleset id, then `PUT` the edited file:

```powershell
gh api repos/microsoft/react-native-windows/rulesets --jq '.[] | "\(.id): \(.name)"'
gh api --method PUT repos/microsoft/react-native-windows/rulesets/RULESET_ID --input branch-namespace-ruleset.json
```

## Change the allowed patterns

Edit `conditions.ref_name.exclude` in the JSON, commit for review, then run the
**update** command above. Add a single `refs/heads/foo/**/*` entry per folder
namespace — it matches that namespace at any depth. Mind the
[pattern gotchas](#pattern-syntax--gotchas): keep the `refs/heads/` prefix and
never use a bare `foo/**` (it will not cross `/`).

## Verify

```powershell
# A non-namespaced name should be rejected:
git push upstream HEAD:policy-smoke-test

# A namespaced name should succeed, then clean it up:
git push upstream HEAD:user/<your-alias>/policy-smoke-test
git push upstream --delete user/<your-alias>/policy-smoke-test
```

Or inspect which rules would apply to a candidate branch name (it need not exist):

```powershell
gh api repos/microsoft/react-native-windows/rules/branches/policy-smoke-test
```

## Roll back

```powershell
# Soft-disable: set "enforcement": "disabled" in the JSON and PUT it, or delete it:
gh api --method DELETE repos/microsoft/react-native-windows/rulesets/RULESET_ID
```

## Notes

- To trial the policy without blocking anyone, set `"enforcement": "evaluate"`
first (violations are logged, not enforced), then switch to `"active"`.
- The allow list was derived from the repo's live branches plus the tooling that
creates them: Dependabot, Copilot, [`compliance-yarn-lock.yml`](../../.github/workflows/compliance-yarn-lock.yml),
[`integrate-rn`](../../packages/@rnw-scripts/integrate-rn), the release process,
and the protected prefixes in the [branch lifecycle policy](../../docs/branch-lifecycle-policy.md).
- If you later want an emergency bypass, add an entry to `bypass_actors` (for
example an `OrganizationAdmin` or a specific `Team`) rather than loosening the
patterns.
38 changes: 38 additions & 0 deletions .ado/repo-rules/branch-namespace-ruleset.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "Require namespaced branches",
"target": "branch",
"enforcement": "active",
"bypass_actors": [],
"conditions": {
"ref_name": {
"include": ["~ALL"],
"exclude": [
"refs/heads/user/**/*",

"refs/heads/users/merlinbot/**/*",
"refs/heads/dependabot/**/*",
"refs/heads/copilot/**/*",
"refs/heads/chore/weekly-lock-refresh-*",

"refs/heads/*-stable",
"refs/heads/preview-*-test",
"refs/heads/prepare-release/**/*",
"refs/heads/release/**/*",
"refs/heads/hotfix/**/*",

"refs/heads/integrate/**/*",
"refs/heads/cherry-pick/**/*",
"refs/heads/archive/**/*",

"refs/heads/revert-*",
"refs/heads/revert-*/**/*",
"refs/heads/gh-readonly-queue/**/*",

"refs/heads/gh-pages"
]
}
},
"rules": [
{ "type": "creation" }
]
}
4 changes: 2 additions & 2 deletions .config/1espt/PipelineAutobaseliningConfig.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
## DO NOT MODIFY THIS FILE MANUALLY. This is part of auto-baselining from 1ES Pipeline Templates. Go to [https://aka.ms/1espt-autobaselining] for more details.
## DO NOT MODIFY THIS FILE MANUALLY. This is part of auto-baselining from 1ES Pipeline Templates. Go to [https://aka.ms/1espt-autobaselining] for more details.

Comment thread
vmoroz marked this conversation as resolved.
pipelines:
163759:
retail:
Expand Down
Loading