Coverage for src/mafw/devtools/cli/release/create.py: 99%

62 statements  

« prev     ^ index     » next       coverage.py v7.15.0, created at 2026-09-01 19:49 +0000

1# Copyright 2026 European Union 

2# Author: Bulgheroni Antonio (antonio.bulgheroni@ec.europa.eu) 

3# SPDX-License-Identifier: EUPL-1.2 

4"""Click command for creating a new MAFw release.""" 

5 

6from __future__ import annotations 

7 

8import click 

9 

10from mafw.devtools.dependencies.freeze import ( 

11 freeze_dependencies, 

12 unfreeze_dependencies, 

13 update_requirements_and_readme, 

14) 

15from mafw.devtools.git import ( 

16 check_main_branch, 

17 commit_changes, 

18 commit_dependency_unfreeze, 

19 create_tag, 

20 ensure_clean_git, 

21 prevent_duplicate_tag, 

22 push_changes, 

23) 

24from mafw.devtools.release.changelog import CHANGELOG_FILE, generate_changelog 

25from mafw.devtools.release.checks import check_missing_release 

26from mafw.devtools.release.notes import create_release_note 

27from mafw.devtools.release.versioning import ( 

28 ABOUT_FILE, 

29 NOTICE_FILE, 

30 DocTargetVersionPrompt, 

31 bump_version, 

32 compute_doc_target_version, 

33 next_minor_version, 

34 normalize_hatch_segments, 

35 read_current_version, 

36 update_doc_target_version, 

37 update_notice_version, 

38 validate_doc_target_version, 

39) 

40from mafw.tools.shell_tools import CONSOLE 

41 

42 

43@click.command( 

44 name='create', 

45 context_settings={'help_option_names': ['-h', '--help']}, 

46) 

47@click.argument('segments', type=str, help="Hatch version segment to bump (e.g. 'minor', 'micro', 'rc')") 

48@click.option('--dry-run/--no-dry-run', default=False, show_default=True, help='Print commands without executing.') 

49@click.option('--with-push/--without-push', default=True, show_default=True, help='Enable or skip pushing to remote.') 

50@click.option( 

51 '--with-release-note/--without-release-note', 

52 default=True, 

53 show_default=True, 

54 help='Enable or skip release note generation.', 

55) 

56@click.option( 

57 '--with-missing-release-check/--without-missing-release-check', 

58 default=True, 

59 show_default=True, 

60 help='Enable or skip missing release guard.', 

61) 

62@click.option( 

63 '--auto-doc-version/--manual-doc-version', 

64 default=False, 

65 show_default=True, 

66 help='Auto-update doc target version without prompting.', 

67) 

68@click.option( 

69 '--new-doc-version', default=None, type=click.STRING, help='Override documentation target version (major.minor).' 

70) 

71def create( 

72 segments: str, 

73 dry_run: bool, 

74 with_push: bool, 

75 with_release_note: bool, 

76 with_missing_release_check: bool, 

77 auto_doc_version: bool, 

78 new_doc_version: str | None, 

79) -> None: 

80 """Prepare a new MAFw release from the main branch. 

81 

82 The command bumps the project version via hatch, updates the documentation 

83 target version, updates NOTICE.txt, regenerates CHANGELOG.md, optionally 

84 generates release notes, commits tracked release files, creates a local git 

85 tag, and optionally pushes commits and tags to code.europa.eu/main using ssh. 

86 

87 Accepted segments: major, minor, micro, rc, alpha, beta, release. 

88 

89 Segments can be combined with a comma to produce compound versions. 

90 Use at most one core segment (major, minor, micro) and at most one 

91 pre-release segment (rc, alpha, beta). 

92 

93 Examples: 

94 

95 \b 

96 minor bump minor version (e.g. 2.3.0 -> 2.4.0) 

97 micro bump micro/patch version (e.g. 2.3.0 -> 2.3.1) 

98 minor,rc bump minor + start release candidate (2.3.0 -> 2.4.0rc0) 

99 rc increment the RC counter (e.g. 2.4.0rc0 -> 2.4.0rc1) 

100 minor,alpha bump minor + start alpha (e.g. 2.3.0 -> 2.4.0a0) 

101 release promote pre-release to stable (e.g. 2.4.0rc1 -> 2.4.0) 

102 """ 

103 current_version = read_current_version() 

104 normalized_segments = normalize_hatch_segments(segments) 

105 

106 if dry_run: 

107 CONSOLE.print( 

108 'Dry-run mode enabled: commands are printed; git/changelog writes are skipped; ' 

109 f'version is resolved via hatch and {ABOUT_FILE} is restored afterwards.' 

110 ) 

111 else: 

112 check_main_branch() 

113 ensure_clean_git() 

114 if with_missing_release_check: 114 ↛ 117line 114 didn't jump to line 117 because the condition on line 114 was always true

115 check_missing_release(current_version) 

116 

117 original_pyproject_toml = freeze_dependencies(dry_run=dry_run) 

118 

119 version = bump_version(dry_run=dry_run, segment=normalized_segments) 

120 

121 doc_target_version = compute_doc_target_version(version, normalized_segments, new_doc_version) 

122 if auto_doc_version: 

123 CONSOLE.print(f'Documentation target version set to {doc_target_version}.') 

124 else: 

125 CONSOLE.print('Provide a new documentation target version.') 

126 prompt_default = validate_doc_target_version(doc_target_version) 

127 doc_target_version = DocTargetVersionPrompt( 

128 'Documentation target version', 

129 show_default=True, 

130 min_version=next_minor_version(version), 

131 )(default=prompt_default) 

132 CONSOLE.print(f'Documentation target version selected: {doc_target_version}.') 

133 

134 update_doc_target_version(doc_target_version, dry_run=dry_run) 

135 

136 if not dry_run: 

137 prevent_duplicate_tag(version) 

138 

139 update_notice_version(version, dry_run=dry_run) 

140 generate_changelog(version, dry_run=dry_run) 

141 update_requirements_and_readme(dry_run=dry_run) 

142 

143 release_note_path = None 

144 if with_release_note: 

145 release_note_path = create_release_note(version, dry_run=dry_run) 

146 CONSOLE.print(f'Release note file: {release_note_path}') 

147 

148 commit_changes(version, dry_run=dry_run, include_changelog=True) 

149 tag = create_tag(version, dry_run=dry_run) 

150 

151 unfreeze_dependencies(original_pyproject_toml, dry_run=dry_run) 

152 update_requirements_and_readme(dry_run=dry_run) 

153 commit_dependency_unfreeze(dry_run=dry_run) 

154 

155 if with_push: 

156 push_changes(dry_run=dry_run) 

157 else: 

158 CONSOLE.print('Skipping remote push because --without-push is set.') 

159 

160 CONSOLE.print('') 

161 CONSOLE.print('Release pipeline completed.') 

162 CONSOLE.print(f'Version: v{version}') 

163 CONSOLE.print(f'Documentation target: {doc_target_version}') 

164 CONSOLE.print(f'Tag: {tag}') 

165 if with_release_note and release_note_path is not None: 

166 CONSOLE.print(f'Release note: {release_note_path} (left untracked)') 

167 CONSOLE.print(f'{CHANGELOG_FILE} updated.') 

168 CONSOLE.print(f'{NOTICE_FILE} updated.')