Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 941abf0

Browse files
authored
Merge pull request #712 from commitizen-tools/release/v3
Release/v3
2 parents e039f16 + 28ac04f commit 941abf0

24 files changed

+1215
-640
lines changed

‎.gitignore‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,6 @@ venv.bak/
110110

111111
# macOSX
112112
.DS_Store
113+
114+
# ruff
115+
.ruff_cache

‎commitizen/commands/init.py‎

Lines changed: 193 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,59 +13,152 @@
1313
from commitizen.defaults import config_files
1414
from commitizen.exceptions import InitFailedError, NoAnswersError
1515
from commitizen.git import get_latest_tag_name, get_tag_names, smart_open
16+
from commitizen.version_types import VERSION_TYPES
17+
18+
19+
class ProjectInfo:
20+
"""Discover information about the current folder."""
21+
22+
@property
23+
def has_pyproject(self) -> bool:
24+
return os.path.isfile("pyproject.toml")
25+
26+
@property
27+
def has_setup(self) -> bool:
28+
return os.path.isfile("setup.py")
29+
30+
@property
31+
def has_pre_commit_config(self) -> bool:
32+
return os.path.isfile(".pre-commit-config.yaml")
33+
34+
@property
35+
def is_python_poetry(self) -> bool:
36+
if not self.has_pyproject:
37+
return False
38+
with open("pyproject.toml") as f:
39+
return "tool.poetry.version" in f.read()
40+
41+
@property
42+
def is_python(self) -> bool:
43+
return self.has_pyproject or self.has_setup
44+
45+
@property
46+
def is_rust_cargo(self) -> bool:
47+
return os.path.isfile("Cargo.toml")
48+
49+
@property
50+
def is_npm_package(self) -> bool:
51+
return os.path.isfile("package.json")
52+
53+
@property
54+
def is_php_composer(self) -> bool:
55+
return os.path.isfile("composer.json")
56+
57+
@property
58+
def latest_tag(self) -> Optional[str]:
59+
return get_latest_tag_name()
60+
61+
def tags(self) -> Optional[List]:
62+
"""Not a property, only use if necessary"""
63+
if self.latest_tag is None:
64+
return None
65+
return get_tag_names()
66+
67+
@property
68+
def is_pre_commit_installed(self) -> bool:
69+
return not shutil.which("pre-commit")
1670

1771

1872
class Init:
1973
def __init__(self, config: BaseConfig, *args):
2074
self.config: BaseConfig = config
2175
self.cz = factory.commiter_factory(self.config)
76+
self.project_info = ProjectInfo()
2277

2378
def __call__(self):
2479
if self.config.path:
2580
out.line(f"Config file {self.config.path} already exists")
2681
return
2782

28-
# No config for commitizen exist
29-
config_path = self._ask_config_path()
83+
out.info("Welcome to commitizen!\n")
84+
out.line(
85+
"Answer the questions to configure your project.\n"
86+
"For further configuration visit:\n"
87+
"\n"
88+
"https://commitizen-tools.github.io/commitizen/config/"
89+
"\n"
90+
)
91+
92+
# Collect information
93+
try:
94+
config_path = self._ask_config_path() # select
95+
cz_name = self._ask_name() # select
96+
version_provider = self._ask_version_provider() # select
97+
tag = self._ask_tag() # confirm & select
98+
version = Version(tag)
99+
tag_format = self._ask_tag_format(tag) # confirm & text
100+
version_type = self._ask_version_type() # select
101+
update_changelog_on_bump = self._ask_update_changelog_on_bump() # confirm
102+
major_version_zero = self._ask_major_version_zero(version) # confirm
103+
except KeyboardInterrupt:
104+
raise InitFailedError("Stopped by user")
105+
106+
# Initialize configuration
30107
if "toml" in config_path:
31108
self.config = TomlConfig(data="", path=config_path)
32109
elif "json" in config_path:
33110
self.config = JsonConfig(data="{}", path=config_path)
34111
elif "yaml" in config_path:
35112
self.config = YAMLConfig(data="", path=config_path)
36-
self.config.init_empty_config_content()
37-
38113
values_to_add = {}
39-
values_to_add["name"] = self._ask_name()
40-
tag = self._ask_tag()
41-
values_to_add["version"] = Version(tag).public
42-
values_to_add["tag_format"] = self._ask_tag_format(tag)
43-
self._update_config_file(values_to_add)
114+
values_to_add["name"] = cz_name
115+
values_to_add["tag_format"] = tag_format
116+
values_to_add["version_type"] = version_type
44117

