commitizen 4.15.0__tar.gz → 4.15.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. {commitizen-4.15.0 → commitizen-4.15.1}/PKG-INFO +1 -1
  2. commitizen-4.15.1/commitizen/__version__.py +1 -0
  3. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/cli.py +1 -1
  4. commitizen-4.15.1/commitizen/cmd.py +105 -0
  5. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/commands/bump.py +2 -2
  6. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/commands/changelog.py +1 -1
  7. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/commands/commit.py +3 -3
  8. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/commands/init.py +5 -4
  9. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/git.py +53 -36
  10. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/hooks.py +1 -1
  11. {commitizen-4.15.0 → commitizen-4.15.1}/pyproject.toml +1 -1
  12. commitizen-4.15.0/commitizen/__version__.py +0 -1
  13. commitizen-4.15.0/commitizen/cmd.py +0 -57
  14. {commitizen-4.15.0 → commitizen-4.15.1}/LICENSE +0 -0
  15. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/__init__.py +0 -0
  16. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/__main__.py +0 -0
  17. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/bump.py +0 -0
  18. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/changelog.py +0 -0
  19. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/changelog_formats/__init__.py +0 -0
  20. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/changelog_formats/asciidoc.py +0 -0
  21. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/changelog_formats/base.py +0 -0
  22. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/changelog_formats/markdown.py +0 -0
  23. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/changelog_formats/restructuredtext.py +0 -0
  24. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/changelog_formats/textile.py +0 -0
  25. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/commands/__init__.py +0 -0
  26. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/commands/check.py +0 -0
  27. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/commands/example.py +0 -0
  28. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/commands/info.py +0 -0
  29. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/commands/list_cz.py +0 -0
  30. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/commands/schema.py +0 -0
  31. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/commands/version.py +0 -0
  32. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/config/__init__.py +0 -0
  33. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/config/base_config.py +0 -0
  34. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/config/factory.py +0 -0
  35. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/config/json_config.py +0 -0
  36. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/config/toml_config.py +0 -0
  37. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/config/yaml_config.py +0 -0
  38. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/cz/__init__.py +0 -0
  39. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/cz/base.py +0 -0
  40. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/cz/conventional_commits/__init__.py +0 -0
  41. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/cz/conventional_commits/conventional_commits.py +0 -0
  42. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/cz/conventional_commits/conventional_commits_info.txt +0 -0
  43. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/cz/customize/__init__.py +0 -0
  44. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/cz/customize/customize.py +0 -0
  45. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/cz/customize/customize_info.txt +0 -0
  46. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/cz/exceptions.py +0 -0
  47. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/cz/jira/__init__.py +0 -0
  48. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/cz/jira/jira.py +0 -0
  49. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/cz/jira/jira_info.txt +0 -0
  50. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/cz/utils.py +0 -0
  51. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/defaults.py +0 -0
  52. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/exceptions.py +0 -0
  53. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/factory.py +0 -0
  54. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/out.py +0 -0
  55. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/project_info.py +0 -0
  56. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/providers/__init__.py +0 -0
  57. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/providers/base_provider.py +0 -0
  58. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/providers/cargo_provider.py +0 -0
  59. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/providers/commitizen_provider.py +0 -0
  60. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/providers/composer_provider.py +0 -0
  61. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/providers/npm_provider.py +0 -0
  62. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/providers/pep621_provider.py +0 -0
  63. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/providers/poetry_provider.py +0 -0
  64. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/providers/scm_provider.py +0 -0
  65. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/providers/uv_provider.py +0 -0
  66. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/py.typed +0 -0
  67. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/question.py +0 -0
  68. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/tags.py +0 -0
  69. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/templates/CHANGELOG.adoc.j2 +0 -0
  70. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/templates/CHANGELOG.md.j2 +0 -0
  71. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/templates/CHANGELOG.rst.j2 +0 -0
  72. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/templates/CHANGELOG.textile.j2 +0 -0
  73. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/version_increment.py +0 -0
  74. {commitizen-4.15.0 → commitizen-4.15.1}/commitizen/version_schemes.py +0 -0
  75. {commitizen-4.15.0 → commitizen-4.15.1}/docs/README.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: commitizen
