##// END OF EJS Templates
tests: use gunicorn for testing. This is close to production testing...
marcink -
r2453:9cd85ffa default
parent child Browse files
Show More
@@ -1,271 +1,271 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 py.test config for test suite for making push/pull operations.
23 23
24 24 .. important::
25 25
26 26 You must have git >= 1.8.5 for tests to work fine. With 68b939b git started
27 27 to redirect things to stderr instead of stdout.
28 28 """
29 29
30 30 import ConfigParser
31 31 import os
32 32 import subprocess32
33 33 import tempfile
34 34 import textwrap
35 35 import pytest
36 36
37 37 import rhodecode
38 38 from rhodecode.model.db import Repository
39 39 from rhodecode.model.meta import Session
40 40 from rhodecode.model.settings import SettingsModel
41 41 from rhodecode.tests import (
42 42 GIT_REPO, HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,)
43 43 from rhodecode.tests.fixture import Fixture
44 44 from rhodecode.tests.utils import is_url_reachable, wait_for_url
45 45
46 46 RC_LOG = os.path.join(tempfile.gettempdir(), 'rc.log')
47 47 REPO_GROUP = 'a_repo_group'
48 48 HG_REPO_WITH_GROUP = '%s/%s' % (REPO_GROUP, HG_REPO)
49 49 GIT_REPO_WITH_GROUP = '%s/%s' % (REPO_GROUP, GIT_REPO)
50 50
51 51
52 52 def assert_no_running_instance(url):
53 53 if is_url_reachable(url):
54 54 print("Hint: Usually this means another instance of Enterprise "
55 55 "is running in the background.")
56 56 pytest.fail(
57 57 "Port is not free at %s, cannot start web interface" % url)
58 58
59 59
60 60 def get_port(pyramid_config):
61 61 config = ConfigParser.ConfigParser()
62 62 config.read(pyramid_config)
63 63 return config.get('server:main', 'port')
64 64
65 65
66 66 def get_host_url(pyramid_config):
67 67 """Construct the host url using the port in the test configuration."""
68 68 return '127.0.0.1:%s' % get_port(pyramid_config)
69 69
70 70
71 71 class RcWebServer(object):
72 72 """
73 73 Represents a running RCE web server used as a test fixture.
74 74 """
75 75 def __init__(self, pyramid_config, log_file):
76 76 self.pyramid_config = pyramid_config
77 77 self.log_file = log_file
78 78
79 79 def repo_clone_url(self, repo_name, **kwargs):
80 80 params = {
81 81 'user': TEST_USER_ADMIN_LOGIN,
82 82 'passwd': TEST_USER_ADMIN_PASS,
83 83 'host': get_host_url(self.pyramid_config),
84 84 'cloned_repo': repo_name,
85 85 }
86 86 params.update(**kwargs)
87 87 _url = 'http://%(user)s:%(passwd)s@%(host)s/%(cloned_repo)s' % params
88 88 return _url
89 89
90 90 def host_url(self):
91 91 return 'http://' + get_host_url(self.pyramid_config)
92 92
93 93 def get_rc_log(self):
94 94 with open(self.log_file) as f:
95 95 return f.read()
96 96
97 97
98 98 @pytest.fixture(scope="module")
99 99 def rcextensions(request, baseapp, tmpdir_factory):
100 100 """
101 101 Installs a testing rcextensions pack to ensure they work as expected.
102 102 """
103 103 init_content = textwrap.dedent("""
104 104 # Forward import the example rcextensions to make it
105 105 # active for our tests.
106 106 from rhodecode.tests.other.example_rcextensions import *
107 107 """)
108 108
109 109 # Note: rcextensions are looked up based on the path of the ini file
110 110 root_path = tmpdir_factory.getbasetemp()
111 111 rcextensions_path = root_path.join('rcextensions')
112 112 init_path = rcextensions_path.join('__init__.py')
113 113
114 114 if rcextensions_path.check():
115 115 pytest.fail(
116 116 "Path for rcextensions already exists, please clean up before "
117 117 "test run this path: %s" % (rcextensions_path, ))
118 118 return
119 119
120 120 request.addfinalizer(rcextensions_path.remove)
121 121 init_path.write_binary(init_content, ensure=True)
122 122
123 123
124 124 @pytest.fixture(scope="module")
125 125 def repos(request, baseapp):
126 126 """Create a copy of each test repo in a repo group."""
127 127 fixture = Fixture()
128 128 repo_group = fixture.create_repo_group(REPO_GROUP)
129 129 repo_group_id = repo_group.group_id
130 130 fixture.create_fork(HG_REPO, HG_REPO,
131 131 repo_name_full=HG_REPO_WITH_GROUP,
132 132 repo_group=repo_group_id)
133 133 fixture.create_fork(GIT_REPO, GIT_REPO,
134 134 repo_name_full=GIT_REPO_WITH_GROUP,
135 135 repo_group=repo_group_id)
136 136
137 137 @request.addfinalizer
138 138 def cleanup():
139 139 fixture.destroy_repo(HG_REPO_WITH_GROUP)
140 140 fixture.destroy_repo(GIT_REPO_WITH_GROUP)
141 141 fixture.destroy_repo_group(repo_group_id)
142 142
143 143
144 144 @pytest.fixture(scope="module")
145 145 def rc_web_server_config(testini_factory):
146 146 """
147 147 Configuration file used for the fixture `rc_web_server`.
148 148 """
149 149 CUSTOM_PARAMS = [
150 150 {'handler_console': {'level': 'DEBUG'}},
151 151 ]
152 152 return testini_factory(CUSTOM_PARAMS)
153 153
154 154
155 155 @pytest.fixture(scope="module")
156 156 def rc_web_server(
157 157 request, baseapp, rc_web_server_config, repos, rcextensions):
158 158 """
159 159 Run the web server as a subprocess.
160 160
161 161 Since we have already a running vcsserver, this is not spawned again.
162 162 """
163 163 env = os.environ.copy()
164 164 env['RC_NO_TMP_PATH'] = '1'
165 165
166 166 rc_log = list(RC_LOG.partition('.log'))
167 167 rc_log.insert(1, get_port(rc_web_server_config))
168 168 rc_log = ''.join(rc_log)
169 169
170 170 server_out = open(rc_log, 'w')
171 171
172 172 host_url = 'http://' + get_host_url(rc_web_server_config)
173 173 assert_no_running_instance(host_url)
174 command = ['pserve', rc_web_server_config]
174 command = ['gunicorn', '--worker-class', 'gevent', '--paste', rc_web_server_config]
175 175
176 176 print('Starting rhodecode server: {}'.format(host_url))
177 177 print('Command: {}'.format(command))
178 178 print('Logfile: {}'.format(rc_log))
179 179
180 180 proc = subprocess32.Popen(
181 181 command, bufsize=0, env=env, stdout=server_out, stderr=server_out)
182 182
183 183 wait_for_url(host_url, timeout=30)
184 184
185 185 @request.addfinalizer
186 186 def stop_web_server():
187 187 # TODO: Find out how to integrate with the reporting of py.test to
188 188 # make this information available.
189 189 print("\nServer log file written to %s" % (rc_log, ))
190 190 proc.kill()
191 191 server_out.flush()
192 192 server_out.close()
193 193
194 194 return RcWebServer(rc_web_server_config, log_file=rc_log)
195 195
196 196
197 197 @pytest.fixture
198 198 def disable_locking(baseapp):
199 199 r = Repository.get_by_repo_name(GIT_REPO)
200 200 Repository.unlock(r)
201 201 r.enable_locking = False
202 202 Session().add(r)
203 203 Session().commit()
204 204
205 205 r = Repository.get_by_repo_name(HG_REPO)
206 206 Repository.unlock(r)
207 207 r.enable_locking = False
208 208 Session().add(r)
209 209 Session().commit()
210 210
211 211
212 212 @pytest.fixture
213 213 def enable_auth_plugins(request, baseapp, csrf_token):
214 214 """
215 215 Return a factory object that when called, allows to control which
216 216 authentication plugins are enabled.
217 217 """
218 218 def _enable_plugins(plugins_list, override=None):
219 219 override = override or {}
220 220 params = {
221 221 'auth_plugins': ','.join(plugins_list),
222 222 }
223 223
224 224 # helper translate some names to others
225 225 name_map = {
226 226 'token': 'authtoken'
227 227 }
228 228
229 229 for module in plugins_list:
230 230 plugin_name = module.partition('#')[-1]
231 231 if plugin_name in name_map:
232 232 plugin_name = name_map[plugin_name]
233 233 enabled_plugin = 'auth_%s_enabled' % plugin_name
234 234 cache_ttl = 'auth_%s_cache_ttl' % plugin_name
235 235
236 236 # default params that are needed for each plugin,
237 237 # `enabled` and `cache_ttl`
238 238 params.update({
239 239 enabled_plugin: True,
240 240 cache_ttl: 0
241 241 })
242 242 if override.get:
243 243 params.update(override.get(module, {}))
244 244
245 245 validated_params = params
246 246 for k, v in validated_params.items():
247 247 setting = SettingsModel().create_or_update_setting(k, v)
248 248 Session().add(setting)
249 249 Session().commit()
250 250
251 251 def cleanup():
252 252 _enable_plugins(['egg:rhodecode-enterprise-ce#rhodecode'])
253 253
254 254 request.addfinalizer(cleanup)
255 255
256 256 return _enable_plugins
257 257
258 258
259 259 @pytest.fixture
260 260 def fs_repo_only(request, rhodecode_fixtures):
261 261 def fs_repo_fabric(repo_name, repo_type):
262 262 rhodecode_fixtures.create_repo(repo_name, repo_type=repo_type)
263 263 rhodecode_fixtures.destroy_repo(repo_name, fs_remove=False)
264 264
265 265 def cleanup():
266 266 rhodecode_fixtures.destroy_repo(repo_name, fs_remove=True)
267 267 rhodecode_fixtures.destroy_repo_on_filesystem(repo_name)
268 268
269 269 request.addfinalizer(cleanup)
270 270
271 271 return fs_repo_fabric
@@ -1,380 +1,400 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22 import json
23 23 import platform
24 24 import socket
25 import tempfile
26
25 27 import subprocess32
26 28 import time
27 29 from urllib2 import urlopen, URLError
28 30
29 31 import configobj
30 32 import pytest
31 33
32 34 import pyramid.paster
33 35
34 36 from rhodecode.lib.pyramid_utils import get_app_config
35 37 from rhodecode.tests.fixture import TestINI
36 38 import rhodecode
39 from rhodecode.tests.other.vcs_operations.conftest import get_host_url, get_port
40
41 VCSSERVER_LOG = os.path.join(tempfile.gettempdir(), 'rc-vcsserver.log')
37 42
38 43
39 44 def _parse_json(value):
40 45 return json.loads(value) if value else None
41 46
42 47
43 48 def pytest_addoption(parser):
44 49 parser.addoption(
45 50 '--test-loglevel', dest='test_loglevel',
46 51 help="Set default Logging level for tests, warn (default), info, debug")
47 52 group = parser.getgroup('pylons')
48 53 group.addoption(
49 54 '--with-pylons', dest='pyramid_config',
50 55 help="Set up a Pylons environment with the specified config file.")
51 56 group.addoption(
52 57 '--ini-config-override', action='store', type=_parse_json,
53 58 default=None, dest='pyramid_config_override', help=(
54 59 "Overrides the .ini file settings. Should be specified in JSON"
55 60 " format, e.g. '{\"section\": {\"parameter\": \"value\", ...}}'"
56 61 )
57 62 )
58 63 parser.addini(
59 64 'pyramid_config',
60 65 "Set up a Pyramid environment with the specified config file.")
61 66
62 67 vcsgroup = parser.getgroup('vcs')
63 68 vcsgroup.addoption(
64 69 '--without-vcsserver', dest='with_vcsserver', action='store_false',
65 70 help="Do not start the VCSServer in a background process.")
66 71 vcsgroup.addoption(
67 72 '--with-vcsserver-http', dest='vcsserver_config_http',
68 73 help="Start the HTTP VCSServer with the specified config file.")
69 74 vcsgroup.addoption(
70 75 '--vcsserver-protocol', dest='vcsserver_protocol',
71 76 help="Start the VCSServer with HTTP protocol support.")
72 77 vcsgroup.addoption(
73 78 '--vcsserver-config-override', action='store', type=_parse_json,
74 79 default=None, dest='vcsserver_config_override', help=(
75 80 "Overrides the .ini file settings for the VCSServer. "
76 81 "Should be specified in JSON "
77 82 "format, e.g. '{\"section\": {\"parameter\": \"value\", ...}}'"
78 83 )
79 84 )
80 85 vcsgroup.addoption(
81 86 '--vcsserver-port', action='store', type=int,
82 87 default=None, help=(
83 88 "Allows to set the port of the vcsserver. Useful when testing "
84 89 "against an already running server and random ports cause "
85 90 "trouble."))
86 91 parser.addini(
87 92 'vcsserver_config_http',
88 93 "Start the HTTP VCSServer with the specified config file.")
89 94 parser.addini(
90 95 'vcsserver_protocol',
91 96 "Start the VCSServer with HTTP protocol support.")
92 97
93 98
94 99 @pytest.fixture(scope='session')
95 100 def vcsserver(request, vcsserver_port, vcsserver_factory):
96 101 """
97 102 Session scope VCSServer.
98 103
99 104 Tests wich need the VCSServer have to rely on this fixture in order
100 105 to ensure it will be running.
101 106
102 107 For specific needs, the fixture vcsserver_factory can be used. It allows to
103 108 adjust the configuration file for the test run.
104 109
105 110 Command line args:
106 111
107 112 --without-vcsserver: Allows to switch this fixture off. You have to
108 113 manually start the server.
109 114
110 115 --vcsserver-port: Will expect the VCSServer to listen on this port.
111 116 """
112 117
113 118 if not request.config.getoption('with_vcsserver'):
114 119 return None
115 120
116 121 use_http = _use_vcs_http_server(request.config)
117 122 return vcsserver_factory(
118 123 request, use_http=use_http, vcsserver_port=vcsserver_port)
119 124
120 125
121 126 @pytest.fixture(scope='session')
122 127 def vcsserver_factory(tmpdir_factory):
123 128 """
124 129 Use this if you need a running vcsserver with a special configuration.
125 130 """
126 131
127 132 def factory(request, use_http=True, overrides=(), vcsserver_port=None):
128 133
129 134 if vcsserver_port is None:
130 135 vcsserver_port = get_available_port()
131 136
132 137 overrides = list(overrides)
133 138 if use_http:
134 139 overrides.append({'server:main': {'port': vcsserver_port}})
135 140 else:
136 141 overrides.append({'DEFAULT': {'port': vcsserver_port}})
137 142
138 143 if is_cygwin():
139 144 platform_override = {'DEFAULT': {
140 145 'beaker.cache.repo_object.type': 'nocache'}}
141 146 overrides.append(platform_override)
142 147
143 148 option_name = 'vcsserver_config_http' if use_http else ''
144 149 override_option_name = 'vcsserver_config_override'
145 150 config_file = get_config(
146 151 request.config, option_name=option_name,
147 152 override_option_name=override_option_name, overrides=overrides,
148 153 basetemp=tmpdir_factory.getbasetemp().strpath,
149 154 prefix='test_vcs_')
150 155
151 156 print("Using the VCSServer configuration:{}".format(config_file))
152 157 ServerClass = HttpVCSServer if use_http else None
153 158 server = ServerClass(config_file)
154 159 server.start()
155 160
156 161 @request.addfinalizer
157 162 def cleanup():
158 163 server.shutdown()
159 164
160 165 server.wait_until_ready()
161 166 return server
162 167
163 168 return factory
164 169
165 170
166 171 def is_cygwin():
167 172 return 'cygwin' in platform.system().lower()
168 173
169 174
170 175 def _use_vcs_http_server(config):
171 176 protocol_option = 'vcsserver_protocol'
172 177 protocol = (
173 178 config.getoption(protocol_option) or
174 179 config.getini(protocol_option) or
175 180 'http')
176 181 return protocol == 'http'
177 182
178 183
179 184 def _use_log_level(config):
180 185 level = config.getoption('test_loglevel') or 'warn'
181 186 return level.upper()
182 187
183 188
184 189 class VCSServer(object):
185 190 """
186 191 Represents a running VCSServer instance.
187 192 """
188 193
189 194 _args = []
190 195
191 196 def start(self):
192 197 print("Starting the VCSServer: {}".format(self._args))
193 198 self.process = subprocess32.Popen(self._args)
194 199
195 200 def wait_until_ready(self, timeout=30):
196 201 raise NotImplementedError()
197 202
198 203 def shutdown(self):
199 204 self.process.kill()
200 205
201 206
202 207 class HttpVCSServer(VCSServer):
203 208 """
204 209 Represents a running VCSServer instance.
205 210 """
206 211 def __init__(self, config_file):
212 self.config_file = config_file
207 213 config_data = configobj.ConfigObj(config_file)
208 214 self._config = config_data['server:main']
209 215
210 args = ['pserve', config_file]
216 args = ['gunicorn', '--workers', '1', '--paste', config_file]
211 217 self._args = args
212 218
213 219 @property
214 220 def http_url(self):
215 221 template = 'http://{host}:{port}/'
216 222 return template.format(**self._config)
217 223
218 224 def start(self):
219 self.process = subprocess32.Popen(self._args)
225 env = os.environ.copy()
226 host_url = 'http://' + get_host_url(self.config_file)
227
228 rc_log = list(VCSSERVER_LOG.partition('.log'))
229 rc_log.insert(1, get_port(self.config_file))
230 rc_log = ''.join(rc_log)
231
232 server_out = open(rc_log, 'w')
233
234 command = ' '.join(self._args)
235 print('Starting rhodecode-vcsserver: {}'.format(host_url))
236 print('Command: {}'.format(command))
237 print('Logfile: {}'.format(rc_log))
238 self.process = subprocess32.Popen(
239 self._args, bufsize=0, env=env, stdout=server_out, stderr=server_out)
220 240
221 241 def wait_until_ready(self, timeout=30):
222 242 host = self._config['host']
223 243 port = self._config['port']
224 244 status_url = 'http://{host}:{port}/status'.format(host=host, port=port)
225 245 start = time.time()
226 246
227 247 while time.time() - start < timeout:
228 248 try:
229 249 urlopen(status_url)
230 250 break
231 251 except URLError:
232 252 time.sleep(0.2)
233 253 else:
234 254 pytest.exit(
235 255 "Starting the VCSServer failed or took more than {} "
236 256 "seconds. cmd: `{}`".format(timeout, ' '.join(self._args)))
237 257
238 258 def shutdown(self):
239 259 self.process.kill()
240 260
241 261
242 262 @pytest.fixture(scope='session')
243 263 def ini_config(request, tmpdir_factory, rcserver_port, vcsserver_port):
244 264 option_name = 'pyramid_config'
245 265 log_level = _use_log_level(request.config)
246 266
247 267 overrides = [
248 268 {'server:main': {'port': rcserver_port}},
249 269 {'app:main': {
250 270 'vcs.server': 'localhost:%s' % vcsserver_port,
251 271 # johbo: We will always start the VCSServer on our own based on the
252 272 # fixtures of the test cases. For the test run it must always be
253 273 # off in the INI file.
254 274 'vcs.start_server': 'false',
255 275 }},
256 276
257 277 {'handler_console': {
258 278 'class ': 'StreamHandler',
259 279 'args ': '(sys.stderr,)',
260 280 'level': log_level,
261 281 }},
262 282
263 283 ]
264 284 if _use_vcs_http_server(request.config):
265 285 overrides.append({
266 286 'app:main': {
267 287 'vcs.server.protocol': 'http',
268 288 'vcs.scm_app_implementation': 'http',
269 289 'vcs.hooks.protocol': 'http',
270 290 }
271 291 })
272 292
273 293 filename = get_config(
274 294 request.config, option_name=option_name,
275 295 override_option_name='{}_override'.format(option_name),
276 296 overrides=overrides,
277 297 basetemp=tmpdir_factory.getbasetemp().strpath,
278 298 prefix='test_rce_')
279 299 return filename
280 300
281 301
282 302 @pytest.fixture(scope='session')
283 303 def ini_settings(ini_config):
284 304 ini_path = ini_config
285 305 return get_app_config(ini_path)
286 306
287 307
288 308 @pytest.fixture(scope='session')
289 309 def rcserver_port(request):
290 310 port = get_available_port()
291 311 print('Using rcserver port {}'.format(port))
292 312 return port
293 313
294 314
295 315 @pytest.fixture(scope='session')
296 316 def vcsserver_port(request):
297 317 port = request.config.getoption('--vcsserver-port')
298 318 if port is None:
299 319 port = get_available_port()
300 320 print('Using vcsserver port {}'.format(port))
301 321 return port
302 322
303 323
304 324 def get_available_port():
305 325 family = socket.AF_INET
306 326 socktype = socket.SOCK_STREAM
307 327 host = '127.0.0.1'
308 328
309 329 mysocket = socket.socket(family, socktype)
310 330 mysocket.bind((host, 0))
311 331 port = mysocket.getsockname()[1]
312 332 mysocket.close()
313 333 del mysocket
314 334 return port
315 335
316 336
317 337 @pytest.fixture(scope='session')
318 338 def available_port_factory():
319 339 """
320 340 Returns a callable which returns free port numbers.
321 341 """
322 342 return get_available_port
323 343
324 344
325 345 @pytest.fixture
326 346 def available_port(available_port_factory):
327 347 """
328 348 Gives you one free port for the current test.
329 349
330 350 Uses "available_port_factory" to retrieve the port.
331 351 """
332 352 return available_port_factory()
333 353
334 354
335 355 @pytest.fixture(scope='session')
336 356 def testini_factory(tmpdir_factory, ini_config):
337 357 """
338 358 Factory to create an INI file based on TestINI.
339 359
340 360 It will make sure to place the INI file in the correct directory.
341 361 """
342 362 basetemp = tmpdir_factory.getbasetemp().strpath
343 363 return TestIniFactory(basetemp, ini_config)
344 364
345 365
346 366 class TestIniFactory(object):
347 367
348 368 def __init__(self, basetemp, template_ini):
349 369 self._basetemp = basetemp
350 370 self._template_ini = template_ini
351 371
352 372 def __call__(self, ini_params, new_file_prefix='test'):
353 373 ini_file = TestINI(
354 374 self._template_ini, ini_params=ini_params,
355 375 new_file_prefix=new_file_prefix, dir=self._basetemp)
356 376 result = ini_file.create()
357 377 return result
358 378
359 379
360 380 def get_config(
361 381 config, option_name, override_option_name, overrides=None,
362 382 basetemp=None, prefix='test'):
363 383 """
364 384 Find a configuration file and apply overrides for the given `prefix`.
365 385 """
366 386 config_file = (
367 387 config.getoption(option_name) or config.getini(option_name))
368 388 if not config_file:
369 389 pytest.exit(
370 390 "Configuration error, could not extract {}.".format(option_name))
371 391
372 392 overrides = overrides or []
373 393 config_override = config.getoption(override_option_name)
374 394 if config_override:
375 395 overrides.append(config_override)
376 396 temp_ini_file = TestINI(
377 397 config_file, ini_params=overrides, new_file_prefix=prefix,
378 398 dir=basetemp)
379 399
380 400 return temp_ini_file.create()
@@ -1,768 +1,693 b''
1 1
2 2
3 3 ################################################################################
4 4 ## RHODECODE COMMUNITY EDITION CONFIGURATION ##
5 5 # The %(here)s variable will be replaced with the parent directory of this file#
6 6 ################################################################################
7 7
8 8 [DEFAULT]
9 9 debug = true
10 10
11 11 ################################################################################
12 12 ## EMAIL CONFIGURATION ##
13 13 ## Uncomment and replace with the email address which should receive ##
14 14 ## any error reports after an application crash ##
15 15 ## Additionally these settings will be used by the RhodeCode mailing system ##
16 16 ################################################################################
17 17
18 18 ## prefix all emails subjects with given prefix, helps filtering out emails
19 19 #email_prefix = [RhodeCode]
20 20
21 21 ## email FROM address all mails will be sent
22 22 #app_email_from = rhodecode-noreply@localhost
23 23
24 24 ## Uncomment and replace with the address which should receive any error report
25 25 ## note: using appenlight for error handling doesn't need this to be uncommented
26 26 #email_to = admin@localhost
27 27
28 28 ## in case of Application errors, sent an error email form
29 29 #error_email_from = rhodecode_error@localhost
30 30
31 31 ## additional error message to be send in case of server crash
32 32 #error_message =
33 33
34 34
35 35 #smtp_server = mail.server.com
36 36 #smtp_username =
37 37 #smtp_password =
38 38 #smtp_port =
39 39 #smtp_use_tls = false
40 40 #smtp_use_ssl = true
41 41 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
42 42 #smtp_auth =
43 43
44 44 [server:main]
45 45 ## COMMON ##
46 46 host = 0.0.0.0
47 47 port = 5000
48 48
49 ##################################
50 ## WAITRESS WSGI SERVER ##
51 ## Recommended for Development ##
52 ##################################
53
54 use = egg:waitress#main
55 ## number of worker threads
56 threads = 5
57 ## MAX BODY SIZE 100GB
58 max_request_body_size = 107374182400
59 ## Use poll instead of select, fixes file descriptors limits problems.
60 ## May not work on old windows systems.
61 asyncore_use_poll = true
62
63
64 49 ##########################
65 50 ## GUNICORN WSGI SERVER ##
66 51 ##########################
67 52 ## run with gunicorn --log-config rhodecode.ini --paste rhodecode.ini
68 53
69 #use = egg:gunicorn#main
54 use = egg:gunicorn#main
70 55 ## Sets the number of process workers. You must set `instance_id = *`
71 56 ## when this option is set to more than one worker, recommended
72 57 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
73 58 ## The `instance_id = *` must be set in the [app:main] section below
74 59 #workers = 2
75 60 ## number of threads for each of the worker, must be set to 1 for gevent
76 61 ## generally recommened to be at 1
77 62 #threads = 1
78 63 ## process name
79 64 #proc_name = rhodecode
80 65 ## type of worker class, one of sync, gevent
81 66 ## recommended for bigger setup is using of of other than sync one
82 67 #worker_class = sync
83 68 ## The maximum number of simultaneous clients. Valid only for Gevent
84 69 #worker_connections = 10
85 70 ## max number of requests that worker will handle before being gracefully
86 71 ## restarted, could prevent memory leaks
87 72 #max_requests = 1000
88 73 #max_requests_jitter = 30
89 74 ## amount of time a worker can spend with handling a request before it
90 75 ## gets killed and restarted. Set to 6hrs
91 76 #timeout = 21600
92 77
93 ## UWSGI ##
94 ## run with uwsgi --ini-paste-logged <inifile.ini>
95 #[uwsgi]
96 #socket = /tmp/uwsgi.sock
97 #master = true
98 #http = 127.0.0.1:5000
99
100 ## set as deamon and redirect all output to file
101 #daemonize = ./uwsgi_rhodecode.log
102
103 ## master process PID
104 #pidfile = ./uwsgi_rhodecode.pid
105
106 ## stats server with workers statistics, use uwsgitop
107 ## for monitoring, `uwsgitop 127.0.0.1:1717`
108 #stats = 127.0.0.1:1717
109 #memory-report = true
110
111 ## log 5XX errors
112 #log-5xx = true
113
114 ## Set the socket listen queue size.
115 #listen = 256
116
117 ## Gracefully Reload workers after the specified amount of managed requests
118 ## (avoid memory leaks).
119 #max-requests = 1000
120
121 ## enable large buffers
122 #buffer-size=65535
123
124 ## socket and http timeouts ##
125 #http-timeout=3600
126 #socket-timeout=3600
127
128 ## Log requests slower than the specified number of milliseconds.
129 #log-slow = 10
130
131 ## Exit if no app can be loaded.
132 #need-app = true
133
134 ## Set lazy mode (load apps in workers instead of master).
135 #lazy = true
136
137 ## scaling ##
138 ## set cheaper algorithm to use, if not set default will be used
139 #cheaper-algo = spare
140
141 ## minimum number of workers to keep at all times
142 #cheaper = 1
143
144 ## number of workers to spawn at startup
145 #cheaper-initial = 1
146
147 ## maximum number of workers that can be spawned
148 #workers = 4
149
150 ## how many workers should be spawned at a time
151 #cheaper-step = 1
152
153 78 ## prefix middleware for RhodeCode.
154 79 ## recommended when using proxy setup.
155 80 ## allows to set RhodeCode under a prefix in server.
156 81 ## eg https://server.com/custom_prefix. Enable `filter-with =` option below as well.
157 82 ## And set your prefix like: `prefix = /custom_prefix`
158 83 ## be sure to also set beaker.session.cookie_path = /custom_prefix if you need
159 84 ## to make your cookies only work on prefix url
160 85 [filter:proxy-prefix]
161 86 use = egg:PasteDeploy#prefix
162 87 prefix = /
163 88
164 89 [app:main]
165 90 is_test = True
166 91 use = egg:rhodecode-enterprise-ce
167 92
168 93 ## enable proxy prefix middleware, defined above
169 94 #filter-with = proxy-prefix
170 95
171 96
172 97 ## RHODECODE PLUGINS ##
173 98 rhodecode.includes = rhodecode.api
174 99
175 100 # api prefix url
176 101 rhodecode.api.url = /_admin/api
177 102
178 103
179 104 ## END RHODECODE PLUGINS ##
180 105
181 106 ## encryption key used to encrypt social plugin tokens,
182 107 ## remote_urls with credentials etc, if not set it defaults to
183 108 ## `beaker.session.secret`
184 109 #rhodecode.encrypted_values.secret =
185 110
186 111 ## decryption strict mode (enabled by default). It controls if decryption raises
187 112 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
188 113 #rhodecode.encrypted_values.strict = false
189 114
190 115 ## return gzipped responses from Rhodecode (static files/application)
191 116 gzip_responses = false
192 117
193 118 ## autogenerate javascript routes file on startup
194 119 generate_js_files = false
195 120
196 121 ## Optional Languages
197 122 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
198 123 lang = en
199 124
200 125 ## perform a full repository scan on each server start, this should be
201 126 ## set to false after first startup, to allow faster server restarts.
202 127 startup.import_repos = true
203 128
204 129 ## Uncomment and set this path to use archive download cache.
205 130 ## Once enabled, generated archives will be cached at this location
206 131 ## and served from the cache during subsequent requests for the same archive of
207 132 ## the repository.
208 133 #archive_cache_dir = /tmp/tarballcache
209 134
210 135 ## URL at which the application is running. This is used for bootstraping
211 136 ## requests in context when no web request is available. Used in ishell, or
212 137 ## SSH calls. Set this for events to receive proper url for SSH calls.
213 138 app.base_url = http://rhodecode.local
214 139
215 140 ## change this to unique ID for security
216 141 app_instance_uuid = rc-production
217 142
218 143 ## cut off limit for large diffs (size in bytes)
219 144 cut_off_limit_diff = 1024000
220 145 cut_off_limit_file = 256000
221 146
222 147 ## use cache version of scm repo everywhere
223 148 vcs_full_cache = false
224 149
225 150 ## force https in RhodeCode, fixes https redirects, assumes it's always https
226 151 ## Normally this is controlled by proper http flags sent from http server
227 152 force_https = false
228 153
229 154 ## use Strict-Transport-Security headers
230 155 use_htsts = false
231 156
232 157 ## number of commits stats will parse on each iteration
233 158 commit_parse_limit = 25
234 159
235 160 ## git rev filter option, --all is the default filter, if you need to
236 161 ## hide all refs in changelog switch this to --branches --tags
237 162 git_rev_filter = --all
238 163
239 164 # Set to true if your repos are exposed using the dumb protocol
240 165 git_update_server_info = false
241 166
242 167 ## RSS/ATOM feed options
243 168 rss_cut_off_limit = 256000
244 169 rss_items_per_page = 10
245 170 rss_include_diff = false
246 171
247 172 ## gist URL alias, used to create nicer urls for gist. This should be an
248 173 ## url that does rewrites to _admin/gists/{gistid}.
249 174 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
250 175 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/{gistid}
251 176 gist_alias_url =
252 177
253 178 ## List of views (using glob pattern syntax) that AUTH TOKENS could be
254 179 ## used for access.
255 180 ## Adding ?auth_token=TOKEN_HASH to the url authenticates this request as if it
256 181 ## came from the the logged in user who own this authentication token.
257 182 ## Additionally @TOKEN syntaxt can be used to bound the view to specific
258 183 ## authentication token. Such view would be only accessible when used together
259 184 ## with this authentication token
260 185 ##
261 186 ## list of all views can be found under `/_admin/permissions/auth_token_access`
262 187 ## The list should be "," separated and on a single line.
263 188 ##
264 189 ## Most common views to enable:
265 190 # RepoCommitsView:repo_commit_download
266 191 # RepoCommitsView:repo_commit_patch
267 192 # RepoCommitsView:repo_commit_raw
268 193 # RepoCommitsView:repo_commit_raw@TOKEN
269 194 # RepoFilesView:repo_files_diff
270 195 # RepoFilesView:repo_archivefile
271 196 # RepoFilesView:repo_file_raw
272 197 # GistView:*
273 198 api_access_controllers_whitelist =
274 199
275 200 ## default encoding used to convert from and to unicode
276 201 ## can be also a comma separated list of encoding in case of mixed encodings
277 202 default_encoding = UTF-8
278 203
279 204 ## instance-id prefix
280 205 ## a prefix key for this instance used for cache invalidation when running
281 206 ## multiple instances of rhodecode, make sure it's globally unique for
282 207 ## all running rhodecode instances. Leave empty if you don't use it
283 208 instance_id =
284 209
285 210 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
286 211 ## of an authentication plugin also if it is disabled by it's settings.
287 212 ## This could be useful if you are unable to log in to the system due to broken
288 213 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
289 214 ## module to log in again and fix the settings.
290 215 ##
291 216 ## Available builtin plugin IDs (hash is part of the ID):
292 217 ## egg:rhodecode-enterprise-ce#rhodecode
293 218 ## egg:rhodecode-enterprise-ce#pam
294 219 ## egg:rhodecode-enterprise-ce#ldap
295 220 ## egg:rhodecode-enterprise-ce#jasig_cas
296 221 ## egg:rhodecode-enterprise-ce#headers
297 222 ## egg:rhodecode-enterprise-ce#crowd
298 223 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
299 224
300 225 ## alternative return HTTP header for failed authentication. Default HTTP
301 226 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
302 227 ## handling that causing a series of failed authentication calls.
303 228 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
304 229 ## This will be served instead of default 401 on bad authnetication
305 230 auth_ret_code =
306 231
307 232 ## use special detection method when serving auth_ret_code, instead of serving
308 233 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
309 234 ## and then serve auth_ret_code to clients
310 235 auth_ret_code_detection = false
311 236
312 237 ## locking return code. When repository is locked return this HTTP code. 2XX
313 238 ## codes don't break the transactions while 4XX codes do
314 239 lock_ret_code = 423
315 240
316 241 ## allows to change the repository location in settings page
317 242 allow_repo_location_change = true
318 243
319 244 ## allows to setup custom hooks in settings page
320 245 allow_custom_hooks_settings = true
321 246
322 247 ## generated license token, goto license page in RhodeCode settings to obtain
323 248 ## new token
324 249 license_token = abra-cada-bra1-rce3
325 250
326 251 ## supervisor connection uri, for managing supervisor and logs.
327 252 supervisor.uri =
328 253 ## supervisord group name/id we only want this RC instance to handle
329 254 supervisor.group_id = dev
330 255
331 256 ## Display extended labs settings
332 257 labs_settings_active = true
333 258
334 259 ####################################
335 260 ### CELERY CONFIG ####
336 261 ####################################
337 262 use_celery = false
338 263 broker.host = localhost
339 264 broker.vhost = rabbitmqhost
340 265 broker.port = 5672
341 266 broker.user = rabbitmq
342 267 broker.password = qweqwe
343 268
344 269 celery.imports = rhodecode.lib.celerylib.tasks
345 270
346 271 celery.result.backend = amqp
347 272 celery.result.dburi = amqp://
348 273 celery.result.serialier = json
349 274
350 275 #celery.send.task.error.emails = true
351 276 #celery.amqp.task.result.expires = 18000
352 277
353 278 celeryd.concurrency = 2
354 279 #celeryd.log.file = celeryd.log
355 280 celeryd.log.level = debug
356 281 celeryd.max.tasks.per.child = 1
357 282
358 283 ## tasks will never be sent to the queue, but executed locally instead.
359 284 celery.always.eager = false
360 285
361 286 ####################################
362 287 ### BEAKER CACHE ####
363 288 ####################################
364 289 # default cache dir for templates. Putting this into a ramdisk
365 290 ## can boost performance, eg. %(here)s/data_ramdisk
366 291 cache_dir = %(here)s/data
367 292
368 293 ## locking and default file storage for Beaker. Putting this into a ramdisk
369 294 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
370 295 beaker.cache.data_dir = %(here)s/rc/data/cache/beaker_data
371 296 beaker.cache.lock_dir = %(here)s/rc/data/cache/beaker_lock
372 297
373 298 beaker.cache.regions = super_short_term, short_term, long_term, sql_cache_short, auth_plugins, repo_cache_long
374 299
375 300 beaker.cache.super_short_term.type = memory
376 301 beaker.cache.super_short_term.expire = 1
377 302 beaker.cache.super_short_term.key_length = 256
378 303
379 304 beaker.cache.short_term.type = memory
380 305 beaker.cache.short_term.expire = 60
381 306 beaker.cache.short_term.key_length = 256
382 307
383 308 beaker.cache.long_term.type = memory
384 309 beaker.cache.long_term.expire = 36000
385 310 beaker.cache.long_term.key_length = 256
386 311
387 312 beaker.cache.sql_cache_short.type = memory
388 313 beaker.cache.sql_cache_short.expire = 1
389 314 beaker.cache.sql_cache_short.key_length = 256
390 315
391 316 ## default is memory cache, configure only if required
392 317 ## using multi-node or multi-worker setup
393 318 #beaker.cache.auth_plugins.type = memory
394 319 #beaker.cache.auth_plugins.lock_dir = %(here)s/data/cache/auth_plugin_lock
395 320 #beaker.cache.auth_plugins.url = postgresql://postgres:secret@localhost/rhodecode
396 321 #beaker.cache.auth_plugins.url = mysql://root:secret@127.0.0.1/rhodecode
397 322 #beaker.cache.auth_plugins.sa.pool_recycle = 3600
398 323 #beaker.cache.auth_plugins.sa.pool_size = 10
399 324 #beaker.cache.auth_plugins.sa.max_overflow = 0
400 325
401 326 beaker.cache.repo_cache_long.type = memorylru_base
402 327 beaker.cache.repo_cache_long.max_items = 4096
403 328 beaker.cache.repo_cache_long.expire = 2592000
404 329
405 330 ## default is memorylru_base cache, configure only if required
406 331 ## using multi-node or multi-worker setup
407 332 #beaker.cache.repo_cache_long.type = ext:memcached
408 333 #beaker.cache.repo_cache_long.url = localhost:11211
409 334 #beaker.cache.repo_cache_long.expire = 1209600
410 335 #beaker.cache.repo_cache_long.key_length = 256
411 336
412 337 ####################################
413 338 ### BEAKER SESSION ####
414 339 ####################################
415 340
416 341 ## .session.type is type of storage options for the session, current allowed
417 342 ## types are file, ext:memcached, ext:database, and memory (default).
418 343 beaker.session.type = file
419 344 beaker.session.data_dir = %(here)s/rc/data/sessions/data
420 345
421 346 ## db based session, fast, and allows easy management over logged in users
422 347 #beaker.session.type = ext:database
423 348 #beaker.session.table_name = db_session
424 349 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
425 350 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
426 351 #beaker.session.sa.pool_recycle = 3600
427 352 #beaker.session.sa.echo = false
428 353
429 354 beaker.session.key = rhodecode
430 355 beaker.session.secret = test-rc-uytcxaz
431 356 beaker.session.lock_dir = %(here)s/rc/data/sessions/lock
432 357
433 358 ## Secure encrypted cookie. Requires AES and AES python libraries
434 359 ## you must disable beaker.session.secret to use this
435 360 #beaker.session.encrypt_key = key_for_encryption
436 361 #beaker.session.validate_key = validation_key
437 362
438 363 ## sets session as invalid(also logging out user) if it haven not been
439 364 ## accessed for given amount of time in seconds
440 365 beaker.session.timeout = 2592000
441 366 beaker.session.httponly = true
442 367 ## Path to use for the cookie. Set to prefix if you use prefix middleware
443 368 #beaker.session.cookie_path = /custom_prefix
444 369
445 370 ## uncomment for https secure cookie
446 371 beaker.session.secure = false
447 372
448 373 ## auto save the session to not to use .save()
449 374 beaker.session.auto = false
450 375
451 376 ## default cookie expiration time in seconds, set to `true` to set expire
452 377 ## at browser close
453 378 #beaker.session.cookie_expires = 3600
454 379
455 380 ###################################
456 381 ## SEARCH INDEXING CONFIGURATION ##
457 382 ###################################
458 383 ## Full text search indexer is available in rhodecode-tools under
459 384 ## `rhodecode-tools index` command
460 385
461 386 ## WHOOSH Backend, doesn't require additional services to run
462 387 ## it works good with few dozen repos
463 388 search.module = rhodecode.lib.index.whoosh
464 389 search.location = %(here)s/data/index
465 390
466 391 ########################################
467 392 ### CHANNELSTREAM CONFIG ####
468 393 ########################################
469 394 ## channelstream enables persistent connections and live notification
470 395 ## in the system. It's also used by the chat system
471 396
472 397 channelstream.enabled = false
473 398
474 399 ## server address for channelstream server on the backend
475 400 channelstream.server = 127.0.0.1:9800
476 401 ## location of the channelstream server from outside world
477 402 ## use ws:// for http or wss:// for https. This address needs to be handled
478 403 ## by external HTTP server such as Nginx or Apache
479 404 ## see nginx/apache configuration examples in our docs
480 405 channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
481 406 channelstream.secret = secret
482 407 channelstream.history.location = %(here)s/channelstream_history
483 408
484 409 ## Internal application path that Javascript uses to connect into.
485 410 ## If you use proxy-prefix the prefix should be added before /_channelstream
486 411 channelstream.proxy_path = /_channelstream
487 412
488 413
489 414 ###################################
490 415 ## APPENLIGHT CONFIG ##
491 416 ###################################
492 417
493 418 ## Appenlight is tailored to work with RhodeCode, see
494 419 ## http://appenlight.com for details how to obtain an account
495 420
496 421 ## appenlight integration enabled
497 422 appenlight = false
498 423
499 424 appenlight.server_url = https://api.appenlight.com
500 425 appenlight.api_key = YOUR_API_KEY
501 426 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
502 427
503 428 # used for JS client
504 429 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
505 430
506 431 ## TWEAK AMOUNT OF INFO SENT HERE
507 432
508 433 ## enables 404 error logging (default False)
509 434 appenlight.report_404 = false
510 435
511 436 ## time in seconds after request is considered being slow (default 1)
512 437 appenlight.slow_request_time = 1
513 438
514 439 ## record slow requests in application
515 440 ## (needs to be enabled for slow datastore recording and time tracking)
516 441 appenlight.slow_requests = true
517 442
518 443 ## enable hooking to application loggers
519 444 appenlight.logging = true
520 445
521 446 ## minimum log level for log capture
522 447 appenlight.logging.level = WARNING
523 448
524 449 ## send logs only from erroneous/slow requests
525 450 ## (saves API quota for intensive logging)
526 451 appenlight.logging_on_error = false
527 452
528 453 ## list of additonal keywords that should be grabbed from environ object
529 454 ## can be string with comma separated list of words in lowercase
530 455 ## (by default client will always send following info:
531 456 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
532 457 ## start with HTTP* this list be extended with additional keywords here
533 458 appenlight.environ_keys_whitelist =
534 459
535 460 ## list of keywords that should be blanked from request object
536 461 ## can be string with comma separated list of words in lowercase
537 462 ## (by default client will always blank keys that contain following words
538 463 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
539 464 ## this list be extended with additional keywords set here
540 465 appenlight.request_keys_blacklist =
541 466
542 467 ## list of namespaces that should be ignores when gathering log entries
543 468 ## can be string with comma separated list of namespaces
544 469 ## (by default the client ignores own entries: appenlight_client.client)
545 470 appenlight.log_namespace_blacklist =
546 471
547 472
548 473 ################################################################################
549 474 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
550 475 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
551 476 ## execute malicious code after an exception is raised. ##
552 477 ################################################################################
553 478 set debug = false
554 479
555 480
556 481 ##############
557 482 ## STYLING ##
558 483 ##############
559 484 debug_style = false
560 485
561 486 ###########################################
562 487 ### MAIN RHODECODE DATABASE CONFIG ###
563 488 ###########################################
564 489 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode_test.db?timeout=30
565 490 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode_test
566 491 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode_test
567 492 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode_test.db?timeout=30
568 493
569 494 # see sqlalchemy docs for other advanced settings
570 495
571 496 ## print the sql statements to output
572 497 sqlalchemy.db1.echo = false
573 498 ## recycle the connections after this amount of seconds
574 499 sqlalchemy.db1.pool_recycle = 3600
575 500 sqlalchemy.db1.convert_unicode = true
576 501
577 502 ## the number of connections to keep open inside the connection pool.
578 503 ## 0 indicates no limit
579 504 #sqlalchemy.db1.pool_size = 5
580 505
581 506 ## the number of connections to allow in connection pool "overflow", that is
582 507 ## connections that can be opened above and beyond the pool_size setting,
583 508 ## which defaults to five.
584 509 #sqlalchemy.db1.max_overflow = 10
585 510
586 511
587 512 ##################
588 513 ### VCS CONFIG ###
589 514 ##################
590 515 vcs.server.enable = true
591 516 vcs.server = localhost:9901
592 517
593 518 ## Web server connectivity protocol, responsible for web based VCS operatations
594 519 ## Available protocols are:
595 520 ## `http` - use http-rpc backend (default)
596 521 vcs.server.protocol = http
597 522
598 523 ## Push/Pull operations protocol, available options are:
599 524 ## `http` - use http-rpc backend (default)
600 525 ## `vcsserver.scm_app` - internal app (EE only)
601 526 vcs.scm_app_implementation = http
602 527
603 528 ## Push/Pull operations hooks protocol, available options are:
604 529 ## `http` - use http-rpc backend (default)
605 530 vcs.hooks.protocol = http
606 531
607 532 vcs.server.log_level = debug
608 533 ## Start VCSServer with this instance as a subprocess, usefull for development
609 534 vcs.start_server = false
610 535
611 536 ## List of enabled VCS backends, available options are:
612 537 ## `hg` - mercurial
613 538 ## `git` - git
614 539 ## `svn` - subversion
615 540 vcs.backends = hg, git, svn
616 541
617 542 vcs.connection_timeout = 3600
618 543 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
619 544 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible, pre-1.9-compatible
620 545 #vcs.svn.compatible_version = pre-1.8-compatible
621 546
622 547
623 548 ############################################################
624 549 ### Subversion proxy support (mod_dav_svn) ###
625 550 ### Maps RhodeCode repo groups into SVN paths for Apache ###
626 551 ############################################################
627 552 ## Enable or disable the config file generation.
628 553 svn.proxy.generate_config = false
629 554 ## Generate config file with `SVNListParentPath` set to `On`.
630 555 svn.proxy.list_parent_path = true
631 556 ## Set location and file name of generated config file.
632 557 svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf
633 558 ## Used as a prefix to the `Location` block in the generated config file.
634 559 ## In most cases it should be set to `/`.
635 560 svn.proxy.location_root = /
636 561 ## Command to reload the mod dav svn configuration on change.
637 562 ## Example: `/etc/init.d/apache2 reload`
638 563 #svn.proxy.reload_cmd = /etc/init.d/apache2 reload
639 564 ## If the timeout expires before the reload command finishes, the command will
640 565 ## be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
641 566 #svn.proxy.reload_timeout = 10
642 567
643 568 ############################################################
644 569 ### SSH Support Settings ###
645 570 ############################################################
646 571
647 572 ## Defines if the authorized_keys file should be written on any change of
648 573 ## user ssh keys, setting this to false also disables posibility of adding
649 574 ## ssh keys for users from web interface.
650 575 ssh.generate_authorized_keyfile = true
651 576
652 577 ## Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
653 578 # ssh.authorized_keys_ssh_opts =
654 579
655 580 ## File to generate the authorized keys together with options
656 581 ## It is possible to have multiple key files specified in `sshd_config` e.g.
657 582 ## AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys_rhodecode
658 583 ssh.authorized_keys_file_path = %(here)s/rc/authorized_keys_rhodecode
659 584
660 585 ## Command to execute the SSH wrapper. The binary is available in the
661 586 ## rhodecode installation directory.
662 587 ## e.g ~/.rccontrol/community-1/profile/bin/rc-ssh-wrapper
663 588 ssh.wrapper_cmd = ~/.rccontrol/community-1/rc-ssh-wrapper
664 589
665 590 ## Allow shell when executing the ssh-wrapper command
666 591 ssh.wrapper_cmd_allow_shell = false
667 592
668 593 ## Enables logging, and detailed output send back to the client. Usefull for
669 594 ## debugging, shouldn't be used in production.
670 595 ssh.enable_debug_logging = false
671 596
672 597 ## Paths to binary executrables, by default they are the names, but we can
673 598 ## override them if we want to use a custom one
674 599 ssh.executable.hg = ~/.rccontrol/vcsserver-1/profile/bin/hg
675 600 ssh.executable.git = ~/.rccontrol/vcsserver-1/profile/bin/git
676 601 ssh.executable.svn = ~/.rccontrol/vcsserver-1/profile/bin/svnserve
677 602
678 603
679 604 ## Dummy marker to add new entries after.
680 605 ## Add any custom entries below. Please don't remove.
681 606 custom.conf = 1
682 607
683 608
684 609 ################################
685 610 ### LOGGING CONFIGURATION ####
686 611 ################################
687 612 [loggers]
688 613 keys = root, sqlalchemy, beaker, rhodecode, ssh_wrapper
689 614
690 615 [handlers]
691 616 keys = console, console_sql
692 617
693 618 [formatters]
694 619 keys = generic, color_formatter, color_formatter_sql
695 620
696 621 #############
697 622 ## LOGGERS ##
698 623 #############
699 624 [logger_root]
700 625 level = NOTSET
701 626 handlers = console
702 627
703 628 [logger_routes]
704 629 level = DEBUG
705 630 handlers =
706 631 qualname = routes.middleware
707 632 ## "level = DEBUG" logs the route matched and routing variables.
708 633 propagate = 1
709 634
710 635 [logger_beaker]
711 636 level = DEBUG
712 637 handlers =
713 638 qualname = beaker.container
714 639 propagate = 1
715 640
716 641 [logger_rhodecode]
717 642 level = DEBUG
718 643 handlers =
719 644 qualname = rhodecode
720 645 propagate = 1
721 646
722 647 [logger_sqlalchemy]
723 648 level = ERROR
724 649 handlers = console_sql
725 650 qualname = sqlalchemy.engine
726 651 propagate = 0
727 652
728 653 [logger_ssh_wrapper]
729 654 level = DEBUG
730 655 handlers =
731 656 qualname = ssh_wrapper
732 657 propagate = 1
733 658
734 659
735 660 ##############
736 661 ## HANDLERS ##
737 662 ##############
738 663
739 664 [handler_console]
740 665 class = StreamHandler
741 666 args = (sys.stderr,)
742 667 level = DEBUG
743 668 formatter = generic
744 669
745 670 [handler_console_sql]
746 671 class = StreamHandler
747 672 args = (sys.stderr,)
748 673 level = WARN
749 674 formatter = generic
750 675
751 676 ################
752 677 ## FORMATTERS ##
753 678 ################
754 679
755 680 [formatter_generic]
756 681 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
757 682 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
758 683 datefmt = %Y-%m-%d %H:%M:%S
759 684
760 685 [formatter_color_formatter]
761 686 class = rhodecode.lib.logging_formatter.ColorFormatter
762 687 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
763 688 datefmt = %Y-%m-%d %H:%M:%S
764 689
765 690 [formatter_color_formatter_sql]
766 691 class = rhodecode.lib.logging_formatter.ColorFormatterSql
767 692 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
768 693 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,209 +1,257 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 import time
21 22 import shutil
22 import time
23 23 import datetime
24 24
25 25 import pytest
26 26
27 27 from rhodecode.lib.vcs.backends import get_backend
28 28 from rhodecode.lib.vcs.backends.base import Config
29 29 from rhodecode.lib.vcs.nodes import FileNode
30 30 from rhodecode.tests import get_new_dir
31 31 from rhodecode.tests.utils import check_skip_backends, check_xfail_backends
32 from rhodecode.tests.vcs.base import BackendTestMixin
33 32
34 33
35 34 @pytest.fixture()
36 35 def vcs_repository_support(
37 36 request, backend_alias, baseapp, _vcs_repo_container):
38 37 """
39 38 Provide a test repository for the test run.
40 39
41 40 Depending on the value of `recreate_repo_per_test` a new repo for each
42 41 test will be created.
43 42
44 43 The parameter `--backends` can be used to limit this fixture to specific
45 44 backend implementations.
46 45 """
47 46 cls = request.cls
48 47
49 48 check_skip_backends(request.node, backend_alias)
50 49 check_xfail_backends(request.node, backend_alias)
51 50
52 51 if _should_create_repo_per_test(cls):
53 52 _vcs_repo_container = _create_vcs_repo_container(request)
54 53
55 54 repo = _vcs_repo_container.get_repo(cls, backend_alias=backend_alias)
56 55
57 56 # TODO: johbo: Supporting old test class api, think about removing this
58 57 cls.repo = repo
59 58 cls.repo_path = repo.path
60 59 cls.default_branch = repo.DEFAULT_BRANCH_NAME
61 60 cls.Backend = cls.backend_class = repo.__class__
62 61 cls.imc = repo.in_memory_commit
63 62
64 return (backend_alias, repo)
63 return backend_alias, repo
65 64
66 65
67 66 @pytest.fixture(scope='class')
68 67 def _vcs_repo_container(request):
69 68 """
70 69 Internal fixture intended to help support class based scoping on demand.
71 70 """
72 71 return _create_vcs_repo_container(request)
73 72
74 73
75 74 def _create_vcs_repo_container(request):
76 75 repo_container = VcsRepoContainer()
77 76 if not request.config.getoption('--keep-tmp-path'):
78 77 request.addfinalizer(repo_container.cleanup)
79 78 return repo_container
80 79
81 80
82 81 class VcsRepoContainer(object):
83 82
84 83 def __init__(self):
85 84 self._cleanup_paths = []
86 85 self._repos = {}
87 86
88 87 def get_repo(self, test_class, backend_alias):
89 88 if backend_alias not in self._repos:
90 89 repo = _create_empty_repository(test_class, backend_alias)
90
91 91 self._cleanup_paths.append(repo.path)
92 92 self._repos[backend_alias] = repo
93 93 return self._repos[backend_alias]
94 94
95 95 def cleanup(self):
96 96 for repo_path in reversed(self._cleanup_paths):
97 97 shutil.rmtree(repo_path)
98 98
99 99
100 100 def _should_create_repo_per_test(cls):
101 101 return getattr(cls, 'recreate_repo_per_test', False)
102 102
103 103
104 104 def _create_empty_repository(cls, backend_alias=None):
105 105 Backend = get_backend(backend_alias or cls.backend_alias)
106 106 repo_path = get_new_dir(str(time.time()))
107 107 repo = Backend(repo_path, create=True)
108 108 if hasattr(cls, '_get_commits'):
109 cls.tip = _add_commits_to_repo(repo, cls._get_commits())
109 commits = cls._get_commits()
110 cls.tip = _add_commits_to_repo(repo, commits)
110 111
111 112 return repo
112 113
113 114
114 115 @pytest.fixture
115 116 def config():
116 117 """
117 118 Instance of a repository config.
118 119
119 120 The instance contains only one value:
120 121
121 122 - Section: "section-a"
122 123 - Key: "a-1"
123 124 - Value: "value-a-1"
124 125
125 126 The intended usage is for cases where a config instance is needed but no
126 127 specific content is required.
127 128 """
128 129 config = Config()
129 130 config.set('section-a', 'a-1', 'value-a-1')
130 131 return config
131 132
132 133
133 134 def _add_commits_to_repo(repo, commits):
134 135 imc = repo.in_memory_commit
135 commit = None
136 tip = None
136 137
137 138 for commit in commits:
138 139 for node in commit.get('added', []):
139 140 imc.add(FileNode(node.path, content=node.content))
140 141 for node in commit.get('changed', []):
141 142 imc.change(FileNode(node.path, content=node.content))
142 143 for node in commit.get('removed', []):
143 144 imc.remove(FileNode(node.path))
144 145
145 commit = imc.commit(
146 tip = imc.commit(
146 147 message=unicode(commit['message']),
147 148 author=unicode(commit['author']),
148 149 date=commit['date'],
149 150 branch=commit.get('branch'))
150 151
151 return commit
152 return tip
152 153
153 154
154 155 @pytest.fixture
155 156 def vcs_repo(request, backend_alias):
156 157 Backend = get_backend(backend_alias)
157 158 repo_path = get_new_dir(str(time.time()))
158 159 repo = Backend(repo_path, create=True)
159 160
160 161 @request.addfinalizer
161 162 def cleanup():
162 163 shutil.rmtree(repo_path)
163 164
164 165 return repo
165 166
166 167
167 168 @pytest.fixture
168 169 def generate_repo_with_commits(vcs_repo):
169 170 """
170 171 Creates a fabric to generate N comits with some file nodes on a randomly
171 172 generated repository
172 173 """
173 174
174 175 def commit_generator(num):
175 176 start_date = datetime.datetime(2010, 1, 1, 20)
176 177 for x in xrange(num):
177 178 yield {
178 179 'message': 'Commit %d' % x,
179 180 'author': 'Joe Doe <joe.doe@example.com>',
180 181 'date': start_date + datetime.timedelta(hours=12 * x),
181 182 'added': [
182 183 FileNode('file_%d.txt' % x, content='Foobar %d' % x),
183 184 ],
184 185 'modified': [
185 186 FileNode('file_%d.txt' % x,
186 187 content='Foobar %d modified' % (x-1)),
187 188 ]
188 189 }
189 190
190 191 def commit_maker(num=5):
191 192 _add_commits_to_repo(vcs_repo, commit_generator(num))
192 193 return vcs_repo
193 194
194 195 return commit_maker
195 196
196 197
197 198 @pytest.fixture
198 199 def hg_repo(request, vcs_repo):
199 200 repo = vcs_repo
200 201
201 commits = BackendTestMixin._get_commits()
202 commits = repo._get_commits()
202 203 _add_commits_to_repo(repo, commits)
203 204
204 205 return repo
205 206
206 207
207 208 @pytest.fixture
208 209 def hg_commit(hg_repo):
209 210 return hg_repo.get_commit()
211
212
213 class BackendTestMixin(object):
214 """
215 This is a backend independent test case class which should be created
216 with ``type`` method.
217
218 It is required to set following attributes at subclass:
219
220 - ``backend_alias``: alias of used backend (see ``vcs.BACKENDS``)
221 - ``repo_path``: path to the repository which would be created for set of
222 tests
223 - ``recreate_repo_per_test``: If set to ``False``, repo would NOT be
224 created
225 before every single test. Defaults to ``True``.
226 """
227 recreate_repo_per_test = True
228
229 @classmethod
230 def _get_commits(cls):
231 commits = [
232 {
233 'message': u'Initial commit',
234 'author': u'Joe Doe <joe.doe@example.com>',
235 'date': datetime.datetime(2010, 1, 1, 20),
236 'added': [
237 FileNode('foobar', content='Foobar'),
238 FileNode('foobar2', content='Foobar II'),
239 FileNode('foo/bar/baz', content='baz here!'),
240 ],
241 },
242 {
243 'message': u'Changes...',
244 'author': u'Jane Doe <jane.doe@example.com>',
245 'date': datetime.datetime(2010, 1, 1, 21),
246 'added': [
247 FileNode('some/new.txt', content='news...'),
248 ],
249 'changed': [
250 FileNode('foobar', 'Foobar I'),
251 ],
252 'removed': [],
253 },
254 ]
255 return commits
256
257
@@ -1,150 +1,151 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import datetime
22 22 import os
23 23 import shutil
24 24 import tarfile
25 25 import tempfile
26 26 import zipfile
27 27 import StringIO
28 28
29 29 import mock
30 30 import pytest
31 31
32 32 from rhodecode.lib.vcs.backends import base
33 33 from rhodecode.lib.vcs.exceptions import ImproperArchiveTypeError, VCSError
34 34 from rhodecode.lib.vcs.nodes import FileNode
35 from rhodecode.tests.vcs.base import BackendTestMixin
35 from rhodecode.tests.vcs.conftest import BackendTestMixin
36 36
37 37
38 @pytest.mark.usefixtures("vcs_repository_support")
38 39 class TestArchives(BackendTestMixin):
39 40
40 41 @pytest.fixture(autouse=True)
41 42 def tempfile(self, request):
42 43 self.temp_file = tempfile.mkstemp()[1]
43 44
44 45 @request.addfinalizer
45 46 def cleanup():
46 47 os.remove(self.temp_file)
47 48
48 49 @classmethod
49 50 def _get_commits(cls):
50 51 start_date = datetime.datetime(2010, 1, 1, 20)
51 for x in xrange(5):
52 for x in range(5):
52 53 yield {
53 54 'message': 'Commit %d' % x,
54 55 'author': 'Joe Doe <joe.doe@example.com>',
55 56 'date': start_date + datetime.timedelta(hours=12 * x),
56 57 'added': [
57 58 FileNode(
58 59 '%d/file_%d.txt' % (x, x), content='Foobar %d' % x),
59 60 ],
60 61 }
61 62
62 63 @pytest.mark.parametrize('compressor', ['gz', 'bz2'])
63 64 def test_archive_tar(self, compressor):
64 65 self.tip.archive_repo(
65 66 self.temp_file, kind='t' + compressor, prefix='repo')
66 67 out_dir = tempfile.mkdtemp()
67 68 out_file = tarfile.open(self.temp_file, 'r|' + compressor)
68 69 out_file.extractall(out_dir)
69 70 out_file.close()
70 71
71 for x in xrange(5):
72 for x in range(5):
72 73 node_path = '%d/file_%d.txt' % (x, x)
73 74 with open(os.path.join(out_dir, 'repo/' + node_path)) as f:
74 75 file_content = f.read()
75 76 assert file_content == self.tip.get_node(node_path).content
76 77
77 78 shutil.rmtree(out_dir)
78 79
79 80 def test_archive_zip(self):
80 81 self.tip.archive_repo(self.temp_file, kind='zip', prefix='repo')
81 82 out = zipfile.ZipFile(self.temp_file)
82 83
83 for x in xrange(5):
84 for x in range(5):
84 85 node_path = '%d/file_%d.txt' % (x, x)
85 86 decompressed = StringIO.StringIO()
86 87 decompressed.write(out.read('repo/' + node_path))
87 88 assert decompressed.getvalue() == \
88 89 self.tip.get_node(node_path).content
89 90 decompressed.close()
90 91
91 92 def test_archive_zip_with_metadata(self):
92 93 self.tip.archive_repo(self.temp_file, kind='zip',
93 94 prefix='repo', write_metadata=True)
94 95
95 96 out = zipfile.ZipFile(self.temp_file)
96 97 metafile = out.read('.archival.txt')
97 98
98 99 raw_id = self.tip.raw_id
99 100 assert 'rev:%s' % raw_id in metafile
100 101
101 for x in xrange(5):
102 for x in range(5):
102 103 node_path = '%d/file_%d.txt' % (x, x)
103 104 decompressed = StringIO.StringIO()
104 105 decompressed.write(out.read('repo/' + node_path))
105 106 assert decompressed.getvalue() == \
106 107 self.tip.get_node(node_path).content
107 108 decompressed.close()
108 109
109 110 def test_archive_wrong_kind(self):
110 111 with pytest.raises(ImproperArchiveTypeError):
111 112 self.tip.archive_repo(self.temp_file, kind='wrong kind')
112 113
113 114
114 115 @pytest.fixture
115 116 def base_commit():
116 117 """
117 118 Prepare a `base.BaseCommit` just enough for `_validate_archive_prefix`.
118 119 """
119 120 commit = base.BaseCommit()
120 121 commit.repository = mock.Mock()
121 122 commit.repository.name = u'fake_repo'
122 123 commit.short_id = 'fake_id'
123 124 return commit
124 125
125 126
126 127 @pytest.mark.parametrize("prefix", [u"unicode-prefix", u"Ünïcödë"])
127 128 def test_validate_archive_prefix_enforces_bytes_as_prefix(prefix, base_commit):
128 129 with pytest.raises(ValueError):
129 130 base_commit._validate_archive_prefix(prefix)
130 131
131 132
132 133 def test_validate_archive_prefix_empty_prefix(base_commit):
133 134 # TODO: johbo: Should raise a ValueError here.
134 135 with pytest.raises(VCSError):
135 136 base_commit._validate_archive_prefix('')
136 137
137 138
138 139 def test_validate_archive_prefix_with_leading_slash(base_commit):
139 140 # TODO: johbo: Should raise a ValueError here.
140 141 with pytest.raises(VCSError):
141 142 base_commit._validate_archive_prefix('/any')
142 143
143 144
144 145 def test_validate_archive_prefix_falls_back_to_repository_name(base_commit):
145 146 prefix = base_commit._validate_archive_prefix(None)
146 147 expected_prefix = base_commit._ARCHIVE_PREFIX_TEMPLATE.format(
147 148 repo_name='fake_repo',
148 149 short_id='fake_id')
149 150 assert isinstance(prefix, str)
150 151 assert prefix == expected_prefix
@@ -1,146 +1,147 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import datetime
22 22
23 23 import pytest
24 24
25 25 from rhodecode.lib.vcs.nodes import FileNode
26 from rhodecode.tests.vcs.base import BackendTestMixin
26 from rhodecode.tests.vcs.conftest import BackendTestMixin
27 27
28 28
29 @pytest.mark.usefixtures("vcs_repository_support")
29 30 class TestBranches(BackendTestMixin):
30 31
31 32 def test_empty_repository_has_no_branches(self, vcsbackend):
32 33 empty_repo = vcsbackend.create_repo()
33 34 assert empty_repo.branches == {}
34 35
35 36 def test_branches_all(self, vcsbackend):
36 37 branch_count = {
37 38 'git': 1,
38 39 'hg': 1,
39 40 'svn': 0,
40 41 }
41 42 assert len(self.repo.branches_all) == branch_count[vcsbackend.alias]
42 43
43 44 def test_closed_branches(self):
44 45 assert len(self.repo.branches_closed) == 0
45 46
46 47 def test_simple(self, local_dt_to_utc):
47 48 tip = self.repo.get_commit()
48 49 assert tip.message == 'Changes...'
49 50 assert tip.date == local_dt_to_utc(datetime.datetime(2010, 1, 1, 21))
50 51
51 52 @pytest.mark.backends("git", "hg")
52 53 def test_new_branch(self):
53 54 # This check must not be removed to ensure the 'branches' LazyProperty
54 55 # gets hit *before* the new 'foobar' branch got created:
55 56 assert 'foobar' not in self.repo.branches
56 57 self.imc.add(FileNode(
57 58 'docs/index.txt',
58 59 content='Documentation\n'))
59 60 foobar_tip = self.imc.commit(
60 61 message=u'New branch: foobar',
61 62 author=u'joe',
62 63 branch='foobar',
63 64 )
64 65 assert 'foobar' in self.repo.branches
65 66 assert foobar_tip.branch == 'foobar'
66 67
67 68 @pytest.mark.backends("git", "hg")
68 69 def test_new_head(self):
69 70 tip = self.repo.get_commit()
70 71 self.imc.add(FileNode(
71 72 'docs/index.txt',
72 73 content='Documentation\n'))
73 74 foobar_tip = self.imc.commit(
74 75 message=u'New branch: foobar',
75 76 author=u'joe',
76 77 branch='foobar',
77 78 parents=[tip],
78 79 )
79 80 self.imc.change(FileNode(
80 81 'docs/index.txt',
81 82 content='Documentation\nand more...\n'))
82 83 newtip = self.imc.commit(
83 84 message=u'At default branch',
84 85 author=u'joe',
85 86 branch=foobar_tip.branch,
86 87 parents=[foobar_tip],
87 88 )
88 89
89 90 newest_tip = self.imc.commit(
90 91 message=u'Merged with %s' % foobar_tip.raw_id,
91 92 author=u'joe',
92 93 branch=self.backend_class.DEFAULT_BRANCH_NAME,
93 94 parents=[newtip, foobar_tip],
94 95 )
95 96
96 97 assert newest_tip.branch == \
97 98 self.backend_class.DEFAULT_BRANCH_NAME
98 99
99 100 @pytest.mark.backends("git", "hg")
100 101 def test_branch_with_slash_in_name(self):
101 102 self.imc.add(FileNode('extrafile', content='Some data\n'))
102 103 self.imc.commit(
103 104 u'Branch with a slash!', author=u'joe',
104 105 branch='issue/123')
105 106 assert 'issue/123' in self.repo.branches
106 107
107 108 @pytest.mark.backends("git", "hg")
108 109 def test_branch_with_slash_in_name_and_similar_without(self):
109 110 self.imc.add(FileNode('extrafile', content='Some data\n'))
110 111 self.imc.commit(
111 112 u'Branch with a slash!', author=u'joe',
112 113 branch='issue/123')
113 114 self.imc.add(FileNode('extrafile II', content='Some data\n'))
114 115 self.imc.commit(
115 116 u'Branch without a slash...', author=u'joe',
116 117 branch='123')
117 118 assert 'issue/123' in self.repo.branches
118 119 assert '123' in self.repo.branches
119 120
120 121
121 122 class TestSvnBranches(object):
122 123
123 124 def test_empty_repository_has_no_tags_and_branches(self, vcsbackend_svn):
124 125 empty_repo = vcsbackend_svn.create_repo()
125 126 assert empty_repo.branches == {}
126 127 assert empty_repo.tags == {}
127 128
128 129 def test_missing_structure_has_no_tags_and_branches(self, vcsbackend_svn):
129 130 repo = vcsbackend_svn.create_repo(number_of_commits=1)
130 131 assert repo.branches == {}
131 132 assert repo.tags == {}
132 133
133 134 def test_discovers_ordered_branches(self, vcsbackend_svn):
134 135 repo = vcsbackend_svn['svn-simple-layout']
135 136 expected_branches = [
136 137 'branches/add-docs',
137 138 'branches/argparse',
138 139 'trunk',
139 140 ]
140 141 assert repo.branches.keys() == expected_branches
141 142
142 143 def test_discovers_ordered_tags(self, vcsbackend_svn):
143 144 repo = vcsbackend_svn['svn-simple-layout']
144 145 expected_tags = [
145 146 'tags/v0.1', 'tags/v0.2', 'tags/v0.3', 'tags/v0.5']
146 147 assert repo.tags.keys() == expected_tags
@@ -1,590 +1,593 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import datetime
22 22 import time
23 23
24 24 import pytest
25 25
26 26 from rhodecode.lib.vcs.backends.base import (
27 27 CollectionGenerator, FILEMODE_DEFAULT, EmptyCommit)
28 28 from rhodecode.lib.vcs.exceptions import (
29 29 BranchDoesNotExistError, CommitDoesNotExistError,
30 30 RepositoryError, EmptyRepositoryError)
31 31 from rhodecode.lib.vcs.nodes import (
32 32 FileNode, AddedFileNodesGenerator,
33 33 ChangedFileNodesGenerator, RemovedFileNodesGenerator)
34 34 from rhodecode.tests import get_new_dir
35 from rhodecode.tests.vcs.base import BackendTestMixin
35 from rhodecode.tests.vcs.conftest import BackendTestMixin
36 36
37 37
38 38 class TestBaseChangeset:
39 39
40 40 def test_is_deprecated(self):
41 41 from rhodecode.lib.vcs.backends.base import BaseChangeset
42 42 pytest.deprecated_call(BaseChangeset)
43 43
44 44
45 class TestEmptyCommit:
45 class TestEmptyCommit(object):
46 46
47 47 def test_branch_without_alias_returns_none(self):
48 48 commit = EmptyCommit()
49 49 assert commit.branch is None
50 50
51 51
52 @pytest.mark.usefixtures("vcs_repository_support")
52 53 class TestCommitsInNonEmptyRepo(BackendTestMixin):
53 54 recreate_repo_per_test = True
54 55
55 56 @classmethod
56 57 def _get_commits(cls):
57 58 start_date = datetime.datetime(2010, 1, 1, 20)
58 59 for x in xrange(5):
59 60 yield {
60 61 'message': 'Commit %d' % x,
61 62 'author': 'Joe Doe <joe.doe@example.com>',
62 63 'date': start_date + datetime.timedelta(hours=12 * x),
63 64 'added': [
64 65 FileNode('file_%d.txt' % x, content='Foobar %d' % x),
65 66 ],
66 67 }
67 68
68 69 def test_walk_returns_empty_list_in_case_of_file(self):
69 70 result = list(self.tip.walk('file_0.txt'))
70 71 assert result == []
71 72
72 73 @pytest.mark.backends("git", "hg")
73 74 def test_new_branch(self):
74 75 self.imc.add(FileNode('docs/index.txt',
75 76 content='Documentation\n'))
76 77 foobar_tip = self.imc.commit(
77 78 message=u'New branch: foobar',
78 79 author=u'joe',
79 80 branch='foobar',
80 81 )
81 82 assert 'foobar' in self.repo.branches
82 83 assert foobar_tip.branch == 'foobar'
83 84 # 'foobar' should be the only branch that contains the new commit
84 85 branch = self.repo.branches.values()
85 86 assert branch[0] != branch[1]
86 87
87 88 @pytest.mark.backends("git", "hg")
88 89 def test_new_head_in_default_branch(self):
89 90 tip = self.repo.get_commit()
90 91 self.imc.add(FileNode('docs/index.txt',
91 92 content='Documentation\n'))
92 93 foobar_tip = self.imc.commit(
93 94 message=u'New branch: foobar',
94 95 author=u'joe',
95 96 branch='foobar',
96 97 parents=[tip],
97 98 )
98 99 self.imc.change(FileNode('docs/index.txt',
99 100 content='Documentation\nand more...\n'))
100 101 newtip = self.imc.commit(
101 102 message=u'At default branch',
102 103 author=u'joe',
103 104 branch=foobar_tip.branch,
104 105 parents=[foobar_tip],
105 106 )
106 107
107 108 newest_tip = self.imc.commit(
108 109 message=u'Merged with %s' % foobar_tip.raw_id,
109 110 author=u'joe',
110 111 branch=self.backend_class.DEFAULT_BRANCH_NAME,
111 112 parents=[newtip, foobar_tip],
112 113 )
113 114
114 115 assert newest_tip.branch == self.backend_class.DEFAULT_BRANCH_NAME
115 116
116 117 @pytest.mark.backends("git", "hg")
117 118 def test_get_commits_respects_branch_name(self):
118 119 """
119 120 * e1930d0 (HEAD, master) Back in default branch
120 121 | * e1930d0 (docs) New Branch: docs2
121 122 | * dcc14fa New branch: docs
122 123 |/
123 124 * e63c41a Initial commit
124 125 ...
125 126 * 624d3db Commit 0
126 127
127 128 :return:
128 129 """
129 130 DEFAULT_BRANCH = self.repo.DEFAULT_BRANCH_NAME
130 131 TEST_BRANCH = 'docs'
131 132 org_tip = self.repo.get_commit()
132 133
133 134 self.imc.add(FileNode('readme.txt', content='Document\n'))
134 135 initial = self.imc.commit(
135 136 message=u'Initial commit',
136 137 author=u'joe',
137 138 parents=[org_tip],
138 139 branch=DEFAULT_BRANCH,)
139 140
140 141 self.imc.add(FileNode('newdoc.txt', content='foobar\n'))
141 142 docs_branch_commit1 = self.imc.commit(
142 143 message=u'New branch: docs',
143 144 author=u'joe',
144 145 parents=[initial],
145 146 branch=TEST_BRANCH,)
146 147
147 148 self.imc.add(FileNode('newdoc2.txt', content='foobar2\n'))
148 149 docs_branch_commit2 = self.imc.commit(
149 150 message=u'New branch: docs2',
150 151 author=u'joe',
151 152 parents=[docs_branch_commit1],
152 153 branch=TEST_BRANCH,)
153 154
154 155 self.imc.add(FileNode('newfile', content='hello world\n'))
155 156 self.imc.commit(
156 157 message=u'Back in default branch',
157 158 author=u'joe',
158 159 parents=[initial],
159 160 branch=DEFAULT_BRANCH,)
160 161
161 162 default_branch_commits = self.repo.get_commits(
162 163 branch_name=DEFAULT_BRANCH)
163 164 assert docs_branch_commit1 not in list(default_branch_commits)
164 165 assert docs_branch_commit2 not in list(default_branch_commits)
165 166
166 167 docs_branch_commits = self.repo.get_commits(
167 168 start_id=self.repo.commit_ids[0], end_id=self.repo.commit_ids[-1],
168 169 branch_name=TEST_BRANCH)
169 170 assert docs_branch_commit1 in list(docs_branch_commits)
170 171 assert docs_branch_commit2 in list(docs_branch_commits)
171 172
172 173 @pytest.mark.backends("svn")
173 174 def test_get_commits_respects_branch_name_svn(self, vcsbackend_svn):
174 175 repo = vcsbackend_svn['svn-simple-layout']
175 176 commits = repo.get_commits(branch_name='trunk')
176 177 commit_indexes = [c.idx for c in commits]
177 178 assert commit_indexes == [1, 2, 3, 7, 12, 15]
178 179
179 180 def test_get_commit_by_branch(self):
180 181 for branch, commit_id in self.repo.branches.iteritems():
181 182 assert commit_id == self.repo.get_commit(branch).raw_id
182 183
183 184 def test_get_commit_by_tag(self):
184 185 for tag, commit_id in self.repo.tags.iteritems():
185 186 assert commit_id == self.repo.get_commit(tag).raw_id
186 187
187 188 def test_get_commit_parents(self):
188 189 repo = self.repo
189 190 for test_idx in [1, 2, 3]:
190 191 commit = repo.get_commit(commit_idx=test_idx - 1)
191 192 assert [commit] == repo.get_commit(commit_idx=test_idx).parents
192 193
193 194 def test_get_commit_children(self):
194 195 repo = self.repo
195 196 for test_idx in [1, 2, 3]:
196 197 commit = repo.get_commit(commit_idx=test_idx + 1)
197 198 assert [commit] == repo.get_commit(commit_idx=test_idx).children
198 199
199 200
201 @pytest.mark.usefixtures("vcs_repository_support")
200 202 class TestCommits(BackendTestMixin):
201 203 recreate_repo_per_test = False
202 204
203 205 @classmethod
204 206 def _get_commits(cls):
205 207 start_date = datetime.datetime(2010, 1, 1, 20)
206 208 for x in xrange(5):
207 209 yield {
208 210 'message': u'Commit %d' % x,
209 211 'author': u'Joe Doe <joe.doe@example.com>',
210 212 'date': start_date + datetime.timedelta(hours=12 * x),
211 213 'added': [
212 214 FileNode('file_%d.txt' % x, content='Foobar %d' % x),
213 215 ],
214 216 }
215 217
216 218 def test_simple(self):
217 219 tip = self.repo.get_commit()
218 220 assert tip.date, datetime.datetime(2010, 1, 3 == 20)
219 221
220 222 def test_simple_serialized_commit(self):
221 223 tip = self.repo.get_commit()
222 224 # json.dumps(tip) uses .__json__() method
223 225 data = tip.__json__()
224 226 assert 'branch' in data
225 227 assert data['revision']
226 228
227 229 def test_retrieve_tip(self):
228 230 tip = self.repo.get_commit('tip')
229 231 assert tip == self.repo.get_commit()
230 232
231 233 def test_invalid(self):
232 234 with pytest.raises(CommitDoesNotExistError):
233 235 self.repo.get_commit(commit_idx=123456789)
234 236
235 237 def test_idx(self):
236 238 commit = self.repo[0]
237 239 assert commit.idx == 0
238 240
239 241 def test_negative_idx(self):
240 242 commit = self.repo.get_commit(commit_idx=-1)
241 243 assert commit.idx >= 0
242 244
243 245 def test_revision_is_deprecated(self):
244 246 def get_revision(commit):
245 247 return commit.revision
246 248
247 249 commit = self.repo[0]
248 250 pytest.deprecated_call(get_revision, commit)
249 251
250 252 def test_size(self):
251 253 tip = self.repo.get_commit()
252 254 size = 5 * len('Foobar N') # Size of 5 files
253 255 assert tip.size == size
254 256
255 257 def test_size_at_commit(self):
256 258 tip = self.repo.get_commit()
257 259 size = 5 * len('Foobar N') # Size of 5 files
258 260 assert self.repo.size_at_commit(tip.raw_id) == size
259 261
260 262 def test_size_at_first_commit(self):
261 263 commit = self.repo[0]
262 264 size = len('Foobar N') # Size of 1 file
263 265 assert self.repo.size_at_commit(commit.raw_id) == size
264 266
265 267 def test_author(self):
266 268 tip = self.repo.get_commit()
267 269 assert_text_equal(tip.author, u'Joe Doe <joe.doe@example.com>')
268 270
269 271 def test_author_name(self):
270 272 tip = self.repo.get_commit()
271 273 assert_text_equal(tip.author_name, u'Joe Doe')
272 274
273 275 def test_author_email(self):
274 276 tip = self.repo.get_commit()
275 277 assert_text_equal(tip.author_email, u'joe.doe@example.com')
276 278
277 279 def test_message(self):
278 280 tip = self.repo.get_commit()
279 281 assert_text_equal(tip.message, u'Commit 4')
280 282
281 283 def test_diff(self):
282 284 tip = self.repo.get_commit()
283 285 diff = tip.diff()
284 286 assert "+Foobar 4" in diff.raw
285 287
286 288 def test_prev(self):
287 289 tip = self.repo.get_commit()
288 290 prev_commit = tip.prev()
289 291 assert prev_commit.message == 'Commit 3'
290 292
291 293 def test_prev_raises_on_first_commit(self):
292 294 commit = self.repo.get_commit(commit_idx=0)
293 295 with pytest.raises(CommitDoesNotExistError):
294 296 commit.prev()
295 297
296 298 def test_prev_works_on_second_commit_issue_183(self):
297 299 commit = self.repo.get_commit(commit_idx=1)
298 300 prev_commit = commit.prev()
299 301 assert prev_commit.idx == 0
300 302
301 303 def test_next(self):
302 304 commit = self.repo.get_commit(commit_idx=2)
303 305 next_commit = commit.next()
304 306 assert next_commit.message == 'Commit 3'
305 307
306 308 def test_next_raises_on_tip(self):
307 309 commit = self.repo.get_commit()
308 310 with pytest.raises(CommitDoesNotExistError):
309 311 commit.next()
310 312
311 313 def test_get_file_commit(self):
312 314 commit = self.repo.get_commit()
313 315 commit.get_file_commit('file_4.txt')
314 316 assert commit.message == 'Commit 4'
315 317
316 318 def test_get_filenodes_generator(self):
317 319 tip = self.repo.get_commit()
318 320 filepaths = [node.path for node in tip.get_filenodes_generator()]
319 321 assert filepaths == ['file_%d.txt' % x for x in xrange(5)]
320 322
321 323 def test_get_file_annotate(self):
322 324 file_added_commit = self.repo.get_commit(commit_idx=3)
323 325 annotations = list(file_added_commit.get_file_annotate('file_3.txt'))
324 326
325 327 line_no, commit_id, commit_loader, line = annotations[0]
326 328
327 329 assert line_no == 1
328 330 assert commit_id == file_added_commit.raw_id
329 331 assert commit_loader() == file_added_commit
330 332 assert 'Foobar 3' in line
331 333
332 334 def test_get_file_annotate_does_not_exist(self):
333 335 file_added_commit = self.repo.get_commit(commit_idx=2)
334 336 # TODO: Should use a specific exception class here?
335 337 with pytest.raises(Exception):
336 338 list(file_added_commit.get_file_annotate('file_3.txt'))
337 339
338 340 def test_get_file_annotate_tip(self):
339 341 tip = self.repo.get_commit()
340 342 commit = self.repo.get_commit(commit_idx=3)
341 343 expected_values = list(commit.get_file_annotate('file_3.txt'))
342 344 annotations = list(tip.get_file_annotate('file_3.txt'))
343 345
344 346 # Note: Skip index 2 because the loader function is not the same
345 347 for idx in (0, 1, 3):
346 348 assert annotations[0][idx] == expected_values[0][idx]
347 349
348 350 def test_get_commits_is_ordered_by_date(self):
349 351 commits = self.repo.get_commits()
350 352 assert isinstance(commits, CollectionGenerator)
351 353 assert len(commits) == 0 or len(commits) != 0
352 354 commits = list(commits)
353 355 ordered_by_date = sorted(commits, key=lambda commit: commit.date)
354 356 assert commits == ordered_by_date
355 357
356 358 def test_get_commits_respects_start(self):
357 359 second_id = self.repo.commit_ids[1]
358 360 commits = self.repo.get_commits(start_id=second_id)
359 361 assert isinstance(commits, CollectionGenerator)
360 362 commits = list(commits)
361 363 assert len(commits) == 4
362 364
363 365 def test_get_commits_includes_start_commit(self):
364 366 second_id = self.repo.commit_ids[1]
365 367 commits = self.repo.get_commits(start_id=second_id)
366 368 assert isinstance(commits, CollectionGenerator)
367 369 commits = list(commits)
368 370 assert commits[0].raw_id == second_id
369 371
370 372 def test_get_commits_respects_end(self):
371 373 second_id = self.repo.commit_ids[1]
372 374 commits = self.repo.get_commits(end_id=second_id)
373 375 assert isinstance(commits, CollectionGenerator)
374 376 commits = list(commits)
375 377 assert commits[-1].raw_id == second_id
376 378 assert len(commits) == 2
377 379
378 380 def test_get_commits_respects_both_start_and_end(self):
379 381 second_id = self.repo.commit_ids[1]
380 382 third_id = self.repo.commit_ids[2]
381 383 commits = self.repo.get_commits(start_id=second_id, end_id=third_id)
382 384 assert isinstance(commits, CollectionGenerator)
383 385 commits = list(commits)
384 386 assert len(commits) == 2
385 387
386 388 def test_get_commits_on_empty_repo_raises_EmptyRepository_error(self):
387 389 repo_path = get_new_dir(str(time.time()))
388 390 repo = self.Backend(repo_path, create=True)
389 391
390 392 with pytest.raises(EmptyRepositoryError):
391 393 list(repo.get_commits(start_id='foobar'))
392 394
393 395 def test_get_commits_respects_hidden(self):
394 396 commits = self.repo.get_commits(show_hidden=True)
395 397 assert isinstance(commits, CollectionGenerator)
396 398 assert len(commits) == 5
397 399
398 400 def test_get_commits_includes_end_commit(self):
399 401 second_id = self.repo.commit_ids[1]
400 402 commits = self.repo.get_commits(end_id=second_id)
401 403 assert isinstance(commits, CollectionGenerator)
402 404 assert len(commits) == 2
403 405 commits = list(commits)
404 406 assert commits[-1].raw_id == second_id
405 407
406 408 def test_get_commits_respects_start_date(self):
407 409 start_date = datetime.datetime(2010, 1, 2)
408 410 commits = self.repo.get_commits(start_date=start_date)
409 411 assert isinstance(commits, CollectionGenerator)
410 412 # Should be 4 commits after 2010-01-02 00:00:00
411 413 assert len(commits) == 4
412 414 for c in commits:
413 415 assert c.date >= start_date
414 416
415 417 def test_get_commits_respects_start_date_with_branch(self):
416 418 start_date = datetime.datetime(2010, 1, 2)
417 419 commits = self.repo.get_commits(
418 420 start_date=start_date, branch_name=self.repo.DEFAULT_BRANCH_NAME)
419 421 assert isinstance(commits, CollectionGenerator)
420 422 # Should be 4 commits after 2010-01-02 00:00:00
421 423 assert len(commits) == 4
422 424 for c in commits:
423 425 assert c.date >= start_date
424 426
425 427 def test_get_commits_respects_start_date_and_end_date(self):
426 428 start_date = datetime.datetime(2010, 1, 2)
427 429 end_date = datetime.datetime(2010, 1, 3)
428 430 commits = self.repo.get_commits(start_date=start_date,
429 431 end_date=end_date)
430 432 assert isinstance(commits, CollectionGenerator)
431 433 assert len(commits) == 2
432 434 for c in commits:
433 435 assert c.date >= start_date
434 436 assert c.date <= end_date
435 437
436 438 def test_get_commits_respects_end_date(self):
437 439 end_date = datetime.datetime(2010, 1, 2)
438 440 commits = self.repo.get_commits(end_date=end_date)
439 441 assert isinstance(commits, CollectionGenerator)
440 442 assert len(commits) == 1
441 443 for c in commits:
442 444 assert c.date <= end_date
443 445
444 446 def test_get_commits_respects_reverse(self):
445 447 commits = self.repo.get_commits() # no longer reverse support
446 448 assert isinstance(commits, CollectionGenerator)
447 449 assert len(commits) == 5
448 450 commit_ids = reversed([c.raw_id for c in commits])
449 451 assert list(commit_ids) == list(reversed(self.repo.commit_ids))
450 452
451 453 def test_get_commits_slice_generator(self):
452 454 commits = self.repo.get_commits(
453 455 branch_name=self.repo.DEFAULT_BRANCH_NAME)
454 456 assert isinstance(commits, CollectionGenerator)
455 457 commit_slice = list(commits[1:3])
456 458 assert len(commit_slice) == 2
457 459
458 460 def test_get_commits_raise_commitdoesnotexist_for_wrong_start(self):
459 461 with pytest.raises(CommitDoesNotExistError):
460 462 list(self.repo.get_commits(start_id='foobar'))
461 463
462 464 def test_get_commits_raise_commitdoesnotexist_for_wrong_end(self):
463 465 with pytest.raises(CommitDoesNotExistError):
464 466 list(self.repo.get_commits(end_id='foobar'))
465 467
466 468 def test_get_commits_raise_branchdoesnotexist_for_wrong_branch_name(self):
467 469 with pytest.raises(BranchDoesNotExistError):
468 470 list(self.repo.get_commits(branch_name='foobar'))
469 471
470 472 def test_get_commits_raise_repositoryerror_for_wrong_start_end(self):
471 473 start_id = self.repo.commit_ids[-1]
472 474 end_id = self.repo.commit_ids[0]
473 475 with pytest.raises(RepositoryError):
474 476 list(self.repo.get_commits(start_id=start_id, end_id=end_id))
475 477
476 478 def test_get_commits_raises_for_numerical_ids(self):
477 479 with pytest.raises(TypeError):
478 480 self.repo.get_commits(start_id=1, end_id=2)
479 481
480 482 def test_commit_equality(self):
481 483 commit1 = self.repo.get_commit(self.repo.commit_ids[0])
482 484 commit2 = self.repo.get_commit(self.repo.commit_ids[1])
483 485
484 486 assert commit1 == commit1
485 487 assert commit2 == commit2
486 488 assert commit1 != commit2
487 489 assert commit2 != commit1
488 490 assert commit1 != None
489 491 assert None != commit1
490 492 assert 1 != commit1
491 493 assert 'string' != commit1
492 494
493 495
494 496 @pytest.mark.parametrize("filename, expected", [
495 497 ("README.rst", False),
496 498 ("README", True),
497 499 ])
498 500 def test_commit_is_link(vcsbackend, filename, expected):
499 501 commit = vcsbackend.repo.get_commit()
500 502 link_status = commit.is_link(filename)
501 503 assert link_status is expected
502 504
503 505
506 @pytest.mark.usefixtures("vcs_repository_support")
504 507 class TestCommitsChanges(BackendTestMixin):
505 508 recreate_repo_per_test = False
506 509
507 510 @classmethod
508 511 def _get_commits(cls):
509 512 return [
510 513 {
511 514 'message': u'Initial',
512 515 'author': u'Joe Doe <joe.doe@example.com>',
513 516 'date': datetime.datetime(2010, 1, 1, 20),
514 517 'added': [
515 518 FileNode('foo/bar', content='foo'),
516 519 FileNode('foo/bał', content='foo'),
517 520 FileNode('foobar', content='foo'),
518 521 FileNode('qwe', content='foo'),
519 522 ],
520 523 },
521 524 {
522 525 'message': u'Massive changes',
523 526 'author': u'Joe Doe <joe.doe@example.com>',
524 527 'date': datetime.datetime(2010, 1, 1, 22),
525 528 'added': [FileNode('fallout', content='War never changes')],
526 529 'changed': [
527 530 FileNode('foo/bar', content='baz'),
528 531 FileNode('foobar', content='baz'),
529 532 ],
530 533 'removed': [FileNode('qwe')],
531 534 },
532 535 ]
533 536
534 537 def test_initial_commit(self, local_dt_to_utc):
535 538 commit = self.repo.get_commit(commit_idx=0)
536 539 assert set(commit.added) == set([
537 540 commit.get_node('foo/bar'),
538 541 commit.get_node('foo/bał'),
539 542 commit.get_node('foobar'),
540 543 commit.get_node('qwe'),
541 544 ])
542 545 assert set(commit.changed) == set()
543 546 assert set(commit.removed) == set()
544 547 assert set(commit.affected_files) == set(
545 548 ['foo/bar', 'foo/bał', 'foobar', 'qwe'])
546 549 assert commit.date == local_dt_to_utc(
547 550 datetime.datetime(2010, 1, 1, 20, 0))
548 551
549 552 def test_head_added(self):
550 553 commit = self.repo.get_commit()
551 554 assert isinstance(commit.added, AddedFileNodesGenerator)
552 555 assert set(commit.added) == set([commit.get_node('fallout')])
553 556 assert isinstance(commit.changed, ChangedFileNodesGenerator)
554 557 assert set(commit.changed) == set([
555 558 commit.get_node('foo/bar'),
556 559 commit.get_node('foobar'),
557 560 ])
558 561 assert isinstance(commit.removed, RemovedFileNodesGenerator)
559 562 assert len(commit.removed) == 1
560 563 assert list(commit.removed)[0].path == 'qwe'
561 564
562 565 def test_get_filemode(self):
563 566 commit = self.repo.get_commit()
564 567 assert FILEMODE_DEFAULT == commit.get_file_mode('foo/bar')
565 568
566 569 def test_get_filemode_non_ascii(self):
567 570 commit = self.repo.get_commit()
568 571 assert FILEMODE_DEFAULT == commit.get_file_mode('foo/bał')
569 572 assert FILEMODE_DEFAULT == commit.get_file_mode(u'foo/bał')
570 573
571 574 def test_get_file_history(self):
572 575 commit = self.repo.get_commit()
573 576 history = commit.get_file_history('foo/bar')
574 577 assert len(history) == 2
575 578
576 579 def test_get_file_history_with_limit(self):
577 580 commit = self.repo.get_commit()
578 581 history = commit.get_file_history('foo/bar', limit=1)
579 582 assert len(history) == 1
580 583
581 584 def test_get_file_history_first_commit(self):
582 585 commit = self.repo[0]
583 586 history = commit.get_file_history('foo/bar')
584 587 assert len(history) == 1
585 588
586 589
587 590 def assert_text_equal(expected, given):
588 591 assert expected == given
589 592 assert isinstance(expected, unicode)
590 593 assert isinstance(given, unicode)
@@ -1,546 +1,548 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import datetime
22 22 import pytest
23 23
24 24 from rhodecode.lib.vcs.nodes import FileNode
25 from rhodecode.tests.vcs.base import BackendTestMixin
25 from rhodecode.tests.vcs.conftest import BackendTestMixin
26 26
27 27
28 28 class TestGetDiffValidation:
29 29
30 30 def test_raises_on_string_input(self, vcsbackend):
31 31 repo = vcsbackend.repo
32 32 with pytest.raises(TypeError):
33 33 repo.get_diff("1", "2")
34 34
35 35 def test_raises_if_commits_not_of_this_repository(self, vcsbackend):
36 36 repo = vcsbackend.repo
37 37 target_repo = vcsbackend.create_repo(number_of_commits=1)
38 38 repo_commit = repo[0]
39 39 wrong_commit = target_repo[0]
40 40 with pytest.raises(ValueError):
41 41 repo.get_diff(repo_commit, wrong_commit)
42 42
43 43 def test_allows_empty_commit(self, vcsbackend):
44 44 repo = vcsbackend.repo
45 45 commit = repo[0]
46 46 repo.get_diff(repo.EMPTY_COMMIT, commit)
47 47
48 48 def test_raise_if_both_commits_are_empty(self, vcsbackend):
49 49 repo = vcsbackend.repo
50 50 empty_commit = repo.EMPTY_COMMIT
51 51 with pytest.raises(ValueError):
52 52 repo.get_diff(empty_commit, empty_commit)
53 53
54 54 def test_supports_path1_parameter(self, vcsbackend):
55 55 repo = vcsbackend.repo
56 56 commit = repo[1]
57 57 repo.get_diff(
58 58 repo.EMPTY_COMMIT, commit,
59 59 path='vcs/__init__.py', path1='vcs/__init__.py')
60 60
61 61 @pytest.mark.backends("git", "hg")
62 62 def test_raises_value_error_if_paths_not_supported(self, vcsbackend):
63 63 repo = vcsbackend.repo
64 64 commit = repo[1]
65 65 with pytest.raises(ValueError):
66 66 repo.get_diff(
67 67 repo.EMPTY_COMMIT, commit,
68 68 path='trunk/example.py', path1='branches/argparse/example.py')
69 69
70 70
71 @pytest.mark.usefixtures("vcs_repository_support")
71 72 class TestRepositoryGetDiff(BackendTestMixin):
72 73
73 74 recreate_repo_per_test = False
74 75
75 76 @classmethod
76 77 def _get_commits(cls):
77 78 commits = [
78 79 {
79 80 'message': 'Initial commit',
80 81 'author': 'Joe Doe <joe.doe@example.com>',
81 82 'date': datetime.datetime(2010, 1, 1, 20),
82 83 'added': [
83 84 FileNode('foobar', content='foobar'),
84 85 FileNode('foobar2', content='foobar2'),
85 86 ],
86 87 },
87 88 {
88 89 'message': 'Changed foobar, added foobar3',
89 90 'author': 'Jane Doe <jane.doe@example.com>',
90 91 'date': datetime.datetime(2010, 1, 1, 21),
91 92 'added': [
92 93 FileNode('foobar3', content='foobar3'),
93 94 ],
94 95 'changed': [
95 96 FileNode('foobar', 'FOOBAR'),
96 97 ],
97 98 },
98 99 {
99 100 'message': 'Removed foobar, changed foobar3',
100 101 'author': 'Jane Doe <jane.doe@example.com>',
101 102 'date': datetime.datetime(2010, 1, 1, 22),
102 103 'changed': [
103 104 FileNode('foobar3', content='FOOBAR\nFOOBAR\nFOOBAR\n'),
104 105 ],
105 106 'removed': [FileNode('foobar')],
106 107 },
107 108 {
108 109 'message': 'Whitespace changes',
109 110 'author': 'Jane Doe <jane.doe@example.com>',
110 111 'date': datetime.datetime(2010, 1, 1, 23),
111 112 'changed': [
112 113 FileNode('foobar3', content='FOOBAR \nFOOBAR\nFOOBAR\n'),
113 114 ],
114 115 },
115 116 ]
116 117 return commits
117 118
118 119 def test_initial_commit_diff(self):
119 120 initial_commit = self.repo[0]
120 121 diff = self.repo.get_diff(self.repo.EMPTY_COMMIT, initial_commit)
121 122 assert diff.raw == self.first_commit_diffs[self.repo.alias]
122 123
123 124 def test_second_commit_diff(self):
124 125 diff = self.repo.get_diff(self.repo[0], self.repo[1])
125 126 assert diff.raw == self.second_commit_diffs[self.repo.alias]
126 127
127 128 def test_third_commit_diff(self):
128 129 diff = self.repo.get_diff(self.repo[1], self.repo[2])
129 130 assert diff.raw == self.third_commit_diffs[self.repo.alias]
130 131
131 132 def test_ignore_whitespace(self):
132 133 diff = self.repo.get_diff(
133 134 self.repo[2], self.repo[3], ignore_whitespace=True)
134 135 assert '@@' not in diff.raw
135 136
136 137 def test_only_one_file(self):
137 138 diff = self.repo.get_diff(
138 139 self.repo.EMPTY_COMMIT, self.repo[0], path='foobar')
139 140 assert 'foobar2' not in diff.raw
140 141
141 142 def test_context_parameter(self):
142 143 first_commit = self.repo.get_commit(commit_idx=0)
143 144 diff = self.repo.get_diff(
144 145 self.repo.EMPTY_COMMIT, first_commit, context=2)
145 146 assert diff.raw == self.first_commit_diffs[self.repo.alias]
146 147
147 148 def test_context_only_one_file(self):
148 149 diff = self.repo.get_diff(
149 150 self.repo.EMPTY_COMMIT, self.repo[0], path='foobar', context=2)
150 151 assert diff.raw == self.first_commit_one_file[self.repo.alias]
151 152
152 153 first_commit_diffs = {
153 154 'git': r"""diff --git a/foobar b/foobar
154 155 new file mode 100644
155 156 index 0000000000000000000000000000000000000000..f6ea0495187600e7b2288c8ac19c5886383a4632
156 157 --- /dev/null
157 158 +++ b/foobar
158 159 @@ -0,0 +1 @@
159 160 +foobar
160 161 \ No newline at end of file
161 162 diff --git a/foobar2 b/foobar2
162 163 new file mode 100644
163 164 index 0000000000000000000000000000000000000000..e8c9d6b98e3dce993a464935e1a53f50b56a3783
164 165 --- /dev/null
165 166 +++ b/foobar2
166 167 @@ -0,0 +1 @@
167 168 +foobar2
168 169 \ No newline at end of file
169 170 """,
170 171 'hg': r"""diff --git a/foobar b/foobar
171 172 new file mode 100644
172 173 --- /dev/null
173 174 +++ b/foobar
174 175 @@ -0,0 +1,1 @@
175 176 +foobar
176 177 \ No newline at end of file
177 178 diff --git a/foobar2 b/foobar2
178 179 new file mode 100644
179 180 --- /dev/null
180 181 +++ b/foobar2
181 182 @@ -0,0 +1,1 @@
182 183 +foobar2
183 184 \ No newline at end of file
184 185 """,
185 186 'svn': """Index: foobar
186 187 ===================================================================
187 188 diff --git a/foobar b/foobar
188 189 new file mode 10644
189 190 --- /dev/null\t(revision 0)
190 191 +++ b/foobar\t(revision 1)
191 192 @@ -0,0 +1 @@
192 193 +foobar
193 194 \\ No newline at end of file
194 195 Index: foobar2
195 196 ===================================================================
196 197 diff --git a/foobar2 b/foobar2
197 198 new file mode 10644
198 199 --- /dev/null\t(revision 0)
199 200 +++ b/foobar2\t(revision 1)
200 201 @@ -0,0 +1 @@
201 202 +foobar2
202 203 \\ No newline at end of file
203 204 """,
204 205 }
205 206
206 207 second_commit_diffs = {
207 208 'git': r"""diff --git a/foobar b/foobar
208 209 index f6ea0495187600e7b2288c8ac19c5886383a4632..389865bb681b358c9b102d79abd8d5f941e96551 100644
209 210 --- a/foobar
210 211 +++ b/foobar
211 212 @@ -1 +1 @@
212 213 -foobar
213 214 \ No newline at end of file
214 215 +FOOBAR
215 216 \ No newline at end of file
216 217 diff --git a/foobar3 b/foobar3
217 218 new file mode 100644
218 219 index 0000000000000000000000000000000000000000..c11c37d41d33fb47741cff93fa5f9d798c1535b0
219 220 --- /dev/null
220 221 +++ b/foobar3
221 222 @@ -0,0 +1 @@
222 223 +foobar3
223 224 \ No newline at end of file
224 225 """,
225 226 'hg': r"""diff --git a/foobar b/foobar
226 227 --- a/foobar
227 228 +++ b/foobar
228 229 @@ -1,1 +1,1 @@
229 230 -foobar
230 231 \ No newline at end of file
231 232 +FOOBAR
232 233 \ No newline at end of file
233 234 diff --git a/foobar3 b/foobar3
234 235 new file mode 100644
235 236 --- /dev/null
236 237 +++ b/foobar3
237 238 @@ -0,0 +1,1 @@
238 239 +foobar3
239 240 \ No newline at end of file
240 241 """,
241 242 'svn': """Index: foobar
242 243 ===================================================================
243 244 diff --git a/foobar b/foobar
244 245 --- a/foobar\t(revision 1)
245 246 +++ b/foobar\t(revision 2)
246 247 @@ -1 +1 @@
247 248 -foobar
248 249 \\ No newline at end of file
249 250 +FOOBAR
250 251 \\ No newline at end of file
251 252 Index: foobar3
252 253 ===================================================================
253 254 diff --git a/foobar3 b/foobar3
254 255 new file mode 10644
255 256 --- /dev/null\t(revision 0)
256 257 +++ b/foobar3\t(revision 2)
257 258 @@ -0,0 +1 @@
258 259 +foobar3
259 260 \\ No newline at end of file
260 261 """,
261 262 }
262 263
263 264 third_commit_diffs = {
264 265 'git': r"""diff --git a/foobar b/foobar
265 266 deleted file mode 100644
266 267 index 389865bb681b358c9b102d79abd8d5f941e96551..0000000000000000000000000000000000000000
267 268 --- a/foobar
268 269 +++ /dev/null
269 270 @@ -1 +0,0 @@
270 271 -FOOBAR
271 272 \ No newline at end of file
272 273 diff --git a/foobar3 b/foobar3
273 274 index c11c37d41d33fb47741cff93fa5f9d798c1535b0..f9324477362684ff692aaf5b9a81e01b9e9a671c 100644
274 275 --- a/foobar3
275 276 +++ b/foobar3
276 277 @@ -1 +1,3 @@
277 278 -foobar3
278 279 \ No newline at end of file
279 280 +FOOBAR
280 281 +FOOBAR
281 282 +FOOBAR
282 283 """,
283 284 'hg': r"""diff --git a/foobar b/foobar
284 285 deleted file mode 100644
285 286 --- a/foobar
286 287 +++ /dev/null
287 288 @@ -1,1 +0,0 @@
288 289 -FOOBAR
289 290 \ No newline at end of file
290 291 diff --git a/foobar3 b/foobar3
291 292 --- a/foobar3
292 293 +++ b/foobar3
293 294 @@ -1,1 +1,3 @@
294 295 -foobar3
295 296 \ No newline at end of file
296 297 +FOOBAR
297 298 +FOOBAR
298 299 +FOOBAR
299 300 """,
300 301 'svn': """Index: foobar
301 302 ===================================================================
302 303 diff --git a/foobar b/foobar
303 304 deleted file mode 10644
304 305 --- a/foobar\t(revision 2)
305 306 +++ /dev/null\t(revision 3)
306 307 @@ -1 +0,0 @@
307 308 -FOOBAR
308 309 \\ No newline at end of file
309 310 Index: foobar3
310 311 ===================================================================
311 312 diff --git a/foobar3 b/foobar3
312 313 --- a/foobar3\t(revision 2)
313 314 +++ b/foobar3\t(revision 3)
314 315 @@ -1 +1,3 @@
315 316 -foobar3
316 317 \\ No newline at end of file
317 318 +FOOBAR
318 319 +FOOBAR
319 320 +FOOBAR
320 321 """,
321 322 }
322 323
323 324 first_commit_one_file = {
324 325 'git': r"""diff --git a/foobar b/foobar
325 326 new file mode 100644
326 327 index 0000000000000000000000000000000000000000..f6ea0495187600e7b2288c8ac19c5886383a4632
327 328 --- /dev/null
328 329 +++ b/foobar
329 330 @@ -0,0 +1 @@
330 331 +foobar
331 332 \ No newline at end of file
332 333 """,
333 334 'hg': r"""diff --git a/foobar b/foobar
334 335 new file mode 100644
335 336 --- /dev/null
336 337 +++ b/foobar
337 338 @@ -0,0 +1,1 @@
338 339 +foobar
339 340 \ No newline at end of file
340 341 """,
341 342 'svn': """Index: foobar
342 343 ===================================================================
343 344 diff --git a/foobar b/foobar
344 345 new file mode 10644
345 346 --- /dev/null\t(revision 0)
346 347 +++ b/foobar\t(revision 1)
347 348 @@ -0,0 +1 @@
348 349 +foobar
349 350 \\ No newline at end of file
350 351 """,
351 352 }
352 353
353 354
354 355 class TestSvnGetDiff:
355 356
356 357 @pytest.mark.parametrize('path, path1', [
357 358 ('trunk/example.py', 'tags/v0.2/example.py'),
358 359 ('trunk', 'tags/v0.2')
359 360 ], ids=['file', 'dir'])
360 361 def test_diff_to_tagged_version(self, vcsbackend_svn, path, path1):
361 362 repo = vcsbackend_svn['svn-simple-layout']
362 363 commit1 = repo[-2]
363 364 commit2 = repo[-1]
364 365 diff = repo.get_diff(commit1, commit2, path=path, path1=path1)
365 366 assert diff.raw == self.expected_diff_v_0_2
366 367
367 368 expected_diff_v_0_2 = '''Index: example.py
368 369 ===================================================================
369 370 diff --git a/example.py b/example.py
370 371 --- a/example.py\t(revision 25)
371 372 +++ b/example.py\t(revision 26)
372 373 @@ -7,8 +7,12 @@
373 374
374 375 @click.command()
375 376 def main():
376 377 + """
377 378 + Will print out a useful message on invocation.
378 379 + """
379 380 click.echo("Hello world!")
380 381
381 382
382 383 +# Main entry point
383 384 if __name__ == '__main__':
384 385 main()
385 386 '''
386 387
387 388 def test_diff_of_moved_directory(self, vcsbackend_svn):
388 389 repo = vcsbackend_svn['svn-move-directory']
389 390 diff = repo.get_diff(repo[0], repo[1])
390 391 # TODO: johbo: Think about supporting svn directory nodes
391 392 # a little bit better, source is here like a file
392 393 expected_diff = """Index: source
393 394 ===================================================================
394 395 diff --git a/source b/source
395 396 deleted file mode 10644
396 397 --- a/source\t(revision 1)
397 398 +++ /dev/null\t(revision 2)
398 399 Index: target/file
399 400 ===================================================================
400 401 diff --git a/target/file b/target/file
401 402 new file mode 10644
402 403 --- /dev/null\t(revision 0)
403 404 +++ b/target/file\t(revision 2)
404 405 """
405 406 assert diff.raw == expected_diff
406 407
407 408
409 @pytest.mark.usefixtures("vcs_repository_support")
408 410 class TestGetDiffBinary(BackendTestMixin):
409 411
410 412 recreate_repo_per_test = False
411 413
412 414 # Note: "Fake" PNG files, has the correct magic as prefix
413 415 BINARY = """\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00"""
414 416 BINARY2 = """\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x01\x00\x00"""
415 417
416 418 @staticmethod
417 419 def _get_commits():
418 420 commits = [
419 421 {
420 422 'message': 'Add binary file image.png',
421 423 'author': 'Joe Doe <joe.deo@example.com>',
422 424 'date': datetime.datetime(2010, 1, 1, 20),
423 425 'added': [
424 426 FileNode('image.png', content=TestGetDiffBinary.BINARY),
425 427 ]},
426 428 {
427 429 'message': 'Modify image.png',
428 430 'author': 'Joe Doe <joe.deo@example.com>',
429 431 'date': datetime.datetime(2010, 1, 1, 21),
430 432 'changed': [
431 433 FileNode('image.png', content=TestGetDiffBinary.BINARY2),
432 434 ]},
433 435 {
434 436 'message': 'Remove image.png',
435 437 'author': 'Joe Doe <joe.deo@example.com>',
436 438 'date': datetime.datetime(2010, 1, 1, 21),
437 439 'removed': [
438 440 FileNode('image.png'),
439 441 ]},
440 442 ]
441 443 return commits
442 444
443 445 def test_add_a_binary_file(self):
444 446 diff = self.repo.get_diff(self.repo.EMPTY_COMMIT, self.repo[0])
445 447
446 448 expected = {
447 449 'git': """diff --git a/image.png b/image.png
448 450 new file mode 100644
449 451 index 0000000000000000000000000000000000000000..28380fd4a25c58be1b68b523ba2a314f4459ee9c
450 452 GIT binary patch
451 453 literal 19
452 454 YcmeAS@N?(olHy`uVBq!ia0vp^03%2O-T(jq
453 455
454 456 literal 0
455 457 HcmV?d00001
456 458
457 459 """,
458 460 'hg': """diff --git a/image.png b/image.png
459 461 new file mode 100644
460 462 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..28380fd4a25c58be1b68b523ba2a314f4459ee9c
461 463 GIT binary patch
462 464 literal 19
463 465 Yc%17D@N?(olHy`uVBq!ia0vp^03%2O-T(jq
464 466
465 467 """,
466 468 'svn': """===================================================================
467 469 Cannot display: file marked as a binary type.
468 470 svn:mime-type = application/octet-stream
469 471 Index: image.png
470 472 ===================================================================
471 473 diff --git a/image.png b/image.png
472 474 new file mode 10644
473 475 --- /dev/null\t(revision 0)
474 476 +++ b/image.png\t(revision 1)
475 477 """,
476 478 }
477 479 assert diff.raw == expected[self.repo.alias]
478 480
479 481 def test_update_a_binary_file(self):
480 482 diff = self.repo.get_diff(self.repo[0], self.repo[1])
481 483
482 484 expected = {
483 485 'git': """diff --git a/image.png b/image.png
484 486 index 28380fd4a25c58be1b68b523ba2a314f4459ee9c..1008a77cd372386a1c24fbd96019333f67ad0065 100644
485 487 GIT binary patch
486 488 literal 19
487 489 acmeAS@N?(olHy`uVBq!ia0y~$U;qFkO9I~j
488 490
489 491 literal 19
490 492 YcmeAS@N?(olHy`uVBq!ia0vp^03%2O-T(jq
491 493
492 494 """,
493 495 'hg': """diff --git a/image.png b/image.png
494 496 index 28380fd4a25c58be1b68b523ba2a314f4459ee9c..1008a77cd372386a1c24fbd96019333f67ad0065
495 497 GIT binary patch
496 498 literal 19
497 499 ac%17D@N?(olHy`uVBq!ia0y~$U;qFkO9I~j
498 500
499 501 """,
500 502 'svn': """===================================================================
501 503 Cannot display: file marked as a binary type.
502 504 svn:mime-type = application/octet-stream
503 505 Index: image.png
504 506 ===================================================================
505 507 diff --git a/image.png b/image.png
506 508 --- a/image.png\t(revision 1)
507 509 +++ b/image.png\t(revision 2)
508 510 """,
509 511 }
510 512 assert diff.raw == expected[self.repo.alias]
511 513
512 514 def test_remove_a_binary_file(self):
513 515 diff = self.repo.get_diff(self.repo[1], self.repo[2])
514 516
515 517 expected = {
516 518 'git': """diff --git a/image.png b/image.png
517 519 deleted file mode 100644
518 520 index 1008a77cd372386a1c24fbd96019333f67ad0065..0000000000000000000000000000000000000000
519 521 GIT binary patch
520 522 literal 0
521 523 HcmV?d00001
522 524
523 525 literal 19
524 526 acmeAS@N?(olHy`uVBq!ia0y~$U;qFkO9I~j
525 527
526 528 """,
527 529 'hg': """diff --git a/image.png b/image.png
528 530 deleted file mode 100644
529 531 index 1008a77cd372386a1c24fbd96019333f67ad0065..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
530 532 GIT binary patch
531 533 literal 0
532 534 Hc$@<O00001
533 535
534 536 """,
535 537 'svn': """===================================================================
536 538 Cannot display: file marked as a binary type.
537 539 svn:mime-type = application/octet-stream
538 540 Index: image.png
539 541 ===================================================================
540 542 diff --git a/image.png b/image.png
541 543 deleted file mode 10644
542 544 --- a/image.png\t(revision 2)
543 545 +++ /dev/null\t(revision 3)
544 546 """,
545 547 }
546 548 assert diff.raw == expected[self.repo.alias]
@@ -1,50 +1,53 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import datetime
22
23 import pytest
22 24 from rhodecode.lib.vcs.nodes import FileNode
23 from rhodecode.tests.vcs.base import BackendTestMixin
25 from rhodecode.tests.vcs.conftest import BackendTestMixin
24 26
25 27
28 @pytest.mark.usefixtures("vcs_repository_support")
26 29 class TestFileNodeUnicodePath(BackendTestMixin):
27 30
28 31 fname = 'ąśðąęłąć.txt'
29 32 ufname = (fname).decode('utf-8')
30 33
31 34 @classmethod
32 35 def _get_commits(cls):
33 36 nodes = [
34 37 FileNode(cls.fname, content='Foobar'),
35 38 ]
36 39
37 40 commits = [
38 41 {
39 42 'message': 'Initial commit',
40 43 'author': 'Joe Doe <joe.doe@example.com>',
41 44 'date': datetime.datetime(2010, 1, 1, 20),
42 45 'added': nodes,
43 46 },
44 47 ]
45 48 return commits
46 49
47 50 def test_filenode_path(self):
48 51 node = self.tip.get_node(self.fname)
49 52 unode = self.tip.get_node(self.ufname)
50 53 assert node == unode
@@ -1,68 +1,69 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import datetime
22 22
23 23 import pytest
24 24
25 25 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
26 26 from rhodecode.lib.vcs.nodes import FileNode
27 from rhodecode.tests.vcs.base import BackendTestMixin
27 from rhodecode.tests.vcs.conftest import BackendTestMixin
28 28
29 29
30 @pytest.mark.usefixtures("vcs_repository_support")
30 31 class TestGetitem(BackendTestMixin):
31 32
32 33 @classmethod
33 34 def _get_commits(cls):
34 35 start_date = datetime.datetime(2010, 1, 1, 20)
35 36 for x in xrange(5):
36 37 yield {
37 38 'message': 'Commit %d' % x,
38 39 'author': 'Joe Doe <joe.doe@example.com>',
39 40 'date': start_date + datetime.timedelta(hours=12 * x),
40 41 'added': [
41 42 FileNode('file_%d.txt' % x, content='Foobar %d' % x),
42 43 ],
43 44 }
44 45
45 46 def test_last_item_is_tip(self):
46 47 assert self.repo[-1] == self.repo.get_commit()
47 48
48 49 @pytest.mark.parametrize("offset, message", [
49 50 (-1, 'Commit 4'),
50 51 (-2, 'Commit 3'),
51 52 (-5, 'Commit 0'),
52 53 ])
53 54 def test_negative_offset_fetches_correct_commit(self, offset, message):
54 55 assert self.repo[offset].message == message
55 56
56 57 def test_returns_correct_items(self):
57 58 commits = [self.repo[x] for x in xrange(len(self.repo.commit_ids))]
58 59 assert commits == list(self.repo.get_commits())
59 60
60 61 def test_raises_for_next_commit(self):
61 62 next_commit_idx = len(self.repo.commit_ids)
62 63 with pytest.raises(CommitDoesNotExistError):
63 64 self.repo[next_commit_idx]
64 65
65 66 def test_raises_for_not_existing_commit_idx(self):
66 67 not_existing_commit_idx = 1000
67 68 with pytest.raises(CommitDoesNotExistError):
68 69 self.repo[not_existing_commit_idx]
@@ -1,75 +1,77 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20 import datetime
21 21
22 import pytest
22 23 from rhodecode.lib.vcs.nodes import FileNode
23 from rhodecode.tests.vcs.base import BackendTestMixin
24 from rhodecode.tests.vcs.conftest import BackendTestMixin
24 25
25 26
27 @pytest.mark.usefixtures("vcs_repository_support")
26 28 class TestGetslice(BackendTestMixin):
27 29
28 30 @classmethod
29 31 def _get_commits(cls):
30 32 start_date = datetime.datetime(2010, 1, 1, 20)
31 33 for x in xrange(5):
32 34 yield {
33 35 'message': 'Commit %d' % x,
34 36 'author': 'Joe Doe <joe.doe@example.com>',
35 37 'date': start_date + datetime.timedelta(hours=12 * x),
36 38 'added': [
37 39 FileNode('file_%d.txt' % x, content='Foobar %d' % x),
38 40 ],
39 41 }
40 42
41 43 def test__getslice__last_item_is_tip(self):
42 44 assert list(self.repo[-1:])[0] == self.repo.get_commit()
43 45
44 46 def test__getslice__respects_start_index(self):
45 47 assert list(self.repo[2:]) == \
46 48 [self.repo.get_commit(commit_id)
47 49 for commit_id in self.repo.commit_ids[2:]]
48 50
49 51 def test__getslice__respects_negative_start_index(self):
50 52 assert list(self.repo[-2:]) == \
51 53 [self.repo.get_commit(commit_id)
52 54 for commit_id in self.repo.commit_ids[-2:]]
53 55
54 56 def test__getslice__respects_end_index(self):
55 57 assert list(self.repo[:2]) == \
56 58 [self.repo.get_commit(commit_id)
57 59 for commit_id in self.repo.commit_ids[:2]]
58 60
59 61 def test__getslice__respects_negative_end_index(self):
60 62 assert list(self.repo[:-2]) == \
61 63 [self.repo.get_commit(commit_id)
62 64 for commit_id in self.repo.commit_ids[:-2]]
63 65
64 66 def test__getslice__start_grater_than_end(self):
65 67 assert list(self.repo[10:0]) == []
66 68
67 69 def test__getslice__negative_iteration(self):
68 70 assert list(self.repo[::-1]) == \
69 71 [self.repo.get_commit(commit_id)
70 72 for commit_id in self.repo.commit_ids[::-1]]
71 73
72 74 def test__getslice__iterate_even(self):
73 75 assert list(self.repo[0:10:2]) == \
74 76 [self.repo.get_commit(commit_id)
75 77 for commit_id in self.repo.commit_ids[0:10:2]]
@@ -1,1269 +1,1271 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import datetime
22 22 import mock
23 23 import os
24 24 import sys
25 25 import shutil
26 26
27 27 import pytest
28 28
29 29 from rhodecode.lib.utils import make_db_config
30 30 from rhodecode.lib.vcs.backends.base import Reference
31 31 from rhodecode.lib.vcs.backends.git import (
32 32 GitRepository, GitCommit, discover_git_version)
33 33 from rhodecode.lib.vcs.exceptions import (
34 34 RepositoryError, VCSError, NodeDoesNotExistError)
35 35 from rhodecode.lib.vcs.nodes import (
36 36 NodeKind, FileNode, DirNode, NodeState, SubModuleNode)
37 37 from rhodecode.tests import TEST_GIT_REPO, TEST_GIT_REPO_CLONE, get_new_dir
38 from rhodecode.tests.vcs.base import BackendTestMixin
38 from rhodecode.tests.vcs.conftest import BackendTestMixin
39 39
40 40
41 41 pytestmark = pytest.mark.backends("git")
42 42
43 43
44 44 def repo_path_generator():
45 45 """
46 46 Return a different path to be used for cloning repos.
47 47 """
48 48 i = 0
49 49 while True:
50 50 i += 1
51 51 yield '%s-%d' % (TEST_GIT_REPO_CLONE, i)
52 52
53 53
54 54 REPO_PATH_GENERATOR = repo_path_generator()
55 55
56 56
57 57 class TestGitRepository:
58 58
59 59 # pylint: disable=protected-access
60 60
61 61 def __check_for_existing_repo(self):
62 62 if os.path.exists(TEST_GIT_REPO_CLONE):
63 63 self.fail('Cannot test git clone repo as location %s already '
64 64 'exists. You should manually remove it first.'
65 65 % TEST_GIT_REPO_CLONE)
66 66
67 67 @pytest.fixture(autouse=True)
68 68 def prepare(self, request, baseapp):
69 69 self.repo = GitRepository(TEST_GIT_REPO, bare=True)
70 70
71 71 def get_clone_repo(self):
72 72 """
73 73 Return a non bare clone of the base repo.
74 74 """
75 75 clone_path = next(REPO_PATH_GENERATOR)
76 76 repo_clone = GitRepository(
77 77 clone_path, create=True, src_url=self.repo.path, bare=False)
78 78
79 79 return repo_clone
80 80
81 81 def get_empty_repo(self, bare=False):
82 82 """
83 83 Return a non bare empty repo.
84 84 """
85 85 return GitRepository(next(REPO_PATH_GENERATOR), create=True, bare=bare)
86 86
87 87 def test_wrong_repo_path(self):
88 88 wrong_repo_path = '/tmp/errorrepo_git'
89 89 with pytest.raises(RepositoryError):
90 90 GitRepository(wrong_repo_path)
91 91
92 92 def test_repo_clone(self):
93 93 self.__check_for_existing_repo()
94 94 repo = GitRepository(TEST_GIT_REPO)
95 95 repo_clone = GitRepository(
96 96 TEST_GIT_REPO_CLONE,
97 97 src_url=TEST_GIT_REPO, create=True, update_after_clone=True)
98 98 assert len(repo.commit_ids) == len(repo_clone.commit_ids)
99 99 # Checking hashes of commits should be enough
100 100 for commit in repo.get_commits():
101 101 raw_id = commit.raw_id
102 102 assert raw_id == repo_clone.get_commit(raw_id).raw_id
103 103
104 104 def test_repo_clone_without_create(self):
105 105 with pytest.raises(RepositoryError):
106 106 GitRepository(
107 107 TEST_GIT_REPO_CLONE + '_wo_create', src_url=TEST_GIT_REPO)
108 108
109 109 def test_repo_clone_with_update(self):
110 110 repo = GitRepository(TEST_GIT_REPO)
111 111 clone_path = TEST_GIT_REPO_CLONE + '_with_update'
112 112 repo_clone = GitRepository(
113 113 clone_path,
114 114 create=True, src_url=TEST_GIT_REPO, update_after_clone=True)
115 115 assert len(repo.commit_ids) == len(repo_clone.commit_ids)
116 116
117 117 # check if current workdir was updated
118 118 fpath = os.path.join(clone_path, 'MANIFEST.in')
119 119 assert os.path.isfile(fpath)
120 120
121 121 def test_repo_clone_without_update(self):
122 122 repo = GitRepository(TEST_GIT_REPO)
123 123 clone_path = TEST_GIT_REPO_CLONE + '_without_update'
124 124 repo_clone = GitRepository(
125 125 clone_path,
126 126 create=True, src_url=TEST_GIT_REPO, update_after_clone=False)
127 127 assert len(repo.commit_ids) == len(repo_clone.commit_ids)
128 128 # check if current workdir was *NOT* updated
129 129 fpath = os.path.join(clone_path, 'MANIFEST.in')
130 130 # Make sure it's not bare repo
131 131 assert not repo_clone.bare
132 132 assert not os.path.isfile(fpath)
133 133
134 134 def test_repo_clone_into_bare_repo(self):
135 135 repo = GitRepository(TEST_GIT_REPO)
136 136 clone_path = TEST_GIT_REPO_CLONE + '_bare.git'
137 137 repo_clone = GitRepository(
138 138 clone_path, create=True, src_url=repo.path, bare=True)
139 139 assert repo_clone.bare
140 140
141 141 def test_create_repo_is_not_bare_by_default(self):
142 142 repo = GitRepository(get_new_dir('not-bare-by-default'), create=True)
143 143 assert not repo.bare
144 144
145 145 def test_create_bare_repo(self):
146 146 repo = GitRepository(get_new_dir('bare-repo'), create=True, bare=True)
147 147 assert repo.bare
148 148
149 149 def test_update_server_info(self):
150 150 self.repo._update_server_info()
151 151
152 152 def test_fetch(self, vcsbackend_git):
153 153 # Note: This is a git specific part of the API, it's only implemented
154 154 # by the git backend.
155 155 source_repo = vcsbackend_git.repo
156 156 target_repo = vcsbackend_git.create_repo()
157 157 target_repo.fetch(source_repo.path)
158 158 # Note: Get a fresh instance, avoids caching trouble
159 159 target_repo = vcsbackend_git.backend(target_repo.path)
160 160 assert len(source_repo.commit_ids) == len(target_repo.commit_ids)
161 161
162 162 def test_commit_ids(self):
163 163 # there are 112 commits (by now)
164 164 # so we can assume they would be available from now on
165 165 subset = set([
166 166 'c1214f7e79e02fc37156ff215cd71275450cffc3',
167 167 '38b5fe81f109cb111f549bfe9bb6b267e10bc557',
168 168 'fa6600f6848800641328adbf7811fd2372c02ab2',
169 169 '102607b09cdd60e2793929c4f90478be29f85a17',
170 170 '49d3fd156b6f7db46313fac355dca1a0b94a0017',
171 171 '2d1028c054665b962fa3d307adfc923ddd528038',
172 172 'd7e0d30fbcae12c90680eb095a4f5f02505ce501',
173 173 'ff7ca51e58c505fec0dd2491de52c622bb7a806b',
174 174 'dd80b0f6cf5052f17cc738c2951c4f2070200d7f',
175 175 '8430a588b43b5d6da365400117c89400326e7992',
176 176 'd955cd312c17b02143c04fa1099a352b04368118',
177 177 'f67b87e5c629c2ee0ba58f85197e423ff28d735b',
178 178 'add63e382e4aabc9e1afdc4bdc24506c269b7618',
179 179 'f298fe1189f1b69779a4423f40b48edf92a703fc',
180 180 'bd9b619eb41994cac43d67cf4ccc8399c1125808',
181 181 '6e125e7c890379446e98980d8ed60fba87d0f6d1',
182 182 'd4a54db9f745dfeba6933bf5b1e79e15d0af20bd',
183 183 '0b05e4ed56c802098dfc813cbe779b2f49e92500',
184 184 '191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e',
185 185 '45223f8f114c64bf4d6f853e3c35a369a6305520',
186 186 'ca1eb7957a54bce53b12d1a51b13452f95bc7c7e',
187 187 'f5ea29fc42ef67a2a5a7aecff10e1566699acd68',
188 188 '27d48942240f5b91dfda77accd2caac94708cc7d',
189 189 '622f0eb0bafd619d2560c26f80f09e3b0b0d78af',
190 190 'e686b958768ee96af8029fe19c6050b1a8dd3b2b'])
191 191 assert subset.issubset(set(self.repo.commit_ids))
192 192
193 193 def test_slicing(self):
194 194 # 4 1 5 10 95
195 195 for sfrom, sto, size in [(0, 4, 4), (1, 2, 1), (10, 15, 5),
196 196 (10, 20, 10), (5, 100, 95)]:
197 197 commit_ids = list(self.repo[sfrom:sto])
198 198 assert len(commit_ids) == size
199 199 assert commit_ids[0] == self.repo.get_commit(commit_idx=sfrom)
200 200 assert commit_ids[-1] == self.repo.get_commit(commit_idx=sto - 1)
201 201
202 202 def test_branches(self):
203 203 # TODO: Need more tests here
204 204 # Removed (those are 'remotes' branches for cloned repo)
205 205 # assert 'master' in self.repo.branches
206 206 # assert 'gittree' in self.repo.branches
207 207 # assert 'web-branch' in self.repo.branches
208 208 for __, commit_id in self.repo.branches.items():
209 209 assert isinstance(self.repo.get_commit(commit_id), GitCommit)
210 210
211 211 def test_tags(self):
212 212 # TODO: Need more tests here
213 213 assert 'v0.1.1' in self.repo.tags
214 214 assert 'v0.1.2' in self.repo.tags
215 215 for __, commit_id in self.repo.tags.items():
216 216 assert isinstance(self.repo.get_commit(commit_id), GitCommit)
217 217
218 218 def _test_single_commit_cache(self, commit_id):
219 219 commit = self.repo.get_commit(commit_id)
220 220 assert commit_id in self.repo.commits
221 221 assert commit is self.repo.commits[commit_id]
222 222
223 223 def test_initial_commit(self):
224 224 commit_id = self.repo.commit_ids[0]
225 225 init_commit = self.repo.get_commit(commit_id)
226 226 init_author = init_commit.author
227 227
228 228 assert init_commit.message == 'initial import\n'
229 229 assert init_author == 'Marcin Kuzminski <marcin@python-blog.com>'
230 230 assert init_author == init_commit.committer
231 231 for path in ('vcs/__init__.py',
232 232 'vcs/backends/BaseRepository.py',
233 233 'vcs/backends/__init__.py'):
234 234 assert isinstance(init_commit.get_node(path), FileNode)
235 235 for path in ('', 'vcs', 'vcs/backends'):
236 236 assert isinstance(init_commit.get_node(path), DirNode)
237 237
238 238 with pytest.raises(NodeDoesNotExistError):
239 239 init_commit.get_node(path='foobar')
240 240
241 241 node = init_commit.get_node('vcs/')
242 242 assert hasattr(node, 'kind')
243 243 assert node.kind == NodeKind.DIR
244 244
245 245 node = init_commit.get_node('vcs')
246 246 assert hasattr(node, 'kind')
247 247 assert node.kind == NodeKind.DIR
248 248
249 249 node = init_commit.get_node('vcs/__init__.py')
250 250 assert hasattr(node, 'kind')
251 251 assert node.kind == NodeKind.FILE
252 252
253 253 def test_not_existing_commit(self):
254 254 with pytest.raises(RepositoryError):
255 255 self.repo.get_commit('f' * 40)
256 256
257 257 def test_commit10(self):
258 258
259 259 commit10 = self.repo.get_commit(self.repo.commit_ids[9])
260 260 README = """===
261 261 VCS
262 262 ===
263 263
264 264 Various Version Control System management abstraction layer for Python.
265 265
266 266 Introduction
267 267 ------------
268 268
269 269 TODO: To be written...
270 270
271 271 """
272 272 node = commit10.get_node('README.rst')
273 273 assert node.kind == NodeKind.FILE
274 274 assert node.content == README
275 275
276 276 def test_head(self):
277 277 assert self.repo.head == self.repo.get_commit().raw_id
278 278
279 279 def test_checkout_with_create(self):
280 280 repo_clone = self.get_clone_repo()
281 281
282 282 new_branch = 'new_branch'
283 283 assert repo_clone._current_branch() == 'master'
284 284 assert set(repo_clone.branches) == set(('master',))
285 285 repo_clone._checkout(new_branch, create=True)
286 286
287 287 # Branches is a lazy property so we need to recrete the Repo object.
288 288 repo_clone = GitRepository(repo_clone.path)
289 289 assert set(repo_clone.branches) == set(('master', new_branch))
290 290 assert repo_clone._current_branch() == new_branch
291 291
292 292 def test_checkout(self):
293 293 repo_clone = self.get_clone_repo()
294 294
295 295 repo_clone._checkout('new_branch', create=True)
296 296 repo_clone._checkout('master')
297 297
298 298 assert repo_clone._current_branch() == 'master'
299 299
300 300 def test_checkout_same_branch(self):
301 301 repo_clone = self.get_clone_repo()
302 302
303 303 repo_clone._checkout('master')
304 304 assert repo_clone._current_branch() == 'master'
305 305
306 306 def test_checkout_branch_already_exists(self):
307 307 repo_clone = self.get_clone_repo()
308 308
309 309 with pytest.raises(RepositoryError):
310 310 repo_clone._checkout('master', create=True)
311 311
312 312 def test_checkout_bare_repo(self):
313 313 with pytest.raises(RepositoryError):
314 314 self.repo._checkout('master')
315 315
316 316 def test_current_branch_bare_repo(self):
317 317 with pytest.raises(RepositoryError):
318 318 self.repo._current_branch()
319 319
320 320 def test_current_branch_empty_repo(self):
321 321 repo = self.get_empty_repo()
322 322 assert repo._current_branch() is None
323 323
324 324 def test_local_clone(self):
325 325 clone_path = next(REPO_PATH_GENERATOR)
326 326 self.repo._local_clone(clone_path, 'master')
327 327 repo_clone = GitRepository(clone_path)
328 328
329 329 assert self.repo.commit_ids == repo_clone.commit_ids
330 330
331 331 def test_local_clone_with_specific_branch(self):
332 332 source_repo = self.get_clone_repo()
333 333
334 334 # Create a new branch in source repo
335 335 new_branch_commit = source_repo.commit_ids[-3]
336 336 source_repo._checkout(new_branch_commit)
337 337 source_repo._checkout('new_branch', create=True)
338 338
339 339 clone_path = next(REPO_PATH_GENERATOR)
340 340 source_repo._local_clone(clone_path, 'new_branch')
341 341 repo_clone = GitRepository(clone_path)
342 342
343 343 assert source_repo.commit_ids[:-3 + 1] == repo_clone.commit_ids
344 344
345 345 clone_path = next(REPO_PATH_GENERATOR)
346 346 source_repo._local_clone(clone_path, 'master')
347 347 repo_clone = GitRepository(clone_path)
348 348
349 349 assert source_repo.commit_ids == repo_clone.commit_ids
350 350
351 351 def test_local_clone_fails_if_target_exists(self):
352 352 with pytest.raises(RepositoryError):
353 353 self.repo._local_clone(self.repo.path, 'master')
354 354
355 355 def test_local_fetch(self):
356 356 target_repo = self.get_empty_repo()
357 357 source_repo = self.get_clone_repo()
358 358
359 359 # Create a new branch in source repo
360 360 master_commit = source_repo.commit_ids[-1]
361 361 new_branch_commit = source_repo.commit_ids[-3]
362 362 source_repo._checkout(new_branch_commit)
363 363 source_repo._checkout('new_branch', create=True)
364 364
365 365 target_repo._local_fetch(source_repo.path, 'new_branch')
366 366 assert target_repo._last_fetch_heads() == [new_branch_commit]
367 367
368 368 target_repo._local_fetch(source_repo.path, 'master')
369 369 assert target_repo._last_fetch_heads() == [master_commit]
370 370
371 371 def test_local_fetch_from_bare_repo(self):
372 372 target_repo = self.get_empty_repo()
373 373 target_repo._local_fetch(self.repo.path, 'master')
374 374
375 375 master_commit = self.repo.commit_ids[-1]
376 376 assert target_repo._last_fetch_heads() == [master_commit]
377 377
378 378 def test_local_fetch_from_same_repo(self):
379 379 with pytest.raises(ValueError):
380 380 self.repo._local_fetch(self.repo.path, 'master')
381 381
382 382 def test_local_fetch_branch_does_not_exist(self):
383 383 target_repo = self.get_empty_repo()
384 384
385 385 with pytest.raises(RepositoryError):
386 386 target_repo._local_fetch(self.repo.path, 'new_branch')
387 387
388 388 def test_local_pull(self):
389 389 target_repo = self.get_empty_repo()
390 390 source_repo = self.get_clone_repo()
391 391
392 392 # Create a new branch in source repo
393 393 master_commit = source_repo.commit_ids[-1]
394 394 new_branch_commit = source_repo.commit_ids[-3]
395 395 source_repo._checkout(new_branch_commit)
396 396 source_repo._checkout('new_branch', create=True)
397 397
398 398 target_repo._local_pull(source_repo.path, 'new_branch')
399 399 target_repo = GitRepository(target_repo.path)
400 400 assert target_repo.head == new_branch_commit
401 401
402 402 target_repo._local_pull(source_repo.path, 'master')
403 403 target_repo = GitRepository(target_repo.path)
404 404 assert target_repo.head == master_commit
405 405
406 406 def test_local_pull_in_bare_repo(self):
407 407 with pytest.raises(RepositoryError):
408 408 self.repo._local_pull(self.repo.path, 'master')
409 409
410 410 def test_local_merge(self):
411 411 target_repo = self.get_empty_repo()
412 412 source_repo = self.get_clone_repo()
413 413
414 414 # Create a new branch in source repo
415 415 master_commit = source_repo.commit_ids[-1]
416 416 new_branch_commit = source_repo.commit_ids[-3]
417 417 source_repo._checkout(new_branch_commit)
418 418 source_repo._checkout('new_branch', create=True)
419 419
420 420 # This is required as one cannot do a -ff-only merge in an empty repo.
421 421 target_repo._local_pull(source_repo.path, 'new_branch')
422 422
423 423 target_repo._local_fetch(source_repo.path, 'master')
424 424 merge_message = 'Merge message\n\nDescription:...'
425 425 user_name = 'Albert Einstein'
426 426 user_email = 'albert@einstein.com'
427 427 target_repo._local_merge(merge_message, user_name, user_email,
428 428 target_repo._last_fetch_heads())
429 429
430 430 target_repo = GitRepository(target_repo.path)
431 431 assert target_repo.commit_ids[-2] == master_commit
432 432 last_commit = target_repo.get_commit(target_repo.head)
433 433 assert last_commit.message.strip() == merge_message
434 434 assert last_commit.author == '%s <%s>' % (user_name, user_email)
435 435
436 436 assert not os.path.exists(
437 437 os.path.join(target_repo.path, '.git', 'MERGE_HEAD'))
438 438
439 439 def test_local_merge_raises_exception_on_conflict(self, vcsbackend_git):
440 440 target_repo = vcsbackend_git.create_repo(number_of_commits=1)
441 441 vcsbackend_git.ensure_file('README', 'I will conflict with you!!!')
442 442
443 443 target_repo._local_fetch(self.repo.path, 'master')
444 444 with pytest.raises(RepositoryError):
445 445 target_repo._local_merge(
446 446 'merge_message', 'user name', 'user@name.com',
447 447 target_repo._last_fetch_heads())
448 448
449 449 # Check we are not left in an intermediate merge state
450 450 assert not os.path.exists(
451 451 os.path.join(target_repo.path, '.git', 'MERGE_HEAD'))
452 452
453 453 def test_local_merge_into_empty_repo(self):
454 454 target_repo = self.get_empty_repo()
455 455
456 456 # This is required as one cannot do a -ff-only merge in an empty repo.
457 457 target_repo._local_fetch(self.repo.path, 'master')
458 458 with pytest.raises(RepositoryError):
459 459 target_repo._local_merge(
460 460 'merge_message', 'user name', 'user@name.com',
461 461 target_repo._last_fetch_heads())
462 462
463 463 def test_local_merge_in_bare_repo(self):
464 464 with pytest.raises(RepositoryError):
465 465 self.repo._local_merge(
466 466 'merge_message', 'user name', 'user@name.com', None)
467 467
468 468 def test_local_push_non_bare(self):
469 469 target_repo = self.get_empty_repo()
470 470
471 471 pushed_branch = 'pushed_branch'
472 472 self.repo._local_push('master', target_repo.path, pushed_branch)
473 473 # Fix the HEAD of the target repo, or otherwise GitRepository won't
474 474 # report any branches.
475 475 with open(os.path.join(target_repo.path, '.git', 'HEAD'), 'w') as f:
476 476 f.write('ref: refs/heads/%s' % pushed_branch)
477 477
478 478 target_repo = GitRepository(target_repo.path)
479 479
480 480 assert (target_repo.branches[pushed_branch] ==
481 481 self.repo.branches['master'])
482 482
483 483 def test_local_push_bare(self):
484 484 target_repo = self.get_empty_repo(bare=True)
485 485
486 486 pushed_branch = 'pushed_branch'
487 487 self.repo._local_push('master', target_repo.path, pushed_branch)
488 488 # Fix the HEAD of the target repo, or otherwise GitRepository won't
489 489 # report any branches.
490 490 with open(os.path.join(target_repo.path, 'HEAD'), 'w') as f:
491 491 f.write('ref: refs/heads/%s' % pushed_branch)
492 492
493 493 target_repo = GitRepository(target_repo.path)
494 494
495 495 assert (target_repo.branches[pushed_branch] ==
496 496 self.repo.branches['master'])
497 497
498 498 def test_local_push_non_bare_target_branch_is_checked_out(self):
499 499 target_repo = self.get_clone_repo()
500 500
501 501 pushed_branch = 'pushed_branch'
502 502 # Create a new branch in source repo
503 503 new_branch_commit = target_repo.commit_ids[-3]
504 504 target_repo._checkout(new_branch_commit)
505 505 target_repo._checkout(pushed_branch, create=True)
506 506
507 507 self.repo._local_push('master', target_repo.path, pushed_branch)
508 508
509 509 target_repo = GitRepository(target_repo.path)
510 510
511 511 assert (target_repo.branches[pushed_branch] ==
512 512 self.repo.branches['master'])
513 513
514 514 def test_local_push_raises_exception_on_conflict(self, vcsbackend_git):
515 515 target_repo = vcsbackend_git.create_repo(number_of_commits=1)
516 516 with pytest.raises(RepositoryError):
517 517 self.repo._local_push('master', target_repo.path, 'master')
518 518
519 519 def test_hooks_can_be_enabled_via_env_variable_for_local_push(self):
520 520 target_repo = self.get_empty_repo(bare=True)
521 521
522 522 with mock.patch.object(self.repo, 'run_git_command') as run_mock:
523 523 self.repo._local_push(
524 524 'master', target_repo.path, 'master', enable_hooks=True)
525 525 env = run_mock.call_args[1]['extra_env']
526 526 assert 'RC_SKIP_HOOKS' not in env
527 527
528 528 def _add_failing_hook(self, repo_path, hook_name, bare=False):
529 529 path_components = (
530 530 ['hooks', hook_name] if bare else ['.git', 'hooks', hook_name])
531 531 hook_path = os.path.join(repo_path, *path_components)
532 532 with open(hook_path, 'w') as f:
533 533 script_lines = [
534 534 '#!%s' % sys.executable,
535 535 'import os',
536 536 'import sys',
537 537 'if os.environ.get("RC_SKIP_HOOKS"):',
538 538 ' sys.exit(0)',
539 539 'sys.exit(1)',
540 540 ]
541 541 f.write('\n'.join(script_lines))
542 542 os.chmod(hook_path, 0755)
543 543
544 544 def test_local_push_does_not_execute_hook(self):
545 545 target_repo = self.get_empty_repo()
546 546
547 547 pushed_branch = 'pushed_branch'
548 548 self._add_failing_hook(target_repo.path, 'pre-receive')
549 549 self.repo._local_push('master', target_repo.path, pushed_branch)
550 550 # Fix the HEAD of the target repo, or otherwise GitRepository won't
551 551 # report any branches.
552 552 with open(os.path.join(target_repo.path, '.git', 'HEAD'), 'w') as f:
553 553 f.write('ref: refs/heads/%s' % pushed_branch)
554 554
555 555 target_repo = GitRepository(target_repo.path)
556 556
557 557 assert (target_repo.branches[pushed_branch] ==
558 558 self.repo.branches['master'])
559 559
560 560 def test_local_push_executes_hook(self):
561 561 target_repo = self.get_empty_repo(bare=True)
562 562 self._add_failing_hook(target_repo.path, 'pre-receive', bare=True)
563 563 with pytest.raises(RepositoryError):
564 564 self.repo._local_push(
565 565 'master', target_repo.path, 'master', enable_hooks=True)
566 566
567 567 def test_maybe_prepare_merge_workspace(self):
568 568 workspace = self.repo._maybe_prepare_merge_workspace(
569 569 'pr2', Reference('branch', 'master', 'unused'))
570 570
571 571 assert os.path.isdir(workspace)
572 572 workspace_repo = GitRepository(workspace)
573 573 assert workspace_repo.branches == self.repo.branches
574 574
575 575 # Calling it a second time should also succeed
576 576 workspace = self.repo._maybe_prepare_merge_workspace(
577 577 'pr2', Reference('branch', 'master', 'unused'))
578 578 assert os.path.isdir(workspace)
579 579
580 580 def test_cleanup_merge_workspace(self):
581 581 workspace = self.repo._maybe_prepare_merge_workspace(
582 582 'pr3', Reference('branch', 'master', 'unused'))
583 583 self.repo.cleanup_merge_workspace('pr3')
584 584
585 585 assert not os.path.exists(workspace)
586 586
587 587 def test_cleanup_merge_workspace_invalid_workspace_id(self):
588 588 # No assert: because in case of an inexistent workspace this function
589 589 # should still succeed.
590 590 self.repo.cleanup_merge_workspace('pr4')
591 591
592 592 def test_set_refs(self):
593 593 test_ref = 'refs/test-refs/abcde'
594 594 test_commit_id = 'ecb86e1f424f2608262b130db174a7dfd25a6623'
595 595
596 596 self.repo.set_refs(test_ref, test_commit_id)
597 597 stdout, _ = self.repo.run_git_command(['show-ref'])
598 598 assert test_ref in stdout
599 599 assert test_commit_id in stdout
600 600
601 601 def test_remove_ref(self):
602 602 test_ref = 'refs/test-refs/abcde'
603 603 test_commit_id = 'ecb86e1f424f2608262b130db174a7dfd25a6623'
604 604 self.repo.set_refs(test_ref, test_commit_id)
605 605 stdout, _ = self.repo.run_git_command(['show-ref'])
606 606 assert test_ref in stdout
607 607 assert test_commit_id in stdout
608 608
609 609 self.repo.remove_ref(test_ref)
610 610 stdout, _ = self.repo.run_git_command(['show-ref'])
611 611 assert test_ref not in stdout
612 612 assert test_commit_id not in stdout
613 613
614 614
615 615 class TestGitCommit(object):
616 616
617 617 @pytest.fixture(autouse=True)
618 618 def prepare(self):
619 619 self.repo = GitRepository(TEST_GIT_REPO)
620 620
621 621 def test_default_commit(self):
622 622 tip = self.repo.get_commit()
623 623 assert tip == self.repo.get_commit(None)
624 624 assert tip == self.repo.get_commit('tip')
625 625
626 626 def test_root_node(self):
627 627 tip = self.repo.get_commit()
628 628 assert tip.root is tip.get_node('')
629 629
630 630 def test_lazy_fetch(self):
631 631 """
632 632 Test if commit's nodes expands and are cached as we walk through
633 633 the commit. This test is somewhat hard to write as order of tests
634 634 is a key here. Written by running command after command in a shell.
635 635 """
636 636 commit_id = '2a13f185e4525f9d4b59882791a2d397b90d5ddc'
637 637 assert commit_id in self.repo.commit_ids
638 638 commit = self.repo.get_commit(commit_id)
639 639 assert len(commit.nodes) == 0
640 640 root = commit.root
641 641 assert len(commit.nodes) == 1
642 642 assert len(root.nodes) == 8
643 643 # accessing root.nodes updates commit.nodes
644 644 assert len(commit.nodes) == 9
645 645
646 646 docs = root.get_node('docs')
647 647 # we haven't yet accessed anything new as docs dir was already cached
648 648 assert len(commit.nodes) == 9
649 649 assert len(docs.nodes) == 8
650 650 # accessing docs.nodes updates commit.nodes
651 651 assert len(commit.nodes) == 17
652 652
653 653 assert docs is commit.get_node('docs')
654 654 assert docs is root.nodes[0]
655 655 assert docs is root.dirs[0]
656 656 assert docs is commit.get_node('docs')
657 657
658 658 def test_nodes_with_commit(self):
659 659 commit_id = '2a13f185e4525f9d4b59882791a2d397b90d5ddc'
660 660 commit = self.repo.get_commit(commit_id)
661 661 root = commit.root
662 662 docs = root.get_node('docs')
663 663 assert docs is commit.get_node('docs')
664 664 api = docs.get_node('api')
665 665 assert api is commit.get_node('docs/api')
666 666 index = api.get_node('index.rst')
667 667 assert index is commit.get_node('docs/api/index.rst')
668 668 assert index is commit.get_node('docs')\
669 669 .get_node('api')\
670 670 .get_node('index.rst')
671 671
672 672 def test_branch_and_tags(self):
673 673 """
674 674 rev0 = self.repo.commit_ids[0]
675 675 commit0 = self.repo.get_commit(rev0)
676 676 assert commit0.branch == 'master'
677 677 assert commit0.tags == []
678 678
679 679 rev10 = self.repo.commit_ids[10]
680 680 commit10 = self.repo.get_commit(rev10)
681 681 assert commit10.branch == 'master'
682 682 assert commit10.tags == []
683 683
684 684 rev44 = self.repo.commit_ids[44]
685 685 commit44 = self.repo.get_commit(rev44)
686 686 assert commit44.branch == 'web-branch'
687 687
688 688 tip = self.repo.get_commit('tip')
689 689 assert 'tip' in tip.tags
690 690 """
691 691 # Those tests would fail - branches are now going
692 692 # to be changed at main API in order to support git backend
693 693 pass
694 694
695 695 def test_file_size(self):
696 696 to_check = (
697 697 ('c1214f7e79e02fc37156ff215cd71275450cffc3',
698 698 'vcs/backends/BaseRepository.py', 502),
699 699 ('d7e0d30fbcae12c90680eb095a4f5f02505ce501',
700 700 'vcs/backends/hg.py', 854),
701 701 ('6e125e7c890379446e98980d8ed60fba87d0f6d1',
702 702 'setup.py', 1068),
703 703
704 704 ('d955cd312c17b02143c04fa1099a352b04368118',
705 705 'vcs/backends/base.py', 2921),
706 706 ('ca1eb7957a54bce53b12d1a51b13452f95bc7c7e',
707 707 'vcs/backends/base.py', 3936),
708 708 ('f50f42baeed5af6518ef4b0cb2f1423f3851a941',
709 709 'vcs/backends/base.py', 6189),
710 710 )
711 711 for commit_id, path, size in to_check:
712 712 node = self.repo.get_commit(commit_id).get_node(path)
713 713 assert node.is_file()
714 714 assert node.size == size
715 715
716 716 def test_file_history_from_commits(self):
717 717 node = self.repo[10].get_node('setup.py')
718 718 commit_ids = [commit.raw_id for commit in node.history]
719 719 assert ['ff7ca51e58c505fec0dd2491de52c622bb7a806b'] == commit_ids
720 720
721 721 node = self.repo[20].get_node('setup.py')
722 722 node_ids = [commit.raw_id for commit in node.history]
723 723 assert ['191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e',
724 724 'ff7ca51e58c505fec0dd2491de52c622bb7a806b'] == node_ids
725 725
726 726 # special case we check history from commit that has this particular
727 727 # file changed this means we check if it's included as well
728 728 node = self.repo.get_commit('191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e') \
729 729 .get_node('setup.py')
730 730 node_ids = [commit.raw_id for commit in node.history]
731 731 assert ['191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e',
732 732 'ff7ca51e58c505fec0dd2491de52c622bb7a806b'] == node_ids
733 733
734 734 def test_file_history(self):
735 735 # we can only check if those commits are present in the history
736 736 # as we cannot update this test every time file is changed
737 737 files = {
738 738 'setup.py': [
739 739 '54386793436c938cff89326944d4c2702340037d',
740 740 '51d254f0ecf5df2ce50c0b115741f4cf13985dab',
741 741 '998ed409c795fec2012b1c0ca054d99888b22090',
742 742 '5e0eb4c47f56564395f76333f319d26c79e2fb09',
743 743 '0115510b70c7229dbc5dc49036b32e7d91d23acd',
744 744 '7cb3fd1b6d8c20ba89e2264f1c8baebc8a52d36e',
745 745 '2a13f185e4525f9d4b59882791a2d397b90d5ddc',
746 746 '191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e',
747 747 'ff7ca51e58c505fec0dd2491de52c622bb7a806b',
748 748 ],
749 749 'vcs/nodes.py': [
750 750 '33fa3223355104431402a888fa77a4e9956feb3e',
751 751 'fa014c12c26d10ba682fadb78f2a11c24c8118e1',
752 752 'e686b958768ee96af8029fe19c6050b1a8dd3b2b',
753 753 'ab5721ca0a081f26bf43d9051e615af2cc99952f',
754 754 'c877b68d18e792a66b7f4c529ea02c8f80801542',
755 755 '4313566d2e417cb382948f8d9d7c765330356054',
756 756 '6c2303a793671e807d1cfc70134c9ca0767d98c2',
757 757 '54386793436c938cff89326944d4c2702340037d',
758 758 '54000345d2e78b03a99d561399e8e548de3f3203',
759 759 '1c6b3677b37ea064cb4b51714d8f7498f93f4b2b',
760 760 '2d03ca750a44440fb5ea8b751176d1f36f8e8f46',
761 761 '2a08b128c206db48c2f0b8f70df060e6db0ae4f8',
762 762 '30c26513ff1eb8e5ce0e1c6b477ee5dc50e2f34b',
763 763 'ac71e9503c2ca95542839af0ce7b64011b72ea7c',
764 764 '12669288fd13adba2a9b7dd5b870cc23ffab92d2',
765 765 '5a0c84f3e6fe3473e4c8427199d5a6fc71a9b382',
766 766 '12f2f5e2b38e6ff3fbdb5d722efed9aa72ecb0d5',
767 767 '5eab1222a7cd4bfcbabc218ca6d04276d4e27378',
768 768 'f50f42baeed5af6518ef4b0cb2f1423f3851a941',
769 769 'd7e390a45f6aa96f04f5e7f583ad4f867431aa25',
770 770 'f15c21f97864b4f071cddfbf2750ec2e23859414',
771 771 'e906ef056cf539a4e4e5fc8003eaf7cf14dd8ade',
772 772 'ea2b108b48aa8f8c9c4a941f66c1a03315ca1c3b',
773 773 '84dec09632a4458f79f50ddbbd155506c460b4f9',
774 774 '0115510b70c7229dbc5dc49036b32e7d91d23acd',
775 775 '2a13f185e4525f9d4b59882791a2d397b90d5ddc',
776 776 '3bf1c5868e570e39569d094f922d33ced2fa3b2b',
777 777 'b8d04012574729d2c29886e53b1a43ef16dd00a1',
778 778 '6970b057cffe4aab0a792aa634c89f4bebf01441',
779 779 'dd80b0f6cf5052f17cc738c2951c4f2070200d7f',
780 780 'ff7ca51e58c505fec0dd2491de52c622bb7a806b',
781 781 ],
782 782 'vcs/backends/git.py': [
783 783 '4cf116ad5a457530381135e2f4c453e68a1b0105',
784 784 '9a751d84d8e9408e736329767387f41b36935153',
785 785 'cb681fb539c3faaedbcdf5ca71ca413425c18f01',
786 786 '428f81bb652bcba8d631bce926e8834ff49bdcc6',
787 787 '180ab15aebf26f98f714d8c68715e0f05fa6e1c7',
788 788 '2b8e07312a2e89e92b90426ab97f349f4bce2a3a',
789 789 '50e08c506174d8645a4bb517dd122ac946a0f3bf',
790 790 '54000345d2e78b03a99d561399e8e548de3f3203',
791 791 ],
792 792 }
793 793 for path, commit_ids in files.items():
794 794 node = self.repo.get_commit(commit_ids[0]).get_node(path)
795 795 node_ids = [commit.raw_id for commit in node.history]
796 796 assert set(commit_ids).issubset(set(node_ids)), (
797 797 "We assumed that %s is subset of commit_ids for which file %s "
798 798 "has been changed, and history of that node returned: %s"
799 799 % (commit_ids, path, node_ids))
800 800
801 801 def test_file_annotate(self):
802 802 files = {
803 803 'vcs/backends/__init__.py': {
804 804 'c1214f7e79e02fc37156ff215cd71275450cffc3': {
805 805 'lines_no': 1,
806 806 'commits': [
807 807 'c1214f7e79e02fc37156ff215cd71275450cffc3',
808 808 ],
809 809 },
810 810 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647': {
811 811 'lines_no': 21,
812 812 'commits': [
813 813 '49d3fd156b6f7db46313fac355dca1a0b94a0017',
814 814 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
815 815 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
816 816 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
817 817 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
818 818 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
819 819 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
820 820 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
821 821 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
822 822 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
823 823 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
824 824 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
825 825 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
826 826 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
827 827 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
828 828 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
829 829 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
830 830 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
831 831 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
832 832 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
833 833 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
834 834 ],
835 835 },
836 836 'e29b67bd158580fc90fc5e9111240b90e6e86064': {
837 837 'lines_no': 32,
838 838 'commits': [
839 839 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
840 840 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
841 841 '5eab1222a7cd4bfcbabc218ca6d04276d4e27378',
842 842 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
843 843 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
844 844 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
845 845 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
846 846 '54000345d2e78b03a99d561399e8e548de3f3203',
847 847 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
848 848 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
849 849 '78c3f0c23b7ee935ec276acb8b8212444c33c396',
850 850 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
851 851 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
852 852 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
853 853 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
854 854 '2a13f185e4525f9d4b59882791a2d397b90d5ddc',
855 855 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
856 856 '78c3f0c23b7ee935ec276acb8b8212444c33c396',
857 857 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
858 858 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
859 859 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
860 860 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
861 861 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
862 862 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
863 863 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
864 864 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
865 865 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
866 866 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
867 867 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
868 868 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
869 869 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
870 870 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
871 871 ],
872 872 },
873 873 },
874 874 }
875 875
876 876 for fname, commit_dict in files.items():
877 877 for commit_id, __ in commit_dict.items():
878 878 commit = self.repo.get_commit(commit_id)
879 879
880 880 l1_1 = [x[1] for x in commit.get_file_annotate(fname)]
881 881 l1_2 = [x[2]().raw_id for x in commit.get_file_annotate(fname)]
882 882 assert l1_1 == l1_2
883 883 l1 = l1_1
884 884 l2 = files[fname][commit_id]['commits']
885 885 assert l1 == l2, (
886 886 "The lists of commit_ids for %s@commit_id %s"
887 887 "from annotation list should match each other, "
888 888 "got \n%s \nvs \n%s " % (fname, commit_id, l1, l2))
889 889
890 890 def test_files_state(self):
891 891 """
892 892 Tests state of FileNodes.
893 893 """
894 894 node = self.repo\
895 895 .get_commit('e6ea6d16e2f26250124a1f4b4fe37a912f9d86a0')\
896 896 .get_node('vcs/utils/diffs.py')
897 897 assert node.state, NodeState.ADDED
898 898 assert node.added
899 899 assert not node.changed
900 900 assert not node.not_changed
901 901 assert not node.removed
902 902
903 903 node = self.repo\
904 904 .get_commit('33fa3223355104431402a888fa77a4e9956feb3e')\
905 905 .get_node('.hgignore')
906 906 assert node.state, NodeState.CHANGED
907 907 assert not node.added
908 908 assert node.changed
909 909 assert not node.not_changed
910 910 assert not node.removed
911 911
912 912 node = self.repo\
913 913 .get_commit('e29b67bd158580fc90fc5e9111240b90e6e86064')\
914 914 .get_node('setup.py')
915 915 assert node.state, NodeState.NOT_CHANGED
916 916 assert not node.added
917 917 assert not node.changed
918 918 assert node.not_changed
919 919 assert not node.removed
920 920
921 921 # If node has REMOVED state then trying to fetch it would raise
922 922 # CommitError exception
923 923 commit = self.repo.get_commit(
924 924 'fa6600f6848800641328adbf7811fd2372c02ab2')
925 925 path = 'vcs/backends/BaseRepository.py'
926 926 with pytest.raises(NodeDoesNotExistError):
927 927 commit.get_node(path)
928 928 # but it would be one of ``removed`` (commit's attribute)
929 929 assert path in [rf.path for rf in commit.removed]
930 930
931 931 commit = self.repo.get_commit(
932 932 '54386793436c938cff89326944d4c2702340037d')
933 933 changed = [
934 934 'setup.py', 'tests/test_nodes.py', 'vcs/backends/hg.py',
935 935 'vcs/nodes.py']
936 936 assert set(changed) == set([f.path for f in commit.changed])
937 937
938 938 def test_unicode_branch_refs(self):
939 939 unicode_branches = {
940 940 'refs/heads/unicode': '6c0ce52b229aa978889e91b38777f800e85f330b',
941 941 u'refs/heads/uniçö∂e': 'ürl',
942 942 }
943 943 with mock.patch(
944 944 ("rhodecode.lib.vcs.backends.git.repository"
945 945 ".GitRepository._refs"),
946 946 unicode_branches):
947 947 branches = self.repo.branches
948 948
949 949 assert 'unicode' in branches
950 950 assert u'uniçö∂e' in branches
951 951
952 952 def test_unicode_tag_refs(self):
953 953 unicode_tags = {
954 954 'refs/tags/unicode': '6c0ce52b229aa978889e91b38777f800e85f330b',
955 955 u'refs/tags/uniçö∂e': '6c0ce52b229aa978889e91b38777f800e85f330b',
956 956 }
957 957 with mock.patch(
958 958 ("rhodecode.lib.vcs.backends.git.repository"
959 959 ".GitRepository._refs"),
960 960 unicode_tags):
961 961 tags = self.repo.tags
962 962
963 963 assert 'unicode' in tags
964 964 assert u'uniçö∂e' in tags
965 965
966 966 def test_commit_message_is_unicode(self):
967 967 for commit in self.repo:
968 968 assert type(commit.message) == unicode
969 969
970 970 def test_commit_author_is_unicode(self):
971 971 for commit in self.repo:
972 972 assert type(commit.author) == unicode
973 973
974 974 def test_repo_files_content_is_unicode(self):
975 975 commit = self.repo.get_commit()
976 976 for node in commit.get_node('/'):
977 977 if node.is_file():
978 978 assert type(node.content) == unicode
979 979
980 980 def test_wrong_path(self):
981 981 # There is 'setup.py' in the root dir but not there:
982 982 path = 'foo/bar/setup.py'
983 983 tip = self.repo.get_commit()
984 984 with pytest.raises(VCSError):
985 985 tip.get_node(path)
986 986
987 987 @pytest.mark.parametrize("author_email, commit_id", [
988 988 ('marcin@python-blog.com', 'c1214f7e79e02fc37156ff215cd71275450cffc3'),
989 989 ('lukasz.balcerzak@python-center.pl',
990 990 'ff7ca51e58c505fec0dd2491de52c622bb7a806b'),
991 991 ('none@none', '8430a588b43b5d6da365400117c89400326e7992'),
992 992 ])
993 993 def test_author_email(self, author_email, commit_id):
994 994 commit = self.repo.get_commit(commit_id)
995 995 assert author_email == commit.author_email
996 996
997 997 @pytest.mark.parametrize("author, commit_id", [
998 998 ('Marcin Kuzminski', 'c1214f7e79e02fc37156ff215cd71275450cffc3'),
999 999 ('Lukasz Balcerzak', 'ff7ca51e58c505fec0dd2491de52c622bb7a806b'),
1000 1000 ('marcink', '8430a588b43b5d6da365400117c89400326e7992'),
1001 1001 ])
1002 1002 def test_author_username(self, author, commit_id):
1003 1003 commit = self.repo.get_commit(commit_id)
1004 1004 assert author == commit.author_name
1005 1005
1006 1006
1007 1007 class TestLargeFileRepo(object):
1008 1008
1009 1009 def test_large_file(self, backend_git):
1010 1010 conf = make_db_config()
1011 1011 repo = backend_git.create_test_repo('largefiles', conf)
1012 1012
1013 1013 tip = repo.scm_instance().get_commit()
1014 1014
1015 1015 # extract stored LF node into the origin cache
1016 1016 lfs_store = os.path.join(repo.repo_path, repo.repo_name, 'lfs_store')
1017 1017
1018 1018 oid = '7b331c02e313c7599d5a90212e17e6d3cb729bd2e1c9b873c302a63c95a2f9bf'
1019 1019 oid_path = os.path.join(lfs_store, oid)
1020 1020 oid_destination = os.path.join(
1021 1021 conf.get('vcs_git_lfs', 'store_location'), oid)
1022 1022 shutil.copy(oid_path, oid_destination)
1023 1023
1024 1024 node = tip.get_node('1MB.zip')
1025 1025
1026 1026 lf_node = node.get_largefile_node()
1027 1027
1028 1028 assert lf_node.is_largefile() is True
1029 1029 assert lf_node.size == 1024000
1030 1030 assert lf_node.name == '1MB.zip'
1031 1031
1032 1032
1033 @pytest.mark.usefixtures("vcs_repository_support")
1033 1034 class TestGitSpecificWithRepo(BackendTestMixin):
1034 1035
1035 1036 @classmethod
1036 1037 def _get_commits(cls):
1037 1038 return [
1038 1039 {
1039 1040 'message': 'Initial',
1040 1041 'author': 'Joe Doe <joe.doe@example.com>',
1041 1042 'date': datetime.datetime(2010, 1, 1, 20),
1042 1043 'added': [
1043 1044 FileNode('foobar/static/js/admin/base.js', content='base'),
1044 1045 FileNode(
1045 1046 'foobar/static/admin', content='admin',
1046 1047 mode=0120000), # this is a link
1047 1048 FileNode('foo', content='foo'),
1048 1049 ],
1049 1050 },
1050 1051 {
1051 1052 'message': 'Second',
1052 1053 'author': 'Joe Doe <joe.doe@example.com>',
1053 1054 'date': datetime.datetime(2010, 1, 1, 22),
1054 1055 'added': [
1055 1056 FileNode('foo2', content='foo2'),
1056 1057 ],
1057 1058 },
1058 1059 ]
1059 1060
1060 1061 def test_paths_slow_traversing(self):
1061 1062 commit = self.repo.get_commit()
1062 1063 assert commit.get_node('foobar').get_node('static').get_node('js')\
1063 1064 .get_node('admin').get_node('base.js').content == 'base'
1064 1065
1065 1066 def test_paths_fast_traversing(self):
1066 1067 commit = self.repo.get_commit()
1067 1068 assert (
1068 1069 commit.get_node('foobar/static/js/admin/base.js').content ==
1069 1070 'base')
1070 1071
1071 1072 def test_get_diff_runs_git_command_with_hashes(self):
1072 1073 self.repo.run_git_command = mock.Mock(return_value=['', ''])
1073 1074 self.repo.get_diff(self.repo[0], self.repo[1])
1074 1075 self.repo.run_git_command.assert_called_once_with(
1075 1076 ['diff', '-U3', '--full-index', '--binary', '-p', '-M',
1076 1077 '--abbrev=40', self.repo._get_commit_id(0),
1077 1078 self.repo._get_commit_id(1)])
1078 1079
1079 1080 def test_get_diff_runs_git_command_with_str_hashes(self):
1080 1081 self.repo.run_git_command = mock.Mock(return_value=['', ''])
1081 1082 self.repo.get_diff(self.repo.EMPTY_COMMIT, self.repo[1])
1082 1083 self.repo.run_git_command.assert_called_once_with(
1083 1084 ['show', '-U3', '--full-index', '--binary', '-p', '-M',
1084 1085 '--abbrev=40', self.repo._get_commit_id(1)])
1085 1086
1086 1087 def test_get_diff_runs_git_command_with_path_if_its_given(self):
1087 1088 self.repo.run_git_command = mock.Mock(return_value=['', ''])
1088 1089 self.repo.get_diff(self.repo[0], self.repo[1], 'foo')
1089 1090 self.repo.run_git_command.assert_called_once_with(
1090 1091 ['diff', '-U3', '--full-index', '--binary', '-p', '-M',
1091 1092 '--abbrev=40', self.repo._get_commit_id(0),
1092 1093 self.repo._get_commit_id(1), '--', 'foo'])
1093 1094
1094 1095
1096 @pytest.mark.usefixtures("vcs_repository_support")
1095 1097 class TestGitRegression(BackendTestMixin):
1096 1098
1097 1099 @classmethod
1098 1100 def _get_commits(cls):
1099 1101 return [
1100 1102 {
1101 1103 'message': 'Initial',
1102 1104 'author': 'Joe Doe <joe.doe@example.com>',
1103 1105 'date': datetime.datetime(2010, 1, 1, 20),
1104 1106 'added': [
1105 1107 FileNode('bot/__init__.py', content='base'),
1106 1108 FileNode('bot/templates/404.html', content='base'),
1107 1109 FileNode('bot/templates/500.html', content='base'),
1108 1110 ],
1109 1111 },
1110 1112 {
1111 1113 'message': 'Second',
1112 1114 'author': 'Joe Doe <joe.doe@example.com>',
1113 1115 'date': datetime.datetime(2010, 1, 1, 22),
1114 1116 'added': [
1115 1117 FileNode('bot/build/migrations/1.py', content='foo2'),
1116 1118 FileNode('bot/build/migrations/2.py', content='foo2'),
1117 1119 FileNode(
1118 1120 'bot/build/static/templates/f.html', content='foo2'),
1119 1121 FileNode(
1120 1122 'bot/build/static/templates/f1.html', content='foo2'),
1121 1123 FileNode('bot/build/templates/err.html', content='foo2'),
1122 1124 FileNode('bot/build/templates/err2.html', content='foo2'),
1123 1125 ],
1124 1126 },
1125 1127 ]
1126 1128
1127 1129 @pytest.mark.parametrize("path, expected_paths", [
1128 1130 ('bot', [
1129 1131 'bot/build',
1130 1132 'bot/templates',
1131 1133 'bot/__init__.py']),
1132 1134 ('bot/build', [
1133 1135 'bot/build/migrations',
1134 1136 'bot/build/static',
1135 1137 'bot/build/templates']),
1136 1138 ('bot/build/static', [
1137 1139 'bot/build/static/templates']),
1138 1140 ('bot/build/static/templates', [
1139 1141 'bot/build/static/templates/f.html',
1140 1142 'bot/build/static/templates/f1.html']),
1141 1143 ('bot/build/templates', [
1142 1144 'bot/build/templates/err.html',
1143 1145 'bot/build/templates/err2.html']),
1144 1146 ('bot/templates/', [
1145 1147 'bot/templates/404.html',
1146 1148 'bot/templates/500.html']),
1147 1149 ])
1148 1150 def test_similar_paths(self, path, expected_paths):
1149 1151 commit = self.repo.get_commit()
1150 1152 paths = [n.path for n in commit.get_nodes(path)]
1151 1153 assert paths == expected_paths
1152 1154
1153 1155
1154 1156 class TestDiscoverGitVersion:
1155 1157
1156 1158 def test_returns_git_version(self, baseapp):
1157 1159 version = discover_git_version()
1158 1160 assert version
1159 1161
1160 1162 def test_returns_empty_string_without_vcsserver(self):
1161 1163 mock_connection = mock.Mock()
1162 1164 mock_connection.discover_git_version = mock.Mock(
1163 1165 side_effect=Exception)
1164 1166 with mock.patch('rhodecode.lib.vcs.connection.Git', mock_connection):
1165 1167 version = discover_git_version()
1166 1168 assert version == ''
1167 1169
1168 1170
1169 1171 class TestGetSubmoduleUrl(object):
1170 1172 def test_submodules_file_found(self):
1171 1173 commit = GitCommit(repository=mock.Mock(), raw_id='abcdef12', idx=1)
1172 1174 node = mock.Mock()
1173 1175 with mock.patch.object(
1174 1176 commit, 'get_node', return_value=node) as get_node_mock:
1175 1177 node.content = (
1176 1178 '[submodule "subrepo1"]\n'
1177 1179 '\tpath = subrepo1\n'
1178 1180 '\turl = https://code.rhodecode.com/dulwich\n'
1179 1181 )
1180 1182 result = commit._get_submodule_url('subrepo1')
1181 1183 get_node_mock.assert_called_once_with('.gitmodules')
1182 1184 assert result == 'https://code.rhodecode.com/dulwich'
1183 1185
1184 1186 def test_complex_submodule_path(self):
1185 1187 commit = GitCommit(repository=mock.Mock(), raw_id='abcdef12', idx=1)
1186 1188 node = mock.Mock()
1187 1189 with mock.patch.object(
1188 1190 commit, 'get_node', return_value=node) as get_node_mock:
1189 1191 node.content = (
1190 1192 '[submodule "complex/subrepo/path"]\n'
1191 1193 '\tpath = complex/subrepo/path\n'
1192 1194 '\turl = https://code.rhodecode.com/dulwich\n'
1193 1195 )
1194 1196 result = commit._get_submodule_url('complex/subrepo/path')
1195 1197 get_node_mock.assert_called_once_with('.gitmodules')
1196 1198 assert result == 'https://code.rhodecode.com/dulwich'
1197 1199
1198 1200 def test_submodules_file_not_found(self):
1199 1201 commit = GitCommit(repository=mock.Mock(), raw_id='abcdef12', idx=1)
1200 1202 with mock.patch.object(
1201 1203 commit, 'get_node', side_effect=NodeDoesNotExistError):
1202 1204 result = commit._get_submodule_url('complex/subrepo/path')
1203 1205 assert result is None
1204 1206
1205 1207 def test_path_not_found(self):
1206 1208 commit = GitCommit(repository=mock.Mock(), raw_id='abcdef12', idx=1)
1207 1209 node = mock.Mock()
1208 1210 with mock.patch.object(
1209 1211 commit, 'get_node', return_value=node) as get_node_mock:
1210 1212 node.content = (
1211 1213 '[submodule "subrepo1"]\n'
1212 1214 '\tpath = subrepo1\n'
1213 1215 '\turl = https://code.rhodecode.com/dulwich\n'
1214 1216 )
1215 1217 result = commit._get_submodule_url('subrepo2')
1216 1218 get_node_mock.assert_called_once_with('.gitmodules')
1217 1219 assert result is None
1218 1220
1219 1221 def test_returns_cached_values(self):
1220 1222 commit = GitCommit(repository=mock.Mock(), raw_id='abcdef12', idx=1)
1221 1223 node = mock.Mock()
1222 1224 with mock.patch.object(
1223 1225 commit, 'get_node', return_value=node) as get_node_mock:
1224 1226 node.content = (
1225 1227 '[submodule "subrepo1"]\n'
1226 1228 '\tpath = subrepo1\n'
1227 1229 '\turl = https://code.rhodecode.com/dulwich\n'
1228 1230 )
1229 1231 for _ in range(3):
1230 1232 commit._get_submodule_url('subrepo1')
1231 1233 get_node_mock.assert_called_once_with('.gitmodules')
1232 1234
1233 1235 def test_get_node_returns_a_link(self):
1234 1236 repository = mock.Mock()
1235 1237 repository.alias = 'git'
1236 1238 commit = GitCommit(repository=repository, raw_id='abcdef12', idx=1)
1237 1239 submodule_url = 'https://code.rhodecode.com/dulwich'
1238 1240 get_id_patch = mock.patch.object(
1239 1241 commit, '_get_id_for_path', return_value=(1, 'link'))
1240 1242 get_submodule_patch = mock.patch.object(
1241 1243 commit, '_get_submodule_url', return_value=submodule_url)
1242 1244
1243 1245 with get_id_patch, get_submodule_patch as submodule_mock:
1244 1246 node = commit.get_node('/abcde')
1245 1247
1246 1248 submodule_mock.assert_called_once_with('/abcde')
1247 1249 assert type(node) == SubModuleNode
1248 1250 assert node.url == submodule_url
1249 1251
1250 1252 def test_get_nodes_returns_links(self):
1251 1253 repository = mock.MagicMock()
1252 1254 repository.alias = 'git'
1253 1255 repository._remote.tree_items.return_value = [
1254 1256 ('subrepo', 'stat', 1, 'link')
1255 1257 ]
1256 1258 commit = GitCommit(repository=repository, raw_id='abcdef12', idx=1)
1257 1259 submodule_url = 'https://code.rhodecode.com/dulwich'
1258 1260 get_id_patch = mock.patch.object(
1259 1261 commit, '_get_id_for_path', return_value=(1, 'tree'))
1260 1262 get_submodule_patch = mock.patch.object(
1261 1263 commit, '_get_submodule_url', return_value=submodule_url)
1262 1264
1263 1265 with get_id_patch, get_submodule_patch as submodule_mock:
1264 1266 nodes = commit.get_nodes('/abcde')
1265 1267
1266 1268 submodule_mock.assert_called_once_with('/abcde/subrepo')
1267 1269 assert len(nodes) == 1
1268 1270 assert type(nodes[0]) == SubModuleNode
1269 1271 assert nodes[0].url == submodule_url
@@ -1,344 +1,345 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Tests so called "in memory commits" commit API of vcs.
23 23 """
24 24 import datetime
25 25
26 26 import pytest
27 27
28 28 from rhodecode.lib.utils2 import safe_unicode
29 29 from rhodecode.lib.vcs.exceptions import (
30 30 EmptyRepositoryError, NodeAlreadyAddedError, NodeAlreadyExistsError,
31 31 NodeAlreadyRemovedError, NodeAlreadyChangedError, NodeDoesNotExistError,
32 32 NodeNotChangedError)
33 33 from rhodecode.lib.vcs.nodes import DirNode, FileNode
34 from rhodecode.tests.vcs.base import BackendTestMixin
34 from rhodecode.tests.vcs.conftest import BackendTestMixin
35 35
36 36
37 37 @pytest.fixture
38 38 def nodes():
39 39 nodes = [
40 40 FileNode('foobar', content='Foo & bar'),
41 41 FileNode('foobar2', content='Foo & bar, doubled!'),
42 42 FileNode('foo bar with spaces', content=''),
43 43 FileNode('foo/bar/baz', content='Inside'),
44 44 FileNode(
45 45 'foo/bar/file.bin',
46 46 content=(
47 47 '\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1\x00\x00\x00\x00\x00\x00'
48 48 '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00;\x00\x03\x00\xfe'
49 49 '\xff\t\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
50 50 '\x01\x00\x00\x00\x1a\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00'
51 51 '\x00\x18\x00\x00\x00\x01\x00\x00\x00\xfe\xff\xff\xff\x00\x00'
52 52 '\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff'
53 53 '\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
54 54 )
55 55 ),
56 56 ]
57 57 return nodes
58 58
59 59
60 @pytest.mark.usefixtures("vcs_repository_support")
60 61 class TestInMemoryCommit(BackendTestMixin):
61 62 """
62 63 This is a backend independent test case class which should be created
63 64 with ``type`` method.
64 65
65 66 It is required to set following attributes at subclass:
66 67
67 68 - ``backend_alias``: alias of used backend (see ``vcs.BACKENDS``)
68 69 """
69 70
70 71 @classmethod
71 72 def _get_commits(cls):
72 73 return []
73 74
74 75 def test_add(self, nodes):
75 76 for node in nodes:
76 77 self.imc.add(node)
77 78
78 79 self.commit()
79 80 self.assert_succesful_commit(nodes)
80 81
81 82 @pytest.mark.skip_backends(
82 83 'svn', reason="Svn does not support commits on branches.")
83 84 def test_add_on_branch(self, nodes):
84 85 for node in nodes:
85 86 self.imc.add(node)
86 87 self.commit(branch=u'stable')
87 88 self.assert_succesful_commit(nodes)
88 89
89 90 def test_add_in_bulk(self, nodes):
90 91 self.imc.add(*nodes)
91 92
92 93 self.commit()
93 94 self.assert_succesful_commit(nodes)
94 95
95 96 def test_add_non_ascii_files(self):
96 97 nodes = [
97 98 FileNode('żółwik/zwierzątko_utf8_str', content='ćććć'),
98 99 FileNode(u'żółwik/zwierzątko_unicode', content=u'ćććć'),
99 100 ]
100 101
101 102 for node in nodes:
102 103 self.imc.add(node)
103 104
104 105 self.commit()
105 106 self.assert_succesful_commit(nodes)
106 107
107 108 def commit(self, branch=None):
108 109 self.old_commit_count = len(self.repo.commit_ids)
109 110 self.commit_message = u'Test commit with unicode: żółwik'
110 111 self.commit_author = unicode(self.__class__)
111 112 self.commit = self.imc.commit(
112 113 message=self.commit_message, author=self.commit_author,
113 114 branch=branch)
114 115
115 116 def test_add_actually_adds_all_nodes_at_second_commit_too(self):
116 117 to_add = [
117 118 FileNode('foo/bar/image.png', content='\0'),
118 119 FileNode('foo/README.txt', content='readme!'),
119 120 ]
120 121 self.imc.add(*to_add)
121 122 commit = self.imc.commit(u'Initial', u'joe.doe@example.com')
122 123 assert isinstance(commit.get_node('foo'), DirNode)
123 124 assert isinstance(commit.get_node('foo/bar'), DirNode)
124 125 self.assert_nodes_in_commit(commit, to_add)
125 126
126 127 # commit some more files again
127 128 to_add = [
128 129 FileNode('foo/bar/foobaz/bar', content='foo'),
129 130 FileNode('foo/bar/another/bar', content='foo'),
130 131 FileNode('foo/baz.txt', content='foo'),
131 132 FileNode('foobar/foobaz/file', content='foo'),
132 133 FileNode('foobar/barbaz', content='foo'),
133 134 ]
134 135 self.imc.add(*to_add)
135 136 commit = self.imc.commit(u'Another', u'joe.doe@example.com')
136 137 self.assert_nodes_in_commit(commit, to_add)
137 138
138 139 def test_add_raise_already_added(self):
139 140 node = FileNode('foobar', content='baz')
140 141 self.imc.add(node)
141 142 with pytest.raises(NodeAlreadyAddedError):
142 143 self.imc.add(node)
143 144
144 145 def test_check_integrity_raise_already_exist(self):
145 146 node = FileNode('foobar', content='baz')
146 147 self.imc.add(node)
147 148 self.imc.commit(message=u'Added foobar', author=unicode(self))
148 149 self.imc.add(node)
149 150 with pytest.raises(NodeAlreadyExistsError):
150 151 self.imc.commit(message='new message', author=str(self))
151 152
152 153 def test_change(self):
153 154 self.imc.add(FileNode('foo/bar/baz', content='foo'))
154 155 self.imc.add(FileNode('foo/fbar', content='foobar'))
155 156 tip = self.imc.commit(u'Initial', u'joe.doe@example.com')
156 157
157 158 # Change node's content
158 159 node = FileNode('foo/bar/baz', content='My **changed** content')
159 160 self.imc.change(node)
160 161 self.imc.commit(u'Changed %s' % node.path, u'joe.doe@example.com')
161 162
162 163 newtip = self.repo.get_commit()
163 164 assert tip != newtip
164 165 assert tip.id != newtip.id
165 166 self.assert_nodes_in_commit(newtip, (node,))
166 167
167 168 def test_change_non_ascii(self):
168 169 to_add = [
169 170 FileNode('żółwik/zwierzątko', content='ćććć'),
170 171 FileNode(u'żółwik/zwierzątko_uni', content=u'ćććć'),
171 172 ]
172 173 for node in to_add:
173 174 self.imc.add(node)
174 175
175 176 tip = self.imc.commit(u'Initial', u'joe.doe@example.com')
176 177
177 178 # Change node's content
178 179 node = FileNode('żółwik/zwierzątko', content='My **changed** content')
179 180 self.imc.change(node)
180 181 self.imc.commit(u'Changed %s' % safe_unicode(node.path),
181 182 u'joe.doe@example.com')
182 183
183 184 node_uni = FileNode(
184 185 u'żółwik/zwierzątko_uni', content=u'My **changed** content')
185 186 self.imc.change(node_uni)
186 187 self.imc.commit(u'Changed %s' % safe_unicode(node_uni.path),
187 188 u'joe.doe@example.com')
188 189
189 190 newtip = self.repo.get_commit()
190 191 assert tip != newtip
191 192 assert tip.id != newtip.id
192 193
193 194 self.assert_nodes_in_commit(newtip, (node, node_uni))
194 195
195 196 def test_change_raise_empty_repository(self):
196 197 node = FileNode('foobar')
197 198 with pytest.raises(EmptyRepositoryError):
198 199 self.imc.change(node)
199 200
200 201 def test_check_integrity_change_raise_node_does_not_exist(self):
201 202 node = FileNode('foobar', content='baz')
202 203 self.imc.add(node)
203 204 self.imc.commit(message=u'Added foobar', author=unicode(self))
204 205 node = FileNode('not-foobar', content='')
205 206 self.imc.change(node)
206 207 with pytest.raises(NodeDoesNotExistError):
207 208 self.imc.commit(
208 209 message='Changed not existing node',
209 210 author=str(self))
210 211
211 212 def test_change_raise_node_already_changed(self):
212 213 node = FileNode('foobar', content='baz')
213 214 self.imc.add(node)
214 215 self.imc.commit(message=u'Added foobar', author=unicode(self))
215 216 node = FileNode('foobar', content='more baz')
216 217 self.imc.change(node)
217 218 with pytest.raises(NodeAlreadyChangedError):
218 219 self.imc.change(node)
219 220
220 221 def test_check_integrity_change_raise_node_not_changed(self, nodes):
221 222 self.test_add(nodes) # Performs first commit
222 223
223 224 node = FileNode(nodes[0].path, content=nodes[0].content)
224 225 self.imc.change(node)
225 226 with pytest.raises(NodeNotChangedError):
226 227 self.imc.commit(
227 228 message=u'Trying to mark node as changed without touching it',
228 229 author=unicode(self))
229 230
230 231 def test_change_raise_node_already_removed(self):
231 232 node = FileNode('foobar', content='baz')
232 233 self.imc.add(node)
233 234 self.imc.commit(message=u'Added foobar', author=unicode(self))
234 235 self.imc.remove(FileNode('foobar'))
235 236 with pytest.raises(NodeAlreadyRemovedError):
236 237 self.imc.change(node)
237 238
238 239 def test_remove(self, nodes):
239 240 self.test_add(nodes) # Performs first commit
240 241
241 242 tip = self.repo.get_commit()
242 243 node = nodes[0]
243 244 assert node.content == tip.get_node(node.path).content
244 245 self.imc.remove(node)
245 246 self.imc.commit(
246 247 message=u'Removed %s' % node.path, author=unicode(self))
247 248
248 249 newtip = self.repo.get_commit()
249 250 assert tip != newtip
250 251 assert tip.id != newtip.id
251 252 with pytest.raises(NodeDoesNotExistError):
252 253 newtip.get_node(node.path)
253 254
254 255 def test_remove_last_file_from_directory(self):
255 256 node = FileNode('omg/qwe/foo/bar', content='foobar')
256 257 self.imc.add(node)
257 258 self.imc.commit(u'added', u'joe doe')
258 259
259 260 self.imc.remove(node)
260 261 tip = self.imc.commit(u'removed', u'joe doe')
261 262 with pytest.raises(NodeDoesNotExistError):
262 263 tip.get_node('omg/qwe/foo/bar')
263 264
264 265 def test_remove_raise_node_does_not_exist(self, nodes):
265 266 self.imc.remove(nodes[0])
266 267 with pytest.raises(NodeDoesNotExistError):
267 268 self.imc.commit(
268 269 message='Trying to remove node at empty repository',
269 270 author=str(self))
270 271
271 272 def test_check_integrity_remove_raise_node_does_not_exist(self, nodes):
272 273 self.test_add(nodes) # Performs first commit
273 274
274 275 node = FileNode('no-such-file')
275 276 self.imc.remove(node)
276 277 with pytest.raises(NodeDoesNotExistError):
277 278 self.imc.commit(
278 279 message=u'Trying to remove not existing node',
279 280 author=unicode(self))
280 281
281 282 def test_remove_raise_node_already_removed(self, nodes):
282 283 self.test_add(nodes) # Performs first commit
283 284
284 285 node = FileNode(nodes[0].path)
285 286 self.imc.remove(node)
286 287 with pytest.raises(NodeAlreadyRemovedError):
287 288 self.imc.remove(node)
288 289
289 290 def test_remove_raise_node_already_changed(self, nodes):
290 291 self.test_add(nodes) # Performs first commit
291 292
292 293 node = FileNode(nodes[0].path, content='Bending time')
293 294 self.imc.change(node)
294 295 with pytest.raises(NodeAlreadyChangedError):
295 296 self.imc.remove(node)
296 297
297 298 def test_reset(self):
298 299 self.imc.add(FileNode('foo', content='bar'))
299 300 # self.imc.change(FileNode('baz', content='new'))
300 301 # self.imc.remove(FileNode('qwe'))
301 302 self.imc.reset()
302 303 assert not any((self.imc.added, self.imc.changed, self.imc.removed))
303 304
304 305 def test_multiple_commits(self):
305 306 N = 3 # number of commits to perform
306 307 last = None
307 308 for x in xrange(N):
308 309 fname = 'file%s' % str(x).rjust(5, '0')
309 310 content = 'foobar\n' * x
310 311 node = FileNode(fname, content=content)
311 312 self.imc.add(node)
312 313 commit = self.imc.commit(u"Commit no. %s" % (x + 1), author=u'vcs')
313 314 assert last != commit
314 315 last = commit
315 316
316 317 # Check commit number for same repo
317 318 assert len(self.repo.commit_ids) == N
318 319
319 320 # Check commit number for recreated repo
320 321 repo = self.Backend(self.repo_path)
321 322 assert len(repo.commit_ids) == N
322 323
323 324 def test_date_attr(self, local_dt_to_utc):
324 325 node = FileNode('foobar.txt', content='Foobared!')
325 326 self.imc.add(node)
326 327 date = datetime.datetime(1985, 1, 30, 1, 45)
327 328 commit = self.imc.commit(
328 329 u"Committed at time when I was born ;-)",
329 330 author=u'lb', date=date)
330 331
331 332 assert commit.date == local_dt_to_utc(date)
332 333
333 334 def assert_succesful_commit(self, added_nodes):
334 335 newtip = self.repo.get_commit()
335 336 assert self.commit == newtip
336 337 assert self.old_commit_count + 1 == len(self.repo.commit_ids)
337 338 assert newtip.message == self.commit_message
338 339 assert newtip.author == self.commit_author
339 340 assert not any((self.imc.added, self.imc.changed, self.imc.removed))
340 341 self.assert_nodes_in_commit(newtip, added_nodes)
341 342
342 343 def assert_nodes_in_commit(self, commit, nodes):
343 344 for node in nodes:
344 345 assert commit.get_node(node.path).content == node.content
@@ -1,274 +1,275 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import stat
22 22
23 23 import pytest
24 24
25 25 from rhodecode.lib.vcs.nodes import DirNode
26 26 from rhodecode.lib.vcs.nodes import FileNode
27 27 from rhodecode.lib.vcs.nodes import Node
28 28 from rhodecode.lib.vcs.nodes import NodeError
29 29 from rhodecode.lib.vcs.nodes import NodeKind
30 from rhodecode.tests.vcs.base import BackendTestMixin
30 from rhodecode.tests.vcs.conftest import BackendTestMixin
31 31
32 32
33 33 @pytest.fixture()
34 34 def binary_filenode():
35 35 def node_maker(filename):
36 36 data = (
37 37 "\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00"
38 38 "\x10\x08\x06\x00\x00\x00\x1f??a\x00\x00\x00\x04gAMA\x00\x00\xaf?7"
39 39 "\x05\x8a?\x00\x00\x00\x19tEXtSoftware\x00Adobe ImageReadyq?e<\x00"
40 40 "\x00\x025IDAT8?\xa5\x93?K\x94Q\x14\x87\x9f\xf7?Q\x1bs4?\x03\x9a"
41 41 "\xa8?B\x02\x8b$\x10[U;i\x13?6h?&h[?\"\x14j?\xa2M\x7fB\x14F\x9aQ?&"
42 42 "\x842?\x0b\x89\"\x82??!?\x9c!\x9c2l??{N\x8bW\x9dY\xb4\t/\x1c?="
43 43 "\x9b?}????\xa9*;9!?\x83\x91?[?\\v*?D\x04\'`EpNp\xa2X\'U?pVq\"Sw."
44 44 "\x1e?\x08\x01D?jw????\xbc??7{|\x9b?\x89$\x01??W@\x15\x9c\x05q`Lt/"
45 45 "\x97?\x94\xa1d?\x18~?\x18?\x18W[%\xb0?\x83??\x14\x88\x8dB?\xa6H"
46 46 "\tL\tl\x19>/\x01`\xac\xabx?\x9cl\nx\xb0\x98\x07\x95\x88D$\"q["
47 47 "\x19?d\x00(o\n\xa0??\x7f\xb9\xa4?\x1bF\x1f\x8e\xac\xa8?j??eUU}?.?"
48 48 "\x9f\x8cE??x\x94??\r\xbdtoJU5\"0N\x10U?\x00??V\t\x02\x9f\x81?U?"
49 49 "\x00\x9eM\xae2?r\x9b7\x83\x82\x8aP3????.?&\"?\xb7ZP \x0c<?O"
50 50 "\xa5\t}\xb8?\x99\xa6?\x87?\x1di|/\xa0??0\xbe\x1fp?d&\x1a\xad"
51 51 "\x95\x8a\x07?\t*\x10??b:?d?.\x13C\x8a?\x12\xbe\xbf\x8e?{???"
52 52 "\x08?\x80\xa7\x13+d\x13>J?\x80\x15T\x95\x9a\x00??S\x8c\r?\xa1"
53 53 "\x03\x07?\x96\x9b\xa7\xab=E??\xa4\xb3?\x19q??B\x91=\x8d??k?J"
54 54 "\x0bV\"??\xf7x?\xa1\x00?\\.\x87\x87???\x02F@D\x99],??\x10#?X"
55 55 "\xb7=\xb9\x10?Z\x1by???cI??\x1ag?\x92\xbc?T?t[\x92\x81?<_\x17~"
56 56 "\x92\x88?H%?\x10Q\x02\x9f\n\x81qQ\x0bm?\x1bX?\xb1AK\xa6\x9e\xb9?u"
57 57 "\xb2?1\xbe|/\x92M@\xa2!F?\xa9>\"\r<DT?>\x92\x8e?>\x9a9Qv\x127?a"
58 58 "\xac?Y?8?:??]X???9\x80\xb7?u?\x0b#BZ\x8d=\x1d?p\x00\x00\x00\x00"
59 59 "IEND\xaeB`\x82")
60 60 return FileNode(filename, content=data)
61 61 return node_maker
62 62
63 63
64 64 class TestNodeBasics:
65 65
66 66 @pytest.mark.parametrize("path", ['/foo', '/foo/bar'])
67 67 @pytest.mark.parametrize(
68 68 "kind", [NodeKind.FILE, NodeKind.DIR], ids=["FILE", "DIR"])
69 69 def test_init_wrong_paths(self, path, kind):
70 70 """
71 71 Cannot innitialize Node objects with path with slash at the beginning.
72 72 """
73 73 with pytest.raises(NodeError):
74 74 Node(path, kind)
75 75
76 76 @pytest.mark.parametrize("path", ['path', 'some/path'])
77 77 @pytest.mark.parametrize(
78 78 "kind", [NodeKind.FILE, NodeKind.DIR], ids=["FILE", "DIR"])
79 79 def test_name(self, path, kind):
80 80 node = Node(path, kind)
81 81 assert node.name == 'path'
82 82
83 83 def test_name_root(self):
84 84 node = Node('', NodeKind.DIR)
85 85 assert node.name == ''
86 86
87 87 def test_root_node_cannot_be_file(self):
88 88 with pytest.raises(NodeError):
89 89 Node('', NodeKind.FILE)
90 90
91 91 def test_kind_setter(self):
92 92 node = Node('', NodeKind.DIR)
93 93 with pytest.raises(NodeError):
94 94 node.kind = NodeKind.FILE
95 95
96 96 def test_compare_equal(self):
97 97 node1 = FileNode('test', content='')
98 98 node2 = FileNode('test', content='')
99 99 assert node1 == node2
100 100 assert not node1 != node2
101 101
102 102 def test_compare_unequal(self):
103 103 node1 = FileNode('test', content='a')
104 104 node2 = FileNode('test', content='b')
105 105 assert node1 != node2
106 106 assert not node1 == node2
107 107
108 108 @pytest.mark.parametrize("node_path, expected_parent_path", [
109 109 ('', ''),
110 110 ('some/path/', 'some/'),
111 111 ('some/longer/path/', 'some/longer/'),
112 112 ])
113 113 def test_parent_path_new(self, node_path, expected_parent_path):
114 114 """
115 115 Tests if node's parent path are properly computed.
116 116 """
117 117 node = Node(node_path, NodeKind.DIR)
118 118 parent_path = node.get_parent_path()
119 119 assert (parent_path.endswith('/') or
120 120 node.is_root() and parent_path == '')
121 121 assert parent_path == expected_parent_path
122 122
123 123 '''
124 124 def _test_trailing_slash(self, path):
125 125 if not path.endswith('/'):
126 126 pytest.fail("Trailing slash tests needs paths to end with slash")
127 127 for kind in NodeKind.FILE, NodeKind.DIR:
128 128 with pytest.raises(NodeError):
129 129 Node(path=path, kind=kind)
130 130
131 131 def test_trailing_slash(self):
132 132 for path in ('/', 'foo/', 'foo/bar/', 'foo/bar/biz/'):
133 133 self._test_trailing_slash(path)
134 134 '''
135 135
136 136 def test_is_file(self):
137 137 node = Node('any', NodeKind.FILE)
138 138 assert node.is_file()
139 139
140 140 node = FileNode('any')
141 141 assert node.is_file()
142 142 with pytest.raises(AttributeError):
143 143 node.nodes
144 144
145 145 def test_is_dir(self):
146 146 node = Node('any_dir', NodeKind.DIR)
147 147 assert node.is_dir()
148 148
149 149 node = DirNode('any_dir')
150 150
151 151 assert node.is_dir()
152 152 with pytest.raises(NodeError):
153 153 node.content
154 154
155 155 def test_dir_node_iter(self):
156 156 nodes = [
157 157 DirNode('docs'),
158 158 DirNode('tests'),
159 159 FileNode('bar'),
160 160 FileNode('foo'),
161 161 FileNode('readme.txt'),
162 162 FileNode('setup.py'),
163 163 ]
164 164 dirnode = DirNode('', nodes=nodes)
165 165 for node in dirnode:
166 166 assert node == dirnode.get_node(node.path)
167 167
168 168 def test_node_state(self):
169 169 """
170 170 Without link to commit nodes should raise NodeError.
171 171 """
172 172 node = FileNode('anything')
173 173 with pytest.raises(NodeError):
174 174 node.state
175 175 node = DirNode('anything')
176 176 with pytest.raises(NodeError):
177 177 node.state
178 178
179 179 def test_file_node_stat(self):
180 180 node = FileNode('foobar', 'empty... almost')
181 181 mode = node.mode # default should be 0100644
182 182 assert mode & stat.S_IRUSR
183 183 assert mode & stat.S_IWUSR
184 184 assert mode & stat.S_IRGRP
185 185 assert mode & stat.S_IROTH
186 186 assert not mode & stat.S_IWGRP
187 187 assert not mode & stat.S_IWOTH
188 188 assert not mode & stat.S_IXUSR
189 189 assert not mode & stat.S_IXGRP
190 190 assert not mode & stat.S_IXOTH
191 191
192 192 def test_file_node_is_executable(self):
193 193 node = FileNode('foobar', 'empty... almost', mode=0100755)
194 194 assert node.is_executable
195 195
196 196 node = FileNode('foobar', 'empty... almost', mode=0100500)
197 197 assert node.is_executable
198 198
199 199 node = FileNode('foobar', 'empty... almost', mode=0100644)
200 200 assert not node.is_executable
201 201
202 202 def test_file_node_is_not_symlink(self):
203 203 node = FileNode('foobar', 'empty...')
204 204 assert not node.is_link()
205 205
206 206 def test_mimetype(self):
207 207 py_node = FileNode('test.py')
208 208 tar_node = FileNode('test.tar.gz')
209 209
210 210 ext = 'CustomExtension'
211 211
212 212 my_node2 = FileNode('myfile2')
213 213 my_node2._mimetype = [ext]
214 214
215 215 my_node3 = FileNode('myfile3')
216 216 my_node3._mimetype = [ext, ext]
217 217
218 218 assert py_node.mimetype == 'text/x-python'
219 219 assert py_node.get_mimetype() == ('text/x-python', None)
220 220
221 221 assert tar_node.mimetype == 'application/x-tar'
222 222 assert tar_node.get_mimetype() == ('application/x-tar', 'gzip')
223 223
224 224 with pytest.raises(NodeError):
225 225 my_node2.get_mimetype()
226 226
227 227 assert my_node3.mimetype == ext
228 228 assert my_node3.get_mimetype() == [ext, ext]
229 229
230 230 def test_lines_counts(self):
231 231 lines = [
232 232 'line1\n',
233 233 'line2\n',
234 234 'line3\n',
235 235 '\n',
236 236 '\n',
237 237 'line4\n',
238 238 ]
239 239 py_node = FileNode('test.py', ''.join(lines))
240 240
241 241 assert (len(lines), len(lines)) == py_node.lines()
242 242 assert (len(lines), len(lines) - 2) == py_node.lines(count_empty=True)
243 243
244 244 def test_lines_no_newline(self):
245 245 py_node = FileNode('test.py', 'oneline')
246 246
247 247 assert (1, 1) == py_node.lines()
248 248 assert (1, 1) == py_node.lines(count_empty=True)
249 249
250 250
251 class TestNodeContent:
251 class TestNodeContent(object):
252 252
253 253 def test_if_binary(self, binary_filenode):
254 254 filenode = binary_filenode('calendar.jpg')
255 255 assert filenode.is_binary
256 256
257 257 def test_binary_line_counts(self, binary_filenode):
258 258 tar_node = binary_filenode('archive.tar.gz')
259 259 assert (0, 0) == tar_node.lines(count_empty=True)
260 260
261 261 def test_binary_mimetype(self, binary_filenode):
262 262 tar_node = binary_filenode('archive.tar.gz')
263 263 assert tar_node.mimetype == 'application/x-tar'
264 264
265 265
266 @pytest.mark.usefixtures("vcs_repository_support")
266 267 class TestNodesCommits(BackendTestMixin):
267 268
268 269 def test_node_last_commit(self, generate_repo_with_commits):
269 270 repo = generate_repo_with_commits(20)
270 271 last_commit = repo.get_commit()
271 272
272 273 for x in xrange(3):
273 274 node = last_commit.get_node('file_%s.txt' % x)
274 275 assert node.last_commit == repo[x]
@@ -1,534 +1,537 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import datetime
22 22 from urllib2 import URLError
23 23
24 24 import mock
25 25 import pytest
26 26
27 27 from rhodecode.lib.vcs import backends
28 28 from rhodecode.lib.vcs.backends.base import (
29 29 Config, BaseInMemoryCommit, Reference, MergeResponse, MergeFailureReason)
30 30 from rhodecode.lib.vcs.exceptions import VCSError, RepositoryError
31 31 from rhodecode.lib.vcs.nodes import FileNode
32 from rhodecode.tests.vcs.base import BackendTestMixin
32 from rhodecode.tests.vcs.conftest import BackendTestMixin
33 33
34 34
35 @pytest.mark.usefixtures("vcs_repository_support")
35 36 class TestRepositoryBase(BackendTestMixin):
36 37 recreate_repo_per_test = False
37 38
38 39 def test_init_accepts_unicode_path(self, tmpdir):
39 40 path = unicode(tmpdir.join(u'unicode ä'))
40 41 self.Backend(path, create=True)
41 42
42 43 def test_init_accepts_str_path(self, tmpdir):
43 44 path = str(tmpdir.join('str ä'))
44 45 self.Backend(path, create=True)
45 46
46 47 def test_init_fails_if_path_does_not_exist(self, tmpdir):
47 48 path = unicode(tmpdir.join('i-do-not-exist'))
48 49 with pytest.raises(VCSError):
49 50 self.Backend(path)
50 51
51 52 def test_init_fails_if_path_is_not_a_valid_repository(self, tmpdir):
52 53 path = unicode(tmpdir.mkdir(u'unicode ä'))
53 54 with pytest.raises(VCSError):
54 55 self.Backend(path)
55 56
56 57 def test_has_commits_attribute(self):
57 58 self.repo.commit_ids
58 59
59 60 def test_name(self):
60 61 assert self.repo.name.startswith('vcs-test')
61 62
62 63 @pytest.mark.backends("hg", "git")
63 64 def test_has_default_branch_name(self):
64 65 assert self.repo.DEFAULT_BRANCH_NAME is not None
65 66
66 67 @pytest.mark.backends("svn")
67 68 def test_has_no_default_branch_name(self):
68 69 assert self.repo.DEFAULT_BRANCH_NAME is None
69 70
70 71 def test_has_empty_commit(self):
71 72 assert self.repo.EMPTY_COMMIT_ID is not None
72 73 assert self.repo.EMPTY_COMMIT is not None
73 74
74 75 def test_empty_changeset_is_deprecated(self):
75 76 def get_empty_changeset(repo):
76 77 return repo.EMPTY_CHANGESET
77 78 pytest.deprecated_call(get_empty_changeset, self.repo)
78 79
79 80 def test_bookmarks(self):
80 81 assert len(self.repo.bookmarks) == 0
81 82
82 83 # TODO: Cover two cases: Local repo path, remote URL
83 84 def test_check_url(self):
84 85 config = Config()
85 86 assert self.Backend.check_url(self.repo.path, config)
86 87
87 88 def test_check_url_invalid(self):
88 89 config = Config()
89 90 with pytest.raises(URLError):
90 91 self.Backend.check_url(self.repo.path + "invalid", config)
91 92
92 93 def test_get_contact(self):
93 94 assert self.repo.contact
94 95
95 96 def test_get_description(self):
96 97 assert self.repo.description
97 98
98 99 def test_get_hook_location(self):
99 100 assert len(self.repo.get_hook_location()) != 0
100 101
101 102 def test_last_change(self, local_dt_to_utc):
102 103 assert self.repo.last_change >= local_dt_to_utc(
103 104 datetime.datetime(2010, 1, 1, 21, 0))
104 105
105 106 def test_last_change_in_empty_repository(self, vcsbackend, local_dt_to_utc):
106 107 delta = datetime.timedelta(seconds=1)
107 108
108 109 start = local_dt_to_utc(datetime.datetime.now())
109 110 empty_repo = vcsbackend.create_repo()
110 111 now = local_dt_to_utc(datetime.datetime.now())
111 112 assert empty_repo.last_change >= start - delta
112 113 assert empty_repo.last_change <= now + delta
113 114
114 115 def test_repo_equality(self):
115 116 assert self.repo == self.repo
116 117
117 118 def test_repo_equality_broken_object(self):
118 119 import copy
119 120 _repo = copy.copy(self.repo)
120 121 delattr(_repo, 'path')
121 122 assert self.repo != _repo
122 123
123 124 def test_repo_equality_other_object(self):
124 125 class dummy(object):
125 126 path = self.repo.path
126 127 assert self.repo != dummy()
127 128
128 129 def test_get_commit_is_implemented(self):
129 130 self.repo.get_commit()
130 131
131 132 def test_get_commits_is_implemented(self):
132 133 commit_iter = iter(self.repo.get_commits())
133 134 commit = next(commit_iter)
134 135 assert commit.idx == 0
135 136
136 137 def test_supports_iteration(self):
137 138 repo_iter = iter(self.repo)
138 139 commit = next(repo_iter)
139 140 assert commit.idx == 0
140 141
141 142 def test_in_memory_commit(self):
142 143 imc = self.repo.in_memory_commit
143 144 assert isinstance(imc, BaseInMemoryCommit)
144 145
145 146 @pytest.mark.backends("hg")
146 147 def test__get_url_unicode(self):
147 148 url = u'/home/repos/malmö'
148 149 assert self.repo._get_url(url)
149 150
150 151
152 @pytest.mark.usefixtures("vcs_repository_support")
151 153 class TestDeprecatedRepositoryAPI(BackendTestMixin):
152 154 recreate_repo_per_test = False
153 155
154 156 def test_revisions_is_deprecated(self):
155 157 def get_revisions(repo):
156 158 return repo.revisions
157 159 pytest.deprecated_call(get_revisions, self.repo)
158 160
159 161 def test_get_changeset_is_deprecated(self):
160 162 pytest.deprecated_call(self.repo.get_changeset)
161 163
162 164 def test_get_changesets_is_deprecated(self):
163 165 pytest.deprecated_call(self.repo.get_changesets)
164 166
165 167 def test_in_memory_changeset_is_deprecated(self):
166 168 def get_imc(repo):
167 169 return repo.in_memory_changeset
168 170 pytest.deprecated_call(get_imc, self.repo)
169 171
170 172
171 173 # TODO: these tests are incomplete, must check the resulting compare result for
172 174 # correcteness
173 175 class TestRepositoryCompare:
174 176
175 177 @pytest.mark.parametrize('merge', [True, False])
176 178 def test_compare_commits_of_same_repository(self, vcsbackend, merge):
177 179 target_repo = vcsbackend.create_repo(number_of_commits=5)
178 180 target_repo.compare(
179 181 target_repo[1].raw_id, target_repo[3].raw_id, target_repo,
180 182 merge=merge)
181 183
182 184 @pytest.mark.xfail_backends('svn')
183 185 @pytest.mark.parametrize('merge', [True, False])
184 186 def test_compare_cloned_repositories(self, vcsbackend, merge):
185 187 target_repo = vcsbackend.create_repo(number_of_commits=5)
186 188 source_repo = vcsbackend.clone_repo(target_repo)
187 189 assert target_repo != source_repo
188 190
189 191 vcsbackend.add_file(source_repo, 'newfile', 'somecontent')
190 192 source_commit = source_repo.get_commit()
191 193
192 194 target_repo.compare(
193 195 target_repo[1].raw_id, source_repo[3].raw_id, source_repo,
194 196 merge=merge)
195 197
196 198 @pytest.mark.xfail_backends('svn')
197 199 @pytest.mark.parametrize('merge', [True, False])
198 200 def test_compare_unrelated_repositories(self, vcsbackend, merge):
199 201 orig = vcsbackend.create_repo(number_of_commits=5)
200 202 unrelated = vcsbackend.create_repo(number_of_commits=5)
201 203 assert orig != unrelated
202 204
203 205 orig.compare(
204 206 orig[1].raw_id, unrelated[3].raw_id, unrelated, merge=merge)
205 207
206 208
207 209 class TestRepositoryGetCommonAncestor:
208 210
209 211 def test_get_common_ancestor_from_same_repo_existing(self, vcsbackend):
210 212 target_repo = vcsbackend.create_repo(number_of_commits=5)
211 213
212 214 expected_ancestor = target_repo[2].raw_id
213 215
214 216 assert target_repo.get_common_ancestor(
215 217 commit_id1=target_repo[2].raw_id,
216 218 commit_id2=target_repo[4].raw_id,
217 219 repo2=target_repo
218 220 ) == expected_ancestor
219 221
220 222 assert target_repo.get_common_ancestor(
221 223 commit_id1=target_repo[4].raw_id,
222 224 commit_id2=target_repo[2].raw_id,
223 225 repo2=target_repo
224 226 ) == expected_ancestor
225 227
226 228 @pytest.mark.xfail_backends("svn")
227 229 def test_get_common_ancestor_from_cloned_repo_existing(self, vcsbackend):
228 230 target_repo = vcsbackend.create_repo(number_of_commits=5)
229 231 source_repo = vcsbackend.clone_repo(target_repo)
230 232 assert target_repo != source_repo
231 233
232 234 vcsbackend.add_file(source_repo, 'newfile', 'somecontent')
233 235 source_commit = source_repo.get_commit()
234 236
235 237 expected_ancestor = target_repo[4].raw_id
236 238
237 239 assert target_repo.get_common_ancestor(
238 240 commit_id1=target_repo[4].raw_id,
239 241 commit_id2=source_commit.raw_id,
240 242 repo2=source_repo
241 243 ) == expected_ancestor
242 244
243 245 assert target_repo.get_common_ancestor(
244 246 commit_id1=source_commit.raw_id,
245 247 commit_id2=target_repo[4].raw_id,
246 248 repo2=target_repo
247 249 ) == expected_ancestor
248 250
249 251 @pytest.mark.xfail_backends("svn")
250 252 def test_get_common_ancestor_from_unrelated_repo_missing(self, vcsbackend):
251 253 original = vcsbackend.create_repo(number_of_commits=5)
252 254 unrelated = vcsbackend.create_repo(number_of_commits=5)
253 255 assert original != unrelated
254 256
255 257 assert original.get_common_ancestor(
256 258 commit_id1=original[0].raw_id,
257 259 commit_id2=unrelated[0].raw_id,
258 260 repo2=unrelated
259 261 ) == None
260 262
261 263 assert original.get_common_ancestor(
262 264 commit_id1=original[-1].raw_id,
263 265 commit_id2=unrelated[-1].raw_id,
264 266 repo2=unrelated
265 267 ) == None
266 268
267 269
268 270 @pytest.mark.backends("git", "hg")
269 271 class TestRepositoryMerge:
270 272 def prepare_for_success(self, vcsbackend):
271 273 self.target_repo = vcsbackend.create_repo(number_of_commits=1)
272 274 self.source_repo = vcsbackend.clone_repo(self.target_repo)
273 275 vcsbackend.add_file(self.target_repo, 'README_MERGE1', 'Version 1')
274 276 vcsbackend.add_file(self.source_repo, 'README_MERGE2', 'Version 2')
275 277 imc = self.source_repo.in_memory_commit
276 278 imc.add(FileNode('file_x', content=self.source_repo.name))
277 279 imc.commit(
278 280 message=u'Automatic commit from repo merge test',
279 281 author=u'Automatic')
280 282 self.target_commit = self.target_repo.get_commit()
281 283 self.source_commit = self.source_repo.get_commit()
282 284 # This only works for Git and Mercurial
283 285 default_branch = self.target_repo.DEFAULT_BRANCH_NAME
284 286 self.target_ref = Reference(
285 287 'branch', default_branch, self.target_commit.raw_id)
286 288 self.source_ref = Reference(
287 289 'branch', default_branch, self.source_commit.raw_id)
288 290 self.workspace = 'test-merge'
289 291
290 292 def prepare_for_conflict(self, vcsbackend):
291 293 self.target_repo = vcsbackend.create_repo(number_of_commits=1)
292 294 self.source_repo = vcsbackend.clone_repo(self.target_repo)
293 295 vcsbackend.add_file(self.target_repo, 'README_MERGE', 'Version 1')
294 296 vcsbackend.add_file(self.source_repo, 'README_MERGE', 'Version 2')
295 297 self.target_commit = self.target_repo.get_commit()
296 298 self.source_commit = self.source_repo.get_commit()
297 299 # This only works for Git and Mercurial
298 300 default_branch = self.target_repo.DEFAULT_BRANCH_NAME
299 301 self.target_ref = Reference(
300 302 'branch', default_branch, self.target_commit.raw_id)
301 303 self.source_ref = Reference(
302 304 'branch', default_branch, self.source_commit.raw_id)
303 305 self.workspace = 'test-merge'
304 306
305 307 def test_merge_success(self, vcsbackend):
306 308 self.prepare_for_success(vcsbackend)
307 309
308 310 merge_response = self.target_repo.merge(
309 311 self.target_ref, self.source_repo, self.source_ref, self.workspace,
310 312 'test user', 'test@rhodecode.com', 'merge message 1',
311 313 dry_run=False)
312 314 expected_merge_response = MergeResponse(
313 315 True, True, merge_response.merge_ref,
314 316 MergeFailureReason.NONE)
315 317 assert merge_response == expected_merge_response
316 318
317 319 target_repo = backends.get_backend(vcsbackend.alias)(
318 320 self.target_repo.path)
319 321 target_commits = list(target_repo.get_commits())
320 322 commit_ids = [c.raw_id for c in target_commits[:-1]]
321 323 assert self.source_ref.commit_id in commit_ids
322 324 assert self.target_ref.commit_id in commit_ids
323 325
324 326 merge_commit = target_commits[-1]
325 327 assert merge_commit.raw_id == merge_response.merge_ref.commit_id
326 328 assert merge_commit.message.strip() == 'merge message 1'
327 329 assert merge_commit.author == 'test user <test@rhodecode.com>'
328 330
329 331 # We call it twice so to make sure we can handle updates
330 332 target_ref = Reference(
331 333 self.target_ref.type, self.target_ref.name,
332 334 merge_response.merge_ref.commit_id)
333 335
334 336 merge_response = target_repo.merge(
335 337 target_ref, self.source_repo, self.source_ref, self.workspace,
336 338 'test user', 'test@rhodecode.com', 'merge message 2',
337 339 dry_run=False)
338 340 expected_merge_response = MergeResponse(
339 341 True, True, merge_response.merge_ref,
340 342 MergeFailureReason.NONE)
341 343 assert merge_response == expected_merge_response
342 344
343 345 target_repo = backends.get_backend(
344 346 vcsbackend.alias)(self.target_repo.path)
345 347 merge_commit = target_repo.get_commit(
346 348 merge_response.merge_ref.commit_id)
347 349 assert merge_commit.message.strip() == 'merge message 1'
348 350 assert merge_commit.author == 'test user <test@rhodecode.com>'
349 351
350 352 def test_merge_success_dry_run(self, vcsbackend):
351 353 self.prepare_for_success(vcsbackend)
352 354
353 355 merge_response = self.target_repo.merge(
354 356 self.target_ref, self.source_repo, self.source_ref, self.workspace,
355 357 dry_run=True)
356 358
357 359 # We call it twice so to make sure we can handle updates
358 360 merge_response_update = self.target_repo.merge(
359 361 self.target_ref, self.source_repo, self.source_ref, self.workspace,
360 362 dry_run=True)
361 363
362 364 # Multiple merges may differ in their commit id. Therefore we set the
363 365 # commit id to `None` before comparing the merge responses.
364 366 merge_response = merge_response._replace(
365 367 merge_ref=merge_response.merge_ref._replace(commit_id=None))
366 368 merge_response_update = merge_response_update._replace(
367 369 merge_ref=merge_response_update.merge_ref._replace(commit_id=None))
368 370
369 371 assert merge_response == merge_response_update
370 372 assert merge_response.possible is True
371 373 assert merge_response.executed is False
372 374 assert merge_response.merge_ref
373 375 assert merge_response.failure_reason is MergeFailureReason.NONE
374 376
375 377 @pytest.mark.parametrize('dry_run', [True, False])
376 378 def test_merge_conflict(self, vcsbackend, dry_run):
377 379 self.prepare_for_conflict(vcsbackend)
378 380 expected_merge_response = MergeResponse(
379 381 False, False, None, MergeFailureReason.MERGE_FAILED)
380 382
381 383 merge_response = self.target_repo.merge(
382 384 self.target_ref, self.source_repo, self.source_ref, self.workspace,
383 385 'test_user', 'test@rhodecode.com', 'test message', dry_run=dry_run)
384 386 assert merge_response == expected_merge_response
385 387
386 388 # We call it twice so to make sure we can handle updates
387 389 merge_response = self.target_repo.merge(
388 390 self.target_ref, self.source_repo, self.source_ref, self.workspace,
389 391 'test_user', 'test@rhodecode.com', 'test message', dry_run=dry_run)
390 392 assert merge_response == expected_merge_response
391 393
392 394 def test_merge_target_is_not_head(self, vcsbackend):
393 395 self.prepare_for_success(vcsbackend)
394 396 expected_merge_response = MergeResponse(
395 397 False, False, None, MergeFailureReason.TARGET_IS_NOT_HEAD)
396 398
397 399 target_ref = Reference(
398 400 self.target_ref.type, self.target_ref.name, '0' * 40)
399 401
400 402 merge_response = self.target_repo.merge(
401 403 target_ref, self.source_repo, self.source_ref, self.workspace,
402 404 dry_run=True)
403 405
404 406 assert merge_response == expected_merge_response
405 407
406 408 def test_merge_missing_source_reference(self, vcsbackend):
407 409 self.prepare_for_success(vcsbackend)
408 410 expected_merge_response = MergeResponse(
409 411 False, False, None, MergeFailureReason.MISSING_SOURCE_REF)
410 412
411 413 source_ref = Reference(
412 414 self.source_ref.type, 'not_existing', self.source_ref.commit_id)
413 415
414 416 merge_response = self.target_repo.merge(
415 417 self.target_ref, self.source_repo, source_ref, self.workspace,
416 418 dry_run=True)
417 419
418 420 assert merge_response == expected_merge_response
419 421
420 422 def test_merge_raises_exception(self, vcsbackend):
421 423 self.prepare_for_success(vcsbackend)
422 424 expected_merge_response = MergeResponse(
423 425 False, False, None, MergeFailureReason.UNKNOWN)
424 426
425 427 with mock.patch.object(self.target_repo, '_merge_repo',
426 428 side_effect=RepositoryError()):
427 429 merge_response = self.target_repo.merge(
428 430 self.target_ref, self.source_repo, self.source_ref,
429 431 self.workspace, dry_run=True)
430 432
431 433 assert merge_response == expected_merge_response
432 434
433 435 def test_merge_invalid_user_name(self, vcsbackend):
434 436 repo = vcsbackend.create_repo(number_of_commits=1)
435 437 ref = Reference('branch', 'master', 'not_used')
436 438 with pytest.raises(ValueError):
437 439 repo.merge(ref, self, ref, 'workspace_id')
438 440
439 441 def test_merge_invalid_user_email(self, vcsbackend):
440 442 repo = vcsbackend.create_repo(number_of_commits=1)
441 443 ref = Reference('branch', 'master', 'not_used')
442 444 with pytest.raises(ValueError):
443 445 repo.merge(ref, self, ref, 'workspace_id', 'user name')
444 446
445 447 def test_merge_invalid_message(self, vcsbackend):
446 448 repo = vcsbackend.create_repo(number_of_commits=1)
447 449 ref = Reference('branch', 'master', 'not_used')
448 450 with pytest.raises(ValueError):
449 451 repo.merge(
450 452 ref, self, ref, 'workspace_id', 'user name', 'user@email.com')
451 453
452 454
455 @pytest.mark.usefixtures("vcs_repository_support")
453 456 class TestRepositoryStrip(BackendTestMixin):
454 457 recreate_repo_per_test = True
455 458
456 459 @classmethod
457 460 def _get_commits(cls):
458 461 commits = [
459 462 {
460 463 'message': 'Initial commit',
461 464 'author': 'Joe Doe <joe.doe@example.com>',
462 465 'date': datetime.datetime(2010, 1, 1, 20),
463 466 'branch': 'master',
464 467 'added': [
465 468 FileNode('foobar', content='foobar'),
466 469 FileNode('foobar2', content='foobar2'),
467 470 ],
468 471 },
469 472 ]
470 473 for x in xrange(10):
471 474 commit_data = {
472 475 'message': 'Changed foobar - commit%s' % x,
473 476 'author': 'Jane Doe <jane.doe@example.com>',
474 477 'date': datetime.datetime(2010, 1, 1, 21, x),
475 478 'branch': 'master',
476 479 'changed': [
477 480 FileNode('foobar', 'FOOBAR - %s' % x),
478 481 ],
479 482 }
480 483 commits.append(commit_data)
481 484 return commits
482 485
483 486 @pytest.mark.backends("git", "hg")
484 487 def test_strip_commit(self):
485 488 tip = self.repo.get_commit()
486 489 assert tip.idx == 10
487 490 self.repo.strip(tip.raw_id, self.repo.DEFAULT_BRANCH_NAME)
488 491
489 492 tip = self.repo.get_commit()
490 493 assert tip.idx == 9
491 494
492 495 @pytest.mark.backends("git", "hg")
493 496 def test_strip_multiple_commits(self):
494 497 tip = self.repo.get_commit()
495 498 assert tip.idx == 10
496 499
497 500 old = self.repo.get_commit(commit_idx=5)
498 501 self.repo.strip(old.raw_id, self.repo.DEFAULT_BRANCH_NAME)
499 502
500 503 tip = self.repo.get_commit()
501 504 assert tip.idx == 4
502 505
503 506
504 507 @pytest.mark.backends('hg', 'git')
505 508 class TestRepositoryPull:
506 509
507 510 def test_pull(self, vcsbackend):
508 511 source_repo = vcsbackend.repo
509 512 target_repo = vcsbackend.create_repo()
510 513 assert len(source_repo.commit_ids) > len(target_repo.commit_ids)
511 514
512 515 target_repo.pull(source_repo.path)
513 516 # Note: Get a fresh instance, avoids caching trouble
514 517 target_repo = vcsbackend.backend(target_repo.path)
515 518 assert len(source_repo.commit_ids) == len(target_repo.commit_ids)
516 519
517 520 def test_pull_wrong_path(self, vcsbackend):
518 521 target_repo = vcsbackend.create_repo()
519 522 with pytest.raises(RepositoryError):
520 523 target_repo.pull(target_repo.path + "wrong")
521 524
522 525 def test_pull_specific_commits(self, vcsbackend):
523 526 source_repo = vcsbackend.repo
524 527 target_repo = vcsbackend.create_repo()
525 528
526 529 second_commit = source_repo[1].raw_id
527 530 if vcsbackend.alias == 'git':
528 531 second_commit_ref = 'refs/test-refs/a'
529 532 source_repo.set_refs(second_commit_ref, second_commit)
530 533
531 534 target_repo.pull(source_repo.path, commit_ids=[second_commit])
532 535 target_repo = vcsbackend.backend(target_repo.path)
533 536 assert 2 == len(target_repo.commit_ids)
534 537 assert second_commit == target_repo.get_commit().raw_id
@@ -1,69 +1,70 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 from rhodecode.tests.vcs.base import BackendTestMixin
23 from rhodecode.tests.vcs.conftest import BackendTestMixin
24 24 from rhodecode.lib.vcs.exceptions import (
25 25 TagAlreadyExistError, TagDoesNotExistError)
26 26
27 27
28 28 pytestmark = pytest.mark.backends("git", "hg")
29 29
30 30
31 @pytest.mark.usefixtures("vcs_repository_support")
31 32 class TestTags(BackendTestMixin):
32 33
33 34 def test_new_tag(self):
34 35 tip = self.repo.get_commit()
35 36 tagsize = len(self.repo.tags)
36 37 tag = self.repo.tag('last-commit', 'joe', tip.raw_id)
37 38
38 39 assert len(self.repo.tags) == tagsize + 1
39 40 for top, __, __ in tip.walk():
40 41 assert top == tag.get_node(top.path)
41 42
42 43 def test_tag_already_exist(self):
43 44 tip = self.repo.get_commit()
44 45 self.repo.tag('last-commit', 'joe', tip.raw_id)
45 46
46 47 with pytest.raises(TagAlreadyExistError):
47 48 self.repo.tag('last-commit', 'joe', tip.raw_id)
48 49
49 50 commit = self.repo.get_commit(commit_idx=0)
50 51 with pytest.raises(TagAlreadyExistError):
51 52 self.repo.tag('last-commit', 'jane', commit.raw_id)
52 53
53 54 def test_remove_tag(self):
54 55 tip = self.repo.get_commit()
55 56 self.repo.tag('last-commit', 'joe', tip.raw_id)
56 57 tagsize = len(self.repo.tags)
57 58
58 59 self.repo.remove_tag('last-commit', user='evil joe')
59 60 assert len(self.repo.tags) == tagsize - 1
60 61
61 62 def test_remove_tag_which_does_not_exist(self):
62 63 with pytest.raises(TagDoesNotExistError):
63 64 self.repo.remove_tag('last-commit', user='evil joe')
64 65
65 66 def test_name_with_slash(self):
66 67 self.repo.tag('19/10/11', 'joe')
67 68 assert '19/10/11' in self.repo.tags
68 69 self.repo.tag('11', 'joe')
69 70 assert '11' in self.repo.tags
@@ -1,195 +1,195 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 import os
21 22 import datetime
22 import os
23 23 import subprocess32
24 24
25 25 import pytest
26 26
27 27 from rhodecode.lib.vcs.exceptions import VCSError
28 28 from rhodecode.lib.vcs.utils import author_email, author_name
29 29 from rhodecode.lib.vcs.utils.helpers import get_scm
30 30 from rhodecode.lib.vcs.utils.helpers import get_scms_for_path
31 31 from rhodecode.lib.vcs.utils.helpers import parse_datetime
32 32 from rhodecode.lib.vcs.utils.paths import get_dirs_for_path
33 33
34 34
35 35 @pytest.mark.usefixtures("baseapp")
36 class TestPaths:
36 class TestPaths(object):
37 37
38 38 def _test_get_dirs_for_path(self, path, expected):
39 39 """
40 40 Tests if get_dirs_for_path returns same as expected.
41 41 """
42 42 expected = sorted(expected)
43 43 result = sorted(get_dirs_for_path(path))
44 44 assert result == expected, (
45 45 "%s != %s which was expected result for path %s"
46 46 % (result, expected, path))
47 47
48 48 def test_get_dirs_for_path(self):
49 49 path = 'foo/bar/baz/file'
50 50 paths_and_results = (
51 51 ('foo/bar/baz/file', ['foo', 'foo/bar', 'foo/bar/baz']),
52 52 ('foo/bar/', ['foo', 'foo/bar']),
53 53 ('foo/bar', ['foo']),
54 54 )
55 55 for path, expected in paths_and_results:
56 56 self._test_get_dirs_for_path(path, expected)
57 57
58 58 def test_get_scms_for_path(self, tmpdir):
59 59 new = tmpdir.strpath
60 60 assert get_scms_for_path(new) == []
61 61
62 62 os.mkdir(os.path.join(new, '.tux'))
63 63 assert get_scms_for_path(new) == []
64 64
65 65 os.mkdir(os.path.join(new, '.git'))
66 66 assert set(get_scms_for_path(new)) == set(['git'])
67 67
68 68 os.mkdir(os.path.join(new, '.hg'))
69 69 assert set(get_scms_for_path(new)) == set(['git', 'hg'])
70 70
71 71
72 class TestGetScm:
72 class TestGetScm(object):
73 73
74 74 def test_existing_repository(self, vcs_repository_support):
75 75 alias, repo = vcs_repository_support
76 76 assert (alias, repo.path) == get_scm(repo.path)
77 77
78 78 def test_raises_if_path_is_empty(self, tmpdir):
79 79 with pytest.raises(VCSError):
80 80 get_scm(str(tmpdir))
81 81
82 82 def test_get_scm_error_path(self):
83 83 with pytest.raises(VCSError):
84 84 get_scm('err')
85 85
86 86 def test_get_two_scms_for_path(self, tmpdir):
87 87 multialias_repo_path = str(tmpdir)
88 88
89 89 subprocess32.check_call(['hg', 'init', multialias_repo_path])
90 90 subprocess32.check_call(['git', 'init', multialias_repo_path])
91 91
92 92 with pytest.raises(VCSError):
93 93 get_scm(multialias_repo_path)
94 94
95 95 def test_ignores_svn_working_copy(self, tmpdir):
96 96 tmpdir.mkdir('.svn')
97 97 with pytest.raises(VCSError):
98 98 get_scm(tmpdir.strpath)
99 99
100 100
101 class TestParseDatetime:
101 class TestParseDatetime(object):
102 102
103 103 def test_datetime_text(self):
104 104 assert parse_datetime('2010-04-07 21:29:41') == \
105 105 datetime.datetime(2010, 4, 7, 21, 29, 41)
106 106
107 107 def test_no_seconds(self):
108 108 assert parse_datetime('2010-04-07 21:29') == \
109 109 datetime.datetime(2010, 4, 7, 21, 29)
110 110
111 111 def test_date_only(self):
112 112 assert parse_datetime('2010-04-07') == \
113 113 datetime.datetime(2010, 4, 7)
114 114
115 115 def test_another_format(self):
116 116 assert parse_datetime('04/07/10 21:29:41') == \
117 117 datetime.datetime(2010, 4, 7, 21, 29, 41)
118 118
119 119 def test_now(self):
120 120 assert parse_datetime('now') - datetime.datetime.now() < \
121 121 datetime.timedelta(seconds=1)
122 122
123 123 def test_today(self):
124 124 today = datetime.date.today()
125 125 assert parse_datetime('today') == \
126 126 datetime.datetime(*today.timetuple()[:3])
127 127
128 128 def test_yesterday(self):
129 129 yesterday = datetime.date.today() - datetime.timedelta(days=1)
130 130 assert parse_datetime('yesterday') == \
131 131 datetime.datetime(*yesterday.timetuple()[:3])
132 132
133 133 def test_tomorrow(self):
134 134 tomorrow = datetime.date.today() + datetime.timedelta(days=1)
135 135 args = tomorrow.timetuple()[:3] + (23, 59, 59)
136 136 assert parse_datetime('tomorrow') == datetime.datetime(*args)
137 137
138 138 def test_days(self):
139 139 timestamp = datetime.datetime.today() - datetime.timedelta(days=3)
140 140 args = timestamp.timetuple()[:3] + (0, 0, 0, 0)
141 141 expected = datetime.datetime(*args)
142 142 assert parse_datetime('3d') == expected
143 143 assert parse_datetime('3 d') == expected
144 144 assert parse_datetime('3 day') == expected
145 145 assert parse_datetime('3 days') == expected
146 146
147 147 def test_weeks(self):
148 148 timestamp = datetime.datetime.today() - datetime.timedelta(days=3 * 7)
149 149 args = timestamp.timetuple()[:3] + (0, 0, 0, 0)
150 150 expected = datetime.datetime(*args)
151 151 assert parse_datetime('3w') == expected
152 152 assert parse_datetime('3 w') == expected
153 153 assert parse_datetime('3 week') == expected
154 154 assert parse_datetime('3 weeks') == expected
155 155
156 156 def test_mixed(self):
157 157 timestamp = (
158 158 datetime.datetime.today() - datetime.timedelta(days=2 * 7 + 3))
159 159 args = timestamp.timetuple()[:3] + (0, 0, 0, 0)
160 160 expected = datetime.datetime(*args)
161 161 assert parse_datetime('2w3d') == expected
162 162 assert parse_datetime('2w 3d') == expected
163 163 assert parse_datetime('2w 3 days') == expected
164 164 assert parse_datetime('2 weeks 3 days') == expected
165 165
166 166
167 167 @pytest.mark.parametrize("test_str, name, email", [
168 168 ('Marcin Kuzminski <marcin@python-works.com>',
169 169 'Marcin Kuzminski', 'marcin@python-works.com'),
170 170 ('Marcin Kuzminski Spaces < marcin@python-works.com >',
171 171 'Marcin Kuzminski Spaces', 'marcin@python-works.com'),
172 172 ('Marcin Kuzminski <marcin.kuzminski@python-works.com>',
173 173 'Marcin Kuzminski', 'marcin.kuzminski@python-works.com'),
174 174 ('mrf RFC_SPEC <marcin+kuzminski@python-works.com>',
175 175 'mrf RFC_SPEC', 'marcin+kuzminski@python-works.com'),
176 176 ('username <user@email.com>',
177 177 'username', 'user@email.com'),
178 178 ('username <user@email.com',
179 179 'username', 'user@email.com'),
180 180 ('broken missing@email.com',
181 181 'broken', 'missing@email.com'),
182 182 ('<justemail@mail.com>',
183 183 '', 'justemail@mail.com'),
184 184 ('justname',
185 185 'justname', ''),
186 186 ('Mr Double Name withemail@email.com ',
187 187 'Mr Double Name', 'withemail@email.com'),
188 188 ])
189 class TestAuthorExtractors:
189 class TestAuthorExtractors(object):
190 190
191 191 def test_author_email(self, test_str, name, email):
192 192 assert email == author_email(test_str)
193 193
194 194 def test_author_name(self, test_str, name, email):
195 195 assert name == author_name(test_str)
@@ -1,77 +1,77 b''
1 1 ################################################################################
2 2 # RhodeCode VCSServer with HTTP Backend - configuration #
3 3 # #
4 4 ################################################################################
5 5
6 6 [app:main]
7 7 use = egg:rhodecode-vcsserver
8 8
9 9 pyramid.default_locale_name = en
10 10 pyramid.includes =
11 pyramid.reload_templates = true
12 11
13 12 # default locale used by VCS systems
14 13 locale = en_US.UTF-8
15 14
16 15 # cache regions, please don't change
17 16 beaker.cache.regions = repo_object
18 17 beaker.cache.repo_object.type = memorylru
19 18 beaker.cache.repo_object.max_items = 100
20 19 # cache auto-expires after N seconds
21 20 beaker.cache.repo_object.expire = 300
22 21 beaker.cache.repo_object.enabled = true
23 22
24 23 [server:main]
25 use = egg:waitress#main
26 24 host = 127.0.0.1
27 25 port = 9900
28 26
27 use = egg:gunicorn#main
28
29 29 ################################
30 30 ### LOGGING CONFIGURATION ####
31 31 ################################
32 32 [loggers]
33 33 keys = root, vcsserver, beaker
34 34
35 35 [handlers]
36 36 keys = console
37 37
38 38 [formatters]
39 39 keys = generic
40 40
41 41 #############
42 42 ## LOGGERS ##
43 43 #############
44 44 [logger_root]
45 45 level = NOTSET
46 46 handlers = console
47 47
48 48 [logger_vcsserver]
49 49 level = DEBUG
50 50 handlers =
51 51 qualname = vcsserver
52 52 propagate = 1
53 53
54 54 [logger_beaker]
55 55 level = DEBUG
56 56 handlers =
57 57 qualname = beaker
58 58 propagate = 1
59 59
60 60
61 61 ##############
62 62 ## HANDLERS ##
63 63 ##############
64 64
65 65 [handler_console]
66 66 class = StreamHandler
67 67 args = (sys.stderr,)
68 68 level = INFO
69 69 formatter = generic
70 70
71 71 ################
72 72 ## FORMATTERS ##
73 73 ################
74 74
75 75 [formatter_generic]
76 76 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
77 77 datefmt = %Y-%m-%d %H:%M:%S
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now