118+
if version_provider == "commitizen":
119+
values_to_add["version"] = version.public
120+
else:
121+
values_to_add["version_provider"] = version_provider
122+
123+
if update_changelog_on_bump:
124+
values_to_add["update_changelog_on_bump"] = update_changelog_on_bump
125+
126+
if major_version_zero:
127+
values_to_add["major_version_zero"] = major_version_zero
128+
129+
# Collect hook data
45130
hook_types = questionary.checkbox(
46131
"What types of pre-commit hook you want to install? (Leave blank if you don't want to install)",
47132
choices=[
48-
questionary.Choice("commit-msg", checked=True),
49-
questionary.Choice("pre-push", checked=True),
133+
questionary.Choice("commit-msg", checked=False),
134+
questionary.Choice("pre-push", checked=False),
50135
],
51-
).ask()
136+
).unsafe_ask()
52137
if hook_types:
53138
try:
54139
self._install_pre_commit_hook(hook_types)
55140
except InitFailedError as e:
56141
raise InitFailedError(f"Failed to install pre-commit hook.\n{e}")
57142

58-
out.write("You can bump the version and create changelog running:\n")
59-
out.info("cz bump --changelog")
60-
out.success("The configuration are all set.")
143+
# Create and initialize config
144+
self.config.init_empty_config_content()
145+
self._update_config_file(values_to_add)
146+
147+
out.write("\nYou can bump the version running:\n")
148+
out.info("\tcz bump\n")
149+
out.success("Configuration complete 🚀")
61150

62151
def _ask_config_path(self) -> str:
152+
default_path = ".cz.toml"
153+
if self.project_info.has_pyproject:
154+
default_path = "pyproject.toml"
155+
63156
name: str = questionary.select(
64-
"Please choose a supported config file: (default: pyproject.toml)",
157+
"Please choose a supported config file: ",
65158
choices=config_files,
66-
default="pyproject.toml",
159+
default=default_path,
67160
style=self.cz.style,
68-
).ask()
161+
).unsafe_ask()
69162
return name
70163

71164
def _ask_name(self) -> str:
@@ -74,29 +167,29 @@ def _ask_name(self) -> str:
74167
choices=list(registry.keys()),
75168
default="cz_conventional_commits",
76169
style=self.cz.style,
77-
).ask()
170+
).unsafe_ask()
78171
return name
79172

80173
def _ask_tag(self) -> str:
81-
latest_tag = get_latest_tag_name()
174+
latest_tag = self.project_info.latest_tag
82175
if not latest_tag:
83176
out.error("No Existing Tag. Set tag to v0.0.1")
84177
return "0.0.1"
85178

86179
is_correct_tag = questionary.confirm(
87180
f"Is {latest_tag} the latest tag?", style=self.cz.style, default=False
88-
).ask()
181+
).unsafe_ask()
89182
if not is_correct_tag:
90-
tags = get_tag_names()
183+
tags = self.project_info.tags()
91184
if not tags:
92185
out.error("No Existing Tag. Set tag to v0.0.1")
93186
return "0.0.1"
94187

95188
latest_tag = questionary.select(
96189
"Please choose the latest tag: ",
97-
choices=get_tag_names(), # type: ignore
190+
choices=tags,
98191
style=self.cz.style,
99-
).ask()
192+
).unsafe_ask()
100193

101194
if not latest_tag:
102195
raise NoAnswersError("Tag is required!")
@@ -108,21 +201,90 @@ def _ask_tag_format(self, latest_tag) -> str:
108201
tag_format = r"v$version"
109202
is_correct_format = questionary.confirm(
110203
f'Is "{tag_format}" the correct tag format?', style=self.cz.style
111-
).ask()
204+
).unsafe_ask()
112205

113206
if not is_correct_format:
114207
tag_format = questionary.text(
115208
'Please enter the correct version format: (default: "$version")',
116209
style=self.cz.style,
117-
).ask()
210+
).unsafe_ask()
118211

119212
if not tag_format:
120213
tag_format = "$version"
121214
return tag_format
122215

