gpt-pr 0.1.0__tar.gz → 0.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.
- {gpt-pr-0.1.0 → gpt-pr-0.2.1}/PKG-INFO +1 -1
- {gpt-pr-0.1.0 → gpt-pr-0.2.1}/README.md +31 -5
- {gpt-pr-0.1.0 → gpt-pr-0.2.1}/gpt_pr.egg-info/PKG-INFO +1 -1
- {gpt-pr-0.1.0 → gpt-pr-0.2.1}/gpt_pr.egg-info/SOURCES.txt +3 -2
- {gpt-pr-0.1.0 → gpt-pr-0.2.1}/gpt_pr.egg-info/requires.txt +35 -25
- {gpt-pr-0.1.0 → gpt-pr-0.2.1}/gptpr/gh.py +5 -4
- {gpt-pr-0.1.0 → gpt-pr-0.2.1}/gptpr/gitutil.py +47 -28
- {gpt-pr-0.1.0 → gpt-pr-0.2.1}/gptpr/main.py +22 -3
- {gpt-pr-0.1.0 → gpt-pr-0.2.1}/gptpr/prdata.py +49 -31
- gpt-pr-0.2.1/gptpr/test_prdata.py +13 -0
- gpt-pr-0.2.1/gptpr/version.py +1 -0
- gpt-pr-0.2.1/requirements.txt +40 -0
- {gpt-pr-0.1.0 → gpt-pr-0.2.1}/setup.py +9 -5
- gpt-pr-0.1.0/gptpr/__version__.py +0 -1
- gpt-pr-0.1.0/requirements.txt +0 -34
- {gpt-pr-0.1.0 → gpt-pr-0.2.1}/MANIFEST.in +0 -0
- {gpt-pr-0.1.0 → gpt-pr-0.2.1}/gpt_pr.egg-info/dependency_links.txt +0 -0
- {gpt-pr-0.1.0 → gpt-pr-0.2.1}/gpt_pr.egg-info/entry_points.txt +0 -0
- {gpt-pr-0.1.0 → gpt-pr-0.2.1}/gpt_pr.egg-info/not-zip-safe +0 -0
- {gpt-pr-0.1.0 → gpt-pr-0.2.1}/gpt_pr.egg-info/top_level.txt +0 -0
- {gpt-pr-0.1.0 → gpt-pr-0.2.1}/gptpr/__init__.py +0 -0
- {gpt-pr-0.1.0 → gpt-pr-0.2.1}/gptpr/consolecolor.py +0 -0
- {gpt-pr-0.1.0 → gpt-pr-0.2.1}/setup.cfg +0 -0
|
@@ -26,7 +26,7 @@ Before getting started, make sure you have the following installed:
|
|
|
26
26
|
- Python 3.7 or higher
|
|
27
27
|
- [Pipenv](https://pipenv.pypa.io/en/latest/)
|
|
28
28
|
|
|
29
|
-
## Installation
|
|
29
|
+
## Installation
|
|
30
30
|
|
|
31
31
|
You can install and use GPT-PR in one of two ways. Choose the option that best suits your needs.
|
|
32
32
|
|
|
@@ -45,12 +45,11 @@ pip install -U gpt-pr
|
|
|
45
45
|
3. Inside the Git repository you are working on, ensure you have pushed your branch to origin, then run:
|
|
46
46
|
|
|
47
47
|
```bash
|
|
48
|
-
gpt-pr
|
|
48
|
+
gpt-pr --help
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
### Option 2: Cloning the code
|
|
52
52
|
|
|
53
|
-
|
|
54
53
|
1. Clone the repository:
|
|
55
54
|
|
|
56
55
|
```bash
|
|
@@ -67,7 +66,33 @@ pipenv install
|
|
|
67
66
|
After exporting api keys as environment variables ([Authentication & API Keys](#authentication--api-keys)), you can use GPT-PR within any git project directory. Suppose you've cloned **this project** to `~/workplace/gpt-pr`, here's how you can use it:
|
|
68
67
|
|
|
69
68
|
```bash
|
|
70
|
-
|
|
69
|
+
PYTHONPATH=~/workplace/gpt-pr/gpt-pr \
|
|
70
|
+
PIPENV_PIPFILE=~/workplace/gpt-pr/Pipfile \
|
|
71
|
+
pipenv run python ~/workplace/gpt-pr/gptpr/main.py --help
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Usage
|
|
75
|
+
|
|
76
|
+
### Generating Github Pull Requests
|
|
77
|
+
|
|
78
|
+
To create a Pull request from your current branch commits to merge with `main` branch, just run:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
gpt-pr
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
If you would like to compare with other base branch that is not `main`, just use `-b` param:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
gpt-pr -b my-other-branch
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Usage help
|
|
91
|
+
|
|
92
|
+
To show help commands:
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
gpt-pr -h
|
|
71
96
|
```
|
|
72
97
|
|
|
73
98
|
## Authentication & API Keys
|
|
@@ -113,5 +138,6 @@ Please follow our [CONTRIBUTING](./CONTRIBUTING.md) guide.
|
|
|
113
138
|
|
|
114
139
|
- [x] Improve execution method, possibly through a shell script or at least an alias in bash rc files.
|
|
115
140
|
- Change to use with pip installation and console_scripts entry point.
|
|
141
|
+
- [x] Fetch GitHub PR templates from the current project.
|
|
142
|
+
- [ ] Add configuration to set which LLM and model should be used (OpenAI GPT, Mistral, etc...)
|
|
116
143
|
- [ ] Add unit tests.
|
|
117
|
-
- [ ] Fetch GitHub PR templates from the current project.
|
|
@@ -11,9 +11,10 @@ gpt_pr.egg-info/not-zip-safe
|
|
|
11
11
|
gpt_pr.egg-info/requires.txt
|
|
12
12
|
gpt_pr.egg-info/top_level.txt
|
|
13
13
|
gptpr/__init__.py
|
|
14
|
-
gptpr/__version__.py
|
|
15
14
|
gptpr/consolecolor.py
|
|
16
15
|
gptpr/gh.py
|
|
17
16
|
gptpr/gitutil.py
|
|
18
17
|
gptpr/main.py
|
|
19
|
-
gptpr/prdata.py
|
|
18
|
+
gptpr/prdata.py
|
|
19
|
+
gptpr/test_prdata.py
|
|
20
|
+
gptpr/version.py
|
|
@@ -1,49 +1,59 @@
|
|
|
1
1
|
cffi==1.15.1
|
|
2
|
-
cryptography==
|
|
2
|
+
cryptography==42.0.5
|
|
3
|
+
fire==0.6.0
|
|
3
4
|
pycparser==2.21
|
|
4
|
-
wcwidth==0.2.
|
|
5
|
+
wcwidth==0.2.13
|
|
5
6
|
|
|
6
7
|
[:python_full_version >= "3.7.0"]
|
|
7
|
-
charset-normalizer==3.2
|
|
8
|
-
prompt-toolkit==3.0.
|
|
8
|
+
charset-normalizer==3.3.2
|
|
9
|
+
prompt-toolkit==3.0.43
|
|
9
10
|
|
|
10
11
|
[:python_full_version >= "3.7.1"]
|
|
11
|
-
openai==
|
|
12
|
+
openai==1.14.0
|
|
13
|
+
|
|
14
|
+
[:python_version < "3.11"]
|
|
15
|
+
exceptiongroup==1.2.0
|
|
12
16
|
|
|
13
17
|
[:python_version < "3.8"]
|
|
14
|
-
|
|
15
|
-
importlib-metadata==6.7.0
|
|
18
|
+
cached-property==1.5.2
|
|
16
19
|
typing-extensions==4.7.1
|
|
17
20
|
|
|
21
|
+
[:python_version == "3.7"]
|
|
22
|
+
importlib-metadata==6.7.0
|
|
23
|
+
|
|
24
|
+
[:python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2"]
|
|
25
|
+
six==1.16.0
|
|
26
|
+
|
|
18
27
|
[:python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3"]
|
|
19
28
|
deprecated==1.2.14
|
|
20
29
|
|
|
21
|
-
[:python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3, 3.4"]
|
|
22
|
-
wrapt==1.15.0
|
|
23
|
-
|
|
24
30
|
[:python_version >= "3.5"]
|
|
25
|
-
idna==3.
|
|
31
|
+
idna==3.6
|
|
26
32
|
|
|
27
33
|
[:python_version >= "3.6"]
|
|
28
|
-
|
|
29
|
-
|
|
34
|
+
certifi==2024.2.2
|
|
35
|
+
distro==1.9.0
|
|
30
36
|
pynacl==1.5.0
|
|
31
|
-
|
|
37
|
+
wrapt==1.16.0
|
|
32
38
|
|
|
33
39
|
[:python_version >= "3.7"]
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
annotated-types==0.5.0
|
|
41
|
+
anyio==3.7.1
|
|
42
|
+
gitdb==4.0.11
|
|
43
|
+
gitpython==3.1.42
|
|
44
|
+
h11==0.14.0
|
|
45
|
+
httpcore==0.17.3
|
|
46
|
+
httpx==0.24.1
|
|
47
|
+
pydantic==2.5.3
|
|
48
|
+
pydantic-core==2.14.6
|
|
49
|
+
pygithub==2.2.0
|
|
42
50
|
pyjwt[crypto]==2.8.0
|
|
43
51
|
requests==2.31.0
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
52
|
+
smmap==5.0.1
|
|
53
|
+
sniffio==1.3.1
|
|
54
|
+
termcolor==2.3.0
|
|
55
|
+
tqdm==4.66.2
|
|
56
|
+
urllib3==2.0.7
|
|
47
57
|
zipp==3.15.0
|
|
48
58
|
|
|
49
59
|
[:python_version >= "3.7" and python_version < "4.0"]
|
|
@@ -11,16 +11,17 @@ if not GH_TOKEN:
|
|
|
11
11
|
gh = Github(GH_TOKEN)
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
def create_pr(pr_data):
|
|
14
|
+
def create_pr(pr_data, yield_confirmation):
|
|
15
15
|
repo = gh.get_repo(
|
|
16
16
|
f'{pr_data.branch_info.owner}/{pr_data.branch_info.repo}')
|
|
17
17
|
|
|
18
|
-
pr_confirmation = inquirer.confirm(
|
|
19
|
-
|
|
18
|
+
pr_confirmation = yield_confirmation or inquirer.confirm(
|
|
19
|
+
message="Create GitHub PR?",
|
|
20
|
+
default=True).execute()
|
|
20
21
|
|
|
21
22
|
if pr_confirmation:
|
|
22
23
|
pr = repo.create_pull(title=pr_data.title, body=pr_data.body,
|
|
23
|
-
head=pr_data.branch_info.branch, base=
|
|
24
|
+
head=pr_data.branch_info.branch, base=pr_data.branch_info.base_branch)
|
|
24
25
|
print("Pull request created successfully: ", pr.html_url)
|
|
25
26
|
else:
|
|
26
27
|
print('cancelling...')
|
|
@@ -10,9 +10,10 @@ from InquirerPy.base.control import Choice
|
|
|
10
10
|
class BranchInfo:
|
|
11
11
|
owner: str
|
|
12
12
|
repo: str
|
|
13
|
+
base_branch: str
|
|
13
14
|
branch: str
|
|
14
15
|
commits: list
|
|
15
|
-
|
|
16
|
+
highlight_commits: list
|
|
16
17
|
diff: str
|
|
17
18
|
|
|
18
19
|
|
|
@@ -31,7 +32,7 @@ class FileChange:
|
|
|
31
32
|
return f'{self.file_path} (+{(self.lines_added)} -{self.lines_removed})'
|
|
32
33
|
|
|
33
34
|
|
|
34
|
-
def get_branch_info():
|
|
35
|
+
def get_branch_info(base_branch, yield_confirmation):
|
|
35
36
|
# Get current directory
|
|
36
37
|
current_dir = os.getcwd()
|
|
37
38
|
|
|
@@ -53,60 +54,75 @@ def get_branch_info():
|
|
|
53
54
|
else:
|
|
54
55
|
raise Exception('Not currently on any branch.')
|
|
55
56
|
|
|
57
|
+
if not _branch_exists(repo, base_branch):
|
|
58
|
+
raise Exception(f'Base branch {base_branch} does not exist.')
|
|
59
|
+
|
|
56
60
|
owner, repo_name = _get_remote_info(repo)
|
|
57
61
|
|
|
58
|
-
commits =
|
|
59
|
-
commits = _get_valid_commits(commits)
|
|
62
|
+
commits = _get_diff_messages_against_base_branch(repo, current_branch.name, base_branch)
|
|
63
|
+
commits = _get_valid_commits(commits, yield_confirmation)
|
|
60
64
|
|
|
61
65
|
if not commits:
|
|
62
66
|
print('No commit changes detected.')
|
|
63
67
|
return None
|
|
64
68
|
|
|
65
|
-
|
|
69
|
+
highlight_commits = _get_highlight_commits(commits, yield_confirmation)
|
|
66
70
|
|
|
67
71
|
return BranchInfo(
|
|
68
72
|
owner=owner,
|
|
69
73
|
repo=repo_name,
|
|
74
|
+
base_branch=base_branch,
|
|
70
75
|
branch=current_branch.name,
|
|
71
76
|
commits=commits,
|
|
72
|
-
|
|
73
|
-
diff=_get_diff_changes(repo, current_branch.name)
|
|
77
|
+
highlight_commits=highlight_commits,
|
|
78
|
+
diff=_get_diff_changes(repo, base_branch, current_branch.name, yield_confirmation)
|
|
74
79
|
)
|
|
75
80
|
|
|
76
81
|
|
|
77
|
-
def
|
|
78
|
-
|
|
79
|
-
|
|
82
|
+
def _branch_exists(repo, branch_name):
|
|
83
|
+
if branch_name in repo.branches:
|
|
84
|
+
return True
|
|
85
|
+
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _get_diff_messages_against_base_branch(repo, branch, base_branch):
|
|
90
|
+
# Get commit messages that are in the current branch but not in the base branch
|
|
91
|
+
commits_diff = list(repo.iter_commits(f'{base_branch}..{branch}'))
|
|
80
92
|
|
|
81
93
|
return [commit.message.strip('\n') for commit in commits_diff]
|
|
82
94
|
|
|
83
95
|
|
|
84
|
-
def _get_valid_commits(commits):
|
|
96
|
+
def _get_valid_commits(commits, yield_confirmation):
|
|
85
97
|
if not commits:
|
|
86
98
|
return commits
|
|
87
99
|
|
|
88
100
|
options = [Choice(value=commit, name=commit) for commit in commits]
|
|
89
101
|
|
|
90
|
-
commits_to_ignore =
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
102
|
+
commits_to_ignore = []
|
|
103
|
+
if not yield_confirmation:
|
|
104
|
+
commits_to_ignore = inquirer.checkbox(
|
|
105
|
+
message='Pick commits that should be IGNORED (optional)\':',
|
|
106
|
+
choices=options,
|
|
107
|
+
instruction="(Press <space> to select, <enter> to confirm)",
|
|
108
|
+
).execute()
|
|
95
109
|
|
|
96
110
|
return [commit for commit in commits if commit not in commits_to_ignore]
|
|
97
111
|
|
|
98
112
|
|
|
99
|
-
|
|
100
|
-
|
|
113
|
+
def _get_highlight_commits(commits, yield_confirmation):
|
|
114
|
+
if yield_confirmation or len(commits) <= 1:
|
|
115
|
+
return []
|
|
116
|
+
|
|
101
117
|
options = [Choice(value=commit, name=commit) for commit in commits]
|
|
102
118
|
|
|
103
|
-
|
|
119
|
+
highlight_commits = inquirer.checkbox(
|
|
104
120
|
message='Pick commits to highlight in description (optional)\':',
|
|
105
121
|
choices=options,
|
|
106
122
|
instruction="(Press <space> to select, <enter> to confirm)",
|
|
107
123
|
).execute()
|
|
108
124
|
|
|
109
|
-
return
|
|
125
|
+
return highlight_commits
|
|
110
126
|
|
|
111
127
|
|
|
112
128
|
def _get_remote_info(repo):
|
|
@@ -133,33 +149,33 @@ def _extract_owner_and_repo(repo_url):
|
|
|
133
149
|
return owner, '.'.join(repo_info.split('.')[:-1])
|
|
134
150
|
|
|
135
151
|
|
|
136
|
-
def _get_diff_changes(repo, branch):
|
|
152
|
+
def _get_diff_changes(repo, base_branch, branch, yield_confirmation):
|
|
137
153
|
diff_changes = []
|
|
138
154
|
|
|
139
|
-
stats = _get_stats(repo, branch)
|
|
140
|
-
files_to_ignore = _get_files_to_ignore(stats)
|
|
155
|
+
stats = _get_stats(repo, base_branch, branch)
|
|
156
|
+
files_to_ignore = _get_files_to_ignore(stats, yield_confirmation)
|
|
141
157
|
|
|
142
158
|
for file_change in stats:
|
|
143
159
|
if file_change.file_path in files_to_ignore:
|
|
144
160
|
continue
|
|
145
161
|
|
|
146
|
-
file_diff = repo.git.diff(
|
|
162
|
+
file_diff = repo.git.diff(base_branch, branch, '--', file_change.file_path)
|
|
147
163
|
|
|
148
164
|
diff_changes.append(file_diff)
|
|
149
165
|
|
|
150
166
|
return '\n'.join(diff_changes)
|
|
151
167
|
|
|
152
168
|
|
|
153
|
-
def _get_stats(repo, branch):
|
|
169
|
+
def _get_stats(repo, base_branch, branch):
|
|
154
170
|
'''
|
|
155
|
-
Get the stats of the difference between the current branch and the
|
|
171
|
+
Get the stats of the difference between the current branch and the base branch
|
|
156
172
|
'''
|
|
157
173
|
|
|
158
174
|
# returns:
|
|
159
175
|
# 4 0 README.md
|
|
160
176
|
# 2 0 application/aggregator/aggregator.go
|
|
161
177
|
# 0 257 go.sum
|
|
162
|
-
diff_index = repo.git.diff(
|
|
178
|
+
diff_index = repo.git.diff(base_branch, branch, '--numstat')
|
|
163
179
|
|
|
164
180
|
files_changed = []
|
|
165
181
|
for line in diff_index.split('\n'):
|
|
@@ -176,11 +192,14 @@ def _get_stats(repo, branch):
|
|
|
176
192
|
return files_changed
|
|
177
193
|
|
|
178
194
|
|
|
179
|
-
def _get_files_to_ignore(stats):
|
|
195
|
+
def _get_files_to_ignore(stats, yield_confirmation):
|
|
180
196
|
'''
|
|
181
197
|
Get the files that should be ignored from the stats
|
|
182
198
|
'''
|
|
183
199
|
|
|
200
|
+
if yield_confirmation:
|
|
201
|
+
return []
|
|
202
|
+
|
|
184
203
|
options = [Choice(stats.file_path, name=stats.desc) for stats in stats]
|
|
185
204
|
|
|
186
205
|
files_to_ignore = inquirer.checkbox(
|
|
@@ -1,12 +1,22 @@
|
|
|
1
|
+
import fire
|
|
1
2
|
from InquirerPy import inquirer
|
|
2
3
|
|
|
3
4
|
from gptpr.gitutil import get_branch_info
|
|
4
5
|
from gptpr.gh import create_pr
|
|
5
6
|
from gptpr.prdata import get_pr_data
|
|
7
|
+
from gptpr.version import __version__
|
|
6
8
|
|
|
7
9
|
|
|
8
|
-
def main
|
|
9
|
-
|
|
10
|
+
def run(base_branch='main', yield_confirmation=False, version=False):
|
|
11
|
+
'''
|
|
12
|
+
Create Pull Requests from current branch with base branch (default 'main' branch)
|
|
13
|
+
'''
|
|
14
|
+
|
|
15
|
+
if version:
|
|
16
|
+
print('Current version:', __version__)
|
|
17
|
+
return
|
|
18
|
+
|
|
19
|
+
branch_info = get_branch_info(base_branch, yield_confirmation)
|
|
10
20
|
|
|
11
21
|
if not branch_info:
|
|
12
22
|
exit(0)
|
|
@@ -20,13 +30,22 @@ def main():
|
|
|
20
30
|
print(pr_data.to_display())
|
|
21
31
|
print('#########################################')
|
|
22
32
|
print('')
|
|
33
|
+
|
|
34
|
+
if yield_confirmation:
|
|
35
|
+
break
|
|
36
|
+
|
|
23
37
|
generate_pr_data = not inquirer.confirm(
|
|
24
38
|
message="Create PR with this? If 'no', let's try again...",
|
|
25
39
|
default=True).execute()
|
|
40
|
+
|
|
26
41
|
if generate_pr_data:
|
|
27
42
|
print('Generating another PR data...')
|
|
28
43
|
|
|
29
|
-
create_pr(pr_data)
|
|
44
|
+
create_pr(pr_data, yield_confirmation)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def main():
|
|
48
|
+
fire.Fire(run)
|
|
30
49
|
|
|
31
50
|
|
|
32
51
|
if __name__ == '__main__':
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
|
-
import pprint
|
|
3
2
|
import json
|
|
4
3
|
import os
|
|
5
|
-
import
|
|
4
|
+
from openai import OpenAI
|
|
6
5
|
|
|
7
6
|
from gptpr.gitutil import BranchInfo
|
|
8
7
|
import gptpr.consolecolor as cc
|
|
@@ -10,20 +9,12 @@ import gptpr.consolecolor as cc
|
|
|
10
9
|
TOKENIZER_RATIO = 4
|
|
11
10
|
MAX_TOKENS = 6000
|
|
12
11
|
|
|
13
|
-
OPENAI_API_KEY = os.environ.get('OPENAI_API_KEY')
|
|
14
|
-
|
|
15
|
-
if not OPENAI_API_KEY:
|
|
16
|
-
print("Please set OPENAI_API_KEY environment variable.")
|
|
17
|
-
exit(1)
|
|
18
|
-
|
|
19
|
-
openai.api_key = OPENAI_API_KEY
|
|
20
|
-
|
|
21
12
|
DEFAULT_PR_TEMPLATE = ('### Ref. [Link]\n\n## What was done?\n[Fill here]\n\n'
|
|
22
13
|
'## How was it done?\n[Fill here]\n\n'
|
|
23
14
|
'## How was it tested?\n[Fill here with test information from diff content or commits]')
|
|
24
15
|
|
|
25
16
|
|
|
26
|
-
def
|
|
17
|
+
def _get_pr_template():
|
|
27
18
|
pr_template = DEFAULT_PR_TEMPLATE
|
|
28
19
|
|
|
29
20
|
try:
|
|
@@ -57,7 +48,7 @@ class PrData():
|
|
|
57
48
|
f'{cc.bold("Repository")}: {cc.yellow(self.branch_info.owner)}/{cc.yellow(self.branch_info.repo)}',
|
|
58
49
|
f'{cc.bold("Title")}: {cc.yellow(self.title)}',
|
|
59
50
|
f'{cc.bold("Branch name")}: {cc.yellow(self.branch_info.branch)}',
|
|
60
|
-
f'{cc.bold("Base branch")}: {cc.yellow(
|
|
51
|
+
f'{cc.bold("Base branch")}: {cc.yellow(self.branch_info.base_branch)}',
|
|
61
52
|
f'{cc.bold("PR Description")}:\n{self.body}',
|
|
62
53
|
])
|
|
63
54
|
|
|
@@ -85,25 +76,26 @@ functions = [
|
|
|
85
76
|
|
|
86
77
|
|
|
87
78
|
def get_pr_data(branch_info):
|
|
88
|
-
system_content = ''
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
79
|
+
system_content = ('You are a development assistant designed to craft Git pull requests '
|
|
80
|
+
'by incorporating information from main and secondary commits, diff changes, '
|
|
81
|
+
'and adhering to a provided PR template. Your output includes a complete PR '
|
|
82
|
+
'template with all necessary details and a suitable PR title. In the '
|
|
83
|
+
'PR description, detail the work accomplished, the methodology employed, '
|
|
84
|
+
'including testing procedures, and list significant changes in bullet points '
|
|
85
|
+
'if they are extensive. Avoid incorporating diff content directly into '
|
|
86
|
+
'the PR description.')
|
|
95
87
|
|
|
96
88
|
messages = [
|
|
97
89
|
{'role': 'system', 'content': system_content},
|
|
98
90
|
]
|
|
99
91
|
|
|
100
|
-
if len(branch_info.
|
|
101
|
-
messages.append({'role': 'user', 'content': 'main commits: ' + '\n'.join(branch_info.
|
|
92
|
+
if len(branch_info.highlight_commits) > 0:
|
|
93
|
+
messages.append({'role': 'user', 'content': 'main commits: ' + '\n'.join(branch_info.highlight_commits)})
|
|
102
94
|
messages.append({'role': 'user', 'content': 'secondary commits: ' + '\n'.join(branch_info.commits)})
|
|
103
95
|
else:
|
|
104
96
|
messages.append({'role': 'user', 'content': 'git commits: ' + '\n'.join(branch_info.commits)})
|
|
105
97
|
|
|
106
|
-
messages.append({'role': 'user', 'content': 'PR template:\n' +
|
|
98
|
+
messages.append({'role': 'user', 'content': 'PR template:\n' + _get_pr_template()})
|
|
107
99
|
|
|
108
100
|
current_total_length = sum([len(m['content']) for m in messages])
|
|
109
101
|
|
|
@@ -116,9 +108,17 @@ def get_pr_data(branch_info):
|
|
|
116
108
|
else:
|
|
117
109
|
messages.append({'role': 'user', 'content': 'Diff changes:\n' + branch_info.diff})
|
|
118
110
|
|
|
119
|
-
|
|
120
|
-
|
|
111
|
+
openai_api_key = os.environ.get('OPENAI_API_KEY')
|
|
112
|
+
|
|
113
|
+
if not openai_api_key:
|
|
114
|
+
print("Please set OPENAI_API_KEY environment variable.")
|
|
115
|
+
exit(1)
|
|
116
|
+
|
|
117
|
+
client = OpenAI(api_key=openai_api_key)
|
|
118
|
+
|
|
119
|
+
chat_completion = client.chat.completions.create(
|
|
121
120
|
messages=messages,
|
|
121
|
+
model='gpt-4-0613',
|
|
122
122
|
functions=functions,
|
|
123
123
|
function_call={'name': 'create_pr'},
|
|
124
124
|
temperature=0,
|
|
@@ -128,16 +128,34 @@ def get_pr_data(branch_info):
|
|
|
128
128
|
presence_penalty=0
|
|
129
129
|
)
|
|
130
130
|
|
|
131
|
-
|
|
132
|
-
arguments = json.loads(response.choices[0].message.function_call.arguments)
|
|
133
|
-
except Exception as e:
|
|
134
|
-
print('Error to decode message:', e)
|
|
135
|
-
print('Response message')
|
|
136
|
-
pprint.pprint(response.choices[0].message)
|
|
137
|
-
raise e
|
|
131
|
+
arguments = _parse_json(chat_completion.choices[0].message.function_call.arguments)
|
|
138
132
|
|
|
139
133
|
return PrData(
|
|
140
134
|
branch_info=branch_info,
|
|
141
135
|
title=arguments['title'],
|
|
142
136
|
body=arguments['description']
|
|
143
137
|
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _parse_json(content):
|
|
141
|
+
'''
|
|
142
|
+
A bit of a hack to parse the json content from the chat completion
|
|
143
|
+
Sometimes it returns a string with invalid json content (line breaks) that
|
|
144
|
+
makes it hard to parse.
|
|
145
|
+
example:
|
|
146
|
+
|
|
147
|
+
content = '{\n"title": "feat(dependencies): pin dependencies versions",\n"description":
|
|
148
|
+
"### Ref. [Link]\n\n## What was done? ..."\n}'
|
|
149
|
+
'''
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
content = content.replace('{\n"title":', '{"title":')
|
|
153
|
+
content = content.replace(',\n"description":', ',"description":')
|
|
154
|
+
content = content.replace('\n}', '}')
|
|
155
|
+
content = content.replace('\n', '\\n')
|
|
156
|
+
|
|
157
|
+
return json.loads(content)
|
|
158
|
+
except Exception as e:
|
|
159
|
+
print('Error to decode message:', e)
|
|
160
|
+
print('Content:', content)
|
|
161
|
+
raise e
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from gptpr.prdata import _parse_json
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_parse_json():
|
|
5
|
+
content = ('{\n"title": "feat(dependencies): pin dependencies '
|
|
6
|
+
'versions",\n"description": "### Ref. [Link]\n\n## What was done? ..."\n}')
|
|
7
|
+
|
|
8
|
+
expected = {
|
|
9
|
+
'title': 'feat(dependencies): pin dependencies versions',
|
|
10
|
+
'description': '### Ref. [Link]\n\n## What was done? ...'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
assert _parse_json(content) == expected, "The function did not return the expected output."
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.1"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
-i https://pypi.org/simple
|
|
2
|
+
annotated-types==0.5.0; python_version >= '3.7'
|
|
3
|
+
anyio==3.7.1; python_version >= '3.7'
|
|
4
|
+
cached-property==1.5.2; python_version < '3.8'
|
|
5
|
+
certifi==2024.2.2; python_version >= '3.6'
|
|
6
|
+
cffi==1.15.1
|
|
7
|
+
charset-normalizer==3.3.2; python_full_version >= '3.7.0'
|
|
8
|
+
cryptography==42.0.5
|
|
9
|
+
deprecated==1.2.14; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
|
10
|
+
distro==1.9.0; python_version >= '3.6'
|
|
11
|
+
exceptiongroup==1.2.0; python_version < '3.11'
|
|
12
|
+
fire==0.6.0
|
|
13
|
+
gitdb==4.0.11; python_version >= '3.7'
|
|
14
|
+
gitpython==3.1.42; python_version >= '3.7'
|
|
15
|
+
h11==0.14.0; python_version >= '3.7'
|
|
16
|
+
httpcore==0.17.3; python_version >= '3.7'
|
|
17
|
+
httpx==0.24.1; python_version >= '3.7'
|
|
18
|
+
idna==3.6; python_version >= '3.5'
|
|
19
|
+
importlib-metadata==6.7.0; python_version == '3.7'
|
|
20
|
+
inquirerpy==0.3.4; python_version >= '3.7' and python_version < '4.0'
|
|
21
|
+
openai==1.14.0; python_full_version >= '3.7.1'
|
|
22
|
+
pfzy==0.3.4; python_version >= '3.7' and python_version < '4.0'
|
|
23
|
+
prompt-toolkit==3.0.43; python_full_version >= '3.7.0'
|
|
24
|
+
pycparser==2.21
|
|
25
|
+
pydantic==2.5.3; python_version >= '3.7'
|
|
26
|
+
pydantic-core==2.14.6; python_version >= '3.7'
|
|
27
|
+
pygithub==2.2.0; python_version >= '3.7'
|
|
28
|
+
pyjwt[crypto]==2.8.0; python_version >= '3.7'
|
|
29
|
+
pynacl==1.5.0; python_version >= '3.6'
|
|
30
|
+
requests==2.31.0; python_version >= '3.7'
|
|
31
|
+
six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'
|
|
32
|
+
smmap==5.0.1; python_version >= '3.7'
|
|
33
|
+
sniffio==1.3.1; python_version >= '3.7'
|
|
34
|
+
termcolor==2.3.0; python_version >= '3.7'
|
|
35
|
+
tqdm==4.66.2; python_version >= '3.7'
|
|
36
|
+
typing-extensions==4.7.1; python_version < '3.8'
|
|
37
|
+
urllib3==2.0.7; python_version >= '3.7'
|
|
38
|
+
wcwidth==0.2.13
|
|
39
|
+
wrapt==1.16.0; python_version >= '3.6'
|
|
40
|
+
zipp==3.15.0; python_version >= '3.7'
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import os
|
|
1
2
|
from setuptools import setup, find_packages
|
|
2
3
|
|
|
3
4
|
|
|
@@ -16,14 +17,17 @@ def get_requirements():
|
|
|
16
17
|
return dependencies
|
|
17
18
|
|
|
18
19
|
|
|
19
|
-
version =
|
|
20
|
+
version = os.environ.get('VERSION')
|
|
20
21
|
if not version:
|
|
21
|
-
|
|
22
|
-
with open('./gptpr/__version__.py') as f:
|
|
23
|
-
exec(f.read(), version_package_data)
|
|
22
|
+
raise Exception('VERSION environment variable not set')
|
|
24
23
|
|
|
25
|
-
version = version_package_data['__version__']
|
|
26
24
|
|
|
25
|
+
def write_version(current_version):
|
|
26
|
+
with open('gptpr/version.py', 'w') as f:
|
|
27
|
+
f.write(f'__version__ = "{current_version}"\n')
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
write_version(version)
|
|
27
31
|
|
|
28
32
|
setup(name='gpt-pr',
|
|
29
33
|
version=version,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = '0.1.0'
|
gpt-pr-0.1.0/requirements.txt
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
-i https://pypi.org/simple
|
|
2
|
-
aiohttp==3.8.5; python_version >= '3.6'
|
|
3
|
-
aiosignal==1.3.1; python_version >= '3.7'
|
|
4
|
-
async-timeout==4.0.3; python_version >= '3.7'
|
|
5
|
-
asynctest==0.13.0; python_version < '3.8'
|
|
6
|
-
attrs==23.1.0; python_version >= '3.7'
|
|
7
|
-
certifi==2023.7.22; python_version >= '3.6'
|
|
8
|
-
cffi==1.15.1
|
|
9
|
-
charset-normalizer==3.2.0; python_full_version >= '3.7.0'
|
|
10
|
-
cryptography==41.0.3
|
|
11
|
-
deprecated==1.2.14; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
|
12
|
-
frozenlist==1.3.3; python_version >= '3.7'
|
|
13
|
-
gitdb==4.0.10; python_version >= '3.7'
|
|
14
|
-
gitpython==3.1.35; python_version >= '3.7'
|
|
15
|
-
idna==3.4; python_version >= '3.5'
|
|
16
|
-
importlib-metadata==6.7.0; python_version < '3.8'
|
|
17
|
-
inquirerpy==0.3.4; python_version >= '3.7' and python_version < '4.0'
|
|
18
|
-
multidict==6.0.4; python_version >= '3.7'
|
|
19
|
-
openai==0.28.0; python_full_version >= '3.7.1'
|
|
20
|
-
pfzy==0.3.4; python_version >= '3.7' and python_version < '4.0'
|
|
21
|
-
prompt-toolkit==3.0.39; python_full_version >= '3.7.0'
|
|
22
|
-
pycparser==2.21
|
|
23
|
-
pygithub==1.59.1; python_version >= '3.7'
|
|
24
|
-
pyjwt[crypto]==2.8.0; python_version >= '3.7'
|
|
25
|
-
pynacl==1.5.0; python_version >= '3.6'
|
|
26
|
-
requests==2.31.0; python_version >= '3.7'
|
|
27
|
-
smmap==5.0.0; python_version >= '3.6'
|
|
28
|
-
tqdm==4.66.1; python_version >= '3.7'
|
|
29
|
-
typing-extensions==4.7.1; python_version < '3.8'
|
|
30
|
-
urllib3==2.0.4; python_version >= '3.7'
|
|
31
|
-
wcwidth==0.2.6
|
|
32
|
-
wrapt==1.15.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
|
|
33
|
-
yarl==1.9.2; python_version >= '3.7'
|
|
34
|
-
zipp==3.15.0; python_version >= '3.7'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|