##// END OF EJS Templates
tests: fixes #4168 removing git xfails in pr and vcs commit tests
lisaq -
r677:191d2d88 default
parent child Browse files
Show More
@@ -1,917 +1,915 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23 from webob.exc import HTTPNotFound
23 from webob.exc import HTTPNotFound
24
24
25 import rhodecode
25 import rhodecode
26 from rhodecode.lib.vcs.nodes import FileNode
26 from rhodecode.lib.vcs.nodes import FileNode
27 from rhodecode.model.changeset_status import ChangesetStatusModel
27 from rhodecode.model.changeset_status import ChangesetStatusModel
28 from rhodecode.model.db import (
28 from rhodecode.model.db import (
29 PullRequest, ChangesetStatus, UserLog, Notification)
29 PullRequest, ChangesetStatus, UserLog, Notification)
30 from rhodecode.model.meta import Session
30 from rhodecode.model.meta import Session
31 from rhodecode.model.pull_request import PullRequestModel
31 from rhodecode.model.pull_request import PullRequestModel
32 from rhodecode.model.user import UserModel
32 from rhodecode.model.user import UserModel
33 from rhodecode.tests import assert_session_flash, url, TEST_USER_ADMIN_LOGIN
33 from rhodecode.tests import assert_session_flash, url, TEST_USER_ADMIN_LOGIN
34 from rhodecode.tests.utils import AssertResponse
34 from rhodecode.tests.utils import AssertResponse
35
35
36
36
37 @pytest.mark.usefixtures('app', 'autologin_user')
37 @pytest.mark.usefixtures('app', 'autologin_user')
38 @pytest.mark.backends("git", "hg")
38 @pytest.mark.backends("git", "hg")
39 class TestPullrequestsController:
39 class TestPullrequestsController:
40
40
41 def test_index(self, backend):
41 def test_index(self, backend):
42 self.app.get(url(
42 self.app.get(url(
43 controller='pullrequests', action='index',
43 controller='pullrequests', action='index',
44 repo_name=backend.repo_name))
44 repo_name=backend.repo_name))
45
45
46 def test_option_menu_create_pull_request_exists(self, backend):
46 def test_option_menu_create_pull_request_exists(self, backend):
47 repo_name = backend.repo_name
47 repo_name = backend.repo_name
48 response = self.app.get(url('summary_home', repo_name=repo_name))
48 response = self.app.get(url('summary_home', repo_name=repo_name))
49
49
50 create_pr_link = '<a href="%s">Create Pull Request</a>' % url(
50 create_pr_link = '<a href="%s">Create Pull Request</a>' % url(
51 'pullrequest', repo_name=repo_name)
51 'pullrequest', repo_name=repo_name)
52 response.mustcontain(create_pr_link)
52 response.mustcontain(create_pr_link)
53
53
54 def test_global_redirect_of_pr(self, backend, pr_util):
54 def test_global_redirect_of_pr(self, backend, pr_util):
55 pull_request = pr_util.create_pull_request()
55 pull_request = pr_util.create_pull_request()
56
56
57 response = self.app.get(
57 response = self.app.get(
58 url('pull_requests_global',
58 url('pull_requests_global',
59 pull_request_id=pull_request.pull_request_id))
59 pull_request_id=pull_request.pull_request_id))
60
60
61 repo_name = pull_request.target_repo.repo_name
61 repo_name = pull_request.target_repo.repo_name
62 redirect_url = url('pullrequest_show', repo_name=repo_name,
62 redirect_url = url('pullrequest_show', repo_name=repo_name,
63 pull_request_id=pull_request.pull_request_id)
63 pull_request_id=pull_request.pull_request_id)
64 assert response.status == '302 Found'
64 assert response.status == '302 Found'
65 assert redirect_url in response.location
65 assert redirect_url in response.location
66
66
67 @pytest.mark.xfail_backends(
68 "git", reason="Pending bugfix/feature, issue #6")
69 def test_create_pr_form_with_raw_commit_id(self, backend):
67 def test_create_pr_form_with_raw_commit_id(self, backend):
70 repo = backend.repo
68 repo = backend.repo
71
69
72 self.app.get(
70 self.app.get(
73 url(controller='pullrequests', action='index',
71 url(controller='pullrequests', action='index',
74 repo_name=repo.repo_name,
72 repo_name=repo.repo_name,
75 commit=repo.get_commit().raw_id),
73 commit=repo.get_commit().raw_id),
76 status=200)
74 status=200)
77
75
78 @pytest.mark.parametrize('pr_merge_enabled', [True, False])
76 @pytest.mark.parametrize('pr_merge_enabled', [True, False])
79 def test_show(self, pr_util, pr_merge_enabled):
77 def test_show(self, pr_util, pr_merge_enabled):
80 pull_request = pr_util.create_pull_request(
78 pull_request = pr_util.create_pull_request(
81 mergeable=pr_merge_enabled, enable_notifications=False)
79 mergeable=pr_merge_enabled, enable_notifications=False)
82
80
83 response = self.app.get(url(
81 response = self.app.get(url(
84 controller='pullrequests', action='show',
82 controller='pullrequests', action='show',
85 repo_name=pull_request.target_repo.scm_instance().name,
83 repo_name=pull_request.target_repo.scm_instance().name,
86 pull_request_id=str(pull_request.pull_request_id)))
84 pull_request_id=str(pull_request.pull_request_id)))
87
85
88 for commit_id in pull_request.revisions:
86 for commit_id in pull_request.revisions:
89 response.mustcontain(commit_id)
87 response.mustcontain(commit_id)
90
88
91 assert pull_request.target_ref_parts.type in response
89 assert pull_request.target_ref_parts.type in response
92 assert pull_request.target_ref_parts.name in response
90 assert pull_request.target_ref_parts.name in response
93 target_clone_url = pull_request.target_repo.clone_url()
91 target_clone_url = pull_request.target_repo.clone_url()
94 assert target_clone_url in response
92 assert target_clone_url in response
95
93
96 assert 'class="pull-request-merge"' in response
94 assert 'class="pull-request-merge"' in response
97 assert (
95 assert (
98 'Server-side pull request merging is disabled.'
96 'Server-side pull request merging is disabled.'
99 in response) != pr_merge_enabled
97 in response) != pr_merge_enabled
100
98
101 def test_close_status_visibility(self, pr_util, csrf_token):
99 def test_close_status_visibility(self, pr_util, csrf_token):
102 from rhodecode.tests.functional.test_login import login_url, logut_url
100 from rhodecode.tests.functional.test_login import login_url, logut_url
103 # Logout
101 # Logout
104 response = self.app.post(
102 response = self.app.post(
105 logut_url,
103 logut_url,
106 params={'csrf_token': csrf_token})
104 params={'csrf_token': csrf_token})
107 # Login as regular user
105 # Login as regular user
108 response = self.app.post(login_url,
106 response = self.app.post(login_url,
109 {'username': 'test_regular',
107 {'username': 'test_regular',
110 'password': 'test12'})
108 'password': 'test12'})
111
109
112 pull_request = pr_util.create_pull_request(author='test_regular')
110 pull_request = pr_util.create_pull_request(author='test_regular')
113
111
114 response = self.app.get(url(
112 response = self.app.get(url(
115 controller='pullrequests', action='show',
113 controller='pullrequests', action='show',
116 repo_name=pull_request.target_repo.scm_instance().name,
114 repo_name=pull_request.target_repo.scm_instance().name,
117 pull_request_id=str(pull_request.pull_request_id)))
115 pull_request_id=str(pull_request.pull_request_id)))
118
116
119 assert 'Server-side pull request merging is disabled.' in response
117 assert 'Server-side pull request merging is disabled.' in response
120 assert 'value="forced_closed"' in response
118 assert 'value="forced_closed"' in response
121
119
122 def test_show_invalid_commit_id(self, pr_util):
120 def test_show_invalid_commit_id(self, pr_util):
123 # Simulating invalid revisions which will cause a lookup error
121 # Simulating invalid revisions which will cause a lookup error
124 pull_request = pr_util.create_pull_request()
122 pull_request = pr_util.create_pull_request()
125 pull_request.revisions = ['invalid']
123 pull_request.revisions = ['invalid']
126 Session().add(pull_request)
124 Session().add(pull_request)
127 Session().commit()
125 Session().commit()
128
126
129 response = self.app.get(url(
127 response = self.app.get(url(
130 controller='pullrequests', action='show',
128 controller='pullrequests', action='show',
131 repo_name=pull_request.target_repo.scm_instance().name,
129 repo_name=pull_request.target_repo.scm_instance().name,
132 pull_request_id=str(pull_request.pull_request_id)))
130 pull_request_id=str(pull_request.pull_request_id)))
133
131
134 for commit_id in pull_request.revisions:
132 for commit_id in pull_request.revisions:
135 response.mustcontain(commit_id)
133 response.mustcontain(commit_id)
136
134
137 def test_show_invalid_source_reference(self, pr_util):
135 def test_show_invalid_source_reference(self, pr_util):
138 pull_request = pr_util.create_pull_request()
136 pull_request = pr_util.create_pull_request()
139 pull_request.source_ref = 'branch:b:invalid'
137 pull_request.source_ref = 'branch:b:invalid'
140 Session().add(pull_request)
138 Session().add(pull_request)
141 Session().commit()
139 Session().commit()
142
140
143 self.app.get(url(
141 self.app.get(url(
144 controller='pullrequests', action='show',
142 controller='pullrequests', action='show',
145 repo_name=pull_request.target_repo.scm_instance().name,
143 repo_name=pull_request.target_repo.scm_instance().name,
146 pull_request_id=str(pull_request.pull_request_id)))
144 pull_request_id=str(pull_request.pull_request_id)))
147
145
148 def test_edit_title_description(self, pr_util, csrf_token):
146 def test_edit_title_description(self, pr_util, csrf_token):
149 pull_request = pr_util.create_pull_request()
147 pull_request = pr_util.create_pull_request()
150 pull_request_id = pull_request.pull_request_id
148 pull_request_id = pull_request.pull_request_id
151
149
152 response = self.app.post(
150 response = self.app.post(
153 url(controller='pullrequests', action='update',
151 url(controller='pullrequests', action='update',
154 repo_name=pull_request.target_repo.repo_name,
152 repo_name=pull_request.target_repo.repo_name,
155 pull_request_id=str(pull_request_id)),
153 pull_request_id=str(pull_request_id)),
156 params={
154 params={
157 'edit_pull_request': 'true',
155 'edit_pull_request': 'true',
158 '_method': 'put',
156 '_method': 'put',
159 'title': 'New title',
157 'title': 'New title',
160 'description': 'New description',
158 'description': 'New description',
161 'csrf_token': csrf_token})
159 'csrf_token': csrf_token})
162
160
163 assert_session_flash(
161 assert_session_flash(
164 response, u'Pull request title & description updated.',
162 response, u'Pull request title & description updated.',
165 category='success')
163 category='success')
166
164
167 pull_request = PullRequest.get(pull_request_id)
165 pull_request = PullRequest.get(pull_request_id)
168 assert pull_request.title == 'New title'
166 assert pull_request.title == 'New title'
169 assert pull_request.description == 'New description'
167 assert pull_request.description == 'New description'
170
168
171 def test_edit_title_description_closed(self, pr_util, csrf_token):
169 def test_edit_title_description_closed(self, pr_util, csrf_token):
172 pull_request = pr_util.create_pull_request()
170 pull_request = pr_util.create_pull_request()
173 pull_request_id = pull_request.pull_request_id
171 pull_request_id = pull_request.pull_request_id
174 pr_util.close()
172 pr_util.close()
175
173
176 response = self.app.post(
174 response = self.app.post(
177 url(controller='pullrequests', action='update',
175 url(controller='pullrequests', action='update',
178 repo_name=pull_request.target_repo.repo_name,
176 repo_name=pull_request.target_repo.repo_name,
179 pull_request_id=str(pull_request_id)),
177 pull_request_id=str(pull_request_id)),
180 params={
178 params={
181 'edit_pull_request': 'true',
179 'edit_pull_request': 'true',
182 '_method': 'put',
180 '_method': 'put',
183 'title': 'New title',
181 'title': 'New title',
184 'description': 'New description',
182 'description': 'New description',
185 'csrf_token': csrf_token})
183 'csrf_token': csrf_token})
186
184
187 assert_session_flash(
185 assert_session_flash(
188 response, u'Cannot update closed pull requests.',
186 response, u'Cannot update closed pull requests.',
189 category='error')
187 category='error')
190
188
191 def test_update_invalid_source_reference(self, pr_util, csrf_token):
189 def test_update_invalid_source_reference(self, pr_util, csrf_token):
192 pull_request = pr_util.create_pull_request()
190 pull_request = pr_util.create_pull_request()
193 pull_request.source_ref = 'branch:invalid-branch:invalid-commit-id'
191 pull_request.source_ref = 'branch:invalid-branch:invalid-commit-id'
194 Session().add(pull_request)
192 Session().add(pull_request)
195 Session().commit()
193 Session().commit()
196
194
197 pull_request_id = pull_request.pull_request_id
195 pull_request_id = pull_request.pull_request_id
198
196
199 response = self.app.post(
197 response = self.app.post(
200 url(controller='pullrequests', action='update',
198 url(controller='pullrequests', action='update',
201 repo_name=pull_request.target_repo.repo_name,
199 repo_name=pull_request.target_repo.repo_name,
202 pull_request_id=str(pull_request_id)),
200 pull_request_id=str(pull_request_id)),
203 params={'update_commits': 'true', '_method': 'put',
201 params={'update_commits': 'true', '_method': 'put',
204 'csrf_token': csrf_token})
202 'csrf_token': csrf_token})
205
203
206 assert_session_flash(
204 assert_session_flash(
207 response, u'Update failed due to missing commits.',
205 response, u'Update failed due to missing commits.',
208 category='error')
206 category='error')
209
207
210 def test_comment_and_close_pull_request(self, pr_util, csrf_token):
208 def test_comment_and_close_pull_request(self, pr_util, csrf_token):
211 pull_request = pr_util.create_pull_request(approved=True)
209 pull_request = pr_util.create_pull_request(approved=True)
212 pull_request_id = pull_request.pull_request_id
210 pull_request_id = pull_request.pull_request_id
213 author = pull_request.user_id
211 author = pull_request.user_id
214 repo = pull_request.target_repo.repo_id
212 repo = pull_request.target_repo.repo_id
215
213
216 self.app.post(
214 self.app.post(
217 url(controller='pullrequests',
215 url(controller='pullrequests',
218 action='comment',
216 action='comment',
219 repo_name=pull_request.target_repo.scm_instance().name,
217 repo_name=pull_request.target_repo.scm_instance().name,
220 pull_request_id=str(pull_request_id)),
218 pull_request_id=str(pull_request_id)),
221 params={
219 params={
222 'changeset_status':
220 'changeset_status':
223 ChangesetStatus.STATUS_APPROVED + '_closed',
221 ChangesetStatus.STATUS_APPROVED + '_closed',
224 'change_changeset_status': 'on',
222 'change_changeset_status': 'on',
225 'text': '',
223 'text': '',
226 'csrf_token': csrf_token},
224 'csrf_token': csrf_token},
227 status=302)
225 status=302)
228
226
229 action = 'user_closed_pull_request:%d' % pull_request_id
227 action = 'user_closed_pull_request:%d' % pull_request_id
230 journal = UserLog.query()\
228 journal = UserLog.query()\
231 .filter(UserLog.user_id == author)\
229 .filter(UserLog.user_id == author)\
232 .filter(UserLog.repository_id == repo)\
230 .filter(UserLog.repository_id == repo)\
233 .filter(UserLog.action == action)\
231 .filter(UserLog.action == action)\
234 .all()
232 .all()
235 assert len(journal) == 1
233 assert len(journal) == 1
236
234
237 def test_reject_and_close_pull_request(self, pr_util, csrf_token):
235 def test_reject_and_close_pull_request(self, pr_util, csrf_token):
238 pull_request = pr_util.create_pull_request()
236 pull_request = pr_util.create_pull_request()
239 pull_request_id = pull_request.pull_request_id
237 pull_request_id = pull_request.pull_request_id
240 response = self.app.post(
238 response = self.app.post(
241 url(controller='pullrequests',
239 url(controller='pullrequests',
242 action='update',
240 action='update',
243 repo_name=pull_request.target_repo.scm_instance().name,
241 repo_name=pull_request.target_repo.scm_instance().name,
244 pull_request_id=str(pull_request.pull_request_id)),
242 pull_request_id=str(pull_request.pull_request_id)),
245 params={'close_pull_request': 'true', '_method': 'put',
243 params={'close_pull_request': 'true', '_method': 'put',
246 'csrf_token': csrf_token})
244 'csrf_token': csrf_token})
247
245
248 pull_request = PullRequest.get(pull_request_id)
246 pull_request = PullRequest.get(pull_request_id)
249
247
250 assert response.json is True
248 assert response.json is True
251 assert pull_request.is_closed()
249 assert pull_request.is_closed()
252
250
253 # check only the latest status, not the review status
251 # check only the latest status, not the review status
254 status = ChangesetStatusModel().get_status(
252 status = ChangesetStatusModel().get_status(
255 pull_request.source_repo, pull_request=pull_request)
253 pull_request.source_repo, pull_request=pull_request)
256 assert status == ChangesetStatus.STATUS_REJECTED
254 assert status == ChangesetStatus.STATUS_REJECTED
257
255
258 def test_comment_force_close_pull_request(self, pr_util, csrf_token):
256 def test_comment_force_close_pull_request(self, pr_util, csrf_token):
259 pull_request = pr_util.create_pull_request()
257 pull_request = pr_util.create_pull_request()
260 pull_request_id = pull_request.pull_request_id
258 pull_request_id = pull_request.pull_request_id
261 reviewers_ids = [1, 2]
259 reviewers_ids = [1, 2]
262 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
260 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
263 author = pull_request.user_id
261 author = pull_request.user_id
264 repo = pull_request.target_repo.repo_id
262 repo = pull_request.target_repo.repo_id
265 self.app.post(
263 self.app.post(
266 url(controller='pullrequests',
264 url(controller='pullrequests',
267 action='comment',
265 action='comment',
268 repo_name=pull_request.target_repo.scm_instance().name,
266 repo_name=pull_request.target_repo.scm_instance().name,
269 pull_request_id=str(pull_request_id)),
267 pull_request_id=str(pull_request_id)),
270 params={
268 params={
271 'changeset_status': 'forced_closed',
269 'changeset_status': 'forced_closed',
272 'csrf_token': csrf_token},
270 'csrf_token': csrf_token},
273 status=302)
271 status=302)
274
272
275 pull_request = PullRequest.get(pull_request_id)
273 pull_request = PullRequest.get(pull_request_id)
276
274
277 action = 'user_closed_pull_request:%d' % pull_request_id
275 action = 'user_closed_pull_request:%d' % pull_request_id
278 journal = UserLog.query().filter(
276 journal = UserLog.query().filter(
279 UserLog.user_id == author,
277 UserLog.user_id == author,
280 UserLog.repository_id == repo,
278 UserLog.repository_id == repo,
281 UserLog.action == action).all()
279 UserLog.action == action).all()
282 assert len(journal) == 1
280 assert len(journal) == 1
283
281
284 # check only the latest status, not the review status
282 # check only the latest status, not the review status
285 status = ChangesetStatusModel().get_status(
283 status = ChangesetStatusModel().get_status(
286 pull_request.source_repo, pull_request=pull_request)
284 pull_request.source_repo, pull_request=pull_request)
287 assert status == ChangesetStatus.STATUS_REJECTED
285 assert status == ChangesetStatus.STATUS_REJECTED
288
286
289 def test_create_pull_request(self, backend, csrf_token):
287 def test_create_pull_request(self, backend, csrf_token):
290 commits = [
288 commits = [
291 {'message': 'ancestor'},
289 {'message': 'ancestor'},
292 {'message': 'change'},
290 {'message': 'change'},
293 ]
291 ]
294 commit_ids = backend.create_master_repo(commits)
292 commit_ids = backend.create_master_repo(commits)
295 target = backend.create_repo(heads=['ancestor'])
293 target = backend.create_repo(heads=['ancestor'])
296 source = backend.create_repo(heads=['change'])
294 source = backend.create_repo(heads=['change'])
297
295
298 response = self.app.post(
296 response = self.app.post(
299 url(
297 url(
300 controller='pullrequests',
298 controller='pullrequests',
301 action='create',
299 action='create',
302 repo_name=source.repo_name),
300 repo_name=source.repo_name),
303 params={
301 params={
304 'source_repo': source.repo_name,
302 'source_repo': source.repo_name,
305 'source_ref': 'branch:default:' + commit_ids['change'],
303 'source_ref': 'branch:default:' + commit_ids['change'],
306 'target_repo': target.repo_name,
304 'target_repo': target.repo_name,
307 'target_ref': 'branch:default:' + commit_ids['ancestor'],
305 'target_ref': 'branch:default:' + commit_ids['ancestor'],
308 'pullrequest_desc': 'Description',
306 'pullrequest_desc': 'Description',
309 'pullrequest_title': 'Title',
307 'pullrequest_title': 'Title',
310 'review_members': '1',
308 'review_members': '1',
311 'revisions': commit_ids['change'],
309 'revisions': commit_ids['change'],
312 'user': '',
310 'user': '',
313 'csrf_token': csrf_token,
311 'csrf_token': csrf_token,
314 },
312 },
315 status=302)
313 status=302)
316
314
317 location = response.headers['Location']
315 location = response.headers['Location']
318 pull_request_id = int(location.rsplit('/', 1)[1])
316 pull_request_id = int(location.rsplit('/', 1)[1])
319 pull_request = PullRequest.get(pull_request_id)
317 pull_request = PullRequest.get(pull_request_id)
320
318
321 # check that we have now both revisions
319 # check that we have now both revisions
322 assert pull_request.revisions == [commit_ids['change']]
320 assert pull_request.revisions == [commit_ids['change']]
323 assert pull_request.source_ref == 'branch:default:' + commit_ids['change']
321 assert pull_request.source_ref == 'branch:default:' + commit_ids['change']
324 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
322 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
325 assert pull_request.target_ref == expected_target_ref
323 assert pull_request.target_ref == expected_target_ref
326
324
327 def test_reviewer_notifications(self, backend, csrf_token):
325 def test_reviewer_notifications(self, backend, csrf_token):
328 # We have to use the app.post for this test so it will create the
326 # We have to use the app.post for this test so it will create the
329 # notifications properly with the new PR
327 # notifications properly with the new PR
330 commits = [
328 commits = [
331 {'message': 'ancestor',
329 {'message': 'ancestor',
332 'added': [FileNode('file_A', content='content_of_ancestor')]},
330 'added': [FileNode('file_A', content='content_of_ancestor')]},
333 {'message': 'change',
331 {'message': 'change',
334 'added': [FileNode('file_a', content='content_of_change')]},
332 'added': [FileNode('file_a', content='content_of_change')]},
335 {'message': 'change-child'},
333 {'message': 'change-child'},
336 {'message': 'ancestor-child', 'parents': ['ancestor'],
334 {'message': 'ancestor-child', 'parents': ['ancestor'],
337 'added': [
335 'added': [
338 FileNode('file_B', content='content_of_ancestor_child')]},
336 FileNode('file_B', content='content_of_ancestor_child')]},
339 {'message': 'ancestor-child-2'},
337 {'message': 'ancestor-child-2'},
340 ]
338 ]
341 commit_ids = backend.create_master_repo(commits)
339 commit_ids = backend.create_master_repo(commits)
342 target = backend.create_repo(heads=['ancestor-child'])
340 target = backend.create_repo(heads=['ancestor-child'])
343 source = backend.create_repo(heads=['change'])
341 source = backend.create_repo(heads=['change'])
344
342
345 response = self.app.post(
343 response = self.app.post(
346 url(
344 url(
347 controller='pullrequests',
345 controller='pullrequests',
348 action='create',
346 action='create',
349 repo_name=source.repo_name),
347 repo_name=source.repo_name),
350 params={
348 params={
351 'source_repo': source.repo_name,
349 'source_repo': source.repo_name,
352 'source_ref': 'branch:default:' + commit_ids['change'],
350 'source_ref': 'branch:default:' + commit_ids['change'],
353 'target_repo': target.repo_name,
351 'target_repo': target.repo_name,
354 'target_ref': 'branch:default:' + commit_ids['ancestor-child'],
352 'target_ref': 'branch:default:' + commit_ids['ancestor-child'],
355 'pullrequest_desc': 'Description',
353 'pullrequest_desc': 'Description',
356 'pullrequest_title': 'Title',
354 'pullrequest_title': 'Title',
357 'review_members': '2',
355 'review_members': '2',
358 'revisions': commit_ids['change'],
356 'revisions': commit_ids['change'],
359 'user': '',
357 'user': '',
360 'csrf_token': csrf_token,
358 'csrf_token': csrf_token,
361 },
359 },
362 status=302)
360 status=302)
363
361
364 location = response.headers['Location']
362 location = response.headers['Location']
365 pull_request_id = int(location.rsplit('/', 1)[1])
363 pull_request_id = int(location.rsplit('/', 1)[1])
366 pull_request = PullRequest.get(pull_request_id)
364 pull_request = PullRequest.get(pull_request_id)
367
365
368 # Check that a notification was made
366 # Check that a notification was made
369 notifications = Notification.query()\
367 notifications = Notification.query()\
370 .filter(Notification.created_by == pull_request.author.user_id,
368 .filter(Notification.created_by == pull_request.author.user_id,
371 Notification.type_ == Notification.TYPE_PULL_REQUEST,
369 Notification.type_ == Notification.TYPE_PULL_REQUEST,
372 Notification.subject.contains("wants you to review "
370 Notification.subject.contains("wants you to review "
373 "pull request #%d"
371 "pull request #%d"
374 % pull_request_id))
372 % pull_request_id))
375 assert len(notifications.all()) == 1
373 assert len(notifications.all()) == 1
376
374
377 # Change reviewers and check that a notification was made
375 # Change reviewers and check that a notification was made
378 PullRequestModel().update_reviewers(pull_request.pull_request_id, [1])
376 PullRequestModel().update_reviewers(pull_request.pull_request_id, [1])
379 assert len(notifications.all()) == 2
377 assert len(notifications.all()) == 2
380
378
381 def test_create_pull_request_stores_ancestor_commit_id(self, backend,
379 def test_create_pull_request_stores_ancestor_commit_id(self, backend,
382 csrf_token):
380 csrf_token):
383 commits = [
381 commits = [
384 {'message': 'ancestor',
382 {'message': 'ancestor',
385 'added': [FileNode('file_A', content='content_of_ancestor')]},
383 'added': [FileNode('file_A', content='content_of_ancestor')]},
386 {'message': 'change',
384 {'message': 'change',
387 'added': [FileNode('file_a', content='content_of_change')]},
385 'added': [FileNode('file_a', content='content_of_change')]},
388 {'message': 'change-child'},
386 {'message': 'change-child'},
389 {'message': 'ancestor-child', 'parents': ['ancestor'],
387 {'message': 'ancestor-child', 'parents': ['ancestor'],
390 'added': [
388 'added': [
391 FileNode('file_B', content='content_of_ancestor_child')]},
389 FileNode('file_B', content='content_of_ancestor_child')]},
392 {'message': 'ancestor-child-2'},
390 {'message': 'ancestor-child-2'},
393 ]
391 ]
394 commit_ids = backend.create_master_repo(commits)
392 commit_ids = backend.create_master_repo(commits)
395 target = backend.create_repo(heads=['ancestor-child'])
393 target = backend.create_repo(heads=['ancestor-child'])
396 source = backend.create_repo(heads=['change'])
394 source = backend.create_repo(heads=['change'])
397
395
398 response = self.app.post(
396 response = self.app.post(
399 url(
397 url(
400 controller='pullrequests',
398 controller='pullrequests',
401 action='create',
399 action='create',
402 repo_name=source.repo_name),
400 repo_name=source.repo_name),
403 params={
401 params={
404 'source_repo': source.repo_name,
402 'source_repo': source.repo_name,
405 'source_ref': 'branch:default:' + commit_ids['change'],
403 'source_ref': 'branch:default:' + commit_ids['change'],
406 'target_repo': target.repo_name,
404 'target_repo': target.repo_name,
407 'target_ref': 'branch:default:' + commit_ids['ancestor-child'],
405 'target_ref': 'branch:default:' + commit_ids['ancestor-child'],
408 'pullrequest_desc': 'Description',
406 'pullrequest_desc': 'Description',
409 'pullrequest_title': 'Title',
407 'pullrequest_title': 'Title',
410 'review_members': '1',
408 'review_members': '1',
411 'revisions': commit_ids['change'],
409 'revisions': commit_ids['change'],
412 'user': '',
410 'user': '',
413 'csrf_token': csrf_token,
411 'csrf_token': csrf_token,
414 },
412 },
415 status=302)
413 status=302)
416
414
417 location = response.headers['Location']
415 location = response.headers['Location']
418 pull_request_id = int(location.rsplit('/', 1)[1])
416 pull_request_id = int(location.rsplit('/', 1)[1])
419 pull_request = PullRequest.get(pull_request_id)
417 pull_request = PullRequest.get(pull_request_id)
420
418
421 # target_ref has to point to the ancestor's commit_id in order to
419 # target_ref has to point to the ancestor's commit_id in order to
422 # show the correct diff
420 # show the correct diff
423 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
421 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
424 assert pull_request.target_ref == expected_target_ref
422 assert pull_request.target_ref == expected_target_ref
425
423
426 # Check generated diff contents
424 # Check generated diff contents
427 response = response.follow()
425 response = response.follow()
428 assert 'content_of_ancestor' not in response.body
426 assert 'content_of_ancestor' not in response.body
429 assert 'content_of_ancestor-child' not in response.body
427 assert 'content_of_ancestor-child' not in response.body
430 assert 'content_of_change' in response.body
428 assert 'content_of_change' in response.body
431
429
432 def test_merge_pull_request_enabled(self, pr_util, csrf_token):
430 def test_merge_pull_request_enabled(self, pr_util, csrf_token):
433 # Clear any previous calls to rcextensions
431 # Clear any previous calls to rcextensions
434 rhodecode.EXTENSIONS.calls.clear()
432 rhodecode.EXTENSIONS.calls.clear()
435
433
436 pull_request = pr_util.create_pull_request(
434 pull_request = pr_util.create_pull_request(
437 approved=True, mergeable=True)
435 approved=True, mergeable=True)
438 pull_request_id = pull_request.pull_request_id
436 pull_request_id = pull_request.pull_request_id
439 repo_name = pull_request.target_repo.scm_instance().name,
437 repo_name = pull_request.target_repo.scm_instance().name,
440
438
441 response = self.app.post(
439 response = self.app.post(
442 url(controller='pullrequests',
440 url(controller='pullrequests',
443 action='merge',
441 action='merge',
444 repo_name=str(repo_name[0]),
442 repo_name=str(repo_name[0]),
445 pull_request_id=str(pull_request_id)),
443 pull_request_id=str(pull_request_id)),
446 params={'csrf_token': csrf_token}).follow()
444 params={'csrf_token': csrf_token}).follow()
447
445
448 pull_request = PullRequest.get(pull_request_id)
446 pull_request = PullRequest.get(pull_request_id)
449
447
450 assert response.status_int == 200
448 assert response.status_int == 200
451 assert pull_request.is_closed()
449 assert pull_request.is_closed()
452 assert_pull_request_status(
450 assert_pull_request_status(
453 pull_request, ChangesetStatus.STATUS_APPROVED)
451 pull_request, ChangesetStatus.STATUS_APPROVED)
454
452
455 # Check the relevant log entries were added
453 # Check the relevant log entries were added
456 user_logs = UserLog.query().order_by('-user_log_id').limit(4)
454 user_logs = UserLog.query().order_by('-user_log_id').limit(4)
457 actions = [log.action for log in user_logs]
455 actions = [log.action for log in user_logs]
458 pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request)
456 pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request)
459 expected_actions = [
457 expected_actions = [
460 u'user_closed_pull_request:%d' % pull_request_id,
458 u'user_closed_pull_request:%d' % pull_request_id,
461 u'user_merged_pull_request:%d' % pull_request_id,
459 u'user_merged_pull_request:%d' % pull_request_id,
462 # The action below reflect that the post push actions were executed
460 # The action below reflect that the post push actions were executed
463 u'user_commented_pull_request:%d' % pull_request_id,
461 u'user_commented_pull_request:%d' % pull_request_id,
464 u'push:%s' % ','.join(pr_commit_ids),
462 u'push:%s' % ','.join(pr_commit_ids),
465 ]
463 ]
466 assert actions == expected_actions
464 assert actions == expected_actions
467
465
468 # Check post_push rcextension was really executed
466 # Check post_push rcextension was really executed
469 push_calls = rhodecode.EXTENSIONS.calls['post_push']
467 push_calls = rhodecode.EXTENSIONS.calls['post_push']
470 assert len(push_calls) == 1
468 assert len(push_calls) == 1
471 unused_last_call_args, last_call_kwargs = push_calls[0]
469 unused_last_call_args, last_call_kwargs = push_calls[0]
472 assert last_call_kwargs['action'] == 'push'
470 assert last_call_kwargs['action'] == 'push'
473 assert last_call_kwargs['pushed_revs'] == pr_commit_ids
471 assert last_call_kwargs['pushed_revs'] == pr_commit_ids
474
472
475 def test_merge_pull_request_disabled(self, pr_util, csrf_token):
473 def test_merge_pull_request_disabled(self, pr_util, csrf_token):
476 pull_request = pr_util.create_pull_request(mergeable=False)
474 pull_request = pr_util.create_pull_request(mergeable=False)
477 pull_request_id = pull_request.pull_request_id
475 pull_request_id = pull_request.pull_request_id
478 pull_request = PullRequest.get(pull_request_id)
476 pull_request = PullRequest.get(pull_request_id)
479
477
480 response = self.app.post(
478 response = self.app.post(
481 url(controller='pullrequests',
479 url(controller='pullrequests',
482 action='merge',
480 action='merge',
483 repo_name=pull_request.target_repo.scm_instance().name,
481 repo_name=pull_request.target_repo.scm_instance().name,
484 pull_request_id=str(pull_request.pull_request_id)),
482 pull_request_id=str(pull_request.pull_request_id)),
485 params={'csrf_token': csrf_token}).follow()
483 params={'csrf_token': csrf_token}).follow()
486
484
487 assert response.status_int == 200
485 assert response.status_int == 200
488 assert 'Server-side pull request merging is disabled.' in response.body
486 assert 'Server-side pull request merging is disabled.' in response.body
489
487
490 @pytest.mark.skip_backends('svn')
488 @pytest.mark.skip_backends('svn')
491 def test_merge_pull_request_not_approved(self, pr_util, csrf_token):
489 def test_merge_pull_request_not_approved(self, pr_util, csrf_token):
492 pull_request = pr_util.create_pull_request(mergeable=True)
490 pull_request = pr_util.create_pull_request(mergeable=True)
493 pull_request_id = pull_request.pull_request_id
491 pull_request_id = pull_request.pull_request_id
494 repo_name = pull_request.target_repo.scm_instance().name,
492 repo_name = pull_request.target_repo.scm_instance().name,
495
493
496 response = self.app.post(
494 response = self.app.post(
497 url(controller='pullrequests',
495 url(controller='pullrequests',
498 action='merge',
496 action='merge',
499 repo_name=str(repo_name[0]),
497 repo_name=str(repo_name[0]),
500 pull_request_id=str(pull_request_id)),
498 pull_request_id=str(pull_request_id)),
501 params={'csrf_token': csrf_token}).follow()
499 params={'csrf_token': csrf_token}).follow()
502
500
503 pull_request = PullRequest.get(pull_request_id)
501 pull_request = PullRequest.get(pull_request_id)
504
502
505 assert response.status_int == 200
503 assert response.status_int == 200
506 assert ' Reviewer approval is pending.' in response.body
504 assert ' Reviewer approval is pending.' in response.body
507
505
508 def test_update_source_revision(self, backend, csrf_token):
506 def test_update_source_revision(self, backend, csrf_token):
509 commits = [
507 commits = [
510 {'message': 'ancestor'},
508 {'message': 'ancestor'},
511 {'message': 'change'},
509 {'message': 'change'},
512 {'message': 'change-2'},
510 {'message': 'change-2'},
513 ]
511 ]
514 commit_ids = backend.create_master_repo(commits)
512 commit_ids = backend.create_master_repo(commits)
515 target = backend.create_repo(heads=['ancestor'])
513 target = backend.create_repo(heads=['ancestor'])
516 source = backend.create_repo(heads=['change'])
514 source = backend.create_repo(heads=['change'])
517
515
518 # create pr from a in source to A in target
516 # create pr from a in source to A in target
519 pull_request = PullRequest()
517 pull_request = PullRequest()
520 pull_request.source_repo = source
518 pull_request.source_repo = source
521 # TODO: johbo: Make sure that we write the source ref this way!
519 # TODO: johbo: Make sure that we write the source ref this way!
522 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
520 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
523 branch=backend.default_branch_name, commit_id=commit_ids['change'])
521 branch=backend.default_branch_name, commit_id=commit_ids['change'])
524 pull_request.target_repo = target
522 pull_request.target_repo = target
525
523
526 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
524 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
527 branch=backend.default_branch_name,
525 branch=backend.default_branch_name,
528 commit_id=commit_ids['ancestor'])
526 commit_id=commit_ids['ancestor'])
529 pull_request.revisions = [commit_ids['change']]
527 pull_request.revisions = [commit_ids['change']]
530 pull_request.title = u"Test"
528 pull_request.title = u"Test"
531 pull_request.description = u"Description"
529 pull_request.description = u"Description"
532 pull_request.author = UserModel().get_by_username(
530 pull_request.author = UserModel().get_by_username(
533 TEST_USER_ADMIN_LOGIN)
531 TEST_USER_ADMIN_LOGIN)
534 Session().add(pull_request)
532 Session().add(pull_request)
535 Session().commit()
533 Session().commit()
536 pull_request_id = pull_request.pull_request_id
534 pull_request_id = pull_request.pull_request_id
537
535
538 # source has ancestor - change - change-2
536 # source has ancestor - change - change-2
539 backend.pull_heads(source, heads=['change-2'])
537 backend.pull_heads(source, heads=['change-2'])
540
538
541 # update PR
539 # update PR
542 self.app.post(
540 self.app.post(
543 url(controller='pullrequests', action='update',
541 url(controller='pullrequests', action='update',
544 repo_name=target.repo_name,
542 repo_name=target.repo_name,
545 pull_request_id=str(pull_request_id)),
543 pull_request_id=str(pull_request_id)),
546 params={'update_commits': 'true', '_method': 'put',
544 params={'update_commits': 'true', '_method': 'put',
547 'csrf_token': csrf_token})
545 'csrf_token': csrf_token})
548
546
549 # check that we have now both revisions
547 # check that we have now both revisions
550 pull_request = PullRequest.get(pull_request_id)
548 pull_request = PullRequest.get(pull_request_id)
551 assert pull_request.revisions == [
549 assert pull_request.revisions == [
552 commit_ids['change-2'], commit_ids['change']]
550 commit_ids['change-2'], commit_ids['change']]
553
551
554 # TODO: johbo: this should be a test on its own
552 # TODO: johbo: this should be a test on its own
555 response = self.app.get(url(
553 response = self.app.get(url(
556 controller='pullrequests', action='index',
554 controller='pullrequests', action='index',
557 repo_name=target.repo_name))
555 repo_name=target.repo_name))
558 assert response.status_int == 200
556 assert response.status_int == 200
559 assert 'Pull request updated to' in response.body
557 assert 'Pull request updated to' in response.body
560 assert 'with 1 added, 0 removed commits.' in response.body
558 assert 'with 1 added, 0 removed commits.' in response.body
561
559
562 def test_update_target_revision(self, backend, csrf_token):
560 def test_update_target_revision(self, backend, csrf_token):
563 commits = [
561 commits = [
564 {'message': 'ancestor'},
562 {'message': 'ancestor'},
565 {'message': 'change'},
563 {'message': 'change'},
566 {'message': 'ancestor-new', 'parents': ['ancestor']},
564 {'message': 'ancestor-new', 'parents': ['ancestor']},
567 {'message': 'change-rebased'},
565 {'message': 'change-rebased'},
568 ]
566 ]
569 commit_ids = backend.create_master_repo(commits)
567 commit_ids = backend.create_master_repo(commits)
570 target = backend.create_repo(heads=['ancestor'])
568 target = backend.create_repo(heads=['ancestor'])
571 source = backend.create_repo(heads=['change'])
569 source = backend.create_repo(heads=['change'])
572
570
573 # create pr from a in source to A in target
571 # create pr from a in source to A in target
574 pull_request = PullRequest()
572 pull_request = PullRequest()
575 pull_request.source_repo = source
573 pull_request.source_repo = source
576 # TODO: johbo: Make sure that we write the source ref this way!
574 # TODO: johbo: Make sure that we write the source ref this way!
577 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
575 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
578 branch=backend.default_branch_name, commit_id=commit_ids['change'])
576 branch=backend.default_branch_name, commit_id=commit_ids['change'])
579 pull_request.target_repo = target
577 pull_request.target_repo = target
580 # TODO: johbo: Target ref should be branch based, since tip can jump
578 # TODO: johbo: Target ref should be branch based, since tip can jump
581 # from branch to branch
579 # from branch to branch
582 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
580 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
583 branch=backend.default_branch_name,
581 branch=backend.default_branch_name,
584 commit_id=commit_ids['ancestor'])
582 commit_id=commit_ids['ancestor'])
585 pull_request.revisions = [commit_ids['change']]
583 pull_request.revisions = [commit_ids['change']]
586 pull_request.title = u"Test"
584 pull_request.title = u"Test"
587 pull_request.description = u"Description"
585 pull_request.description = u"Description"
588 pull_request.author = UserModel().get_by_username(
586 pull_request.author = UserModel().get_by_username(
589 TEST_USER_ADMIN_LOGIN)
587 TEST_USER_ADMIN_LOGIN)
590 Session().add(pull_request)
588 Session().add(pull_request)
591 Session().commit()
589 Session().commit()
592 pull_request_id = pull_request.pull_request_id
590 pull_request_id = pull_request.pull_request_id
593
591
594 # target has ancestor - ancestor-new
592 # target has ancestor - ancestor-new
595 # source has ancestor - ancestor-new - change-rebased
593 # source has ancestor - ancestor-new - change-rebased
596 backend.pull_heads(target, heads=['ancestor-new'])
594 backend.pull_heads(target, heads=['ancestor-new'])
597 backend.pull_heads(source, heads=['change-rebased'])
595 backend.pull_heads(source, heads=['change-rebased'])
598
596
599 # update PR
597 # update PR
600 self.app.post(
598 self.app.post(
601 url(controller='pullrequests', action='update',
599 url(controller='pullrequests', action='update',
602 repo_name=target.repo_name,
600 repo_name=target.repo_name,
603 pull_request_id=str(pull_request_id)),
601 pull_request_id=str(pull_request_id)),
604 params={'update_commits': 'true', '_method': 'put',
602 params={'update_commits': 'true', '_method': 'put',
605 'csrf_token': csrf_token},
603 'csrf_token': csrf_token},
606 status=200)
604 status=200)
607
605
608 # check that we have now both revisions
606 # check that we have now both revisions
609 pull_request = PullRequest.get(pull_request_id)
607 pull_request = PullRequest.get(pull_request_id)
610 assert pull_request.revisions == [commit_ids['change-rebased']]
608 assert pull_request.revisions == [commit_ids['change-rebased']]
611 assert pull_request.target_ref == 'branch:{branch}:{commit_id}'.format(
609 assert pull_request.target_ref == 'branch:{branch}:{commit_id}'.format(
612 branch=backend.default_branch_name,
610 branch=backend.default_branch_name,
613 commit_id=commit_ids['ancestor-new'])
611 commit_id=commit_ids['ancestor-new'])
614
612
615 # TODO: johbo: This should be a test on its own
613 # TODO: johbo: This should be a test on its own
616 response = self.app.get(url(
614 response = self.app.get(url(
617 controller='pullrequests', action='index',
615 controller='pullrequests', action='index',
618 repo_name=target.repo_name))
616 repo_name=target.repo_name))
619 assert response.status_int == 200
617 assert response.status_int == 200
620 assert 'Pull request updated to' in response.body
618 assert 'Pull request updated to' in response.body
621 assert 'with 1 added, 1 removed commits.' in response.body
619 assert 'with 1 added, 1 removed commits.' in response.body
622
620
623 def test_update_of_ancestor_reference(self, backend, csrf_token):
621 def test_update_of_ancestor_reference(self, backend, csrf_token):
624 commits = [
622 commits = [
625 {'message': 'ancestor'},
623 {'message': 'ancestor'},
626 {'message': 'change'},
624 {'message': 'change'},
627 {'message': 'change-2'},
625 {'message': 'change-2'},
628 {'message': 'ancestor-new', 'parents': ['ancestor']},
626 {'message': 'ancestor-new', 'parents': ['ancestor']},
629 {'message': 'change-rebased'},
627 {'message': 'change-rebased'},
630 ]
628 ]
631 commit_ids = backend.create_master_repo(commits)
629 commit_ids = backend.create_master_repo(commits)
632 target = backend.create_repo(heads=['ancestor'])
630 target = backend.create_repo(heads=['ancestor'])
633 source = backend.create_repo(heads=['change'])
631 source = backend.create_repo(heads=['change'])
634
632
635 # create pr from a in source to A in target
633 # create pr from a in source to A in target
636 pull_request = PullRequest()
634 pull_request = PullRequest()
637 pull_request.source_repo = source
635 pull_request.source_repo = source
638 # TODO: johbo: Make sure that we write the source ref this way!
636 # TODO: johbo: Make sure that we write the source ref this way!
639 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
637 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
640 branch=backend.default_branch_name,
638 branch=backend.default_branch_name,
641 commit_id=commit_ids['change'])
639 commit_id=commit_ids['change'])
642 pull_request.target_repo = target
640 pull_request.target_repo = target
643 # TODO: johbo: Target ref should be branch based, since tip can jump
641 # TODO: johbo: Target ref should be branch based, since tip can jump
644 # from branch to branch
642 # from branch to branch
645 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
643 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
646 branch=backend.default_branch_name,
644 branch=backend.default_branch_name,
647 commit_id=commit_ids['ancestor'])
645 commit_id=commit_ids['ancestor'])
648 pull_request.revisions = [commit_ids['change']]
646 pull_request.revisions = [commit_ids['change']]
649 pull_request.title = u"Test"
647 pull_request.title = u"Test"
650 pull_request.description = u"Description"
648 pull_request.description = u"Description"
651 pull_request.author = UserModel().get_by_username(
649 pull_request.author = UserModel().get_by_username(
652 TEST_USER_ADMIN_LOGIN)
650 TEST_USER_ADMIN_LOGIN)
653 Session().add(pull_request)
651 Session().add(pull_request)
654 Session().commit()
652 Session().commit()
655 pull_request_id = pull_request.pull_request_id
653 pull_request_id = pull_request.pull_request_id
656
654
657 # target has ancestor - ancestor-new
655 # target has ancestor - ancestor-new
658 # source has ancestor - ancestor-new - change-rebased
656 # source has ancestor - ancestor-new - change-rebased
659 backend.pull_heads(target, heads=['ancestor-new'])
657 backend.pull_heads(target, heads=['ancestor-new'])
660 backend.pull_heads(source, heads=['change-rebased'])
658 backend.pull_heads(source, heads=['change-rebased'])
661
659
662 # update PR
660 # update PR
663 self.app.post(
661 self.app.post(
664 url(controller='pullrequests', action='update',
662 url(controller='pullrequests', action='update',
665 repo_name=target.repo_name,
663 repo_name=target.repo_name,
666 pull_request_id=str(pull_request_id)),
664 pull_request_id=str(pull_request_id)),
667 params={'update_commits': 'true', '_method': 'put',
665 params={'update_commits': 'true', '_method': 'put',
668 'csrf_token': csrf_token},
666 'csrf_token': csrf_token},
669 status=200)
667 status=200)
670
668
671 # Expect the target reference to be updated correctly
669 # Expect the target reference to be updated correctly
672 pull_request = PullRequest.get(pull_request_id)
670 pull_request = PullRequest.get(pull_request_id)
673 assert pull_request.revisions == [commit_ids['change-rebased']]
671 assert pull_request.revisions == [commit_ids['change-rebased']]
674 expected_target_ref = 'branch:{branch}:{commit_id}'.format(
672 expected_target_ref = 'branch:{branch}:{commit_id}'.format(
675 branch=backend.default_branch_name,
673 branch=backend.default_branch_name,
676 commit_id=commit_ids['ancestor-new'])
674 commit_id=commit_ids['ancestor-new'])
677 assert pull_request.target_ref == expected_target_ref
675 assert pull_request.target_ref == expected_target_ref
678
676
679 def test_remove_pull_request_branch(self, backend_git, csrf_token):
677 def test_remove_pull_request_branch(self, backend_git, csrf_token):
680 branch_name = 'development'
678 branch_name = 'development'
681 commits = [
679 commits = [
682 {'message': 'initial-commit'},
680 {'message': 'initial-commit'},
683 {'message': 'old-feature'},
681 {'message': 'old-feature'},
684 {'message': 'new-feature', 'branch': branch_name},
682 {'message': 'new-feature', 'branch': branch_name},
685 ]
683 ]
686 repo = backend_git.create_repo(commits)
684 repo = backend_git.create_repo(commits)
687 commit_ids = backend_git.commit_ids
685 commit_ids = backend_git.commit_ids
688
686
689 pull_request = PullRequest()
687 pull_request = PullRequest()
690 pull_request.source_repo = repo
688 pull_request.source_repo = repo
691 pull_request.target_repo = repo
689 pull_request.target_repo = repo
692 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
690 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
693 branch=branch_name, commit_id=commit_ids['new-feature'])
691 branch=branch_name, commit_id=commit_ids['new-feature'])
694 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
692 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
695 branch=backend_git.default_branch_name,
693 branch=backend_git.default_branch_name,
696 commit_id=commit_ids['old-feature'])
694 commit_id=commit_ids['old-feature'])
697 pull_request.revisions = [commit_ids['new-feature']]
695 pull_request.revisions = [commit_ids['new-feature']]
698 pull_request.title = u"Test"
696 pull_request.title = u"Test"
699 pull_request.description = u"Description"
697 pull_request.description = u"Description"
700 pull_request.author = UserModel().get_by_username(
698 pull_request.author = UserModel().get_by_username(
701 TEST_USER_ADMIN_LOGIN)
699 TEST_USER_ADMIN_LOGIN)
702 Session().add(pull_request)
700 Session().add(pull_request)
703 Session().commit()
701 Session().commit()
704
702
705 vcs = repo.scm_instance()
703 vcs = repo.scm_instance()
706 vcs.remove_ref('refs/heads/{}'.format(branch_name))
704 vcs.remove_ref('refs/heads/{}'.format(branch_name))
707
705
708 response = self.app.get(url(
706 response = self.app.get(url(
709 controller='pullrequests', action='show',
707 controller='pullrequests', action='show',
710 repo_name=repo.repo_name,
708 repo_name=repo.repo_name,
711 pull_request_id=str(pull_request.pull_request_id)))
709 pull_request_id=str(pull_request.pull_request_id)))
712
710
713 assert response.status_int == 200
711 assert response.status_int == 200
714 assert_response = AssertResponse(response)
712 assert_response = AssertResponse(response)
715 assert_response.element_contains(
713 assert_response.element_contains(
716 '#changeset_compare_view_content .alert strong',
714 '#changeset_compare_view_content .alert strong',
717 'Missing commits')
715 'Missing commits')
718 assert_response.element_contains(
716 assert_response.element_contains(
719 '#changeset_compare_view_content .alert',
717 '#changeset_compare_view_content .alert',
720 'This pull request cannot be displayed, because one or more'
718 'This pull request cannot be displayed, because one or more'
721 ' commits no longer exist in the source repository.')
719 ' commits no longer exist in the source repository.')
722
720
723 def test_strip_commits_from_pull_request(
721 def test_strip_commits_from_pull_request(
724 self, backend, pr_util, csrf_token):
722 self, backend, pr_util, csrf_token):
725 commits = [
723 commits = [
726 {'message': 'initial-commit'},
724 {'message': 'initial-commit'},
727 {'message': 'old-feature'},
725 {'message': 'old-feature'},
728 {'message': 'new-feature', 'parents': ['initial-commit']},
726 {'message': 'new-feature', 'parents': ['initial-commit']},
729 ]
727 ]
730 pull_request = pr_util.create_pull_request(
728 pull_request = pr_util.create_pull_request(
731 commits, target_head='initial-commit', source_head='new-feature',
729 commits, target_head='initial-commit', source_head='new-feature',
732 revisions=['new-feature'])
730 revisions=['new-feature'])
733
731
734 vcs = pr_util.source_repository.scm_instance()
732 vcs = pr_util.source_repository.scm_instance()
735 if backend.alias == 'git':
733 if backend.alias == 'git':
736 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
734 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
737 else:
735 else:
738 vcs.strip(pr_util.commit_ids['new-feature'])
736 vcs.strip(pr_util.commit_ids['new-feature'])
739
737
740 response = self.app.get(url(
738 response = self.app.get(url(
741 controller='pullrequests', action='show',
739 controller='pullrequests', action='show',
742 repo_name=pr_util.target_repository.repo_name,
740 repo_name=pr_util.target_repository.repo_name,
743 pull_request_id=str(pull_request.pull_request_id)))
741 pull_request_id=str(pull_request.pull_request_id)))
744
742
745 assert response.status_int == 200
743 assert response.status_int == 200
746 assert_response = AssertResponse(response)
744 assert_response = AssertResponse(response)
747 assert_response.element_contains(
745 assert_response.element_contains(
748 '#changeset_compare_view_content .alert strong',
746 '#changeset_compare_view_content .alert strong',
749 'Missing commits')
747 'Missing commits')
750 assert_response.element_contains(
748 assert_response.element_contains(
751 '#changeset_compare_view_content .alert',
749 '#changeset_compare_view_content .alert',
752 'This pull request cannot be displayed, because one or more'
750 'This pull request cannot be displayed, because one or more'
753 ' commits no longer exist in the source repository.')
751 ' commits no longer exist in the source repository.')
754 assert_response.element_contains(
752 assert_response.element_contains(
755 '#update_commits',
753 '#update_commits',
756 'Update commits')
754 'Update commits')
757
755
758 def test_strip_commits_and_update(
756 def test_strip_commits_and_update(
759 self, backend, pr_util, csrf_token):
757 self, backend, pr_util, csrf_token):
760 commits = [
758 commits = [
761 {'message': 'initial-commit'},
759 {'message': 'initial-commit'},
762 {'message': 'old-feature'},
760 {'message': 'old-feature'},
763 {'message': 'new-feature', 'parents': ['old-feature']},
761 {'message': 'new-feature', 'parents': ['old-feature']},
764 ]
762 ]
765 pull_request = pr_util.create_pull_request(
763 pull_request = pr_util.create_pull_request(
766 commits, target_head='old-feature', source_head='new-feature',
764 commits, target_head='old-feature', source_head='new-feature',
767 revisions=['new-feature'], mergeable=True)
765 revisions=['new-feature'], mergeable=True)
768
766
769 vcs = pr_util.source_repository.scm_instance()
767 vcs = pr_util.source_repository.scm_instance()
770 if backend.alias == 'git':
768 if backend.alias == 'git':
771 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
769 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
772 else:
770 else:
773 vcs.strip(pr_util.commit_ids['new-feature'])
771 vcs.strip(pr_util.commit_ids['new-feature'])
774
772
775 response = self.app.post(
773 response = self.app.post(
776 url(controller='pullrequests', action='update',
774 url(controller='pullrequests', action='update',
777 repo_name=pull_request.target_repo.repo_name,
775 repo_name=pull_request.target_repo.repo_name,
778 pull_request_id=str(pull_request.pull_request_id)),
776 pull_request_id=str(pull_request.pull_request_id)),
779 params={'update_commits': 'true', '_method': 'put',
777 params={'update_commits': 'true', '_method': 'put',
780 'csrf_token': csrf_token})
778 'csrf_token': csrf_token})
781
779
782 assert response.status_int == 200
780 assert response.status_int == 200
783 assert response.body == 'true'
781 assert response.body == 'true'
784
782
785 # Make sure that after update, it won't raise 500 errors
783 # Make sure that after update, it won't raise 500 errors
786 response = self.app.get(url(
784 response = self.app.get(url(
787 controller='pullrequests', action='show',
785 controller='pullrequests', action='show',
788 repo_name=pr_util.target_repository.repo_name,
786 repo_name=pr_util.target_repository.repo_name,
789 pull_request_id=str(pull_request.pull_request_id)))
787 pull_request_id=str(pull_request.pull_request_id)))
790
788
791 assert response.status_int == 200
789 assert response.status_int == 200
792 assert_response = AssertResponse(response)
790 assert_response = AssertResponse(response)
793 assert_response.element_contains(
791 assert_response.element_contains(
794 '#changeset_compare_view_content .alert strong',
792 '#changeset_compare_view_content .alert strong',
795 'Missing commits')
793 'Missing commits')
796
794
797 def test_branch_is_a_link(self, pr_util):
795 def test_branch_is_a_link(self, pr_util):
798 pull_request = pr_util.create_pull_request()
796 pull_request = pr_util.create_pull_request()
799 pull_request.source_ref = 'branch:origin:1234567890abcdef'
797 pull_request.source_ref = 'branch:origin:1234567890abcdef'
800 pull_request.target_ref = 'branch:target:abcdef1234567890'
798 pull_request.target_ref = 'branch:target:abcdef1234567890'
801 Session().add(pull_request)
799 Session().add(pull_request)
802 Session().commit()
800 Session().commit()
803
801
804 response = self.app.get(url(
802 response = self.app.get(url(
805 controller='pullrequests', action='show',
803 controller='pullrequests', action='show',
806 repo_name=pull_request.target_repo.scm_instance().name,
804 repo_name=pull_request.target_repo.scm_instance().name,
807 pull_request_id=str(pull_request.pull_request_id)))
805 pull_request_id=str(pull_request.pull_request_id)))
808 assert response.status_int == 200
806 assert response.status_int == 200
809 assert_response = AssertResponse(response)
807 assert_response = AssertResponse(response)
810
808
811 origin = assert_response.get_element('.pr-origininfo .tag')
809 origin = assert_response.get_element('.pr-origininfo .tag')
812 origin_children = origin.getchildren()
810 origin_children = origin.getchildren()
813 assert len(origin_children) == 1
811 assert len(origin_children) == 1
814 target = assert_response.get_element('.pr-targetinfo .tag')
812 target = assert_response.get_element('.pr-targetinfo .tag')
815 target_children = target.getchildren()
813 target_children = target.getchildren()
816 assert len(target_children) == 1
814 assert len(target_children) == 1
817
815
818 expected_origin_link = url(
816 expected_origin_link = url(
819 'changelog_home',
817 'changelog_home',
820 repo_name=pull_request.source_repo.scm_instance().name,
818 repo_name=pull_request.source_repo.scm_instance().name,
821 branch='origin')
819 branch='origin')
822 expected_target_link = url(
820 expected_target_link = url(
823 'changelog_home',
821 'changelog_home',
824 repo_name=pull_request.target_repo.scm_instance().name,
822 repo_name=pull_request.target_repo.scm_instance().name,
825 branch='target')
823 branch='target')
826 assert origin_children[0].attrib['href'] == expected_origin_link
824 assert origin_children[0].attrib['href'] == expected_origin_link
827 assert origin_children[0].text == 'branch: origin'
825 assert origin_children[0].text == 'branch: origin'
828 assert target_children[0].attrib['href'] == expected_target_link
826 assert target_children[0].attrib['href'] == expected_target_link
829 assert target_children[0].text == 'branch: target'
827 assert target_children[0].text == 'branch: target'
830
828
831 def test_bookmark_is_not_a_link(self, pr_util):
829 def test_bookmark_is_not_a_link(self, pr_util):
832 pull_request = pr_util.create_pull_request()
830 pull_request = pr_util.create_pull_request()
833 pull_request.source_ref = 'bookmark:origin:1234567890abcdef'
831 pull_request.source_ref = 'bookmark:origin:1234567890abcdef'
834 pull_request.target_ref = 'bookmark:target:abcdef1234567890'
832 pull_request.target_ref = 'bookmark:target:abcdef1234567890'
835 Session().add(pull_request)
833 Session().add(pull_request)
836 Session().commit()
834 Session().commit()
837
835
838 response = self.app.get(url(
836 response = self.app.get(url(
839 controller='pullrequests', action='show',
837 controller='pullrequests', action='show',
840 repo_name=pull_request.target_repo.scm_instance().name,
838 repo_name=pull_request.target_repo.scm_instance().name,
841 pull_request_id=str(pull_request.pull_request_id)))
839 pull_request_id=str(pull_request.pull_request_id)))
842 assert response.status_int == 200
840 assert response.status_int == 200
843 assert_response = AssertResponse(response)
841 assert_response = AssertResponse(response)
844
842
845 origin = assert_response.get_element('.pr-origininfo .tag')
843 origin = assert_response.get_element('.pr-origininfo .tag')
846 assert origin.text.strip() == 'bookmark: origin'
844 assert origin.text.strip() == 'bookmark: origin'
847 assert origin.getchildren() == []
845 assert origin.getchildren() == []
848
846
849 target = assert_response.get_element('.pr-targetinfo .tag')
847 target = assert_response.get_element('.pr-targetinfo .tag')
850 assert target.text.strip() == 'bookmark: target'
848 assert target.text.strip() == 'bookmark: target'
851 assert target.getchildren() == []
849 assert target.getchildren() == []
852
850
853 def test_tag_is_not_a_link(self, pr_util):
851 def test_tag_is_not_a_link(self, pr_util):
854 pull_request = pr_util.create_pull_request()
852 pull_request = pr_util.create_pull_request()
855 pull_request.source_ref = 'tag:origin:1234567890abcdef'
853 pull_request.source_ref = 'tag:origin:1234567890abcdef'
856 pull_request.target_ref = 'tag:target:abcdef1234567890'
854 pull_request.target_ref = 'tag:target:abcdef1234567890'
857 Session().add(pull_request)
855 Session().add(pull_request)
858 Session().commit()
856 Session().commit()
859
857
860 response = self.app.get(url(
858 response = self.app.get(url(
861 controller='pullrequests', action='show',
859 controller='pullrequests', action='show',
862 repo_name=pull_request.target_repo.scm_instance().name,
860 repo_name=pull_request.target_repo.scm_instance().name,
863 pull_request_id=str(pull_request.pull_request_id)))
861 pull_request_id=str(pull_request.pull_request_id)))
864 assert response.status_int == 200
862 assert response.status_int == 200
865 assert_response = AssertResponse(response)
863 assert_response = AssertResponse(response)
866
864
867 origin = assert_response.get_element('.pr-origininfo .tag')
865 origin = assert_response.get_element('.pr-origininfo .tag')
868 assert origin.text.strip() == 'tag: origin'
866 assert origin.text.strip() == 'tag: origin'
869 assert origin.getchildren() == []
867 assert origin.getchildren() == []
870
868
871 target = assert_response.get_element('.pr-targetinfo .tag')
869 target = assert_response.get_element('.pr-targetinfo .tag')
872 assert target.text.strip() == 'tag: target'
870 assert target.text.strip() == 'tag: target'
873 assert target.getchildren() == []
871 assert target.getchildren() == []
874
872
875 def test_description_is_escaped_on_index_page(self, backend, pr_util):
873 def test_description_is_escaped_on_index_page(self, backend, pr_util):
876 xss_description = "<script>alert('Hi!')</script>"
874 xss_description = "<script>alert('Hi!')</script>"
877 pull_request = pr_util.create_pull_request(description=xss_description)
875 pull_request = pr_util.create_pull_request(description=xss_description)
878 response = self.app.get(url(
876 response = self.app.get(url(
879 controller='pullrequests', action='show_all',
877 controller='pullrequests', action='show_all',
880 repo_name=pull_request.target_repo.repo_name))
878 repo_name=pull_request.target_repo.repo_name))
881 response.mustcontain(
879 response.mustcontain(
882 "&lt;script&gt;alert(&#39;Hi!&#39;)&lt;/script&gt;")
880 "&lt;script&gt;alert(&#39;Hi!&#39;)&lt;/script&gt;")
883
881
884
882
885 def assert_pull_request_status(pull_request, expected_status):
883 def assert_pull_request_status(pull_request, expected_status):
886 status = ChangesetStatusModel().calculated_review_status(
884 status = ChangesetStatusModel().calculated_review_status(
887 pull_request=pull_request)
885 pull_request=pull_request)
888 assert status == expected_status
886 assert status == expected_status
889
887
890
888
891 @pytest.mark.parametrize('action', ['show_all', 'index', 'create'])
889 @pytest.mark.parametrize('action', ['show_all', 'index', 'create'])
892 @pytest.mark.usefixtures("autologin_user")
890 @pytest.mark.usefixtures("autologin_user")
893 def test_redirects_to_repo_summary_for_svn_repositories(
891 def test_redirects_to_repo_summary_for_svn_repositories(
894 backend_svn, app, action):
892 backend_svn, app, action):
895 denied_actions = ['show_all', 'index', 'create']
893 denied_actions = ['show_all', 'index', 'create']
896 for action in denied_actions:
894 for action in denied_actions:
897 response = app.get(url(
895 response = app.get(url(
898 controller='pullrequests', action=action,
896 controller='pullrequests', action=action,
899 repo_name=backend_svn.repo_name))
897 repo_name=backend_svn.repo_name))
900 assert response.status_int == 302
898 assert response.status_int == 302
901
899
902 # Not allowed, redirect to the summary
900 # Not allowed, redirect to the summary
903 redirected = response.follow()
901 redirected = response.follow()
904 summary_url = url('summary_home', repo_name=backend_svn.repo_name)
902 summary_url = url('summary_home', repo_name=backend_svn.repo_name)
905
903
906 # URL adds leading slash and path doesn't have it
904 # URL adds leading slash and path doesn't have it
907 assert redirected.req.path == summary_url
905 assert redirected.req.path == summary_url
908
906
909
907
910 def test_delete_comment_returns_404_if_comment_does_not_exist(pylonsapp):
908 def test_delete_comment_returns_404_if_comment_does_not_exist(pylonsapp):
911 # TODO: johbo: Global import not possible because models.forms blows up
909 # TODO: johbo: Global import not possible because models.forms blows up
912 from rhodecode.controllers.pullrequests import PullrequestsController
910 from rhodecode.controllers.pullrequests import PullrequestsController
913 controller = PullrequestsController()
911 controller = PullrequestsController()
914 patcher = mock.patch(
912 patcher = mock.patch(
915 'rhodecode.model.db.BaseModel.get', return_value=None)
913 'rhodecode.model.db.BaseModel.get', return_value=None)
916 with pytest.raises(HTTPNotFound), patcher:
914 with pytest.raises(HTTPNotFound), patcher:
917 controller._delete_comment(1)
915 controller._delete_comment(1)
@@ -1,561 +1,564 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import datetime
21 import datetime
22 import time
22 import time
23
23
24 import pytest
24 import pytest
25
25
26 from rhodecode.lib.vcs.backends.base import (
26 from rhodecode.lib.vcs.backends.base import (
27 CollectionGenerator, FILEMODE_DEFAULT, EmptyCommit)
27 CollectionGenerator, FILEMODE_DEFAULT, EmptyCommit)
28 from rhodecode.lib.vcs.exceptions import (
28 from rhodecode.lib.vcs.exceptions import (
29 BranchDoesNotExistError, CommitDoesNotExistError,
29 BranchDoesNotExistError, CommitDoesNotExistError,
30 RepositoryError, EmptyRepositoryError)
30 RepositoryError, EmptyRepositoryError)
31 from rhodecode.lib.vcs.nodes import (
31 from rhodecode.lib.vcs.nodes import (
32 FileNode, AddedFileNodesGenerator,
32 FileNode, AddedFileNodesGenerator,
33 ChangedFileNodesGenerator, RemovedFileNodesGenerator)
33 ChangedFileNodesGenerator, RemovedFileNodesGenerator)
34 from rhodecode.tests import get_new_dir
34 from rhodecode.tests import get_new_dir
35 from rhodecode.tests.vcs.base import BackendTestMixin
35 from rhodecode.tests.vcs.base import BackendTestMixin
36
36
37
37
38 class TestBaseChangeset:
38 class TestBaseChangeset:
39
39
40 def test_is_deprecated(self):
40 def test_is_deprecated(self):
41 from rhodecode.lib.vcs.backends.base import BaseChangeset
41 from rhodecode.lib.vcs.backends.base import BaseChangeset
42 pytest.deprecated_call(BaseChangeset)
42 pytest.deprecated_call(BaseChangeset)
43
43
44
44
45 class TestEmptyCommit:
45 class TestEmptyCommit:
46
46
47 def test_branch_without_alias_returns_none(self):
47 def test_branch_without_alias_returns_none(self):
48 commit = EmptyCommit()
48 commit = EmptyCommit()
49 assert commit.branch is None
49 assert commit.branch is None
50
50
51
51
52 class TestCommitsInNonEmptyRepo(BackendTestMixin):
52 class TestCommitsInNonEmptyRepo(BackendTestMixin):
53 recreate_repo_per_test = True
53 recreate_repo_per_test = True
54
54
55 @classmethod
55 @classmethod
56 def _get_commits(cls):
56 def _get_commits(cls):
57 start_date = datetime.datetime(2010, 1, 1, 20)
57 start_date = datetime.datetime(2010, 1, 1, 20)
58 for x in xrange(5):
58 for x in xrange(5):
59 yield {
59 yield {
60 'message': 'Commit %d' % x,
60 'message': 'Commit %d' % x,
61 'author': 'Joe Doe <joe.doe@example.com>',
61 'author': 'Joe Doe <joe.doe@example.com>',
62 'date': start_date + datetime.timedelta(hours=12 * x),
62 'date': start_date + datetime.timedelta(hours=12 * x),
63 'added': [
63 'added': [
64 FileNode('file_%d.txt' % x, content='Foobar %d' % x),
64 FileNode('file_%d.txt' % x, content='Foobar %d' % x),
65 ],
65 ],
66 }
66 }
67
67
68 def test_walk_returns_empty_list_in_case_of_file(self):
68 def test_walk_returns_empty_list_in_case_of_file(self):
69 result = list(self.tip.walk('file_0.txt'))
69 result = list(self.tip.walk('file_0.txt'))
70 assert result == []
70 assert result == []
71
71
72 @pytest.mark.backends("git", "hg")
72 @pytest.mark.backends("git", "hg")
73 def test_new_branch(self):
73 def test_new_branch(self):
74 self.imc.add(FileNode('docs/index.txt',
74 self.imc.add(FileNode('docs/index.txt',
75 content='Documentation\n'))
75 content='Documentation\n'))
76 foobar_tip = self.imc.commit(
76 foobar_tip = self.imc.commit(
77 message=u'New branch: foobar',
77 message=u'New branch: foobar',
78 author=u'joe',
78 author=u'joe',
79 branch='foobar',
79 branch='foobar',
80 )
80 )
81 assert 'foobar' in self.repo.branches
81 assert 'foobar' in self.repo.branches
82 assert foobar_tip.branch == 'foobar'
82 assert foobar_tip.branch == 'foobar'
83 # 'foobar' should be the only branch that contains the new commit
83 # 'foobar' should be the only branch that contains the new commit
84 branch = self.repo.branches.values()
84 branch = self.repo.branches.values()
85 assert branch[0] != branch[1]
85 assert branch[0] != branch[1]
86
86
87 @pytest.mark.backends("git", "hg")
87 @pytest.mark.backends("git", "hg")
88 def test_new_head_in_default_branch(self):
88 def test_new_head_in_default_branch(self):
89 tip = self.repo.get_commit()
89 tip = self.repo.get_commit()
90 self.imc.add(FileNode('docs/index.txt',
90 self.imc.add(FileNode('docs/index.txt',
91 content='Documentation\n'))
91 content='Documentation\n'))
92 foobar_tip = self.imc.commit(
92 foobar_tip = self.imc.commit(
93 message=u'New branch: foobar',
93 message=u'New branch: foobar',
94 author=u'joe',
94 author=u'joe',
95 branch='foobar',
95 branch='foobar',
96 parents=[tip],
96 parents=[tip],
97 )
97 )
98 self.imc.change(FileNode('docs/index.txt',
98 self.imc.change(FileNode('docs/index.txt',
99 content='Documentation\nand more...\n'))
99 content='Documentation\nand more...\n'))
100 newtip = self.imc.commit(
100 newtip = self.imc.commit(
101 message=u'At default branch',
101 message=u'At default branch',
102 author=u'joe',
102 author=u'joe',
103 branch=foobar_tip.branch,
103 branch=foobar_tip.branch,
104 parents=[foobar_tip],
104 parents=[foobar_tip],
105 )
105 )
106
106
107 newest_tip = self.imc.commit(
107 newest_tip = self.imc.commit(
108 message=u'Merged with %s' % foobar_tip.raw_id,
108 message=u'Merged with %s' % foobar_tip.raw_id,
109 author=u'joe',
109 author=u'joe',
110 branch=self.backend_class.DEFAULT_BRANCH_NAME,
110 branch=self.backend_class.DEFAULT_BRANCH_NAME,
111 parents=[newtip, foobar_tip],
111 parents=[newtip, foobar_tip],
112 )
112 )
113
113
114 assert newest_tip.branch == self.backend_class.DEFAULT_BRANCH_NAME
114 assert newest_tip.branch == self.backend_class.DEFAULT_BRANCH_NAME
115
115
116 @pytest.mark.backends("git", "hg")
116 @pytest.mark.backends("git", "hg")
117 def test_get_commits_respects_branch_name(self):
117 def test_get_commits_respects_branch_name(self):
118 """
118 """
119 * e1930d0 (HEAD, master) Back in default branch
119 * e1930d0 (HEAD, master) Back in default branch
120 | * e1930d0 (docs) New Branch: docs2
120 | * e1930d0 (docs) New Branch: docs2
121 | * dcc14fa New branch: docs
121 | * dcc14fa New branch: docs
122 |/
122 |/
123 * e63c41a Initial commit
123 * e63c41a Initial commit
124 ...
124 ...
125 * 624d3db Commit 0
125 * 624d3db Commit 0
126
126
127 :return:
127 :return:
128 """
128 """
129 DEFAULT_BRANCH = self.repo.DEFAULT_BRANCH_NAME
129 DEFAULT_BRANCH = self.repo.DEFAULT_BRANCH_NAME
130 TEST_BRANCH = 'docs'
130 TEST_BRANCH = 'docs'
131 org_tip = self.repo.get_commit()
131 org_tip = self.repo.get_commit()
132
132
133 self.imc.add(FileNode('readme.txt', content='Document\n'))
133 self.imc.add(FileNode('readme.txt', content='Document\n'))
134 initial = self.imc.commit(
134 initial = self.imc.commit(
135 message=u'Initial commit',
135 message=u'Initial commit',
136 author=u'joe',
136 author=u'joe',
137 parents=[org_tip],
137 parents=[org_tip],
138 branch=DEFAULT_BRANCH,)
138 branch=DEFAULT_BRANCH,)
139
139
140 self.imc.add(FileNode('newdoc.txt', content='foobar\n'))
140 self.imc.add(FileNode('newdoc.txt', content='foobar\n'))
141 docs_branch_commit1 = self.imc.commit(
141 docs_branch_commit1 = self.imc.commit(
142 message=u'New branch: docs',
142 message=u'New branch: docs',
143 author=u'joe',
143 author=u'joe',
144 parents=[initial],
144 parents=[initial],
145 branch=TEST_BRANCH,)
145 branch=TEST_BRANCH,)
146
146
147 self.imc.add(FileNode('newdoc2.txt', content='foobar2\n'))
147 self.imc.add(FileNode('newdoc2.txt', content='foobar2\n'))
148 docs_branch_commit2 = self.imc.commit(
148 docs_branch_commit2 = self.imc.commit(
149 message=u'New branch: docs2',
149 message=u'New branch: docs2',
150 author=u'joe',
150 author=u'joe',
151 parents=[docs_branch_commit1],
151 parents=[docs_branch_commit1],
152 branch=TEST_BRANCH,)
152 branch=TEST_BRANCH,)
153
153
154 self.imc.add(FileNode('newfile', content='hello world\n'))
154 self.imc.add(FileNode('newfile', content='hello world\n'))
155 self.imc.commit(
155 self.imc.commit(
156 message=u'Back in default branch',
156 message=u'Back in default branch',
157 author=u'joe',
157 author=u'joe',
158 parents=[initial],
158 parents=[initial],
159 branch=DEFAULT_BRANCH,)
159 branch=DEFAULT_BRANCH,)
160
160
161 default_branch_commits = self.repo.get_commits(
161 default_branch_commits = self.repo.get_commits(
162 branch_name=DEFAULT_BRANCH)
162 branch_name=DEFAULT_BRANCH)
163 assert docs_branch_commit1 not in list(default_branch_commits)
163 assert docs_branch_commit1 not in list(default_branch_commits)
164 assert docs_branch_commit2 not in list(default_branch_commits)
164 assert docs_branch_commit2 not in list(default_branch_commits)
165
165
166 docs_branch_commits = self.repo.get_commits(
166 docs_branch_commits = self.repo.get_commits(
167 start_id=self.repo.commit_ids[0], end_id=self.repo.commit_ids[-1],
167 start_id=self.repo.commit_ids[0], end_id=self.repo.commit_ids[-1],
168 branch_name=TEST_BRANCH)
168 branch_name=TEST_BRANCH)
169 assert docs_branch_commit1 in list(docs_branch_commits)
169 assert docs_branch_commit1 in list(docs_branch_commits)
170 assert docs_branch_commit2 in list(docs_branch_commits)
170 assert docs_branch_commit2 in list(docs_branch_commits)
171
171
172 @pytest.mark.backends("svn")
172 @pytest.mark.backends("svn")
173 def test_get_commits_respects_branch_name_svn(self, vcsbackend_svn):
173 def test_get_commits_respects_branch_name_svn(self, vcsbackend_svn):
174 repo = vcsbackend_svn['svn-simple-layout']
174 repo = vcsbackend_svn['svn-simple-layout']
175 commits = repo.get_commits(branch_name='trunk')
175 commits = repo.get_commits(branch_name='trunk')
176 commit_indexes = [c.idx for c in commits]
176 commit_indexes = [c.idx for c in commits]
177 assert commit_indexes == [1, 2, 3, 7, 12, 15]
177 assert commit_indexes == [1, 2, 3, 7, 12, 15]
178
178
179 def test_get_commit_by_branch(self):
179 def test_get_commit_by_branch(self):
180 for branch, commit_id in self.repo.branches.iteritems():
180 for branch, commit_id in self.repo.branches.iteritems():
181 assert commit_id == self.repo.get_commit(branch).raw_id
181 assert commit_id == self.repo.get_commit(branch).raw_id
182
182
183 def test_get_commit_by_tag(self):
183 def test_get_commit_by_tag(self):
184 for tag, commit_id in self.repo.tags.iteritems():
184 for tag, commit_id in self.repo.tags.iteritems():
185 assert commit_id == self.repo.get_commit(tag).raw_id
185 assert commit_id == self.repo.get_commit(tag).raw_id
186
186
187 def test_get_commit_parents(self):
187 def test_get_commit_parents(self):
188 repo = self.repo
188 repo = self.repo
189 for test_idx in [1, 2, 3]:
189 for test_idx in [1, 2, 3]:
190 commit = repo.get_commit(commit_idx=test_idx - 1)
190 commit = repo.get_commit(commit_idx=test_idx - 1)
191 assert [commit] == repo.get_commit(commit_idx=test_idx).parents
191 assert [commit] == repo.get_commit(commit_idx=test_idx).parents
192
192
193 def test_get_commit_children(self):
193 def test_get_commit_children(self):
194 repo = self.repo
194 repo = self.repo
195 for test_idx in [1, 2, 3]:
195 for test_idx in [1, 2, 3]:
196 commit = repo.get_commit(commit_idx=test_idx + 1)
196 commit = repo.get_commit(commit_idx=test_idx + 1)
197 assert [commit] == repo.get_commit(commit_idx=test_idx).children
197 assert [commit] == repo.get_commit(commit_idx=test_idx).children
198
198
199
199
200 class TestCommits(BackendTestMixin):
200 class TestCommits(BackendTestMixin):
201 recreate_repo_per_test = False
201 recreate_repo_per_test = False
202
202
203 @classmethod
203 @classmethod
204 def _get_commits(cls):
204 def _get_commits(cls):
205 start_date = datetime.datetime(2010, 1, 1, 20)
205 start_date = datetime.datetime(2010, 1, 1, 20)
206 for x in xrange(5):
206 for x in xrange(5):
207 yield {
207 yield {
208 'message': u'Commit %d' % x,
208 'message': u'Commit %d' % x,
209 'author': u'Joe Doe <joe.doe@example.com>',
209 'author': u'Joe Doe <joe.doe@example.com>',
210 'date': start_date + datetime.timedelta(hours=12 * x),
210 'date': start_date + datetime.timedelta(hours=12 * x),
211 'added': [
211 'added': [
212 FileNode('file_%d.txt' % x, content='Foobar %d' % x),
212 FileNode('file_%d.txt' % x, content='Foobar %d' % x),
213 ],
213 ],
214 }
214 }
215
215
216 def test_simple(self):
216 def test_simple(self):
217 tip = self.repo.get_commit()
217 tip = self.repo.get_commit()
218 assert tip.date, datetime.datetime(2010, 1, 3 == 20)
218 assert tip.date, datetime.datetime(2010, 1, 3 == 20)
219
219
220 def test_simple_serialized_commit(self):
220 def test_simple_serialized_commit(self):
221 tip = self.repo.get_commit()
221 tip = self.repo.get_commit()
222 # json.dumps(tip) uses .__json__() method
222 # json.dumps(tip) uses .__json__() method
223 data = tip.__json__()
223 data = tip.__json__()
224 assert 'branch' in data
224 assert 'branch' in data
225 assert data['revision']
225 assert data['revision']
226
226
227 def test_retrieve_tip(self):
227 def test_retrieve_tip(self):
228 tip = self.repo.get_commit('tip')
228 tip = self.repo.get_commit('tip')
229 assert tip == self.repo.get_commit()
229 assert tip == self.repo.get_commit()
230
230
231 def test_invalid(self):
231 def test_invalid(self):
232 with pytest.raises(CommitDoesNotExistError):
232 with pytest.raises(CommitDoesNotExistError):
233 self.repo.get_commit(commit_idx=123456789)
233 self.repo.get_commit(commit_idx=123456789)
234
234
235 def test_idx(self):
235 def test_idx(self):
236 commit = self.repo[0]
236 commit = self.repo[0]
237 assert commit.idx == 0
237 assert commit.idx == 0
238
238
239 def test_negative_idx(self):
239 def test_negative_idx(self):
240 commit = self.repo.get_commit(commit_idx=-1)
240 commit = self.repo.get_commit(commit_idx=-1)
241 assert commit.idx >= 0
241 assert commit.idx >= 0
242
242
243 def test_revision_is_deprecated(self):
243 def test_revision_is_deprecated(self):
244 def get_revision(commit):
244 def get_revision(commit):
245 return commit.revision
245 return commit.revision
246
246
247 commit = self.repo[0]
247 commit = self.repo[0]
248 pytest.deprecated_call(get_revision, commit)
248 pytest.deprecated_call(get_revision, commit)
249
249
250 def test_size(self):
250 def test_size(self):
251 tip = self.repo.get_commit()
251 tip = self.repo.get_commit()
252 size = 5 * len('Foobar N') # Size of 5 files
252 size = 5 * len('Foobar N') # Size of 5 files
253 assert tip.size == size
253 assert tip.size == size
254
254
255 def test_size_at_commit(self):
255 def test_size_at_commit(self):
256 tip = self.repo.get_commit()
256 tip = self.repo.get_commit()
257 size = 5 * len('Foobar N') # Size of 5 files
257 size = 5 * len('Foobar N') # Size of 5 files
258 assert self.repo.size_at_commit(tip.raw_id) == size
258 assert self.repo.size_at_commit(tip.raw_id) == size
259
259
260 def test_size_at_first_commit(self):
260 def test_size_at_first_commit(self):
261 commit = self.repo[0]
261 commit = self.repo[0]
262 size = len('Foobar N') # Size of 1 file
262 size = len('Foobar N') # Size of 1 file
263 assert self.repo.size_at_commit(commit.raw_id) == size
263 assert self.repo.size_at_commit(commit.raw_id) == size
264
264
265 def test_author(self):
265 def test_author(self):
266 tip = self.repo.get_commit()
266 tip = self.repo.get_commit()
267 assert_text_equal(tip.author, u'Joe Doe <joe.doe@example.com>')
267 assert_text_equal(tip.author, u'Joe Doe <joe.doe@example.com>')
268
268
269 def test_author_name(self):
269 def test_author_name(self):
270 tip = self.repo.get_commit()
270 tip = self.repo.get_commit()
271 assert_text_equal(tip.author_name, u'Joe Doe')
271 assert_text_equal(tip.author_name, u'Joe Doe')
272
272
273 def test_author_email(self):
273 def test_author_email(self):
274 tip = self.repo.get_commit()
274 tip = self.repo.get_commit()
275 assert_text_equal(tip.author_email, u'joe.doe@example.com')
275 assert_text_equal(tip.author_email, u'joe.doe@example.com')
276
276
277 def test_message(self):
277 def test_message(self):
278 tip = self.repo.get_commit()
278 tip = self.repo.get_commit()
279 assert_text_equal(tip.message, u'Commit 4')
279 assert_text_equal(tip.message, u'Commit 4')
280
280
281 def test_diff(self):
281 def test_diff(self):
282 tip = self.repo.get_commit()
282 tip = self.repo.get_commit()
283 diff = tip.diff()
283 diff = tip.diff()
284 assert "+Foobar 4" in diff.raw
284 assert "+Foobar 4" in diff.raw
285
285
286 def test_prev(self):
286 def test_prev(self):
287 tip = self.repo.get_commit()
287 tip = self.repo.get_commit()
288 prev_commit = tip.prev()
288 prev_commit = tip.prev()
289 assert prev_commit.message == 'Commit 3'
289 assert prev_commit.message == 'Commit 3'
290
290
291 def test_prev_raises_on_first_commit(self):
291 def test_prev_raises_on_first_commit(self):
292 commit = self.repo.get_commit(commit_idx=0)
292 commit = self.repo.get_commit(commit_idx=0)
293 with pytest.raises(CommitDoesNotExistError):
293 with pytest.raises(CommitDoesNotExistError):
294 commit.prev()
294 commit.prev()
295
295
296 def test_prev_works_on_second_commit_issue_183(self):
296 def test_prev_works_on_second_commit_issue_183(self):
297 commit = self.repo.get_commit(commit_idx=1)
297 commit = self.repo.get_commit(commit_idx=1)
298 prev_commit = commit.prev()
298 prev_commit = commit.prev()
299 assert prev_commit.idx == 0
299 assert prev_commit.idx == 0
300
300
301 def test_next(self):
301 def test_next(self):
302 commit = self.repo.get_commit(commit_idx=2)
302 commit = self.repo.get_commit(commit_idx=2)
303 next_commit = commit.next()
303 next_commit = commit.next()
304 assert next_commit.message == 'Commit 3'
304 assert next_commit.message == 'Commit 3'
305
305
306 def test_next_raises_on_tip(self):
306 def test_next_raises_on_tip(self):
307 commit = self.repo.get_commit()
307 commit = self.repo.get_commit()
308 with pytest.raises(CommitDoesNotExistError):
308 with pytest.raises(CommitDoesNotExistError):
309 commit.next()
309 commit.next()
310
310
311 def test_get_file_commit(self):
311 def test_get_file_commit(self):
312 commit = self.repo.get_commit()
312 commit = self.repo.get_commit()
313 commit.get_file_commit('file_4.txt')
313 commit.get_file_commit('file_4.txt')
314 assert commit.message == 'Commit 4'
314 assert commit.message == 'Commit 4'
315
315
316 def test_get_filenodes_generator(self):
316 def test_get_filenodes_generator(self):
317 tip = self.repo.get_commit()
317 tip = self.repo.get_commit()
318 filepaths = [node.path for node in tip.get_filenodes_generator()]
318 filepaths = [node.path for node in tip.get_filenodes_generator()]
319 assert filepaths == ['file_%d.txt' % x for x in xrange(5)]
319 assert filepaths == ['file_%d.txt' % x for x in xrange(5)]
320
320
321 def test_get_file_annotate(self):
321 def test_get_file_annotate(self):
322 file_added_commit = self.repo.get_commit(commit_idx=3)
322 file_added_commit = self.repo.get_commit(commit_idx=3)
323 annotations = list(file_added_commit.get_file_annotate('file_3.txt'))
323 annotations = list(file_added_commit.get_file_annotate('file_3.txt'))
324 line_no, commit_id, commit_loader, line = annotations[0]
324 line_no, commit_id, commit_loader, line = annotations[0]
325 assert line_no == 1
325 assert line_no == 1
326 assert commit_id == file_added_commit.raw_id
326 assert commit_id == file_added_commit.raw_id
327 assert commit_loader() == file_added_commit
327 assert commit_loader() == file_added_commit
328
329 # git annotation is generated differently thus different results
328 if self.repo.alias == 'git':
330 if self.repo.alias == 'git':
329 pytest.xfail("TODO: Git returns wrong value in line")
331 assert line == '(Joe Doe 2010-01-03 08:00:00 +0000 1) Foobar 3'
332 else:
330 assert line == 'Foobar 3'
333 assert line == 'Foobar 3'
331
334
332 def test_get_file_annotate_does_not_exist(self):
335 def test_get_file_annotate_does_not_exist(self):
333 file_added_commit = self.repo.get_commit(commit_idx=2)
336 file_added_commit = self.repo.get_commit(commit_idx=2)
334 # TODO: Should use a specific exception class here?
337 # TODO: Should use a specific exception class here?
335 with pytest.raises(Exception):
338 with pytest.raises(Exception):
336 list(file_added_commit.get_file_annotate('file_3.txt'))
339 list(file_added_commit.get_file_annotate('file_3.txt'))
337
340
338 def test_get_file_annotate_tip(self):
341 def test_get_file_annotate_tip(self):
339 tip = self.repo.get_commit()
342 tip = self.repo.get_commit()
340 commit = self.repo.get_commit(commit_idx=3)
343 commit = self.repo.get_commit(commit_idx=3)
341 expected_values = list(commit.get_file_annotate('file_3.txt'))
344 expected_values = list(commit.get_file_annotate('file_3.txt'))
342 annotations = list(tip.get_file_annotate('file_3.txt'))
345 annotations = list(tip.get_file_annotate('file_3.txt'))
343
346
344 # Note: Skip index 2 because the loader function is not the same
347 # Note: Skip index 2 because the loader function is not the same
345 for idx in (0, 1, 3):
348 for idx in (0, 1, 3):
346 assert annotations[0][idx] == expected_values[0][idx]
349 assert annotations[0][idx] == expected_values[0][idx]
347
350
348 def test_get_commits_is_ordered_by_date(self):
351 def test_get_commits_is_ordered_by_date(self):
349 commits = self.repo.get_commits()
352 commits = self.repo.get_commits()
350 assert isinstance(commits, CollectionGenerator)
353 assert isinstance(commits, CollectionGenerator)
351 assert len(commits) == 0 or len(commits) != 0
354 assert len(commits) == 0 or len(commits) != 0
352 commits = list(commits)
355 commits = list(commits)
353 ordered_by_date = sorted(commits, key=lambda commit: commit.date)
356 ordered_by_date = sorted(commits, key=lambda commit: commit.date)
354 assert commits == ordered_by_date
357 assert commits == ordered_by_date
355
358
356 def test_get_commits_respects_start(self):
359 def test_get_commits_respects_start(self):
357 second_id = self.repo.commit_ids[1]
360 second_id = self.repo.commit_ids[1]
358 commits = self.repo.get_commits(start_id=second_id)
361 commits = self.repo.get_commits(start_id=second_id)
359 assert isinstance(commits, CollectionGenerator)
362 assert isinstance(commits, CollectionGenerator)
360 commits = list(commits)
363 commits = list(commits)
361 assert len(commits) == 4
364 assert len(commits) == 4
362
365
363 def test_get_commits_includes_start_commit(self):
366 def test_get_commits_includes_start_commit(self):
364 second_id = self.repo.commit_ids[1]
367 second_id = self.repo.commit_ids[1]
365 commits = self.repo.get_commits(start_id=second_id)
368 commits = self.repo.get_commits(start_id=second_id)
366 assert isinstance(commits, CollectionGenerator)
369 assert isinstance(commits, CollectionGenerator)
367 commits = list(commits)
370 commits = list(commits)
368 assert commits[0].raw_id == second_id
371 assert commits[0].raw_id == second_id
369
372
370 def test_get_commits_respects_end(self):
373 def test_get_commits_respects_end(self):
371 second_id = self.repo.commit_ids[1]
374 second_id = self.repo.commit_ids[1]
372 commits = self.repo.get_commits(end_id=second_id)
375 commits = self.repo.get_commits(end_id=second_id)
373 assert isinstance(commits, CollectionGenerator)
376 assert isinstance(commits, CollectionGenerator)
374 commits = list(commits)
377 commits = list(commits)
375 assert commits[-1].raw_id == second_id
378 assert commits[-1].raw_id == second_id
376 assert len(commits) == 2
379 assert len(commits) == 2
377
380
378 def test_get_commits_respects_both_start_and_end(self):
381 def test_get_commits_respects_both_start_and_end(self):
379 second_id = self.repo.commit_ids[1]
382 second_id = self.repo.commit_ids[1]
380 third_id = self.repo.commit_ids[2]
383 third_id = self.repo.commit_ids[2]
381 commits = self.repo.get_commits(start_id=second_id, end_id=third_id)
384 commits = self.repo.get_commits(start_id=second_id, end_id=third_id)
382 assert isinstance(commits, CollectionGenerator)
385 assert isinstance(commits, CollectionGenerator)
383 commits = list(commits)
386 commits = list(commits)
384 assert len(commits) == 2
387 assert len(commits) == 2
385
388
386 def test_get_commits_on_empty_repo_raises_EmptyRepository_error(self):
389 def test_get_commits_on_empty_repo_raises_EmptyRepository_error(self):
387 repo_path = get_new_dir(str(time.time()))
390 repo_path = get_new_dir(str(time.time()))
388 repo = self.Backend(repo_path, create=True)
391 repo = self.Backend(repo_path, create=True)
389
392
390 with pytest.raises(EmptyRepositoryError):
393 with pytest.raises(EmptyRepositoryError):
391 list(repo.get_commits(start_id='foobar'))
394 list(repo.get_commits(start_id='foobar'))
392
395
393 def test_get_commits_includes_end_commit(self):
396 def test_get_commits_includes_end_commit(self):
394 second_id = self.repo.commit_ids[1]
397 second_id = self.repo.commit_ids[1]
395 commits = self.repo.get_commits(end_id=second_id)
398 commits = self.repo.get_commits(end_id=second_id)
396 assert isinstance(commits, CollectionGenerator)
399 assert isinstance(commits, CollectionGenerator)
397 assert len(commits) == 2
400 assert len(commits) == 2
398 commits = list(commits)
401 commits = list(commits)
399 assert commits[-1].raw_id == second_id
402 assert commits[-1].raw_id == second_id
400
403
401 def test_get_commits_respects_start_date(self):
404 def test_get_commits_respects_start_date(self):
402 start_date = datetime.datetime(2010, 1, 2)
405 start_date = datetime.datetime(2010, 1, 2)
403 commits = self.repo.get_commits(start_date=start_date)
406 commits = self.repo.get_commits(start_date=start_date)
404 assert isinstance(commits, CollectionGenerator)
407 assert isinstance(commits, CollectionGenerator)
405 # Should be 4 commits after 2010-01-02 00:00:00
408 # Should be 4 commits after 2010-01-02 00:00:00
406 assert len(commits) == 4
409 assert len(commits) == 4
407 for c in commits:
410 for c in commits:
408 assert c.date >= start_date
411 assert c.date >= start_date
409
412
410 def test_get_commits_respects_start_date_and_end_date(self):
413 def test_get_commits_respects_start_date_and_end_date(self):
411 start_date = datetime.datetime(2010, 1, 2)
414 start_date = datetime.datetime(2010, 1, 2)
412 end_date = datetime.datetime(2010, 1, 3)
415 end_date = datetime.datetime(2010, 1, 3)
413 commits = self.repo.get_commits(start_date=start_date,
416 commits = self.repo.get_commits(start_date=start_date,
414 end_date=end_date)
417 end_date=end_date)
415 assert isinstance(commits, CollectionGenerator)
418 assert isinstance(commits, CollectionGenerator)
416 assert len(commits) == 2
419 assert len(commits) == 2
417 for c in commits:
420 for c in commits:
418 assert c.date >= start_date
421 assert c.date >= start_date
419 assert c.date <= end_date
422 assert c.date <= end_date
420
423
421 def test_get_commits_respects_end_date(self):
424 def test_get_commits_respects_end_date(self):
422 end_date = datetime.datetime(2010, 1, 2)
425 end_date = datetime.datetime(2010, 1, 2)
423 commits = self.repo.get_commits(end_date=end_date)
426 commits = self.repo.get_commits(end_date=end_date)
424 assert isinstance(commits, CollectionGenerator)
427 assert isinstance(commits, CollectionGenerator)
425 assert len(commits) == 1
428 assert len(commits) == 1
426 for c in commits:
429 for c in commits:
427 assert c.date <= end_date
430 assert c.date <= end_date
428
431
429 def test_get_commits_respects_reverse(self):
432 def test_get_commits_respects_reverse(self):
430 commits = self.repo.get_commits() # no longer reverse support
433 commits = self.repo.get_commits() # no longer reverse support
431 assert isinstance(commits, CollectionGenerator)
434 assert isinstance(commits, CollectionGenerator)
432 assert len(commits) == 5
435 assert len(commits) == 5
433 commit_ids = reversed([c.raw_id for c in commits])
436 commit_ids = reversed([c.raw_id for c in commits])
434 assert list(commit_ids) == list(reversed(self.repo.commit_ids))
437 assert list(commit_ids) == list(reversed(self.repo.commit_ids))
435
438
436 def test_get_commits_slice_generator(self):
439 def test_get_commits_slice_generator(self):
437 commits = self.repo.get_commits(
440 commits = self.repo.get_commits(
438 branch_name=self.repo.DEFAULT_BRANCH_NAME)
441 branch_name=self.repo.DEFAULT_BRANCH_NAME)
439 assert isinstance(commits, CollectionGenerator)
442 assert isinstance(commits, CollectionGenerator)
440 commit_slice = list(commits[1:3])
443 commit_slice = list(commits[1:3])
441 assert len(commit_slice) == 2
444 assert len(commit_slice) == 2
442
445
443 def test_get_commits_raise_commitdoesnotexist_for_wrong_start(self):
446 def test_get_commits_raise_commitdoesnotexist_for_wrong_start(self):
444 with pytest.raises(CommitDoesNotExistError):
447 with pytest.raises(CommitDoesNotExistError):
445 list(self.repo.get_commits(start_id='foobar'))
448 list(self.repo.get_commits(start_id='foobar'))
446
449
447 def test_get_commits_raise_commitdoesnotexist_for_wrong_end(self):
450 def test_get_commits_raise_commitdoesnotexist_for_wrong_end(self):
448 with pytest.raises(CommitDoesNotExistError):
451 with pytest.raises(CommitDoesNotExistError):
449 list(self.repo.get_commits(end_id='foobar'))
452 list(self.repo.get_commits(end_id='foobar'))
450
453
451 def test_get_commits_raise_branchdoesnotexist_for_wrong_branch_name(self):
454 def test_get_commits_raise_branchdoesnotexist_for_wrong_branch_name(self):
452 with pytest.raises(BranchDoesNotExistError):
455 with pytest.raises(BranchDoesNotExistError):
453 list(self.repo.get_commits(branch_name='foobar'))
456 list(self.repo.get_commits(branch_name='foobar'))
454
457
455 def test_get_commits_raise_repositoryerror_for_wrong_start_end(self):
458 def test_get_commits_raise_repositoryerror_for_wrong_start_end(self):
456 start_id = self.repo.commit_ids[-1]
459 start_id = self.repo.commit_ids[-1]
457 end_id = self.repo.commit_ids[0]
460 end_id = self.repo.commit_ids[0]
458 with pytest.raises(RepositoryError):
461 with pytest.raises(RepositoryError):
459 list(self.repo.get_commits(start_id=start_id, end_id=end_id))
462 list(self.repo.get_commits(start_id=start_id, end_id=end_id))
460
463
461 def test_get_commits_raises_for_numerical_ids(self):
464 def test_get_commits_raises_for_numerical_ids(self):
462 with pytest.raises(TypeError):
465 with pytest.raises(TypeError):
463 self.repo.get_commits(start_id=1, end_id=2)
466 self.repo.get_commits(start_id=1, end_id=2)
464
467
465
468
466 @pytest.mark.parametrize("filename, expected", [
469 @pytest.mark.parametrize("filename, expected", [
467 ("README.rst", False),
470 ("README.rst", False),
468 ("README", True),
471 ("README", True),
469 ])
472 ])
470 def test_commit_is_link(vcsbackend, filename, expected):
473 def test_commit_is_link(vcsbackend, filename, expected):
471 commit = vcsbackend.repo.get_commit()
474 commit = vcsbackend.repo.get_commit()
472 link_status = commit.is_link(filename)
475 link_status = commit.is_link(filename)
473 assert link_status is expected
476 assert link_status is expected
474
477
475
478
476 class TestCommitsChanges(BackendTestMixin):
479 class TestCommitsChanges(BackendTestMixin):
477 recreate_repo_per_test = False
480 recreate_repo_per_test = False
478
481
479 @classmethod
482 @classmethod
480 def _get_commits(cls):
483 def _get_commits(cls):
481 return [
484 return [
482 {
485 {
483 'message': u'Initial',
486 'message': u'Initial',
484 'author': u'Joe Doe <joe.doe@example.com>',
487 'author': u'Joe Doe <joe.doe@example.com>',
485 'date': datetime.datetime(2010, 1, 1, 20),
488 'date': datetime.datetime(2010, 1, 1, 20),
486 'added': [
489 'added': [
487 FileNode('foo/bar', content='foo'),
490 FileNode('foo/bar', content='foo'),
488 FileNode('foo/bał', content='foo'),
491 FileNode('foo/bał', content='foo'),
489 FileNode('foobar', content='foo'),
492 FileNode('foobar', content='foo'),
490 FileNode('qwe', content='foo'),
493 FileNode('qwe', content='foo'),
491 ],
494 ],
492 },
495 },
493 {
496 {
494 'message': u'Massive changes',
497 'message': u'Massive changes',
495 'author': u'Joe Doe <joe.doe@example.com>',
498 'author': u'Joe Doe <joe.doe@example.com>',
496 'date': datetime.datetime(2010, 1, 1, 22),
499 'date': datetime.datetime(2010, 1, 1, 22),
497 'added': [FileNode('fallout', content='War never changes')],
500 'added': [FileNode('fallout', content='War never changes')],
498 'changed': [
501 'changed': [
499 FileNode('foo/bar', content='baz'),
502 FileNode('foo/bar', content='baz'),
500 FileNode('foobar', content='baz'),
503 FileNode('foobar', content='baz'),
501 ],
504 ],
502 'removed': [FileNode('qwe')],
505 'removed': [FileNode('qwe')],
503 },
506 },
504 ]
507 ]
505
508
506 def test_initial_commit(self):
509 def test_initial_commit(self):
507 commit = self.repo.get_commit(commit_idx=0)
510 commit = self.repo.get_commit(commit_idx=0)
508 assert set(commit.added) == set([
511 assert set(commit.added) == set([
509 commit.get_node('foo/bar'),
512 commit.get_node('foo/bar'),
510 commit.get_node('foo/bał'),
513 commit.get_node('foo/bał'),
511 commit.get_node('foobar'),
514 commit.get_node('foobar'),
512 commit.get_node('qwe'),
515 commit.get_node('qwe'),
513 ])
516 ])
514 assert set(commit.changed) == set()
517 assert set(commit.changed) == set()
515 assert set(commit.removed) == set()
518 assert set(commit.removed) == set()
516 assert set(commit.affected_files) == set(
519 assert set(commit.affected_files) == set(
517 ['foo/bar', 'foo/bał', 'foobar', 'qwe'])
520 ['foo/bar', 'foo/bał', 'foobar', 'qwe'])
518 assert commit.date == datetime.datetime(2010, 1, 1, 20, 0)
521 assert commit.date == datetime.datetime(2010, 1, 1, 20, 0)
519
522
520 def test_head_added(self):
523 def test_head_added(self):
521 commit = self.repo.get_commit()
524 commit = self.repo.get_commit()
522 assert isinstance(commit.added, AddedFileNodesGenerator)
525 assert isinstance(commit.added, AddedFileNodesGenerator)
523 assert set(commit.added) == set([commit.get_node('fallout')])
526 assert set(commit.added) == set([commit.get_node('fallout')])
524 assert isinstance(commit.changed, ChangedFileNodesGenerator)
527 assert isinstance(commit.changed, ChangedFileNodesGenerator)
525 assert set(commit.changed) == set([
528 assert set(commit.changed) == set([
526 commit.get_node('foo/bar'),
529 commit.get_node('foo/bar'),
527 commit.get_node('foobar'),
530 commit.get_node('foobar'),
528 ])
531 ])
529 assert isinstance(commit.removed, RemovedFileNodesGenerator)
532 assert isinstance(commit.removed, RemovedFileNodesGenerator)
530 assert len(commit.removed) == 1
533 assert len(commit.removed) == 1
531 assert list(commit.removed)[0].path == 'qwe'
534 assert list(commit.removed)[0].path == 'qwe'
532
535
533 def test_get_filemode(self):
536 def test_get_filemode(self):
534 commit = self.repo.get_commit()
537 commit = self.repo.get_commit()
535 assert FILEMODE_DEFAULT == commit.get_file_mode('foo/bar')
538 assert FILEMODE_DEFAULT == commit.get_file_mode('foo/bar')
536
539
537 def test_get_filemode_non_ascii(self):
540 def test_get_filemode_non_ascii(self):
538 commit = self.repo.get_commit()
541 commit = self.repo.get_commit()
539 assert FILEMODE_DEFAULT == commit.get_file_mode('foo/bał')
542 assert FILEMODE_DEFAULT == commit.get_file_mode('foo/bał')
540 assert FILEMODE_DEFAULT == commit.get_file_mode(u'foo/bał')
543 assert FILEMODE_DEFAULT == commit.get_file_mode(u'foo/bał')
541
544
542 def test_get_file_history(self):
545 def test_get_file_history(self):
543 commit = self.repo.get_commit()
546 commit = self.repo.get_commit()
544 history = commit.get_file_history('foo/bar')
547 history = commit.get_file_history('foo/bar')
545 assert len(history) == 2
548 assert len(history) == 2
546
549
547 def test_get_file_history_with_limit(self):
550 def test_get_file_history_with_limit(self):
548 commit = self.repo.get_commit()
551 commit = self.repo.get_commit()
549 history = commit.get_file_history('foo/bar', limit=1)
552 history = commit.get_file_history('foo/bar', limit=1)
550 assert len(history) == 1
553 assert len(history) == 1
551
554
552 def test_get_file_history_first_commit(self):
555 def test_get_file_history_first_commit(self):
553 commit = self.repo[0]
556 commit = self.repo[0]
554 history = commit.get_file_history('foo/bar')
557 history = commit.get_file_history('foo/bar')
555 assert len(history) == 1
558 assert len(history) == 1
556
559
557
560
558 def assert_text_equal(expected, given):
561 def assert_text_equal(expected, given):
559 assert expected == given
562 assert expected == given
560 assert isinstance(expected, unicode)
563 assert isinstance(expected, unicode)
561 assert isinstance(given, unicode)
564 assert isinstance(given, unicode)
General Comments 0
You need to be logged in to leave comments. Login now