suite-py 1.47.2__tar.gz → 1.48.0__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 (46) hide show
  1. {suite_py-1.47.2 → suite_py-1.48.0}/PKG-INFO +1 -1
  2. {suite_py-1.47.2 → suite_py-1.48.0}/pyproject.toml +1 -1
  3. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/__version__.py +1 -1
  4. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/cli.py +24 -2
  5. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/commands/release.py +136 -47
  6. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/lib/handler/pre_commit_handler.py +10 -3
  7. {suite_py-1.47.2 → suite_py-1.48.0}/LICENSE-APACHE +0 -0
  8. {suite_py-1.47.2 → suite_py-1.48.0}/LICENSE-MIT +0 -0
  9. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/__init__.py +0 -0
  10. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/commands/__init__.py +0 -0
  11. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/commands/ask_review.py +0 -0
  12. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/commands/bump.py +0 -0
  13. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/commands/check.py +0 -0
  14. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/commands/common.py +0 -0
  15. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/commands/context.py +0 -0
  16. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/commands/create_branch.py +0 -0
  17. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/commands/estimate_cone.py +0 -0
  18. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/commands/login.py +0 -0
  19. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/commands/merge_pr.py +0 -0
  20. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/commands/open_pr.py +0 -0
  21. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/commands/project_lock.py +0 -0
  22. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/commands/set_token.py +0 -0
  23. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/commands/status.py +0 -0
  24. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/lib/__init__.py +0 -0
  25. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/lib/config.py +0 -0
  26. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/lib/handler/__init__.py +0 -0
  27. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/lib/handler/aws_handler.py +0 -0
  28. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/lib/handler/captainhook_handler.py +0 -0
  29. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/lib/handler/changelog_handler.py +0 -0
  30. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/lib/handler/frequent_reviewers_handler.py +0 -0
  31. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/lib/handler/git_handler.py +0 -0
  32. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/lib/handler/github_handler.py +0 -0
  33. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/lib/handler/metrics_handler.py +0 -0
  34. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/lib/handler/okta_handler.py +0 -0
  35. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/lib/handler/prompt_utils.py +0 -0
  36. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/lib/handler/version_handler.py +0 -0
  37. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/lib/handler/youtrack_handler.py +0 -0
  38. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/lib/logger.py +0 -0
  39. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/lib/metrics.py +0 -0
  40. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/lib/oauth.py +0 -0
  41. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/lib/requests/__init__.py +0 -0
  42. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/lib/requests/auth.py +0 -0
  43. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/lib/requests/session.py +0 -0
  44. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/lib/symbol.py +0 -0
  45. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/lib/tokens.py +0 -0
  46. {suite_py-1.47.2 → suite_py-1.48.0}/suite_py/templates/login.html +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: suite-py
3
- Version: 1.47.2
3
+ Version: 1.48.0
4
4
  Summary:
5
5
  Author: larrywax, EugenioLaghi, michelangelomo
6
6
  Author-email: devops@prima.it
@@ -2,7 +2,7 @@
2
2
  authors = ["larrywax, EugenioLaghi, michelangelomo <devops@prima.it>"]
3
3
  description = ""
4
4
  name = "suite-py"
5
- version = "1.47.2"
5
+ version = "1.48.0"
6
6
 
7
7
  [tool.poetry.dependencies]
8
8
  Click = ">=7.0"
@@ -1,2 +1,2 @@
1
1
  # -*- encoding: utf-8 -*-
2
- __version__ = "1.47.2"
2
+ __version__ = "1.48.0"
@@ -326,12 +326,34 @@ def release():
326
326
  @release.command(
327
327
  "create", help="Create a github release (and deploy it if GHA are used)"
328
328
  )
329
+ @click.argument(
330
+ "commit",
331
+ required=False,
332
+ type=str,
333
+ metavar="[COMMIT]",
334
+ )
335
+ @click.option(
336
+ "--interactive",
337
+ "-i",
338
+ is_flag=True,
339
+ help="Interactively choose which unreleased commit to release (ignored if COMMIT is provided)",
340
+ )
329
341
  @click.pass_obj
330
342
  @catch_exceptions
331
- def cli_release_create(obj):
343
+ def cli_release_create(obj, commit, interactive): # type: ignore[override]
344
+ """Create a release.
345
+
346
+ Optionally pass a COMMIT (full or short SHA) to create the release at that
347
+ specific commit instead of the latest commit (HEAD). Example:
348
+
349
+ suite-py release create 644a699
350
+
351
+ If you don't provide a COMMIT you can pass --interactive / -i to select
352
+ one among the unreleased commits (those after the last tag).
353
+ """
332
354
  from suite_py.commands.release import Release
333
355
 
334
- obj.call(Release, action="create").run()
356
+ obj.call(Release, action="create", commit=commit, interactive=interactive).run()
335
357
 
336
358
 