3
- Version: 4.15.0
3
+ Version: 4.15.1
4
4
  Summary: Python commitizen client tool
5
5
  Keywords: commitizen,conventional,commits,git
6
6
  Author: Santiago Fraire
@@ -0,0 +1 @@
1
+ __version__ = "4.15.1"
@@ -704,7 +704,7 @@ def main() -> None:
704
704
  logger.warning(
705
705
  "\nWARN: Incomplete commit command: received -- separator without any following git arguments\n"
706
706
  )
707
- extra_args = " ".join(unknown_args[1:])
707
+ extra_args = unknown_args[1:]
708
708
  arguments["extra_cli_args"] = extra_args
709
709
 
710
710
  conf = config.read_cfg(args.config)
@@ -0,0 +1,105 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import subprocess
5
+ import warnings
6
+ from typing import TYPE_CHECKING, NamedTuple, overload
7
+
8
+ from charset_normalizer import from_bytes
9
+
10
+ from commitizen.exceptions import CharacterSetDecodeError
11
+
12
+ if TYPE_CHECKING:
13
+ from collections.abc import Mapping, Sequence
14
+
15
+
16
+ class Command(NamedTuple):
17
+ out: str
18
+ err: str
19
+ stdout: bytes
20
+ stderr: bytes
21
+ return_code: int
22
+
23
+
24
+ def _try_decode(bytes_: bytes) -> str:
25
+ try:
26
+ return bytes_.decode("utf-8")
27
+ except UnicodeDecodeError:
28
+ pass
29
+
30
+ charset_match = from_bytes(bytes_).best()
31
+ if charset_match is None:
32
+ raise CharacterSetDecodeError()
33
+ try:
34
+ return bytes_.decode(charset_match.encoding)
35
+ except UnicodeDecodeError as e:
36
+ raise CharacterSetDecodeError() from e
37
+
38
+
39
+ def _popen(
40
+ cmd: str | Sequence[str],
41
+ *,
42
+ shell: bool,
43
+ env: Mapping[str, str] | None = None,
44
+ ) -> Command:
45
+ if env is not None:
46
+ env = {**os.environ, **env}
47
+
48
+ process = subprocess.Popen(
49
+ cmd,
50
+ shell=shell,
51
+ stdout=subprocess.PIPE,
52
+ stderr=subprocess.PIPE,
53
+ stdin=subprocess.PIPE,
54
+ env=env,
55
+ )
56
+ stdout, stderr = process.communicate()
57
+ return_code = process.returncode
58
+ return Command(
59
+ _try_decode(stdout),
60
+ _try_decode(stderr),
61
+ stdout,
62
+ stderr,
63
+ return_code,
64
+ )
65
+
66
+
67
+ @overload
68
+ def run(cmd: str, env: Mapping[str, str] | None = None) -> Command: ...
69
+
70
+
71
+ @overload
72
+ def run(cmd: Sequence[str], env: Mapping[str, str] | None = None) -> Command: ...
73
+
74
+
75
+ def run(cmd: str | Sequence[str], env: Mapping[str, str] | None = None) -> Command:
76
+ """Run a command safely without shell interpretation (shell=False).
77
+
78
+ Arguments are passed directly to the OS, preventing shell-injection
79
+ vulnerabilities (CWE-78).
80
+
81
+ Passing a string is deprecated and will be removed in a future version.
82
+ Use a list of arguments instead, or use run_shell() for shell features.
83
+ """
84
+ if isinstance(cmd, str):
85
+ warnings.warn(
86
+ "Passing a string to cmd.run() is deprecated and will be removed in v5. "
87
+ "Use a list of arguments instead, or use cmd.run_shell() explicitly.",
88
+ DeprecationWarning,
89
+ stacklevel=2,
90
+ )
91
+ return _popen(cmd, shell=True, env=env)
92
+ return _popen(cmd, shell=False, env=env)
93
+
94
+
95
+ def run_shell(cmd: str, env: Mapping[str, str] | None = None) -> Command:
96
+ """Run a command string via the system shell (shell=True).
97
+
98
+ Only use this for cases that intentionally require shell features
99
+ (e.g., user-defined hooks with pipes/redirects). Never pass
100
+ untrusted/user-controlled values into *cmd*.
101
+
102
+ Related: CWE-78 (OS Command Injection),
103
+ https://github.com/commitizen-tools/commitizen/issues/1918
104
+ """
105
+ return _popen(cmd, shell=True, env=env)
@@ -439,8 +439,8 @@ class Bump:
439
439
  else:
440
440
  out.success("Done!")
441
441
 
442
- def _get_commit_args(self) -> str:
442
+ def _get_commit_args(self) -> list[str]:
443
443
  commit_args = ["-a"]
444
444
  if self.no_verify:
445
445
  commit_args.append("--no-verify")
446
- return " ".join(commit_args)
446
+ return commit_args
@@ -257,7 +257,7 @@ class Changelog:
257
257
  changelog_meta.latest_version_position = None
258
258
  changelog_meta.unreleased_end = latest_full_release_info.index + 1
259
259
 
260
- commits = git.get_commits(start=start_rev, end=end_rev, args="--topo-order")
260
+ commits = git.get_commits(start=start_rev, end=end_rev, args=["--topo-order"])
261
261
  if (
262
262
  not self.allow_no_commit
263
263
  and not commits
@@ -34,7 +34,7 @@ class CommitArgs(TypedDict, total=False):
34
34
  all: bool
35
35
  dry_run: bool
36
36
  edit: bool
37
- extra_cli_args: str
37
+ extra_cli_args: list[str]
38
38
  message_length_limit: int
39
39
  no_retry: bool
40
40
  signoff: bool
@@ -132,7 +132,7 @@ class Commit:
132
132
  return self._get_message_by_prompt_commit_questions()
133
133
 
134
134
  def __call__(self) -> None:
135
- extra_args = self.arguments.get("extra_cli_args", "")
135
+ extra_args: list[str] = self.arguments.get("extra_cli_args", [])
136
136
  dry_run = bool(self.arguments.get("dry_run"))
137
137
  write_message_to_file = self.arguments.get("write_message_to_file")
138
138
  signoff = bool(self.arguments.get("signoff"))
@@ -167,7 +167,7 @@ class Commit:
167
167
  raise DryRunExit()
168
168
 
169
169
  if self.config.settings["always_signoff"] or signoff:
170
- extra_args = f"{extra_args} -s".strip()
170
+ extra_args = [*extra_args, "-s"]
171
171
 
172
172
  c = git.commit(commit_message, args=extra_args)
173
173
  if c.return_code != 0:
@@ -134,11 +134,12 @@ class Init:
134
134
  "pre-commit is not installed in current environment."
135
135
  )
136
136
 
137
- cmd_str = "pre-commit install " + " ".join(
138
- f"--hook-type {ty}" for ty in hook_types
139
- )
140
- c = cmd.run(cmd_str)
137
+ cmd_args = ["pre-commit", "install"]
138
+ for ty in hook_types:
139
+ cmd_args.extend(["--hook-type", ty])
140
+ c = cmd.run(cmd_args)
141
141
  if c.return_code != 0:
142
+ cmd_str = " ".join(cmd_args)
142
143
  raise InitFailedError(
143
144
  "Failed to install pre-commit hook.\n"
144
145
  f"Error running {cmd_str}."
@@ -5,10 +5,14 @@ from enum import Enum
5
5
  from functools import lru_cache
6
6
  from pathlib import Path
7
7
  from tempfile import NamedTemporaryFile
8
+ from typing import TYPE_CHECKING
8
9
 
9
10
  from commitizen import cmd, out
10
11
  from commitizen.exceptions import GitCommandError
11
12
 
13
+ if TYPE_CHECKING:
14
+ from collections.abc import Sequence
15
+
12
16
 
13
17
  class EOLType(Enum):
14
18
  """The EOL type from `git config core.eol`."""
@@ -19,7 +23,7 @@ class EOLType(Enum):
19
23
 
20
24
  @classmethod
21
25
  def for_open(cls) -> str:
22
- c = cmd.run("git config core.eol")
26
+ c = cmd.run(["git", "config", "core.eol"])
23
27
  eol = c.out.strip().upper()
24
28
  return cls._char_for_open()[cls._safe_cast(eol)]
25
29
 
@@ -164,49 +168,47 @@ def tag(
164
168
  tag: str, annotated: bool = False, signed: bool = False, msg: str | None = None
165
169
  ) -> cmd.Command:
166
170
  if not annotated and not signed:
167
- return cmd.run(f"git tag {tag}")
171
+ return cmd.run(["git", "tag", tag])
168
172
 
169
173
  # according to https://git-scm.com/book/en/v2/Git-Basics-Tagging,
170
174
  # we're not able to create lightweight tag with message.
171
175
  # by adding message, we make it a annotated tags
172
176
  option = "-s" if signed else "-a" # The else case is for annotated tags
173
- return cmd.run(f'git tag {option} {tag} -m "{msg or tag}"')
177
+ return cmd.run(["git", "tag", option, tag, "-m", msg or tag])
174
178
 
175
179
 
176
180
  def add(*args: str) -> cmd.Command:
177
- return cmd.run(f"git add {' '.join(args)}")
181
+ return cmd.run(["git", "add", *args])
178
182
 
179
183
 
180
184
  def commit(
181
185
  message: str,
182
- args: str = "",
186
+ args: Sequence[str] = (),
183
187
  committer_date: str | None = None,
184
188
  ) -> cmd.Command:
185
189
  f = NamedTemporaryFile("wb", delete=False)
186
190
  f.write(message.encode("utf-8"))
187
191
  f.close()
188
192
 
189
- command = _create_commit_cmd_string(args, committer_date, f.name)
190
- c = cmd.run(command)
191
- os.unlink(f.name)
192
- return c
193
+ cmd_args = ["git", "commit"]
194
+ if args:
195
+ cmd_args.extend(args)
196
+ cmd_args.extend(["-F", f.name])
193
197
 
198
+ env: dict[str, str] | None = None
199
+ if committer_date:
200
+ env = {"GIT_COMMITTER_DATE": committer_date}
194
201
 
195
- def _create_commit_cmd_string(args: str, committer_date: str | None, name: str) -> str:
196
- command = f'git commit {args} -F "{name}"'
197
- if not committer_date:
198
- return command
199
- if os.name != "nt":
200
- return f"GIT_COMMITTER_DATE={committer_date} {command}"
201
- # Using `cmd /v /c "{command}"` sets environment variables only for that command
202
- return f'cmd /v /c "set GIT_COMMITTER_DATE={committer_date}&& {command}"'
202
+ c = cmd.run(cmd_args, env=env)
203
+ os.unlink(f.name)
204
+ return c
203
205
 
204
206
 
205
207
  def get_commits(
206
208
  start: str | None = None,
207
209
  end: str | None = None,
208
210
  *,
209
- args: str = "",
211
+ args: Sequence[str] = (),
210
212
  ) -> list[GitCommit]:
211
213
  """Get the commits between start and end."""
212
214
  if end is None:
@@ -226,7 +228,10 @@ def get_filenames_in_commit(git_reference: str = "") -> list[str]:
226
228
 
227
229
  :returns: file names committed in the last commit by default or inside the passed git reference
228
230
  """
229
- c = cmd.run(f"git show --name-only --pretty=format: {git_reference}")
231
+ cmd_args = ["git", "show", "--name-only", "--pretty=format:"]
232
+ if git_reference:
233
+ cmd_args.append(git_reference)
234
+ c = cmd.run(cmd_args)
230
235
  if c.return_code == 0:
231
236
  return c.out.strip().split("\n")
232
237
  raise GitCommandError(c.err)
@@ -237,15 +242,17 @@ def get_tags(
237
242
  ) -> list[GitTag]:
238
243
  inner_delimiter = "---inner_delimiter---"
239
244
  formatter = (
240
- f'"%(refname:strip=2){inner_delimiter}'
245
+ f"%(refname:strip=2){inner_delimiter}"
241
246
  f"%(objectname){inner_delimiter}"
242
247
  f"%(creatordate:format:{dateformat}){inner_delimiter}"
243
- f'%(object)"'
248
+ f"%(object)"
244
249
  )
245
- extra = "--merged" if reachable_only else ""
250
+ cmd_args = ["git", "tag", f"--format={formatter}", "--sort=-creatordate"]
251
+ if reachable_only:
252
+ cmd_args.append("--merged")
246
253
  # Force the default language for parsing
247
254
  env = {"LC_ALL": "C", "LANG": "C", "LANGUAGE": "C"}
248
- c = cmd.run(f"git tag --format={formatter} --sort=-creatordate {extra}", env=env)
255
+ c = cmd.run(cmd_args, env=env)
249
256
  if c.return_code != 0:
250
257
  if reachable_only and c.err == "fatal: malformed object name HEAD\n":
251
258
  # this can happen if there are no commits in the repo yet
@@ -262,37 +269,37 @@ def get_tags(
262
269
 
263
270
 
264
271
  def tag_exist(tag: str) -> bool:
265
- c = cmd.run(f"git tag --list {tag}")
272
+ c = cmd.run(["git", "tag", "--list", tag])
266
273
  return tag in c.out
267
274
 
268
275
 
269
276
  def is_signed_tag(tag: str) -> bool:
270
- return cmd.run(f"git tag -v {tag}").return_code == 0
277
+ return cmd.run(["git", "tag", "-v", tag]).return_code == 0
271
278
 
272
279
 
273
280
  def get_latest_tag_name() -> str | None:
274
- c = cmd.run("git describe --abbrev=0 --tags")
281
+ c = cmd.run(["git", "describe", "--abbrev=0", "--tags"])
275
282
  if c.err:
276
283
  return None
277
284
  return c.out.strip()
278
285
 
279
286
 
280
287
  def get_tag_message(tag: str) -> str | None:
281
- c = cmd.run(f"git tag -l --format='%(contents:subject)' {tag}")
288
+ c = cmd.run(["git", "tag", "-l", "--format=%(contents:subject)", tag])
282
289
  if c.err:
283
290
  return None
284
291
  return c.out.strip()
285
292
 
286
293
 
287
294
  def get_tag_names() -> list[str]:
288
- c = cmd.run("git tag --list")
295
+ c = cmd.run(["git", "tag", "--list"])
289
296
  if c.err:
290
297
  return []
291
298
  return [tag for raw in c.out.split("\n") if (tag := raw.strip())]
292
299
 
293
300
 
294
301
  def find_git_project_root() -> Path | None:
295
- c = cmd.run("git rev-parse --show-toplevel")
302
+ c = cmd.run(["git", "rev-parse", "--show-toplevel"])
296
303
  if c.err:
297
304
  return None
298
305
  return Path(c.out.strip())
@@ -300,17 +307,17 @@ def find_git_project_root() -> Path | None:
300
307
 
301
308
  def is_staging_clean() -> bool:
302
309
  """Check if staging is clean."""
303
- c = cmd.run("git diff --no-ext-diff --cached --name-only")
310
+ c = cmd.run(["git", "diff", "--no-ext-diff", "--cached", "--name-only"])
304
311
  return not bool(c.out)
305
312
 
306
313
 
307
314
  def is_git_project() -> bool:
308
- c = cmd.run("git rev-parse --is-inside-work-tree")
315
+ c = cmd.run(["git", "rev-parse", "--is-inside-work-tree"])
309
316
  return c.out.strip() == "true"
310
317
 
311
318
 
312
319
  def get_core_editor() -> str | None:
313
- c = cmd.run("git var GIT_EDITOR")
320
+ c = cmd.run(["git", "var", "GIT_EDITOR"])
314
321
  if c.out:
315
322
  return c.out.strip()
316
323
  return None
@@ -321,21 +328,31 @@ def smart_open(*args, **kwargs): # type: ignore[no-untyped-def,unused-ignore] #
321
328
  return open(*args, newline=EOLType.for_open(), **kwargs)
322
329
 
323
330
 
324
- def _get_log_as_str_list(start: str | None, end: str, args: str) -> list[str]:
331
+ def _get_log_as_str_list(start: str | None, end: str, args: Sequence[str]) -> list[str]:
325
332
  """Get string representation of each log entry"""
326
333
  delimiter = "----------commit-delimiter----------"
327
334
  log_format: str = "%H%n%P%n%s%n%an%n%ae%n%b"
328
335
  command_range = f"{start}..{end}" if start else end
329
- command = f"git -c log.showSignature=False log --pretty={log_format}{delimiter} {args} {command_range}"
336
+ cmd_args = [
337
+ "git",
338
+ "-c",
339
+ "log.showSignature=False",
340
+ "log",
341
+ f"--pretty={log_format}{delimiter}",
342
+ ]
343
+ if args:
344
+ cmd_args.extend(args)
345
+ if command_range:
346
+ cmd_args.append(command_range)
330
347
 
331
- c = cmd.run(command)
348
+ c = cmd.run(cmd_args)
332
349
  if c.return_code != 0:
333
350
  raise GitCommandError(c.err)
334
351
  return c.out.split(f"{delimiter}\n")
335
352
 
336
353
 
337
354
  def get_default_branch() -> str:
338
- c = cmd.run("git symbolic-ref refs/remotes/origin/HEAD")
355
+ c = cmd.run(["git", "symbolic-ref", "refs/remotes/origin/HEAD"])
339
356
  if c.return_code != 0:
340
357
  raise GitCommandError(c.err)
341
358
  return c.out.strip()
@@ -17,7 +17,7 @@ def run(hooks: str | list[str], _env_prefix: str = "CZ_", **env: object) -> None
17
17
  for hook in hooks:
18
18
  out.info(f"Running hook '{hook}'")
19
19
 
20
- c = cmd.run(hook, env=_format_env(_env_prefix, env))
20
+ c = cmd.run_shell(hook, env=_format_env(_env_prefix, env))
21
21
 
22
22
  if c.out:
23
23
  out.write(c.out)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "commitizen"
3
- version = "4.15.0"
3
+ version = "4.15.1"
4
4
  description = "Python commitizen client tool"
5
5
  authors = [{ name = "Santiago Fraire", email = "santiwilly@gmail.com" }]
6
6
  maintainers = [
@@ -1 +0,0 @@
1
- __version__ = "4.15.0"
@@ -1,57 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import os
4
- import subprocess
5
- from typing import TYPE_CHECKING, NamedTuple
6
-
7
- from charset_normalizer import from_bytes
8
-
9
- from commitizen.exceptions import CharacterSetDecodeError
10
-
11
- if TYPE_CHECKING:
12
- from collections.abc import Mapping
13
-
14
-
15
- class Command(NamedTuple):
16
- out: str
17
- err: str
18
- stdout: bytes
19
- stderr: bytes
20
- return_code: int
21
-
22
-
23
- def _try_decode(bytes_: bytes) -> str:
24
- try:
25
- return bytes_.decode("utf-8")
26
- except UnicodeDecodeError:
27
- pass
28
-
29
- charset_match = from_bytes(bytes_).best()
30
- if charset_match is None:
31
- raise CharacterSetDecodeError()
32
- try:
33
- return bytes_.decode(charset_match.encoding)
34
- except UnicodeDecodeError as e:
35
- raise CharacterSetDecodeError() from e
36
-
37
-
38
- def run(cmd: str, env: Mapping[str, str] | None = None) -> Command:
39
- if env is not None:
40
- env = {**os.environ, **env}
41
- process = subprocess.Popen(
42
- cmd,
43
- shell=True,
44
- stdout=subprocess.PIPE,
45
- stderr=subprocess.PIPE,
46
- stdin=subprocess.PIPE,
47
- env=env,
48
- )
49
- stdout, stderr = process.communicate()
50
- return_code = process.returncode
51
- return Command(
52
- _try_decode(stdout),
53
- _try_decode(stderr),
54
- stdout,
55
- stderr,
56
- return_code,
57
- )
File without changes
File without changes