Skip to content

Commit 29c5075

Browse files
committed
implementing more feedback, choosing a different Bitbucket diff strategy depending on API version, and expanded unit test cases
1 parent 0442cdc commit 29c5075

File tree

3 files changed

+129
-19
lines changed

3 files changed

+129
-19
lines changed

pr_agent/git_providers/bitbucket_server_provider.py

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
from distutils.version import LooseVersion
2+
from requests.exceptions import HTTPError
13
from typing import Optional, Tuple
24
from urllib.parse import quote_plus, urlparse
35

4-
from requests.exceptions import HTTPError
56
from atlassian.bitbucket import Bitbucket
6-
from starlette_context import context
77

88
from .git_provider import GitProvider
99
from ..algo.types import EDIT_TYPE, FilePatchInfo
@@ -34,6 +34,10 @@ def __init__(
3434
self.bitbucket_client = bitbucket_client or Bitbucket(url=self.bitbucket_server_url,
3535
token=get_settings().get("BITBUCKET_SERVER.BEARER_TOKEN",
3636
None))
37+
try:
38+
self.bitbucket_api_version = LooseVersion(self.bitbucket_client.get("rest/api/1.0/application-properties").get('version'))
39+
except Exception:
40+
self.bitbucket_api_version = None
3741

3842
if pr_url:
3943
self.set_pr(pr_url)
@@ -152,14 +156,34 @@ def get_diff_files(self) -> list[FilePatchInfo]:
152156
if self.diff_files:
153157
return self.diff_files
154158

155-
source_commits_list = list(self.bitbucket_client.get_pull_requests_commits(self.workspace_slug, self.repo_slug, self.pr_num))
156-
guaranteed_common_ancestor = source_commits_list[-1]['parents'][0]['id']
157-
destination_commits = list(self.bitbucket_client.get_commits(self.workspace_slug, self.repo_slug, guaranteed_common_ancestor, self.pr.toRef['latestCommit']))
158-
159-
base_sha = self.pr.toRef['latestCommit']
160-
head_sha = self.pr.fromRef['latestCommit']
161-
if not get_settings().bitbucket_server.get("legacy_diff_calculation", False):
162-
base_sha = self.get_best_common_ancestor(source_commits_list, destination_commits, guaranteed_common_ancestor)
159+
source_commits_list = list(self.bitbucket_client.get_pull_requests_commits(
160+
self.workspace_slug,
161+
self.repo_slug,
162+
self.pr_num
163+
))
164+
165+
# defaults to basic diff functionality with a guaranteed common ancestor
166+
base_sha, head_sha = source_commits_list[-1]['parents'][0]['id'], source_commits_list[0]['id']
167+
168+
# if Bitbucket api version is greater than or equal to 7.0 then use 2-way diff functionality for the base_sha
169+
if self.bitbucket_api_version is not None and self.bitbucket_api_version >= LooseVersion("7.0"):
170+
# Bitbucket endpoint for getting merge-base is available as of 8.16
171+
if self.bitbucket_api_version >= LooseVersion("8.16"):
172+
try:
173+
base_sha = self.bitbucket_client.get(self._get_best_common_ancestor())['id']
174+
except Exception as e:
175+
get_logger().error(f"Failed to get the best common ancestor for PR: {self.pr_url}, \nerror: {e}")
176+
raise e
177+
# for versions 7.0-8.15 try to calculate the merge-base on our own
178+
else:
179+
try:
180+
destination_commits = list(
181+
self.bitbucket_client.get_commits(self.workspace_slug, self.repo_slug, base_sha,
182+
self.pr.toRef['latestCommit']))
183+
base_sha = self.get_best_common_ancestor(source_commits_list, destination_commits, base_sha)
184+
except Exception as e:
185+
get_logger().error(f"Failed to get the commit list for calculating best common ancestor for PR: {self.pr_url}, \nerror: {e}")
186+
raise e
163187

164188
diff_files = []
165189
original_file_content_str = ""
@@ -427,3 +451,6 @@ def get_pr_labels(self, update=False):
427451

428452
def _get_pr_comments_path(self):
429453
return f"rest/api/latest/projects/{self.workspace_slug}/repos/{self.repo_slug}/pull-requests/{self.pr_num}/comments"
454+
455+
def _get_best_common_ancestor(self):
456+
return f"rest/api/latest/projects/{self.workspace_slug}/repos/{self.repo_slug}/pull-requests/{self.pr_num}/merge-base"

pr_agent/settings/configuration.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,6 @@ pr_commands = [
263263
"/review --pr_reviewer.num_code_suggestions=0",
264264
"/improve --pr_code_suggestions.commitable_code_suggestions=true --pr_code_suggestions.suggestions_score_threshold=7",
265265
]
266-
legacy_diff_calculation = false
267266

268267
[litellm]
269268
# use_client = false

tests/unittest/test_bitbucket_provider.py

Lines changed: 92 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,39 @@ def mock_get_content_of_file(self, project_key, repository_slug, filename, at=No
3030
'cb68a3027d6dda065a7692ebf2c90bed1bcdec28': 'file\nwith\nsome\nchanges\nto\nemulate\na\nreal\nfile\n',
3131
'1905dcf16c0aac6ac24f7ab617ad09c73dc1d23b': 'file\nwith\nsome\nlines\nto\nemulate\na\nfake\ntest\n',
3232
'ae4eca7f222c96d396927d48ab7538e2ee13ca63': 'readme\nwithout\nsome\nlines\nto\nsimulate\na\nreal\nfile',
33-
'548f8ba15abc30875a082156314426806c3f4d97': 'file\nwith\nsome\nlines\nto\nemulate\na\nreal\nfile'
33+
'548f8ba15abc30875a082156314426806c3f4d97': 'file\nwith\nsome\nlines\nto\nemulate\na\nreal\nfile',
34+
'0e898cb355a5170d8c8771b25d43fcaa1d2d9489': 'file\nwith\nmultiple\nlines\nto\nemulate\na\nreal\nfile'
3435
}
35-
3636
return content_map.get(at, '')
3737

38+
def mock_get_from_bitbucket_60(self, url):
39+
response_map = {
40+
"rest/api/1.0/application-properties": {
41+
"version": "6.0"
42+
}
43+
}
44+
return response_map.get(url, '')
45+
46+
def mock_get_from_bitbucket_70(self, url):
47+
response_map = {
48+
"rest/api/1.0/application-properties": {
49+
"version": "7.0"
50+
}
51+
}
52+
return response_map.get(url, '')
53+
54+
def mock_get_from_bitbucket_816(self, url):
55+
response_map = {
56+
"rest/api/1.0/application-properties": {
57+
"version": "8.16"
58+
},
59+
"rest/api/latest/projects/AAA/repos/my-repo/pull-requests/1/merge-base": {
60+
'id': '548f8ba15abc30875a082156314426806c3f4d97'
61+
}
62+
}
63+
return response_map.get(url, '')
64+
65+
3866
'''
3967
tests the 2-way diff functionality where the diff should be between the HEAD of branch b and node c
4068
NOT between the HEAD of main and the HEAD of branch b
@@ -44,8 +72,7 @@ def mock_get_content_of_file(self, project_key, repository_slug, filename, at=No
4472
o - o - o main
4573
^ node c
4674
'''
47-
48-
def test_get_diff_files_simple_diverge(self):
75+
def test_get_diff_files_simple_diverge_70(self):
4976
bitbucket_client = MagicMock(Bitbucket)
5077
bitbucket_client.get_pull_request.return_value = {
5178
'toRef': {'latestCommit': '9c1cffdd9f276074bfb6fb3b70fbee62d298b058'},
@@ -66,6 +93,7 @@ def test_get_diff_files_simple_diverge(self):
6693
}
6794
]
6895

