11import re
22import subprocess
33from enum import StrEnum
4+ from pathlib import Path
45from typing import Annotated , Optional
56
67import typer
@@ -37,6 +38,12 @@ def _is_on_main_branch() -> bool:
3738 return gitta .get_current_branch () == "main"
3839
3940
41+ def _is_staging_area_clean () -> bool :
42+ # git diff --cached shows the changes staged for commit.
43+ # If there are no staged changes, the output will be empty.
44+ return not gitta .run_git ("diff" , "--cached" ).strip ()
45+
46+
4047def _get_metta_repo_remote () -> str :
4148 matching_remote_names = [
4249 remote_name
@@ -114,6 +121,81 @@ def _get_next_version(*, package: str, version_override: Optional[str]) -> str:
114121 return next_version
115122
116123
124+ def _pin_dependency_version (* , package : str , dependency : str , version : str , dry_run : bool ) -> None :
125+ pyproject_path = Path (get_repo_root ()) / "packages" / package / "pyproject.toml"
126+ assert pyproject_path .exists ()
127+
128+ # Pattern to match dependency in the dependencies array.
129+ # Matches: "dependency", or "dependency>=X.Y.Z", or "dependency==X.Y.Z"
130+ pattern = rf'("{ dependency } (?:[><=~!]+[\d.]+)?",)'
131+ replacement = f'"{ dependency } =={ version } ",'
132+
133+ old_content = pyproject_path .read_text ()
134+ new_content = re .sub (pattern , replacement , old_content )
135+
136+ assert re .search (pattern , old_content )
137+ assert new_content != old_content
138+
139+ current_branch = gitta .get_current_branch ()
140+ new_branch_name = f"chore/update-{ package } -{ dependency } -to-{ version } "
141+ new_pr_title = f"chore: update { package } { dependency } to { version } "
142+ new_pr_body = "This PR was automatically created by `metta/setup/tools/publish.py`."
143+ new_commit_msg = new_pr_title
144+
145+ if dry_run :
146+ info (f"Would pin { dependency } to =={ version } in { package } /pyproject.toml" )
147+ info (f"Would commit changes in new branch { new_branch_name } " )
148+ info ("Would put up PR to update cogames' mettagrid dependency." )
149+ return
150+
151+ if not _is_staging_area_clean ():
152+ error (f"Staging area is not clean. Can't safely write and commit changes to { package } /pyproject.toml" )
153+ raise typer .Exit (1 )
154+
155+ if not typer .confirm (
156+ f"Okay to write changes to { package } /pyproject.toml and create branch { new_branch_name } ?" ,
157+ default = True ,
158+ ):
159+ error ("Publishing aborted." )
160+ raise typer .Exit (1 )
161+
162+ info (f"Pinning dependency { dependency } to version { version } in { package } /pyproject.toml" )
163+ pyproject_path .write_text (new_content )
164+
165+ info (f"Creating new branch { new_branch_name } " )
166+ gitta .run_git ("checkout" , "-b" , new_branch_name )
167+
168+ info (f"Staging changes to { package } /pyproject.toml" )
169+ gitta .run_git ("add" , str (pyproject_path ))
170+
171+ info (f"Committing changes to { package } /pyproject.toml" )
172+ gitta .run_git ("commit" , "-m" , new_commit_msg )
173+
174+ if typer .confirm (
175+ f"Automatically put up PR from { new_branch_name } to { current_branch } ?" ,
176+ default = True ,
177+ ):
178+ info ("Pushing branch to remote..." )
179+ gitta .run_git ("push" , "-u" , "origin" , new_branch_name )
180+
181+ info (f"Putting up PR from { new_branch_name } to { current_branch } ..." )
182+ pr_url = gitta .run_gh (
183+ "pr" ,
184+ "create" ,
185+ "--title" ,
186+ new_pr_title ,
187+ "--body" ,
188+ new_pr_body ,
189+ "--head" ,
190+ new_branch_name ,
191+ "--base" ,
192+ current_branch ,
193+ )
194+ success (f"PR created: { pr_url } " )
195+ else :
196+ warning (f"Skipping putting up PR from { new_branch_name } to { current_branch } ; do it yourself later." )
197+
198+
117199def _create_and_push_tag_to_monorepo (* , package : str , version : str , remote : str , dry_run : bool ) -> None :
118200 tag_prefix = _tag_prefix_for_package (package )
119201 tag = f"{ tag_prefix } { version } "
@@ -125,7 +207,7 @@ def _create_and_push_tag_to_monorepo(*, package: str, version: str, remote: str,
125207 info (f"Tagging { package } { version } ..." )
126208 gitta .run_git ("tag" , "-a" , tag , "-m" , f"Release { package } { version } " )
127209 gitta .run_git ("push" , remote , tag )
128- success (f"Created and pushed tag { tag } to remote '{ remote } '" )
210+ success (f"Pushed tag { tag } to remote '{ remote } '" )
129211
130212
131213def _post_to_discord (
@@ -270,6 +352,13 @@ def _publish(
270352 print ()
271353 header (f"Resuming with publishing { package } ..." )
272354
355+ _pin_dependency_version (
356+ package = "cogames" ,
357+ dependency = "mettagrid" ,
358+ version = mettagrid_version ,
359+ dry_run = dry_run ,
360+ )
361+
273362 _create_and_push_tag_to_monorepo (package = package , version = next_version , remote = remote , dry_run = dry_run )
274363
275364 try :
0 commit comments