Skip to content

Commit 8553395

Browse files
committed
Merge remote-tracking branch 'origin/next' into merge-train/barretenberg
2 parents ec2df16 + 8b62274 commit 8553395

9 files changed

Lines changed: 270 additions & 212 deletions

File tree

.github/workflows/ci3.yml

Lines changed: 34 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ jobs:
3434
with:
3535
# The commit to checkout. We want our actual commit, and not the result of merging the PR to the target.
3636
ref: ${{ github.event.pull_request.head.sha || github.sha }}
37+
# Fetch PR commits depth (we'll deepen by 1 in squash script if needed)
38+
fetch-depth: ${{ github.event.pull_request.commits || 0 }}
39+
token: ${{ secrets.AZTEC_BOT_GITHUB_TOKEN }}
3740

3841
- name: CI Merge Queue Override (grind on PR)
3942
if: contains(github.event.pull_request.labels.*.name, 'ci-merge-queue')
@@ -98,10 +101,21 @@ jobs:
98101
echo "$GCP_SA_KEY" | base64 -w 0 > gcp_sa_key.b64
99102
echo "GCP_SA_KEY_B64=$(cat gcp_sa_key.b64)" >> $GITHUB_ENV
100103
104+
- name: Get Tree Hash
105+
run: echo "TREE_HASH=$(git rev-parse HEAD^{tree})" >> $GITHUB_ENV
106+
107+
- name: Check CI Cache
108+
id: ci_cache
109+
uses: actions/cache@v3
110+
with:
111+
path: ci-success.txt
112+
key: ci-${{ github.event_name == 'merge_group' && 'merge-queue' || env.CI_FULL == '1' && 'full' || env.CI_DOCS == '1' && 'docs' || env.CI_BARRETENBERG == '1' && 'barretenberg' || 'fast' }}-${{ env.TREE_HASH }}
113+
101114
#############
102115
# Run
103116
#############
104117
- name: Run
118+
if: steps.ci_cache.outputs.cache-hit != 'true'
105119
env:
106120
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
107121
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
@@ -136,6 +150,26 @@ jobs:
136150
exec ./ci.sh fast
137151
fi
138152
153+
- name: Save CI Success
154+
if: steps.ci_cache.outputs.cache-hit != 'true'
155+
run: echo "success" > ci-success.txt
156+
157+
# If we have passed CI and labelled with ci-squash-and-merge, squash the PR.
158+
# This will rerun CI on the squash commit - but is intended to be a no-op due to caching.
159+
- name: CI Squash and Merge
160+
if: contains(github.event.pull_request.labels.*.name, 'ci-squash-and-merge')
161+
env:
162+
GITHUB_TOKEN: ${{ secrets.AZTEC_BOT_GITHUB_TOKEN }}
163+
run: |
164+
# Get the base commit (merge-base) for the PR
165+
./scripts/merge-train/squash-pr.sh \
166+
"${{ github.event.pull_request.number }}" \
167+
"${{ github.event.pull_request.head.ref }}" \
168+
"${{ github.event.pull_request.base.ref }}" \
169+
"${{ github.event.pull_request.base.sha }}"
170+
gh pr edit "${{ github.event.pull_request.number }}" --remove-label "ci-squash-and-merge"
171+
gh pr ready "${{ github.event.pull_request.number }}" || true
172+
139173
- name: Download benchmarks
140174
if: github.event_name == 'merge_group'
141175
run: ./ci.sh gh-bench
@@ -155,39 +189,3 @@ jobs:
155189
comment-on-alert: false
156190
fail-on-alert: false
157191
max-items-in-chart: 100
158-
159-
notify:
160-
runs-on: ubuntu-latest
161-
if: github.event_name == 'push' && failure()
162-
needs:
163-
- ci
164-
steps:
165-
- name: Checkout Code
166-
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
167-
168-
- name: Get Context
169-
id: get_context
170-
run: |
171-
authors=$(git log -1 --pretty=format:'%an <%ae>')
172-
echo "authors=${authors}" >> $GITHUB_OUTPUT
173-
# Note, we have to make sure double quotes don't break our JSON.
174-
title=$(git log -1 --pretty=format:'%s' | sed s/\"/\'/g)
175-
echo "commit_title=${title}" >> $GITHUB_OUTPUT
176-
failed_jobs=""
177-
[ "${{ needs.ci-grind.result }}" = "failure" ] && failed_jobs+="ci-grind"
178-
[ "${{ needs.ci.result }}" = "failure" ] && failed_jobs+="ci"
179-
echo "failed_jobs=${failed_jobs}" >> $GITHUB_OUTPUT
180-
cat $GITHUB_OUTPUT
181-
182-
- name: Send Notification
183-
uses: slackapi/slack-github-action@6c661ce58804a1a20f6dc5fbee7f0381b469e001
184-
with:
185-
payload: |
186-
{
187-
"text": "Failure on ${{ github.ref_name }}\nCommit: ${{ steps.get_context.outputs.commit_title }}",
188-
"url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}",
189-
"authors": "${{ steps.get_context.outputs.authors }}",
190-
"failed_jobs": "${{ steps.get_context.outputs.failed_jobs }}"
191-
}
192-
env:
193-
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_NOTIFY_WORKFLOW_TRIGGER_URL2 }}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Merge-Train Create PR
2+
3+
on:
4+
push:
5+
branches:
6+
- 'merge-train/*'
7+
8+
jobs:
9+
create-pr:
10+
runs-on: ubuntu-latest
11+
permissions:
12+
contents: read
13+
pull-requests: write
14+
15+
steps:
16+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
17+
18+
- name: Check if PR exists and create if needed
19+
env:
20+
GITHUB_TOKEN: ${{ secrets.AZTEC_BOT_GITHUB_TOKEN }}
21+
run: |
22+
branch="${{ github.ref_name }}"
23+
24+
# Check if PR already exists for this branch
25+
existing_pr=$(gh pr list --state open --head "$branch" --json number --jq '.[0].number // empty')
26+
27+
if [[ -z "$existing_pr" ]]; then
28+
echo "No PR exists for $branch, creating one"
29+
30+
# Determine base branch (default to next)
31+
base_branch="next"
32+
33+
# Create PR with ci-no-squash label
34+
gh pr create --base "$base_branch" --head "$branch" \
35+
--title "feat: $branch" \
36+
--body "$(echo -e "See [merge-train-readme.md](https://github.com/${{ github.repository }}/blob/next/.github/workflows/merge-train-readme.md).\nThis is a merge-train.")" \
37+
--label "ci-no-squash"
38+
39+
echo "Created PR for $branch"
40+
else
41+
echo "PR #$existing_pr already exists for $branch"
42+
fi

.github/workflows/merge-train-readme.md

Lines changed: 4 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ Inspired by [rust rollups](https://forge.rust-lang.org/release/rollups.html), bu
2929

3030
### Merge Train Lifecycle
3131

32-
1. **Creation**: A merge train PR is created with an empty commit. All existing PRs are updated to prevent issues due to the squash merge.
33-
2. **Accumulation**: Feature PRs are merged into the merge train branch
34-
3. **Auto-merge**: After 4 hours of inactivity (with meaningful commits), the train is automatically merged
35-
4. **Recreation**: The cycle starts again with a new empty merge train
32+
1. **Creation**: A merge train PR is created automatically when changes are pushed to the branch and labeled with `ci-no-squash`.
33+
2. **Accumulation**: Feature PRs are merged into the merge train branch, squashed
34+
3. **Auto-merge**: After 4 hours of inactivity (with meaningful commits), the train is automatically merged with a merge-commit (not a squash)
35+
4. **Recreation**: The cycle starts again with a new merge train
3636

3737
## Handling Merge Failures
3838

@@ -47,31 +47,3 @@ When a merge-train fails due to issues from `next`:
4747
- Merge a revert or workaround into `next`
4848
- The fix will auto-propagate to merge-train via automation
4949
- Best when key assumptions are broken or multiple trains affected
50-
51-
## Resolving Merge Conflicts
52-
53-
When merge conflicts arise in the merge-train branch:
54-
55-
1. Pull the latest merge-train branch locally
56-
2. Resolve conflicts in your editor
57-
3. Push the merge commit directly to the merge-train branch
58-
59-
**Note**: Keep the merge commit when resolving conflicts - squashing won't help with the history divergence issues that occur after merge-train recreation.
60-
61-
## RECOVERING FROM MERGE TRAIN RECREATION
62-
63-
When a merge-train is recreated after squashing, PRs with commits from the old merge-train need rebasing.
64-
65-
### Three Recovery Options:
66-
67-
1. **Automatic (Recommended)**: Add the `auto-rebase` label to your PR
68-
2. **Script**: Run `./scripts/auto_rebase_pr.sh` locally
69-
3. **Manual**: Follow the git instructions posted as a comment on your PR
70-
71-
The automation will detect if your PR contains commits from the old merge-train and post specific rebase instructions as a comment. These instructions use `git rebase --onto` to cleanly move your commits to the new base, preserving your work while avoiding the squashed history.
72-
73-
**Important**:
74-
- PRs without commits from the old merge-train need no action
75-
- The recreation process automatically checks each PR and only comments on affected ones
76-
- The posted git commands help you merge the old PR head and then rebase onto the new base
77-
- Always check your diff afterwards for unexpected changes

.github/workflows/merge-train-recreate.yml

Lines changed: 11 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,7 @@ name: Merge-Train Recreate
33
on:
44
pull_request:
55
types: [closed]
6-
workflow_dispatch:
7-
inputs:
8-
merge_train_branch:
9-
description: 'Merge train branch to recreate (e.g., merge-train/docs)'
10-
required: true
11-
type: string
12-
base_branch:
13-
description: 'Base branch (e.g., next)'
14-
required: true
15-
type: string
16-
default: 'next'
17-
6+
187
jobs:
198
recreate:
209
if: ${{ github.event_name == 'workflow_dispatch' || (github.event.pull_request.merged && startsWith(github.event.pull_request.head.ref, 'merge-train/')) }}
@@ -36,24 +25,14 @@ jobs:
3625
3726
- name: Recreate branch
3827
run: |
39-
# Determine branch names based on event type
40-
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
41-
MERGE_TRAIN_BRANCH="${{ github.event.inputs.merge_train_branch }}"
42-
BASE_BRANCH="${{ github.event.inputs.base_branch }}"
43-
# For manual dispatch, use current SHA and get latest from base branch
44-
HEAD_COMMIT_SHA="${{ github.sha }}"
45-
BASE_COMMIT_SHA=$(git rev-parse origin/$BASE_BRANCH)
46-
else
47-
MERGE_TRAIN_BRANCH="${{ github.event.pull_request.head.ref }}"
48-
BASE_BRANCH="${{ github.event.pull_request.base.ref }}"
49-
HEAD_COMMIT_SHA="${{ github.event.pull_request.head.sha }}"
50-
BASE_COMMIT_SHA="${{ github.event.pull_request.merge_commit_sha }}"
51-
fi
28+
MERGE_TRAIN_BRANCH="${{ github.event.pull_request.head.ref }}"
29+
BASE_BRANCH="${{ github.event.pull_request.base.ref }}"
30+
git fetch origin "$BASE_BRANCH"
31+
git checkout -b "$MERGE_TRAIN_BRANCH" "origin/$BASE_BRANCH"
32+
33+
- name: Push branch
34+
continue-on-error: true
35+
run: |
36+
# This will generally only succeed if the branch does not exist, as a fallback if the branch is unprotected and gets deleted.
37+
git push origin "$MERGE_TRAIN_BRANCH"
5238
53-
# Use aztecbot token for all operations including PR creation
54-
GH_TOKEN=${{ secrets.AZTEC_BOT_GITHUB_TOKEN }} \
55-
./scripts/merge-train/recreate-branch.sh \
56-
"$MERGE_TRAIN_BRANCH" \
57-
"$BASE_BRANCH" \
58-
"$BASE_COMMIT_SHA" \
59-
"$HEAD_COMMIT_SHA"
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Squashed PR Check
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize, reopened, labeled, unlabeled]
6+
7+
jobs:
8+
check-squashed:
9+
runs-on: ubuntu-latest
10+
# Only run if PR doesn't have ci-no-squash label AND is targeting 'next' branch
11+
if: ${{ !contains(github.event.pull_request.labels.*.name, 'ci-no-squash') && github.event.pull_request.base.ref == 'next' }}
12+
13+
steps:
14+
- name: Check if PR is squashed
15+
run: |
16+
# Get number of commits from the event payload
17+
commit_count=${{ github.event.pull_request.commits }}
18+
19+
echo "PR has $commit_count commits"
20+
21+
# Fail if PR has more than 1 commit
22+
if [ "$commit_count" -gt 1 ]; then
23+
echo "::error::This PR has $commit_count commits but must be squashed to a single commit."
24+
echo "::error::Either squash your commits or add the 'ci-no-squash' label if multiple commits are intentional."
25+
exit 1
26+
fi
27+
28+
echo "✅ PR is properly squashed with $commit_count commit"

CI.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,43 @@ ci rlog
256256

257257
This will also show the last completed log if the run has already completed. You can provide a GA run id (output by `ci trigger`) to see a historical run.
258258

259+
## CI Labels and Automation
260+
261+
### Pull Request Labels
262+
263+
The following labels can be used to control CI behavior on pull requests:
264+
265+
- **`ci-squash-and-merge`**: Automatically squashes all commits in your PR into a single commit. Add this label to trigger squashing. The label is automatically removed after squashing. Thanks to content-based caching (see below), subsequent CI runs will be skipped if the content hasn't changed.
266+
267+
- **`ci-no-squash`**: Exempts the PR from the single-commit requirement. Use when multiple commits are intentional (e.g., merge-train PRs).
268+
269+
- **`ci-merge-queue`**: Simulates merge queue behavior on your PR, running the full test suite.
270+
271+
- **`ci-full`**: Forces a full CI run instead of the default fast run.
272+
273+
- **`ci-docs`**: Runs only documentation-related CI checks.
274+
275+
- **`ci-barretenberg`**: Runs only Barretenberg-related CI checks.
276+
277+
- **`ci-no-cache`**: Disables build caching for this CI run.
278+
279+
- **`ci-no-fail-fast`**: Continues running all tests even if some fail.
280+
281+
### Squash Enforcement
282+
283+
PRs targeting `next` must be squashed to a single commit unless labeled with `ci-no-squash`. PRs targeting other branches (like `merge-train/*`) are automatically exempt from this requirement.
284+
285+
### Top-level Content-Based CI Caching
286+
287+
CI3 has granular caching, but as well it includes an additional layer of caching based on git content. When CI completes successfully, it stores a success marker keyed by the hash of the repository's file tree. On subsequent runs, if the exact same content is detected (same tree hash), CI will skip execution entirely.
288+
289+
This is particularly useful when:
290+
- You squash commits using `ci-squash-and-merge` - the resulting single commit has the same content, so CI won't re-run
291+
- You rebase without changes - if the final content is identical, CI is skipped
292+
- Multiple PRs have identical changes - only the first needs to run CI
293+
294+
The content hash is computed using `git rev-parse HEAD^{tree}`, which provides a unique identifier for the entire file tree state, regardless of commit history.
295+
259296
## Denoise Logs
260297

261298
When a CI run is taking place and it has a redis cache available, you will see logs like this:

ci.sh

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -78,26 +78,45 @@ function prep_vars {
7878
export DENOISE_WIDTH=32
7979
}
8080

81+
# We want to run CI with caching - even though it internally uses caching - to quickly catch no-ops that have the same
82+
# git contents.
83+
function run_ci_with_cache {
84+
local ci_mode=$1
85+
local job_id=$2
86+
local bootstrap_cmd=$3
87+
88+
# Check if CI already succeeded for this content
89+
local content_hash=$(git rev-parse HEAD^{tree} | cut -c1-16)
90+
local cache_key="ci-success-${ci_mode}-${content_hash}.txt"
91+
92+
if cache_download "$cache_key" 2>/dev/null; then
93+
if [ -f "$cache_key" ] && grep -q "success" "$cache_key"; then
94+
echo "Found cached success for ${ci_mode} mode with hash $content_hash"
95+
exit 0
96+
fi
97+
fi
98+
99+
# Spin up ec2 instance and run the CI flow
100+
export JOB_ID="$job_id"
101+
bootstrap_ec2 "$bootstrap_cmd"
102+
103+
# Upload success marker
104+
echo "success" > success.txt
105+
cache_upload "$cache_key" success.txt
106+
}
107+
81108
case "$cmd" in
82109
"fast")
83-
# Spin up ec2 instance and run the fast flow.
84-
export JOB_ID="x1-fast"
85-
exec bootstrap_ec2 "./bootstrap.sh ci-fast"
110+
run_ci_with_cache "fast" "x1-fast" "./bootstrap.sh ci-fast"
86111
;;
87112
"full")
88-
# Spin up ec2 instance and run the full flow.
89-
export JOB_ID="x1-full"
90-
exec bootstrap_ec2 "./bootstrap.sh ci-full"
113+
run_ci_with_cache "full" "x1-full" "./bootstrap.sh ci-full"
91114
;;
92115
"docs")
93-
# Spin up ec2 instance and run docs-only CI.
94-
export JOB_ID="x1-docs"
95-
exec bootstrap_ec2 "./bootstrap.sh ci-docs"
116+
run_ci_with_cache "docs" "x1-docs" "./bootstrap.sh ci-docs"
96117
;;
97118
"barretenberg")
98-
# Spin up ec2 instance and run barretenberg-only CI.
99-
export JOB_ID="x1-barretenberg"
100-
exec bootstrap_ec2 "./bootstrap.sh ci-barretenberg"
119+
run_ci_with_cache "barretenberg" "x1-barretenberg" "./bootstrap.sh ci-barretenberg"
101120
;;
102121
"grind")
103122
# Spin up ec2 instance and run the merge-queue flow.

0 commit comments

Comments
 (0)