Skip to content

bot: dry-run, modes, separate commands for merge and bump, url-change, recipe selector - #6763

Closed
MMesch wants to merge 1 commit into
mainfrom
bot/dry-run
Closed

MMesch wants to merge 1 commit into
mainfrom
bot/dry-run

Conversation

@MMesch

@MMesch MMesch commented Sep 15, 2026 •

Copy link
Copy Markdown
Collaborator

Splits the version-bump bot into four named stages, exposed as a --mode flag. You can now run any of the first N stages instead of always doing everything: check → plan → edit → submit. Also skips candidate versions whose rendered URL equals the current one, which should stop the no-source-change "bump" PRs seen on commit-pinned recipes (#6698, #6570). And fixes a pre-existing indentation bug in the recipe rewriter (see second commit).

The refactor is a bit larger than strictly needed for adding the flag. The old bump_recipe_version did URL probing, hashing, git branching and PR opening in one function, so a mode flag alone would have meant a lot of if mode >= X: checks inside it. Splitting into one function per stage means the dispatcher does one check per stage, and it should be easier to change individual stages later (for example if we want to run the check stage in parallel, add a confirmation before submit, or replace URL probing with a GitHub/PyPI API call). Data flows through two small dataclasses (Candidate after check, BumpAction after plan) rather than inheritance — happy to revisit if that turns out worse.

emci/bot/bump_recipes_versions.py

  • Add Mode enum (@total_ordering + a custom __lt__ that compares by definition order — the default str comparison would put edit < plan alphabetically) and four stage functions one per mode: check_bump / plan_bump / edit_bump / submit_bump.
  • Add dataclasses Candidate and BumpAction.
  • Rename get_new_version → find_new_version_url. Returns the URL (not the sha256), returns the recipe's current version even when no newer release is found, and skips candidate versions whose rendered URL equals the current one.
  • Move the previously-inlined skip list to module scope as SKIP_RECIPES; add discover_recipes() helper.
  • Pull _process_existing_bot_prs() and _checkout_target_branch() out of the dispatcher.
  • Use git -c user.name=... -c user.email=... on the one commit call instead of git config --global. No writes to the user's global git config, so --mode edit works on read-only $HOME. Removes the bot_github_user_ctx / set_bot_user wrapping in this file (both still used elsewhere; git_utils.py untouched).
  • Stage functions don't print anything; the dispatcher prints all output (per recipe: BUMP / no bump / error, followed by url: and — in plan/edit/submit — sha256:).

emci/__main__.py

  • Add --mode {check,plan,edit,submit}, default check.
  • Add --limit N (default 20). Counts successful completions of the last stage, so --limit 20 in submit mode caps at 20 real PRs.

.github/workflows/new_versions.yaml

  • Pass --mode submit explicitly (default flipped to check). Otherwise unchanged.

Second commit — bot: preserve 4-space list indent when rewriting recipe.yaml
Pre-existing bug in update_recipe_version: ruamel.yaml's default sequence indent (2, offset 0) rewrote 4-space-dash list items as 2-space-dash, producing noisy indentation-only diffs on every bump for recipes with patch lists. Configure the dumper with sequence=4, offset=2 to match the repo's recipe style.

@MMesch
MMesch force-pushed the bot/dry-run branch 2 times, most recently from 567f893 to 1b73a2f Compare September 15, 2026 09:32
@IsabelParedes IsabelParedes added Review In Progress bot Bot related 🤖 ci Workflows and actions labels Sep 15, 2026

@IsabelParedes IsabelParedes left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Testing the plan mode locally, it creates new branches and tries to commit changes. It should not, based on the comments that should only be done by the edit mode.

Switched to a new branch 'bump-raylib_5.5_to_6.0_for_origin/main'
error: gpg failed to sign the data:
gpg: skipped "emscripten-forge-bot <emscripten-forge-bot@users.noreply.github.com>": No secret key
[GNUPG:] INV_SGNR 9 emscripten-forge-bot <emscripten-forge-bot@users.noreply.github.com>
[GNUPG:] FAILURE sign 17
gpg: signing failed: No secret key

fatal: failed to write commit object

Comment thread emci/bot/bump_recipes_versions.py Outdated
# recipe convention of 4-space-indented list dashes (ruamel default is 2).
yaml = YAML()
yaml.width = 120
yaml.indent(mapping=2, sequence=4, offset=2)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree with this change. Most recipes already use the 2-space indentation, so changing that will create more "noise".

Also, I don't consider it noise. I think it's a way of cleaning up the recipes to make them look nicer.

I prefer the default 2-space indentation because the text starts 2-spaces from the parent key in both lists and mappings, and the dashes just act as decorators:

# Default
source:
  url: https://gh.com
  sha256: lskdflskdjf

# looks the same as
source:
- url: https://gh.com
  sha256: lskdflskdjf

With custom 4-space indentation, they would be different

source:
  url: https://gh.com
  sha256: lskdflskdjf

# not the same, even though it is the same
source:
  - url: https://gh.com
    sha256: lskdflskdjf

Not sure if I'm explaining it correctly, but let me know if you have other opinions :)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm completely fine with 2 space. I don't have an opinion, I only saw while testing that these were additional changes introduced unrelated to bumping. I wonder though whether we should keep an explicit call here so that we don't just rely on the defaults. Maybe later on we could try to move this to a formatter for recipe PRs?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should keep it as is. If later on we have a formatter, it could be easily added.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, so with explicit line or without? I have a light tendency to keep it to make the choices explicit and avoid default changes leaking in but I don't mind.

Comment on lines +34 to +37
check = "check" # HEAD candidate URLs until one exists.
plan = "plan" # + download the winning tarball and compute sha256.
edit = "edit" # + create branch, write recipe.yaml, commit locally (no push).
submit = "submit" # + push branch, open PR. Default in CI.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With these modes we would still not have a complete dry-run which we can use to verify the changes that running the workflow applies to the recipes.

A complete dry-run would write the bumped recipe.yaml files, but it would not commit or create multiple branches. Ideally, all changes would remain unstaged in the current branch, that way they can be easily verified and discarded.

The current edit mode would create 20 different commits and branches locally, which would not be easy to verify.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A complete dry-run would write the bumped recipe.yaml files, but it would not commit or create multiple branches.

Shall we maybe just have a no git flag? And then we could also have a --dry-run that just activates this --mode edit and --no-git? I find it useful to be able to run stuff with git as well, since that is what the bot is actually doing in the end.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be useful for you to have git enabled?

As in, creating 20 different commits in 20 different branches locally would be useful for something?

For me personally, I wouldn't find that useful. I would prefer to see all the changes in the current branch I'm working on. So then I wouldn't have to verify and delete 20 branches I don't need.

Or what sort of local workflow do you have in mind?

Comment thread emci/bot/bump_recipes_versions.py Outdated
assert get_current_branch_name() == pr_target_branch


def bump_recipe_versions(recipe_dir, pr_target_branch, pr_limit=20, mode: Mode = Mode.check):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably time to increase the limit to 40 or 50. We have many more packages now than when we started.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. Maybe a separate PR for this so that we don't mix too much?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that works

@IsabelParedes

Copy link
Copy Markdown
Member

Would it be possible to extract the changes that fix this issue and push them to a separate PR?

Also skips candidate versions whose rendered URL equals the current one, which should stop the no-source-change "bump" PRs seen on commit-pinned recipes (#6698, #6570).

This is a bug that affects the nightly workflow runs, so it would be good to have that merged soon.

Restructures the version-bump bot around two ideas: a stage ladder for
"how far the run goes" and a shared ops model for "execute vs preview."
The two phases the bot used to run together (merging its own open PRs
and opening new bumps) are split into independent subcommands.

Mode ladder for `bump-recipes-versions`
---------------------------------------
Each mode does its own stage plus every earlier one; default is `check`.

  check   HEAD candidate URLs until one exists. No downloads.
  plan    + download the tarball and compute sha256.
  edit    + create a local branch, write recipe.yaml, commit (no push).
  submit  + push the branch and open the PR. CI passes this explicitly.

Dry-run (orthogonal flag)
-------------------------
--dry-run turns every write / git / gh mutation into a printed line and
shows a unified diff of the recipe.yaml change instead of writing it.
Reads (HEADs, gh pr list, gh pr checks, gh pr view) still run so
decisions are made against real state. `--mode submit --dry-run` is a
full rehearsal that touches nothing.

Ops model
---------
Stage functions (edit_bump, submit_bump, merge processing, target
checkout) build a list of SubprocessCmd / FileWrite dataclasses instead
of invoking side effects directly. A single execute() runs them for
real; print_ops() prints them without running. Dry-run picks the
printer — same op list, no drift possible.

Data classes
------------
Candidate (post-check) and BumpAction (BumpAction inherits from
Candidate, adds sha256 + edit_ops + edit_cleanup + submit_ops). plan_bump
builds the whole plan up front; edit_bump/submit_bump are thin runners.

URL-unchanged skip
------------------
In find_new_version_url, skip candidate versions whose rendered URL
matches the current one. Stops the bot from opening no-source-change
"bump" PRs on commit-pinned recipes whose URL template doesn't
reference version (#6698, #6570).

Split subcommands
-----------------
- bump-recipes-versions: open new bump PRs. In submit mode, fetches
  open bot PRs read-only to skip recipes with an in-flight PR (skipped
  entirely when --recipe is given).
- merge-open-prs: NEW subcommand for the merge/label pass over the
  bot's already-open PRs. Same --dry-run semantics.
- CI (.github/workflows/new_versions.yaml) runs the two as separate
  steps for independent failure signal.

Other flags on bump-recipes-versions
------------------------------------
- --limit N (default 20): stop after N successful completions of the
  terminal stage — 20 real PRs in submit, 20 hashed candidates in plan.
- --recipe NAME (repeatable): scope to specific recipes.

Misc
----
- Normalise recipe.yaml block sequences to 2-space dashes on write
  (ruamel default; standardises across recipes as bumps land).
- Bot-authored commits use `git -c user.name=... -c user.email=...`
  on the one commit call instead of `git config --global`, so
  --mode edit works on read-only $HOME.
- shlex.join on dry-run argv output — copy-pasteable command lines.
- One "Would" line per PR grouped with that PR's header.
- Loud DRY RUN banner top and bottom of a dry-run run.
- emci/README.md updated with both subcommands and worked examples.

Removed helpers no longer needed in this file: bot_github_user_ctx,
set_bot_user (still used by update_matplotlib_fontcache.py via
git_utils.py; that module is untouched).
@MMesch

MMesch commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator Author

@IsabelParedes I did more changes. There is now a real dry-run. It looks like this for a bump of a specific recipe:

➜ python -m emci bot bump-recipes-versions main --recipe hypothesis --mode submit --dry-run
========================================================================
  DRY RUN — no writes, no git mutations, no gh mutations, no PRs
========================================================================
Bumping recipes in /home/matto/Projects/Conda/pixi/recipes/recipes/recipes_emscripten to main [mode=submit, dry-run]
switching from bot/dry-run to main
     [dry] would run    git stash
     [dry] would run    git fetch origin main
     [dry] would run    git checkout main
  BUMP     hypothesis: recipe @ 6.161.6 → available 6.161.7
             url:    https://pypi.io/packages/source/h/hypothesis/hypothesis-6.161.7.tar.gz
             sha256: 05dfa54c44d5c4a768c9aa8e3126824f8f9aa83f6691706abaf7fc3ee010b053
     [dry] would run    git checkout -b bump-hypothesis_6.161.6_to_6.161.7_for_main
     [dry] would write  /home/matto/Projects/Conda/pixi/recipes/recipes/recipes_emscripten/hypothesis/recipe.yaml
     [dry]   --- /home/matto/Projects/Conda/pixi/recipes/recipes/recipes_emscripten/hypothesis/recipe.yaml
     [dry]   +++ /home/matto/Projects/Conda/pixi/recipes/recipes/recipes_emscripten/hypothesis/recipe.yaml
     [dry]   @@ -2,3 +2,3 @@
     [dry]      name: hypothesis
     [dry]   -  version: 6.161.6
     [dry]   +  version: 6.161.7
     [dry]   
     [dry]   @@ -10,3 +10,3 @@
     [dry]    - url: https://pypi.io/packages/source/${{ name[0] }}/${{ name }}/${{ name }}-${{ version }}.tar.gz
     [dry]   -  sha256: e276f5705cb929ff059785db8fd3a5074c9081529944833a0a4b4f6b9b9303dd
     [dry]   +  sha256: 05dfa54c44d5c4a768c9aa8e3126824f8f9aa83f6691706abaf7fc3ee010b053
     [dry]   
     [dry] would run    git add /home/matto/Projects/Conda/pixi/recipes/recipes/recipes_emscripten/hypothesis
     [dry] would run    git -c user.name=emscripten-forge-bot -c user.email=emscripten-forge-bot@users.noreply.github.com commit -m 'Update hypothesis from 6.161.6 to 6.161.7'
     [dry] would run    git checkout bot/dry-run --force
     [dry] would run    git push -u origin bump-hypothesis_6.161.6_to_6.161.7_for_main --force
     [dry] would run    gh repo set-default emscripten-forge/recipes
     [dry] would run    gh pr create -B main --title 'Update hypothesis from 6.161.6 to 6.161.7' --body 'Beep-boop-beep! Whistle-whistle-woo!' --label Automerge
     [dry] would run    git branch -D bump-hypothesis_6.161.6_to_6.161.7_for_main
Total (submit): 1
========================================================================
  DRY RUN complete — nothing was actually executed
========================================================================

And like this for merging open PRs (which I split out into an independent subcommand):

➜ python -m emci bot merge-open-prs main --dry-run
========================================================================
  DRY RUN — no gh mutations
========================================================================
Merging open bot PRs on main [dry-run]
Checking opened PRs and merge them if green!
PR #6814  (numcodecs)
Labels for PR 6814: {'labels': [{'id': 'LA_kwDOG-uhD88AAAABTvTcHQ', 'name': 'Needs Human Review', 'description': 'The CI is not passing, automerge is disabled', 'color': 'd93f0b'}, {'id': 'LA_kwDOG-uhD88AAAABUoM2_g', 'name': 'Automerge', 'description': 'The PR will be automatically merged if CI is green', 'color': '49D06A'}]}
     [dry] would run    gh pr edit 6814 --add-label 'Needs Human Review'
     [dry] would run    gh pr comment 6814 --body 'Either the CI is failing, or the recipe is not tested. I need help from a human.' --edit-last
...

@MMesch

MMesch commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator Author

We don't necessarily need mode anymore with this, I guess. Maybe it's still useful to actually execute the stages locally and it's not a big overhead to keep though. LMK.

@MMesch MMesch changed the title bot: split bump-recipes-versions into check/plan/edit/submit stages bot: dry-run, modes, separate commands for merge and bump, url-change, recipe selector Sep 18, 2026
@MMesch

MMesch commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator Author

Should maybe activate dry-run by default to avoid accidental things happening.

@MMesch

MMesch commented Sep 22, 2026

Copy link
Copy Markdown
Collaborator Author

closing for now. @IsabelParedes taking over directly.

@MMesch MMesch closed this Sep 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot Bot related 🤖 ci Workflows and actions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants