git-alternative 0.2.2__py3-none-any.whl
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.
- git_alternative/__init__.py +63 -0
- git_alternative/ci.py +162 -0
- git_alternative/cli.py +117 -0
- git_alternative/client.py +72 -0
- git_alternative/exceptions.py +43 -0
- git_alternative/http.py +137 -0
- git_alternative/managers/__init__.py +1 -0
- git_alternative/managers/issues.py +292 -0
- git_alternative/managers/repos.py +218 -0
- git_alternative/models.py +291 -0
- git_alternative/pagination.py +45 -0
- git_alternative/resources/__init__.py +1 -0
- git_alternative/resources/issues.py +291 -0
- git_alternative/resources/labels.py +91 -0
- git_alternative/resources/pipelines.py +118 -0
- git_alternative/resources/pulls.py +207 -0
- git_alternative/resources/repos.py +184 -0
- git_alternative/resources/ssh_keys.py +64 -0
- git_alternative/ssh_keys.py +80 -0
- git_alternative/utils.py +91 -0
- git_alternative/webhooks.py +104 -0
- git_alternative-0.2.2.dist-info/METADATA +36 -0
- git_alternative-0.2.2.dist-info/RECORD +26 -0
- git_alternative-0.2.2.dist-info/WHEEL +4 -0
- git_alternative-0.2.2.dist-info/entry_points.txt +3 -0
- git_alternative-0.2.2.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"""Issue resource manager."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
from git_alternative.http import HttpClient
|
|
8
|
+
from git_alternative.models import Comment, Issue, IssueState, Label
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _parse_label(data: dict) -> Label:
|
|
12
|
+
return Label(
|
|
13
|
+
id=data["id"],
|
|
14
|
+
name=data["name"],
|
|
15
|
+
color=data["color"],
|
|
16
|
+
description=data.get("description"),
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _parse_issue(data: dict) -> Issue:
|
|
21
|
+
return Issue(
|
|
22
|
+
id=data["id"],
|
|
23
|
+
repo_id=data["repo_id"],
|
|
24
|
+
number=data["number"],
|
|
25
|
+
title=data["title"],
|
|
26
|
+
author_id=data["author_id"],
|
|
27
|
+
state=IssueState(data["state"]),
|
|
28
|
+
created_at=datetime.fromisoformat(data["created_at"]),
|
|
29
|
+
updated_at=datetime.fromisoformat(data["updated_at"]),
|
|
30
|
+
body=data.get("body"),
|
|
31
|
+
labels=[_parse_label(lb) for lb in data.get("labels", [])],
|
|
32
|
+
assignees=data.get("assignees", []),
|
|
33
|
+
milestone_id=data.get("milestone_id"),
|
|
34
|
+
closed_at=(
|
|
35
|
+
datetime.fromisoformat(data["closed_at"]) if data.get("closed_at") else None
|
|
36
|
+
),
|
|
37
|
+
comment_count=data.get("comment_count", 0),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _parse_comment(data: dict) -> Comment:
|
|
42
|
+
return Comment(
|
|
43
|
+
id=data["id"],
|
|
44
|
+
author_id=data["author_id"],
|
|
45
|
+
body=data["body"],
|
|
46
|
+
created_at=datetime.fromisoformat(data["created_at"]),
|
|
47
|
+
updated_at=datetime.fromisoformat(data["updated_at"]),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class IssueManager:
|
|
52
|
+
"""Manages issue operations against the Forge API.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
http: An :class:`~git_alternative.http.HttpClient` instance.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(self, http: HttpClient) -> None:
|
|
59
|
+
self._http = http
|
|
60
|
+
|
|
61
|
+
def list(
|
|
62
|
+
self,
|
|
63
|
+
owner: str,
|
|
64
|
+
repo: str,
|
|
65
|
+
state: str = "open",
|
|
66
|
+
labels: list[str] | None = None,
|
|
67
|
+
page: int = 1,
|
|
68
|
+
per_page: int = 30,
|
|
69
|
+
) -> list[Issue]:
|
|
70
|
+
"""List issues in a repository.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
owner: Repository owner username.
|
|
74
|
+
repo: Repository name.
|
|
75
|
+
state: Filter by state: ``"open"``, ``"closed"``, or ``"all"``.
|
|
76
|
+
labels: Filter by label names.
|
|
77
|
+
page: Page number (1-based).
|
|
78
|
+
per_page: Items per page.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
List of :class:`~git_alternative.models.Issue` objects.
|
|
82
|
+
"""
|
|
83
|
+
params: dict = {"state": state, "page": page, "per_page": per_page}
|
|
84
|
+
if labels:
|
|
85
|
+
params["labels"] = ",".join(labels)
|
|
86
|
+
data = self._http.get(f"/api/v1/repos/{owner}/{repo}/issues", params=params)
|
|
87
|
+
return [_parse_issue(i) for i in data]
|
|
88
|
+
|
|
89
|
+
def get(self, owner: str, repo: str, issue_number: int) -> Issue:
|
|
90
|
+
"""Fetch a single issue by number.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
owner: Repository owner username.
|
|
94
|
+
repo: Repository name.
|
|
95
|
+
issue_number: The issue number.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
An :class:`~git_alternative.models.Issue` instance.
|
|
99
|
+
"""
|
|
100
|
+
data = self._http.get(f"/api/v1/repos/{owner}/{repo}/issues/{issue_number}")
|
|
101
|
+
return _parse_issue(data)
|
|
102
|
+
|
|
103
|
+
def create(
|
|
104
|
+
self,
|
|
105
|
+
owner: str,
|
|
106
|
+
repo: str,
|
|
107
|
+
title: str,
|
|
108
|
+
body: str | None = None,
|
|
109
|
+
labels: list[str] | None = None,
|
|
110
|
+
assignees: list[str] | None = None,
|
|
111
|
+
milestone_id: str | None = None,
|
|
112
|
+
) -> Issue:
|
|
113
|
+
"""Create a new issue.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
owner: Repository owner username.
|
|
117
|
+
repo: Repository name.
|
|
118
|
+
title: Issue title.
|
|
119
|
+
body: Issue body in Markdown.
|
|
120
|
+
labels: Label names to attach.
|
|
121
|
+
assignees: Usernames to assign.
|
|
122
|
+
milestone_id: Milestone ID to associate.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
The newly created :class:`~git_alternative.models.Issue`.
|
|
126
|
+
"""
|
|
127
|
+
payload: dict = {"title": title}
|
|
128
|
+
if body is not None:
|
|
129
|
+
payload["body"] = body
|
|
130
|
+
if labels:
|
|
131
|
+
payload["labels"] = labels
|
|
132
|
+
if assignees:
|
|
133
|
+
payload["assignees"] = assignees
|
|
134
|
+
if milestone_id:
|
|
135
|
+
payload["milestone_id"] = milestone_id
|
|
136
|
+
data = self._http.post(f"/api/v1/repos/{owner}/{repo}/issues", json=payload)
|
|
137
|
+
return _parse_issue(data)
|
|
138
|
+
|
|
139
|
+
def update(
|
|
140
|
+
self,
|
|
141
|
+
owner: str,
|
|
142
|
+
repo: str,
|
|
143
|
+
issue_number: int,
|
|
144
|
+
title: str | None = None,
|
|
145
|
+
body: str | None = None,
|
|
146
|
+
state: str | None = None,
|
|
147
|
+
) -> Issue:
|
|
148
|
+
"""Update an existing issue.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
owner: Repository owner username.
|
|
152
|
+
repo: Repository name.
|
|
153
|
+
issue_number: The issue number.
|
|
154
|
+
title: New title (optional).
|
|
155
|
+
body: New body (optional).
|
|
156
|
+
state: New state: ``"open"`` or ``"closed"`` (optional).
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
The updated :class:`~git_alternative.models.Issue`.
|
|
160
|
+
"""
|
|
161
|
+
payload: dict = {}
|
|
162
|
+
if title is not None:
|
|
163
|
+
payload["title"] = title
|
|
164
|
+
if body is not None:
|
|
165
|
+
payload["body"] = body
|
|
166
|
+
if state is not None:
|
|
167
|
+
payload["state"] = state
|
|
168
|
+
data = self._http.patch(
|
|
169
|
+
f"/api/v1/repos/{owner}/{repo}/issues/{issue_number}", json=payload
|
|
170
|
+
)
|
|
171
|
+
return _parse_issue(data)
|
|
172
|
+
|
|
173
|
+
def delete(self, owner: str, repo: str, issue_number: int) -> None:
|
|
174
|
+
"""Delete an issue.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
owner: Repository owner username.
|
|
178
|
+
repo: Repository name.
|
|
179
|
+
issue_number: The issue number.
|
|
180
|
+
"""
|
|
181
|
+
self._http.delete(f"/api/v1/repos/{owner}/{repo}/issues/{issue_number}")
|
|
182
|
+
|
|
183
|
+
def list_comments(self, owner: str, repo: str, issue_number: int) -> list[Comment]:
|
|
184
|
+
"""List all comments on an issue.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
owner: Repository owner username.
|
|
188
|
+
repo: Repository name.
|
|
189
|
+
issue_number: The issue number.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
List of :class:`~git_alternative.models.Comment` objects.
|
|
193
|
+
"""
|
|
194
|
+
data = self._http.get(
|
|
195
|
+
f"/api/v1/repos/{owner}/{repo}/issues/{issue_number}/comments"
|
|
196
|
+
)
|
|
197
|
+
return [_parse_comment(c) for c in data]
|
|
198
|
+
|
|
199
|
+
def add_comment(
|
|
200
|
+
self, owner: str, repo: str, issue_number: int, body: str
|
|
201
|
+
) -> Comment:
|
|
202
|
+
"""Add a comment to an issue.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
owner: Repository owner username.
|
|
206
|
+
repo: Repository name.
|
|
207
|
+
issue_number: The issue number.
|
|
208
|
+
body: Comment body in Markdown.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
The newly created :class:`~git_alternative.models.Comment`.
|
|
212
|
+
"""
|
|
213
|
+
data = self._http.post(
|
|
214
|
+
f"/api/v1/repos/{owner}/{repo}/issues/{issue_number}/comments",
|
|
215
|
+
json={"body": body},
|
|
216
|
+
)
|
|
217
|
+
return _parse_comment(data)
|
|
218
|
+
|
|
219
|
+
def update_comment(
|
|
220
|
+
self, owner: str, repo: str, issue_number: int, comment_id: str, body: str
|
|
221
|
+
) -> Comment:
|
|
222
|
+
"""Update an existing comment.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
owner: Repository owner username.
|
|
226
|
+
repo: Repository name.
|
|
227
|
+
issue_number: The issue number.
|
|
228
|
+
comment_id: The comment ID.
|
|
229
|
+
body: New comment body.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
The updated :class:`~git_alternative.models.Comment`.
|
|
233
|
+
"""
|
|
234
|
+
data = self._http.patch(
|
|
235
|
+
f"/api/v1/repos/{owner}/{repo}/issues/{issue_number}/comments/{comment_id}",
|
|
236
|
+
json={"body": body},
|
|
237
|
+
)
|
|
238
|
+
return _parse_comment(data)
|
|
239
|
+
|
|
240
|
+
def delete_comment(
|
|
241
|
+
self, owner: str, repo: str, issue_number: int, comment_id: str
|
|
242
|
+
) -> None:
|
|
243
|
+
"""Delete a comment from an issue.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
owner: Repository owner username.
|
|
247
|
+
repo: Repository name.
|
|
248
|
+
issue_number: The issue number.
|
|
249
|
+
comment_id: The comment ID.
|
|
250
|
+
"""
|
|
251
|
+
self._http.delete(
|
|
252
|
+
f"/api/v1/repos/{owner}/{repo}/issues/{issue_number}/comments/{comment_id}"
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
def list_labels(self, owner: str, repo: str) -> list[Label]:
|
|
256
|
+
"""List all labels defined in a repository.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
owner: Repository owner username.
|
|
260
|
+
repo: Repository name.
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
List of :class:`~git_alternative.models.Label` objects.
|
|
264
|
+
"""
|
|
265
|
+
data = self._http.get(f"/api/v1/repos/{owner}/{repo}/labels")
|
|
266
|
+
return [_parse_label(lb) for lb in data]
|
|
267
|
+
|
|
268
|
+
def create_label(
|
|
269
|
+
self,
|
|
270
|
+
owner: str,
|
|
271
|
+
repo: str,
|
|
272
|
+
name: str,
|
|
273
|
+
color: str,
|
|
274
|
+
description: str | None = None,
|
|
275
|
+
) -> Label:
|
|
276
|
+
"""Create a new label in a repository.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
owner: Repository owner username.
|
|
280
|
+
repo: Repository name.
|
|
281
|
+
name: Label name.
|
|
282
|
+
color: Hex color code (e.g. ``"#ff0000"``).
|
|
283
|
+
description: Optional label description.
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
The newly created :class:`~git_alternative.models.Label`.
|
|
287
|
+
"""
|
|
288
|
+
payload: dict = {"name": name, "color": color}
|
|
289
|
+
if description:
|
|
290
|
+
payload["description"] = description
|
|
291
|
+
data = self._http.post(f"/api/v1/repos/{owner}/{repo}/labels", json=payload)
|
|
292
|
+
return _parse_label(data)
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""Repository resource manager."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
from git_alternative.http import HttpClient
|
|
8
|
+
from git_alternative.models import (
|
|
9
|
+
Branch,
|
|
10
|
+
CommitSummary,
|
|
11
|
+
CreateRepoRequest,
|
|
12
|
+
PaginatedResponse,
|
|
13
|
+
Repository,
|
|
14
|
+
)
|
|
15
|
+
from git_alternative.pagination import paginate_commits
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _parse_repo(data: dict) -> Repository:
|
|
19
|
+
return Repository(
|
|
20
|
+
id=data["id"],
|
|
21
|
+
owner_id=data["owner_id"],
|
|
22
|
+
name=data["name"],
|
|
23
|
+
description=data.get("description"),
|
|
24
|
+
is_private=data.get("is_private", False),
|
|
25
|
+
is_fork=data.get("is_fork", False),
|
|
26
|
+
default_branch=data.get("default_branch", "main"),
|
|
27
|
+
created_at=datetime.fromisoformat(data["created_at"]),
|
|
28
|
+
updated_at=datetime.fromisoformat(data["updated_at"]),
|
|
29
|
+
star_count=data.get("star_count", 0),
|
|
30
|
+
fork_count=data.get("fork_count", 0),
|
|
31
|
+
fork_of=data.get("fork_of"),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _parse_branch(data: dict) -> Branch:
|
|
36
|
+
return Branch(
|
|
37
|
+
name=data["name"],
|
|
38
|
+
sha=data["sha"],
|
|
39
|
+
is_default=data.get("is_default", False),
|
|
40
|
+
is_protected=data.get("is_protected", False),
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _parse_commit(data: dict) -> CommitSummary:
|
|
45
|
+
return CommitSummary(
|
|
46
|
+
sha=data["sha"],
|
|
47
|
+
message=data["message"],
|
|
48
|
+
author_name=data["author_name"],
|
|
49
|
+
author_email=data["author_email"],
|
|
50
|
+
authored_at=datetime.fromisoformat(data["authored_at"]),
|
|
51
|
+
committer_name=data["committer_name"],
|
|
52
|
+
committer_email=data["committer_email"],
|
|
53
|
+
committed_at=datetime.fromisoformat(data["committed_at"]),
|
|
54
|
+
parents=data.get("parents", []),
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class RepoManager:
|
|
59
|
+
"""Manages repository operations against the Forge API.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
http: An :class:`~git_alternative.http.HttpClient` instance.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
def __init__(self, http: HttpClient) -> None:
|
|
66
|
+
self._http = http
|
|
67
|
+
|
|
68
|
+
def list(self, owner: str) -> list[Repository]:
|
|
69
|
+
"""List all repositories for a user.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
owner: The username whose repositories to list.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
List of :class:`~git_alternative.models.Repository` objects.
|
|
76
|
+
"""
|
|
77
|
+
data = self._http.get(f"/api/v1/users/{owner}/repos")
|
|
78
|
+
return [_parse_repo(r) for r in data]
|
|
79
|
+
|
|
80
|
+
def get(self, owner: str, repo: str) -> Repository:
|
|
81
|
+
"""Fetch a single repository by owner and name.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
owner: Repository owner username.
|
|
85
|
+
repo: Repository name.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
A :class:`~git_alternative.models.Repository` instance.
|
|
89
|
+
"""
|
|
90
|
+
data = self._http.get(f"/api/v1/repos/{owner}/{repo}")
|
|
91
|
+
return _parse_repo(data)
|
|
92
|
+
|
|
93
|
+
def create(self, request: CreateRepoRequest) -> Repository:
|
|
94
|
+
"""Create a new repository.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
request: A :class:`~git_alternative.models.CreateRepoRequest` with
|
|
98
|
+
the desired repository settings.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
The newly created :class:`~git_alternative.models.Repository`.
|
|
102
|
+
"""
|
|
103
|
+
payload = {
|
|
104
|
+
"name": request.name,
|
|
105
|
+
"description": request.description,
|
|
106
|
+
"is_private": request.is_private,
|
|
107
|
+
"default_branch": request.default_branch,
|
|
108
|
+
"auto_init": request.auto_init,
|
|
109
|
+
}
|
|
110
|
+
data = self._http.post("/api/v1/repos", json=payload)
|
|
111
|
+
return _parse_repo(data)
|
|
112
|
+
|
|
113
|
+
def delete(self, owner: str, repo: str) -> None:
|
|
114
|
+
"""Delete a repository.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
owner: Repository owner username.
|
|
118
|
+
repo: Repository name.
|
|
119
|
+
"""
|
|
120
|
+
self._http.delete(f"/api/v1/repos/{owner}/{repo}")
|
|
121
|
+
|
|
122
|
+
def update(
|
|
123
|
+
self,
|
|
124
|
+
owner: str,
|
|
125
|
+
repo: str,
|
|
126
|
+
description: str | None = None,
|
|
127
|
+
is_private: bool | None = None,
|
|
128
|
+
default_branch: str | None = None,
|
|
129
|
+
) -> Repository:
|
|
130
|
+
"""Update repository settings.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
owner: Repository owner username.
|
|
134
|
+
repo: Repository name.
|
|
135
|
+
description: New description (optional).
|
|
136
|
+
is_private: New visibility setting (optional).
|
|
137
|
+
default_branch: New default branch name (optional).
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
The updated :class:`~git_alternative.models.Repository`.
|
|
141
|
+
"""
|
|
142
|
+
payload: dict = {}
|
|
143
|
+
if description is not None:
|
|
144
|
+
payload["description"] = description
|
|
145
|
+
if is_private is not None:
|
|
146
|
+
payload["is_private"] = is_private
|
|
147
|
+
if default_branch is not None:
|
|
148
|
+
payload["default_branch"] = default_branch
|
|
149
|
+
data = self._http.patch(f"/api/v1/repos/{owner}/{repo}", json=payload)
|
|
150
|
+
return _parse_repo(data)
|
|
151
|
+
|
|
152
|
+
def list_branches(self, owner: str, repo: str) -> list[Branch]:
|
|
153
|
+
"""List all branches in a repository.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
owner: Repository owner username.
|
|
157
|
+
repo: Repository name.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
List of :class:`~git_alternative.models.Branch` objects.
|
|
161
|
+
"""
|
|
162
|
+
data = self._http.get(f"/api/v1/repos/{owner}/{repo}/branches")
|
|
163
|
+
return [_parse_branch(b) for b in data]
|
|
164
|
+
|
|
165
|
+
def get_branch(self, owner: str, repo: str, branch: str) -> Branch:
|
|
166
|
+
"""Fetch a single branch by name.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
owner: Repository owner username.
|
|
170
|
+
repo: Repository name.
|
|
171
|
+
branch: Branch name.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
A :class:`~git_alternative.models.Branch` instance.
|
|
175
|
+
"""
|
|
176
|
+
data = self._http.get(f"/api/v1/repos/{owner}/{repo}/branches/{branch}")
|
|
177
|
+
return _parse_branch(data)
|
|
178
|
+
|
|
179
|
+
def list_commits(
|
|
180
|
+
self,
|
|
181
|
+
owner: str,
|
|
182
|
+
repo: str,
|
|
183
|
+
page: int = 1,
|
|
184
|
+
per_page: int = 30,
|
|
185
|
+
) -> PaginatedResponse[CommitSummary]:
|
|
186
|
+
"""List commits in a repository with pagination.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
owner: Repository owner username.
|
|
190
|
+
repo: Repository name.
|
|
191
|
+
page: Page number (1-based).
|
|
192
|
+
per_page: Items per page (max 100).
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
A :class:`~git_alternative.models.PaginatedResponse` of
|
|
196
|
+
:class:`~git_alternative.models.CommitSummary` objects.
|
|
197
|
+
"""
|
|
198
|
+
capped = min(per_page, 100)
|
|
199
|
+
data = self._http.get(
|
|
200
|
+
f"/api/v1/repos/{owner}/{repo}/commits",
|
|
201
|
+
params={"page": page, "per_page": capped + 1},
|
|
202
|
+
)
|
|
203
|
+
raw = [_parse_commit(c) for c in data]
|
|
204
|
+
return paginate_commits(raw, page=page, per_page=capped)
|
|
205
|
+
|
|
206
|
+
def get_commit(self, owner: str, repo: str, sha: str) -> CommitSummary:
|
|
207
|
+
"""Fetch a single commit by SHA.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
owner: Repository owner username.
|
|
211
|
+
repo: Repository name.
|
|
212
|
+
sha: Full or abbreviated commit SHA.
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
A :class:`~git_alternative.models.CommitSummary` instance.
|
|
216
|
+
"""
|
|
217
|
+
data = self._http.get(f"/api/v1/repos/{owner}/{repo}/commits/{sha}")
|
|
218
|
+
return _parse_commit(data)
|