96+
bitbucket_client.get.side_effect = self.mock_get_from_bitbucket_70
6997
bitbucket_client.get_content_of_file.side_effect = self.mock_get_content_of_file
7098

7199
provider = BitbucketServerProvider(
@@ -87,6 +115,7 @@ def test_get_diff_files_simple_diverge(self):
87115

88116
assert actual == expected
89117

118+
90119
'''
91120
tests the 2-way diff functionality where the diff should be between the HEAD of branch b and node c
92121
NOT between the HEAD of main and the HEAD of branch b
@@ -96,8 +125,7 @@ def test_get_diff_files_simple_diverge(self):
96125
o - o -- o - o main
97126
^ node c
98127
'''
99-
100-
def test_get_diff_files_diverge_with_merge_commit(self):
128+
def test_get_diff_files_diverge_with_merge_commit_70(self):
101129
bitbucket_client = MagicMock(Bitbucket)
102130
bitbucket_client.get_pull_request.return_value = {
103131
'toRef': {'latestCommit': 'cb68a3027d6dda065a7692ebf2c90bed1bcdec28'},
@@ -125,6 +153,7 @@ def test_get_diff_files_diverge_with_merge_commit(self):
125153
}
126154
]
127155

156+
bitbucket_client.get.side_effect = self.mock_get_from_bitbucket_70
128157
bitbucket_client.get_content_of_file.side_effect = self.mock_get_content_of_file
129158