123-
def _search_pre_commit(self) -> bool:
124-
"""Check whether pre-commit is installed"""
125-
return shutil.which("pre-commit") is not None
216+
def _ask_version_provider(self) -> str:
217+
"""Ask for setting: version_provider"""
218+
219+
OPTS = {
220+
"commitizen": "commitizen: Fetch and set version in commitizen config (default)",
221+
"cargo": "cargo: Get and set version from Cargo.toml:project.version field",
222+
"composer": "composer: Get and set version from composer.json:project.version field",
223+
"npm": "npm: Get and set version from package.json:project.version field",
224+
"pep621": "pep621: Get and set version from pyproject.toml:project.version field",
225+
"poetry": "poetry: Get and set version from pyproject.toml:tool.poetry.version field",
226+
"scm": "scm: Fetch the version from git and does not need to set it back",
227+
}
228+
229+
default_val = "commitizen"
230+
if self.project_info.is_python:
231+
if self.project_info.is_python_poetry:
232+
default_val = "poetry"
233+
else:
234+
default_val = "pep621"
235+
elif self.project_info.is_rust_cargo:
236+
default_val = "cargo"
237+
elif self.project_info.is_npm_package:
238+
default_val = "npm"
239+
elif self.project_info.is_php_composer:
240+
default_val = "composer"
241+
242+
choices = [
243+
questionary.Choice(title=title, value=value)
244+
for value, title in OPTS.items()
245+
]
246+
default = next(filter(lambda x: x.value == default_val, choices))
247+
version_provider: str = questionary.select(
248+
"Choose the source of the version:",
249+
choices=choices,
250+
style=self.cz.style,
251+
default=default,
252+
).unsafe_ask()
253+
return version_provider
254+
255+
def _ask_version_type(self) -> str:
256+
"""Ask for setting: version_type"""
257+
default = "semver"
258+
if self.project_info.is_python:
259+
default = "pep440"
260+
261+
version_type: str = questionary.select(
262+
"Choose version type scheme: ",
263+
choices=[*VERSION_TYPES],
264+
style=self.cz.style,
265+
default=default,
266+
).unsafe_ask()
267+
return version_type
268+
269+
def _ask_major_version_zero(self, version: Version) -> bool:
270+
"""Ask for setting: major_version_zero"""
271+
if version.major > 0:
272+
return False
273+
major_version_zero: bool = questionary.confirm(
274+
"Keep major version zero (0.x) during breaking changes",
275+
default=True,
276+
auto_enter=True,
277+
).unsafe_ask()
278+
return major_version_zero
279+
280+
def _ask_update_changelog_on_bump(self) -> bool:
281+
"Ask for setting: update_changelog_on_bump"
282+
update_changelog_on_bump: bool = questionary.confirm(
283+
"Create changelog automatically on bump",
284+
default=True,
285+
auto_enter=True,
286+
).unsafe_ask()
287+
return update_changelog_on_bump
126288

127289
def _exec_install_pre_commit_hook(self, hook_types: List[str]):
128290
cmd_str = self._gen_pre_commit_cmd(hook_types)
@@ -157,7 +319,7 @@ def _install_pre_commit_hook(self, hook_types: Optional[List[str]] = None):
157319
}
158320

159321
config_data = {}
160-
if not os.path.isfile(pre_commit_config_filename):
322+
if not self.project_info.has_pre_commit_config:
161323
# .pre-commit-config.yaml does not exist
162324
config_data["repos"] = [cz_hook_config]
163325
else:
@@ -180,7 +342,7 @@ def _install_pre_commit_hook(self, hook_types: Optional[List[str]] = None):
180342
with smart_open(pre_commit_config_filename, "w") as config_file:
181343
yaml.safe_dump(config_data, stream=config_file)
182344

183-
if not self._search_pre_commit():
345+
if not self.project_info.is_pre_commit_installed:
184346
raise InitFailedError("pre-commit is not installed in current environment.")
185347
if hook_types is None:
186348
hook_types = ["commit-msg", "pre-push"]

‎commitizen/version_types.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,6 @@ def __str__(self) -> str:
9494

9595

9696
VERSION_TYPES = {
97-
"pep440": Version,
9897
"semver": SemVerVersion,
98+
"pep440": Version,
9999
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /