1515log = logging .getLogger (__name__ )
1616
1717VersionPart = Literal ["major" , "minor" , "patch" ]
18- _VERSION_PATTERN = re .compile (r"^(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)$" )
19- _INIT_VERSION_PATTERN = re .compile (r'^(?P<before>__version__\s*=\s*")(?P<version>\d+\.\d+\.\d+)(?P<after>"\s*)$' , re .MULTILINE )
18+ _VERSION_PATTERN = re .compile (r"^(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\.\w+)? $" )
19+ _INIT_VERSION_PATTERN = re .compile (r'^(?P<before>__version__\s*=\s*")(?P<version>\d+\.\d+\.\d+(?:\.\w+)? )(?P<after>"\s*)$' , re .MULTILINE )
2020_PYPROJECT_VERSION_PATTERN = re .compile (
21- r'(?m)^(?P<before>\[project\]\n(?:.*\n)*?^version\s*=\s*")(?P<version>\d+\.\d+\.\d+)(?P<after>"\s*)$'
21+ r'(?m)^(?P<before>\[project\]\n(?:.*\n)*?^version\s*=\s*")(?P<version>\d+\.\d+\.\d+(?:\.\w+)? )(?P<after>"\s*)$'
2222)
2323_UNRELEASED_HEADER = "# Unreleased (main)\n "
2424
@@ -35,22 +35,40 @@ def bump_version(major: bool, minor: bool, patch: bool, target_version: str | No
3535 raise click .ClickException ("Working directory is not clean. Please commit or stash your changes first." )
3636
3737 log .info ("bump_version called: major=%s, minor=%s, patch=%s, target_version=%s" , major , minor , patch , target_version )
38+
39+ # determine part to bump
3840 version_part = resolve_version_selection (major = major , minor = minor , patch = patch , target_version = target_version )
3941 log .info ("Resolved version_part=%s" , version_part )
4042
43+ # bump it (never incrementing patch because it was already updated with the last .dev version)
4144 repo_root = find_repo_root ()
4245 log .info ("Repo root: %s" , repo_root )
43- new_version = bump_repo_version (repo_root , version_part = version_part , target_version = target_version , dry_run = dry_run )
46+ new_version = bump_repo_version (repo_root , version_part = version_part , target_version = target_version , dry_run = dry_run , increment_patch = False )
4447 log .info ("New version: %s" , new_version )
48+
49+ # commit and tag for new version
4550 if dry_run :
4651 click .echo (f"Dry run complete. Version would be bumped to { new_version } " )
52+ return
4753 else :
4854 os .system ("uv lock" )
4955 click .echo (f"Bumped version to { new_version } " )
5056 os .system ("git add -u" )
5157 os .system (f'git commit -m "Release v{ new_version } "' )
5258 os .system (f"git tag v{ new_version } " )
5359
60+ # bump patch and add suffix for next dev iteration
61+ new_snapshot_version = bump_repo_version (
62+ repo_root , version_part = "patch" , target_version = None , dry_run = dry_run , target_version_suffix = ".dev0" , increment_patch = True ,
63+ )
64+ log .info ("New snapshot version: %s" , new_snapshot_version )
65+
66+ # commit the new snapshot version
67+ os .system ("uv lock" )
68+ click .echo (f"Bumped version to { new_snapshot_version } " )
69+ os .system ("git add -u" )
70+ os .system (f'git commit -m "Set version to v{ new_snapshot_version } "' )
71+
5472
5573def find_repo_root () -> Path :
5674 return Path (REPO_ROOT )
@@ -74,7 +92,15 @@ def resolve_version_selection(*, major: bool, minor: bool, patch: bool, target_v
7492 raise click .ClickException ("No version bump selected. Use --major, --minor, --patch or --version." )
7593
7694
77- def bump_repo_version (repo_root : Path , * , version_part : VersionPart | None , target_version : str | None , dry_run : bool = False ) -> str :
95+ def bump_repo_version (
96+ repo_root : Path ,
97+ * ,
98+ version_part : VersionPart | None ,
99+ target_version : str | None ,
100+ dry_run : bool = False ,
101+ target_version_suffix : str | None = None ,
102+ increment_patch : bool = True ,
103+ ) -> str :
78104 pyproject_path = repo_root / "pyproject.toml"
79105 init_path = repo_root / "src" / "serena" / "__init__.py"
80106 changelog_path = repo_root / "CHANGELOG.md"
@@ -100,19 +126,24 @@ def bump_repo_version(repo_root: Path, *, version_part: VersionPart | None, targ
100126 else :
101127 if version_part is None :
102128 raise click .ClickException ("No version target specified." )
103- new_version = increment_version (current_version , version_part )
129+ new_version = increment_version (current_version , version_part , increment_patch = increment_patch )
130+ if target_version_suffix is not None :
131+ new_version += target_version_suffix
104132 log .info ("New version will be: %s" , new_version )
105133
106134 new_pyproject_text = replace_version (pyproject_text , _PYPROJECT_VERSION_PATTERN , new_version , "pyproject.toml" )
107135 new_init_text = replace_version (init_text , _INIT_VERSION_PATTERN , new_version , "src/serena/__init__.py" )
108- new_changelog_text = update_changelog (changelog_text , new_version )
109136
110137 file_changes : list [tuple [Path , str , str ]] = [
111138 (pyproject_path , pyproject_text , new_pyproject_text ),
112139 (init_path , init_text , new_init_text ),
113- (changelog_path , changelog_text , new_changelog_text ),
114140 ]
115141
142+ # update changelog only for actual releases (not -dev versions with suffixes)
143+ if target_version_suffix is None :
144+ new_changelog_text = update_changelog (changelog_text , new_version )
145+ file_changes .append ((changelog_path , changelog_text , new_changelog_text ))
146+
116147 if dry_run :
117148 for path , old , new in file_changes :
118149 if old != new :
@@ -139,6 +170,13 @@ def _print_diff(old: str, new: str) -> None:
139170
140171
141172def extract_version (text : str , pattern : re .Pattern [str ], file_label : str ) -> str :
173+ """
174+ Extracts the core version Major.Minor.Patch in the given text
175+ :param text:
176+ :param pattern: the pattern to search for
177+ :param file_label: file reference for error messages
178+ :return: the core version
179+ """
142180 match = pattern .search (text )
143181 if match is None :
144182 raise click .ClickException (f"Could not find version in { file_label } ." )
@@ -152,7 +190,7 @@ def replace_version(text: str, pattern: re.Pattern[str], new_version: str, file_
152190 return f"{ text [: match .start ('version' )]} { new_version } { text [match .end ('version' ) :]} "
153191
154192
155- def increment_version (version : str , version_part : VersionPart ) -> str :
193+ def increment_version (version : str , version_part : VersionPart , increment_patch : bool ) -> str :
156194 match = _VERSION_PATTERN .fullmatch (version )
157195 if match is None :
158196 raise click .ClickException (f"Unsupported version format: { version } " )
@@ -165,7 +203,12 @@ def increment_version(version: str, version_part: VersionPart) -> str:
165203 return f"{ major + 1 } .0.0"
166204 if version_part == "minor" :
167205 return f"{ major } .{ minor + 1 } .0"
168- return f"{ major } .{ minor } .{ patch + 1 } "
206+ elif version_part == "patch" :
207+ if increment_patch :
208+ patch += 1
209+ return f"{ major } .{ minor } .{ patch } "
210+ else :
211+ raise ValueError (version_part )
169212
170213
171214def validate_version_string (version : str ) -> str :
0 commit comments