github-haystack 1.1.0__tar.gz → 1.2.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.
- github_haystack-1.2.1/CHANGELOG.md +50 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/PKG-INFO +7 -16
- github_haystack-1.2.1/README.md +12 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/components/connectors/github/file_editor.py +4 -2
- {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/components/connectors/github/issue_commenter.py +3 -1
- {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/components/connectors/github/issue_viewer.py +3 -1
- {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/components/connectors/github/pr_creator.py +3 -1
- {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/components/connectors/github/repo_forker.py +5 -3
- {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/components/connectors/github/repo_viewer.py +3 -1
- {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/prompts/github/__init__.py +3 -0
- github_haystack-1.2.1/src/haystack_integrations/prompts/github/repo_forker_prompt.py +61 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/tools/github/__init__.py +2 -0
- github_haystack-1.2.1/src/haystack_integrations/tools/github/repo_forker_tool.py +116 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/tests/test_file_editor.py +11 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/tests/test_file_editor_tool.py +9 -7
- {github_haystack-1.1.0 → github_haystack-1.2.1}/tests/test_issue_commenter.py +11 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/tests/test_issue_viewer.py +34 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/tests/test_pr_creator.py +11 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/tests/test_repo_forker.py +12 -1
- github_haystack-1.2.1/tests/test_repo_forker_tool.py +128 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/tests/test_repo_viewer.py +12 -0
- github_haystack-1.1.0/CHANGELOG.md +0 -20
- github_haystack-1.1.0/README.md +0 -21
- {github_haystack-1.1.0 → github_haystack-1.2.1}/.gitignore +0 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/LICENSE.txt +0 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/pydoc/config.yml +0 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/pyproject.toml +0 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/components/connectors/github/__init__.py +0 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/components/connectors/py.typed +0 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/prompts/github/context_prompt.py +0 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/prompts/github/file_editor_prompt.py +0 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/prompts/github/issue_commenter_prompt.py +0 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/prompts/github/issue_viewer_prompt.py +0 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/prompts/github/pr_creator_prompt.py +0 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/prompts/github/repo_viewer_prompt.py +0 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/prompts/github/system_prompt.py +0 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/prompts/py.typed +0 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/tools/github/file_editor_tool.py +0 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/tools/github/issue_commenter_tool.py +0 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/tools/github/issue_viewer_tool.py +0 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/tools/github/pr_creator_tool.py +0 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/tools/github/repo_viewer_tool.py +0 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/tools/github/utils.py +0 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/tools/py.typed +0 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/tests/__init__.py +0 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/tests/test_issue_commenter_tool.py +0 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/tests/test_issue_viewer_tool.py +0 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/tests/test_pr_creator_tool.py +0 -0
- {github_haystack-1.1.0 → github_haystack-1.2.1}/tests/test_repo_viewer_tool.py +0 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [integrations/github-v1.2.0] - 2025-10-13
|
|
4
|
+
|
|
5
|
+
### 🚀 Features
|
|
6
|
+
|
|
7
|
+
- Add support for GitHubRepoForkerTool (#1968)
|
|
8
|
+
|
|
9
|
+
### 🧪 Testing
|
|
10
|
+
|
|
11
|
+
- Fix GitHub test (#2119)
|
|
12
|
+
|
|
13
|
+
### 🧹 Chores
|
|
14
|
+
|
|
15
|
+
- Standardize readmes - part 2 (#2205)
|
|
16
|
+
|
|
17
|
+
### 🌀 Miscellaneous
|
|
18
|
+
|
|
19
|
+
- Chore: Fix linting github (#2255)
|
|
20
|
+
|
|
21
|
+
## [integrations/github-v1.1.0] - 2025-07-01
|
|
22
|
+
|
|
23
|
+
### 🐛 Bug Fixes
|
|
24
|
+
|
|
25
|
+
- Fix Github types + add py.typed (#2024)
|
|
26
|
+
|
|
27
|
+
### 🧹 Chores
|
|
28
|
+
|
|
29
|
+
- Fix linting for ruff 0.12.0 (#1969)
|
|
30
|
+
- Remove black (#1985)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
## [integrations/github-v1.0.1] - 2025-06-12
|
|
34
|
+
|
|
35
|
+
### 🐛 Bug Fixes
|
|
36
|
+
|
|
37
|
+
- Prompts and schemas of GitHubIssueCommenterTool and GitHubIssueViewerTool now properly explain url parameter (#1929)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
### 🧹 Chores
|
|
41
|
+
|
|
42
|
+
- Align core-integrations Hatch scripts (#1898)
|
|
43
|
+
|
|
44
|
+
## [integrations/github-v1.0.0] - 2025-05-28
|
|
45
|
+
|
|
46
|
+
### 🚀 Features
|
|
47
|
+
|
|
48
|
+
- Add GitHub integration with components, tools, and prompts (#1637)
|
|
49
|
+
|
|
50
|
+
<!-- generated by git-cliff -->
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: github-haystack
|
|
3
|
-
Version: 1.1
|
|
3
|
+
Version: 1.2.1
|
|
4
4
|
Summary: Haystack components for interacting with GitHub repositories
|
|
5
5
|
Project-URL: Source, https://github.com/deepset-ai/haystack-core-integrations/github
|
|
6
6
|
Project-URL: Documentation, https://github.com/deepset-ai/haystack-core-integrations/blob/main/integrations/github/README.md
|
|
@@ -27,19 +27,10 @@ Description-Content-Type: text/markdown
|
|
|
27
27
|
[](https://pypi.org/project/github-haystack)
|
|
28
28
|
[](https://pypi.org/project/github-haystack)
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
- [Integration page](https://haystack.deepset.ai/integrations/github)
|
|
31
|
+
- [Changelog](https://github.com/deepset-ai/haystack-core-integrations/blob/main/integrations/github/CHANGELOG.md)
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
## Contributing
|
|
31
35
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
- [Installation](#installation)
|
|
35
|
-
- [License](#license)
|
|
36
|
-
|
|
37
|
-
## Installation
|
|
38
|
-
|
|
39
|
-
```console
|
|
40
|
-
pip install github-haystack
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## License
|
|
44
|
-
|
|
45
|
-
`github-haystack` is distributed under the terms of the [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) license.
|
|
36
|
+
Refer to the general [Contribution Guidelines](https://github.com/deepset-ai/haystack-core-integrations/blob/main/CONTRIBUTING.md).
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# github-haystack
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/github-haystack)
|
|
4
|
+
[](https://pypi.org/project/github-haystack)
|
|
5
|
+
|
|
6
|
+
- [Integration page](https://haystack.deepset.ai/integrations/github)
|
|
7
|
+
- [Changelog](https://github.com/deepset-ai/haystack-core-integrations/blob/main/integrations/github/CHANGELOG.md)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
## Contributing
|
|
11
|
+
|
|
12
|
+
Refer to the general [Contribution Guidelines](https://github.com/deepset-ai/haystack-core-integrations/blob/main/CONTRIBUTING.md).
|
|
@@ -115,7 +115,9 @@ class GitHubFileEditor:
|
|
|
115
115
|
"""
|
|
116
116
|
headers = self.base_headers.copy()
|
|
117
117
|
if self.github_token is not None:
|
|
118
|
-
|
|
118
|
+
token_value = self.github_token.resolve_value()
|
|
119
|
+
if token_value:
|
|
120
|
+
headers["Authorization"] = f"Bearer {token_value}"
|
|
119
121
|
return headers
|
|
120
122
|
|
|
121
123
|
def _get_file_content(self, owner: str, repo: str, path: str, branch: str) -> tuple[str, str]:
|
|
@@ -225,7 +227,7 @@ class GitHubFileEditor:
|
|
|
225
227
|
def _delete_file(self, owner: str, repo: str, payload: Dict[str, str], branch: str) -> str:
|
|
226
228
|
"""Handle file deletion."""
|
|
227
229
|
try:
|
|
228
|
-
|
|
230
|
+
_, sha = self._get_file_content(owner, repo, payload["path"], branch)
|
|
229
231
|
url = f"https://api.github.com/repos/{owner}/{repo}/contents/{payload['path']}"
|
|
230
232
|
|
|
231
233
|
data = {"message": payload["message"], "sha": sha, "branch": branch}
|
|
@@ -67,7 +67,9 @@ class GitHubIssueCommenter:
|
|
|
67
67
|
"""
|
|
68
68
|
headers = self.base_headers.copy()
|
|
69
69
|
if self.github_token is not None:
|
|
70
|
-
|
|
70
|
+
token_value = self.github_token.resolve_value()
|
|
71
|
+
if token_value:
|
|
72
|
+
headers["Authorization"] = f"Bearer {token_value}"
|
|
71
73
|
return headers
|
|
72
74
|
|
|
73
75
|
def _parse_github_url(self, url: str) -> tuple[str, str, int]:
|
|
@@ -66,7 +66,9 @@ class GitHubIssueViewer:
|
|
|
66
66
|
"""
|
|
67
67
|
headers = self.base_headers.copy()
|
|
68
68
|
if self.github_token:
|
|
69
|
-
|
|
69
|
+
token_value = self.github_token.resolve_value()
|
|
70
|
+
if token_value:
|
|
71
|
+
headers["Authorization"] = f"Bearer {token_value}"
|
|
70
72
|
return headers
|
|
71
73
|
|
|
72
74
|
def _parse_github_url(self, url: str) -> tuple[str, str, int]:
|
|
@@ -65,7 +65,9 @@ class GitHubPRCreator:
|
|
|
65
65
|
"""
|
|
66
66
|
headers = self.base_headers.copy()
|
|
67
67
|
if self.github_token is not None:
|
|
68
|
-
|
|
68
|
+
token_value = self.github_token.resolve_value()
|
|
69
|
+
if token_value:
|
|
70
|
+
headers["Authorization"] = f"Bearer {token_value}"
|
|
69
71
|
return headers
|
|
70
72
|
|
|
71
73
|
def _parse_issue_url(self, issue_url: str) -> tuple[str, str, str]:
|
|
@@ -60,9 +60,9 @@ class GitHubRepoForker:
|
|
|
60
60
|
:param auto_sync: If True, syncs fork with original repository if it already exists
|
|
61
61
|
:param create_branch: If True, creates a fix branch based on the issue number
|
|
62
62
|
"""
|
|
63
|
-
error_message = "github_token must be a Secret"
|
|
64
63
|
if not isinstance(github_token, Secret):
|
|
65
|
-
|
|
64
|
+
msg = "github_token must be a Secret"
|
|
65
|
+
raise TypeError(msg)
|
|
66
66
|
|
|
67
67
|
self.github_token = github_token
|
|
68
68
|
self.raise_on_failure = raise_on_failure
|
|
@@ -85,7 +85,9 @@ class GitHubRepoForker:
|
|
|
85
85
|
"""
|
|
86
86
|
headers = self.base_headers.copy()
|
|
87
87
|
if self.github_token is not None:
|
|
88
|
-
|
|
88
|
+
token_value = self.github_token.resolve_value()
|
|
89
|
+
if token_value:
|
|
90
|
+
headers["Authorization"] = f"Bearer {token_value}"
|
|
89
91
|
return headers
|
|
90
92
|
|
|
91
93
|
def _parse_github_url(self, url: str) -> tuple[str, str, str]:
|
|
@@ -109,7 +109,9 @@ class GitHubRepoViewer:
|
|
|
109
109
|
"""
|
|
110
110
|
headers = self.base_headers.copy()
|
|
111
111
|
if self.github_token is not None:
|
|
112
|
-
|
|
112
|
+
token_value = self.github_token.resolve_value()
|
|
113
|
+
if token_value:
|
|
114
|
+
headers["Authorization"] = f"Bearer {token_value}"
|
|
113
115
|
return headers
|
|
114
116
|
|
|
115
117
|
def to_dict(self) -> Dict[str, Any]:
|
{github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/prompts/github/__init__.py
RENAMED
|
@@ -5,6 +5,7 @@ from .context_prompt import CONTEXT_PROMPT
|
|
|
5
5
|
from .file_editor_prompt import FILE_EDITOR_PROMPT, FILE_EDITOR_SCHEMA
|
|
6
6
|
from .issue_commenter_prompt import ISSUE_COMMENTER_PROMPT, ISSUE_COMMENTER_SCHEMA
|
|
7
7
|
from .pr_creator_prompt import PR_CREATOR_PROMPT, PR_CREATOR_SCHEMA
|
|
8
|
+
from .repo_forker_prompt import REPO_FORKER_PROMPT, REPO_FORKER_SCHEMA
|
|
8
9
|
from .repo_viewer_prompt import REPO_VIEWER_PROMPT, REPO_VIEWER_SCHEMA
|
|
9
10
|
from .system_prompt import SYSTEM_PROMPT
|
|
10
11
|
|
|
@@ -16,6 +17,8 @@ __all__ = [
|
|
|
16
17
|
"ISSUE_COMMENTER_SCHEMA",
|
|
17
18
|
"PR_CREATOR_PROMPT",
|
|
18
19
|
"PR_CREATOR_SCHEMA",
|
|
20
|
+
"REPO_FORKER_PROMPT",
|
|
21
|
+
"REPO_FORKER_SCHEMA",
|
|
19
22
|
"REPO_VIEWER_PROMPT",
|
|
20
23
|
"REPO_VIEWER_SCHEMA",
|
|
21
24
|
"SYSTEM_PROMPT",
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2023-present deepset GmbH <info@deepset.ai>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
REPO_FORKER_PROMPT = """Haystack-Agent uses this tool to fork GitHub repositories in order to contribute to issues.
|
|
5
|
+
Haystack-Agent initiates a fork so it can freely make changes for contributions.
|
|
6
|
+
A fork is required to open a pull request to the upstream repository.
|
|
7
|
+
Haystack-Agent works by forking the repository associated with a given issue.
|
|
8
|
+
|
|
9
|
+
<usage>
|
|
10
|
+
Pass a `url` string for the GitHub issue you want to work on in a fork.
|
|
11
|
+
It is REQUIRED to pass `url` to use this tool.
|
|
12
|
+
The structure must be "https://github.com/<repo-owner>/<repo-name>/issues/<issue-number>".
|
|
13
|
+
|
|
14
|
+
Examples:
|
|
15
|
+
|
|
16
|
+
- {"url": "https://github.com/deepset-ai/haystack/issues/9343"}
|
|
17
|
+
- will fork the "deepset-ai/haystack" repository to work on issue 9343
|
|
18
|
+
- {"url": "https://github.com/deepset-ai/haystack-core-integrations/issues/1685"}
|
|
19
|
+
- will fork the "deepset-ai/haystack-core-integrations" repository to work on issue 1685
|
|
20
|
+
</usage>
|
|
21
|
+
|
|
22
|
+
Haystack-Agent uses the `repo_forker` tool to create a copy (fork) of the target repository into its own account.
|
|
23
|
+
Haystack-Agent ensures the issue URL is valid and points to a real GitHub issue.
|
|
24
|
+
It parses the URL to identify the correct repository.
|
|
25
|
+
|
|
26
|
+
<thinking>
|
|
27
|
+
- Does this issue belong to the repository I need to work on?
|
|
28
|
+
- Can I extract the owner and repository name from the URL?
|
|
29
|
+
- Why am I forking this repository? (e.g., to implement a fix, to add a feature)
|
|
30
|
+
- Is there anything special about the branch or base state I should be aware of?
|
|
31
|
+
</thinking>
|
|
32
|
+
|
|
33
|
+
Haystack-Agent reflects on the results after forking:
|
|
34
|
+
<thinking>
|
|
35
|
+
- Did the fork succeed? Is the fork visible in my account?
|
|
36
|
+
- Can I access, clone, and push to my fork?
|
|
37
|
+
- Are there any permissions or fork-specific settings to configure before proceeding?
|
|
38
|
+
- Which branch will I be working on in the fork?
|
|
39
|
+
</thinking>
|
|
40
|
+
|
|
41
|
+
IMPORTANT
|
|
42
|
+
Haystack-Agent ONLY forks the repository mentioned in the given issue URL.
|
|
43
|
+
Haystack-Agent does NOT attempt to fork organizations, user profiles, or non-issue URLs.
|
|
44
|
+
Haystack-Agent knows that forking is a prerequisite to contributing changes and creating pull requests.
|
|
45
|
+
|
|
46
|
+
Haystack-Agent takes notes after the fork:
|
|
47
|
+
<scratchpad>
|
|
48
|
+
- Record the URL of the forked repository
|
|
49
|
+
- Note the original issue being worked on
|
|
50
|
+
- Document any post-fork steps (e.g., git cloning, installing dependencies)
|
|
51
|
+
- Make note of any errors or special setup requirements
|
|
52
|
+
</scratchpad>
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
REPO_FORKER_SCHEMA = {
|
|
56
|
+
"properties": {
|
|
57
|
+
"url": {"type": "string", "description": "URL of the GitHub issue to work on in the fork."},
|
|
58
|
+
},
|
|
59
|
+
"required": ["url"],
|
|
60
|
+
"type": "object",
|
|
61
|
+
}
|
{github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/tools/github/__init__.py
RENAMED
|
@@ -5,6 +5,7 @@ from .file_editor_tool import GitHubFileEditorTool
|
|
|
5
5
|
from .issue_commenter_tool import GitHubIssueCommenterTool
|
|
6
6
|
from .issue_viewer_tool import GitHubIssueViewerTool
|
|
7
7
|
from .pr_creator_tool import GitHubPRCreatorTool
|
|
8
|
+
from .repo_forker_tool import GitHubRepoForkerTool
|
|
8
9
|
from .repo_viewer_tool import GitHubRepoViewerTool
|
|
9
10
|
|
|
10
11
|
__all__ = [
|
|
@@ -12,5 +13,6 @@ __all__ = [
|
|
|
12
13
|
"GitHubIssueCommenterTool",
|
|
13
14
|
"GitHubIssueViewerTool",
|
|
14
15
|
"GitHubPRCreatorTool",
|
|
16
|
+
"GitHubRepoForkerTool",
|
|
15
17
|
"GitHubRepoViewerTool",
|
|
16
18
|
]
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2023-present deepset GmbH <info@deepset.ai>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
from typing import Any, Callable, Dict, Optional, Union
|
|
5
|
+
|
|
6
|
+
from haystack.core.serialization import generate_qualified_class_name
|
|
7
|
+
from haystack.tools import ComponentTool
|
|
8
|
+
from haystack.utils import Secret, deserialize_secrets_inplace
|
|
9
|
+
|
|
10
|
+
from haystack_integrations.components.connectors.github.repo_forker import GitHubRepoForker
|
|
11
|
+
from haystack_integrations.prompts.github.repo_forker_prompt import REPO_FORKER_PROMPT, REPO_FORKER_SCHEMA
|
|
12
|
+
from haystack_integrations.tools.github.utils import deserialize_handlers, serialize_handlers
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class GitHubRepoForkerTool(ComponentTool):
|
|
16
|
+
"""
|
|
17
|
+
A tool for forking Github repository.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
*,
|
|
23
|
+
name: Optional[str] = "repo_forker",
|
|
24
|
+
description: Optional[str] = REPO_FORKER_PROMPT,
|
|
25
|
+
parameters: Optional[Dict[str, Any]] = REPO_FORKER_SCHEMA,
|
|
26
|
+
github_token: Secret = Secret.from_env_var("GITHUB_TOKEN"),
|
|
27
|
+
raise_on_failure: bool = True,
|
|
28
|
+
outputs_to_string: Optional[Dict[str, Union[str, Callable[[Any], str]]]] = None,
|
|
29
|
+
inputs_from_state: Optional[Dict[str, str]] = None,
|
|
30
|
+
outputs_to_state: Optional[Dict[str, Dict[str, Union[str, Callable]]]] = None,
|
|
31
|
+
):
|
|
32
|
+
"""
|
|
33
|
+
Initialize the GitHub Repo Forker tool.
|
|
34
|
+
|
|
35
|
+
:param name: Optional name for the tool.
|
|
36
|
+
:param description: Optional description.
|
|
37
|
+
:param parameters: Optional JSON schema defining the parameters expected by the Tool.
|
|
38
|
+
:param github_token: GitHub personal access token for API authentication
|
|
39
|
+
:param raise_on_failure: If True, raises exceptions on API errors
|
|
40
|
+
:param outputs_to_string:
|
|
41
|
+
Optional dictionary defining how a tool outputs should be converted into a string.
|
|
42
|
+
If the source is provided only the specified output key is sent to the handler.
|
|
43
|
+
If the source is omitted the whole tool result is sent to the handler.
|
|
44
|
+
Example: {
|
|
45
|
+
"source": "docs", "handler": format_documents
|
|
46
|
+
}
|
|
47
|
+
:param inputs_from_state:
|
|
48
|
+
Optional dictionary mapping state keys to tool parameter names.
|
|
49
|
+
Example: {"repository": "repo"} maps state's "repository" to tool's "repo" parameter.
|
|
50
|
+
:param outputs_to_state:
|
|
51
|
+
Optional dictionary defining how tool outputs map to keys within state as well as optional handlers.
|
|
52
|
+
If the source is provided only the specified output key is sent to the handler.
|
|
53
|
+
Example: {
|
|
54
|
+
"documents": {"source": "docs", "handler": custom_handler}
|
|
55
|
+
}
|
|
56
|
+
If the source is omitted the whole tool result is sent to the handler.
|
|
57
|
+
Example: {
|
|
58
|
+
"documents": {"handler": custom_handler}
|
|
59
|
+
}
|
|
60
|
+
"""
|
|
61
|
+
self.github_token = github_token
|
|
62
|
+
self.raise_on_failure = raise_on_failure
|
|
63
|
+
self.outputs_to_string = outputs_to_string
|
|
64
|
+
self.inputs_from_state = inputs_from_state
|
|
65
|
+
self.outputs_to_state = outputs_to_state
|
|
66
|
+
|
|
67
|
+
repo_forker = GitHubRepoForker(
|
|
68
|
+
github_token=github_token,
|
|
69
|
+
raise_on_failure=raise_on_failure,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
super().__init__(
|
|
73
|
+
component=repo_forker,
|
|
74
|
+
name=name,
|
|
75
|
+
description=description,
|
|
76
|
+
parameters=parameters,
|
|
77
|
+
outputs_to_string=self.outputs_to_string,
|
|
78
|
+
inputs_from_state=self.inputs_from_state,
|
|
79
|
+
outputs_to_state=self.outputs_to_state,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
83
|
+
"""
|
|
84
|
+
Serializes the tool to a dictionary.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Dictionary with serialized data.
|
|
88
|
+
"""
|
|
89
|
+
serialized = {
|
|
90
|
+
"name": self.name,
|
|
91
|
+
"description": self.description,
|
|
92
|
+
"parameters": self.parameters,
|
|
93
|
+
"github_token": self.github_token.to_dict() if self.github_token else None,
|
|
94
|
+
"raise_on_failure": self.raise_on_failure,
|
|
95
|
+
"outputs_to_string": self.outputs_to_string,
|
|
96
|
+
"inputs_from_state": self.inputs_from_state,
|
|
97
|
+
"outputs_to_state": self.outputs_to_state,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
serialize_handlers(serialized, self.outputs_to_state, self.outputs_to_string)
|
|
101
|
+
return {"type": generate_qualified_class_name(type(self)), "data": serialized}
|
|
102
|
+
|
|
103
|
+
@classmethod
|
|
104
|
+
def from_dict(cls, data: Dict[str, Any]) -> "GitHubRepoForkerTool":
|
|
105
|
+
"""
|
|
106
|
+
Deserializes the tool from a dictionary.
|
|
107
|
+
|
|
108
|
+
:param data:
|
|
109
|
+
Dictionary to deserialize from.
|
|
110
|
+
:returns:
|
|
111
|
+
Deserialized tool.
|
|
112
|
+
"""
|
|
113
|
+
inner_data = data["data"]
|
|
114
|
+
deserialize_secrets_inplace(inner_data, keys=["github_token"])
|
|
115
|
+
deserialize_handlers(inner_data)
|
|
116
|
+
return cls(**inner_data)
|
|
@@ -269,3 +269,14 @@ class TestGitHubFileEditor:
|
|
|
269
269
|
repo="owner/repo",
|
|
270
270
|
branch="main",
|
|
271
271
|
)
|
|
272
|
+
|
|
273
|
+
def test_get_request_headers_with_empty_token(self, monkeypatch):
|
|
274
|
+
monkeypatch.setenv("GITHUB_TOKEN", "")
|
|
275
|
+
|
|
276
|
+
editor = GitHubFileEditor()
|
|
277
|
+
|
|
278
|
+
headers = editor._get_request_headers()
|
|
279
|
+
|
|
280
|
+
assert "Authorization" not in headers
|
|
281
|
+
assert headers["Accept"] == "application/vnd.github.v3+json"
|
|
282
|
+
assert headers["User-Agent"] == "Haystack/GitHubFileEditor"
|
|
@@ -141,13 +141,15 @@ class TestGitHubFileEditorTool:
|
|
|
141
141
|
|
|
142
142
|
pipeline_dict = pipeline.to_dict()
|
|
143
143
|
|
|
144
|
-
# Remove
|
|
145
|
-
#
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
144
|
+
# Remove parameters introduced after haystack-ai==2.12.0 (the minimum supported version)
|
|
145
|
+
# to maintain compatibility
|
|
146
|
+
try:
|
|
147
|
+
pipeline_dict["components"]["agent"]["init_parameters"]["chat_generator"]["init_parameters"].pop(
|
|
148
|
+
"http_client_kwargs", None
|
|
149
|
+
)
|
|
150
|
+
pipeline_dict["components"]["agent"]["init_parameters"].pop("tool_invoker_kwargs", None)
|
|
151
|
+
except KeyError:
|
|
152
|
+
pass
|
|
151
153
|
|
|
152
154
|
expected_dict = {
|
|
153
155
|
"metadata": {},
|
|
@@ -114,3 +114,14 @@ class TestGitHubIssueCommenter:
|
|
|
114
114
|
|
|
115
115
|
with pytest.raises(ValueError):
|
|
116
116
|
commenter._parse_github_url("https://github.com/invalid/url")
|
|
117
|
+
|
|
118
|
+
def test_get_request_headers_with_empty_token(self, monkeypatch):
|
|
119
|
+
monkeypatch.setenv("GITHUB_TOKEN", "")
|
|
120
|
+
|
|
121
|
+
commenter = GitHubIssueCommenter()
|
|
122
|
+
|
|
123
|
+
headers = commenter._get_request_headers()
|
|
124
|
+
|
|
125
|
+
assert "Authorization" not in headers
|
|
126
|
+
assert headers["Accept"] == "application/vnd.github.v3+json"
|
|
127
|
+
assert headers["User-Agent"] == "Haystack/GitHubIssueCommenter"
|
|
@@ -150,3 +150,37 @@ class TestGitHubIssueViewer:
|
|
|
150
150
|
|
|
151
151
|
with pytest.raises(ValueError):
|
|
152
152
|
viewer._parse_github_url("https://github.com/invalid/url")
|
|
153
|
+
|
|
154
|
+
def test_get_request_headers_with_valid_token(self, monkeypatch):
|
|
155
|
+
monkeypatch.setenv("GITHUB_TOKEN", "test-token-value")
|
|
156
|
+
|
|
157
|
+
token = Secret.from_env_var("GITHUB_TOKEN")
|
|
158
|
+
viewer = GitHubIssueViewer(github_token=token)
|
|
159
|
+
|
|
160
|
+
headers = viewer._get_request_headers()
|
|
161
|
+
|
|
162
|
+
assert "Authorization" in headers
|
|
163
|
+
assert headers["Authorization"] == "Bearer test-token-value"
|
|
164
|
+
assert headers["Accept"] == "application/vnd.github.v3+json"
|
|
165
|
+
assert headers["User-Agent"] == "Haystack/GitHubIssueViewer"
|
|
166
|
+
|
|
167
|
+
def test_get_request_headers_without_token(self):
|
|
168
|
+
viewer = GitHubIssueViewer(github_token=None)
|
|
169
|
+
|
|
170
|
+
headers = viewer._get_request_headers()
|
|
171
|
+
|
|
172
|
+
assert "Authorization" not in headers
|
|
173
|
+
assert headers["Accept"] == "application/vnd.github.v3+json"
|
|
174
|
+
assert headers["User-Agent"] == "Haystack/GitHubIssueViewer"
|
|
175
|
+
|
|
176
|
+
def test_get_request_headers_with_empty_token(self, monkeypatch):
|
|
177
|
+
monkeypatch.setenv("GITHUB_TOKEN", "")
|
|
178
|
+
|
|
179
|
+
token = Secret.from_env_var("GITHUB_TOKEN")
|
|
180
|
+
viewer = GitHubIssueViewer(github_token=token)
|
|
181
|
+
|
|
182
|
+
headers = viewer._get_request_headers()
|
|
183
|
+
|
|
184
|
+
assert "Authorization" not in headers
|
|
185
|
+
assert headers["Accept"] == "application/vnd.github.v3+json"
|
|
186
|
+
assert headers["User-Agent"] == "Haystack/GitHubIssueViewer"
|
|
@@ -132,3 +132,14 @@ class TestGitHubPRCreator:
|
|
|
132
132
|
branch="feature-branch",
|
|
133
133
|
base="main",
|
|
134
134
|
)
|
|
135
|
+
|
|
136
|
+
def test_get_request_headers_with_empty_token(self, monkeypatch):
|
|
137
|
+
monkeypatch.setenv("GITHUB_TOKEN", "")
|
|
138
|
+
|
|
139
|
+
pr_creator = GitHubPRCreator()
|
|
140
|
+
|
|
141
|
+
headers = pr_creator._get_request_headers()
|
|
142
|
+
|
|
143
|
+
assert "Authorization" not in headers
|
|
144
|
+
assert headers["Accept"] == "application/vnd.github.v3+json"
|
|
145
|
+
assert headers["User-Agent"] == "Haystack/GitHubPRCreator"
|
|
@@ -14,7 +14,7 @@ class TestGitHubRepoForker:
|
|
|
14
14
|
def test_init_default(self, monkeypatch):
|
|
15
15
|
monkeypatch.setenv("GITHUB_TOKEN", "test-token")
|
|
16
16
|
|
|
17
|
-
forker = GitHubRepoForker()
|
|
17
|
+
forker = GitHubRepoForker(github_token=Secret.from_env_var("GITHUB_TOKEN"))
|
|
18
18
|
assert forker.github_token is not None
|
|
19
19
|
assert forker.github_token.resolve_value() == "test-token"
|
|
20
20
|
assert forker.raise_on_failure is True
|
|
@@ -261,3 +261,14 @@ class TestGitHubRepoForker:
|
|
|
261
261
|
|
|
262
262
|
with pytest.raises(ValueError):
|
|
263
263
|
forker._parse_github_url("https://github.com/invalid/url")
|
|
264
|
+
|
|
265
|
+
def test_get_request_headers_with_empty_token(self, monkeypatch):
|
|
266
|
+
monkeypatch.setenv("GITHUB_TOKEN", "")
|
|
267
|
+
|
|
268
|
+
forker = GitHubRepoForker()
|
|
269
|
+
|
|
270
|
+
headers = forker._get_request_headers()
|
|
271
|
+
|
|
272
|
+
assert "Authorization" not in headers
|
|
273
|
+
assert headers["Accept"] == "application/vnd.github.v3+json"
|
|
274
|
+
assert headers["User-Agent"] == "Haystack/GitHubRepoForker"
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2023-present deepset GmbH <info@deepset.ai>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
from haystack.utils import Secret
|
|
5
|
+
|
|
6
|
+
from haystack_integrations.prompts.github.repo_forker_prompt import REPO_FORKER_PROMPT, REPO_FORKER_SCHEMA
|
|
7
|
+
from haystack_integrations.tools.github.repo_forker_tool import GitHubRepoForkerTool
|
|
8
|
+
from haystack_integrations.tools.github.utils import message_handler
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestGitHubRepoForkerTool:
|
|
12
|
+
def test_init(self, monkeypatch):
|
|
13
|
+
monkeypatch.setenv("GITHUB_TOKEN", "test-token")
|
|
14
|
+
|
|
15
|
+
tool = GitHubRepoForkerTool()
|
|
16
|
+
assert tool.name == "repo_forker"
|
|
17
|
+
assert tool.description == REPO_FORKER_PROMPT
|
|
18
|
+
assert tool.parameters == REPO_FORKER_SCHEMA
|
|
19
|
+
assert tool.github_token == Secret.from_env_var("GITHUB_TOKEN")
|
|
20
|
+
assert tool.raise_on_failure is True
|
|
21
|
+
assert tool.outputs_to_string is None
|
|
22
|
+
assert tool.inputs_from_state is None
|
|
23
|
+
assert tool.outputs_to_state is None
|
|
24
|
+
|
|
25
|
+
def test_from_dict(self, monkeypatch):
|
|
26
|
+
monkeypatch.setenv("GITHUB_TOKEN", "test-token")
|
|
27
|
+
tool_dict = {
|
|
28
|
+
"type": "haystack_integrations.tools.github.repo_forker_tool.GitHubRepoForkerTool",
|
|
29
|
+
"data": {
|
|
30
|
+
"name": "repo_forker",
|
|
31
|
+
"description": REPO_FORKER_PROMPT,
|
|
32
|
+
"parameters": REPO_FORKER_SCHEMA,
|
|
33
|
+
"github_token": {"env_vars": ["GITHUB_TOKEN"], "strict": True, "type": "env_var"},
|
|
34
|
+
"raise_on_failure": True,
|
|
35
|
+
"outputs_to_string": None,
|
|
36
|
+
"inputs_from_state": None,
|
|
37
|
+
"outputs_to_state": None,
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
tool = GitHubRepoForkerTool.from_dict(tool_dict)
|
|
41
|
+
assert tool.name == "repo_forker"
|
|
42
|
+
assert tool.description == REPO_FORKER_PROMPT
|
|
43
|
+
assert tool.parameters == REPO_FORKER_SCHEMA
|
|
44
|
+
assert tool.github_token == Secret.from_env_var("GITHUB_TOKEN")
|
|
45
|
+
assert tool.raise_on_failure is True
|
|
46
|
+
assert tool.outputs_to_string is None
|
|
47
|
+
assert tool.inputs_from_state is None
|
|
48
|
+
assert tool.outputs_to_state is None
|
|
49
|
+
|
|
50
|
+
def test_to_dict(self, monkeypatch):
|
|
51
|
+
monkeypatch.setenv("GITHUB_TOKEN", "test-token")
|
|
52
|
+
tool = GitHubRepoForkerTool()
|
|
53
|
+
tool_dict = tool.to_dict()
|
|
54
|
+
assert tool_dict["type"] == "haystack_integrations.tools.github.repo_forker_tool.GitHubRepoForkerTool"
|
|
55
|
+
assert tool_dict["data"]["name"] == "repo_forker"
|
|
56
|
+
assert tool_dict["data"]["description"] == REPO_FORKER_PROMPT
|
|
57
|
+
assert tool_dict["data"]["parameters"] == REPO_FORKER_SCHEMA
|
|
58
|
+
assert tool_dict["data"]["github_token"] == {
|
|
59
|
+
"env_vars": ["GITHUB_TOKEN"],
|
|
60
|
+
"strict": True,
|
|
61
|
+
"type": "env_var",
|
|
62
|
+
}
|
|
63
|
+
assert tool_dict["data"]["raise_on_failure"] is True
|
|
64
|
+
assert tool_dict["data"]["outputs_to_string"] is None
|
|
65
|
+
assert tool_dict["data"]["inputs_from_state"] is None
|
|
66
|
+
assert tool_dict["data"]["outputs_to_state"] is None
|
|
67
|
+
|
|
68
|
+
def test_to_dict_with_extra_params(self, monkeypatch):
|
|
69
|
+
monkeypatch.setenv("GITHUB_TOKEN", "test-token")
|
|
70
|
+
tool = GitHubRepoForkerTool(
|
|
71
|
+
github_token=Secret.from_env_var("GITHUB_TOKEN"),
|
|
72
|
+
raise_on_failure=False,
|
|
73
|
+
outputs_to_string={"source": "docs", "handler": message_handler},
|
|
74
|
+
inputs_from_state={"repository": "repo"},
|
|
75
|
+
outputs_to_state={"documents": {"source": "docs", "handler": message_handler}},
|
|
76
|
+
)
|
|
77
|
+
tool_dict = tool.to_dict()
|
|
78
|
+
assert tool_dict["type"] == "haystack_integrations.tools.github.repo_forker_tool.GitHubRepoForkerTool"
|
|
79
|
+
assert tool_dict["data"]["name"] == "repo_forker"
|
|
80
|
+
assert tool_dict["data"]["description"] == REPO_FORKER_PROMPT
|
|
81
|
+
assert tool_dict["data"]["parameters"] == REPO_FORKER_SCHEMA
|
|
82
|
+
assert tool_dict["data"]["github_token"] == {
|
|
83
|
+
"env_vars": ["GITHUB_TOKEN"],
|
|
84
|
+
"strict": True,
|
|
85
|
+
"type": "env_var",
|
|
86
|
+
}
|
|
87
|
+
assert tool_dict["data"]["raise_on_failure"] is False
|
|
88
|
+
assert (
|
|
89
|
+
tool_dict["data"]["outputs_to_string"]["handler"]
|
|
90
|
+
== "haystack_integrations.tools.github.utils.message_handler"
|
|
91
|
+
)
|
|
92
|
+
assert tool_dict["data"]["inputs_from_state"] == {"repository": "repo"}
|
|
93
|
+
assert tool_dict["data"]["outputs_to_state"]["documents"]["source"] == "docs"
|
|
94
|
+
assert (
|
|
95
|
+
tool_dict["data"]["outputs_to_state"]["documents"]["handler"]
|
|
96
|
+
== "haystack_integrations.tools.github.utils.message_handler"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
def test_from_dict_with_extra_params(self, monkeypatch):
|
|
100
|
+
monkeypatch.setenv("GITHUB_TOKEN", "test-token")
|
|
101
|
+
tool_dict = {
|
|
102
|
+
"type": "haystack_integrations.tools.github.repo_forker_tool.GitHubRepoForkerTool",
|
|
103
|
+
"data": {
|
|
104
|
+
"name": "repo_forker",
|
|
105
|
+
"description": REPO_FORKER_PROMPT,
|
|
106
|
+
"parameters": REPO_FORKER_SCHEMA,
|
|
107
|
+
"github_token": {"env_vars": ["GITHUB_TOKEN"], "strict": True, "type": "env_var"},
|
|
108
|
+
"raise_on_failure": False,
|
|
109
|
+
"outputs_to_string": {"handler": "haystack_integrations.tools.github.utils.message_handler"},
|
|
110
|
+
"inputs_from_state": {"repository": "repo"},
|
|
111
|
+
"outputs_to_state": {
|
|
112
|
+
"documents": {
|
|
113
|
+
"source": "docs",
|
|
114
|
+
"handler": "haystack_integrations.tools.github.utils.message_handler",
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
tool = GitHubRepoForkerTool.from_dict(tool_dict)
|
|
120
|
+
assert tool.name == "repo_forker"
|
|
121
|
+
assert tool.description == REPO_FORKER_PROMPT
|
|
122
|
+
assert tool.parameters == REPO_FORKER_SCHEMA
|
|
123
|
+
assert tool.github_token == Secret.from_env_var("GITHUB_TOKEN")
|
|
124
|
+
assert tool.raise_on_failure is False
|
|
125
|
+
assert tool.outputs_to_string["handler"] == message_handler
|
|
126
|
+
assert tool.inputs_from_state == {"repository": "repo"}
|
|
127
|
+
assert tool.outputs_to_state["documents"]["source"] == "docs"
|
|
128
|
+
assert tool.outputs_to_state["documents"]["handler"] == message_handler
|
|
@@ -174,3 +174,15 @@ class TestGitHubRepoViewer:
|
|
|
174
174
|
|
|
175
175
|
with pytest.raises(ValueError):
|
|
176
176
|
viewer._parse_repo("invalid_format")
|
|
177
|
+
|
|
178
|
+
def test_get_request_headers_with_empty_token(self, monkeypatch):
|
|
179
|
+
monkeypatch.setenv("GITHUB_TOKEN", "")
|
|
180
|
+
|
|
181
|
+
token = Secret.from_env_var("GITHUB_TOKEN")
|
|
182
|
+
viewer = GitHubRepoViewer(github_token=token)
|
|
183
|
+
|
|
184
|
+
headers = viewer._get_request_headers()
|
|
185
|
+
|
|
186
|
+
assert "Authorization" not in headers
|
|
187
|
+
assert headers["Accept"] == "application/vnd.github.v3+json"
|
|
188
|
+
assert headers["User-Agent"] == "Haystack/GitHubRepoViewer"
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## [integrations/github-v1.0.1] - 2025-06-12
|
|
4
|
-
|
|
5
|
-
### 🐛 Bug Fixes
|
|
6
|
-
|
|
7
|
-
- Prompts and schemas of GitHubIssueCommenterTool and GitHubIssueViewerTool now properly explain url parameter (#1929)
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
### 🧹 Chores
|
|
11
|
-
|
|
12
|
-
- Align core-integrations Hatch scripts (#1898)
|
|
13
|
-
|
|
14
|
-
## [integrations/github-v1.0.0] - 2025-05-28
|
|
15
|
-
|
|
16
|
-
### 🚀 Features
|
|
17
|
-
|
|
18
|
-
- Add GitHub integration with components, tools, and prompts (#1637)
|
|
19
|
-
|
|
20
|
-
<!-- generated by git-cliff -->
|
github_haystack-1.1.0/README.md
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# github-haystack
|
|
2
|
-
|
|
3
|
-
[](https://pypi.org/project/github-haystack)
|
|
4
|
-
[](https://pypi.org/project/github-haystack)
|
|
5
|
-
|
|
6
|
-
-----
|
|
7
|
-
|
|
8
|
-
## Table of Contents
|
|
9
|
-
|
|
10
|
-
- [Installation](#installation)
|
|
11
|
-
- [License](#license)
|
|
12
|
-
|
|
13
|
-
## Installation
|
|
14
|
-
|
|
15
|
-
```console
|
|
16
|
-
pip install github-haystack
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## License
|
|
20
|
-
|
|
21
|
-
`github-haystack` is distributed under the terms of the [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) license.
|
|
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
|
{github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/tools/github/utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|