pygitcode 0.1.1__tar.gz → 0.1.2__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.
- {pygitcode-0.1.1 → pygitcode-0.1.2}/PKG-INFO +28 -11
- {pygitcode-0.1.1 → pygitcode-0.1.2}/README.md +27 -10
- {pygitcode-0.1.1 → pygitcode-0.1.2}/pyproject.toml +4 -1
- {pygitcode-0.1.1 → pygitcode-0.1.2}/src/gitcode_cli/__init__.py +1 -1
- pygitcode-0.1.2/src/gitcode_cli/adapters/__init__.py +7 -0
- pygitcode-0.1.2/src/gitcode_cli/adapters/base.py +14 -0
- pygitcode-0.1.2/src/gitcode_cli/adapters/capabilities.py +29 -0
- pygitcode-0.1.2/src/gitcode_cli/adapters/issues.py +239 -0
- pygitcode-0.1.2/src/gitcode_cli/adapters/pulls.py +170 -0
- pygitcode-0.1.2/src/gitcode_cli/cli.py +109 -0
- {pygitcode-0.1.1 → pygitcode-0.1.2}/src/gitcode_cli/cli_compat.py +2 -0
- {pygitcode-0.1.1 → pygitcode-0.1.2}/src/gitcode_cli/client.py +22 -8
- pygitcode-0.1.2/src/gitcode_cli/commands/auth.py +55 -0
- pygitcode-0.1.2/src/gitcode_cli/commands/issue.py +595 -0
- pygitcode-0.1.2/src/gitcode_cli/commands/pr.py +725 -0
- {pygitcode-0.1.1 → pygitcode-0.1.2}/src/gitcode_cli/errors.py +4 -0
- {pygitcode-0.1.1 → pygitcode-0.1.2}/src/gitcode_cli/formatters.py +15 -7
- pygitcode-0.1.2/src/gitcode_cli/helptext.py +220 -0
- {pygitcode-0.1.1 → pygitcode-0.1.2}/src/gitcode_cli/services/__init__.py +2 -1
- {pygitcode-0.1.1 → pygitcode-0.1.2}/src/gitcode_cli/services/issues.py +5 -2
- {pygitcode-0.1.1 → pygitcode-0.1.2}/src/gitcode_cli/services/pulls.py +3 -0
- pygitcode-0.1.2/src/gitcode_cli/services/users.py +13 -0
- {pygitcode-0.1.1 → pygitcode-0.1.2}/src/gitcode_cli/utils.py +34 -5
- {pygitcode-0.1.1 → pygitcode-0.1.2}/src/pygitcode.egg-info/PKG-INFO +28 -11
- {pygitcode-0.1.1 → pygitcode-0.1.2}/src/pygitcode.egg-info/SOURCES.txt +7 -0
- pygitcode-0.1.1/src/gitcode_cli/cli.py +0 -44
- pygitcode-0.1.1/src/gitcode_cli/commands/auth.py +0 -23
- pygitcode-0.1.1/src/gitcode_cli/commands/issue.py +0 -338
- pygitcode-0.1.1/src/gitcode_cli/commands/pr.py +0 -496
- {pygitcode-0.1.1 → pygitcode-0.1.2}/LICENSE +0 -0
- {pygitcode-0.1.1 → pygitcode-0.1.2}/setup.cfg +0 -0
- {pygitcode-0.1.1 → pygitcode-0.1.2}/src/gitcode_cli/commands/__init__.py +0 -0
- {pygitcode-0.1.1 → pygitcode-0.1.2}/src/gitcode_cli/config.py +0 -0
- {pygitcode-0.1.1 → pygitcode-0.1.2}/src/gitcode_cli/context.py +0 -0
- {pygitcode-0.1.1 → pygitcode-0.1.2}/src/gitcode_cli/repo.py +0 -0
- {pygitcode-0.1.1 → pygitcode-0.1.2}/src/pygitcode.egg-info/dependency_links.txt +0 -0
- {pygitcode-0.1.1 → pygitcode-0.1.2}/src/pygitcode.egg-info/entry_points.txt +0 -0
- {pygitcode-0.1.1 → pygitcode-0.1.2}/src/pygitcode.egg-info/requires.txt +0 -0
- {pygitcode-0.1.1 → pygitcode-0.1.2}/src/pygitcode.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pygitcode
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: A CLI tool for GitCode (api.gitcode.com), modeled after GitHub CLI.
|
|
5
5
|
Author-email: codeasier <825770651@qq.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -45,7 +45,6 @@ Dynamic: license-file
|
|
|
45
45
|
[](https://pypi.org/project/pygitcode/)
|
|
46
46
|
[](https://pypi.org/project/pygitcode/)
|
|
47
47
|
[](https://github.com/yourusername/pygitcode/blob/main/LICENSE)
|
|
48
|
-
[](https://github.com/yourusername/pygitcode/actions)
|
|
49
48
|
|
|
50
49
|
## Installation
|
|
51
50
|
|
|
@@ -83,6 +82,15 @@ Set-Alias -Name gc -Value 'C:\Users\<user>\AppData\Roaming\Python\Python313\Scri
|
|
|
83
82
|
# Login with your GitCode personal access token
|
|
84
83
|
gc auth login
|
|
85
84
|
|
|
85
|
+
# Check auth status
|
|
86
|
+
gc auth status
|
|
87
|
+
|
|
88
|
+
# Print the current auth token
|
|
89
|
+
gc auth token
|
|
90
|
+
|
|
91
|
+
# Logout
|
|
92
|
+
gc auth logout
|
|
93
|
+
|
|
86
94
|
# Or set environment variable
|
|
87
95
|
export GC_TOKEN=your_token_here
|
|
88
96
|
```
|
|
@@ -103,21 +111,24 @@ gc issue view 42 --web
|
|
|
103
111
|
|
|
104
112
|
# Create an issue
|
|
105
113
|
gc issue create -t "Bug report" -b "Something is broken"
|
|
114
|
+
gc issue create --label bug --label "help wanted" # Multiple labels
|
|
106
115
|
gc issue create --web # Create in browser
|
|
107
116
|
|
|
108
|
-
# Edit an issue (
|
|
117
|
+
# Edit an issue (title, labels, assignee, milestone)
|
|
109
118
|
gc issue edit 42 -t "Updated title"
|
|
110
|
-
gc issue edit 42 --add-label bug --
|
|
111
|
-
gc issue edit 42 --add-assignee @me
|
|
119
|
+
gc issue edit 42 --add-label bug --add-label docs
|
|
120
|
+
gc issue edit 42 --add-assignee @me
|
|
121
|
+
gc issue edit 42 --milestone v1.0 --remove-milestone
|
|
112
122
|
|
|
113
123
|
# Comment on an issue
|
|
114
124
|
gc issue comment 42 -b "Thanks for the report!"
|
|
115
125
|
gc issue comment 42 --editor # Use system editor
|
|
116
126
|
|
|
117
|
-
#
|
|
127
|
+
gc issue delete 42 # Unsupported by GitCode API
|
|
128
|
+
|
|
129
|
+
# Close / reopen
|
|
118
130
|
gc issue close 42
|
|
119
|
-
gc issue
|
|
120
|
-
gc issue delete 42
|
|
131
|
+
gc issue close 42 -c "Fixed in #50" --reason completed
|
|
121
132
|
```
|
|
122
133
|
|
|
123
134
|
### Pull Requests
|
|
@@ -132,6 +143,7 @@ gc pr list --web # Open in browser
|
|
|
132
143
|
# View a PR (identifier optional - infers from current branch)
|
|
133
144
|
gc pr view # View PR for current branch
|
|
134
145
|
gc pr view 42 # By number
|
|
146
|
+
gc pr view 42 --comments # Include comments
|
|
135
147
|
gc pr view https://gitcode.com/owner/repo/pulls/42
|
|
136
148
|
gc pr view feature-branch
|
|
137
149
|
|
|
@@ -160,11 +172,13 @@ gc pr close --delete-branch # Delete remote branch too
|
|
|
160
172
|
gc pr merge # Merge PR for current branch
|
|
161
173
|
gc pr merge 42 -s # Squash merge
|
|
162
174
|
gc pr merge --rebase
|
|
175
|
+
gc pr merge --delete-branch # Delete remote branch after merge
|
|
163
176
|
|
|
164
|
-
# Edit a PR (add/remove labels, assignees, reviewers)
|
|
177
|
+
# Edit a PR (add/remove labels, assignees, reviewers, milestones)
|
|
165
178
|
gc pr edit 42 -t "New title"
|
|
166
179
|
gc pr edit --add-label bug --remove-label "needs review"
|
|
167
180
|
gc pr edit --add-reviewer @me --remove-reviewer otheruser
|
|
181
|
+
gc pr edit --milestone v1.0 --remove-milestone
|
|
168
182
|
|
|
169
183
|
# Mark as ready or convert to draft
|
|
170
184
|
gc pr ready # Mark current branch's PR as ready
|
|
@@ -173,6 +187,8 @@ gc pr ready --undo # Convert back to draft
|
|
|
173
187
|
# Comment / review / diff (identifier optional)
|
|
174
188
|
gc pr comment -b "LGTM"
|
|
175
189
|
gc pr comment --path src/file.py --position 5 -b "Suggestion"
|
|
190
|
+
gc pr comment --body-file comment.txt
|
|
191
|
+
gc pr comment --editor
|
|
176
192
|
|
|
177
193
|
gc pr review --approve
|
|
178
194
|
gc pr review --comment -b "Looks good but needs tests"
|
|
@@ -224,7 +240,7 @@ gc pr view -w
|
|
|
224
240
|
| `--web` (create/view in browser) | ✅ | ✅ |
|
|
225
241
|
| `--remove-*` flags for edit commands | ✅ | ✅ |
|
|
226
242
|
| `pr ready --undo` (convert to draft) | ✅ | ✅ |
|
|
227
|
-
| `pr review --
|
|
243
|
+
| `pr review --request-changes` | ✅ | ✅ (fallback) |
|
|
228
244
|
| `--json fields` | ✅ | ✅ |
|
|
229
245
|
| `-q jq` filtering | ✅ | ✅ |
|
|
230
246
|
| `-t template` formatting | ✅ | ✅ |
|
|
@@ -234,7 +250,8 @@ gc pr view -w
|
|
|
234
250
|
### Known Limitations (GitCode API differences)
|
|
235
251
|
|
|
236
252
|
- **PR comment model**: GitCode uses `path + position`, not GitHub's `line/side/commit`
|
|
237
|
-
- **PR review**: GitCode review API differs from GitHub; `--
|
|
253
|
+
- **PR review**: GitCode review API differs from GitHub; `--request-changes` falls back to PR comments
|
|
254
|
+
- **Issue deletion**: `gc issue delete` is exposed for CLI parity, but GitCode API does not support deleting issues
|
|
238
255
|
- **Issue create/update API**: GitCode puts `repo` in request body, not URL path
|
|
239
256
|
|
|
240
257
|
## Development
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
[](https://pypi.org/project/pygitcode/)
|
|
6
6
|
[](https://pypi.org/project/pygitcode/)
|
|
7
7
|
[](https://github.com/yourusername/pygitcode/blob/main/LICENSE)
|
|
8
|
-
[](https://github.com/yourusername/pygitcode/actions)
|
|
9
8
|
|
|
10
9
|
## Installation
|
|
11
10
|
|
|
@@ -43,6 +42,15 @@ Set-Alias -Name gc -Value 'C:\Users\<user>\AppData\Roaming\Python\Python313\Scri
|
|
|
43
42
|
# Login with your GitCode personal access token
|
|
44
43
|
gc auth login
|
|
45
44
|
|
|
45
|
+
# Check auth status
|
|
46
|
+
gc auth status
|
|
47
|
+
|
|
48
|
+
# Print the current auth token
|
|
49
|
+
gc auth token
|
|
50
|
+
|
|
51
|
+
# Logout
|
|
52
|
+
gc auth logout
|
|
53
|
+
|
|
46
54
|
# Or set environment variable
|
|
47
55
|
export GC_TOKEN=your_token_here
|
|
48
56
|
```
|
|
@@ -63,21 +71,24 @@ gc issue view 42 --web
|
|
|
63
71
|
|
|
64
72
|
# Create an issue
|
|
65
73
|
gc issue create -t "Bug report" -b "Something is broken"
|
|
74
|
+
gc issue create --label bug --label "help wanted" # Multiple labels
|
|
66
75
|
gc issue create --web # Create in browser
|
|
67
76
|
|
|
68
|
-
# Edit an issue (
|
|
77
|
+
# Edit an issue (title, labels, assignee, milestone)
|
|
69
78
|
gc issue edit 42 -t "Updated title"
|
|
70
|
-
gc issue edit 42 --add-label bug --
|
|
71
|
-
gc issue edit 42 --add-assignee @me
|
|
79
|
+
gc issue edit 42 --add-label bug --add-label docs
|
|
80
|
+
gc issue edit 42 --add-assignee @me
|
|
81
|
+
gc issue edit 42 --milestone v1.0 --remove-milestone
|
|
72
82
|
|
|
73
83
|
# Comment on an issue
|
|
74
84
|
gc issue comment 42 -b "Thanks for the report!"
|
|
75
85
|
gc issue comment 42 --editor # Use system editor
|
|
76
86
|
|
|
77
|
-
#
|
|
87
|
+
gc issue delete 42 # Unsupported by GitCode API
|
|
88
|
+
|
|
89
|
+
# Close / reopen
|
|
78
90
|
gc issue close 42
|
|
79
|
-
gc issue
|
|
80
|
-
gc issue delete 42
|
|
91
|
+
gc issue close 42 -c "Fixed in #50" --reason completed
|
|
81
92
|
```
|
|
82
93
|
|
|
83
94
|
### Pull Requests
|
|
@@ -92,6 +103,7 @@ gc pr list --web # Open in browser
|
|
|
92
103
|
# View a PR (identifier optional - infers from current branch)
|
|
93
104
|
gc pr view # View PR for current branch
|
|
94
105
|
gc pr view 42 # By number
|
|
106
|
+
gc pr view 42 --comments # Include comments
|
|
95
107
|
gc pr view https://gitcode.com/owner/repo/pulls/42
|
|
96
108
|
gc pr view feature-branch
|
|
97
109
|
|
|
@@ -120,11 +132,13 @@ gc pr close --delete-branch # Delete remote branch too
|
|
|
120
132
|
gc pr merge # Merge PR for current branch
|
|
121
133
|
gc pr merge 42 -s # Squash merge
|
|
122
134
|
gc pr merge --rebase
|
|
135
|
+
gc pr merge --delete-branch # Delete remote branch after merge
|
|
123
136
|
|
|
124
|
-
# Edit a PR (add/remove labels, assignees, reviewers)
|
|
137
|
+
# Edit a PR (add/remove labels, assignees, reviewers, milestones)
|
|
125
138
|
gc pr edit 42 -t "New title"
|
|
126
139
|
gc pr edit --add-label bug --remove-label "needs review"
|
|
127
140
|
gc pr edit --add-reviewer @me --remove-reviewer otheruser
|
|
141
|
+
gc pr edit --milestone v1.0 --remove-milestone
|
|
128
142
|
|
|
129
143
|
# Mark as ready or convert to draft
|
|
130
144
|
gc pr ready # Mark current branch's PR as ready
|
|
@@ -133,6 +147,8 @@ gc pr ready --undo # Convert back to draft
|
|
|
133
147
|
# Comment / review / diff (identifier optional)
|
|
134
148
|
gc pr comment -b "LGTM"
|
|
135
149
|
gc pr comment --path src/file.py --position 5 -b "Suggestion"
|
|
150
|
+
gc pr comment --body-file comment.txt
|
|
151
|
+
gc pr comment --editor
|
|
136
152
|
|
|
137
153
|
gc pr review --approve
|
|
138
154
|
gc pr review --comment -b "Looks good but needs tests"
|
|
@@ -184,7 +200,7 @@ gc pr view -w
|
|
|
184
200
|
| `--web` (create/view in browser) | ✅ | ✅ |
|
|
185
201
|
| `--remove-*` flags for edit commands | ✅ | ✅ |
|
|
186
202
|
| `pr ready --undo` (convert to draft) | ✅ | ✅ |
|
|
187
|
-
| `pr review --
|
|
203
|
+
| `pr review --request-changes` | ✅ | ✅ (fallback) |
|
|
188
204
|
| `--json fields` | ✅ | ✅ |
|
|
189
205
|
| `-q jq` filtering | ✅ | ✅ |
|
|
190
206
|
| `-t template` formatting | ✅ | ✅ |
|
|
@@ -194,7 +210,8 @@ gc pr view -w
|
|
|
194
210
|
### Known Limitations (GitCode API differences)
|
|
195
211
|
|
|
196
212
|
- **PR comment model**: GitCode uses `path + position`, not GitHub's `line/side/commit`
|
|
197
|
-
- **PR review**: GitCode review API differs from GitHub; `--
|
|
213
|
+
- **PR review**: GitCode review API differs from GitHub; `--request-changes` falls back to PR comments
|
|
214
|
+
- **Issue deletion**: `gc issue delete` is exposed for CLI parity, but GitCode API does not support deleting issues
|
|
198
215
|
- **Issue create/update API**: GitCode puts `repo` in request body, not URL path
|
|
199
216
|
|
|
200
217
|
## Development
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pygitcode"
|
|
7
|
-
|
|
7
|
+
dynamic = ["version"]
|
|
8
8
|
description = "A CLI tool for GitCode (api.gitcode.com), modeled after GitHub CLI."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -58,6 +58,9 @@ gitcode = "gitcode_cli.cli:main"
|
|
|
58
58
|
[tool.setuptools.packages.find]
|
|
59
59
|
where = ["src"]
|
|
60
60
|
|
|
61
|
+
[tool.setuptools.dynamic]
|
|
62
|
+
version = {attr = "gitcode_cli.__version__"}
|
|
63
|
+
|
|
61
64
|
# --- pytest ---
|
|
62
65
|
[tool.pytest.ini_options]
|
|
63
66
|
testpaths = ["tests"]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class AdapterActionResult:
|
|
9
|
+
item: dict[str, Any] | None = None
|
|
10
|
+
items: list[dict[str, Any]] | None = None
|
|
11
|
+
message: str | None = None
|
|
12
|
+
warning: str | None = None
|
|
13
|
+
degraded: bool = False
|
|
14
|
+
approximated: bool = False
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
CAPABILITY_MESSAGES = {
|
|
6
|
+
"ISSUE_COMMENT_LAST_NOT_FOUND": "No editable issue comment from the current user was found.",
|
|
7
|
+
"ISSUE_COMMENT_OWNERSHIP_UNVERIFIABLE": (
|
|
8
|
+
"Unable to verify the current user's issue comments on GitCode; refusing to edit or delete comments safely."
|
|
9
|
+
),
|
|
10
|
+
"ISSUE_CREATE_IF_NONE_REQUIRES_EDIT_LAST": "--create-if-none can only be used together with --edit-last.",
|
|
11
|
+
"ISSUE_DELETE": "GitCode API does not support deleting issues.",
|
|
12
|
+
"ISSUE_DEVELOP_BASE": "--base and --name are not supported by 'gc issue develop'",
|
|
13
|
+
"ISSUE_DEVELOP_NAME": "--base and --name are not supported by 'gc issue develop'",
|
|
14
|
+
"ISSUE_STATUS_GH_SEMANTICS": "GitCode-limited approximation of gh issue status",
|
|
15
|
+
"PR_MERGE_AUTHOR_EMAIL": "GitCode merge API does not support --author-email.",
|
|
16
|
+
"PR_MERGE_AUTO": "GitCode merge API does not support --auto.",
|
|
17
|
+
"PR_REVIEW_REQUEST_CHANGES": (
|
|
18
|
+
"GitCode review API does not support request-changes reviews; the pull request comment was posted instead."
|
|
19
|
+
),
|
|
20
|
+
"PR_STATUS_GH_SEMANTICS": ("GitCode API approximation -- user-specific filtering is not available"),
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def capability_message(feature: str) -> str:
|
|
25
|
+
return CAPABILITY_MESSAGES[feature]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def unsupported(feature: str) -> click.ClickException:
|
|
29
|
+
return click.ClickException(capability_message(feature))
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from ..errors import APIError
|
|
8
|
+
from ..services import IssueService, UserService
|
|
9
|
+
from .base import AdapterActionResult
|
|
10
|
+
from .capabilities import capability_message, unsupported
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _normalize_multi_values(values: tuple[str, ...] | None) -> str | None:
|
|
14
|
+
if not values:
|
|
15
|
+
return None
|
|
16
|
+
return ",".join(values)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _extract_label_names(issue: dict[str, Any] | None) -> list[str]:
|
|
20
|
+
labels = issue.get("labels") if isinstance(issue, dict) else None
|
|
21
|
+
if not labels:
|
|
22
|
+
return []
|
|
23
|
+
if isinstance(labels, str):
|
|
24
|
+
return [part.strip() for part in labels.split(",") if part.strip()]
|
|
25
|
+
|
|
26
|
+
names: list[str] = []
|
|
27
|
+
if isinstance(labels, list):
|
|
28
|
+
for label in labels:
|
|
29
|
+
if isinstance(label, dict):
|
|
30
|
+
name = label.get("name") or label.get("title")
|
|
31
|
+
if name:
|
|
32
|
+
names.append(str(name))
|
|
33
|
+
elif label:
|
|
34
|
+
names.append(str(label))
|
|
35
|
+
return names
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _extract_login(data: dict[str, Any] | None) -> str | None:
|
|
39
|
+
if not isinstance(data, dict):
|
|
40
|
+
return None
|
|
41
|
+
for key in ("login", "username", "name"):
|
|
42
|
+
value = data.get(key)
|
|
43
|
+
if isinstance(value, str) and value.strip():
|
|
44
|
+
return value.strip()
|
|
45
|
+
user = data.get("user")
|
|
46
|
+
if isinstance(user, dict):
|
|
47
|
+
return _extract_login(user)
|
|
48
|
+
author = data.get("author")
|
|
49
|
+
if isinstance(author, dict):
|
|
50
|
+
return _extract_login(author)
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class IssueAdapter:
|
|
55
|
+
def __init__(self, service: IssueService, user_service: UserService | None = None):
|
|
56
|
+
self.service = service
|
|
57
|
+
self.user_service = user_service
|
|
58
|
+
|
|
59
|
+
def list_issues(
|
|
60
|
+
self,
|
|
61
|
+
owner: str,
|
|
62
|
+
repo: str,
|
|
63
|
+
*,
|
|
64
|
+
state: str | None,
|
|
65
|
+
labels: tuple[str, ...] | None,
|
|
66
|
+
author: str | None,
|
|
67
|
+
assignee: str | None,
|
|
68
|
+
milestone: str | None,
|
|
69
|
+
mention: str | None,
|
|
70
|
+
search: str | None,
|
|
71
|
+
limit: int | None,
|
|
72
|
+
) -> Any:
|
|
73
|
+
items = self.service.list(
|
|
74
|
+
owner,
|
|
75
|
+
repo,
|
|
76
|
+
state=state,
|
|
77
|
+
labels=_normalize_multi_values(labels),
|
|
78
|
+
creator=author,
|
|
79
|
+
assignee=assignee,
|
|
80
|
+
milestone=milestone,
|
|
81
|
+
mention=mention,
|
|
82
|
+
search=search,
|
|
83
|
+
)
|
|
84
|
+
if limit is not None:
|
|
85
|
+
return items[:limit]
|
|
86
|
+
return items
|
|
87
|
+
|
|
88
|
+
def create_issue(
|
|
89
|
+
self,
|
|
90
|
+
owner: str,
|
|
91
|
+
repo: str,
|
|
92
|
+
*,
|
|
93
|
+
title: str,
|
|
94
|
+
body: str | None,
|
|
95
|
+
assignee: str | None,
|
|
96
|
+
labels: tuple[str, ...] | None,
|
|
97
|
+
milestone: str | None,
|
|
98
|
+
) -> dict[str, Any] | None:
|
|
99
|
+
return self.service.create(
|
|
100
|
+
owner,
|
|
101
|
+
repo,
|
|
102
|
+
title=title,
|
|
103
|
+
body=body,
|
|
104
|
+
assignee=assignee,
|
|
105
|
+
labels=_normalize_multi_values(labels),
|
|
106
|
+
milestone=milestone,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
def close_issue(
|
|
110
|
+
self,
|
|
111
|
+
owner: str,
|
|
112
|
+
repo: str,
|
|
113
|
+
number: str,
|
|
114
|
+
*,
|
|
115
|
+
comment: str | None,
|
|
116
|
+
reason: str | None,
|
|
117
|
+
) -> AdapterActionResult:
|
|
118
|
+
current = self.service.get(owner, repo, number)
|
|
119
|
+
if current and current.get("state") == "closed":
|
|
120
|
+
if comment:
|
|
121
|
+
self.service.comment(owner, repo, number, comment)
|
|
122
|
+
return AdapterActionResult(item=current, message="already_closed_commented")
|
|
123
|
+
return AdapterActionResult(item=current, message="already_closed")
|
|
124
|
+
if comment:
|
|
125
|
+
self.service.comment(owner, repo, number, comment)
|
|
126
|
+
payload: dict[str, Any] = {"state": "close"}
|
|
127
|
+
if reason:
|
|
128
|
+
payload["state_reason"] = reason
|
|
129
|
+
item = self.service.update(owner, repo, number, **payload)
|
|
130
|
+
return AdapterActionResult(item=item, message="closed")
|
|
131
|
+
|
|
132
|
+
def comment_issue(self, owner: str, repo: str, number: str, *, body: str) -> dict[str, Any] | None:
|
|
133
|
+
return self.service.comment(owner, repo, number, body)
|
|
134
|
+
|
|
135
|
+
def manage_comment_history(
|
|
136
|
+
self,
|
|
137
|
+
owner: str,
|
|
138
|
+
repo: str,
|
|
139
|
+
number: str,
|
|
140
|
+
*,
|
|
141
|
+
body: str | None,
|
|
142
|
+
delete_last: bool,
|
|
143
|
+
create_if_none: bool,
|
|
144
|
+
) -> AdapterActionResult:
|
|
145
|
+
comment = self._find_last_owned_comment(owner, repo, number)
|
|
146
|
+
if delete_last:
|
|
147
|
+
if comment is None:
|
|
148
|
+
raise click.ClickException(capability_message("ISSUE_COMMENT_LAST_NOT_FOUND"))
|
|
149
|
+
self.service.delete_comment(owner, repo, comment["id"])
|
|
150
|
+
return AdapterActionResult(item=comment, message="deleted")
|
|
151
|
+
if comment is None:
|
|
152
|
+
if create_if_none:
|
|
153
|
+
item = self.service.comment(owner, repo, number, body or "")
|
|
154
|
+
return AdapterActionResult(item=item, message="created")
|
|
155
|
+
raise click.ClickException(capability_message("ISSUE_COMMENT_LAST_NOT_FOUND"))
|
|
156
|
+
item = self.service.update_comment(owner, repo, comment["id"], body or "")
|
|
157
|
+
return AdapterActionResult(item=item, message="edited")
|
|
158
|
+
|
|
159
|
+
def _find_last_owned_comment(self, owner: str, repo: str, number: str) -> dict[str, Any] | None:
|
|
160
|
+
if self.user_service is None:
|
|
161
|
+
raise click.ClickException(capability_message("ISSUE_COMMENT_OWNERSHIP_UNVERIFIABLE"))
|
|
162
|
+
try:
|
|
163
|
+
current_user = self.user_service.current()
|
|
164
|
+
except APIError as exc:
|
|
165
|
+
raise click.ClickException(capability_message("ISSUE_COMMENT_OWNERSHIP_UNVERIFIABLE")) from exc
|
|
166
|
+
current_login = _extract_login(current_user)
|
|
167
|
+
if not current_login:
|
|
168
|
+
raise click.ClickException(capability_message("ISSUE_COMMENT_OWNERSHIP_UNVERIFIABLE"))
|
|
169
|
+
comments = self.service.list_comments(owner, repo, number) or []
|
|
170
|
+
for comment in reversed(comments):
|
|
171
|
+
if not isinstance(comment, dict):
|
|
172
|
+
continue
|
|
173
|
+
if _extract_login(comment) == current_login:
|
|
174
|
+
return comment
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
def reopen_issue(self, owner: str, repo: str, number: str) -> AdapterActionResult:
|
|
178
|
+
current = self.service.get(owner, repo, number)
|
|
179
|
+
if current and current.get("state") == "open":
|
|
180
|
+
return AdapterActionResult(item=current, message="already_open")
|
|
181
|
+
item = self.service.update(owner, repo, number, state="reopen")
|
|
182
|
+
return AdapterActionResult(item=item, message="reopened")
|
|
183
|
+
|
|
184
|
+
def delete_issue(self, owner: str, repo: str, number: str) -> AdapterActionResult: # noqa: ARG002
|
|
185
|
+
raise unsupported("ISSUE_DELETE")
|
|
186
|
+
|
|
187
|
+
def edit_issue(
|
|
188
|
+
self,
|
|
189
|
+
owner: str,
|
|
190
|
+
repo: str,
|
|
191
|
+
number: str,
|
|
192
|
+
*,
|
|
193
|
+
title: str | None,
|
|
194
|
+
body: str | None,
|
|
195
|
+
add_assignee: str | None,
|
|
196
|
+
add_labels: tuple[str, ...] | None,
|
|
197
|
+
milestone: str | None,
|
|
198
|
+
remove_milestone: bool,
|
|
199
|
+
) -> dict[str, Any] | None:
|
|
200
|
+
data = {k: v for k, v in {"title": title, "body": body}.items() if v is not None}
|
|
201
|
+
|
|
202
|
+
if add_assignee is not None:
|
|
203
|
+
data["assignee"] = add_assignee
|
|
204
|
+
|
|
205
|
+
if add_labels:
|
|
206
|
+
current = self.service.get(owner, repo, number)
|
|
207
|
+
existing = _extract_label_names(current)
|
|
208
|
+
merged: list[str] = []
|
|
209
|
+
seen: set[str] = set()
|
|
210
|
+
for label in [*existing, *list(add_labels)]:
|
|
211
|
+
normalized = label.strip()
|
|
212
|
+
if normalized and normalized not in seen:
|
|
213
|
+
seen.add(normalized)
|
|
214
|
+
merged.append(normalized)
|
|
215
|
+
data["labels"] = ",".join(merged)
|
|
216
|
+
|
|
217
|
+
if milestone is not None:
|
|
218
|
+
data["milestone"] = milestone
|
|
219
|
+
if remove_milestone:
|
|
220
|
+
data["milestone"] = ""
|
|
221
|
+
return self.service.update(owner, repo, number, **data)
|
|
222
|
+
|
|
223
|
+
def status(self, owner: str, repo: str) -> AdapterActionResult:
|
|
224
|
+
items = self.service.list(owner, repo, state="open")
|
|
225
|
+
return AdapterActionResult(
|
|
226
|
+
items=items,
|
|
227
|
+
message=capability_message("ISSUE_STATUS_GH_SEMANTICS"),
|
|
228
|
+
approximated=True,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
def develop(self, owner: str, repo: str, number: str, *, base: str | None, name: str | None) -> AdapterActionResult: # noqa: ARG002
|
|
232
|
+
if base is not None:
|
|
233
|
+
raise unsupported("ISSUE_DEVELOP_BASE")
|
|
234
|
+
if name is not None:
|
|
235
|
+
raise unsupported("ISSUE_DEVELOP_NAME")
|
|
236
|
+
return AdapterActionResult(
|
|
237
|
+
message=f"Opening issue #{number} in the browser instead.",
|
|
238
|
+
warning="Note: 'issue develop' does not create a local branch on GitCode.",
|
|
239
|
+
)
|