337
359
  @main.command("status", help="Current status of a project")
@@ -16,11 +16,24 @@ from suite_py.lib.handler.youtrack_handler import YoutrackHandler
16
16
  class Release:
17
17
  # pylint: disable=too-many-instance-attributes
18
18
  # pylint: disable=too-many-positional-arguments
19
- def __init__(self, action, project, captainhook, config, tokens):
19
+ def __init__(
20
+ self,
21
+ action,
22
+ project,
23
+ captainhook,
24
+ config,
25
+ tokens,
26
+ commit=None,
27
+ interactive=False,
28
+ ):
20
29
  self._action = action
21
30
  self._project = project
22
31
  self._config = config
23
32
  self._tokens = tokens
33
+ # Optional commit (sha or short sha) to release instead of HEAD
34
+ self._commit = commit
35
+ # Whether to prompt the user to choose commit among unreleased ones
36
+ self._interactive = interactive
24
37
  self._changelog_handler = ChangelogHandler()
25
38
  self._youtrack = YoutrackHandler(config, tokens)
26
39
  self._captainhook = captainhook
@@ -52,62 +65,138 @@ class Release:
52
65
 
53
66
  def _create(self):
54
67
  latest = self._version.get_latest_version()
68
+ commits, new_version, message = self._gather_commits_and_version(latest)
69
+ message = self._augment_message_with_changelog(new_version, message)
70
+ message = common.ask_for_release_description(message)
71
+ sha = self._resolve_target_sha(commits)
72
+ self._create_release(new_version, message, sha)
55
73
 
56
- commits = []
57
- if latest != "":
58
- logger.info(f"The current release is {latest}")
59
- commits = self._github.get_commits_since_release(self._repo, latest)
60
-
61
- _check_migrations_deploy(commits)
62
-
63
- message = "\n".join(
64
- [
65
- "* "
66
- + c.commit.message.splitlines()[0]
67
- + " by "
68
- + c.commit.author.name
69
- for c in commits
70
- ]
71
- )
74
+ # ---- helpers (extracted to reduce complexity of _create) ----
75
+ def _gather_commits_and_version(self, latest):
76
+ """Return (commits, new_version, base_message)."""
77
+ if latest == "":
78
+ return self._first_release_flow()
72
79
 
73
- logger.info(f"\nCommits list:\n{message}\n")
80
+ logger.info(f"The current release is {latest}")
81
+ commits = self._github.get_commits_since_release(self._repo, latest)
82
+ # If user did not pass a target commit, ask interactively which commit to release.
83
+ # Only perform interactive selection if user explicitly requested it
84
+ if self._interactive:
85
+ self._select_commit_if_needed(commits)
86
+ commits = self._maybe_trim_commits(commits)
87
+ _check_migrations_deploy(commits)
88
+ message = self._build_commits_message(commits)
89
+ logger.info(f"\nCommits list:\n{message}\n")
90
+ if not prompt_utils.ask_confirm("Do you want to continue?"):
91
+ sys.exit() # Preserve previous early return semantics
92
+ new_version = self._version.select_new_version(latest, allow_prerelease=True)
93
+ return commits, new_version, message
74
94
 
75
- if not prompt_utils.ask_confirm("Do you want to continue?"):
76
- return
95
+ def _first_release_flow(self):
96
+ logger.warning(f"No tags found, I'm about to push the tag {DEFAULT_VERSION}")
97
+ if not prompt_utils.ask_confirm(
98
+ "Are you sure you want to continue?", default=False
99
+ ):
100
+ sys.exit()
101
+ return [], DEFAULT_VERSION, f"First release with tag {DEFAULT_VERSION}"
77
102
 
