suite-py 1.48.0__tar.gz → 1.49.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.
- {suite_py-1.48.0 → suite_py-1.49.1}/PKG-INFO +2 -2
- {suite_py-1.48.0 → suite_py-1.49.1}/pyproject.toml +2 -2
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/__version__.py +1 -1
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/cli.py +27 -6
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/commands/context.py +7 -3
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/commands/create_branch.py +48 -24
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/commands/release.py +105 -16
- {suite_py-1.48.0 → suite_py-1.49.1}/LICENSE-APACHE +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/LICENSE-MIT +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/__init__.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/commands/__init__.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/commands/ask_review.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/commands/bump.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/commands/check.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/commands/common.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/commands/estimate_cone.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/commands/login.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/commands/merge_pr.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/commands/open_pr.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/commands/project_lock.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/commands/set_token.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/commands/status.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/lib/__init__.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/lib/config.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/lib/handler/__init__.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/lib/handler/aws_handler.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/lib/handler/captainhook_handler.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/lib/handler/changelog_handler.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/lib/handler/frequent_reviewers_handler.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/lib/handler/git_handler.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/lib/handler/github_handler.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/lib/handler/metrics_handler.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/lib/handler/okta_handler.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/lib/handler/pre_commit_handler.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/lib/handler/prompt_utils.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/lib/handler/version_handler.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/lib/handler/youtrack_handler.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/lib/logger.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/lib/metrics.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/lib/oauth.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/lib/requests/__init__.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/lib/requests/auth.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/lib/requests/session.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/lib/symbol.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/suite_py/lib/tokens.py +0 -0
- {suite_py-1.48.0 → suite_py-1.49.1}/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.
|
|
3
|
+
Version: 1.49.1
|
|
4
4
|
Summary:
|
|
5
5
|
Author: larrywax, EugenioLaghi, michelangelomo
|
|
6
6
|
Author-email: devops@prima.it
|
|
@@ -28,7 +28,7 @@ Requires-Dist: pytest (>=7.0.0)
|
|
|
28
28
|
Requires-Dist: python-dateutil (>=2.8.2)
|
|
29
29
|
Requires-Dist: requests (>=2.26.0)
|
|
30
30
|
Requires-Dist: requests-toolbelt (>=0.9.1)
|
|
31
|
-
Requires-Dist: rich (==14.
|
|
31
|
+
Requires-Dist: rich (==14.2.0)
|
|
32
32
|
Requires-Dist: semver (>=3.0.4,<4.0.0)
|
|
33
33
|
Requires-Dist: termcolor (>=1.1.0)
|
|
34
34
|
Requires-Dist: truststore (>=0.7,<0.11) ; python_version >= "3.10"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
authors = ["larrywax, EugenioLaghi, michelangelomo <devops@prima.it>"]
|
|
3
3
|
description = ""
|
|
4
4
|
name = "suite-py"
|
|
5
|
-
version = "1.
|
|
5
|
+
version = "1.49.1"
|
|
6
6
|
|
|
7
7
|
[tool.poetry.dependencies]
|
|
8
8
|
Click = ">=7.0"
|
|
@@ -23,7 +23,7 @@ python = ">=3.8,<4.0"
|
|
|
23
23
|
python-dateutil = ">=2.8.2"
|
|
24
24
|
requests = ">=2.26.0"
|
|
25
25
|
requests-toolbelt = ">=0.9.1"
|
|
26
|
-
rich = "==14.
|
|
26
|
+
rich = "==14.2.0"
|
|
27
27
|
semver = "^3.0.4"
|
|
28
28
|
termcolor = ">=1.1.0"
|
|
29
29
|
truststore = {version = ">=0.7,<0.11", python = ">=3.10"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
|
-
__version__ = "1.
|
|
2
|
+
__version__ = "1.49.1"
|
|
@@ -47,8 +47,10 @@ from suite_py.lib.config import Config
|
|
|
47
47
|
from suite_py.lib.handler import git_handler as git
|
|
48
48
|
from suite_py.lib.handler import prompt_utils
|
|
49
49
|
from suite_py.lib.handler.captainhook_handler import CaptainHook
|
|
50
|
+
from suite_py.lib.handler.git_handler import GitHandler
|
|
50
51
|
from suite_py.lib.handler.okta_handler import Okta
|
|
51
52
|
from suite_py.lib.handler.pre_commit_handler import PreCommit
|
|
53
|
+
from suite_py.lib.handler.youtrack_handler import YoutrackHandler
|
|
52
54
|
from suite_py.lib.tokens import Tokens
|
|
53
55
|
|
|
54
56
|
# pylint: enable=wrong-import-position
|
|
@@ -176,17 +178,21 @@ def main(ctx, project, timeout, verbose):
|
|
|
176
178
|
if timeout:
|
|
177
179
|
config.user["captainhook_timeout"] = timeout
|
|
178
180
|
|
|
179
|
-
project = os.path.basename(project)
|
|
180
181
|
tokens = Tokens()
|
|
181
182
|
okta = Okta(config, tokens)
|
|
182
183
|
captainhook = CaptainHook(config, okta, tokens)
|
|
184
|
+
project = os.path.basename(project)
|
|
185
|
+
git_handler = GitHandler(project, config)
|
|
186
|
+
youtrack_handler = YoutrackHandler(config, tokens)
|
|
183
187
|
|
|
184
188
|
ctx.obj = Context(
|
|
189
|
+
captainhook=captainhook,
|
|
190
|
+
config=config,
|
|
191
|
+
git_handler=git_handler,
|
|
192
|
+
okta=okta,
|
|
185
193
|
project=project,
|
|
186
194
|
tokens=tokens,
|
|
187
|
-
|
|
188
|
-
config=config,
|
|
189
|
-
captainhook=captainhook,
|
|
195
|
+
youtrack_handler=youtrack_handler,
|
|
190
196
|
)
|
|
191
197
|
|
|
192
198
|
ctx.obj.call(metrics.setup)
|
|
@@ -233,12 +239,27 @@ def bump(obj: Context, project: Optional[str] = None, version: Optional[str] = N
|
|
|
233
239
|
is_flag=True,
|
|
234
240
|
help="Stash uncommitted changes before creating the branch and reapply them afterward",
|
|
235
241
|
)
|
|
242
|
+
@click.option(
|
|
243
|
+
"--parent-branch",
|
|
244
|
+
type=click.STRING,
|
|
245
|
+
help="Parent branch to create the new branch from",
|
|
246
|
+
)
|
|
247
|
+
@click.option(
|
|
248
|
+
"--branch-name",
|
|
249
|
+
type=click.STRING,
|
|
250
|
+
help="Branch name template. Supports {card_id}, {type}, {summary} placeholders (ex. '{card_id}/{type}/{summary}')",
|
|
251
|
+
)
|
|
236
252
|
@click.pass_obj
|
|
237
253
|
@catch_exceptions
|
|
238
|
-
def cli_create_branch(obj, card, autostash):
|
|
254
|
+
def cli_create_branch(obj, card, autostash, parent_branch, branch_name):
|
|
239
255
|
from suite_py.commands.create_branch import CreateBranch
|
|
240
256
|
|
|
241
|
-
obj.call(CreateBranch
|
|
257
|
+
obj.call(CreateBranch).run(
|
|
258
|
+
card_id=card,
|
|
259
|
+
autostash=autostash,
|
|
260
|
+
parent_branch=parent_branch,
|
|
261
|
+
branch_name=branch_name,
|
|
262
|
+
)
|
|
242
263
|
|
|
243
264
|
|
|
244
265
|
@main.command("lock", help="Lock project on staging or prod")
|
|
@@ -3,17 +3,21 @@ from inspect import signature
|
|
|
3
3
|
|
|
4
4
|
from suite_py.lib.config import Config
|
|
5
5
|
from suite_py.lib.handler.captainhook_handler import CaptainHook
|
|
6
|
+
from suite_py.lib.handler.git_handler import GitHandler
|
|
6
7
|
from suite_py.lib.handler.okta_handler import Okta
|
|
8
|
+
from suite_py.lib.handler.youtrack_handler import YoutrackHandler
|
|
7
9
|
from suite_py.lib.tokens import Tokens
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
@dataclasses.dataclass
|
|
11
13
|
class Context:
|
|
12
|
-
project: str
|
|
13
|
-
config: Config
|
|
14
14
|
captainhook: CaptainHook
|
|
15
|
-
|
|
15
|
+
config: Config
|
|
16
|
+
git_handler: GitHandler
|
|
16
17
|
okta: Okta
|
|
18
|
+
project: str
|
|
19
|
+
tokens: Tokens
|
|
20
|
+
youtrack_handler: YoutrackHandler
|
|
17
21
|
|
|
18
22
|
# Call the function to_call with kwargs, injecting fields from self as default arguments
|
|
19
23
|
def call(self, to_call, **kwargs):
|
|
@@ -11,25 +11,37 @@ from suite_py.lib.handler.youtrack_handler import YoutrackHandler
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class CreateBranch:
|
|
14
|
-
def __init__(
|
|
15
|
-
self
|
|
16
|
-
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
config,
|
|
17
|
+
git_handler: GitHandler,
|
|
18
|
+
youtrack_handler: YoutrackHandler,
|
|
19
|
+
):
|
|
17
20
|
self._config = config
|
|
18
|
-
self.
|
|
19
|
-
self.
|
|
20
|
-
self._autostash = autostash
|
|
21
|
+
self._git_handler = git_handler
|
|
22
|
+
self._youtrack = youtrack_handler
|
|
21
23
|
|
|
22
24
|
@metrics.command("create-branch")
|
|
23
|
-
def run(
|
|
24
|
-
|
|
25
|
+
def run(
|
|
26
|
+
self,
|
|
27
|
+
autostash=False,
|
|
28
|
+
branch_name=None,
|
|
29
|
+
card_id=None,
|
|
30
|
+
parent_branch=None,
|
|
31
|
+
):
|
|
32
|
+
if (
|
|
33
|
+
not self._git_handler.is_detached()
|
|
34
|
+
and self._git_handler.is_dirty()
|
|
35
|
+
and not autostash
|
|
36
|
+
):
|
|
25
37
|
# Default behaviour is to pull when not detached.
|
|
26
38
|
# Can't do that with uncommitted changes.
|
|
27
39
|
logger.error("You have some uncommitted changes, I can't continue")
|
|
28
40
|
sys.exit(-1)
|
|
29
41
|
|
|
30
42
|
try:
|
|
31
|
-
if
|
|
32
|
-
issue = self._youtrack.get_issue(
|
|
43
|
+
if card_id:
|
|
44
|
+
issue = self._youtrack.get_issue(card_id)
|
|
33
45
|
else:
|
|
34
46
|
issue = self._youtrack.get_issue(self._ask_card())
|
|
35
47
|
except Exception:
|
|
@@ -38,7 +50,7 @@ class CreateBranch:
|
|
|
38
50
|
)
|
|
39
51
|
sys.exit(-1)
|
|
40
52
|
|
|
41
|
-
self._checkout_branch(issue,
|
|
53
|
+
self._checkout_branch(issue, autostash, parent_branch, branch_name)
|
|
42
54
|
|
|
43
55
|
user = self._youtrack.get_current_user()
|
|
44
56
|
self._youtrack.assign_to(issue["id"], user["login"])
|
|
@@ -95,33 +107,45 @@ class CreateBranch:
|
|
|
95
107
|
)
|
|
96
108
|
return []
|
|
97
109
|
|
|
98
|
-
def _checkout_branch(
|
|
110
|
+
def _checkout_branch(
|
|
111
|
+
self, issue, autostash=False, parent_branch=None, branch_name_template=None
|
|
112
|
+
):
|
|
99
113
|
default_parent_branch_name = self._config.user.get(
|
|
100
|
-
"default_parent_branch", self.
|
|
114
|
+
"default_parent_branch", self._git_handler.current_branch_name()
|
|
101
115
|
)
|
|
102
116
|
|
|
103
|
-
parent_branch_name = prompt_utils.ask_questions_input(
|
|
104
|
-
"
|
|
117
|
+
parent_branch_name = parent_branch or prompt_utils.ask_questions_input(
|
|
118
|
+
"Enter parent branch: ", default_parent_branch_name
|
|
105
119
|
)
|
|
106
120
|
|
|
107
121
|
full_branch_name = ""
|
|
108
122
|
branch_name = _normalize_git_ref_segment(issue["summary"])
|
|
109
123
|
branch_type = _normalize_git_ref_segment(issue["Type"])
|
|
110
124
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
125
|
+
if branch_name_template:
|
|
126
|
+
# Use the template and replace placeholders
|
|
127
|
+
full_branch_name = branch_name_template.format(
|
|
128
|
+
card_id=issue["idReadable"], type=branch_type, summary=branch_name
|
|
114
129
|
)
|
|
115
130
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
131
|
+
if not is_branch_name_valid(full_branch_name):
|
|
132
|
+
logger.error(f"Invalid branch name from template: {full_branch_name}")
|
|
133
|
+
sys.exit(-1)
|
|
134
|
+
else:
|
|
135
|
+
while True:
|
|
136
|
+
branch_name = str(
|
|
137
|
+
prompt_utils.ask_questions_input("Enter branch name: ", branch_name)
|
|
138
|
+
)
|
|
119
139
|
|
|
120
|
-
|
|
140
|
+
full_branch_name = f"{issue['idReadable']}/{branch_type}/{branch_name}"
|
|
141
|
+
if is_branch_name_valid(full_branch_name):
|
|
142
|
+
break
|
|
121
143
|
|
|
122
|
-
|
|
144
|
+
logger.error(f"Invalid branch name: {full_branch_name}. Try again?")
|
|
123
145
|
|
|
124
|
-
self.
|
|
146
|
+
self._git_handler.checkout(parent_branch_name, autostash=autostash)
|
|
147
|
+
|
|
148
|
+
self._git_handler.checkout(full_branch_name, new=True, autostash=autostash)
|
|
125
149
|
|
|
126
150
|
|
|
127
151
|
# Normalize a string into a valid segment(ie. the part of the branch name between the /)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
import re
|
|
3
3
|
import sys
|
|
4
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
4
5
|
|
|
5
6
|
from suite_py.commands import common
|
|
6
7
|
from suite_py.lib import logger, metrics
|
|
@@ -71,7 +72,6 @@ class Release:
|
|
|
71
72
|
sha = self._resolve_target_sha(commits)
|
|
72
73
|
self._create_release(new_version, message, sha)
|
|
73
74
|
|
|
74
|
-
# ---- helpers (extracted to reduce complexity of _create) ----
|
|
75
75
|
def _gather_commits_and_version(self, latest):
|
|
76
76
|
"""Return (commits, new_version, base_message)."""
|
|
77
77
|
if latest == "":
|
|
@@ -79,6 +79,11 @@ class Release:
|
|
|
79
79
|
|
|
80
80
|
logger.info(f"The current release is {latest}")
|
|
81
81
|
commits = self._github.get_commits_since_release(self._repo, latest)
|
|
82
|
+
if not commits:
|
|
83
|
+
logger.warning(
|
|
84
|
+
"No commits found after the latest release tag; nothing to release."
|
|
85
|
+
)
|
|
86
|
+
sys.exit(0)
|
|
82
87
|
# If user did not pass a target commit, ask interactively which commit to release.
|
|
83
88
|
# Only perform interactive selection if user explicitly requested it
|
|
84
89
|
if self._interactive:
|
|
@@ -88,7 +93,7 @@ class Release:
|
|
|
88
93
|
message = self._build_commits_message(commits)
|
|
89
94
|
logger.info(f"\nCommits list:\n{message}\n")
|
|
90
95
|
if not prompt_utils.ask_confirm("Do you want to continue?"):
|
|
91
|
-
sys.exit()
|
|
96
|
+
sys.exit()
|
|
92
97
|
new_version = self._version.select_new_version(latest, allow_prerelease=True)
|
|
93
98
|
return commits, new_version, message
|
|
94
99
|
|
|
@@ -106,7 +111,7 @@ class Release:
|
|
|
106
111
|
try:
|
|
107
112
|
resolved_commit = self._repo.get_commit(self._commit)
|
|
108
113
|
target_sha = resolved_commit.sha
|
|
109
|
-
except Exception:
|
|
114
|
+
except Exception:
|
|
110
115
|
logger.error(
|
|
111
116
|
f"The provided commit '{self._commit}' was not found in the repository."
|
|
112
117
|
)
|
|
@@ -125,6 +130,8 @@ class Release:
|
|
|
125
130
|
"The specified commit is not part of the unreleased commits (i.e., not after the latest release)."
|
|
126
131
|
)
|
|
127
132
|
sys.exit(-1)
|
|
133
|
+
# We want ONLY the selected commit and the older commits (towards the previous release),
|
|
134
|
+
# excluding any *newer* commits that appear before it in the list.
|
|
128
135
|
return commits[target_index:]
|
|
129
136
|
|
|
130
137
|
def _select_commit_if_needed(self, commits):
|
|
@@ -137,22 +144,104 @@ class Release:
|
|
|
137
144
|
"""
|
|
138
145
|
if self._commit or not commits:
|
|
139
146
|
return
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
+
|
|
148
|
+
choices = []
|
|
149
|
+
|
|
150
|
+
def build_choice(c, icon):
|
|
151
|
+
summary = c.commit.message.splitlines()[0]
|
|
152
|
+
author = c.commit.author.name
|
|
153
|
+
return {
|
|
154
|
+
"name": f"{icon} {c.sha[:8]} | {summary} by {author}",
|
|
147
155
|
"value": c.sha,
|
|
148
156
|
}
|
|
149
|
-
|
|
150
|
-
|
|
157
|
+
|
|
158
|
+
with ThreadPoolExecutor(max_workers=min(8, len(commits))) as executor:
|
|
159
|
+
future_map = {
|
|
160
|
+
executor.submit(self._get_commit_status_icon, c.sha): c for c in commits
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
icon_results = {}
|
|
164
|
+
for future in as_completed(future_map):
|
|
165
|
+
c = future_map[future]
|
|
166
|
+
try:
|
|
167
|
+
icon_results[c.sha] = future.result()
|
|
168
|
+
except Exception:
|
|
169
|
+
icon_results[c.sha] = "?"
|
|
170
|
+
for c in commits:
|
|
171
|
+
choices.append(build_choice(c, icon_results.get(c.sha, "?")))
|
|
172
|
+
|
|
173
|
+
subtitle = "(only one available)" if len(choices) == 1 else "(newest first):"
|
|
151
174
|
selected = prompt_utils.ask_choices(
|
|
152
|
-
"Select commit to release
|
|
175
|
+
f"Select commit to release {subtitle}", choices
|
|
153
176
|
)
|
|
154
177
|
self._commit = selected
|
|
155
178
|
|
|
179
|
+
def _get_commit_status_icon(self, sha):
|
|
180
|
+
"""Return an icon representing the simplified CI status for a commit.
|
|
181
|
+
|
|
182
|
+
Final exposed statuses (icons):
|
|
183
|
+
success (✅), failure (❌), in_progress (🏗️), cancelled (🚫), unknown (❓)
|
|
184
|
+
"""
|
|
185
|
+
check_runs = self._safe_get_check_runs(sha)
|
|
186
|
+
if not check_runs:
|
|
187
|
+
return self._icon_for_state("unknown")
|
|
188
|
+
state = self._classify_from_check_runs(check_runs)
|
|
189
|
+
return self._icon_for_state(state)
|
|
190
|
+
|
|
191
|
+
def _safe_get_check_runs(self, sha):
|
|
192
|
+
try:
|
|
193
|
+
commit = self._repo.get_commit(sha)
|
|
194
|
+
return list(commit.get_check_runs())
|
|
195
|
+
except Exception:
|
|
196
|
+
return []
|
|
197
|
+
|
|
198
|
+
def _classify_from_check_runs(self, check_runs):
|
|
199
|
+
statuses = {r.status for r in check_runs if getattr(r, "status", None)}
|
|
200
|
+
conclusions = {
|
|
201
|
+
r.conclusion for r in check_runs if getattr(r, "conclusion", None)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if self._any_in_progress(statuses):
|
|
205
|
+
return "in_progress"
|
|
206
|
+
if self._any_failure(conclusions):
|
|
207
|
+
return "failure"
|
|
208
|
+
if "cancelled" in conclusions:
|
|
209
|
+
return "cancelled"
|
|
210
|
+
if self._is_success(conclusions):
|
|
211
|
+
return "success"
|
|
212
|
+
return "unknown"
|
|
213
|
+
|
|
214
|
+
@staticmethod
|
|
215
|
+
def _any_in_progress(statuses):
|
|
216
|
+
return any(
|
|
217
|
+
s in {"in_progress", "queued", "waiting", "pending"} for s in statuses
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
@staticmethod
|
|
221
|
+
def _any_failure(conclusions):
|
|
222
|
+
return any(
|
|
223
|
+
c in {"failure", "timed_out", "action_required", "stale"}
|
|
224
|
+
for c in conclusions
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
@staticmethod
|
|
228
|
+
def _is_success(conclusions):
|
|
229
|
+
return bool(conclusions) and (
|
|
230
|
+
all(c == "success" for c in conclusions)
|
|
231
|
+
or ("success" in conclusions and not Release._any_failure(conclusions))
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
@staticmethod
|
|
235
|
+
def _icon_for_state(state):
|
|
236
|
+
icon_mapping = {
|
|
237
|
+
"success": "✅",
|
|
238
|
+
"failure": "❌",
|
|
239
|
+
"in_progress": "🏗️",
|
|
240
|
+
"cancelled": "🚫",
|
|
241
|
+
"unknown": "❓",
|
|
242
|
+
}
|
|
243
|
+
return icon_mapping.get(state, "❓")
|
|
244
|
+
|
|
156
245
|
@staticmethod
|
|
157
246
|
def _build_commits_message(commits):
|
|
158
247
|
return "\n".join(
|
|
@@ -179,7 +268,7 @@ class Release:
|
|
|
179
268
|
return commits[0].commit.sha if commits else ""
|
|
180
269
|
try:
|
|
181
270
|
sha = self._repo.get_commit(self._commit).sha
|
|
182
|
-
except Exception:
|
|
271
|
+
except Exception:
|
|
183
272
|
logger.error(
|
|
184
273
|
f"Unable to resolve commit '{self._commit}' during release creation."
|
|
185
274
|
)
|
|
@@ -189,13 +278,13 @@ class Release:
|
|
|
189
278
|
|
|
190
279
|
def _validate_target_commit_not_tagged(self, sha):
|
|
191
280
|
try:
|
|
192
|
-
for t in self._repo.get_tags():
|
|
281
|
+
for t in self._repo.get_tags():
|
|
193
282
|
if t.commit.sha == sha:
|
|
194
283
|
logger.error(
|
|
195
284
|
f"The commit {sha[:7]} is already tagged with {t.name}. Aborting."
|
|
196
285
|
)
|
|
197
286
|
sys.exit(-1)
|
|
198
|
-
except Exception:
|
|
287
|
+
except Exception:
|
|
199
288
|
logger.warning("Could not verify if commit already tagged; continuing.")
|
|
200
289
|
|
|
201
290
|
def _create_release(self, new_version, message, commit):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|