130159
provider = BitbucketServerProvider(
@@ -146,6 +175,7 @@ def test_get_diff_files_diverge_with_merge_commit(self):
146175

147176
assert actual == expected
148177

178+
149179
'''
150180
tests the 2-way diff functionality where the diff should be between the HEAD of branch c and node d
151181
NOT between the HEAD of main and the HEAD of branch c
@@ -157,8 +187,7 @@ def test_get_diff_files_diverge_with_merge_commit(self):
157187
o - o - o main
158188
^ node d
159189
'''
160-
161-
def test_get_diff_files_multi_merge_diverge(self):
190+
def get_multi_merge_diverge_mock_client(self, api_version):
162191
bitbucket_client = MagicMock(Bitbucket)
163192
bitbucket_client.get_pull_request.return_value = {
164193
'toRef': {'latestCommit': '9569922b22fe4fd0968be6a50ed99f71efcd0504'},
@@ -192,6 +221,39 @@ def test_get_diff_files_multi_merge_diverge(self):
192221
]
193222

194223
bitbucket_client.get_content_of_file.side_effect = self.mock_get_content_of_file
224+
if api_version == 60:
225+
bitbucket_client.get.side_effect = self.mock_get_from_bitbucket_60
226+
elif api_version == 70:
227+
bitbucket_client.get.side_effect = self.mock_get_from_bitbucket_70
228+
elif api_version == 816:
229+
bitbucket_client.get.side_effect = self.mock_get_from_bitbucket_816
230+
231+
return bitbucket_client
232+
233+
def test_get_diff_files_multi_merge_diverge_60(self):
234+
bitbucket_client = self.get_multi_merge_diverge_mock_client(60)
235+
236+
provider = BitbucketServerProvider(
237+
"https://git.onpreminstance.com/projects/AAA/repos/my-repo/pull-requests/1",
238+
bitbucket_client=bitbucket_client
239+
)
240+
241+
expected = [
242+
FilePatchInfo(
243+
'file\nwith\nmultiple\nlines\nto\nemulate\na\nreal\nfile',
244+
'readme\nwithout\nsome\nlines\nto\nsimulate\na\nreal\nfile',
245+
'--- \n+++ \n@@ -1,9 +1,9 @@\n-file\n-with\n-multiple\n+readme\n+without\n+some\n lines\n to\n-emulate\n+simulate\n a\n real\n file',
246+
'Readme.md',
247+
edit_type=EDIT_TYPE.MODIFIED,
248+
)
249+
]
250+
251+
actual = provider.get_diff_files()
252+
253+
assert actual == expected
254+
255+
def test_get_diff_files_multi_merge_diverge_70(self):
256+
bitbucket_client = self.get_multi_merge_diverge_mock_client(70)
195257

196258
provider = BitbucketServerProvider(
197259
"https://git.onpreminstance.com/projects/AAA/repos/my-repo/pull-requests/1",
@@ -211,3 +273,25 @@ def test_get_diff_files_multi_merge_diverge(self):
211273
actual = provider.get_diff_files()
212274

213275
assert actual == expected
276+
277+
def test_get_diff_files_multi_merge_diverge_816(self):
278+
bitbucket_client = self.get_multi_merge_diverge_mock_client(816)
279+
280+
provider = BitbucketServerProvider(
281+
"https://git.onpreminstance.com/projects/AAA/repos/my-repo/pull-requests/1",
282+
bitbucket_client=bitbucket_client
283+
)
284+
285+
expected = [
286+
FilePatchInfo(
287+
'file\nwith\nsome\nlines\nto\nemulate\na\nreal\nfile',
288+
'readme\nwithout\nsome\nlines\nto\nsimulate\na\nreal\nfile',
289+
'--- \n+++ \n@@ -1,9 +1,9 @@\n-file\n-with\n+readme\n+without\n some\n lines\n to\n-emulate\n+simulate\n a\n real\n file',
290+
'Readme.md',
291+
edit_type=EDIT_TYPE.MODIFIED,
292+
)
293+
]
294+
295+
actual = provider.get_diff_files()
296+
297+
assert actual == expected

0 commit comments

Comments
 (0)