78
- new_version = self._version.select_new_version(
79
- latest, allow_prerelease=True
103
+ def _maybe_trim_commits(self, commits):
104
+ if not self._commit:
105
+ return commits
106
+ try:
107
+ resolved_commit = self._repo.get_commit(self._commit)
108
+ target_sha = resolved_commit.sha
109
+ except Exception: # pragma: no cover
110
+ logger.error(
111
+ f"The provided commit '{self._commit}' was not found in the repository."
80
112
  )
81
-
82
- else:
83
- # Se non viene trovata la release e non ci sono tag, viene saltato il check delle migrations e l'update delle card su youtrack
84
- logger.warning(
85
- f"No tags found, I'm about to push the tag {DEFAULT_VERSION}"
113
+ sys.exit(-1)
114
+ target_index = None
115
+ for idx, c in enumerate(commits):
116
+ if (
117
+ c.sha == target_sha
118
+ or getattr(c, "commit", None)
119
+ and c.commit.sha == target_sha
120
+ ):
121
+ target_index = idx
122
+ break
123
+ if target_index is None:
124
+ logger.error(
125
+ "The specified commit is not part of the unreleased commits (i.e., not after the latest release)."
86
126
  )
127
+ sys.exit(-1)
128
+ return commits[target_index:]
129
+
130
+ def _select_commit_if_needed(self, commits):
131
+ """Interactively ask the user which commit to release if none was provided.
132
+
133
+ Presents the unreleased commits (newest first) allowing the user to pick a
134
+ target commit. The chosen commit's SHA (full) is stored in `self._commit` so
135
+ that downstream logic (_maybe_trim_commits / _resolve_target_sha) works
136
+ unchanged.
137
+ """
138
+ if self._commit or not commits:
139
+ return
140
+ if len(commits) == 1:
141
+ # Only one unreleased commit; implicitly choose it.
142
+ self._commit = commits[0].sha
143
+ return
144
+ choices = [
145
+ {
146
+ "name": f"{c.sha[:8]} | {c.commit.message.splitlines()[0]}",
147
+ "value": c.sha,
148
+ }
149
+ for c in commits
150
+ ]
151
+ selected = prompt_utils.ask_choices(
152
+ "Select commit to release (newest first):", choices
153
+ )
154
+ self._commit = selected
155
+
156
+ @staticmethod
157
+ def _build_commits_message(commits):
158
+ return "\n".join(
159
+ [
160
+ "* " + c.commit.message.splitlines()[0] + " by " + c.commit.author.name
161
+ for c in commits
162
+ ]
163
+ )
164
+
165
+ def _augment_message_with_changelog(self, new_version, message):
166
+ if not self._changelog_handler.changelog_exists():
167
+ return message
168
+ latest_tag, latest_entry = self._changelog_handler.get_latest_entry_with_tag()
169
+ if latest_tag != new_version:
87
170
  if not prompt_utils.ask_confirm(
88
- "Are you sure you want to continue?", default=False
171
+ "You didn't update your changelog, are you sure you want to proceed?"
89
172
  ):
90
173
  sys.exit()
91
- new_version = DEFAULT_VERSION
92
- message = f"First release with tag {new_version}"
93
-
94
- if self._changelog_handler.changelog_exists():
95
- (
96
- latest_tag,
97
- latest_entry,
98
- ) = self._changelog_handler.get_latest_entry_with_tag()
99
-
100
- if latest_tag != new_version:
101
- if not prompt_utils.ask_confirm(
102
- "You didn't update your changelog, are you sure you want to proceed?"
103
- ):
104
- sys.exit()
105
- else:
106
- message = f"{latest_entry}\n\n# Commits\n\n{message}"
174
+ return message
175
+ return f"{latest_entry}\n\n# Commits\n\n{message}"
107
176
 
108
- message = common.ask_for_release_description(message)
109
- sha = commits[0].commit.sha if len(commits) > 0 else ""
110
- self._create_release(new_version, message, sha)
177
+ def _resolve_target_sha(self, commits):
178
+ if not self._commit:
179
+ return commits[0].commit.sha if commits else ""
180
+ try:
181
+ sha = self._repo.get_commit(self._commit).sha
182
+ except Exception: # pragma: no cover
183
+ logger.error(
184
+ f"Unable to resolve commit '{self._commit}' during release creation."
185
+ )
186
+ sys.exit(-1)
187
+ self._validate_target_commit_not_tagged(sha)
188
+ return sha
189
+
190
+ def _validate_target_commit_not_tagged(self, sha):
191
+ try:
192
+ for t in self._repo.get_tags(): # network call
193
+ if t.commit.sha == sha:
194
+ logger.error(
195
+ f"The commit {sha[:7]} is already tagged with {t.name}. Aborting."
196
+ )
197
+ sys.exit(-1)
198
+ except Exception: # pragma: no cover
199
+ logger.warning("Could not verify if commit already tagged; continuing.")
111
200
 
112
201
  def _create_release(self, new_version, message, commit):
113
202
  new_release = self._repo.create_git_release(
@@ -51,10 +51,17 @@ to disable it globally
51
51
  * is there a `.git/hooks/pre-commit` shell script that contains keyword "gitleaks"
52
52
  * is there a `.husky/pre-commit` shell script that contains keyword "security-hooks" (primait/security-hooks repo)
53
53
  """
54
- path = os.path.join(self._git.hooks_path(), "pre-commit")
55
- keywords = ["gitleaks", "security-hooks"]
54
+ checks = [
55
+ (os.path.join(self._git.hooks_path(), "pre-commit"), "gitleaks"),
56
+ (
57
+ os.path.join(self._git.get_path(), ".husky", "pre-commit"),
58
+ "security-hooks",
59
+ ),
60
+ ]
56
61
 
57
- return any(self._script_contains_keyword(path, keyword) for keyword in keywords)
62
+ return any(
63
+ self._script_contains_keyword(path, keyword) for path, keyword in checks
64
+ )
58
65
 
59
66
  def _is_pre_commit_py_hook_setup(self):
60
67
  """
File without changes
File without changes