##// 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 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 py.test config for test suite for making push/pull operations.
22 py.test config for test suite for making push/pull operations.
23
23
24 .. important::
24 .. important::
25
25
26 You must have git >= 1.8.5 for tests to work fine. With 68b939b git started
26 You must have git >= 1.8.5 for tests to work fine. With 68b939b git started
27 to redirect things to stderr instead of stdout.
27 to redirect things to stderr instead of stdout.
28 """
28 """
29
29
30 import ConfigParser
30 import ConfigParser
31 import os
31 import os
32 import subprocess32
32 import subprocess32
33 import tempfile
33 import tempfile
34 import textwrap
34 import textwrap
35 import pytest
35 import pytest
36
36
37 import rhodecode
37 import rhodecode
38 from rhodecode.model.db import Repository
38 from rhodecode.model.db import Repository
39 from rhodecode.model.meta import Session
39 from rhodecode.model.meta import Session
40 from rhodecode.model.settings import SettingsModel
40 from rhodecode.model.settings import SettingsModel
41 from rhodecode.tests import (
41 from rhodecode.tests import (
42 GIT_REPO, HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,)
42 GIT_REPO, HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,)
43 from rhodecode.tests.fixture import Fixture
43 from rhodecode.tests.fixture import Fixture
44 from rhodecode.tests.utils import is_url_reachable, wait_for_url
44 from rhodecode.tests.utils import is_url_reachable, wait_for_url
45
45
46 RC_LOG = os.path.join(tempfile.gettempdir(), 'rc.log')
46 RC_LOG = os.path.join(tempfile.gettempdir(), 'rc.log')
47 REPO_GROUP = 'a_repo_group'
47 REPO_GROUP = 'a_repo_group'
48 HG_REPO_WITH_GROUP = '%s/%s' % (REPO_GROUP, HG_REPO)
48 HG_REPO_WITH_GROUP = '%s/%s' % (REPO_GROUP, HG_REPO)
49 GIT_REPO_WITH_GROUP = '%s/%s' % (REPO_GROUP, GIT_REPO)
49 GIT_REPO_WITH_GROUP = '%s/%s' % (REPO_GROUP, GIT_REPO)
50
50
51
51
52 def assert_no_running_instance(url):
52 def assert_no_running_instance(url):
53 if is_url_reachable(url):
53 if is_url_reachable(url):
54 print("Hint: Usually this means another instance of Enterprise "
54 print("Hint: Usually this means another instance of Enterprise "
55 "is running in the background.")
55 "is running in the background.")
56 pytest.fail(
56 pytest.fail(
57 "Port is not free at %s, cannot start web interface" % url)
57 "Port is not free at %s, cannot start web interface" % url)
58
58
59
59
60 def get_port(pyramid_config):
60 def get_port(pyramid_config):
61 config = ConfigParser.ConfigParser()
61 config = ConfigParser.ConfigParser()
62 config.read(pyramid_config)
62 config.read(pyramid_config)
63 return config.get('server:main', 'port')
63 return config.get('server:main', 'port')
64
64
65
65
66 def get_host_url(pyramid_config):
66 def get_host_url(pyramid_config):
67 """Construct the host url using the port in the test configuration."""
67 """Construct the host url using the port in the test configuration."""
68 return '127.0.0.1:%s' % get_port(pyramid_config)
68 return '127.0.0.1:%s' % get_port(pyramid_config)
69
69
70
70
71 class RcWebServer(object):
71 class RcWebServer(object):
72 """
72 """
73 Represents a running RCE web server used as a test fixture.
73 Represents a running RCE web server used as a test fixture.
74 """
74 """
75 def __init__(self, pyramid_config, log_file):
75 def __init__(self, pyramid_config, log_file):
76 self.pyramid_config = pyramid_config
76 self.pyramid_config = pyramid_config
77 self.log_file = log_file
77 self.log_file = log_file
78
78
79 def repo_clone_url(self, repo_name, **kwargs):
79 def repo_clone_url(self, repo_name, **kwargs):
80 params = {
80 params = {
81 'user': TEST_USER_ADMIN_LOGIN,
81 'user': TEST_USER_ADMIN_LOGIN,
82 'passwd': TEST_USER_ADMIN_PASS,
82 'passwd': TEST_USER_ADMIN_PASS,
83 'host': get_host_url(self.pyramid_config),
83 'host': get_host_url(self.pyramid_config),
84 'cloned_repo': repo_name,
84 'cloned_repo': repo_name,
85 }
85 }
86 params.update(**kwargs)
86 params.update(**kwargs)
87 _url = 'http://%(user)s:%(passwd)s@%(host)s/%(cloned_repo)s' % params
87 _url = 'http://%(user)s:%(passwd)s@%(host)s/%(cloned_repo)s' % params
88 return _url
88 return _url
89
89
90 def host_url(self):
90 def host_url(self):
91 return 'http://' + get_host_url(self.pyramid_config)
91 return 'http://' + get_host_url(self.pyramid_config)
92
92
93 def get_rc_log(self):
93 def get_rc_log(self):
94 with open(self.log_file) as f:
94 with open(self.log_file) as f:
95 return f.read()
95 return f.read()
96
96
97
97
98 @pytest.fixture(scope="module")
98 @pytest.fixture(scope="module")
99 def rcextensions(request, baseapp, tmpdir_factory):
99 def rcextensions(request, baseapp, tmpdir_factory):
100 """
100 """
101 Installs a testing rcextensions pack to ensure they work as expected.
101 Installs a testing rcextensions pack to ensure they work as expected.
102 """
102 """
103 init_content = textwrap.dedent("""
103 init_content = textwrap.dedent("""
104 # Forward import the example rcextensions to make it
104 # Forward import the example rcextensions to make it
105 # active for our tests.
105 # active for our tests.
106 from rhodecode.tests.other.example_rcextensions import *
106 from rhodecode.tests.other.example_rcextensions import *
107 """)
107 """)
108
108
109 # Note: rcextensions are looked up based on the path of the ini file
109 # Note: rcextensions are looked up based on the path of the ini file
110 root_path = tmpdir_factory.getbasetemp()
110 root_path = tmpdir_factory.getbasetemp()
111 rcextensions_path = root_path.join('rcextensions')
111 rcextensions_path = root_path.join('rcextensions')
112 init_path = rcextensions_path.join('__init__.py')
112 init_path = rcextensions_path.join('__init__.py')
113
113
114 if rcextensions_path.check():
114 if rcextensions_path.check():
115 pytest.fail(
115 pytest.fail(
116 "Path for rcextensions already exists, please clean up before "
116 "Path for rcextensions already exists, please clean up before "
117 "test run this path: %s" % (rcextensions_path, ))
117 "test run this path: %s" % (rcextensions_path, ))
118 return
118 return
119
119
120 request.addfinalizer(rcextensions_path.remove)
120 request.addfinalizer(rcextensions_path.remove)
121 init_path.write_binary(init_content, ensure=True)
121 init_path.write_binary(init_content, ensure=True)
122
122
123
123
124 @pytest.fixture(scope="module")
124 @pytest.fixture(scope="module")
125 def repos(request, baseapp):
125 def repos(request, baseapp):
126 """Create a copy of each test repo in a repo group."""
126 """Create a copy of each test repo in a repo group."""
127 fixture = Fixture()
127 fixture = Fixture()
128 repo_group = fixture.create_repo_group(REPO_GROUP)
128 repo_group = fixture.create_repo_group(REPO_GROUP)
129 repo_group_id = repo_group.group_id
129 repo_group_id = repo_group.group_id
130 fixture.create_fork(HG_REPO, HG_REPO,
130 fixture.create_fork(HG_REPO, HG_REPO,
131 repo_name_full=HG_REPO_WITH_GROUP,
131 repo_name_full=HG_REPO_WITH_GROUP,
132 repo_group=repo_group_id)
132 repo_group=repo_group_id)
133 fixture.create_fork(GIT_REPO, GIT_REPO,
133 fixture.create_fork(GIT_REPO, GIT_REPO,
134 repo_name_full=GIT_REPO_WITH_GROUP,
134 repo_name_full=GIT_REPO_WITH_GROUP,
135 repo_group=repo_group_id)
135 repo_group=repo_group_id)
136
136
137 @request.addfinalizer
137 @request.addfinalizer
138 def cleanup():
138 def cleanup():
139 fixture.destroy_repo(HG_REPO_WITH_GROUP)
139 fixture.destroy_repo(HG_REPO_WITH_GROUP)
140 fixture.destroy_repo(GIT_REPO_WITH_GROUP)
140 fixture.destroy_repo(GIT_REPO_WITH_GROUP)
141 fixture.destroy_repo_group(repo_group_id)
141 fixture.destroy_repo_group(repo_group_id)
142
142
143
143
144 @pytest.fixture(scope="module")
144 @pytest.fixture(scope="module")
145 def rc_web_server_config(testini_factory):
145 def rc_web_server_config(testini_factory):
146 """
146 """
147 Configuration file used for the fixture `rc_web_server`.
147 Configuration file used for the fixture `rc_web_server`.
148 """
148 """
149 CUSTOM_PARAMS = [
149 CUSTOM_PARAMS = [
150 {'handler_console': {'level': 'DEBUG'}},
150 {'handler_console': {'level': 'DEBUG'}},
151 ]
151 ]
152 return testini_factory(CUSTOM_PARAMS)
152 return testini_factory(CUSTOM_PARAMS)
153
153
154
154
155 @pytest.fixture(scope="module")
155 @pytest.fixture(scope="module")
156 def rc_web_server(
156 def rc_web_server(
157 request, baseapp, rc_web_server_config, repos, rcextensions):
157 request, baseapp, rc_web_server_config, repos, rcextensions):
158 """
158 """
159 Run the web server as a subprocess.
159 Run the web server as a subprocess.
160
160
161 Since we have already a running vcsserver, this is not spawned again.
161 Since we have already a running vcsserver, this is not spawned again.
162 """
162 """
163 env = os.environ.copy()
163 env = os.environ.copy()
164 env['RC_NO_TMP_PATH'] = '1'
164 env['RC_NO_TMP_PATH'] = '1'
165
165
166 rc_log = list(RC_LOG.partition('.log'))
166 rc_log = list(RC_LOG.partition('.log'))
167 rc_log.insert(1, get_port(rc_web_server_config))
167 rc_log.insert(1, get_port(rc_web_server_config))
168 rc_log = ''.join(rc_log)
168 rc_log = ''.join(rc_log)
169
169
170 server_out = open(rc_log, 'w')
170 server_out = open(rc_log, 'w')
171
171
172 host_url = 'http://' + get_host_url(rc_web_server_config)
172 host_url = 'http://' + get_host_url(rc_web_server_config)
173 assert_no_running_instance(host_url)
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 print('Starting rhodecode server: {}'.format(host_url))
176 print('Starting rhodecode server: {}'.format(host_url))
177 print('Command: {}'.format(command))
177 print('Command: {}'.format(command))
178 print('Logfile: {}'.format(rc_log))
178 print('Logfile: {}'.format(rc_log))
179
179
180 proc = subprocess32.Popen(
180 proc = subprocess32.Popen(
181 command, bufsize=0, env=env, stdout=server_out, stderr=server_out)
181 command, bufsize=0, env=env, stdout=server_out, stderr=server_out)
182
182
183 wait_for_url(host_url, timeout=30)
183 wait_for_url(host_url, timeout=30)
184
184
185 @request.addfinalizer
185 @request.addfinalizer
186 def stop_web_server():
186 def stop_web_server():
187 # TODO: Find out how to integrate with the reporting of py.test to
187 # TODO: Find out how to integrate with the reporting of py.test to
188 # make this information available.
188 # make this information available.
189 print("\nServer log file written to %s" % (rc_log, ))
189 print("\nServer log file written to %s" % (rc_log, ))
190 proc.kill()
190 proc.kill()
191 server_out.flush()
191 server_out.flush()
192 server_out.close()
192 server_out.close()
193
193
194 return RcWebServer(rc_web_server_config, log_file=rc_log)
194 return RcWebServer(rc_web_server_config, log_file=rc_log)
195
195
196
196
197 @pytest.fixture
197 @pytest.fixture
198 def disable_locking(baseapp):
198 def disable_locking(baseapp):
199 r = Repository.get_by_repo_name(GIT_REPO)
199 r = Repository.get_by_repo_name(GIT_REPO)
200 Repository.unlock(r)
200 Repository.unlock(r)
201 r.enable_locking = False
201 r.enable_locking = False
202 Session().add(r)
202 Session().add(r)
203 Session().commit()
203 Session().commit()
204
204
205 r = Repository.get_by_repo_name(HG_REPO)
205 r = Repository.get_by_repo_name(HG_REPO)
206 Repository.unlock(r)
206 Repository.unlock(r)
207 r.enable_locking = False
207 r.enable_locking = False
208 Session().add(r)
208 Session().add(r)
209 Session().commit()
209 Session().commit()
210
210
211
211
212 @pytest.fixture
212 @pytest.fixture
213 def enable_auth_plugins(request, baseapp, csrf_token):
213 def enable_auth_plugins(request, baseapp, csrf_token):
214 """
214 """
215 Return a factory object that when called, allows to control which
215 Return a factory object that when called, allows to control which
216 authentication plugins are enabled.
216 authentication plugins are enabled.
217 """
217 """
218 def _enable_plugins(plugins_list, override=None):
218 def _enable_plugins(plugins_list, override=None):
219 override = override or {}
219 override = override or {}
220 params = {
220 params = {
221 'auth_plugins': ','.join(plugins_list),
221 'auth_plugins': ','.join(plugins_list),
222 }
222 }
223
223
224 # helper translate some names to others
224 # helper translate some names to others
225 name_map = {
225 name_map = {
226 'token': 'authtoken'
226 'token': 'authtoken'
227 }
227 }
228
228
229 for module in plugins_list:
229 for module in plugins_list:
230 plugin_name = module.partition('#')[-1]
230 plugin_name = module.partition('#')[-1]
231 if plugin_name in name_map:
231 if plugin_name in name_map:
232 plugin_name = name_map[plugin_name]
232 plugin_name = name_map[plugin_name]
233 enabled_plugin = 'auth_%s_enabled' % plugin_name
233 enabled_plugin = 'auth_%s_enabled' % plugin_name
234 cache_ttl = 'auth_%s_cache_ttl' % plugin_name
234 cache_ttl = 'auth_%s_cache_ttl' % plugin_name
235
235
236 # default params that are needed for each plugin,
236 # default params that are needed for each plugin,
237 # `enabled` and `cache_ttl`
237 # `enabled` and `cache_ttl`
238 params.update({
238 params.update({
239 enabled_plugin: True,
239 enabled_plugin: True,
240 cache_ttl: 0
240 cache_ttl: 0
241 })
241 })
242 if override.get:
242 if override.get:
243 params.update(override.get(module, {}))
243 params.update(override.get(module, {}))
244
244
245 validated_params = params
245 validated_params = params
246 for k, v in validated_params.items():
246 for k, v in validated_params.items():
247 setting = SettingsModel().create_or_update_setting(k, v)
247 setting = SettingsModel().create_or_update_setting(k, v)
248 Session().add(setting)
248 Session().add(setting)
249 Session().commit()
249 Session().commit()
250
250
251 def cleanup():
251 def cleanup():
252 _enable_plugins(['egg:rhodecode-enterprise-ce#rhodecode'])
252 _enable_plugins(['egg:rhodecode-enterprise-ce#rhodecode'])
253
253
254 request.addfinalizer(cleanup)
254 request.addfinalizer(cleanup)
255
255
256 return _enable_plugins
256 return _enable_plugins
257
257
258
258
259 @pytest.fixture
259 @pytest.fixture
260 def fs_repo_only(request, rhodecode_fixtures):
260 def fs_repo_only(request, rhodecode_fixtures):
261 def fs_repo_fabric(repo_name, repo_type):
261 def fs_repo_fabric(repo_name, repo_type):
262 rhodecode_fixtures.create_repo(repo_name, repo_type=repo_type)
262 rhodecode_fixtures.create_repo(repo_name, repo_type=repo_type)
263 rhodecode_fixtures.destroy_repo(repo_name, fs_remove=False)
263 rhodecode_fixtures.destroy_repo(repo_name, fs_remove=False)
264
264
265 def cleanup():
265 def cleanup():
266 rhodecode_fixtures.destroy_repo(repo_name, fs_remove=True)
266 rhodecode_fixtures.destroy_repo(repo_name, fs_remove=True)
267 rhodecode_fixtures.destroy_repo_on_filesystem(repo_name)
267 rhodecode_fixtures.destroy_repo_on_filesystem(repo_name)
268
268
269 request.addfinalizer(cleanup)
269 request.addfinalizer(cleanup)
270
270
271 return fs_repo_fabric
271 return fs_repo_fabric
@@ -1,380 +1,400 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 import json
22 import json
23 import platform
23 import platform
24 import socket
24 import socket
25 import tempfile
26
25 import subprocess32
27 import subprocess32
26 import time
28 import time
27 from urllib2 import urlopen, URLError
29 from urllib2 import urlopen, URLError
28
30
29 import configobj
31 import configobj
30 import pytest
32 import pytest
31
33
32 import pyramid.paster
34 import pyramid.paster
33
35
34 from rhodecode.lib.pyramid_utils import get_app_config
36 from rhodecode.lib.pyramid_utils import get_app_config
35 from rhodecode.tests.fixture import TestINI
37 from rhodecode.tests.fixture import TestINI
36 import rhodecode
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 def _parse_json(value):
44 def _parse_json(value):
40 return json.loads(value) if value else None
45 return json.loads(value) if value else None
41
46
42
47
43 def pytest_addoption(parser):
48 def pytest_addoption(parser):
44 parser.addoption(
49 parser.addoption(
45 '--test-loglevel', dest='test_loglevel',
50 '--test-loglevel', dest='test_loglevel',
46 help="Set default Logging level for tests, warn (default), info, debug")
51 help="Set default Logging level for tests, warn (default), info, debug")
47 group = parser.getgroup('pylons')
52 group = parser.getgroup('pylons')
48 group.addoption(
53 group.addoption(
49 '--with-pylons', dest='pyramid_config',
54 '--with-pylons', dest='pyramid_config',
50 help="Set up a Pylons environment with the specified config file.")
55 help="Set up a Pylons environment with the specified config file.")
51 group.addoption(
56 group.addoption(
52 '--ini-config-override', action='store', type=_parse_json,
57 '--ini-config-override', action='store', type=_parse_json,
53 default=None, dest='pyramid_config_override', help=(
58 default=None, dest='pyramid_config_override', help=(
54 "Overrides the .ini file settings. Should be specified in JSON"
59 "Overrides the .ini file settings. Should be specified in JSON"
55 " format, e.g. '{\"section\": {\"parameter\": \"value\", ...}}'"
60 " format, e.g. '{\"section\": {\"parameter\": \"value\", ...}}'"
56 )
61 )
57 )
62 )
58 parser.addini(
63 parser.addini(
59 'pyramid_config',
64 'pyramid_config',
60 "Set up a Pyramid environment with the specified config file.")
65 "Set up a Pyramid environment with the specified config file.")
61
66
62 vcsgroup = parser.getgroup('vcs')
67 vcsgroup = parser.getgroup('vcs')
63 vcsgroup.addoption(
68 vcsgroup.addoption(
64 '--without-vcsserver', dest='with_vcsserver', action='store_false',
69 '--without-vcsserver', dest='with_vcsserver', action='store_false',
65 help="Do not start the VCSServer in a background process.")
70 help="Do not start the VCSServer in a background process.")
66 vcsgroup.addoption(
71 vcsgroup.addoption(
67 '--with-vcsserver-http', dest='vcsserver_config_http',
72 '--with-vcsserver-http', dest='vcsserver_config_http',
68 help="Start the HTTP VCSServer with the specified config file.")
73 help="Start the HTTP VCSServer with the specified config file.")
69 vcsgroup.addoption(
74 vcsgroup.addoption(
70 '--vcsserver-protocol', dest='vcsserver_protocol',
75 '--vcsserver-protocol', dest='vcsserver_protocol',
71 help="Start the VCSServer with HTTP protocol support.")
76 help="Start the VCSServer with HTTP protocol support.")
72 vcsgroup.addoption(
77 vcsgroup.addoption(
73 '--vcsserver-config-override', action='store', type=_parse_json,
78 '--vcsserver-config-override', action='store', type=_parse_json,
74 default=None, dest='vcsserver_config_override', help=(
79 default=None, dest='vcsserver_config_override', help=(
75 "Overrides the .ini file settings for the VCSServer. "
80 "Overrides the .ini file settings for the VCSServer. "
76 "Should be specified in JSON "
81 "Should be specified in JSON "
77 "format, e.g. '{\"section\": {\"parameter\": \"value\", ...}}'"
82 "format, e.g. '{\"section\": {\"parameter\": \"value\", ...}}'"
78 )
83 )
79 )
84 )
80 vcsgroup.addoption(
85 vcsgroup.addoption(
81 '--vcsserver-port', action='store', type=int,
86 '--vcsserver-port', action='store', type=int,
82 default=None, help=(
87 default=None, help=(
83 "Allows to set the port of the vcsserver. Useful when testing "
88 "Allows to set the port of the vcsserver. Useful when testing "
84 "against an already running server and random ports cause "
89 "against an already running server and random ports cause "
85 "trouble."))
90 "trouble."))
86 parser.addini(
91 parser.addini(
87 'vcsserver_config_http',
92 'vcsserver_config_http',
88 "Start the HTTP VCSServer with the specified config file.")
93 "Start the HTTP VCSServer with the specified config file.")
89 parser.addini(
94 parser.addini(
90 'vcsserver_protocol',
95 'vcsserver_protocol',
91 "Start the VCSServer with HTTP protocol support.")
96 "Start the VCSServer with HTTP protocol support.")
92
97
93
98
94 @pytest.fixture(scope='session')
99 @pytest.fixture(scope='session')
95 def vcsserver(request, vcsserver_port, vcsserver_factory):
100 def vcsserver(request, vcsserver_port, vcsserver_factory):
96 """
101 """
97 Session scope VCSServer.
102 Session scope VCSServer.
98
103
99 Tests wich need the VCSServer have to rely on this fixture in order
104 Tests wich need the VCSServer have to rely on this fixture in order
100 to ensure it will be running.
105 to ensure it will be running.
101
106
102 For specific needs, the fixture vcsserver_factory can be used. It allows to
107 For specific needs, the fixture vcsserver_factory can be used. It allows to
103 adjust the configuration file for the test run.
108 adjust the configuration file for the test run.
104
109
105 Command line args:
110 Command line args:
106
111
107 --without-vcsserver: Allows to switch this fixture off. You have to
112 --without-vcsserver: Allows to switch this fixture off. You have to
108 manually start the server.
113 manually start the server.
109
114
110 --vcsserver-port: Will expect the VCSServer to listen on this port.
115 --vcsserver-port: Will expect the VCSServer to listen on this port.
111 """
116 """
112
117
113 if not request.config.getoption('with_vcsserver'):
118 if not request.config.getoption('with_vcsserver'):
114 return None
119 return None
115
120
116 use_http = _use_vcs_http_server(request.config)
121 use_http = _use_vcs_http_server(request.config)
117 return vcsserver_factory(
122 return vcsserver_factory(
118 request, use_http=use_http, vcsserver_port=vcsserver_port)
123 request, use_http=use_http, vcsserver_port=vcsserver_port)
119
124
120
125
121 @pytest.fixture(scope='session')
126 @pytest.fixture(scope='session')
122 def vcsserver_factory(tmpdir_factory):
127 def vcsserver_factory(tmpdir_factory):
123 """
128 """
124 Use this if you need a running vcsserver with a special configuration.
129 Use this if you need a running vcsserver with a special configuration.
125 """
130 """
126
131
127 def factory(request, use_http=True, overrides=(), vcsserver_port=None):
132 def factory(request, use_http=True, overrides=(), vcsserver_port=None):
128
133
129 if vcsserver_port is None:
134 if vcsserver_port is None:
130 vcsserver_port = get_available_port()
135 vcsserver_port = get_available_port()
131
136
132 overrides = list(overrides)
137 overrides = list(overrides)
133 if use_http:
138 if use_http:
134 overrides.append({'server:main': {'port': vcsserver_port}})
139 overrides.append({'server:main': {'port': vcsserver_port}})
135 else:
140 else:
136 overrides.append({'DEFAULT': {'port': vcsserver_port}})
141 overrides.append({'DEFAULT': {'port': vcsserver_port}})
137
142
138 if is_cygwin():
143 if is_cygwin():
139 platform_override = {'DEFAULT': {
144 platform_override = {'DEFAULT': {
140 'beaker.cache.repo_object.type': 'nocache'}}
145 'beaker.cache.repo_object.type': 'nocache'}}
141 overrides.append(platform_override)
146 overrides.append(platform_override)
142
147
143 option_name = 'vcsserver_config_http' if use_http else ''
148 option_name = 'vcsserver_config_http' if use_http else ''
144 override_option_name = 'vcsserver_config_override'
149 override_option_name = 'vcsserver_config_override'
145 config_file = get_config(
150 config_file = get_config(
146 request.config, option_name=option_name,
151 request.config, option_name=option_name,
147 override_option_name=override_option_name, overrides=overrides,
152 override_option_name=override_option_name, overrides=overrides,
148 basetemp=tmpdir_factory.getbasetemp().strpath,
153 basetemp=tmpdir_factory.getbasetemp().strpath,
149 prefix='test_vcs_')
154 prefix='test_vcs_')
150
155
151 print("Using the VCSServer configuration:{}".format(config_file))
156 print("Using the VCSServer configuration:{}".format(config_file))
152 ServerClass = HttpVCSServer if use_http else None
157 ServerClass = HttpVCSServer if use_http else None
153 server = ServerClass(config_file)
158 server = ServerClass(config_file)
154 server.start()
159 server.start()
155
160
156 @request.addfinalizer
161 @request.addfinalizer
157 def cleanup():
162 def cleanup():
158 server.shutdown()
163 server.shutdown()
159
164
160 server.wait_until_ready()
165 server.wait_until_ready()
161 return server
166 return server
162
167
163 return factory
168 return factory
164
169
165
170
166 def is_cygwin():
171 def is_cygwin():
167 return 'cygwin' in platform.system().lower()
172 return 'cygwin' in platform.system().lower()
168
173
169
174
170 def _use_vcs_http_server(config):
175 def _use_vcs_http_server(config):
171 protocol_option = 'vcsserver_protocol'
176 protocol_option = 'vcsserver_protocol'
172 protocol = (
177 protocol = (
173 config.getoption(protocol_option) or
178 config.getoption(protocol_option) or
174 config.getini(protocol_option) or
179 config.getini(protocol_option) or
175 'http')
180 'http')
176 return protocol == 'http'
181 return protocol == 'http'
177
182
178
183
179 def _use_log_level(config):
184 def _use_log_level(config):
180 level = config.getoption('test_loglevel') or 'warn'
185 level = config.getoption('test_loglevel') or 'warn'
181 return level.upper()
186 return level.upper()
182
187
183
188
184 class VCSServer(object):
189 class VCSServer(object):
185 """
190 """
186 Represents a running VCSServer instance.
191 Represents a running VCSServer instance.
187 """
192 """
188
193
189 _args = []
194 _args = []
190
195
191 def start(self):
196 def start(self):
192 print("Starting the VCSServer: {}".format(self._args))
197 print("Starting the VCSServer: {}".format(self._args))
193 self.process = subprocess32.Popen(self._args)
198 self.process = subprocess32.Popen(self._args)
194
199
195 def wait_until_ready(self, timeout=30):
200 def wait_until_ready(self, timeout=30):
196 raise NotImplementedError()
201 raise NotImplementedError()
197
202
198 def shutdown(self):
203 def shutdown(self):
199 self.process.kill()
204 self.process.kill()
200
205
201
206
202 class HttpVCSServer(VCSServer):
207 class HttpVCSServer(VCSServer):
203 """
208 """
204 Represents a running VCSServer instance.
209 Represents a running VCSServer instance.
205 """
210 """
206 def __init__(self, config_file):
211 def __init__(self, config_file):
212 self.config_file = config_file
207 config_data = configobj.ConfigObj(config_file)
213 config_data = configobj.ConfigObj(config_file)
208 self._config = config_data['server:main']
214 self._config = config_data['server:main']
209
215
210 args = ['pserve', config_file]
216 args = ['gunicorn', '--workers', '1', '--paste', config_file]
211 self._args = args
217 self._args = args
212
218
213 @property
219 @property
214 def http_url(self):
220 def http_url(self):
215 template = 'http://{host}:{port}/'
221 template = 'http://{host}:{port}/'
216 return template.format(**self._config)
222 return template.format(**self._config)
217
223
218 def start(self):
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 def wait_until_ready(self, timeout=30):
241 def wait_until_ready(self, timeout=30):
222 host = self._config['host']
242 host = self._config['host']
223 port = self._config['port']
243 port = self._config['port']
224 status_url = 'http://{host}:{port}/status'.format(host=host, port=port)
244 status_url = 'http://{host}:{port}/status'.format(host=host, port=port)
225 start = time.time()
245 start = time.time()
226
246
227 while time.time() - start < timeout:
247 while time.time() - start < timeout:
228 try:
248 try:
229 urlopen(status_url)
249 urlopen(status_url)
230 break
250 break
231 except URLError:
251 except URLError:
232 time.sleep(0.2)
252 time.sleep(0.2)
233 else:
253 else:
234 pytest.exit(
254 pytest.exit(
235 "Starting the VCSServer failed or took more than {} "
255 "Starting the VCSServer failed or took more than {} "
236 "seconds. cmd: `{}`".format(timeout, ' '.join(self._args)))
256 "seconds. cmd: `{}`".format(timeout, ' '.join(self._args)))
237
257
238 def shutdown(self):
258 def shutdown(self):
239 self.process.kill()
259 self.process.kill()
240
260
241
261
242 @pytest.fixture(scope='session')
262 @pytest.fixture(scope='session')
243 def ini_config(request, tmpdir_factory, rcserver_port, vcsserver_port):
263 def ini_config(request, tmpdir_factory, rcserver_port, vcsserver_port):
244 option_name = 'pyramid_config'
264 option_name = 'pyramid_config'
245 log_level = _use_log_level(request.config)
265 log_level = _use_log_level(request.config)
246
266
247 overrides = [
267 overrides = [
248 {'server:main': {'port': rcserver_port}},
268 {'server:main': {'port': rcserver_port}},
249 {'app:main': {
269 {'app:main': {
250 'vcs.server': 'localhost:%s' % vcsserver_port,
270 'vcs.server': 'localhost:%s' % vcsserver_port,
251 # johbo: We will always start the VCSServer on our own based on the
271 # johbo: We will always start the VCSServer on our own based on the
252 # fixtures of the test cases. For the test run it must always be
272 # fixtures of the test cases. For the test run it must always be
253 # off in the INI file.
273 # off in the INI file.
254 'vcs.start_server': 'false',
274 'vcs.start_server': 'false',
255 }},
275 }},
256
276
257 {'handler_console': {
277 {'handler_console': {
258 'class ': 'StreamHandler',
278 'class ': 'StreamHandler',
259 'args ': '(sys.stderr,)',
279 'args ': '(sys.stderr,)',
260 'level': log_level,
280 'level': log_level,
261 }},
281 }},
262
282
263 ]
283 ]
264 if _use_vcs_http_server(request.config):
284 if _use_vcs_http_server(request.config):
265 overrides.append({
285 overrides.append({
266 'app:main': {
286 'app:main': {
267 'vcs.server.protocol': 'http',
287 'vcs.server.protocol': 'http',
268 'vcs.scm_app_implementation': 'http',
288 'vcs.scm_app_implementation': 'http',
269 'vcs.hooks.protocol': 'http',
289 'vcs.hooks.protocol': 'http',
270 }
290 }
271 })
291 })
272
292
273 filename = get_config(
293 filename = get_config(
274 request.config, option_name=option_name,
294 request.config, option_name=option_name,
275 override_option_name='{}_override'.format(option_name),
295 override_option_name='{}_override'.format(option_name),
276 overrides=overrides,
296 overrides=overrides,
277 basetemp=tmpdir_factory.getbasetemp().strpath,
297 basetemp=tmpdir_factory.getbasetemp().strpath,
278 prefix='test_rce_')
298 prefix='test_rce_')
279 return filename
299 return filename
280
300
281
301
282 @pytest.fixture(scope='session')
302 @pytest.fixture(scope='session')
283 def ini_settings(ini_config):
303 def ini_settings(ini_config):
284 ini_path = ini_config
304 ini_path = ini_config
285 return get_app_config(ini_path)
305 return get_app_config(ini_path)
286
306
287
307
288 @pytest.fixture(scope='session')
308 @pytest.fixture(scope='session')
289 def rcserver_port(request):
309 def rcserver_port(request):
290 port = get_available_port()
310 port = get_available_port()
291 print('Using rcserver port {}'.format(port))
311 print('Using rcserver port {}'.format(port))
292 return port
312 return port
293
313
294
314
295 @pytest.fixture(scope='session')
315 @pytest.fixture(scope='session')
296 def vcsserver_port(request):
316 def vcsserver_port(request):
297 port = request.config.getoption('--vcsserver-port')
317 port = request.config.getoption('--vcsserver-port')
298 if port is None:
318 if port is None:
299 port = get_available_port()
319 port = get_available_port()
300 print('Using vcsserver port {}'.format(port))
320 print('Using vcsserver port {}'.format(port))
301 return port
321 return port
302
322
303
323
304 def get_available_port():
324 def get_available_port():
305 family = socket.AF_INET
325 family = socket.AF_INET
306 socktype = socket.SOCK_STREAM
326 socktype = socket.SOCK_STREAM
307 host = '127.0.0.1'
327 host = '127.0.0.1'
308
328
309 mysocket = socket.socket(family, socktype)
329 mysocket = socket.socket(family, socktype)
310 mysocket.bind((host, 0))
330 mysocket.bind((host, 0))
311 port = mysocket.getsockname()[1]
331 port = mysocket.getsockname()[1]
312 mysocket.close()
332 mysocket.close()
313 del mysocket
333 del mysocket
314 return port
334 return port
315
335
316
336
317 @pytest.fixture(scope='session')
337 @pytest.fixture(scope='session')
318 def available_port_factory():
338 def available_port_factory():
319 """
339 """
320 Returns a callable which returns free port numbers.
340 Returns a callable which returns free port numbers.
321 """
341 """
322 return get_available_port
342 return get_available_port
323
343
324
344
325 @pytest.fixture
345 @pytest.fixture
326 def available_port(available_port_factory):
346 def available_port(available_port_factory):
327 """
347 """
328 Gives you one free port for the current test.
348 Gives you one free port for the current test.
329
349
330 Uses "available_port_factory" to retrieve the port.
350 Uses "available_port_factory" to retrieve the port.
331 """
351 """
332 return available_port_factory()
352 return available_port_factory()
333
353
334
354
335 @pytest.fixture(scope='session')
355 @pytest.fixture(scope='session')
336 def testini_factory(tmpdir_factory, ini_config):
356 def testini_factory(tmpdir_factory, ini_config):
337 """
357 """
338 Factory to create an INI file based on TestINI.
358 Factory to create an INI file based on TestINI.
339
359
340 It will make sure to place the INI file in the correct directory.
360 It will make sure to place the INI file in the correct directory.
341 """
361 """
342 basetemp = tmpdir_factory.getbasetemp().strpath
362 basetemp = tmpdir_factory.getbasetemp().strpath
343 return TestIniFactory(basetemp, ini_config)
363 return TestIniFactory(basetemp, ini_config)
344
364
345
365
346 class TestIniFactory(object):
366 class TestIniFactory(object):
347
367
348 def __init__(self, basetemp, template_ini):
368 def __init__(self, basetemp, template_ini):
349 self._basetemp = basetemp
369 self._basetemp = basetemp
350 self._template_ini = template_ini
370 self._template_ini = template_ini
351
371
352 def __call__(self, ini_params, new_file_prefix='test'):
372 def __call__(self, ini_params, new_file_prefix='test'):
353 ini_file = TestINI(
373 ini_file = TestINI(
354 self._template_ini, ini_params=ini_params,
374 self._template_ini, ini_params=ini_params,
355 new_file_prefix=new_file_prefix, dir=self._basetemp)
375 new_file_prefix=new_file_prefix, dir=self._basetemp)
356 result = ini_file.create()
376 result = ini_file.create()
357 return result
377 return result
358
378
359
379
360 def get_config(
380 def get_config(
361 config, option_name, override_option_name, overrides=None,
381 config, option_name, override_option_name, overrides=None,
362 basetemp=None, prefix='test'):
382 basetemp=None, prefix='test'):
363 """
383 """
364 Find a configuration file and apply overrides for the given `prefix`.
384 Find a configuration file and apply overrides for the given `prefix`.
365 """
385 """
366 config_file = (
386 config_file = (
367 config.getoption(option_name) or config.getini(option_name))
387 config.getoption(option_name) or config.getini(option_name))
368 if not config_file:
388 if not config_file:
369 pytest.exit(
389 pytest.exit(
370 "Configuration error, could not extract {}.".format(option_name))
390 "Configuration error, could not extract {}.".format(option_name))
371
391
372 overrides = overrides or []
392 overrides = overrides or []
373 config_override = config.getoption(override_option_name)
393 config_override = config.getoption(override_option_name)
374 if config_override:
394 if config_override:
375 overrides.append(config_override)
395 overrides.append(config_override)
376 temp_ini_file = TestINI(
396 temp_ini_file = TestINI(
377 config_file, ini_params=overrides, new_file_prefix=prefix,
397 config_file, ini_params=overrides, new_file_prefix=prefix,
378 dir=basetemp)
398 dir=basetemp)
379
399
380 return temp_ini_file.create()
400 return temp_ini_file.create()
@@ -1,768 +1,693 b''
1
1
2
2
3 ################################################################################
3 ################################################################################
4 ## RHODECODE COMMUNITY EDITION CONFIGURATION ##
4 ## RHODECODE COMMUNITY EDITION CONFIGURATION ##
5 # The %(here)s variable will be replaced with the parent directory of this file#
5 # The %(here)s variable will be replaced with the parent directory of this file#
6 ################################################################################
6 ################################################################################
7
7
8 [DEFAULT]
8 [DEFAULT]
9 debug = true
9 debug = true
10
10
11 ################################################################################
11 ################################################################################
12 ## EMAIL CONFIGURATION ##
12 ## EMAIL CONFIGURATION ##
13 ## Uncomment and replace with the email address which should receive ##
13 ## Uncomment and replace with the email address which should receive ##
14 ## any error reports after an application crash ##
14 ## any error reports after an application crash ##
15 ## Additionally these settings will be used by the RhodeCode mailing system ##
15 ## Additionally these settings will be used by the RhodeCode mailing system ##
16 ################################################################################
16 ################################################################################
17
17
18 ## prefix all emails subjects with given prefix, helps filtering out emails
18 ## prefix all emails subjects with given prefix, helps filtering out emails
19 #email_prefix = [RhodeCode]
19 #email_prefix = [RhodeCode]
20
20
21 ## email FROM address all mails will be sent
21 ## email FROM address all mails will be sent
22 #app_email_from = rhodecode-noreply@localhost
22 #app_email_from = rhodecode-noreply@localhost
23
23
24 ## Uncomment and replace with the address which should receive any error report
24 ## Uncomment and replace with the address which should receive any error report
25 ## note: using appenlight for error handling doesn't need this to be uncommented
25 ## note: using appenlight for error handling doesn't need this to be uncommented
26 #email_to = admin@localhost
26 #email_to = admin@localhost
27
27
28 ## in case of Application errors, sent an error email form
28 ## in case of Application errors, sent an error email form
29 #error_email_from = rhodecode_error@localhost
29 #error_email_from = rhodecode_error@localhost
30
30
31 ## additional error message to be send in case of server crash
31 ## additional error message to be send in case of server crash
32 #error_message =
32 #error_message =
33
33
34
34
35 #smtp_server = mail.server.com
35 #smtp_server = mail.server.com
36 #smtp_username =
36 #smtp_username =
37 #smtp_password =
37 #smtp_password =
38 #smtp_port =
38 #smtp_port =
39 #smtp_use_tls = false
39 #smtp_use_tls = false
40 #smtp_use_ssl = true
40 #smtp_use_ssl = true
41 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
41 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
42 #smtp_auth =
42 #smtp_auth =
43
43
44 [server:main]
44 [server:main]
45 ## COMMON ##
45 ## COMMON ##
46 host = 0.0.0.0
46 host = 0.0.0.0
47 port = 5000
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 ## GUNICORN WSGI SERVER ##
50 ## GUNICORN WSGI SERVER ##
66 ##########################
51 ##########################
67 ## run with gunicorn --log-config rhodecode.ini --paste rhodecode.ini
52 ## run with gunicorn --log-config rhodecode.ini --paste rhodecode.ini
68
53
69 #use = egg:gunicorn#main
54 use = egg:gunicorn#main
70 ## Sets the number of process workers. You must set `instance_id = *`
55 ## Sets the number of process workers. You must set `instance_id = *`
71 ## when this option is set to more than one worker, recommended
56 ## when this option is set to more than one worker, recommended
72 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
57 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
73 ## The `instance_id = *` must be set in the [app:main] section below
58 ## The `instance_id = *` must be set in the [app:main] section below
74 #workers = 2
59 #workers = 2
75 ## number of threads for each of the worker, must be set to 1 for gevent
60 ## number of threads for each of the worker, must be set to 1 for gevent
76 ## generally recommened to be at 1
61 ## generally recommened to be at 1
77 #threads = 1
62 #threads = 1
78 ## process name
63 ## process name
79 #proc_name = rhodecode
64 #proc_name = rhodecode
80 ## type of worker class, one of sync, gevent
65 ## type of worker class, one of sync, gevent
81 ## recommended for bigger setup is using of of other than sync one
66 ## recommended for bigger setup is using of of other than sync one
82 #worker_class = sync
67 #worker_class = sync
83 ## The maximum number of simultaneous clients. Valid only for Gevent
68 ## The maximum number of simultaneous clients. Valid only for Gevent
84 #worker_connections = 10
69 #worker_connections = 10
85 ## max number of requests that worker will handle before being gracefully
70 ## max number of requests that worker will handle before being gracefully
86 ## restarted, could prevent memory leaks
71 ## restarted, could prevent memory leaks
87 #max_requests = 1000
72 #max_requests = 1000
88 #max_requests_jitter = 30
73 #max_requests_jitter = 30
89 ## amount of time a worker can spend with handling a request before it
74 ## amount of time a worker can spend with handling a request before it
90 ## gets killed and restarted. Set to 6hrs
75 ## gets killed and restarted. Set to 6hrs
91 #timeout = 21600
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 ## prefix middleware for RhodeCode.
78 ## prefix middleware for RhodeCode.
154 ## recommended when using proxy setup.
79 ## recommended when using proxy setup.
155 ## allows to set RhodeCode under a prefix in server.
80 ## allows to set RhodeCode under a prefix in server.
156 ## eg https://server.com/custom_prefix. Enable `filter-with =` option below as well.
81 ## eg https://server.com/custom_prefix. Enable `filter-with =` option below as well.
157 ## And set your prefix like: `prefix = /custom_prefix`
82 ## And set your prefix like: `prefix = /custom_prefix`
158 ## be sure to also set beaker.session.cookie_path = /custom_prefix if you need
83 ## be sure to also set beaker.session.cookie_path = /custom_prefix if you need
159 ## to make your cookies only work on prefix url
84 ## to make your cookies only work on prefix url
160 [filter:proxy-prefix]
85 [filter:proxy-prefix]
161 use = egg:PasteDeploy#prefix
86 use = egg:PasteDeploy#prefix
162 prefix = /
87 prefix = /
163
88
164 [app:main]
89 [app:main]
165 is_test = True
90 is_test = True
166 use = egg:rhodecode-enterprise-ce
91 use = egg:rhodecode-enterprise-ce
167
92
168 ## enable proxy prefix middleware, defined above
93 ## enable proxy prefix middleware, defined above
169 #filter-with = proxy-prefix
94 #filter-with = proxy-prefix
170
95
171
96
172 ## RHODECODE PLUGINS ##
97 ## RHODECODE PLUGINS ##
173 rhodecode.includes = rhodecode.api
98 rhodecode.includes = rhodecode.api
174
99
175 # api prefix url
100 # api prefix url
176 rhodecode.api.url = /_admin/api
101 rhodecode.api.url = /_admin/api
177
102
178
103
179 ## END RHODECODE PLUGINS ##
104 ## END RHODECODE PLUGINS ##
180
105
181 ## encryption key used to encrypt social plugin tokens,
106 ## encryption key used to encrypt social plugin tokens,
182 ## remote_urls with credentials etc, if not set it defaults to
107 ## remote_urls with credentials etc, if not set it defaults to
183 ## `beaker.session.secret`
108 ## `beaker.session.secret`
184 #rhodecode.encrypted_values.secret =
109 #rhodecode.encrypted_values.secret =
185
110
186 ## decryption strict mode (enabled by default). It controls if decryption raises
111 ## decryption strict mode (enabled by default). It controls if decryption raises
187 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
112 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
188 #rhodecode.encrypted_values.strict = false
113 #rhodecode.encrypted_values.strict = false
189
114
190 ## return gzipped responses from Rhodecode (static files/application)
115 ## return gzipped responses from Rhodecode (static files/application)
191 gzip_responses = false
116 gzip_responses = false
192
117
193 ## autogenerate javascript routes file on startup
118 ## autogenerate javascript routes file on startup
194 generate_js_files = false
119 generate_js_files = false
195
120
196 ## Optional Languages
121 ## Optional Languages
197 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
122 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
198 lang = en
123 lang = en
199
124
200 ## perform a full repository scan on each server start, this should be
125 ## perform a full repository scan on each server start, this should be
201 ## set to false after first startup, to allow faster server restarts.
126 ## set to false after first startup, to allow faster server restarts.
202 startup.import_repos = true
127 startup.import_repos = true
203
128
204 ## Uncomment and set this path to use archive download cache.
129 ## Uncomment and set this path to use archive download cache.
205 ## Once enabled, generated archives will be cached at this location
130 ## Once enabled, generated archives will be cached at this location
206 ## and served from the cache during subsequent requests for the same archive of
131 ## and served from the cache during subsequent requests for the same archive of
207 ## the repository.
132 ## the repository.
208 #archive_cache_dir = /tmp/tarballcache
133 #archive_cache_dir = /tmp/tarballcache
209
134
210 ## URL at which the application is running. This is used for bootstraping
135 ## URL at which the application is running. This is used for bootstraping
211 ## requests in context when no web request is available. Used in ishell, or
136 ## requests in context when no web request is available. Used in ishell, or
212 ## SSH calls. Set this for events to receive proper url for SSH calls.
137 ## SSH calls. Set this for events to receive proper url for SSH calls.
213 app.base_url = http://rhodecode.local
138 app.base_url = http://rhodecode.local
214
139
215 ## change this to unique ID for security
140 ## change this to unique ID for security
216 app_instance_uuid = rc-production
141 app_instance_uuid = rc-production
217
142
218 ## cut off limit for large diffs (size in bytes)
143 ## cut off limit for large diffs (size in bytes)
219 cut_off_limit_diff = 1024000
144 cut_off_limit_diff = 1024000
220 cut_off_limit_file = 256000
145 cut_off_limit_file = 256000
221
146
222 ## use cache version of scm repo everywhere
147 ## use cache version of scm repo everywhere
223 vcs_full_cache = false
148 vcs_full_cache = false
224
149
225 ## force https in RhodeCode, fixes https redirects, assumes it's always https
150 ## force https in RhodeCode, fixes https redirects, assumes it's always https
226 ## Normally this is controlled by proper http flags sent from http server
151 ## Normally this is controlled by proper http flags sent from http server
227 force_https = false
152 force_https = false
228
153
229 ## use Strict-Transport-Security headers
154 ## use Strict-Transport-Security headers
230 use_htsts = false
155 use_htsts = false
231
156
232 ## number of commits stats will parse on each iteration
157 ## number of commits stats will parse on each iteration
233 commit_parse_limit = 25
158 commit_parse_limit = 25
234
159
235 ## git rev filter option, --all is the default filter, if you need to
160 ## git rev filter option, --all is the default filter, if you need to
236 ## hide all refs in changelog switch this to --branches --tags
161 ## hide all refs in changelog switch this to --branches --tags
237 git_rev_filter = --all
162 git_rev_filter = --all
238
163
239 # Set to true if your repos are exposed using the dumb protocol
164 # Set to true if your repos are exposed using the dumb protocol
240 git_update_server_info = false
165 git_update_server_info = false
241
166
242 ## RSS/ATOM feed options
167 ## RSS/ATOM feed options
243 rss_cut_off_limit = 256000
168 rss_cut_off_limit = 256000
244 rss_items_per_page = 10
169 rss_items_per_page = 10
245 rss_include_diff = false
170 rss_include_diff = false
246
171
247 ## gist URL alias, used to create nicer urls for gist. This should be an
172 ## gist URL alias, used to create nicer urls for gist. This should be an
248 ## url that does rewrites to _admin/gists/{gistid}.
173 ## url that does rewrites to _admin/gists/{gistid}.
249 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
174 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
250 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/{gistid}
175 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/{gistid}
251 gist_alias_url =
176 gist_alias_url =
252
177
253 ## List of views (using glob pattern syntax) that AUTH TOKENS could be
178 ## List of views (using glob pattern syntax) that AUTH TOKENS could be
254 ## used for access.
179 ## used for access.
255 ## Adding ?auth_token=TOKEN_HASH to the url authenticates this request as if it
180 ## Adding ?auth_token=TOKEN_HASH to the url authenticates this request as if it
256 ## came from the the logged in user who own this authentication token.
181 ## came from the the logged in user who own this authentication token.
257 ## Additionally @TOKEN syntaxt can be used to bound the view to specific
182 ## Additionally @TOKEN syntaxt can be used to bound the view to specific
258 ## authentication token. Such view would be only accessible when used together
183 ## authentication token. Such view would be only accessible when used together
259 ## with this authentication token
184 ## with this authentication token
260 ##
185 ##
261 ## list of all views can be found under `/_admin/permissions/auth_token_access`
186 ## list of all views can be found under `/_admin/permissions/auth_token_access`
262 ## The list should be "," separated and on a single line.
187 ## The list should be "," separated and on a single line.
263 ##
188 ##
264 ## Most common views to enable:
189 ## Most common views to enable:
265 # RepoCommitsView:repo_commit_download
190 # RepoCommitsView:repo_commit_download
266 # RepoCommitsView:repo_commit_patch
191 # RepoCommitsView:repo_commit_patch
267 # RepoCommitsView:repo_commit_raw
192 # RepoCommitsView:repo_commit_raw
268 # RepoCommitsView:repo_commit_raw@TOKEN
193 # RepoCommitsView:repo_commit_raw@TOKEN
269 # RepoFilesView:repo_files_diff
194 # RepoFilesView:repo_files_diff
270 # RepoFilesView:repo_archivefile
195 # RepoFilesView:repo_archivefile
271 # RepoFilesView:repo_file_raw
196 # RepoFilesView:repo_file_raw
272 # GistView:*
197 # GistView:*
273 api_access_controllers_whitelist =
198 api_access_controllers_whitelist =
274
199
275 ## default encoding used to convert from and to unicode
200 ## default encoding used to convert from and to unicode
276 ## can be also a comma separated list of encoding in case of mixed encodings
201 ## can be also a comma separated list of encoding in case of mixed encodings
277 default_encoding = UTF-8
202 default_encoding = UTF-8
278
203
279 ## instance-id prefix
204 ## instance-id prefix
280 ## a prefix key for this instance used for cache invalidation when running
205 ## a prefix key for this instance used for cache invalidation when running
281 ## multiple instances of rhodecode, make sure it's globally unique for
206 ## multiple instances of rhodecode, make sure it's globally unique for
282 ## all running rhodecode instances. Leave empty if you don't use it
207 ## all running rhodecode instances. Leave empty if you don't use it
283 instance_id =
208 instance_id =
284
209
285 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
210 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
286 ## of an authentication plugin also if it is disabled by it's settings.
211 ## of an authentication plugin also if it is disabled by it's settings.
287 ## This could be useful if you are unable to log in to the system due to broken
212 ## This could be useful if you are unable to log in to the system due to broken
288 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
213 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
289 ## module to log in again and fix the settings.
214 ## module to log in again and fix the settings.
290 ##
215 ##
291 ## Available builtin plugin IDs (hash is part of the ID):
216 ## Available builtin plugin IDs (hash is part of the ID):
292 ## egg:rhodecode-enterprise-ce#rhodecode
217 ## egg:rhodecode-enterprise-ce#rhodecode
293 ## egg:rhodecode-enterprise-ce#pam
218 ## egg:rhodecode-enterprise-ce#pam
294 ## egg:rhodecode-enterprise-ce#ldap
219 ## egg:rhodecode-enterprise-ce#ldap
295 ## egg:rhodecode-enterprise-ce#jasig_cas
220 ## egg:rhodecode-enterprise-ce#jasig_cas
296 ## egg:rhodecode-enterprise-ce#headers
221 ## egg:rhodecode-enterprise-ce#headers
297 ## egg:rhodecode-enterprise-ce#crowd
222 ## egg:rhodecode-enterprise-ce#crowd
298 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
223 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
299
224
300 ## alternative return HTTP header for failed authentication. Default HTTP
225 ## alternative return HTTP header for failed authentication. Default HTTP
301 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
226 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
302 ## handling that causing a series of failed authentication calls.
227 ## handling that causing a series of failed authentication calls.
303 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
228 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
304 ## This will be served instead of default 401 on bad authnetication
229 ## This will be served instead of default 401 on bad authnetication
305 auth_ret_code =
230 auth_ret_code =
306
231
307 ## use special detection method when serving auth_ret_code, instead of serving
232 ## use special detection method when serving auth_ret_code, instead of serving
308 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
233 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
309 ## and then serve auth_ret_code to clients
234 ## and then serve auth_ret_code to clients
310 auth_ret_code_detection = false
235 auth_ret_code_detection = false
311
236
312 ## locking return code. When repository is locked return this HTTP code. 2XX
237 ## locking return code. When repository is locked return this HTTP code. 2XX
313 ## codes don't break the transactions while 4XX codes do
238 ## codes don't break the transactions while 4XX codes do
314 lock_ret_code = 423
239 lock_ret_code = 423
315
240
316 ## allows to change the repository location in settings page
241 ## allows to change the repository location in settings page
317 allow_repo_location_change = true
242 allow_repo_location_change = true
318
243
319 ## allows to setup custom hooks in settings page
244 ## allows to setup custom hooks in settings page
320 allow_custom_hooks_settings = true
245 allow_custom_hooks_settings = true
321
246
322 ## generated license token, goto license page in RhodeCode settings to obtain
247 ## generated license token, goto license page in RhodeCode settings to obtain
323 ## new token
248 ## new token
324 license_token = abra-cada-bra1-rce3
249 license_token = abra-cada-bra1-rce3
325
250
326 ## supervisor connection uri, for managing supervisor and logs.
251 ## supervisor connection uri, for managing supervisor and logs.
327 supervisor.uri =
252 supervisor.uri =
328 ## supervisord group name/id we only want this RC instance to handle
253 ## supervisord group name/id we only want this RC instance to handle
329 supervisor.group_id = dev
254 supervisor.group_id = dev
330
255
331 ## Display extended labs settings
256 ## Display extended labs settings
332 labs_settings_active = true
257 labs_settings_active = true
333
258
334 ####################################
259 ####################################
335 ### CELERY CONFIG ####
260 ### CELERY CONFIG ####
336 ####################################
261 ####################################
337 use_celery = false
262 use_celery = false
338 broker.host = localhost
263 broker.host = localhost
339 broker.vhost = rabbitmqhost
264 broker.vhost = rabbitmqhost
340 broker.port = 5672
265 broker.port = 5672
341 broker.user = rabbitmq
266 broker.user = rabbitmq
342 broker.password = qweqwe
267 broker.password = qweqwe
343
268
344 celery.imports = rhodecode.lib.celerylib.tasks
269 celery.imports = rhodecode.lib.celerylib.tasks
345
270
346 celery.result.backend = amqp
271 celery.result.backend = amqp
347 celery.result.dburi = amqp://
272 celery.result.dburi = amqp://
348 celery.result.serialier = json
273 celery.result.serialier = json
349
274
350 #celery.send.task.error.emails = true
275 #celery.send.task.error.emails = true
351 #celery.amqp.task.result.expires = 18000
276 #celery.amqp.task.result.expires = 18000
352
277
353 celeryd.concurrency = 2
278 celeryd.concurrency = 2
354 #celeryd.log.file = celeryd.log
279 #celeryd.log.file = celeryd.log
355 celeryd.log.level = debug
280 celeryd.log.level = debug
356 celeryd.max.tasks.per.child = 1
281 celeryd.max.tasks.per.child = 1
357
282
358 ## tasks will never be sent to the queue, but executed locally instead.
283 ## tasks will never be sent to the queue, but executed locally instead.
359 celery.always.eager = false
284 celery.always.eager = false
360
285
361 ####################################
286 ####################################
362 ### BEAKER CACHE ####
287 ### BEAKER CACHE ####
363 ####################################
288 ####################################
364 # default cache dir for templates. Putting this into a ramdisk
289 # default cache dir for templates. Putting this into a ramdisk
365 ## can boost performance, eg. %(here)s/data_ramdisk
290 ## can boost performance, eg. %(here)s/data_ramdisk
366 cache_dir = %(here)s/data
291 cache_dir = %(here)s/data
367
292
368 ## locking and default file storage for Beaker. Putting this into a ramdisk
293 ## locking and default file storage for Beaker. Putting this into a ramdisk
369 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
294 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
370 beaker.cache.data_dir = %(here)s/rc/data/cache/beaker_data
295 beaker.cache.data_dir = %(here)s/rc/data/cache/beaker_data
371 beaker.cache.lock_dir = %(here)s/rc/data/cache/beaker_lock
296 beaker.cache.lock_dir = %(here)s/rc/data/cache/beaker_lock
372
297
373 beaker.cache.regions = super_short_term, short_term, long_term, sql_cache_short, auth_plugins, repo_cache_long
298 beaker.cache.regions = super_short_term, short_term, long_term, sql_cache_short, auth_plugins, repo_cache_long
374
299
375 beaker.cache.super_short_term.type = memory
300 beaker.cache.super_short_term.type = memory
376 beaker.cache.super_short_term.expire = 1
301 beaker.cache.super_short_term.expire = 1
377 beaker.cache.super_short_term.key_length = 256
302 beaker.cache.super_short_term.key_length = 256
378
303
379 beaker.cache.short_term.type = memory
304 beaker.cache.short_term.type = memory
380 beaker.cache.short_term.expire = 60
305 beaker.cache.short_term.expire = 60
381 beaker.cache.short_term.key_length = 256
306 beaker.cache.short_term.key_length = 256
382
307
383 beaker.cache.long_term.type = memory
308 beaker.cache.long_term.type = memory
384 beaker.cache.long_term.expire = 36000
309 beaker.cache.long_term.expire = 36000
385 beaker.cache.long_term.key_length = 256
310 beaker.cache.long_term.key_length = 256
386
311
387 beaker.cache.sql_cache_short.type = memory
312 beaker.cache.sql_cache_short.type = memory
388 beaker.cache.sql_cache_short.expire = 1
313 beaker.cache.sql_cache_short.expire = 1
389 beaker.cache.sql_cache_short.key_length = 256
314 beaker.cache.sql_cache_short.key_length = 256
390
315
391 ## default is memory cache, configure only if required
316 ## default is memory cache, configure only if required
392 ## using multi-node or multi-worker setup
317 ## using multi-node or multi-worker setup
393 #beaker.cache.auth_plugins.type = memory
318 #beaker.cache.auth_plugins.type = memory
394 #beaker.cache.auth_plugins.lock_dir = %(here)s/data/cache/auth_plugin_lock
319 #beaker.cache.auth_plugins.lock_dir = %(here)s/data/cache/auth_plugin_lock
395 #beaker.cache.auth_plugins.url = postgresql://postgres:secret@localhost/rhodecode
320 #beaker.cache.auth_plugins.url = postgresql://postgres:secret@localhost/rhodecode
396 #beaker.cache.auth_plugins.url = mysql://root:secret@127.0.0.1/rhodecode
321 #beaker.cache.auth_plugins.url = mysql://root:secret@127.0.0.1/rhodecode
397 #beaker.cache.auth_plugins.sa.pool_recycle = 3600
322 #beaker.cache.auth_plugins.sa.pool_recycle = 3600
398 #beaker.cache.auth_plugins.sa.pool_size = 10
323 #beaker.cache.auth_plugins.sa.pool_size = 10
399 #beaker.cache.auth_plugins.sa.max_overflow = 0
324 #beaker.cache.auth_plugins.sa.max_overflow = 0
400
325
401 beaker.cache.repo_cache_long.type = memorylru_base
326 beaker.cache.repo_cache_long.type = memorylru_base
402 beaker.cache.repo_cache_long.max_items = 4096
327 beaker.cache.repo_cache_long.max_items = 4096
403 beaker.cache.repo_cache_long.expire = 2592000
328 beaker.cache.repo_cache_long.expire = 2592000
404
329
405 ## default is memorylru_base cache, configure only if required
330 ## default is memorylru_base cache, configure only if required
406 ## using multi-node or multi-worker setup
331 ## using multi-node or multi-worker setup
407 #beaker.cache.repo_cache_long.type = ext:memcached
332 #beaker.cache.repo_cache_long.type = ext:memcached
408 #beaker.cache.repo_cache_long.url = localhost:11211
333 #beaker.cache.repo_cache_long.url = localhost:11211
409 #beaker.cache.repo_cache_long.expire = 1209600
334 #beaker.cache.repo_cache_long.expire = 1209600
410 #beaker.cache.repo_cache_long.key_length = 256
335 #beaker.cache.repo_cache_long.key_length = 256
411
336
412 ####################################
337 ####################################
413 ### BEAKER SESSION ####
338 ### BEAKER SESSION ####
414 ####################################
339 ####################################
415
340
416 ## .session.type is type of storage options for the session, current allowed
341 ## .session.type is type of storage options for the session, current allowed
417 ## types are file, ext:memcached, ext:database, and memory (default).
342 ## types are file, ext:memcached, ext:database, and memory (default).
418 beaker.session.type = file
343 beaker.session.type = file
419 beaker.session.data_dir = %(here)s/rc/data/sessions/data
344 beaker.session.data_dir = %(here)s/rc/data/sessions/data
420
345
421 ## db based session, fast, and allows easy management over logged in users
346 ## db based session, fast, and allows easy management over logged in users
422 #beaker.session.type = ext:database
347 #beaker.session.type = ext:database
423 #beaker.session.table_name = db_session
348 #beaker.session.table_name = db_session
424 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
349 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
425 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
350 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
426 #beaker.session.sa.pool_recycle = 3600
351 #beaker.session.sa.pool_recycle = 3600
427 #beaker.session.sa.echo = false
352 #beaker.session.sa.echo = false
428
353
429 beaker.session.key = rhodecode
354 beaker.session.key = rhodecode
430 beaker.session.secret = test-rc-uytcxaz
355 beaker.session.secret = test-rc-uytcxaz
431 beaker.session.lock_dir = %(here)s/rc/data/sessions/lock
356 beaker.session.lock_dir = %(here)s/rc/data/sessions/lock
432
357
433 ## Secure encrypted cookie. Requires AES and AES python libraries
358 ## Secure encrypted cookie. Requires AES and AES python libraries
434 ## you must disable beaker.session.secret to use this
359 ## you must disable beaker.session.secret to use this
435 #beaker.session.encrypt_key = key_for_encryption
360 #beaker.session.encrypt_key = key_for_encryption
436 #beaker.session.validate_key = validation_key
361 #beaker.session.validate_key = validation_key
437
362
438 ## sets session as invalid(also logging out user) if it haven not been
363 ## sets session as invalid(also logging out user) if it haven not been
439 ## accessed for given amount of time in seconds
364 ## accessed for given amount of time in seconds
440 beaker.session.timeout = 2592000
365 beaker.session.timeout = 2592000
441 beaker.session.httponly = true
366 beaker.session.httponly = true
442 ## Path to use for the cookie. Set to prefix if you use prefix middleware
367 ## Path to use for the cookie. Set to prefix if you use prefix middleware
443 #beaker.session.cookie_path = /custom_prefix
368 #beaker.session.cookie_path = /custom_prefix
444
369
445 ## uncomment for https secure cookie
370 ## uncomment for https secure cookie
446 beaker.session.secure = false
371 beaker.session.secure = false
447
372
448 ## auto save the session to not to use .save()
373 ## auto save the session to not to use .save()
449 beaker.session.auto = false
374 beaker.session.auto = false
450
375
451 ## default cookie expiration time in seconds, set to `true` to set expire
376 ## default cookie expiration time in seconds, set to `true` to set expire
452 ## at browser close
377 ## at browser close
453 #beaker.session.cookie_expires = 3600
378 #beaker.session.cookie_expires = 3600
454
379
455 ###################################
380 ###################################
456 ## SEARCH INDEXING CONFIGURATION ##
381 ## SEARCH INDEXING CONFIGURATION ##
457 ###################################
382 ###################################
458 ## Full text search indexer is available in rhodecode-tools under
383 ## Full text search indexer is available in rhodecode-tools under
459 ## `rhodecode-tools index` command
384 ## `rhodecode-tools index` command
460
385
461 ## WHOOSH Backend, doesn't require additional services to run
386 ## WHOOSH Backend, doesn't require additional services to run
462 ## it works good with few dozen repos
387 ## it works good with few dozen repos
463 search.module = rhodecode.lib.index.whoosh
388 search.module = rhodecode.lib.index.whoosh
464 search.location = %(here)s/data/index
389 search.location = %(here)s/data/index
465
390
466 ########################################
391 ########################################
467 ### CHANNELSTREAM CONFIG ####
392 ### CHANNELSTREAM CONFIG ####
468 ########################################
393 ########################################
469 ## channelstream enables persistent connections and live notification
394 ## channelstream enables persistent connections and live notification
470 ## in the system. It's also used by the chat system
395 ## in the system. It's also used by the chat system
471
396
472 channelstream.enabled = false
397 channelstream.enabled = false
473
398
474 ## server address for channelstream server on the backend
399 ## server address for channelstream server on the backend
475 channelstream.server = 127.0.0.1:9800
400 channelstream.server = 127.0.0.1:9800
476 ## location of the channelstream server from outside world
401 ## location of the channelstream server from outside world
477 ## use ws:// for http or wss:// for https. This address needs to be handled
402 ## use ws:// for http or wss:// for https. This address needs to be handled
478 ## by external HTTP server such as Nginx or Apache
403 ## by external HTTP server such as Nginx or Apache
479 ## see nginx/apache configuration examples in our docs
404 ## see nginx/apache configuration examples in our docs
480 channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
405 channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
481 channelstream.secret = secret
406 channelstream.secret = secret
482 channelstream.history.location = %(here)s/channelstream_history
407 channelstream.history.location = %(here)s/channelstream_history
483
408
484 ## Internal application path that Javascript uses to connect into.
409 ## Internal application path that Javascript uses to connect into.
485 ## If you use proxy-prefix the prefix should be added before /_channelstream
410 ## If you use proxy-prefix the prefix should be added before /_channelstream
486 channelstream.proxy_path = /_channelstream
411 channelstream.proxy_path = /_channelstream
487
412
488
413
489 ###################################
414 ###################################
490 ## APPENLIGHT CONFIG ##
415 ## APPENLIGHT CONFIG ##
491 ###################################
416 ###################################
492
417
493 ## Appenlight is tailored to work with RhodeCode, see
418 ## Appenlight is tailored to work with RhodeCode, see
494 ## http://appenlight.com for details how to obtain an account
419 ## http://appenlight.com for details how to obtain an account
495
420
496 ## appenlight integration enabled
421 ## appenlight integration enabled
497 appenlight = false
422 appenlight = false
498
423
499 appenlight.server_url = https://api.appenlight.com
424 appenlight.server_url = https://api.appenlight.com
500 appenlight.api_key = YOUR_API_KEY
425 appenlight.api_key = YOUR_API_KEY
501 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
426 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
502
427
503 # used for JS client
428 # used for JS client
504 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
429 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
505
430
506 ## TWEAK AMOUNT OF INFO SENT HERE
431 ## TWEAK AMOUNT OF INFO SENT HERE
507
432
508 ## enables 404 error logging (default False)
433 ## enables 404 error logging (default False)
509 appenlight.report_404 = false
434 appenlight.report_404 = false
510
435
511 ## time in seconds after request is considered being slow (default 1)
436 ## time in seconds after request is considered being slow (default 1)
512 appenlight.slow_request_time = 1
437 appenlight.slow_request_time = 1
513
438
514 ## record slow requests in application
439 ## record slow requests in application
515 ## (needs to be enabled for slow datastore recording and time tracking)
440 ## (needs to be enabled for slow datastore recording and time tracking)
516 appenlight.slow_requests = true
441 appenlight.slow_requests = true
517
442
518 ## enable hooking to application loggers
443 ## enable hooking to application loggers
519 appenlight.logging = true
444 appenlight.logging = true
520
445
521 ## minimum log level for log capture
446 ## minimum log level for log capture
522 appenlight.logging.level = WARNING
447 appenlight.logging.level = WARNING
523
448
524 ## send logs only from erroneous/slow requests
449 ## send logs only from erroneous/slow requests
525 ## (saves API quota for intensive logging)
450 ## (saves API quota for intensive logging)
526 appenlight.logging_on_error = false
451 appenlight.logging_on_error = false
527
452
528 ## list of additonal keywords that should be grabbed from environ object
453 ## list of additonal keywords that should be grabbed from environ object
529 ## can be string with comma separated list of words in lowercase
454 ## can be string with comma separated list of words in lowercase
530 ## (by default client will always send following info:
455 ## (by default client will always send following info:
531 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
456 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
532 ## start with HTTP* this list be extended with additional keywords here
457 ## start with HTTP* this list be extended with additional keywords here
533 appenlight.environ_keys_whitelist =
458 appenlight.environ_keys_whitelist =
534
459
535 ## list of keywords that should be blanked from request object
460 ## list of keywords that should be blanked from request object
536 ## can be string with comma separated list of words in lowercase
461 ## can be string with comma separated list of words in lowercase
537 ## (by default client will always blank keys that contain following words
462 ## (by default client will always blank keys that contain following words
538 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
463 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
539 ## this list be extended with additional keywords set here
464 ## this list be extended with additional keywords set here
540 appenlight.request_keys_blacklist =
465 appenlight.request_keys_blacklist =
541
466
542 ## list of namespaces that should be ignores when gathering log entries
467 ## list of namespaces that should be ignores when gathering log entries
543 ## can be string with comma separated list of namespaces
468 ## can be string with comma separated list of namespaces
544 ## (by default the client ignores own entries: appenlight_client.client)
469 ## (by default the client ignores own entries: appenlight_client.client)
545 appenlight.log_namespace_blacklist =
470 appenlight.log_namespace_blacklist =
546
471
547
472
548 ################################################################################
473 ################################################################################
549 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
474 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
550 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
475 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
551 ## execute malicious code after an exception is raised. ##
476 ## execute malicious code after an exception is raised. ##
552 ################################################################################
477 ################################################################################
553 set debug = false
478 set debug = false
554
479
555
480
556 ##############
481 ##############
557 ## STYLING ##
482 ## STYLING ##
558 ##############
483 ##############
559 debug_style = false
484 debug_style = false
560
485
561 ###########################################
486 ###########################################
562 ### MAIN RHODECODE DATABASE CONFIG ###
487 ### MAIN RHODECODE DATABASE CONFIG ###
563 ###########################################
488 ###########################################
564 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode_test.db?timeout=30
489 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode_test.db?timeout=30
565 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode_test
490 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode_test
566 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode_test
491 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode_test
567 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode_test.db?timeout=30
492 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode_test.db?timeout=30
568
493
569 # see sqlalchemy docs for other advanced settings
494 # see sqlalchemy docs for other advanced settings
570
495
571 ## print the sql statements to output
496 ## print the sql statements to output
572 sqlalchemy.db1.echo = false
497 sqlalchemy.db1.echo = false
573 ## recycle the connections after this amount of seconds
498 ## recycle the connections after this amount of seconds
574 sqlalchemy.db1.pool_recycle = 3600
499 sqlalchemy.db1.pool_recycle = 3600
575 sqlalchemy.db1.convert_unicode = true
500 sqlalchemy.db1.convert_unicode = true
576
501
577 ## the number of connections to keep open inside the connection pool.
502 ## the number of connections to keep open inside the connection pool.
578 ## 0 indicates no limit
503 ## 0 indicates no limit
579 #sqlalchemy.db1.pool_size = 5
504 #sqlalchemy.db1.pool_size = 5
580
505
581 ## the number of connections to allow in connection pool "overflow", that is
506 ## the number of connections to allow in connection pool "overflow", that is
582 ## connections that can be opened above and beyond the pool_size setting,
507 ## connections that can be opened above and beyond the pool_size setting,
583 ## which defaults to five.
508 ## which defaults to five.
584 #sqlalchemy.db1.max_overflow = 10
509 #sqlalchemy.db1.max_overflow = 10
585
510
586
511
587 ##################
512 ##################
588 ### VCS CONFIG ###
513 ### VCS CONFIG ###
589 ##################
514 ##################
590 vcs.server.enable = true
515 vcs.server.enable = true
591 vcs.server = localhost:9901
516 vcs.server = localhost:9901
592
517
593 ## Web server connectivity protocol, responsible for web based VCS operatations
518 ## Web server connectivity protocol, responsible for web based VCS operatations
594 ## Available protocols are:
519 ## Available protocols are:
595 ## `http` - use http-rpc backend (default)
520 ## `http` - use http-rpc backend (default)
596 vcs.server.protocol = http
521 vcs.server.protocol = http
597
522
598 ## Push/Pull operations protocol, available options are:
523 ## Push/Pull operations protocol, available options are:
599 ## `http` - use http-rpc backend (default)
524 ## `http` - use http-rpc backend (default)
600 ## `vcsserver.scm_app` - internal app (EE only)
525 ## `vcsserver.scm_app` - internal app (EE only)
601 vcs.scm_app_implementation = http
526 vcs.scm_app_implementation = http
602
527
603 ## Push/Pull operations hooks protocol, available options are:
528 ## Push/Pull operations hooks protocol, available options are:
604 ## `http` - use http-rpc backend (default)
529 ## `http` - use http-rpc backend (default)
605 vcs.hooks.protocol = http
530 vcs.hooks.protocol = http
606
531
607 vcs.server.log_level = debug
532 vcs.server.log_level = debug
608 ## Start VCSServer with this instance as a subprocess, usefull for development
533 ## Start VCSServer with this instance as a subprocess, usefull for development
609 vcs.start_server = false
534 vcs.start_server = false
610
535
611 ## List of enabled VCS backends, available options are:
536 ## List of enabled VCS backends, available options are:
612 ## `hg` - mercurial
537 ## `hg` - mercurial
613 ## `git` - git
538 ## `git` - git
614 ## `svn` - subversion
539 ## `svn` - subversion
615 vcs.backends = hg, git, svn
540 vcs.backends = hg, git, svn
616
541
617 vcs.connection_timeout = 3600
542 vcs.connection_timeout = 3600
618 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
543 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
619 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible, pre-1.9-compatible
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 #vcs.svn.compatible_version = pre-1.8-compatible
545 #vcs.svn.compatible_version = pre-1.8-compatible
621
546
622
547
623 ############################################################
548 ############################################################
624 ### Subversion proxy support (mod_dav_svn) ###
549 ### Subversion proxy support (mod_dav_svn) ###
625 ### Maps RhodeCode repo groups into SVN paths for Apache ###
550 ### Maps RhodeCode repo groups into SVN paths for Apache ###
626 ############################################################
551 ############################################################
627 ## Enable or disable the config file generation.
552 ## Enable or disable the config file generation.
628 svn.proxy.generate_config = false
553 svn.proxy.generate_config = false
629 ## Generate config file with `SVNListParentPath` set to `On`.
554 ## Generate config file with `SVNListParentPath` set to `On`.
630 svn.proxy.list_parent_path = true
555 svn.proxy.list_parent_path = true
631 ## Set location and file name of generated config file.
556 ## Set location and file name of generated config file.
632 svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf
557 svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf
633 ## Used as a prefix to the `Location` block in the generated config file.
558 ## Used as a prefix to the `Location` block in the generated config file.
634 ## In most cases it should be set to `/`.
559 ## In most cases it should be set to `/`.
635 svn.proxy.location_root = /
560 svn.proxy.location_root = /
636 ## Command to reload the mod dav svn configuration on change.
561 ## Command to reload the mod dav svn configuration on change.
637 ## Example: `/etc/init.d/apache2 reload`
562 ## Example: `/etc/init.d/apache2 reload`
638 #svn.proxy.reload_cmd = /etc/init.d/apache2 reload
563 #svn.proxy.reload_cmd = /etc/init.d/apache2 reload
639 ## If the timeout expires before the reload command finishes, the command will
564 ## If the timeout expires before the reload command finishes, the command will
640 ## be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
565 ## be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
641 #svn.proxy.reload_timeout = 10
566 #svn.proxy.reload_timeout = 10
642
567
643 ############################################################
568 ############################################################
644 ### SSH Support Settings ###
569 ### SSH Support Settings ###
645 ############################################################
570 ############################################################
646
571
647 ## Defines if the authorized_keys file should be written on any change of
572 ## Defines if the authorized_keys file should be written on any change of
648 ## user ssh keys, setting this to false also disables posibility of adding
573 ## user ssh keys, setting this to false also disables posibility of adding
649 ## ssh keys for users from web interface.
574 ## ssh keys for users from web interface.
650 ssh.generate_authorized_keyfile = true
575 ssh.generate_authorized_keyfile = true
651
576
652 ## Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
577 ## Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
653 # ssh.authorized_keys_ssh_opts =
578 # ssh.authorized_keys_ssh_opts =
654
579
655 ## File to generate the authorized keys together with options
580 ## File to generate the authorized keys together with options
656 ## It is possible to have multiple key files specified in `sshd_config` e.g.
581 ## It is possible to have multiple key files specified in `sshd_config` e.g.
657 ## AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys_rhodecode
582 ## AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys_rhodecode
658 ssh.authorized_keys_file_path = %(here)s/rc/authorized_keys_rhodecode
583 ssh.authorized_keys_file_path = %(here)s/rc/authorized_keys_rhodecode
659
584
660 ## Command to execute the SSH wrapper. The binary is available in the
585 ## Command to execute the SSH wrapper. The binary is available in the
661 ## rhodecode installation directory.
586 ## rhodecode installation directory.
662 ## e.g ~/.rccontrol/community-1/profile/bin/rc-ssh-wrapper
587 ## e.g ~/.rccontrol/community-1/profile/bin/rc-ssh-wrapper
663 ssh.wrapper_cmd = ~/.rccontrol/community-1/rc-ssh-wrapper
588 ssh.wrapper_cmd = ~/.rccontrol/community-1/rc-ssh-wrapper
664
589
665 ## Allow shell when executing the ssh-wrapper command
590 ## Allow shell when executing the ssh-wrapper command
666 ssh.wrapper_cmd_allow_shell = false
591 ssh.wrapper_cmd_allow_shell = false
667
592
668 ## Enables logging, and detailed output send back to the client. Usefull for
593 ## Enables logging, and detailed output send back to the client. Usefull for
669 ## debugging, shouldn't be used in production.
594 ## debugging, shouldn't be used in production.
670 ssh.enable_debug_logging = false
595 ssh.enable_debug_logging = false
671
596
672 ## Paths to binary executrables, by default they are the names, but we can
597 ## Paths to binary executrables, by default they are the names, but we can
673 ## override them if we want to use a custom one
598 ## override them if we want to use a custom one
674 ssh.executable.hg = ~/.rccontrol/vcsserver-1/profile/bin/hg
599 ssh.executable.hg = ~/.rccontrol/vcsserver-1/profile/bin/hg
675 ssh.executable.git = ~/.rccontrol/vcsserver-1/profile/bin/git
600 ssh.executable.git = ~/.rccontrol/vcsserver-1/profile/bin/git
676 ssh.executable.svn = ~/.rccontrol/vcsserver-1/profile/bin/svnserve
601 ssh.executable.svn = ~/.rccontrol/vcsserver-1/profile/bin/svnserve
677
602
678
603
679 ## Dummy marker to add new entries after.
604 ## Dummy marker to add new entries after.
680 ## Add any custom entries below. Please don't remove.
605 ## Add any custom entries below. Please don't remove.
681 custom.conf = 1
606 custom.conf = 1
682
607
683
608
684 ################################
609 ################################
685 ### LOGGING CONFIGURATION ####
610 ### LOGGING CONFIGURATION ####
686 ################################
611 ################################
687 [loggers]
612 [loggers]
688 keys = root, sqlalchemy, beaker, rhodecode, ssh_wrapper
613 keys = root, sqlalchemy, beaker, rhodecode, ssh_wrapper
689
614
690 [handlers]
615 [handlers]
691 keys = console, console_sql
616 keys = console, console_sql
692
617
693 [formatters]
618 [formatters]
694 keys = generic, color_formatter, color_formatter_sql
619 keys = generic, color_formatter, color_formatter_sql
695
620
696 #############
621 #############
697 ## LOGGERS ##
622 ## LOGGERS ##
698 #############
623 #############
699 [logger_root]
624 [logger_root]
700 level = NOTSET
625 level = NOTSET
701 handlers = console
626 handlers = console
702
627
703 [logger_routes]
628 [logger_routes]
704 level = DEBUG
629 level = DEBUG
705 handlers =
630 handlers =
706 qualname = routes.middleware
631 qualname = routes.middleware
707 ## "level = DEBUG" logs the route matched and routing variables.
632 ## "level = DEBUG" logs the route matched and routing variables.
708 propagate = 1
633 propagate = 1
709
634
710 [logger_beaker]
635 [logger_beaker]
711 level = DEBUG
636 level = DEBUG
712 handlers =
637 handlers =
713 qualname = beaker.container
638 qualname = beaker.container
714 propagate = 1
639 propagate = 1
715
640
716 [logger_rhodecode]
641 [logger_rhodecode]
717 level = DEBUG
642 level = DEBUG
718 handlers =
643 handlers =
719 qualname = rhodecode
644 qualname = rhodecode
720 propagate = 1
645 propagate = 1
721
646
722 [logger_sqlalchemy]
647 [logger_sqlalchemy]
723 level = ERROR
648 level = ERROR
724 handlers = console_sql
649 handlers = console_sql
725 qualname = sqlalchemy.engine
650 qualname = sqlalchemy.engine
726 propagate = 0
651 propagate = 0
727
652
728 [logger_ssh_wrapper]
653 [logger_ssh_wrapper]
729 level = DEBUG
654 level = DEBUG
730 handlers =
655 handlers =
731 qualname = ssh_wrapper
656 qualname = ssh_wrapper
732 propagate = 1
657 propagate = 1
733
658
734
659
735 ##############
660 ##############
736 ## HANDLERS ##
661 ## HANDLERS ##
737 ##############
662 ##############
738
663
739 [handler_console]
664 [handler_console]
740 class = StreamHandler
665 class = StreamHandler
741 args = (sys.stderr,)
666 args = (sys.stderr,)
742 level = DEBUG
667 level = DEBUG
743 formatter = generic
668 formatter = generic
744
669
745 [handler_console_sql]
670 [handler_console_sql]
746 class = StreamHandler
671 class = StreamHandler
747 args = (sys.stderr,)
672 args = (sys.stderr,)
748 level = WARN
673 level = WARN
749 formatter = generic
674 formatter = generic
750
675
751 ################
676 ################
752 ## FORMATTERS ##
677 ## FORMATTERS ##
753 ################
678 ################
754
679
755 [formatter_generic]
680 [formatter_generic]
756 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
681 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
757 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
682 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
758 datefmt = %Y-%m-%d %H:%M:%S
683 datefmt = %Y-%m-%d %H:%M:%S
759
684
760 [formatter_color_formatter]
685 [formatter_color_formatter]
761 class = rhodecode.lib.logging_formatter.ColorFormatter
686 class = rhodecode.lib.logging_formatter.ColorFormatter
762 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
687 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
763 datefmt = %Y-%m-%d %H:%M:%S
688 datefmt = %Y-%m-%d %H:%M:%S
764
689
765 [formatter_color_formatter_sql]
690 [formatter_color_formatter_sql]
766 class = rhodecode.lib.logging_formatter.ColorFormatterSql
691 class = rhodecode.lib.logging_formatter.ColorFormatterSql
767 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
692 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
768 datefmt = %Y-%m-%d %H:%M:%S
693 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,209 +1,257 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import time
21 import shutil
22 import shutil
22 import time
23 import datetime
23 import datetime
24
24
25 import pytest
25 import pytest
26
26
27 from rhodecode.lib.vcs.backends import get_backend
27 from rhodecode.lib.vcs.backends import get_backend
28 from rhodecode.lib.vcs.backends.base import Config
28 from rhodecode.lib.vcs.backends.base import Config
29 from rhodecode.lib.vcs.nodes import FileNode
29 from rhodecode.lib.vcs.nodes import FileNode
30 from rhodecode.tests import get_new_dir
30 from rhodecode.tests import get_new_dir
31 from rhodecode.tests.utils import check_skip_backends, check_xfail_backends
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 @pytest.fixture()
34 @pytest.fixture()
36 def vcs_repository_support(
35 def vcs_repository_support(
37 request, backend_alias, baseapp, _vcs_repo_container):
36 request, backend_alias, baseapp, _vcs_repo_container):
38 """
37 """
39 Provide a test repository for the test run.
38 Provide a test repository for the test run.
40
39
41 Depending on the value of `recreate_repo_per_test` a new repo for each
40 Depending on the value of `recreate_repo_per_test` a new repo for each
42 test will be created.
41 test will be created.
43
42
44 The parameter `--backends` can be used to limit this fixture to specific
43 The parameter `--backends` can be used to limit this fixture to specific
45 backend implementations.
44 backend implementations.
46 """
45 """
47 cls = request.cls
46 cls = request.cls
48
47
49 check_skip_backends(request.node, backend_alias)
48 check_skip_backends(request.node, backend_alias)
50 check_xfail_backends(request.node, backend_alias)
49 check_xfail_backends(request.node, backend_alias)
51
50
52 if _should_create_repo_per_test(cls):
51 if _should_create_repo_per_test(cls):
53 _vcs_repo_container = _create_vcs_repo_container(request)
52 _vcs_repo_container = _create_vcs_repo_container(request)
54
53
55 repo = _vcs_repo_container.get_repo(cls, backend_alias=backend_alias)
54 repo = _vcs_repo_container.get_repo(cls, backend_alias=backend_alias)
56
55
57 # TODO: johbo: Supporting old test class api, think about removing this
56 # TODO: johbo: Supporting old test class api, think about removing this
58 cls.repo = repo
57 cls.repo = repo
59 cls.repo_path = repo.path
58 cls.repo_path = repo.path
60 cls.default_branch = repo.DEFAULT_BRANCH_NAME
59 cls.default_branch = repo.DEFAULT_BRANCH_NAME
61 cls.Backend = cls.backend_class = repo.__class__
60 cls.Backend = cls.backend_class = repo.__class__
62 cls.imc = repo.in_memory_commit
61 cls.imc = repo.in_memory_commit
63
62
64 return (backend_alias, repo)
63 return backend_alias, repo
65
64
66
65
67 @pytest.fixture(scope='class')
66 @pytest.fixture(scope='class')
68 def _vcs_repo_container(request):
67 def _vcs_repo_container(request):
69 """
68 """
70 Internal fixture intended to help support class based scoping on demand.
69 Internal fixture intended to help support class based scoping on demand.
71 """
70 """
72 return _create_vcs_repo_container(request)
71 return _create_vcs_repo_container(request)
73
72
74
73
75 def _create_vcs_repo_container(request):
74 def _create_vcs_repo_container(request):
76 repo_container = VcsRepoContainer()
75 repo_container = VcsRepoContainer()
77 if not request.config.getoption('--keep-tmp-path'):
76 if not request.config.getoption('--keep-tmp-path'):
78 request.addfinalizer(repo_container.cleanup)
77 request.addfinalizer(repo_container.cleanup)
79 return repo_container
78 return repo_container
80
79
81
80
82 class VcsRepoContainer(object):
81 class VcsRepoContainer(object):
83
82
84 def __init__(self):
83 def __init__(self):
85 self._cleanup_paths = []
84 self._cleanup_paths = []
86 self._repos = {}
85 self._repos = {}
87
86
88 def get_repo(self, test_class, backend_alias):
87 def get_repo(self, test_class, backend_alias):
89 if backend_alias not in self._repos:
88 if backend_alias not in self._repos:
90 repo = _create_empty_repository(test_class, backend_alias)
89 repo = _create_empty_repository(test_class, backend_alias)
90
91 self._cleanup_paths.append(repo.path)
91 self._cleanup_paths.append(repo.path)
92 self._repos[backend_alias] = repo
92 self._repos[backend_alias] = repo
93 return self._repos[backend_alias]
93 return self._repos[backend_alias]
94
94
95 def cleanup(self):
95 def cleanup(self):
96 for repo_path in reversed(self._cleanup_paths):
96 for repo_path in reversed(self._cleanup_paths):
97 shutil.rmtree(repo_path)
97 shutil.rmtree(repo_path)
98
98
99
99
100 def _should_create_repo_per_test(cls):
100 def _should_create_repo_per_test(cls):
101 return getattr(cls, 'recreate_repo_per_test', False)
101 return getattr(cls, 'recreate_repo_per_test', False)
102
102
103
103
104 def _create_empty_repository(cls, backend_alias=None):
104 def _create_empty_repository(cls, backend_alias=None):
105 Backend = get_backend(backend_alias or cls.backend_alias)
105 Backend = get_backend(backend_alias or cls.backend_alias)
106 repo_path = get_new_dir(str(time.time()))
106 repo_path = get_new_dir(str(time.time()))
107 repo = Backend(repo_path, create=True)
107 repo = Backend(repo_path, create=True)
108 if hasattr(cls, '_get_commits'):
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 return repo
112 return repo
112
113
113
114
114 @pytest.fixture
115 @pytest.fixture
115 def config():
116 def config():
116 """
117 """
117 Instance of a repository config.
118 Instance of a repository config.
118
119
119 The instance contains only one value:
120 The instance contains only one value:
120
121
121 - Section: "section-a"
122 - Section: "section-a"
122 - Key: "a-1"
123 - Key: "a-1"
123 - Value: "value-a-1"
124 - Value: "value-a-1"
124
125
125 The intended usage is for cases where a config instance is needed but no
126 The intended usage is for cases where a config instance is needed but no
126 specific content is required.
127 specific content is required.
127 """
128 """
128 config = Config()
129 config = Config()
129 config.set('section-a', 'a-1', 'value-a-1')
130 config.set('section-a', 'a-1', 'value-a-1')
130 return config
131 return config
131
132
132
133
133 def _add_commits_to_repo(repo, commits):
134 def _add_commits_to_repo(repo, commits):
134 imc = repo.in_memory_commit
135 imc = repo.in_memory_commit
135 commit = None
136 tip = None
136
137
137 for commit in commits:
138 for commit in commits:
138 for node in commit.get('added', []):
139 for node in commit.get('added', []):
139 imc.add(FileNode(node.path, content=node.content))
140 imc.add(FileNode(node.path, content=node.content))
140 for node in commit.get('changed', []):
141 for node in commit.get('changed', []):
141 imc.change(FileNode(node.path, content=node.content))
142 imc.change(FileNode(node.path, content=node.content))
142 for node in commit.get('removed', []):
143 for node in commit.get('removed', []):
143 imc.remove(FileNode(node.path))
144 imc.remove(FileNode(node.path))
144
145
145 commit = imc.commit(
146 tip = imc.commit(
146 message=unicode(commit['message']),
147 message=unicode(commit['message']),
147 author=unicode(commit['author']),
148 author=unicode(commit['author']),
148 date=commit['date'],
149 date=commit['date'],
149 branch=commit.get('branch'))
150 branch=commit.get('branch'))
150
151
151 return commit
152 return tip
152
153
153
154
154 @pytest.fixture
155 @pytest.fixture
155 def vcs_repo(request, backend_alias):
156 def vcs_repo(request, backend_alias):
156 Backend = get_backend(backend_alias)
157 Backend = get_backend(backend_alias)
157 repo_path = get_new_dir(str(time.time()))
158 repo_path = get_new_dir(str(time.time()))
158 repo = Backend(repo_path, create=True)
159 repo = Backend(repo_path, create=True)
159
160
160 @request.addfinalizer
161 @request.addfinalizer
161 def cleanup():
162 def cleanup():
162 shutil.rmtree(repo_path)
163 shutil.rmtree(repo_path)
163
164
164 return repo
165 return repo
165
166
166
167
167 @pytest.fixture
168 @pytest.fixture
168 def generate_repo_with_commits(vcs_repo):
169 def generate_repo_with_commits(vcs_repo):
169 """
170 """
170 Creates a fabric to generate N comits with some file nodes on a randomly
171 Creates a fabric to generate N comits with some file nodes on a randomly
171 generated repository
172 generated repository
172 """
173 """
173
174
174 def commit_generator(num):
175 def commit_generator(num):
175 start_date = datetime.datetime(2010, 1, 1, 20)
176 start_date = datetime.datetime(2010, 1, 1, 20)
176 for x in xrange(num):
177 for x in xrange(num):
177 yield {
178 yield {
178 'message': 'Commit %d' % x,
179 'message': 'Commit %d' % x,
179 'author': 'Joe Doe <joe.doe@example.com>',
180 'author': 'Joe Doe <joe.doe@example.com>',
180 'date': start_date + datetime.timedelta(hours=12 * x),
181 'date': start_date + datetime.timedelta(hours=12 * x),
181 'added': [
182 'added': [
182 FileNode('file_%d.txt' % x, content='Foobar %d' % x),
183 FileNode('file_%d.txt' % x, content='Foobar %d' % x),
183 ],
184 ],
184 'modified': [
185 'modified': [
185 FileNode('file_%d.txt' % x,
186 FileNode('file_%d.txt' % x,
186 content='Foobar %d modified' % (x-1)),
187 content='Foobar %d modified' % (x-1)),
187 ]
188 ]
188 }
189 }
189
190
190 def commit_maker(num=5):
191 def commit_maker(num=5):
191 _add_commits_to_repo(vcs_repo, commit_generator(num))
192 _add_commits_to_repo(vcs_repo, commit_generator(num))
192 return vcs_repo
193 return vcs_repo
193
194
194 return commit_maker
195 return commit_maker
195
196
196
197
197 @pytest.fixture
198 @pytest.fixture
198 def hg_repo(request, vcs_repo):
199 def hg_repo(request, vcs_repo):
199 repo = vcs_repo
200 repo = vcs_repo
200
201
201 commits = BackendTestMixin._get_commits()
202 commits = repo._get_commits()
202 _add_commits_to_repo(repo, commits)
203 _add_commits_to_repo(repo, commits)
203
204
204 return repo
205 return repo
205
206
206
207
207 @pytest.fixture
208 @pytest.fixture
208 def hg_commit(hg_repo):
209 def hg_commit(hg_repo):
209 return hg_repo.get_commit()
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 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import datetime
21 import datetime
22 import os
22 import os
23 import shutil
23 import shutil
24 import tarfile
24 import tarfile
25 import tempfile
25 import tempfile
26 import zipfile
26 import zipfile
27 import StringIO
27 import StringIO
28
28
29 import mock
29 import mock
30 import pytest
30 import pytest
31
31
32 from rhodecode.lib.vcs.backends import base
32 from rhodecode.lib.vcs.backends import base
33 from rhodecode.lib.vcs.exceptions import ImproperArchiveTypeError, VCSError
33 from rhodecode.lib.vcs.exceptions import ImproperArchiveTypeError, VCSError
34 from rhodecode.lib.vcs.nodes import FileNode
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 class TestArchives(BackendTestMixin):
39 class TestArchives(BackendTestMixin):
39
40
40 @pytest.fixture(autouse=True)
41 @pytest.fixture(autouse=True)
41 def tempfile(self, request):
42 def tempfile(self, request):
42 self.temp_file = tempfile.mkstemp()[1]
43 self.temp_file = tempfile.mkstemp()[1]
43
44
44 @request.addfinalizer
45 @request.addfinalizer
45 def cleanup():
46 def cleanup():
46 os.remove(self.temp_file)
47 os.remove(self.temp_file)
47
48
48 @classmethod
49 @classmethod
49 def _get_commits(cls):
50 def _get_commits(cls):
50 start_date = datetime.datetime(2010, 1, 1, 20)
51 start_date = datetime.datetime(2010, 1, 1, 20)
51 for x in xrange(5):
52 for x in range(5):
52 yield {
53 yield {
53 'message': 'Commit %d' % x,
54 'message': 'Commit %d' % x,
54 'author': 'Joe Doe <joe.doe@example.com>',
55 'author': 'Joe Doe <joe.doe@example.com>',
55 'date': start_date + datetime.timedelta(hours=12 * x),
56 'date': start_date + datetime.timedelta(hours=12 * x),
56 'added': [
57 'added': [
57 FileNode(
58 FileNode(
58 '%d/file_%d.txt' % (x, x), content='Foobar %d' % x),
59 '%d/file_%d.txt' % (x, x), content='Foobar %d' % x),
59 ],
60 ],
60 }
61 }
61
62
62 @pytest.mark.parametrize('compressor', ['gz', 'bz2'])
63 @pytest.mark.parametrize('compressor', ['gz', 'bz2'])
63 def test_archive_tar(self, compressor):
64 def test_archive_tar(self, compressor):
64 self.tip.archive_repo(
65 self.tip.archive_repo(
65 self.temp_file, kind='t' + compressor, prefix='repo')
66 self.temp_file, kind='t' + compressor, prefix='repo')
66 out_dir = tempfile.mkdtemp()
67 out_dir = tempfile.mkdtemp()
67 out_file = tarfile.open(self.temp_file, 'r|' + compressor)
68 out_file = tarfile.open(self.temp_file, 'r|' + compressor)
68 out_file.extractall(out_dir)
69 out_file.extractall(out_dir)
69 out_file.close()
70 out_file.close()
70
71
71 for x in xrange(5):
72 for x in range(5):
72 node_path = '%d/file_%d.txt' % (x, x)
73 node_path = '%d/file_%d.txt' % (x, x)
73 with open(os.path.join(out_dir, 'repo/' + node_path)) as f:
74 with open(os.path.join(out_dir, 'repo/' + node_path)) as f:
74 file_content = f.read()
75 file_content = f.read()
75 assert file_content == self.tip.get_node(node_path).content
76 assert file_content == self.tip.get_node(node_path).content
76
77
77 shutil.rmtree(out_dir)
78 shutil.rmtree(out_dir)
78
79
79 def test_archive_zip(self):
80 def test_archive_zip(self):
80 self.tip.archive_repo(self.temp_file, kind='zip', prefix='repo')
81 self.tip.archive_repo(self.temp_file, kind='zip', prefix='repo')
81 out = zipfile.ZipFile(self.temp_file)
82 out = zipfile.ZipFile(self.temp_file)
82
83
83 for x in xrange(5):
84 for x in range(5):
84 node_path = '%d/file_%d.txt' % (x, x)
85 node_path = '%d/file_%d.txt' % (x, x)
85 decompressed = StringIO.StringIO()
86 decompressed = StringIO.StringIO()
86 decompressed.write(out.read('repo/' + node_path))
87 decompressed.write(out.read('repo/' + node_path))
87 assert decompressed.getvalue() == \
88 assert decompressed.getvalue() == \
88 self.tip.get_node(node_path).content
89 self.tip.get_node(node_path).content
89 decompressed.close()
90 decompressed.close()
90
91
91 def test_archive_zip_with_metadata(self):
92 def test_archive_zip_with_metadata(self):
92 self.tip.archive_repo(self.temp_file, kind='zip',
93 self.tip.archive_repo(self.temp_file, kind='zip',
93 prefix='repo', write_metadata=True)
94 prefix='repo', write_metadata=True)
94
95
95 out = zipfile.ZipFile(self.temp_file)
96 out = zipfile.ZipFile(self.temp_file)
96 metafile = out.read('.archival.txt')
97 metafile = out.read('.archival.txt')
97
98
98 raw_id = self.tip.raw_id
99 raw_id = self.tip.raw_id
99 assert 'rev:%s' % raw_id in metafile
100 assert 'rev:%s' % raw_id in metafile
100
101
101 for x in xrange(5):
102 for x in range(5):
102 node_path = '%d/file_%d.txt' % (x, x)
103 node_path = '%d/file_%d.txt' % (x, x)
103 decompressed = StringIO.StringIO()
104 decompressed = StringIO.StringIO()
104 decompressed.write(out.read('repo/' + node_path))
105 decompressed.write(out.read('repo/' + node_path))
105 assert decompressed.getvalue() == \
106 assert decompressed.getvalue() == \
106 self.tip.get_node(node_path).content
107 self.tip.get_node(node_path).content
107 decompressed.close()
108 decompressed.close()
108
109
109 def test_archive_wrong_kind(self):
110 def test_archive_wrong_kind(self):
110 with pytest.raises(ImproperArchiveTypeError):
111 with pytest.raises(ImproperArchiveTypeError):
111 self.tip.archive_repo(self.temp_file, kind='wrong kind')
112 self.tip.archive_repo(self.temp_file, kind='wrong kind')
112
113
113
114
114 @pytest.fixture
115 @pytest.fixture
115 def base_commit():
116 def base_commit():
116 """
117 """
117 Prepare a `base.BaseCommit` just enough for `_validate_archive_prefix`.
118 Prepare a `base.BaseCommit` just enough for `_validate_archive_prefix`.
118 """
119 """
119 commit = base.BaseCommit()
120 commit = base.BaseCommit()
120 commit.repository = mock.Mock()
121 commit.repository = mock.Mock()
121 commit.repository.name = u'fake_repo'
122 commit.repository.name = u'fake_repo'
122 commit.short_id = 'fake_id'
123 commit.short_id = 'fake_id'
123 return commit
124 return commit
124
125
125
126
126 @pytest.mark.parametrize("prefix", [u"unicode-prefix", u"Ünïcödë"])
127 @pytest.mark.parametrize("prefix", [u"unicode-prefix", u"Ünïcödë"])
127 def test_validate_archive_prefix_enforces_bytes_as_prefix(prefix, base_commit):
128 def test_validate_archive_prefix_enforces_bytes_as_prefix(prefix, base_commit):
128 with pytest.raises(ValueError):
129 with pytest.raises(ValueError):
129 base_commit._validate_archive_prefix(prefix)
130 base_commit._validate_archive_prefix(prefix)
130
131
131
132
132 def test_validate_archive_prefix_empty_prefix(base_commit):
133 def test_validate_archive_prefix_empty_prefix(base_commit):
133 # TODO: johbo: Should raise a ValueError here.
134 # TODO: johbo: Should raise a ValueError here.
134 with pytest.raises(VCSError):
135 with pytest.raises(VCSError):
135 base_commit._validate_archive_prefix('')
136 base_commit._validate_archive_prefix('')
136
137
137
138
138 def test_validate_archive_prefix_with_leading_slash(base_commit):
139 def test_validate_archive_prefix_with_leading_slash(base_commit):
139 # TODO: johbo: Should raise a ValueError here.
140 # TODO: johbo: Should raise a ValueError here.
140 with pytest.raises(VCSError):
141 with pytest.raises(VCSError):
141 base_commit._validate_archive_prefix('/any')
142 base_commit._validate_archive_prefix('/any')
142
143
143
144
144 def test_validate_archive_prefix_falls_back_to_repository_name(base_commit):
145 def test_validate_archive_prefix_falls_back_to_repository_name(base_commit):
145 prefix = base_commit._validate_archive_prefix(None)
146 prefix = base_commit._validate_archive_prefix(None)
146 expected_prefix = base_commit._ARCHIVE_PREFIX_TEMPLATE.format(
147 expected_prefix = base_commit._ARCHIVE_PREFIX_TEMPLATE.format(
147 repo_name='fake_repo',
148 repo_name='fake_repo',
148 short_id='fake_id')
149 short_id='fake_id')
149 assert isinstance(prefix, str)
150 assert isinstance(prefix, str)
150 assert prefix == expected_prefix
151 assert prefix == expected_prefix
@@ -1,146 +1,147 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import datetime
21 import datetime
22
22
23 import pytest
23 import pytest
24
24
25 from rhodecode.lib.vcs.nodes import FileNode
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 class TestBranches(BackendTestMixin):
30 class TestBranches(BackendTestMixin):
30
31
31 def test_empty_repository_has_no_branches(self, vcsbackend):
32 def test_empty_repository_has_no_branches(self, vcsbackend):
32 empty_repo = vcsbackend.create_repo()
33 empty_repo = vcsbackend.create_repo()
33 assert empty_repo.branches == {}
34 assert empty_repo.branches == {}
34
35
35 def test_branches_all(self, vcsbackend):
36 def test_branches_all(self, vcsbackend):
36 branch_count = {
37 branch_count = {
37 'git': 1,
38 'git': 1,
38 'hg': 1,
39 'hg': 1,
39 'svn': 0,
40 'svn': 0,
40 }
41 }
41 assert len(self.repo.branches_all) == branch_count[vcsbackend.alias]
42 assert len(self.repo.branches_all) == branch_count[vcsbackend.alias]
42
43
43 def test_closed_branches(self):
44 def test_closed_branches(self):
44 assert len(self.repo.branches_closed) == 0
45 assert len(self.repo.branches_closed) == 0
45
46
46 def test_simple(self, local_dt_to_utc):
47 def test_simple(self, local_dt_to_utc):
47 tip = self.repo.get_commit()
48 tip = self.repo.get_commit()
48 assert tip.message == 'Changes...'
49 assert tip.message == 'Changes...'
49 assert tip.date == local_dt_to_utc(datetime.datetime(2010, 1, 1, 21))
50 assert tip.date == local_dt_to_utc(datetime.datetime(2010, 1, 1, 21))
50
51
51 @pytest.mark.backends("git", "hg")
52 @pytest.mark.backends("git", "hg")
52 def test_new_branch(self):
53 def test_new_branch(self):
53 # This check must not be removed to ensure the 'branches' LazyProperty
54 # This check must not be removed to ensure the 'branches' LazyProperty
54 # gets hit *before* the new 'foobar' branch got created:
55 # gets hit *before* the new 'foobar' branch got created:
55 assert 'foobar' not in self.repo.branches
56 assert 'foobar' not in self.repo.branches
56 self.imc.add(FileNode(
57 self.imc.add(FileNode(
57 'docs/index.txt',
58 'docs/index.txt',
58 content='Documentation\n'))
59 content='Documentation\n'))
59 foobar_tip = self.imc.commit(
60 foobar_tip = self.imc.commit(
60 message=u'New branch: foobar',
61 message=u'New branch: foobar',
61 author=u'joe',
62 author=u'joe',
62 branch='foobar',
63 branch='foobar',
63 )
64 )
64 assert 'foobar' in self.repo.branches
65 assert 'foobar' in self.repo.branches
65 assert foobar_tip.branch == 'foobar'
66 assert foobar_tip.branch == 'foobar'
66
67
67 @pytest.mark.backends("git", "hg")
68 @pytest.mark.backends("git", "hg")
68 def test_new_head(self):
69 def test_new_head(self):
69 tip = self.repo.get_commit()
70 tip = self.repo.get_commit()
70 self.imc.add(FileNode(
71 self.imc.add(FileNode(
71 'docs/index.txt',
72 'docs/index.txt',
72 content='Documentation\n'))
73 content='Documentation\n'))
73 foobar_tip = self.imc.commit(
74 foobar_tip = self.imc.commit(
74 message=u'New branch: foobar',
75 message=u'New branch: foobar',
75 author=u'joe',
76 author=u'joe',
76 branch='foobar',
77 branch='foobar',
77 parents=[tip],
78 parents=[tip],
78 )
79 )
79 self.imc.change(FileNode(
80 self.imc.change(FileNode(
80 'docs/index.txt',
81 'docs/index.txt',
81 content='Documentation\nand more...\n'))
82 content='Documentation\nand more...\n'))
82 newtip = self.imc.commit(
83 newtip = self.imc.commit(
83 message=u'At default branch',
84 message=u'At default branch',
84 author=u'joe',
85 author=u'joe',
85 branch=foobar_tip.branch,
86 branch=foobar_tip.branch,
86 parents=[foobar_tip],
87 parents=[foobar_tip],
87 )
88 )
88
89
89 newest_tip = self.imc.commit(
90 newest_tip = self.imc.commit(
90 message=u'Merged with %s' % foobar_tip.raw_id,
91 message=u'Merged with %s' % foobar_tip.raw_id,
91 author=u'joe',
92 author=u'joe',
92 branch=self.backend_class.DEFAULT_BRANCH_NAME,
93 branch=self.backend_class.DEFAULT_BRANCH_NAME,
93 parents=[newtip, foobar_tip],
94 parents=[newtip, foobar_tip],
94 )
95 )
95
96
96 assert newest_tip.branch == \
97 assert newest_tip.branch == \
97 self.backend_class.DEFAULT_BRANCH_NAME
98 self.backend_class.DEFAULT_BRANCH_NAME
98
99
99 @pytest.mark.backends("git", "hg")
100 @pytest.mark.backends("git", "hg")
100 def test_branch_with_slash_in_name(self):
101 def test_branch_with_slash_in_name(self):
101 self.imc.add(FileNode('extrafile', content='Some data\n'))
102 self.imc.add(FileNode('extrafile', content='Some data\n'))
102 self.imc.commit(
103 self.imc.commit(
103 u'Branch with a slash!', author=u'joe',
104 u'Branch with a slash!', author=u'joe',
104 branch='issue/123')
105 branch='issue/123')
105 assert 'issue/123' in self.repo.branches
106 assert 'issue/123' in self.repo.branches
106
107
107 @pytest.mark.backends("git", "hg")
108 @pytest.mark.backends("git", "hg")
108 def test_branch_with_slash_in_name_and_similar_without(self):
109 def test_branch_with_slash_in_name_and_similar_without(self):
109 self.imc.add(FileNode('extrafile', content='Some data\n'))
110 self.imc.add(FileNode('extrafile', content='Some data\n'))
110 self.imc.commit(
111 self.imc.commit(
111 u'Branch with a slash!', author=u'joe',
112 u'Branch with a slash!', author=u'joe',
112 branch='issue/123')
113 branch='issue/123')
113 self.imc.add(FileNode('extrafile II', content='Some data\n'))
114 self.imc.add(FileNode('extrafile II', content='Some data\n'))
114 self.imc.commit(
115 self.imc.commit(
115 u'Branch without a slash...', author=u'joe',
116 u'Branch without a slash...', author=u'joe',
116 branch='123')
117 branch='123')
117 assert 'issue/123' in self.repo.branches
118 assert 'issue/123' in self.repo.branches
118 assert '123' in self.repo.branches
119 assert '123' in self.repo.branches
119
120
120
121
121 class TestSvnBranches(object):
122 class TestSvnBranches(object):
122
123
123 def test_empty_repository_has_no_tags_and_branches(self, vcsbackend_svn):
124 def test_empty_repository_has_no_tags_and_branches(self, vcsbackend_svn):
124 empty_repo = vcsbackend_svn.create_repo()
125 empty_repo = vcsbackend_svn.create_repo()
125 assert empty_repo.branches == {}
126 assert empty_repo.branches == {}
126 assert empty_repo.tags == {}
127 assert empty_repo.tags == {}
127
128
128 def test_missing_structure_has_no_tags_and_branches(self, vcsbackend_svn):
129 def test_missing_structure_has_no_tags_and_branches(self, vcsbackend_svn):
129 repo = vcsbackend_svn.create_repo(number_of_commits=1)
130 repo = vcsbackend_svn.create_repo(number_of_commits=1)
130 assert repo.branches == {}
131 assert repo.branches == {}
131 assert repo.tags == {}
132 assert repo.tags == {}
132
133
133 def test_discovers_ordered_branches(self, vcsbackend_svn):
134 def test_discovers_ordered_branches(self, vcsbackend_svn):
134 repo = vcsbackend_svn['svn-simple-layout']
135 repo = vcsbackend_svn['svn-simple-layout']
135 expected_branches = [
136 expected_branches = [
136 'branches/add-docs',
137 'branches/add-docs',
137 'branches/argparse',
138 'branches/argparse',
138 'trunk',
139 'trunk',
139 ]
140 ]
140 assert repo.branches.keys() == expected_branches
141 assert repo.branches.keys() == expected_branches
141
142
142 def test_discovers_ordered_tags(self, vcsbackend_svn):
143 def test_discovers_ordered_tags(self, vcsbackend_svn):
143 repo = vcsbackend_svn['svn-simple-layout']
144 repo = vcsbackend_svn['svn-simple-layout']
144 expected_tags = [
145 expected_tags = [
145 'tags/v0.1', 'tags/v0.2', 'tags/v0.3', 'tags/v0.5']
146 'tags/v0.1', 'tags/v0.2', 'tags/v0.3', 'tags/v0.5']
146 assert repo.tags.keys() == expected_tags
147 assert repo.tags.keys() == expected_tags
@@ -1,590 +1,593 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import datetime
21 import datetime
22 import time
22 import time
23
23
24 import pytest
24 import pytest
25
25
26 from rhodecode.lib.vcs.backends.base import (
26 from rhodecode.lib.vcs.backends.base import (
27 CollectionGenerator, FILEMODE_DEFAULT, EmptyCommit)
27 CollectionGenerator, FILEMODE_DEFAULT, EmptyCommit)
28 from rhodecode.lib.vcs.exceptions import (
28 from rhodecode.lib.vcs.exceptions import (
29 BranchDoesNotExistError, CommitDoesNotExistError,
29 BranchDoesNotExistError, CommitDoesNotExistError,
30 RepositoryError, EmptyRepositoryError)
30 RepositoryError, EmptyRepositoryError)
31 from rhodecode.lib.vcs.nodes import (
31 from rhodecode.lib.vcs.nodes import (
32 FileNode, AddedFileNodesGenerator,
32 FileNode, AddedFileNodesGenerator,
33 ChangedFileNodesGenerator, RemovedFileNodesGenerator)
33 ChangedFileNodesGenerator, RemovedFileNodesGenerator)
34 from rhodecode.tests import get_new_dir
34 from rhodecode.tests import get_new_dir
35 from rhodecode.tests.vcs.base import BackendTestMixin
35 from rhodecode.tests.vcs.conftest import BackendTestMixin
36
36
37
37
38 class TestBaseChangeset:
38 class TestBaseChangeset:
39
39
40 def test_is_deprecated(self):
40 def test_is_deprecated(self):
41 from rhodecode.lib.vcs.backends.base import BaseChangeset
41 from rhodecode.lib.vcs.backends.base import BaseChangeset
42 pytest.deprecated_call(BaseChangeset)
42 pytest.deprecated_call(BaseChangeset)
43
43
44
44
45 class TestEmptyCommit:
45 class TestEmptyCommit(object):
46
46
47 def test_branch_without_alias_returns_none(self):
47 def test_branch_without_alias_returns_none(self):
48 commit = EmptyCommit()
48 commit = EmptyCommit()
49 assert commit.branch is None
49 assert commit.branch is None
50
50
51
51
52 @pytest.mark.usefixtures("vcs_repository_support")
52 class TestCommitsInNonEmptyRepo(BackendTestMixin):
53 class TestCommitsInNonEmptyRepo(BackendTestMixin):
53 recreate_repo_per_test = True
54 recreate_repo_per_test = True
54
55
55 @classmethod
56 @classmethod
56 def _get_commits(cls):
57 def _get_commits(cls):
57 start_date = datetime.datetime(2010, 1, 1, 20)
58 start_date = datetime.datetime(2010, 1, 1, 20)
58 for x in xrange(5):
59 for x in xrange(5):
59 yield {
60 yield {
60 'message': 'Commit %d' % x,
61 'message': 'Commit %d' % x,
61 'author': 'Joe Doe <joe.doe@example.com>',
62 'author': 'Joe Doe <joe.doe@example.com>',
62 'date': start_date + datetime.timedelta(hours=12 * x),
63 'date': start_date + datetime.timedelta(hours=12 * x),
63 'added': [
64 'added': [
64 FileNode('file_%d.txt' % x, content='Foobar %d' % x),
65 FileNode('file_%d.txt' % x, content='Foobar %d' % x),
65 ],
66 ],
66 }
67 }
67
68
68 def test_walk_returns_empty_list_in_case_of_file(self):
69 def test_walk_returns_empty_list_in_case_of_file(self):
69 result = list(self.tip.walk('file_0.txt'))
70 result = list(self.tip.walk('file_0.txt'))
70 assert result == []
71 assert result == []
71
72
72 @pytest.mark.backends("git", "hg")
73 @pytest.mark.backends("git", "hg")
73 def test_new_branch(self):
74 def test_new_branch(self):
74 self.imc.add(FileNode('docs/index.txt',
75 self.imc.add(FileNode('docs/index.txt',
75 content='Documentation\n'))
76 content='Documentation\n'))
76 foobar_tip = self.imc.commit(
77 foobar_tip = self.imc.commit(
77 message=u'New branch: foobar',
78 message=u'New branch: foobar',
78 author=u'joe',
79 author=u'joe',
79 branch='foobar',
80 branch='foobar',
80 )
81 )
81 assert 'foobar' in self.repo.branches
82 assert 'foobar' in self.repo.branches
82 assert foobar_tip.branch == 'foobar'
83 assert foobar_tip.branch == 'foobar'
83 # 'foobar' should be the only branch that contains the new commit
84 # 'foobar' should be the only branch that contains the new commit
84 branch = self.repo.branches.values()
85 branch = self.repo.branches.values()
85 assert branch[0] != branch[1]
86 assert branch[0] != branch[1]
86
87
87 @pytest.mark.backends("git", "hg")
88 @pytest.mark.backends("git", "hg")
88 def test_new_head_in_default_branch(self):
89 def test_new_head_in_default_branch(self):
89 tip = self.repo.get_commit()
90 tip = self.repo.get_commit()
90 self.imc.add(FileNode('docs/index.txt',
91 self.imc.add(FileNode('docs/index.txt',
91 content='Documentation\n'))
92 content='Documentation\n'))
92 foobar_tip = self.imc.commit(
93 foobar_tip = self.imc.commit(
93 message=u'New branch: foobar',
94 message=u'New branch: foobar',
94 author=u'joe',
95 author=u'joe',
95 branch='foobar',
96 branch='foobar',
96 parents=[tip],
97 parents=[tip],
97 )
98 )
98 self.imc.change(FileNode('docs/index.txt',
99 self.imc.change(FileNode('docs/index.txt',
99 content='Documentation\nand more...\n'))
100 content='Documentation\nand more...\n'))
100 newtip = self.imc.commit(
101 newtip = self.imc.commit(
101 message=u'At default branch',
102 message=u'At default branch',
102 author=u'joe',
103 author=u'joe',
103 branch=foobar_tip.branch,
104 branch=foobar_tip.branch,
104 parents=[foobar_tip],
105 parents=[foobar_tip],
105 )
106 )
106
107
107 newest_tip = self.imc.commit(
108 newest_tip = self.imc.commit(
108 message=u'Merged with %s' % foobar_tip.raw_id,
109 message=u'Merged with %s' % foobar_tip.raw_id,
109 author=u'joe',
110 author=u'joe',
110 branch=self.backend_class.DEFAULT_BRANCH_NAME,
111 branch=self.backend_class.DEFAULT_BRANCH_NAME,
111 parents=[newtip, foobar_tip],
112 parents=[newtip, foobar_tip],
112 )
113 )
113
114
114 assert newest_tip.branch == self.backend_class.DEFAULT_BRANCH_NAME
115 assert newest_tip.branch == self.backend_class.DEFAULT_BRANCH_NAME
115
116
116 @pytest.mark.backends("git", "hg")
117 @pytest.mark.backends("git", "hg")
117 def test_get_commits_respects_branch_name(self):
118 def test_get_commits_respects_branch_name(self):
118 """
119 """
119 * e1930d0 (HEAD, master) Back in default branch
120 * e1930d0 (HEAD, master) Back in default branch
120 | * e1930d0 (docs) New Branch: docs2
121 | * e1930d0 (docs) New Branch: docs2
121 | * dcc14fa New branch: docs
122 | * dcc14fa New branch: docs
122 |/
123 |/
123 * e63c41a Initial commit
124 * e63c41a Initial commit
124 ...
125 ...
125 * 624d3db Commit 0
126 * 624d3db Commit 0
126
127
127 :return:
128 :return:
128 """
129 """
129 DEFAULT_BRANCH = self.repo.DEFAULT_BRANCH_NAME
130 DEFAULT_BRANCH = self.repo.DEFAULT_BRANCH_NAME
130 TEST_BRANCH = 'docs'
131 TEST_BRANCH = 'docs'
131 org_tip = self.repo.get_commit()
132 org_tip = self.repo.get_commit()
132
133
133 self.imc.add(FileNode('readme.txt', content='Document\n'))
134 self.imc.add(FileNode('readme.txt', content='Document\n'))
134 initial = self.imc.commit(
135 initial = self.imc.commit(
135 message=u'Initial commit',
136 message=u'Initial commit',
136 author=u'joe',
137 author=u'joe',
137 parents=[org_tip],
138 parents=[org_tip],
138 branch=DEFAULT_BRANCH,)
139 branch=DEFAULT_BRANCH,)
139
140
140 self.imc.add(FileNode('newdoc.txt', content='foobar\n'))
141 self.imc.add(FileNode('newdoc.txt', content='foobar\n'))
141 docs_branch_commit1 = self.imc.commit(
142 docs_branch_commit1 = self.imc.commit(
142 message=u'New branch: docs',
143 message=u'New branch: docs',
143 author=u'joe',
144 author=u'joe',
144 parents=[initial],
145 parents=[initial],
145 branch=TEST_BRANCH,)
146 branch=TEST_BRANCH,)
146
147
147 self.imc.add(FileNode('newdoc2.txt', content='foobar2\n'))
148 self.imc.add(FileNode('newdoc2.txt', content='foobar2\n'))
148 docs_branch_commit2 = self.imc.commit(
149 docs_branch_commit2 = self.imc.commit(
149 message=u'New branch: docs2',
150 message=u'New branch: docs2',
150 author=u'joe',
151 author=u'joe',
151 parents=[docs_branch_commit1],
152 parents=[docs_branch_commit1],
152 branch=TEST_BRANCH,)
153 branch=TEST_BRANCH,)
153
154
154 self.imc.add(FileNode('newfile', content='hello world\n'))
155 self.imc.add(FileNode('newfile', content='hello world\n'))
155 self.imc.commit(
156 self.imc.commit(
156 message=u'Back in default branch',
157 message=u'Back in default branch',
157 author=u'joe',
158 author=u'joe',
158 parents=[initial],
159 parents=[initial],
159 branch=DEFAULT_BRANCH,)
160 branch=DEFAULT_BRANCH,)
160
161
161 default_branch_commits = self.repo.get_commits(
162 default_branch_commits = self.repo.get_commits(
162 branch_name=DEFAULT_BRANCH)
163 branch_name=DEFAULT_BRANCH)
163 assert docs_branch_commit1 not in list(default_branch_commits)
164 assert docs_branch_commit1 not in list(default_branch_commits)
164 assert docs_branch_commit2 not in list(default_branch_commits)
165 assert docs_branch_commit2 not in list(default_branch_commits)
165
166
166 docs_branch_commits = self.repo.get_commits(
167 docs_branch_commits = self.repo.get_commits(
167 start_id=self.repo.commit_ids[0], end_id=self.repo.commit_ids[-1],
168 start_id=self.repo.commit_ids[0], end_id=self.repo.commit_ids[-1],
168 branch_name=TEST_BRANCH)
169 branch_name=TEST_BRANCH)
169 assert docs_branch_commit1 in list(docs_branch_commits)
170 assert docs_branch_commit1 in list(docs_branch_commits)
170 assert docs_branch_commit2 in list(docs_branch_commits)
171 assert docs_branch_commit2 in list(docs_branch_commits)
171
172
172 @pytest.mark.backends("svn")
173 @pytest.mark.backends("svn")
173 def test_get_commits_respects_branch_name_svn(self, vcsbackend_svn):
174 def test_get_commits_respects_branch_name_svn(self, vcsbackend_svn):
174 repo = vcsbackend_svn['svn-simple-layout']
175 repo = vcsbackend_svn['svn-simple-layout']
175 commits = repo.get_commits(branch_name='trunk')
176 commits = repo.get_commits(branch_name='trunk')
176 commit_indexes = [c.idx for c in commits]
177 commit_indexes = [c.idx for c in commits]
177 assert commit_indexes == [1, 2, 3, 7, 12, 15]
178 assert commit_indexes == [1, 2, 3, 7, 12, 15]
178
179
179 def test_get_commit_by_branch(self):
180 def test_get_commit_by_branch(self):
180 for branch, commit_id in self.repo.branches.iteritems():
181 for branch, commit_id in self.repo.branches.iteritems():
181 assert commit_id == self.repo.get_commit(branch).raw_id
182 assert commit_id == self.repo.get_commit(branch).raw_id
182
183
183 def test_get_commit_by_tag(self):
184 def test_get_commit_by_tag(self):
184 for tag, commit_id in self.repo.tags.iteritems():
185 for tag, commit_id in self.repo.tags.iteritems():
185 assert commit_id == self.repo.get_commit(tag).raw_id
186 assert commit_id == self.repo.get_commit(tag).raw_id
186
187
187 def test_get_commit_parents(self):
188 def test_get_commit_parents(self):
188 repo = self.repo
189 repo = self.repo
189 for test_idx in [1, 2, 3]:
190 for test_idx in [1, 2, 3]:
190 commit = repo.get_commit(commit_idx=test_idx - 1)
191 commit = repo.get_commit(commit_idx=test_idx - 1)
191 assert [commit] == repo.get_commit(commit_idx=test_idx).parents
192 assert [commit] == repo.get_commit(commit_idx=test_idx).parents
192
193
193 def test_get_commit_children(self):
194 def test_get_commit_children(self):
194 repo = self.repo
195 repo = self.repo
195 for test_idx in [1, 2, 3]:
196 for test_idx in [1, 2, 3]:
196 commit = repo.get_commit(commit_idx=test_idx + 1)
197 commit = repo.get_commit(commit_idx=test_idx + 1)
197 assert [commit] == repo.get_commit(commit_idx=test_idx).children
198 assert [commit] == repo.get_commit(commit_idx=test_idx).children
198
199
199
200
201 @pytest.mark.usefixtures("vcs_repository_support")
200 class TestCommits(BackendTestMixin):
202 class TestCommits(BackendTestMixin):
201 recreate_repo_per_test = False
203 recreate_repo_per_test = False
202
204
203 @classmethod
205 @classmethod
204 def _get_commits(cls):
206 def _get_commits(cls):
205 start_date = datetime.datetime(2010, 1, 1, 20)
207 start_date = datetime.datetime(2010, 1, 1, 20)
206 for x in xrange(5):
208 for x in xrange(5):
207 yield {
209 yield {
208 'message': u'Commit %d' % x,
210 'message': u'Commit %d' % x,
209 'author': u'Joe Doe <joe.doe@example.com>',
211 'author': u'Joe Doe <joe.doe@example.com>',
210 'date': start_date + datetime.timedelta(hours=12 * x),
212 'date': start_date + datetime.timedelta(hours=12 * x),
211 'added': [
213 'added': [
212 FileNode('file_%d.txt' % x, content='Foobar %d' % x),
214 FileNode('file_%d.txt' % x, content='Foobar %d' % x),
213 ],
215 ],
214 }
216 }
215
217
216 def test_simple(self):
218 def test_simple(self):
217 tip = self.repo.get_commit()
219 tip = self.repo.get_commit()
218 assert tip.date, datetime.datetime(2010, 1, 3 == 20)
220 assert tip.date, datetime.datetime(2010, 1, 3 == 20)
219
221
220 def test_simple_serialized_commit(self):
222 def test_simple_serialized_commit(self):
221 tip = self.repo.get_commit()
223 tip = self.repo.get_commit()
222 # json.dumps(tip) uses .__json__() method
224 # json.dumps(tip) uses .__json__() method
223 data = tip.__json__()
225 data = tip.__json__()
224 assert 'branch' in data
226 assert 'branch' in data
225 assert data['revision']
227 assert data['revision']
226
228
227 def test_retrieve_tip(self):
229 def test_retrieve_tip(self):
228 tip = self.repo.get_commit('tip')
230 tip = self.repo.get_commit('tip')
229 assert tip == self.repo.get_commit()
231 assert tip == self.repo.get_commit()
230
232
231 def test_invalid(self):
233 def test_invalid(self):
232 with pytest.raises(CommitDoesNotExistError):
234 with pytest.raises(CommitDoesNotExistError):
233 self.repo.get_commit(commit_idx=123456789)
235 self.repo.get_commit(commit_idx=123456789)
234
236
235 def test_idx(self):
237 def test_idx(self):
236 commit = self.repo[0]
238 commit = self.repo[0]
237 assert commit.idx == 0
239 assert commit.idx == 0
238
240
239 def test_negative_idx(self):
241 def test_negative_idx(self):
240 commit = self.repo.get_commit(commit_idx=-1)
242 commit = self.repo.get_commit(commit_idx=-1)
241 assert commit.idx >= 0
243 assert commit.idx >= 0
242
244
243 def test_revision_is_deprecated(self):
245 def test_revision_is_deprecated(self):
244 def get_revision(commit):
246 def get_revision(commit):
245 return commit.revision
247 return commit.revision
246
248
247 commit = self.repo[0]
249 commit = self.repo[0]
248 pytest.deprecated_call(get_revision, commit)
250 pytest.deprecated_call(get_revision, commit)
249
251
250 def test_size(self):
252 def test_size(self):
251 tip = self.repo.get_commit()
253 tip = self.repo.get_commit()
252 size = 5 * len('Foobar N') # Size of 5 files
254 size = 5 * len('Foobar N') # Size of 5 files
253 assert tip.size == size
255 assert tip.size == size
254
256
255 def test_size_at_commit(self):
257 def test_size_at_commit(self):
256 tip = self.repo.get_commit()
258 tip = self.repo.get_commit()
257 size = 5 * len('Foobar N') # Size of 5 files
259 size = 5 * len('Foobar N') # Size of 5 files
258 assert self.repo.size_at_commit(tip.raw_id) == size
260 assert self.repo.size_at_commit(tip.raw_id) == size
259
261
260 def test_size_at_first_commit(self):
262 def test_size_at_first_commit(self):
261 commit = self.repo[0]
263 commit = self.repo[0]
262 size = len('Foobar N') # Size of 1 file
264 size = len('Foobar N') # Size of 1 file
263 assert self.repo.size_at_commit(commit.raw_id) == size
265 assert self.repo.size_at_commit(commit.raw_id) == size
264
266
265 def test_author(self):
267 def test_author(self):
266 tip = self.repo.get_commit()
268 tip = self.repo.get_commit()
267 assert_text_equal(tip.author, u'Joe Doe <joe.doe@example.com>')
269 assert_text_equal(tip.author, u'Joe Doe <joe.doe@example.com>')
268
270
269 def test_author_name(self):
271 def test_author_name(self):
270 tip = self.repo.get_commit()
272 tip = self.repo.get_commit()
271 assert_text_equal(tip.author_name, u'Joe Doe')
273 assert_text_equal(tip.author_name, u'Joe Doe')
272
274
273 def test_author_email(self):
275 def test_author_email(self):
274 tip = self.repo.get_commit()
276 tip = self.repo.get_commit()
275 assert_text_equal(tip.author_email, u'joe.doe@example.com')
277 assert_text_equal(tip.author_email, u'joe.doe@example.com')
276
278
277 def test_message(self):
279 def test_message(self):
278 tip = self.repo.get_commit()
280 tip = self.repo.get_commit()
279 assert_text_equal(tip.message, u'Commit 4')
281 assert_text_equal(tip.message, u'Commit 4')
280
282
281 def test_diff(self):
283 def test_diff(self):
282 tip = self.repo.get_commit()
284 tip = self.repo.get_commit()
283 diff = tip.diff()
285 diff = tip.diff()
284 assert "+Foobar 4" in diff.raw
286 assert "+Foobar 4" in diff.raw
285
287
286 def test_prev(self):
288 def test_prev(self):
287 tip = self.repo.get_commit()
289 tip = self.repo.get_commit()
288 prev_commit = tip.prev()
290 prev_commit = tip.prev()
289 assert prev_commit.message == 'Commit 3'
291 assert prev_commit.message == 'Commit 3'
290
292
291 def test_prev_raises_on_first_commit(self):
293 def test_prev_raises_on_first_commit(self):
292 commit = self.repo.get_commit(commit_idx=0)
294 commit = self.repo.get_commit(commit_idx=0)
293 with pytest.raises(CommitDoesNotExistError):
295 with pytest.raises(CommitDoesNotExistError):
294 commit.prev()
296 commit.prev()
295
297
296 def test_prev_works_on_second_commit_issue_183(self):
298 def test_prev_works_on_second_commit_issue_183(self):
297 commit = self.repo.get_commit(commit_idx=1)
299 commit = self.repo.get_commit(commit_idx=1)
298 prev_commit = commit.prev()
300 prev_commit = commit.prev()
299 assert prev_commit.idx == 0
301 assert prev_commit.idx == 0
300
302
301 def test_next(self):
303 def test_next(self):
302 commit = self.repo.get_commit(commit_idx=2)
304 commit = self.repo.get_commit(commit_idx=2)
303 next_commit = commit.next()
305 next_commit = commit.next()
304 assert next_commit.message == 'Commit 3'
306 assert next_commit.message == 'Commit 3'
305
307
306 def test_next_raises_on_tip(self):
308 def test_next_raises_on_tip(self):
307 commit = self.repo.get_commit()
309 commit = self.repo.get_commit()
308 with pytest.raises(CommitDoesNotExistError):
310 with pytest.raises(CommitDoesNotExistError):
309 commit.next()
311 commit.next()
310
312
311 def test_get_file_commit(self):
313 def test_get_file_commit(self):
312 commit = self.repo.get_commit()
314 commit = self.repo.get_commit()
313 commit.get_file_commit('file_4.txt')
315 commit.get_file_commit('file_4.txt')
314 assert commit.message == 'Commit 4'
316 assert commit.message == 'Commit 4'
315
317
316 def test_get_filenodes_generator(self):
318 def test_get_filenodes_generator(self):
317 tip = self.repo.get_commit()
319 tip = self.repo.get_commit()
318 filepaths = [node.path for node in tip.get_filenodes_generator()]
320 filepaths = [node.path for node in tip.get_filenodes_generator()]
319 assert filepaths == ['file_%d.txt' % x for x in xrange(5)]
321 assert filepaths == ['file_%d.txt' % x for x in xrange(5)]
320
322
321 def test_get_file_annotate(self):
323 def test_get_file_annotate(self):
322 file_added_commit = self.repo.get_commit(commit_idx=3)
324 file_added_commit = self.repo.get_commit(commit_idx=3)
323 annotations = list(file_added_commit.get_file_annotate('file_3.txt'))
325 annotations = list(file_added_commit.get_file_annotate('file_3.txt'))
324
326
325 line_no, commit_id, commit_loader, line = annotations[0]
327 line_no, commit_id, commit_loader, line = annotations[0]
326
328
327 assert line_no == 1
329 assert line_no == 1
328 assert commit_id == file_added_commit.raw_id
330 assert commit_id == file_added_commit.raw_id
329 assert commit_loader() == file_added_commit
331 assert commit_loader() == file_added_commit
330 assert 'Foobar 3' in line
332 assert 'Foobar 3' in line
331
333
332 def test_get_file_annotate_does_not_exist(self):
334 def test_get_file_annotate_does_not_exist(self):
333 file_added_commit = self.repo.get_commit(commit_idx=2)
335 file_added_commit = self.repo.get_commit(commit_idx=2)
334 # TODO: Should use a specific exception class here?
336 # TODO: Should use a specific exception class here?
335 with pytest.raises(Exception):
337 with pytest.raises(Exception):
336 list(file_added_commit.get_file_annotate('file_3.txt'))
338 list(file_added_commit.get_file_annotate('file_3.txt'))
337
339
338 def test_get_file_annotate_tip(self):
340 def test_get_file_annotate_tip(self):
339 tip = self.repo.get_commit()
341 tip = self.repo.get_commit()
340 commit = self.repo.get_commit(commit_idx=3)
342 commit = self.repo.get_commit(commit_idx=3)
341 expected_values = list(commit.get_file_annotate('file_3.txt'))
343 expected_values = list(commit.get_file_annotate('file_3.txt'))
342 annotations = list(tip.get_file_annotate('file_3.txt'))
344 annotations = list(tip.get_file_annotate('file_3.txt'))
343
345
344 # Note: Skip index 2 because the loader function is not the same
346 # Note: Skip index 2 because the loader function is not the same
345 for idx in (0, 1, 3):
347 for idx in (0, 1, 3):
346 assert annotations[0][idx] == expected_values[0][idx]
348 assert annotations[0][idx] == expected_values[0][idx]
347
349
348 def test_get_commits_is_ordered_by_date(self):
350 def test_get_commits_is_ordered_by_date(self):
349 commits = self.repo.get_commits()
351 commits = self.repo.get_commits()
350 assert isinstance(commits, CollectionGenerator)
352 assert isinstance(commits, CollectionGenerator)
351 assert len(commits) == 0 or len(commits) != 0
353 assert len(commits) == 0 or len(commits) != 0
352 commits = list(commits)
354 commits = list(commits)
353 ordered_by_date = sorted(commits, key=lambda commit: commit.date)
355 ordered_by_date = sorted(commits, key=lambda commit: commit.date)
354 assert commits == ordered_by_date
356 assert commits == ordered_by_date
355
357
356 def test_get_commits_respects_start(self):
358 def test_get_commits_respects_start(self):
357 second_id = self.repo.commit_ids[1]
359 second_id = self.repo.commit_ids[1]
358 commits = self.repo.get_commits(start_id=second_id)
360 commits = self.repo.get_commits(start_id=second_id)
359 assert isinstance(commits, CollectionGenerator)
361 assert isinstance(commits, CollectionGenerator)
360 commits = list(commits)
362 commits = list(commits)
361 assert len(commits) == 4
363 assert len(commits) == 4
362
364
363 def test_get_commits_includes_start_commit(self):
365 def test_get_commits_includes_start_commit(self):
364 second_id = self.repo.commit_ids[1]
366 second_id = self.repo.commit_ids[1]
365 commits = self.repo.get_commits(start_id=second_id)
367 commits = self.repo.get_commits(start_id=second_id)
366 assert isinstance(commits, CollectionGenerator)
368 assert isinstance(commits, CollectionGenerator)
367 commits = list(commits)
369 commits = list(commits)
368 assert commits[0].raw_id == second_id
370 assert commits[0].raw_id == second_id
369
371
370 def test_get_commits_respects_end(self):
372 def test_get_commits_respects_end(self):
371 second_id = self.repo.commit_ids[1]
373 second_id = self.repo.commit_ids[1]
372 commits = self.repo.get_commits(end_id=second_id)
374 commits = self.repo.get_commits(end_id=second_id)
373 assert isinstance(commits, CollectionGenerator)
375 assert isinstance(commits, CollectionGenerator)
374 commits = list(commits)
376 commits = list(commits)
375 assert commits[-1].raw_id == second_id
377 assert commits[-1].raw_id == second_id
376 assert len(commits) == 2
378 assert len(commits) == 2
377
379
378 def test_get_commits_respects_both_start_and_end(self):
380 def test_get_commits_respects_both_start_and_end(self):
379 second_id = self.repo.commit_ids[1]
381 second_id = self.repo.commit_ids[1]
380 third_id = self.repo.commit_ids[2]
382 third_id = self.repo.commit_ids[2]
381 commits = self.repo.get_commits(start_id=second_id, end_id=third_id)
383 commits = self.repo.get_commits(start_id=second_id, end_id=third_id)
382 assert isinstance(commits, CollectionGenerator)
384 assert isinstance(commits, CollectionGenerator)
383 commits = list(commits)
385 commits = list(commits)
384 assert len(commits) == 2
386 assert len(commits) == 2
385
387
386 def test_get_commits_on_empty_repo_raises_EmptyRepository_error(self):
388 def test_get_commits_on_empty_repo_raises_EmptyRepository_error(self):
387 repo_path = get_new_dir(str(time.time()))
389 repo_path = get_new_dir(str(time.time()))
388 repo = self.Backend(repo_path, create=True)
390 repo = self.Backend(repo_path, create=True)
389
391
390 with pytest.raises(EmptyRepositoryError):
392 with pytest.raises(EmptyRepositoryError):
391 list(repo.get_commits(start_id='foobar'))
393 list(repo.get_commits(start_id='foobar'))
392
394
393 def test_get_commits_respects_hidden(self):
395 def test_get_commits_respects_hidden(self):
394 commits = self.repo.get_commits(show_hidden=True)
396 commits = self.repo.get_commits(show_hidden=True)
395 assert isinstance(commits, CollectionGenerator)
397 assert isinstance(commits, CollectionGenerator)
396 assert len(commits) == 5
398 assert len(commits) == 5
397
399
398 def test_get_commits_includes_end_commit(self):
400 def test_get_commits_includes_end_commit(self):
399 second_id = self.repo.commit_ids[1]
401 second_id = self.repo.commit_ids[1]
400 commits = self.repo.get_commits(end_id=second_id)
402 commits = self.repo.get_commits(end_id=second_id)
401 assert isinstance(commits, CollectionGenerator)
403 assert isinstance(commits, CollectionGenerator)
402 assert len(commits) == 2
404 assert len(commits) == 2
403 commits = list(commits)
405 commits = list(commits)
404 assert commits[-1].raw_id == second_id
406 assert commits[-1].raw_id == second_id
405
407
406 def test_get_commits_respects_start_date(self):
408 def test_get_commits_respects_start_date(self):
407 start_date = datetime.datetime(2010, 1, 2)
409 start_date = datetime.datetime(2010, 1, 2)
408 commits = self.repo.get_commits(start_date=start_date)
410 commits = self.repo.get_commits(start_date=start_date)
409 assert isinstance(commits, CollectionGenerator)
411 assert isinstance(commits, CollectionGenerator)
410 # Should be 4 commits after 2010-01-02 00:00:00
412 # Should be 4 commits after 2010-01-02 00:00:00
411 assert len(commits) == 4
413 assert len(commits) == 4
412 for c in commits:
414 for c in commits:
413 assert c.date >= start_date
415 assert c.date >= start_date
414
416
415 def test_get_commits_respects_start_date_with_branch(self):
417 def test_get_commits_respects_start_date_with_branch(self):
416 start_date = datetime.datetime(2010, 1, 2)
418 start_date = datetime.datetime(2010, 1, 2)
417 commits = self.repo.get_commits(
419 commits = self.repo.get_commits(
418 start_date=start_date, branch_name=self.repo.DEFAULT_BRANCH_NAME)
420 start_date=start_date, branch_name=self.repo.DEFAULT_BRANCH_NAME)
419 assert isinstance(commits, CollectionGenerator)
421 assert isinstance(commits, CollectionGenerator)
420 # Should be 4 commits after 2010-01-02 00:00:00
422 # Should be 4 commits after 2010-01-02 00:00:00
421 assert len(commits) == 4
423 assert len(commits) == 4
422 for c in commits:
424 for c in commits:
423 assert c.date >= start_date
425 assert c.date >= start_date
424
426
425 def test_get_commits_respects_start_date_and_end_date(self):
427 def test_get_commits_respects_start_date_and_end_date(self):
426 start_date = datetime.datetime(2010, 1, 2)
428 start_date = datetime.datetime(2010, 1, 2)
427 end_date = datetime.datetime(2010, 1, 3)
429 end_date = datetime.datetime(2010, 1, 3)
428 commits = self.repo.get_commits(start_date=start_date,
430 commits = self.repo.get_commits(start_date=start_date,
429 end_date=end_date)
431 end_date=end_date)
430 assert isinstance(commits, CollectionGenerator)
432 assert isinstance(commits, CollectionGenerator)
431 assert len(commits) == 2
433 assert len(commits) == 2
432 for c in commits:
434 for c in commits:
433 assert c.date >= start_date
435 assert c.date >= start_date
434 assert c.date <= end_date
436 assert c.date <= end_date
435
437
436 def test_get_commits_respects_end_date(self):
438 def test_get_commits_respects_end_date(self):
437 end_date = datetime.datetime(2010, 1, 2)
439 end_date = datetime.datetime(2010, 1, 2)
438 commits = self.repo.get_commits(end_date=end_date)
440 commits = self.repo.get_commits(end_date=end_date)
439 assert isinstance(commits, CollectionGenerator)
441 assert isinstance(commits, CollectionGenerator)
440 assert len(commits) == 1
442 assert len(commits) == 1
441 for c in commits:
443 for c in commits:
442 assert c.date <= end_date
444 assert c.date <= end_date
443
445
444 def test_get_commits_respects_reverse(self):
446 def test_get_commits_respects_reverse(self):
445 commits = self.repo.get_commits() # no longer reverse support
447 commits = self.repo.get_commits() # no longer reverse support
446 assert isinstance(commits, CollectionGenerator)
448 assert isinstance(commits, CollectionGenerator)
447 assert len(commits) == 5
449 assert len(commits) == 5
448 commit_ids = reversed([c.raw_id for c in commits])
450 commit_ids = reversed([c.raw_id for c in commits])
449 assert list(commit_ids) == list(reversed(self.repo.commit_ids))
451 assert list(commit_ids) == list(reversed(self.repo.commit_ids))
450
452
451 def test_get_commits_slice_generator(self):
453 def test_get_commits_slice_generator(self):
452 commits = self.repo.get_commits(
454 commits = self.repo.get_commits(
453 branch_name=self.repo.DEFAULT_BRANCH_NAME)
455 branch_name=self.repo.DEFAULT_BRANCH_NAME)
454 assert isinstance(commits, CollectionGenerator)
456 assert isinstance(commits, CollectionGenerator)
455 commit_slice = list(commits[1:3])
457 commit_slice = list(commits[1:3])
456 assert len(commit_slice) == 2
458 assert len(commit_slice) == 2
457
459
458 def test_get_commits_raise_commitdoesnotexist_for_wrong_start(self):
460 def test_get_commits_raise_commitdoesnotexist_for_wrong_start(self):
459 with pytest.raises(CommitDoesNotExistError):
461 with pytest.raises(CommitDoesNotExistError):
460 list(self.repo.get_commits(start_id='foobar'))
462 list(self.repo.get_commits(start_id='foobar'))
461
463
462 def test_get_commits_raise_commitdoesnotexist_for_wrong_end(self):
464 def test_get_commits_raise_commitdoesnotexist_for_wrong_end(self):
463 with pytest.raises(CommitDoesNotExistError):
465 with pytest.raises(CommitDoesNotExistError):
464 list(self.repo.get_commits(end_id='foobar'))
466 list(self.repo.get_commits(end_id='foobar'))
465
467
466 def test_get_commits_raise_branchdoesnotexist_for_wrong_branch_name(self):
468 def test_get_commits_raise_branchdoesnotexist_for_wrong_branch_name(self):
467 with pytest.raises(BranchDoesNotExistError):
469 with pytest.raises(BranchDoesNotExistError):
468 list(self.repo.get_commits(branch_name='foobar'))
470 list(self.repo.get_commits(branch_name='foobar'))
469
471
470 def test_get_commits_raise_repositoryerror_for_wrong_start_end(self):
472 def test_get_commits_raise_repositoryerror_for_wrong_start_end(self):
471 start_id = self.repo.commit_ids[-1]
473 start_id = self.repo.commit_ids[-1]
472 end_id = self.repo.commit_ids[0]
474 end_id = self.repo.commit_ids[0]
473 with pytest.raises(RepositoryError):
475 with pytest.raises(RepositoryError):
474 list(self.repo.get_commits(start_id=start_id, end_id=end_id))
476 list(self.repo.get_commits(start_id=start_id, end_id=end_id))
475
477
476 def test_get_commits_raises_for_numerical_ids(self):
478 def test_get_commits_raises_for_numerical_ids(self):
477 with pytest.raises(TypeError):
479 with pytest.raises(TypeError):
478 self.repo.get_commits(start_id=1, end_id=2)
480 self.repo.get_commits(start_id=1, end_id=2)
479
481
480 def test_commit_equality(self):
482 def test_commit_equality(self):
481 commit1 = self.repo.get_commit(self.repo.commit_ids[0])
483 commit1 = self.repo.get_commit(self.repo.commit_ids[0])
482 commit2 = self.repo.get_commit(self.repo.commit_ids[1])
484 commit2 = self.repo.get_commit(self.repo.commit_ids[1])
483
485
484 assert commit1 == commit1
486 assert commit1 == commit1
485 assert commit2 == commit2
487 assert commit2 == commit2
486 assert commit1 != commit2
488 assert commit1 != commit2
487 assert commit2 != commit1
489 assert commit2 != commit1
488 assert commit1 != None
490 assert commit1 != None
489 assert None != commit1
491 assert None != commit1
490 assert 1 != commit1
492 assert 1 != commit1
491 assert 'string' != commit1
493 assert 'string' != commit1
492
494
493
495
494 @pytest.mark.parametrize("filename, expected", [
496 @pytest.mark.parametrize("filename, expected", [
495 ("README.rst", False),
497 ("README.rst", False),
496 ("README", True),
498 ("README", True),
497 ])
499 ])
498 def test_commit_is_link(vcsbackend, filename, expected):
500 def test_commit_is_link(vcsbackend, filename, expected):
499 commit = vcsbackend.repo.get_commit()
501 commit = vcsbackend.repo.get_commit()
500 link_status = commit.is_link(filename)
502 link_status = commit.is_link(filename)
501 assert link_status is expected
503 assert link_status is expected
502
504
503
505
506 @pytest.mark.usefixtures("vcs_repository_support")
504 class TestCommitsChanges(BackendTestMixin):
507 class TestCommitsChanges(BackendTestMixin):
505 recreate_repo_per_test = False
508 recreate_repo_per_test = False
506
509
507 @classmethod
510 @classmethod
508 def _get_commits(cls):
511 def _get_commits(cls):
509 return [
512 return [
510 {
513 {
511 'message': u'Initial',
514 'message': u'Initial',
512 'author': u'Joe Doe <joe.doe@example.com>',
515 'author': u'Joe Doe <joe.doe@example.com>',
513 'date': datetime.datetime(2010, 1, 1, 20),
516 'date': datetime.datetime(2010, 1, 1, 20),
514 'added': [
517 'added': [
515 FileNode('foo/bar', content='foo'),
518 FileNode('foo/bar', content='foo'),
516 FileNode('foo/bał', content='foo'),
519 FileNode('foo/bał', content='foo'),
517 FileNode('foobar', content='foo'),
520 FileNode('foobar', content='foo'),
518 FileNode('qwe', content='foo'),
521 FileNode('qwe', content='foo'),
519 ],
522 ],
520 },
523 },
521 {
524 {
522 'message': u'Massive changes',
525 'message': u'Massive changes',
523 'author': u'Joe Doe <joe.doe@example.com>',
526 'author': u'Joe Doe <joe.doe@example.com>',
524 'date': datetime.datetime(2010, 1, 1, 22),
527 'date': datetime.datetime(2010, 1, 1, 22),
525 'added': [FileNode('fallout', content='War never changes')],
528 'added': [FileNode('fallout', content='War never changes')],
526 'changed': [
529 'changed': [
527 FileNode('foo/bar', content='baz'),
530 FileNode('foo/bar', content='baz'),
528 FileNode('foobar', content='baz'),
531 FileNode('foobar', content='baz'),
529 ],
532 ],
530 'removed': [FileNode('qwe')],
533 'removed': [FileNode('qwe')],
531 },
534 },
532 ]
535 ]
533
536
534 def test_initial_commit(self, local_dt_to_utc):
537 def test_initial_commit(self, local_dt_to_utc):
535 commit = self.repo.get_commit(commit_idx=0)
538 commit = self.repo.get_commit(commit_idx=0)
536 assert set(commit.added) == set([
539 assert set(commit.added) == set([
537 commit.get_node('foo/bar'),
540 commit.get_node('foo/bar'),
538 commit.get_node('foo/bał'),
541 commit.get_node('foo/bał'),
539 commit.get_node('foobar'),
542 commit.get_node('foobar'),
540 commit.get_node('qwe'),
543 commit.get_node('qwe'),
541 ])
544 ])
542 assert set(commit.changed) == set()
545 assert set(commit.changed) == set()
543 assert set(commit.removed) == set()
546 assert set(commit.removed) == set()
544 assert set(commit.affected_files) == set(
547 assert set(commit.affected_files) == set(
545 ['foo/bar', 'foo/bał', 'foobar', 'qwe'])
548 ['foo/bar', 'foo/bał', 'foobar', 'qwe'])
546 assert commit.date == local_dt_to_utc(
549 assert commit.date == local_dt_to_utc(
547 datetime.datetime(2010, 1, 1, 20, 0))
550 datetime.datetime(2010, 1, 1, 20, 0))
548
551
549 def test_head_added(self):
552 def test_head_added(self):
550 commit = self.repo.get_commit()
553 commit = self.repo.get_commit()
551 assert isinstance(commit.added, AddedFileNodesGenerator)
554 assert isinstance(commit.added, AddedFileNodesGenerator)
552 assert set(commit.added) == set([commit.get_node('fallout')])
555 assert set(commit.added) == set([commit.get_node('fallout')])
553 assert isinstance(commit.changed, ChangedFileNodesGenerator)
556 assert isinstance(commit.changed, ChangedFileNodesGenerator)
554 assert set(commit.changed) == set([
557 assert set(commit.changed) == set([
555 commit.get_node('foo/bar'),
558 commit.get_node('foo/bar'),
556 commit.get_node('foobar'),
559 commit.get_node('foobar'),
557 ])
560 ])
558 assert isinstance(commit.removed, RemovedFileNodesGenerator)
561 assert isinstance(commit.removed, RemovedFileNodesGenerator)
559 assert len(commit.removed) == 1
562 assert len(commit.removed) == 1
560 assert list(commit.removed)[0].path == 'qwe'
563 assert list(commit.removed)[0].path == 'qwe'
561
564
562 def test_get_filemode(self):
565 def test_get_filemode(self):
563 commit = self.repo.get_commit()
566 commit = self.repo.get_commit()
564 assert FILEMODE_DEFAULT == commit.get_file_mode('foo/bar')
567 assert FILEMODE_DEFAULT == commit.get_file_mode('foo/bar')
565
568
566 def test_get_filemode_non_ascii(self):
569 def test_get_filemode_non_ascii(self):
567 commit = self.repo.get_commit()
570 commit = self.repo.get_commit()
568 assert FILEMODE_DEFAULT == commit.get_file_mode('foo/bał')
571 assert FILEMODE_DEFAULT == commit.get_file_mode('foo/bał')
569 assert FILEMODE_DEFAULT == commit.get_file_mode(u'foo/bał')
572 assert FILEMODE_DEFAULT == commit.get_file_mode(u'foo/bał')
570
573
571 def test_get_file_history(self):
574 def test_get_file_history(self):
572 commit = self.repo.get_commit()
575 commit = self.repo.get_commit()
573 history = commit.get_file_history('foo/bar')
576 history = commit.get_file_history('foo/bar')
574 assert len(history) == 2
577 assert len(history) == 2
575
578
576 def test_get_file_history_with_limit(self):
579 def test_get_file_history_with_limit(self):
577 commit = self.repo.get_commit()
580 commit = self.repo.get_commit()
578 history = commit.get_file_history('foo/bar', limit=1)
581 history = commit.get_file_history('foo/bar', limit=1)
579 assert len(history) == 1
582 assert len(history) == 1
580
583
581 def test_get_file_history_first_commit(self):
584 def test_get_file_history_first_commit(self):
582 commit = self.repo[0]
585 commit = self.repo[0]
583 history = commit.get_file_history('foo/bar')
586 history = commit.get_file_history('foo/bar')
584 assert len(history) == 1
587 assert len(history) == 1
585
588
586
589
587 def assert_text_equal(expected, given):
590 def assert_text_equal(expected, given):
588 assert expected == given
591 assert expected == given
589 assert isinstance(expected, unicode)
592 assert isinstance(expected, unicode)
590 assert isinstance(given, unicode)
593 assert isinstance(given, unicode)
@@ -1,546 +1,548 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import datetime
21 import datetime
22 import pytest
22 import pytest
23
23
24 from rhodecode.lib.vcs.nodes import FileNode
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 class TestGetDiffValidation:
28 class TestGetDiffValidation:
29
29
30 def test_raises_on_string_input(self, vcsbackend):
30 def test_raises_on_string_input(self, vcsbackend):
31 repo = vcsbackend.repo
31 repo = vcsbackend.repo
32 with pytest.raises(TypeError):
32 with pytest.raises(TypeError):
33 repo.get_diff("1", "2")
33 repo.get_diff("1", "2")
34
34
35 def test_raises_if_commits_not_of_this_repository(self, vcsbackend):
35 def test_raises_if_commits_not_of_this_repository(self, vcsbackend):
36 repo = vcsbackend.repo
36 repo = vcsbackend.repo
37 target_repo = vcsbackend.create_repo(number_of_commits=1)
37 target_repo = vcsbackend.create_repo(number_of_commits=1)
38 repo_commit = repo[0]
38 repo_commit = repo[0]
39 wrong_commit = target_repo[0]
39 wrong_commit = target_repo[0]
40 with pytest.raises(ValueError):
40 with pytest.raises(ValueError):
41 repo.get_diff(repo_commit, wrong_commit)
41 repo.get_diff(repo_commit, wrong_commit)
42
42
43 def test_allows_empty_commit(self, vcsbackend):
43 def test_allows_empty_commit(self, vcsbackend):
44 repo = vcsbackend.repo
44 repo = vcsbackend.repo
45 commit = repo[0]
45 commit = repo[0]
46 repo.get_diff(repo.EMPTY_COMMIT, commit)
46 repo.get_diff(repo.EMPTY_COMMIT, commit)
47
47
48 def test_raise_if_both_commits_are_empty(self, vcsbackend):
48 def test_raise_if_both_commits_are_empty(self, vcsbackend):
49 repo = vcsbackend.repo
49 repo = vcsbackend.repo
50 empty_commit = repo.EMPTY_COMMIT
50 empty_commit = repo.EMPTY_COMMIT
51 with pytest.raises(ValueError):
51 with pytest.raises(ValueError):
52 repo.get_diff(empty_commit, empty_commit)
52 repo.get_diff(empty_commit, empty_commit)
53
53
54 def test_supports_path1_parameter(self, vcsbackend):
54 def test_supports_path1_parameter(self, vcsbackend):
55 repo = vcsbackend.repo
55 repo = vcsbackend.repo
56 commit = repo[1]
56 commit = repo[1]
57 repo.get_diff(
57 repo.get_diff(
58 repo.EMPTY_COMMIT, commit,
58 repo.EMPTY_COMMIT, commit,
59 path='vcs/__init__.py', path1='vcs/__init__.py')
59 path='vcs/__init__.py', path1='vcs/__init__.py')
60
60
61 @pytest.mark.backends("git", "hg")
61 @pytest.mark.backends("git", "hg")
62 def test_raises_value_error_if_paths_not_supported(self, vcsbackend):
62 def test_raises_value_error_if_paths_not_supported(self, vcsbackend):
63 repo = vcsbackend.repo
63 repo = vcsbackend.repo
64 commit = repo[1]
64 commit = repo[1]
65 with pytest.raises(ValueError):
65 with pytest.raises(ValueError):
66 repo.get_diff(
66 repo.get_diff(
67 repo.EMPTY_COMMIT, commit,
67 repo.EMPTY_COMMIT, commit,
68 path='trunk/example.py', path1='branches/argparse/example.py')
68 path='trunk/example.py', path1='branches/argparse/example.py')
69
69
70
70
71 @pytest.mark.usefixtures("vcs_repository_support")
71 class TestRepositoryGetDiff(BackendTestMixin):
72 class TestRepositoryGetDiff(BackendTestMixin):
72
73
73 recreate_repo_per_test = False
74 recreate_repo_per_test = False
74
75
75 @classmethod
76 @classmethod
76 def _get_commits(cls):
77 def _get_commits(cls):
77 commits = [
78 commits = [
78 {
79 {
79 'message': 'Initial commit',
80 'message': 'Initial commit',
80 'author': 'Joe Doe <joe.doe@example.com>',
81 'author': 'Joe Doe <joe.doe@example.com>',
81 'date': datetime.datetime(2010, 1, 1, 20),
82 'date': datetime.datetime(2010, 1, 1, 20),
82 'added': [
83 'added': [
83 FileNode('foobar', content='foobar'),
84 FileNode('foobar', content='foobar'),
84 FileNode('foobar2', content='foobar2'),
85 FileNode('foobar2', content='foobar2'),
85 ],
86 ],
86 },
87 },
87 {
88 {
88 'message': 'Changed foobar, added foobar3',
89 'message': 'Changed foobar, added foobar3',
89 'author': 'Jane Doe <jane.doe@example.com>',
90 'author': 'Jane Doe <jane.doe@example.com>',
90 'date': datetime.datetime(2010, 1, 1, 21),
91 'date': datetime.datetime(2010, 1, 1, 21),
91 'added': [
92 'added': [
92 FileNode('foobar3', content='foobar3'),
93 FileNode('foobar3', content='foobar3'),
93 ],
94 ],
94 'changed': [
95 'changed': [
95 FileNode('foobar', 'FOOBAR'),
96 FileNode('foobar', 'FOOBAR'),
96 ],
97 ],
97 },
98 },
98 {
99 {
99 'message': 'Removed foobar, changed foobar3',
100 'message': 'Removed foobar, changed foobar3',
100 'author': 'Jane Doe <jane.doe@example.com>',
101 'author': 'Jane Doe <jane.doe@example.com>',
101 'date': datetime.datetime(2010, 1, 1, 22),
102 'date': datetime.datetime(2010, 1, 1, 22),
102 'changed': [
103 'changed': [
103 FileNode('foobar3', content='FOOBAR\nFOOBAR\nFOOBAR\n'),
104 FileNode('foobar3', content='FOOBAR\nFOOBAR\nFOOBAR\n'),
104 ],
105 ],
105 'removed': [FileNode('foobar')],
106 'removed': [FileNode('foobar')],
106 },
107 },
107 {
108 {
108 'message': 'Whitespace changes',
109 'message': 'Whitespace changes',
109 'author': 'Jane Doe <jane.doe@example.com>',
110 'author': 'Jane Doe <jane.doe@example.com>',
110 'date': datetime.datetime(2010, 1, 1, 23),
111 'date': datetime.datetime(2010, 1, 1, 23),
111 'changed': [
112 'changed': [
112 FileNode('foobar3', content='FOOBAR \nFOOBAR\nFOOBAR\n'),
113 FileNode('foobar3', content='FOOBAR \nFOOBAR\nFOOBAR\n'),
113 ],
114 ],
114 },
115 },
115 ]
116 ]
116 return commits
117 return commits
117
118
118 def test_initial_commit_diff(self):
119 def test_initial_commit_diff(self):
119 initial_commit = self.repo[0]
120 initial_commit = self.repo[0]
120 diff = self.repo.get_diff(self.repo.EMPTY_COMMIT, initial_commit)
121 diff = self.repo.get_diff(self.repo.EMPTY_COMMIT, initial_commit)
121 assert diff.raw == self.first_commit_diffs[self.repo.alias]
122 assert diff.raw == self.first_commit_diffs[self.repo.alias]
122
123
123 def test_second_commit_diff(self):
124 def test_second_commit_diff(self):
124 diff = self.repo.get_diff(self.repo[0], self.repo[1])
125 diff = self.repo.get_diff(self.repo[0], self.repo[1])
125 assert diff.raw == self.second_commit_diffs[self.repo.alias]
126 assert diff.raw == self.second_commit_diffs[self.repo.alias]
126
127
127 def test_third_commit_diff(self):
128 def test_third_commit_diff(self):
128 diff = self.repo.get_diff(self.repo[1], self.repo[2])
129 diff = self.repo.get_diff(self.repo[1], self.repo[2])
129 assert diff.raw == self.third_commit_diffs[self.repo.alias]
130 assert diff.raw == self.third_commit_diffs[self.repo.alias]
130
131
131 def test_ignore_whitespace(self):
132 def test_ignore_whitespace(self):
132 diff = self.repo.get_diff(
133 diff = self.repo.get_diff(
133 self.repo[2], self.repo[3], ignore_whitespace=True)
134 self.repo[2], self.repo[3], ignore_whitespace=True)
134 assert '@@' not in diff.raw
135 assert '@@' not in diff.raw
135
136
136 def test_only_one_file(self):
137 def test_only_one_file(self):
137 diff = self.repo.get_diff(
138 diff = self.repo.get_diff(
138 self.repo.EMPTY_COMMIT, self.repo[0], path='foobar')
139 self.repo.EMPTY_COMMIT, self.repo[0], path='foobar')
139 assert 'foobar2' not in diff.raw
140 assert 'foobar2' not in diff.raw
140
141
141 def test_context_parameter(self):
142 def test_context_parameter(self):
142 first_commit = self.repo.get_commit(commit_idx=0)
143 first_commit = self.repo.get_commit(commit_idx=0)
143 diff = self.repo.get_diff(
144 diff = self.repo.get_diff(
144 self.repo.EMPTY_COMMIT, first_commit, context=2)
145 self.repo.EMPTY_COMMIT, first_commit, context=2)
145 assert diff.raw == self.first_commit_diffs[self.repo.alias]
146 assert diff.raw == self.first_commit_diffs[self.repo.alias]
146
147
147 def test_context_only_one_file(self):
148 def test_context_only_one_file(self):
148 diff = self.repo.get_diff(
149 diff = self.repo.get_diff(
149 self.repo.EMPTY_COMMIT, self.repo[0], path='foobar', context=2)
150 self.repo.EMPTY_COMMIT, self.repo[0], path='foobar', context=2)
150 assert diff.raw == self.first_commit_one_file[self.repo.alias]
151 assert diff.raw == self.first_commit_one_file[self.repo.alias]
151
152
152 first_commit_diffs = {
153 first_commit_diffs = {
153 'git': r"""diff --git a/foobar b/foobar
154 'git': r"""diff --git a/foobar b/foobar
154 new file mode 100644
155 new file mode 100644
155 index 0000000000000000000000000000000000000000..f6ea0495187600e7b2288c8ac19c5886383a4632
156 index 0000000000000000000000000000000000000000..f6ea0495187600e7b2288c8ac19c5886383a4632
156 --- /dev/null
157 --- /dev/null
157 +++ b/foobar
158 +++ b/foobar
158 @@ -0,0 +1 @@
159 @@ -0,0 +1 @@
159 +foobar
160 +foobar
160 \ No newline at end of file
161 \ No newline at end of file
161 diff --git a/foobar2 b/foobar2
162 diff --git a/foobar2 b/foobar2
162 new file mode 100644
163 new file mode 100644
163 index 0000000000000000000000000000000000000000..e8c9d6b98e3dce993a464935e1a53f50b56a3783
164 index 0000000000000000000000000000000000000000..e8c9d6b98e3dce993a464935e1a53f50b56a3783
164 --- /dev/null
165 --- /dev/null
165 +++ b/foobar2
166 +++ b/foobar2
166 @@ -0,0 +1 @@
167 @@ -0,0 +1 @@
167 +foobar2
168 +foobar2
168 \ No newline at end of file
169 \ No newline at end of file
169 """,
170 """,
170 'hg': r"""diff --git a/foobar b/foobar
171 'hg': r"""diff --git a/foobar b/foobar
171 new file mode 100644
172 new file mode 100644
172 --- /dev/null
173 --- /dev/null
173 +++ b/foobar
174 +++ b/foobar
174 @@ -0,0 +1,1 @@
175 @@ -0,0 +1,1 @@
175 +foobar
176 +foobar
176 \ No newline at end of file
177 \ No newline at end of file
177 diff --git a/foobar2 b/foobar2
178 diff --git a/foobar2 b/foobar2
178 new file mode 100644
179 new file mode 100644
179 --- /dev/null
180 --- /dev/null
180 +++ b/foobar2
181 +++ b/foobar2
181 @@ -0,0 +1,1 @@
182 @@ -0,0 +1,1 @@
182 +foobar2
183 +foobar2
183 \ No newline at end of file
184 \ No newline at end of file
184 """,
185 """,
185 'svn': """Index: foobar
186 'svn': """Index: foobar
186 ===================================================================
187 ===================================================================
187 diff --git a/foobar b/foobar
188 diff --git a/foobar b/foobar
188 new file mode 10644
189 new file mode 10644
189 --- /dev/null\t(revision 0)
190 --- /dev/null\t(revision 0)
190 +++ b/foobar\t(revision 1)
191 +++ b/foobar\t(revision 1)
191 @@ -0,0 +1 @@
192 @@ -0,0 +1 @@
192 +foobar
193 +foobar
193 \\ No newline at end of file
194 \\ No newline at end of file
194 Index: foobar2
195 Index: foobar2
195 ===================================================================
196 ===================================================================
196 diff --git a/foobar2 b/foobar2
197 diff --git a/foobar2 b/foobar2
197 new file mode 10644
198 new file mode 10644
198 --- /dev/null\t(revision 0)
199 --- /dev/null\t(revision 0)
199 +++ b/foobar2\t(revision 1)
200 +++ b/foobar2\t(revision 1)
200 @@ -0,0 +1 @@
201 @@ -0,0 +1 @@
201 +foobar2
202 +foobar2
202 \\ No newline at end of file
203 \\ No newline at end of file
203 """,
204 """,
204 }
205 }
205
206
206 second_commit_diffs = {
207 second_commit_diffs = {
207 'git': r"""diff --git a/foobar b/foobar
208 'git': r"""diff --git a/foobar b/foobar
208 index f6ea0495187600e7b2288c8ac19c5886383a4632..389865bb681b358c9b102d79abd8d5f941e96551 100644
209 index f6ea0495187600e7b2288c8ac19c5886383a4632..389865bb681b358c9b102d79abd8d5f941e96551 100644
209 --- a/foobar
210 --- a/foobar
210 +++ b/foobar
211 +++ b/foobar
211 @@ -1 +1 @@
212 @@ -1 +1 @@
212 -foobar
213 -foobar
213 \ No newline at end of file
214 \ No newline at end of file
214 +FOOBAR
215 +FOOBAR
215 \ No newline at end of file
216 \ No newline at end of file
216 diff --git a/foobar3 b/foobar3
217 diff --git a/foobar3 b/foobar3
217 new file mode 100644
218 new file mode 100644
218 index 0000000000000000000000000000000000000000..c11c37d41d33fb47741cff93fa5f9d798c1535b0
219 index 0000000000000000000000000000000000000000..c11c37d41d33fb47741cff93fa5f9d798c1535b0
219 --- /dev/null
220 --- /dev/null
220 +++ b/foobar3
221 +++ b/foobar3
221 @@ -0,0 +1 @@
222 @@ -0,0 +1 @@
222 +foobar3
223 +foobar3
223 \ No newline at end of file
224 \ No newline at end of file
224 """,
225 """,
225 'hg': r"""diff --git a/foobar b/foobar
226 'hg': r"""diff --git a/foobar b/foobar
226 --- a/foobar
227 --- a/foobar
227 +++ b/foobar
228 +++ b/foobar
228 @@ -1,1 +1,1 @@
229 @@ -1,1 +1,1 @@
229 -foobar
230 -foobar
230 \ No newline at end of file
231 \ No newline at end of file
231 +FOOBAR
232 +FOOBAR
232 \ No newline at end of file
233 \ No newline at end of file
233 diff --git a/foobar3 b/foobar3
234 diff --git a/foobar3 b/foobar3
234 new file mode 100644
235 new file mode 100644
235 --- /dev/null
236 --- /dev/null
236 +++ b/foobar3
237 +++ b/foobar3
237 @@ -0,0 +1,1 @@
238 @@ -0,0 +1,1 @@
238 +foobar3
239 +foobar3
239 \ No newline at end of file
240 \ No newline at end of file
240 """,
241 """,
241 'svn': """Index: foobar
242 'svn': """Index: foobar
242 ===================================================================
243 ===================================================================
243 diff --git a/foobar b/foobar
244 diff --git a/foobar b/foobar
244 --- a/foobar\t(revision 1)
245 --- a/foobar\t(revision 1)
245 +++ b/foobar\t(revision 2)
246 +++ b/foobar\t(revision 2)
246 @@ -1 +1 @@
247 @@ -1 +1 @@
247 -foobar
248 -foobar
248 \\ No newline at end of file
249 \\ No newline at end of file
249 +FOOBAR
250 +FOOBAR
250 \\ No newline at end of file
251 \\ No newline at end of file
251 Index: foobar3
252 Index: foobar3
252 ===================================================================
253 ===================================================================
253 diff --git a/foobar3 b/foobar3
254 diff --git a/foobar3 b/foobar3
254 new file mode 10644
255 new file mode 10644
255 --- /dev/null\t(revision 0)
256 --- /dev/null\t(revision 0)
256 +++ b/foobar3\t(revision 2)
257 +++ b/foobar3\t(revision 2)
257 @@ -0,0 +1 @@
258 @@ -0,0 +1 @@
258 +foobar3
259 +foobar3
259 \\ No newline at end of file
260 \\ No newline at end of file
260 """,
261 """,
261 }
262 }
262
263
263 third_commit_diffs = {
264 third_commit_diffs = {
264 'git': r"""diff --git a/foobar b/foobar
265 'git': r"""diff --git a/foobar b/foobar
265 deleted file mode 100644
266 deleted file mode 100644
266 index 389865bb681b358c9b102d79abd8d5f941e96551..0000000000000000000000000000000000000000
267 index 389865bb681b358c9b102d79abd8d5f941e96551..0000000000000000000000000000000000000000
267 --- a/foobar
268 --- a/foobar
268 +++ /dev/null
269 +++ /dev/null
269 @@ -1 +0,0 @@
270 @@ -1 +0,0 @@
270 -FOOBAR
271 -FOOBAR
271 \ No newline at end of file
272 \ No newline at end of file
272 diff --git a/foobar3 b/foobar3
273 diff --git a/foobar3 b/foobar3
273 index c11c37d41d33fb47741cff93fa5f9d798c1535b0..f9324477362684ff692aaf5b9a81e01b9e9a671c 100644
274 index c11c37d41d33fb47741cff93fa5f9d798c1535b0..f9324477362684ff692aaf5b9a81e01b9e9a671c 100644
274 --- a/foobar3
275 --- a/foobar3
275 +++ b/foobar3
276 +++ b/foobar3
276 @@ -1 +1,3 @@
277 @@ -1 +1,3 @@
277 -foobar3
278 -foobar3
278 \ No newline at end of file
279 \ No newline at end of file
279 +FOOBAR
280 +FOOBAR
280 +FOOBAR
281 +FOOBAR
281 +FOOBAR
282 +FOOBAR
282 """,
283 """,
283 'hg': r"""diff --git a/foobar b/foobar
284 'hg': r"""diff --git a/foobar b/foobar
284 deleted file mode 100644
285 deleted file mode 100644
285 --- a/foobar
286 --- a/foobar
286 +++ /dev/null
287 +++ /dev/null
287 @@ -1,1 +0,0 @@
288 @@ -1,1 +0,0 @@
288 -FOOBAR
289 -FOOBAR
289 \ No newline at end of file
290 \ No newline at end of file
290 diff --git a/foobar3 b/foobar3
291 diff --git a/foobar3 b/foobar3
291 --- a/foobar3
292 --- a/foobar3
292 +++ b/foobar3
293 +++ b/foobar3
293 @@ -1,1 +1,3 @@
294 @@ -1,1 +1,3 @@
294 -foobar3
295 -foobar3
295 \ No newline at end of file
296 \ No newline at end of file
296 +FOOBAR
297 +FOOBAR
297 +FOOBAR
298 +FOOBAR
298 +FOOBAR
299 +FOOBAR
299 """,
300 """,
300 'svn': """Index: foobar
301 'svn': """Index: foobar
301 ===================================================================
302 ===================================================================
302 diff --git a/foobar b/foobar
303 diff --git a/foobar b/foobar
303 deleted file mode 10644
304 deleted file mode 10644
304 --- a/foobar\t(revision 2)
305 --- a/foobar\t(revision 2)
305 +++ /dev/null\t(revision 3)
306 +++ /dev/null\t(revision 3)
306 @@ -1 +0,0 @@
307 @@ -1 +0,0 @@
307 -FOOBAR
308 -FOOBAR
308 \\ No newline at end of file
309 \\ No newline at end of file
309 Index: foobar3
310 Index: foobar3
310 ===================================================================
311 ===================================================================
311 diff --git a/foobar3 b/foobar3
312 diff --git a/foobar3 b/foobar3
312 --- a/foobar3\t(revision 2)
313 --- a/foobar3\t(revision 2)
313 +++ b/foobar3\t(revision 3)
314 +++ b/foobar3\t(revision 3)
314 @@ -1 +1,3 @@
315 @@ -1 +1,3 @@
315 -foobar3
316 -foobar3
316 \\ No newline at end of file
317 \\ No newline at end of file
317 +FOOBAR
318 +FOOBAR
318 +FOOBAR
319 +FOOBAR
319 +FOOBAR
320 +FOOBAR
320 """,
321 """,
321 }
322 }
322
323
323 first_commit_one_file = {
324 first_commit_one_file = {
324 'git': r"""diff --git a/foobar b/foobar
325 'git': r"""diff --git a/foobar b/foobar
325 new file mode 100644
326 new file mode 100644
326 index 0000000000000000000000000000000000000000..f6ea0495187600e7b2288c8ac19c5886383a4632
327 index 0000000000000000000000000000000000000000..f6ea0495187600e7b2288c8ac19c5886383a4632
327 --- /dev/null
328 --- /dev/null
328 +++ b/foobar
329 +++ b/foobar
329 @@ -0,0 +1 @@
330 @@ -0,0 +1 @@
330 +foobar
331 +foobar
331 \ No newline at end of file
332 \ No newline at end of file
332 """,
333 """,
333 'hg': r"""diff --git a/foobar b/foobar
334 'hg': r"""diff --git a/foobar b/foobar
334 new file mode 100644
335 new file mode 100644
335 --- /dev/null
336 --- /dev/null
336 +++ b/foobar
337 +++ b/foobar
337 @@ -0,0 +1,1 @@
338 @@ -0,0 +1,1 @@
338 +foobar
339 +foobar
339 \ No newline at end of file
340 \ No newline at end of file
340 """,
341 """,
341 'svn': """Index: foobar
342 'svn': """Index: foobar
342 ===================================================================
343 ===================================================================
343 diff --git a/foobar b/foobar
344 diff --git a/foobar b/foobar
344 new file mode 10644
345 new file mode 10644
345 --- /dev/null\t(revision 0)
346 --- /dev/null\t(revision 0)
346 +++ b/foobar\t(revision 1)
347 +++ b/foobar\t(revision 1)
347 @@ -0,0 +1 @@
348 @@ -0,0 +1 @@
348 +foobar
349 +foobar
349 \\ No newline at end of file
350 \\ No newline at end of file
350 """,
351 """,
351 }
352 }
352
353
353
354
354 class TestSvnGetDiff:
355 class TestSvnGetDiff:
355
356
356 @pytest.mark.parametrize('path, path1', [
357 @pytest.mark.parametrize('path, path1', [
357 ('trunk/example.py', 'tags/v0.2/example.py'),
358 ('trunk/example.py', 'tags/v0.2/example.py'),
358 ('trunk', 'tags/v0.2')
359 ('trunk', 'tags/v0.2')
359 ], ids=['file', 'dir'])
360 ], ids=['file', 'dir'])
360 def test_diff_to_tagged_version(self, vcsbackend_svn, path, path1):
361 def test_diff_to_tagged_version(self, vcsbackend_svn, path, path1):
361 repo = vcsbackend_svn['svn-simple-layout']
362 repo = vcsbackend_svn['svn-simple-layout']
362 commit1 = repo[-2]
363 commit1 = repo[-2]
363 commit2 = repo[-1]
364 commit2 = repo[-1]
364 diff = repo.get_diff(commit1, commit2, path=path, path1=path1)
365 diff = repo.get_diff(commit1, commit2, path=path, path1=path1)
365 assert diff.raw == self.expected_diff_v_0_2
366 assert diff.raw == self.expected_diff_v_0_2
366
367
367 expected_diff_v_0_2 = '''Index: example.py
368 expected_diff_v_0_2 = '''Index: example.py
368 ===================================================================
369 ===================================================================
369 diff --git a/example.py b/example.py
370 diff --git a/example.py b/example.py
370 --- a/example.py\t(revision 25)
371 --- a/example.py\t(revision 25)
371 +++ b/example.py\t(revision 26)
372 +++ b/example.py\t(revision 26)
372 @@ -7,8 +7,12 @@
373 @@ -7,8 +7,12 @@
373
374
374 @click.command()
375 @click.command()
375 def main():
376 def main():
376 + """
377 + """
377 + Will print out a useful message on invocation.
378 + Will print out a useful message on invocation.
378 + """
379 + """
379 click.echo("Hello world!")
380 click.echo("Hello world!")
380
381
381
382
382 +# Main entry point
383 +# Main entry point
383 if __name__ == '__main__':
384 if __name__ == '__main__':
384 main()
385 main()
385 '''
386 '''
386
387
387 def test_diff_of_moved_directory(self, vcsbackend_svn):
388 def test_diff_of_moved_directory(self, vcsbackend_svn):
388 repo = vcsbackend_svn['svn-move-directory']
389 repo = vcsbackend_svn['svn-move-directory']
389 diff = repo.get_diff(repo[0], repo[1])
390 diff = repo.get_diff(repo[0], repo[1])
390 # TODO: johbo: Think about supporting svn directory nodes
391 # TODO: johbo: Think about supporting svn directory nodes
391 # a little bit better, source is here like a file
392 # a little bit better, source is here like a file
392 expected_diff = """Index: source
393 expected_diff = """Index: source
393 ===================================================================
394 ===================================================================
394 diff --git a/source b/source
395 diff --git a/source b/source
395 deleted file mode 10644
396 deleted file mode 10644
396 --- a/source\t(revision 1)
397 --- a/source\t(revision 1)
397 +++ /dev/null\t(revision 2)
398 +++ /dev/null\t(revision 2)
398 Index: target/file
399 Index: target/file
399 ===================================================================
400 ===================================================================
400 diff --git a/target/file b/target/file
401 diff --git a/target/file b/target/file
401 new file mode 10644
402 new file mode 10644
402 --- /dev/null\t(revision 0)
403 --- /dev/null\t(revision 0)
403 +++ b/target/file\t(revision 2)
404 +++ b/target/file\t(revision 2)
404 """
405 """
405 assert diff.raw == expected_diff
406 assert diff.raw == expected_diff
406
407
407
408
409 @pytest.mark.usefixtures("vcs_repository_support")
408 class TestGetDiffBinary(BackendTestMixin):
410 class TestGetDiffBinary(BackendTestMixin):
409
411
410 recreate_repo_per_test = False
412 recreate_repo_per_test = False
411
413
412 # Note: "Fake" PNG files, has the correct magic as prefix
414 # Note: "Fake" PNG files, has the correct magic as prefix
413 BINARY = """\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00"""
415 BINARY = """\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00"""
414 BINARY2 = """\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x01\x00\x00"""
416 BINARY2 = """\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x01\x00\x00"""
415
417
416 @staticmethod
418 @staticmethod
417 def _get_commits():
419 def _get_commits():
418 commits = [
420 commits = [
419 {
421 {
420 'message': 'Add binary file image.png',
422 'message': 'Add binary file image.png',
421 'author': 'Joe Doe <joe.deo@example.com>',
423 'author': 'Joe Doe <joe.deo@example.com>',
422 'date': datetime.datetime(2010, 1, 1, 20),
424 'date': datetime.datetime(2010, 1, 1, 20),
423 'added': [
425 'added': [
424 FileNode('image.png', content=TestGetDiffBinary.BINARY),
426 FileNode('image.png', content=TestGetDiffBinary.BINARY),
425 ]},
427 ]},
426 {
428 {
427 'message': 'Modify image.png',
429 'message': 'Modify image.png',
428 'author': 'Joe Doe <joe.deo@example.com>',
430 'author': 'Joe Doe <joe.deo@example.com>',
429 'date': datetime.datetime(2010, 1, 1, 21),
431 'date': datetime.datetime(2010, 1, 1, 21),
430 'changed': [
432 'changed': [
431 FileNode('image.png', content=TestGetDiffBinary.BINARY2),
433 FileNode('image.png', content=TestGetDiffBinary.BINARY2),
432 ]},
434 ]},
433 {
435 {
434 'message': 'Remove image.png',
436 'message': 'Remove image.png',
435 'author': 'Joe Doe <joe.deo@example.com>',
437 'author': 'Joe Doe <joe.deo@example.com>',
436 'date': datetime.datetime(2010, 1, 1, 21),
438 'date': datetime.datetime(2010, 1, 1, 21),
437 'removed': [
439 'removed': [
438 FileNode('image.png'),
440 FileNode('image.png'),
439 ]},
441 ]},
440 ]
442 ]
441 return commits
443 return commits
442
444
443 def test_add_a_binary_file(self):
445 def test_add_a_binary_file(self):
444 diff = self.repo.get_diff(self.repo.EMPTY_COMMIT, self.repo[0])
446 diff = self.repo.get_diff(self.repo.EMPTY_COMMIT, self.repo[0])
445
447
446 expected = {
448 expected = {
447 'git': """diff --git a/image.png b/image.png
449 'git': """diff --git a/image.png b/image.png
448 new file mode 100644
450 new file mode 100644
449 index 0000000000000000000000000000000000000000..28380fd4a25c58be1b68b523ba2a314f4459ee9c
451 index 0000000000000000000000000000000000000000..28380fd4a25c58be1b68b523ba2a314f4459ee9c
450 GIT binary patch
452 GIT binary patch
451 literal 19
453 literal 19
452 YcmeAS@N?(olHy`uVBq!ia0vp^03%2O-T(jq
454 YcmeAS@N?(olHy`uVBq!ia0vp^03%2O-T(jq
453
455
454 literal 0
456 literal 0
455 HcmV?d00001
457 HcmV?d00001
456
458
457 """,
459 """,
458 'hg': """diff --git a/image.png b/image.png
460 'hg': """diff --git a/image.png b/image.png
459 new file mode 100644
461 new file mode 100644
460 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..28380fd4a25c58be1b68b523ba2a314f4459ee9c
462 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..28380fd4a25c58be1b68b523ba2a314f4459ee9c
461 GIT binary patch
463 GIT binary patch
462 literal 19
464 literal 19
463 Yc%17D@N?(olHy`uVBq!ia0vp^03%2O-T(jq
465 Yc%17D@N?(olHy`uVBq!ia0vp^03%2O-T(jq
464
466
465 """,
467 """,
466 'svn': """===================================================================
468 'svn': """===================================================================
467 Cannot display: file marked as a binary type.
469 Cannot display: file marked as a binary type.
468 svn:mime-type = application/octet-stream
470 svn:mime-type = application/octet-stream
469 Index: image.png
471 Index: image.png
470 ===================================================================
472 ===================================================================
471 diff --git a/image.png b/image.png
473 diff --git a/image.png b/image.png
472 new file mode 10644
474 new file mode 10644
473 --- /dev/null\t(revision 0)
475 --- /dev/null\t(revision 0)
474 +++ b/image.png\t(revision 1)
476 +++ b/image.png\t(revision 1)
475 """,
477 """,
476 }
478 }
477 assert diff.raw == expected[self.repo.alias]
479 assert diff.raw == expected[self.repo.alias]
478
480
479 def test_update_a_binary_file(self):
481 def test_update_a_binary_file(self):
480 diff = self.repo.get_diff(self.repo[0], self.repo[1])
482 diff = self.repo.get_diff(self.repo[0], self.repo[1])
481
483
482 expected = {
484 expected = {
483 'git': """diff --git a/image.png b/image.png
485 'git': """diff --git a/image.png b/image.png
484 index 28380fd4a25c58be1b68b523ba2a314f4459ee9c..1008a77cd372386a1c24fbd96019333f67ad0065 100644
486 index 28380fd4a25c58be1b68b523ba2a314f4459ee9c..1008a77cd372386a1c24fbd96019333f67ad0065 100644
485 GIT binary patch
487 GIT binary patch
486 literal 19
488 literal 19
487 acmeAS@N?(olHy`uVBq!ia0y~$U;qFkO9I~j
489 acmeAS@N?(olHy`uVBq!ia0y~$U;qFkO9I~j
488
490
489 literal 19
491 literal 19
490 YcmeAS@N?(olHy`uVBq!ia0vp^03%2O-T(jq
492 YcmeAS@N?(olHy`uVBq!ia0vp^03%2O-T(jq
491
493
492 """,
494 """,
493 'hg': """diff --git a/image.png b/image.png
495 'hg': """diff --git a/image.png b/image.png
494 index 28380fd4a25c58be1b68b523ba2a314f4459ee9c..1008a77cd372386a1c24fbd96019333f67ad0065
496 index 28380fd4a25c58be1b68b523ba2a314f4459ee9c..1008a77cd372386a1c24fbd96019333f67ad0065
495 GIT binary patch
497 GIT binary patch
496 literal 19
498 literal 19
497 ac%17D@N?(olHy`uVBq!ia0y~$U;qFkO9I~j
499 ac%17D@N?(olHy`uVBq!ia0y~$U;qFkO9I~j
498
500
499 """,
501 """,
500 'svn': """===================================================================
502 'svn': """===================================================================
501 Cannot display: file marked as a binary type.
503 Cannot display: file marked as a binary type.
502 svn:mime-type = application/octet-stream
504 svn:mime-type = application/octet-stream
503 Index: image.png
505 Index: image.png
504 ===================================================================
506 ===================================================================
505 diff --git a/image.png b/image.png
507 diff --git a/image.png b/image.png
506 --- a/image.png\t(revision 1)
508 --- a/image.png\t(revision 1)
507 +++ b/image.png\t(revision 2)
509 +++ b/image.png\t(revision 2)
508 """,
510 """,
509 }
511 }
510 assert diff.raw == expected[self.repo.alias]
512 assert diff.raw == expected[self.repo.alias]
511
513
512 def test_remove_a_binary_file(self):
514 def test_remove_a_binary_file(self):
513 diff = self.repo.get_diff(self.repo[1], self.repo[2])
515 diff = self.repo.get_diff(self.repo[1], self.repo[2])
514
516
515 expected = {
517 expected = {
516 'git': """diff --git a/image.png b/image.png
518 'git': """diff --git a/image.png b/image.png
517 deleted file mode 100644
519 deleted file mode 100644
518 index 1008a77cd372386a1c24fbd96019333f67ad0065..0000000000000000000000000000000000000000
520 index 1008a77cd372386a1c24fbd96019333f67ad0065..0000000000000000000000000000000000000000
519 GIT binary patch
521 GIT binary patch
520 literal 0
522 literal 0
521 HcmV?d00001
523 HcmV?d00001
522
524
523 literal 19
525 literal 19
524 acmeAS@N?(olHy`uVBq!ia0y~$U;qFkO9I~j
526 acmeAS@N?(olHy`uVBq!ia0y~$U;qFkO9I~j
525
527
526 """,
528 """,
527 'hg': """diff --git a/image.png b/image.png
529 'hg': """diff --git a/image.png b/image.png
528 deleted file mode 100644
530 deleted file mode 100644
529 index 1008a77cd372386a1c24fbd96019333f67ad0065..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
531 index 1008a77cd372386a1c24fbd96019333f67ad0065..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
530 GIT binary patch
532 GIT binary patch
531 literal 0
533 literal 0
532 Hc$@<O00001
534 Hc$@<O00001
533
535
534 """,
536 """,
535 'svn': """===================================================================
537 'svn': """===================================================================
536 Cannot display: file marked as a binary type.
538 Cannot display: file marked as a binary type.
537 svn:mime-type = application/octet-stream
539 svn:mime-type = application/octet-stream
538 Index: image.png
540 Index: image.png
539 ===================================================================
541 ===================================================================
540 diff --git a/image.png b/image.png
542 diff --git a/image.png b/image.png
541 deleted file mode 10644
543 deleted file mode 10644
542 --- a/image.png\t(revision 2)
544 --- a/image.png\t(revision 2)
543 +++ /dev/null\t(revision 3)
545 +++ /dev/null\t(revision 3)
544 """,
546 """,
545 }
547 }
546 assert diff.raw == expected[self.repo.alias]
548 assert diff.raw == expected[self.repo.alias]
@@ -1,50 +1,53 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import datetime
21 import datetime
22
23 import pytest
22 from rhodecode.lib.vcs.nodes import FileNode
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 class TestFileNodeUnicodePath(BackendTestMixin):
29 class TestFileNodeUnicodePath(BackendTestMixin):
27
30
28 fname = 'ąśðąęłąć.txt'
31 fname = 'ąśðąęłąć.txt'
29 ufname = (fname).decode('utf-8')
32 ufname = (fname).decode('utf-8')
30
33
31 @classmethod
34 @classmethod
32 def _get_commits(cls):
35 def _get_commits(cls):
33 nodes = [
36 nodes = [
34 FileNode(cls.fname, content='Foobar'),
37 FileNode(cls.fname, content='Foobar'),
35 ]
38 ]
36
39
37 commits = [
40 commits = [
38 {
41 {
39 'message': 'Initial commit',
42 'message': 'Initial commit',
40 'author': 'Joe Doe <joe.doe@example.com>',
43 'author': 'Joe Doe <joe.doe@example.com>',
41 'date': datetime.datetime(2010, 1, 1, 20),
44 'date': datetime.datetime(2010, 1, 1, 20),
42 'added': nodes,
45 'added': nodes,
43 },
46 },
44 ]
47 ]
45 return commits
48 return commits
46
49
47 def test_filenode_path(self):
50 def test_filenode_path(self):
48 node = self.tip.get_node(self.fname)
51 node = self.tip.get_node(self.fname)
49 unode = self.tip.get_node(self.ufname)
52 unode = self.tip.get_node(self.ufname)
50 assert node == unode
53 assert node == unode
@@ -1,68 +1,69 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import datetime
21 import datetime
22
22
23 import pytest
23 import pytest
24
24
25 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
25 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
26 from rhodecode.lib.vcs.nodes import FileNode
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 class TestGetitem(BackendTestMixin):
31 class TestGetitem(BackendTestMixin):
31
32
32 @classmethod
33 @classmethod
33 def _get_commits(cls):
34 def _get_commits(cls):
34 start_date = datetime.datetime(2010, 1, 1, 20)
35 start_date = datetime.datetime(2010, 1, 1, 20)
35 for x in xrange(5):
36 for x in xrange(5):
36 yield {
37 yield {
37 'message': 'Commit %d' % x,
38 'message': 'Commit %d' % x,
38 'author': 'Joe Doe <joe.doe@example.com>',
39 'author': 'Joe Doe <joe.doe@example.com>',
39 'date': start_date + datetime.timedelta(hours=12 * x),
40 'date': start_date + datetime.timedelta(hours=12 * x),
40 'added': [
41 'added': [
41 FileNode('file_%d.txt' % x, content='Foobar %d' % x),
42 FileNode('file_%d.txt' % x, content='Foobar %d' % x),
42 ],
43 ],
43 }
44 }
44
45
45 def test_last_item_is_tip(self):
46 def test_last_item_is_tip(self):
46 assert self.repo[-1] == self.repo.get_commit()
47 assert self.repo[-1] == self.repo.get_commit()
47
48
48 @pytest.mark.parametrize("offset, message", [
49 @pytest.mark.parametrize("offset, message", [
49 (-1, 'Commit 4'),
50 (-1, 'Commit 4'),
50 (-2, 'Commit 3'),
51 (-2, 'Commit 3'),
51 (-5, 'Commit 0'),
52 (-5, 'Commit 0'),
52 ])
53 ])
53 def test_negative_offset_fetches_correct_commit(self, offset, message):
54 def test_negative_offset_fetches_correct_commit(self, offset, message):
54 assert self.repo[offset].message == message
55 assert self.repo[offset].message == message
55
56
56 def test_returns_correct_items(self):
57 def test_returns_correct_items(self):
57 commits = [self.repo[x] for x in xrange(len(self.repo.commit_ids))]
58 commits = [self.repo[x] for x in xrange(len(self.repo.commit_ids))]
58 assert commits == list(self.repo.get_commits())
59 assert commits == list(self.repo.get_commits())
59
60
60 def test_raises_for_next_commit(self):
61 def test_raises_for_next_commit(self):
61 next_commit_idx = len(self.repo.commit_ids)
62 next_commit_idx = len(self.repo.commit_ids)
62 with pytest.raises(CommitDoesNotExistError):
63 with pytest.raises(CommitDoesNotExistError):
63 self.repo[next_commit_idx]
64 self.repo[next_commit_idx]
64
65
65 def test_raises_for_not_existing_commit_idx(self):
66 def test_raises_for_not_existing_commit_idx(self):
66 not_existing_commit_idx = 1000
67 not_existing_commit_idx = 1000
67 with pytest.raises(CommitDoesNotExistError):
68 with pytest.raises(CommitDoesNotExistError):
68 self.repo[not_existing_commit_idx]
69 self.repo[not_existing_commit_idx]
@@ -1,75 +1,77 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 import datetime
20 import datetime
21
21
22 import pytest
22 from rhodecode.lib.vcs.nodes import FileNode
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 class TestGetslice(BackendTestMixin):
28 class TestGetslice(BackendTestMixin):
27
29
28 @classmethod
30 @classmethod
29 def _get_commits(cls):
31 def _get_commits(cls):
30 start_date = datetime.datetime(2010, 1, 1, 20)
32 start_date = datetime.datetime(2010, 1, 1, 20)
31 for x in xrange(5):
33 for x in xrange(5):
32 yield {
34 yield {
33 'message': 'Commit %d' % x,
35 'message': 'Commit %d' % x,
34 'author': 'Joe Doe <joe.doe@example.com>',
36 'author': 'Joe Doe <joe.doe@example.com>',
35 'date': start_date + datetime.timedelta(hours=12 * x),
37 'date': start_date + datetime.timedelta(hours=12 * x),
36 'added': [
38 'added': [
37 FileNode('file_%d.txt' % x, content='Foobar %d' % x),
39 FileNode('file_%d.txt' % x, content='Foobar %d' % x),
38 ],
40 ],
39 }
41 }
40
42
41 def test__getslice__last_item_is_tip(self):
43 def test__getslice__last_item_is_tip(self):
42 assert list(self.repo[-1:])[0] == self.repo.get_commit()
44 assert list(self.repo[-1:])[0] == self.repo.get_commit()
43
45
44 def test__getslice__respects_start_index(self):
46 def test__getslice__respects_start_index(self):
45 assert list(self.repo[2:]) == \
47 assert list(self.repo[2:]) == \
46 [self.repo.get_commit(commit_id)
48 [self.repo.get_commit(commit_id)
47 for commit_id in self.repo.commit_ids[2:]]
49 for commit_id in self.repo.commit_ids[2:]]
48
50
49 def test__getslice__respects_negative_start_index(self):
51 def test__getslice__respects_negative_start_index(self):
50 assert list(self.repo[-2:]) == \
52 assert list(self.repo[-2:]) == \
51 [self.repo.get_commit(commit_id)
53 [self.repo.get_commit(commit_id)
52 for commit_id in self.repo.commit_ids[-2:]]
54 for commit_id in self.repo.commit_ids[-2:]]
53
55
54 def test__getslice__respects_end_index(self):
56 def test__getslice__respects_end_index(self):
55 assert list(self.repo[:2]) == \
57 assert list(self.repo[:2]) == \
56 [self.repo.get_commit(commit_id)
58 [self.repo.get_commit(commit_id)
57 for commit_id in self.repo.commit_ids[:2]]
59 for commit_id in self.repo.commit_ids[:2]]
58
60
59 def test__getslice__respects_negative_end_index(self):
61 def test__getslice__respects_negative_end_index(self):
60 assert list(self.repo[:-2]) == \
62 assert list(self.repo[:-2]) == \
61 [self.repo.get_commit(commit_id)
63 [self.repo.get_commit(commit_id)
62 for commit_id in self.repo.commit_ids[:-2]]
64 for commit_id in self.repo.commit_ids[:-2]]
63
65
64 def test__getslice__start_grater_than_end(self):
66 def test__getslice__start_grater_than_end(self):
65 assert list(self.repo[10:0]) == []
67 assert list(self.repo[10:0]) == []
66
68
67 def test__getslice__negative_iteration(self):
69 def test__getslice__negative_iteration(self):
68 assert list(self.repo[::-1]) == \
70 assert list(self.repo[::-1]) == \
69 [self.repo.get_commit(commit_id)
71 [self.repo.get_commit(commit_id)
70 for commit_id in self.repo.commit_ids[::-1]]
72 for commit_id in self.repo.commit_ids[::-1]]
71
73
72 def test__getslice__iterate_even(self):
74 def test__getslice__iterate_even(self):
73 assert list(self.repo[0:10:2]) == \
75 assert list(self.repo[0:10:2]) == \
74 [self.repo.get_commit(commit_id)
76 [self.repo.get_commit(commit_id)
75 for commit_id in self.repo.commit_ids[0:10:2]]
77 for commit_id in self.repo.commit_ids[0:10:2]]
@@ -1,1269 +1,1271 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import datetime
21 import datetime
22 import mock
22 import mock
23 import os
23 import os
24 import sys
24 import sys
25 import shutil
25 import shutil
26
26
27 import pytest
27 import pytest
28
28
29 from rhodecode.lib.utils import make_db_config
29 from rhodecode.lib.utils import make_db_config
30 from rhodecode.lib.vcs.backends.base import Reference
30 from rhodecode.lib.vcs.backends.base import Reference
31 from rhodecode.lib.vcs.backends.git import (
31 from rhodecode.lib.vcs.backends.git import (
32 GitRepository, GitCommit, discover_git_version)
32 GitRepository, GitCommit, discover_git_version)
33 from rhodecode.lib.vcs.exceptions import (
33 from rhodecode.lib.vcs.exceptions import (
34 RepositoryError, VCSError, NodeDoesNotExistError)
34 RepositoryError, VCSError, NodeDoesNotExistError)
35 from rhodecode.lib.vcs.nodes import (
35 from rhodecode.lib.vcs.nodes import (
36 NodeKind, FileNode, DirNode, NodeState, SubModuleNode)
36 NodeKind, FileNode, DirNode, NodeState, SubModuleNode)
37 from rhodecode.tests import TEST_GIT_REPO, TEST_GIT_REPO_CLONE, get_new_dir
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 pytestmark = pytest.mark.backends("git")
41 pytestmark = pytest.mark.backends("git")
42
42
43
43
44 def repo_path_generator():
44 def repo_path_generator():
45 """
45 """
46 Return a different path to be used for cloning repos.
46 Return a different path to be used for cloning repos.
47 """
47 """
48 i = 0
48 i = 0
49 while True:
49 while True:
50 i += 1
50 i += 1
51 yield '%s-%d' % (TEST_GIT_REPO_CLONE, i)
51 yield '%s-%d' % (TEST_GIT_REPO_CLONE, i)
52
52
53
53
54 REPO_PATH_GENERATOR = repo_path_generator()
54 REPO_PATH_GENERATOR = repo_path_generator()
55
55
56
56
57 class TestGitRepository:
57 class TestGitRepository:
58
58
59 # pylint: disable=protected-access
59 # pylint: disable=protected-access
60
60
61 def __check_for_existing_repo(self):
61 def __check_for_existing_repo(self):
62 if os.path.exists(TEST_GIT_REPO_CLONE):
62 if os.path.exists(TEST_GIT_REPO_CLONE):
63 self.fail('Cannot test git clone repo as location %s already '
63 self.fail('Cannot test git clone repo as location %s already '
64 'exists. You should manually remove it first.'
64 'exists. You should manually remove it first.'
65 % TEST_GIT_REPO_CLONE)
65 % TEST_GIT_REPO_CLONE)
66
66
67 @pytest.fixture(autouse=True)
67 @pytest.fixture(autouse=True)
68 def prepare(self, request, baseapp):
68 def prepare(self, request, baseapp):
69 self.repo = GitRepository(TEST_GIT_REPO, bare=True)
69 self.repo = GitRepository(TEST_GIT_REPO, bare=True)
70
70
71 def get_clone_repo(self):
71 def get_clone_repo(self):
72 """
72 """
73 Return a non bare clone of the base repo.
73 Return a non bare clone of the base repo.
74 """
74 """
75 clone_path = next(REPO_PATH_GENERATOR)
75 clone_path = next(REPO_PATH_GENERATOR)
76 repo_clone = GitRepository(
76 repo_clone = GitRepository(
77 clone_path, create=True, src_url=self.repo.path, bare=False)
77 clone_path, create=True, src_url=self.repo.path, bare=False)
78
78
79 return repo_clone
79 return repo_clone
80
80
81 def get_empty_repo(self, bare=False):
81 def get_empty_repo(self, bare=False):
82 """
82 """
83 Return a non bare empty repo.
83 Return a non bare empty repo.
84 """
84 """
85 return GitRepository(next(REPO_PATH_GENERATOR), create=True, bare=bare)
85 return GitRepository(next(REPO_PATH_GENERATOR), create=True, bare=bare)
86
86
87 def test_wrong_repo_path(self):
87 def test_wrong_repo_path(self):
88 wrong_repo_path = '/tmp/errorrepo_git'
88 wrong_repo_path = '/tmp/errorrepo_git'
89 with pytest.raises(RepositoryError):
89 with pytest.raises(RepositoryError):
90 GitRepository(wrong_repo_path)
90 GitRepository(wrong_repo_path)
91
91
92 def test_repo_clone(self):
92 def test_repo_clone(self):
93 self.__check_for_existing_repo()
93 self.__check_for_existing_repo()
94 repo = GitRepository(TEST_GIT_REPO)
94 repo = GitRepository(TEST_GIT_REPO)
95 repo_clone = GitRepository(
95 repo_clone = GitRepository(
96 TEST_GIT_REPO_CLONE,
96 TEST_GIT_REPO_CLONE,
97 src_url=TEST_GIT_REPO, create=True, update_after_clone=True)
97 src_url=TEST_GIT_REPO, create=True, update_after_clone=True)
98 assert len(repo.commit_ids) == len(repo_clone.commit_ids)
98 assert len(repo.commit_ids) == len(repo_clone.commit_ids)
99 # Checking hashes of commits should be enough
99 # Checking hashes of commits should be enough
100 for commit in repo.get_commits():
100 for commit in repo.get_commits():
101 raw_id = commit.raw_id
101 raw_id = commit.raw_id
102 assert raw_id == repo_clone.get_commit(raw_id).raw_id
102 assert raw_id == repo_clone.get_commit(raw_id).raw_id
103
103
104 def test_repo_clone_without_create(self):
104 def test_repo_clone_without_create(self):
105 with pytest.raises(RepositoryError):
105 with pytest.raises(RepositoryError):
106 GitRepository(
106 GitRepository(
107 TEST_GIT_REPO_CLONE + '_wo_create', src_url=TEST_GIT_REPO)
107 TEST_GIT_REPO_CLONE + '_wo_create', src_url=TEST_GIT_REPO)
108
108
109 def test_repo_clone_with_update(self):
109 def test_repo_clone_with_update(self):
110 repo = GitRepository(TEST_GIT_REPO)
110 repo = GitRepository(TEST_GIT_REPO)
111 clone_path = TEST_GIT_REPO_CLONE + '_with_update'
111 clone_path = TEST_GIT_REPO_CLONE + '_with_update'
112 repo_clone = GitRepository(
112 repo_clone = GitRepository(
113 clone_path,
113 clone_path,
114 create=True, src_url=TEST_GIT_REPO, update_after_clone=True)
114 create=True, src_url=TEST_GIT_REPO, update_after_clone=True)
115 assert len(repo.commit_ids) == len(repo_clone.commit_ids)
115 assert len(repo.commit_ids) == len(repo_clone.commit_ids)
116
116
117 # check if current workdir was updated
117 # check if current workdir was updated
118 fpath = os.path.join(clone_path, 'MANIFEST.in')
118 fpath = os.path.join(clone_path, 'MANIFEST.in')
119 assert os.path.isfile(fpath)
119 assert os.path.isfile(fpath)
120
120
121 def test_repo_clone_without_update(self):
121 def test_repo_clone_without_update(self):
122 repo = GitRepository(TEST_GIT_REPO)
122 repo = GitRepository(TEST_GIT_REPO)
123 clone_path = TEST_GIT_REPO_CLONE + '_without_update'
123 clone_path = TEST_GIT_REPO_CLONE + '_without_update'
124 repo_clone = GitRepository(
124 repo_clone = GitRepository(
125 clone_path,
125 clone_path,
126 create=True, src_url=TEST_GIT_REPO, update_after_clone=False)
126 create=True, src_url=TEST_GIT_REPO, update_after_clone=False)
127 assert len(repo.commit_ids) == len(repo_clone.commit_ids)
127 assert len(repo.commit_ids) == len(repo_clone.commit_ids)
128 # check if current workdir was *NOT* updated
128 # check if current workdir was *NOT* updated
129 fpath = os.path.join(clone_path, 'MANIFEST.in')
129 fpath = os.path.join(clone_path, 'MANIFEST.in')
130 # Make sure it's not bare repo
130 # Make sure it's not bare repo
131 assert not repo_clone.bare
131 assert not repo_clone.bare
132 assert not os.path.isfile(fpath)
132 assert not os.path.isfile(fpath)
133
133
134 def test_repo_clone_into_bare_repo(self):
134 def test_repo_clone_into_bare_repo(self):
135 repo = GitRepository(TEST_GIT_REPO)
135 repo = GitRepository(TEST_GIT_REPO)
136 clone_path = TEST_GIT_REPO_CLONE + '_bare.git'
136 clone_path = TEST_GIT_REPO_CLONE + '_bare.git'
137 repo_clone = GitRepository(
137 repo_clone = GitRepository(
138 clone_path, create=True, src_url=repo.path, bare=True)
138 clone_path, create=True, src_url=repo.path, bare=True)
139 assert repo_clone.bare
139 assert repo_clone.bare
140
140
141 def test_create_repo_is_not_bare_by_default(self):
141 def test_create_repo_is_not_bare_by_default(self):
142 repo = GitRepository(get_new_dir('not-bare-by-default'), create=True)
142 repo = GitRepository(get_new_dir('not-bare-by-default'), create=True)
143 assert not repo.bare
143 assert not repo.bare
144
144
145 def test_create_bare_repo(self):
145 def test_create_bare_repo(self):
146 repo = GitRepository(get_new_dir('bare-repo'), create=True, bare=True)
146 repo = GitRepository(get_new_dir('bare-repo'), create=True, bare=True)
147 assert repo.bare
147 assert repo.bare
148
148
149 def test_update_server_info(self):
149 def test_update_server_info(self):
150 self.repo._update_server_info()
150 self.repo._update_server_info()
151
151
152 def test_fetch(self, vcsbackend_git):
152 def test_fetch(self, vcsbackend_git):
153 # Note: This is a git specific part of the API, it's only implemented
153 # Note: This is a git specific part of the API, it's only implemented
154 # by the git backend.
154 # by the git backend.
155 source_repo = vcsbackend_git.repo
155 source_repo = vcsbackend_git.repo
156 target_repo = vcsbackend_git.create_repo()
156 target_repo = vcsbackend_git.create_repo()
157 target_repo.fetch(source_repo.path)
157 target_repo.fetch(source_repo.path)
158 # Note: Get a fresh instance, avoids caching trouble
158 # Note: Get a fresh instance, avoids caching trouble
159 target_repo = vcsbackend_git.backend(target_repo.path)
159 target_repo = vcsbackend_git.backend(target_repo.path)
160 assert len(source_repo.commit_ids) == len(target_repo.commit_ids)
160 assert len(source_repo.commit_ids) == len(target_repo.commit_ids)
161
161
162 def test_commit_ids(self):
162 def test_commit_ids(self):
163 # there are 112 commits (by now)
163 # there are 112 commits (by now)
164 # so we can assume they would be available from now on
164 # so we can assume they would be available from now on
165 subset = set([
165 subset = set([
166 'c1214f7e79e02fc37156ff215cd71275450cffc3',
166 'c1214f7e79e02fc37156ff215cd71275450cffc3',
167 '38b5fe81f109cb111f549bfe9bb6b267e10bc557',
167 '38b5fe81f109cb111f549bfe9bb6b267e10bc557',
168 'fa6600f6848800641328adbf7811fd2372c02ab2',
168 'fa6600f6848800641328adbf7811fd2372c02ab2',
169 '102607b09cdd60e2793929c4f90478be29f85a17',
169 '102607b09cdd60e2793929c4f90478be29f85a17',
170 '49d3fd156b6f7db46313fac355dca1a0b94a0017',
170 '49d3fd156b6f7db46313fac355dca1a0b94a0017',
171 '2d1028c054665b962fa3d307adfc923ddd528038',
171 '2d1028c054665b962fa3d307adfc923ddd528038',
172 'd7e0d30fbcae12c90680eb095a4f5f02505ce501',
172 'd7e0d30fbcae12c90680eb095a4f5f02505ce501',
173 'ff7ca51e58c505fec0dd2491de52c622bb7a806b',
173 'ff7ca51e58c505fec0dd2491de52c622bb7a806b',
174 'dd80b0f6cf5052f17cc738c2951c4f2070200d7f',
174 'dd80b0f6cf5052f17cc738c2951c4f2070200d7f',
175 '8430a588b43b5d6da365400117c89400326e7992',
175 '8430a588b43b5d6da365400117c89400326e7992',
176 'd955cd312c17b02143c04fa1099a352b04368118',
176 'd955cd312c17b02143c04fa1099a352b04368118',
177 'f67b87e5c629c2ee0ba58f85197e423ff28d735b',
177 'f67b87e5c629c2ee0ba58f85197e423ff28d735b',
178 'add63e382e4aabc9e1afdc4bdc24506c269b7618',
178 'add63e382e4aabc9e1afdc4bdc24506c269b7618',
179 'f298fe1189f1b69779a4423f40b48edf92a703fc',
179 'f298fe1189f1b69779a4423f40b48edf92a703fc',
180 'bd9b619eb41994cac43d67cf4ccc8399c1125808',
180 'bd9b619eb41994cac43d67cf4ccc8399c1125808',
181 '6e125e7c890379446e98980d8ed60fba87d0f6d1',
181 '6e125e7c890379446e98980d8ed60fba87d0f6d1',
182 'd4a54db9f745dfeba6933bf5b1e79e15d0af20bd',
182 'd4a54db9f745dfeba6933bf5b1e79e15d0af20bd',
183 '0b05e4ed56c802098dfc813cbe779b2f49e92500',
183 '0b05e4ed56c802098dfc813cbe779b2f49e92500',
184 '191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e',
184 '191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e',
185 '45223f8f114c64bf4d6f853e3c35a369a6305520',
185 '45223f8f114c64bf4d6f853e3c35a369a6305520',
186 'ca1eb7957a54bce53b12d1a51b13452f95bc7c7e',
186 'ca1eb7957a54bce53b12d1a51b13452f95bc7c7e',
187 'f5ea29fc42ef67a2a5a7aecff10e1566699acd68',
187 'f5ea29fc42ef67a2a5a7aecff10e1566699acd68',
188 '27d48942240f5b91dfda77accd2caac94708cc7d',
188 '27d48942240f5b91dfda77accd2caac94708cc7d',
189 '622f0eb0bafd619d2560c26f80f09e3b0b0d78af',
189 '622f0eb0bafd619d2560c26f80f09e3b0b0d78af',
190 'e686b958768ee96af8029fe19c6050b1a8dd3b2b'])
190 'e686b958768ee96af8029fe19c6050b1a8dd3b2b'])
191 assert subset.issubset(set(self.repo.commit_ids))
191 assert subset.issubset(set(self.repo.commit_ids))
192
192
193 def test_slicing(self):
193 def test_slicing(self):
194 # 4 1 5 10 95
194 # 4 1 5 10 95
195 for sfrom, sto, size in [(0, 4, 4), (1, 2, 1), (10, 15, 5),
195 for sfrom, sto, size in [(0, 4, 4), (1, 2, 1), (10, 15, 5),
196 (10, 20, 10), (5, 100, 95)]:
196 (10, 20, 10), (5, 100, 95)]:
197 commit_ids = list(self.repo[sfrom:sto])
197 commit_ids = list(self.repo[sfrom:sto])
198 assert len(commit_ids) == size
198 assert len(commit_ids) == size
199 assert commit_ids[0] == self.repo.get_commit(commit_idx=sfrom)
199 assert commit_ids[0] == self.repo.get_commit(commit_idx=sfrom)
200 assert commit_ids[-1] == self.repo.get_commit(commit_idx=sto - 1)
200 assert commit_ids[-1] == self.repo.get_commit(commit_idx=sto - 1)
201
201
202 def test_branches(self):
202 def test_branches(self):
203 # TODO: Need more tests here
203 # TODO: Need more tests here
204 # Removed (those are 'remotes' branches for cloned repo)
204 # Removed (those are 'remotes' branches for cloned repo)
205 # assert 'master' in self.repo.branches
205 # assert 'master' in self.repo.branches
206 # assert 'gittree' in self.repo.branches
206 # assert 'gittree' in self.repo.branches
207 # assert 'web-branch' in self.repo.branches
207 # assert 'web-branch' in self.repo.branches
208 for __, commit_id in self.repo.branches.items():
208 for __, commit_id in self.repo.branches.items():
209 assert isinstance(self.repo.get_commit(commit_id), GitCommit)
209 assert isinstance(self.repo.get_commit(commit_id), GitCommit)
210
210
211 def test_tags(self):
211 def test_tags(self):
212 # TODO: Need more tests here
212 # TODO: Need more tests here
213 assert 'v0.1.1' in self.repo.tags
213 assert 'v0.1.1' in self.repo.tags
214 assert 'v0.1.2' in self.repo.tags
214 assert 'v0.1.2' in self.repo.tags
215 for __, commit_id in self.repo.tags.items():
215 for __, commit_id in self.repo.tags.items():
216 assert isinstance(self.repo.get_commit(commit_id), GitCommit)
216 assert isinstance(self.repo.get_commit(commit_id), GitCommit)
217
217
218 def _test_single_commit_cache(self, commit_id):
218 def _test_single_commit_cache(self, commit_id):
219 commit = self.repo.get_commit(commit_id)
219 commit = self.repo.get_commit(commit_id)
220 assert commit_id in self.repo.commits
220 assert commit_id in self.repo.commits
221 assert commit is self.repo.commits[commit_id]
221 assert commit is self.repo.commits[commit_id]
222
222
223 def test_initial_commit(self):
223 def test_initial_commit(self):
224 commit_id = self.repo.commit_ids[0]
224 commit_id = self.repo.commit_ids[0]
225 init_commit = self.repo.get_commit(commit_id)
225 init_commit = self.repo.get_commit(commit_id)
226 init_author = init_commit.author
226 init_author = init_commit.author
227
227
228 assert init_commit.message == 'initial import\n'
228 assert init_commit.message == 'initial import\n'
229 assert init_author == 'Marcin Kuzminski <marcin@python-blog.com>'
229 assert init_author == 'Marcin Kuzminski <marcin@python-blog.com>'
230 assert init_author == init_commit.committer
230 assert init_author == init_commit.committer
231 for path in ('vcs/__init__.py',
231 for path in ('vcs/__init__.py',
232 'vcs/backends/BaseRepository.py',
232 'vcs/backends/BaseRepository.py',
233 'vcs/backends/__init__.py'):
233 'vcs/backends/__init__.py'):
234 assert isinstance(init_commit.get_node(path), FileNode)
234 assert isinstance(init_commit.get_node(path), FileNode)
235 for path in ('', 'vcs', 'vcs/backends'):
235 for path in ('', 'vcs', 'vcs/backends'):
236 assert isinstance(init_commit.get_node(path), DirNode)
236 assert isinstance(init_commit.get_node(path), DirNode)
237
237
238 with pytest.raises(NodeDoesNotExistError):
238 with pytest.raises(NodeDoesNotExistError):
239 init_commit.get_node(path='foobar')
239 init_commit.get_node(path='foobar')
240
240
241 node = init_commit.get_node('vcs/')
241 node = init_commit.get_node('vcs/')
242 assert hasattr(node, 'kind')
242 assert hasattr(node, 'kind')
243 assert node.kind == NodeKind.DIR
243 assert node.kind == NodeKind.DIR
244
244
245 node = init_commit.get_node('vcs')
245 node = init_commit.get_node('vcs')
246 assert hasattr(node, 'kind')
246 assert hasattr(node, 'kind')
247 assert node.kind == NodeKind.DIR
247 assert node.kind == NodeKind.DIR
248
248
249 node = init_commit.get_node('vcs/__init__.py')
249 node = init_commit.get_node('vcs/__init__.py')
250 assert hasattr(node, 'kind')
250 assert hasattr(node, 'kind')
251 assert node.kind == NodeKind.FILE
251 assert node.kind == NodeKind.FILE
252
252
253 def test_not_existing_commit(self):
253 def test_not_existing_commit(self):
254 with pytest.raises(RepositoryError):
254 with pytest.raises(RepositoryError):
255 self.repo.get_commit('f' * 40)
255 self.repo.get_commit('f' * 40)
256
256
257 def test_commit10(self):
257 def test_commit10(self):
258
258
259 commit10 = self.repo.get_commit(self.repo.commit_ids[9])
259 commit10 = self.repo.get_commit(self.repo.commit_ids[9])
260 README = """===
260 README = """===
261 VCS
261 VCS
262 ===
262 ===
263
263
264 Various Version Control System management abstraction layer for Python.
264 Various Version Control System management abstraction layer for Python.
265
265
266 Introduction
266 Introduction
267 ------------
267 ------------
268
268
269 TODO: To be written...
269 TODO: To be written...
270
270
271 """
271 """
272 node = commit10.get_node('README.rst')
272 node = commit10.get_node('README.rst')
273 assert node.kind == NodeKind.FILE
273 assert node.kind == NodeKind.FILE
274 assert node.content == README
274 assert node.content == README
275
275
276 def test_head(self):
276 def test_head(self):
277 assert self.repo.head == self.repo.get_commit().raw_id
277 assert self.repo.head == self.repo.get_commit().raw_id
278
278
279 def test_checkout_with_create(self):
279 def test_checkout_with_create(self):
280 repo_clone = self.get_clone_repo()
280 repo_clone = self.get_clone_repo()
281
281
282 new_branch = 'new_branch'
282 new_branch = 'new_branch'
283 assert repo_clone._current_branch() == 'master'
283 assert repo_clone._current_branch() == 'master'
284 assert set(repo_clone.branches) == set(('master',))
284 assert set(repo_clone.branches) == set(('master',))
285 repo_clone._checkout(new_branch, create=True)
285 repo_clone._checkout(new_branch, create=True)
286
286
287 # Branches is a lazy property so we need to recrete the Repo object.
287 # Branches is a lazy property so we need to recrete the Repo object.
288 repo_clone = GitRepository(repo_clone.path)
288 repo_clone = GitRepository(repo_clone.path)
289 assert set(repo_clone.branches) == set(('master', new_branch))
289 assert set(repo_clone.branches) == set(('master', new_branch))
290 assert repo_clone._current_branch() == new_branch
290 assert repo_clone._current_branch() == new_branch
291
291
292 def test_checkout(self):
292 def test_checkout(self):
293 repo_clone = self.get_clone_repo()
293 repo_clone = self.get_clone_repo()
294
294
295 repo_clone._checkout('new_branch', create=True)
295 repo_clone._checkout('new_branch', create=True)
296 repo_clone._checkout('master')
296 repo_clone._checkout('master')
297
297
298 assert repo_clone._current_branch() == 'master'
298 assert repo_clone._current_branch() == 'master'
299
299
300 def test_checkout_same_branch(self):
300 def test_checkout_same_branch(self):
301 repo_clone = self.get_clone_repo()
301 repo_clone = self.get_clone_repo()
302
302
303 repo_clone._checkout('master')
303 repo_clone._checkout('master')
304 assert repo_clone._current_branch() == 'master'
304 assert repo_clone._current_branch() == 'master'
305
305
306 def test_checkout_branch_already_exists(self):
306 def test_checkout_branch_already_exists(self):
307 repo_clone = self.get_clone_repo()
307 repo_clone = self.get_clone_repo()
308
308
309 with pytest.raises(RepositoryError):
309 with pytest.raises(RepositoryError):
310 repo_clone._checkout('master', create=True)
310 repo_clone._checkout('master', create=True)
311
311
312 def test_checkout_bare_repo(self):
312 def test_checkout_bare_repo(self):
313 with pytest.raises(RepositoryError):
313 with pytest.raises(RepositoryError):
314 self.repo._checkout('master')
314 self.repo._checkout('master')
315
315
316 def test_current_branch_bare_repo(self):
316 def test_current_branch_bare_repo(self):
317 with pytest.raises(RepositoryError):
317 with pytest.raises(RepositoryError):
318 self.repo._current_branch()
318 self.repo._current_branch()
319
319
320 def test_current_branch_empty_repo(self):
320 def test_current_branch_empty_repo(self):
321 repo = self.get_empty_repo()
321 repo = self.get_empty_repo()
322 assert repo._current_branch() is None
322 assert repo._current_branch() is None
323
323
324 def test_local_clone(self):
324 def test_local_clone(self):
325 clone_path = next(REPO_PATH_GENERATOR)
325 clone_path = next(REPO_PATH_GENERATOR)
326 self.repo._local_clone(clone_path, 'master')
326 self.repo._local_clone(clone_path, 'master')
327 repo_clone = GitRepository(clone_path)
327 repo_clone = GitRepository(clone_path)
328
328
329 assert self.repo.commit_ids == repo_clone.commit_ids
329 assert self.repo.commit_ids == repo_clone.commit_ids
330
330
331 def test_local_clone_with_specific_branch(self):
331 def test_local_clone_with_specific_branch(self):
332 source_repo = self.get_clone_repo()
332 source_repo = self.get_clone_repo()
333
333
334 # Create a new branch in source repo
334 # Create a new branch in source repo
335 new_branch_commit = source_repo.commit_ids[-3]
335 new_branch_commit = source_repo.commit_ids[-3]
336 source_repo._checkout(new_branch_commit)
336 source_repo._checkout(new_branch_commit)
337 source_repo._checkout('new_branch', create=True)
337 source_repo._checkout('new_branch', create=True)
338
338
339 clone_path = next(REPO_PATH_GENERATOR)
339 clone_path = next(REPO_PATH_GENERATOR)
340 source_repo._local_clone(clone_path, 'new_branch')
340 source_repo._local_clone(clone_path, 'new_branch')
341 repo_clone = GitRepository(clone_path)
341 repo_clone = GitRepository(clone_path)
342
342
343 assert source_repo.commit_ids[:-3 + 1] == repo_clone.commit_ids
343 assert source_repo.commit_ids[:-3 + 1] == repo_clone.commit_ids
344
344
345 clone_path = next(REPO_PATH_GENERATOR)
345 clone_path = next(REPO_PATH_GENERATOR)
346 source_repo._local_clone(clone_path, 'master')
346 source_repo._local_clone(clone_path, 'master')
347 repo_clone = GitRepository(clone_path)
347 repo_clone = GitRepository(clone_path)
348
348
349 assert source_repo.commit_ids == repo_clone.commit_ids
349 assert source_repo.commit_ids == repo_clone.commit_ids
350
350
351 def test_local_clone_fails_if_target_exists(self):
351 def test_local_clone_fails_if_target_exists(self):
352 with pytest.raises(RepositoryError):
352 with pytest.raises(RepositoryError):
353 self.repo._local_clone(self.repo.path, 'master')
353 self.repo._local_clone(self.repo.path, 'master')
354
354
355 def test_local_fetch(self):
355 def test_local_fetch(self):
356 target_repo = self.get_empty_repo()
356 target_repo = self.get_empty_repo()
357 source_repo = self.get_clone_repo()
357 source_repo = self.get_clone_repo()
358
358
359 # Create a new branch in source repo
359 # Create a new branch in source repo
360 master_commit = source_repo.commit_ids[-1]
360 master_commit = source_repo.commit_ids[-1]
361 new_branch_commit = source_repo.commit_ids[-3]
361 new_branch_commit = source_repo.commit_ids[-3]
362 source_repo._checkout(new_branch_commit)
362 source_repo._checkout(new_branch_commit)
363 source_repo._checkout('new_branch', create=True)
363 source_repo._checkout('new_branch', create=True)
364
364
365 target_repo._local_fetch(source_repo.path, 'new_branch')
365 target_repo._local_fetch(source_repo.path, 'new_branch')
366 assert target_repo._last_fetch_heads() == [new_branch_commit]
366 assert target_repo._last_fetch_heads() == [new_branch_commit]
367
367
368 target_repo._local_fetch(source_repo.path, 'master')
368 target_repo._local_fetch(source_repo.path, 'master')
369 assert target_repo._last_fetch_heads() == [master_commit]
369 assert target_repo._last_fetch_heads() == [master_commit]
370
370
371 def test_local_fetch_from_bare_repo(self):
371 def test_local_fetch_from_bare_repo(self):
372 target_repo = self.get_empty_repo()
372 target_repo = self.get_empty_repo()
373 target_repo._local_fetch(self.repo.path, 'master')
373 target_repo._local_fetch(self.repo.path, 'master')
374
374
375 master_commit = self.repo.commit_ids[-1]
375 master_commit = self.repo.commit_ids[-1]
376 assert target_repo._last_fetch_heads() == [master_commit]
376 assert target_repo._last_fetch_heads() == [master_commit]
377
377
378 def test_local_fetch_from_same_repo(self):
378 def test_local_fetch_from_same_repo(self):
379 with pytest.raises(ValueError):
379 with pytest.raises(ValueError):
380 self.repo._local_fetch(self.repo.path, 'master')
380 self.repo._local_fetch(self.repo.path, 'master')
381
381
382 def test_local_fetch_branch_does_not_exist(self):
382 def test_local_fetch_branch_does_not_exist(self):
383 target_repo = self.get_empty_repo()
383 target_repo = self.get_empty_repo()
384
384
385 with pytest.raises(RepositoryError):
385 with pytest.raises(RepositoryError):
386 target_repo._local_fetch(self.repo.path, 'new_branch')
386 target_repo._local_fetch(self.repo.path, 'new_branch')
387
387
388 def test_local_pull(self):
388 def test_local_pull(self):
389 target_repo = self.get_empty_repo()
389 target_repo = self.get_empty_repo()
390 source_repo = self.get_clone_repo()
390 source_repo = self.get_clone_repo()
391
391
392 # Create a new branch in source repo
392 # Create a new branch in source repo
393 master_commit = source_repo.commit_ids[-1]
393 master_commit = source_repo.commit_ids[-1]
394 new_branch_commit = source_repo.commit_ids[-3]
394 new_branch_commit = source_repo.commit_ids[-3]
395 source_repo._checkout(new_branch_commit)
395 source_repo._checkout(new_branch_commit)
396 source_repo._checkout('new_branch', create=True)
396 source_repo._checkout('new_branch', create=True)
397
397
398 target_repo._local_pull(source_repo.path, 'new_branch')
398 target_repo._local_pull(source_repo.path, 'new_branch')
399 target_repo = GitRepository(target_repo.path)
399 target_repo = GitRepository(target_repo.path)
400 assert target_repo.head == new_branch_commit
400 assert target_repo.head == new_branch_commit
401
401
402 target_repo._local_pull(source_repo.path, 'master')
402 target_repo._local_pull(source_repo.path, 'master')
403 target_repo = GitRepository(target_repo.path)
403 target_repo = GitRepository(target_repo.path)
404 assert target_repo.head == master_commit
404 assert target_repo.head == master_commit
405
405
406 def test_local_pull_in_bare_repo(self):
406 def test_local_pull_in_bare_repo(self):
407 with pytest.raises(RepositoryError):
407 with pytest.raises(RepositoryError):
408 self.repo._local_pull(self.repo.path, 'master')
408 self.repo._local_pull(self.repo.path, 'master')
409
409
410 def test_local_merge(self):
410 def test_local_merge(self):
411 target_repo = self.get_empty_repo()
411 target_repo = self.get_empty_repo()
412 source_repo = self.get_clone_repo()
412 source_repo = self.get_clone_repo()
413
413
414 # Create a new branch in source repo
414 # Create a new branch in source repo
415 master_commit = source_repo.commit_ids[-1]
415 master_commit = source_repo.commit_ids[-1]
416 new_branch_commit = source_repo.commit_ids[-3]
416 new_branch_commit = source_repo.commit_ids[-3]
417 source_repo._checkout(new_branch_commit)
417 source_repo._checkout(new_branch_commit)
418 source_repo._checkout('new_branch', create=True)
418 source_repo._checkout('new_branch', create=True)
419
419
420 # This is required as one cannot do a -ff-only merge in an empty repo.
420 # This is required as one cannot do a -ff-only merge in an empty repo.
421 target_repo._local_pull(source_repo.path, 'new_branch')
421 target_repo._local_pull(source_repo.path, 'new_branch')
422
422
423 target_repo._local_fetch(source_repo.path, 'master')
423 target_repo._local_fetch(source_repo.path, 'master')
424 merge_message = 'Merge message\n\nDescription:...'
424 merge_message = 'Merge message\n\nDescription:...'
425 user_name = 'Albert Einstein'
425 user_name = 'Albert Einstein'
426 user_email = 'albert@einstein.com'
426 user_email = 'albert@einstein.com'
427 target_repo._local_merge(merge_message, user_name, user_email,
427 target_repo._local_merge(merge_message, user_name, user_email,
428 target_repo._last_fetch_heads())
428 target_repo._last_fetch_heads())
429
429
430 target_repo = GitRepository(target_repo.path)
430 target_repo = GitRepository(target_repo.path)
431 assert target_repo.commit_ids[-2] == master_commit
431 assert target_repo.commit_ids[-2] == master_commit
432 last_commit = target_repo.get_commit(target_repo.head)
432 last_commit = target_repo.get_commit(target_repo.head)
433 assert last_commit.message.strip() == merge_message
433 assert last_commit.message.strip() == merge_message
434 assert last_commit.author == '%s <%s>' % (user_name, user_email)
434 assert last_commit.author == '%s <%s>' % (user_name, user_email)
435
435
436 assert not os.path.exists(
436 assert not os.path.exists(
437 os.path.join(target_repo.path, '.git', 'MERGE_HEAD'))
437 os.path.join(target_repo.path, '.git', 'MERGE_HEAD'))
438
438
439 def test_local_merge_raises_exception_on_conflict(self, vcsbackend_git):
439 def test_local_merge_raises_exception_on_conflict(self, vcsbackend_git):
440 target_repo = vcsbackend_git.create_repo(number_of_commits=1)
440 target_repo = vcsbackend_git.create_repo(number_of_commits=1)
441 vcsbackend_git.ensure_file('README', 'I will conflict with you!!!')
441 vcsbackend_git.ensure_file('README', 'I will conflict with you!!!')
442
442
443 target_repo._local_fetch(self.repo.path, 'master')
443 target_repo._local_fetch(self.repo.path, 'master')
444 with pytest.raises(RepositoryError):
444 with pytest.raises(RepositoryError):
445 target_repo._local_merge(
445 target_repo._local_merge(
446 'merge_message', 'user name', 'user@name.com',
446 'merge_message', 'user name', 'user@name.com',
447 target_repo._last_fetch_heads())
447 target_repo._last_fetch_heads())
448
448
449 # Check we are not left in an intermediate merge state
449 # Check we are not left in an intermediate merge state
450 assert not os.path.exists(
450 assert not os.path.exists(
451 os.path.join(target_repo.path, '.git', 'MERGE_HEAD'))
451 os.path.join(target_repo.path, '.git', 'MERGE_HEAD'))
452
452
453 def test_local_merge_into_empty_repo(self):
453 def test_local_merge_into_empty_repo(self):
454 target_repo = self.get_empty_repo()
454 target_repo = self.get_empty_repo()
455
455
456 # This is required as one cannot do a -ff-only merge in an empty repo.
456 # This is required as one cannot do a -ff-only merge in an empty repo.
457 target_repo._local_fetch(self.repo.path, 'master')
457 target_repo._local_fetch(self.repo.path, 'master')
458 with pytest.raises(RepositoryError):
458 with pytest.raises(RepositoryError):
459 target_repo._local_merge(
459 target_repo._local_merge(
460 'merge_message', 'user name', 'user@name.com',
460 'merge_message', 'user name', 'user@name.com',
461 target_repo._last_fetch_heads())
461 target_repo._last_fetch_heads())
462
462
463 def test_local_merge_in_bare_repo(self):
463 def test_local_merge_in_bare_repo(self):
464 with pytest.raises(RepositoryError):
464 with pytest.raises(RepositoryError):
465 self.repo._local_merge(
465 self.repo._local_merge(
466 'merge_message', 'user name', 'user@name.com', None)
466 'merge_message', 'user name', 'user@name.com', None)
467
467
468 def test_local_push_non_bare(self):
468 def test_local_push_non_bare(self):
469 target_repo = self.get_empty_repo()
469 target_repo = self.get_empty_repo()
470
470
471 pushed_branch = 'pushed_branch'
471 pushed_branch = 'pushed_branch'
472 self.repo._local_push('master', target_repo.path, pushed_branch)
472 self.repo._local_push('master', target_repo.path, pushed_branch)
473 # Fix the HEAD of the target repo, or otherwise GitRepository won't
473 # Fix the HEAD of the target repo, or otherwise GitRepository won't
474 # report any branches.
474 # report any branches.
475 with open(os.path.join(target_repo.path, '.git', 'HEAD'), 'w') as f:
475 with open(os.path.join(target_repo.path, '.git', 'HEAD'), 'w') as f:
476 f.write('ref: refs/heads/%s' % pushed_branch)
476 f.write('ref: refs/heads/%s' % pushed_branch)
477
477
478 target_repo = GitRepository(target_repo.path)
478 target_repo = GitRepository(target_repo.path)
479
479
480 assert (target_repo.branches[pushed_branch] ==
480 assert (target_repo.branches[pushed_branch] ==
481 self.repo.branches['master'])
481 self.repo.branches['master'])
482
482
483 def test_local_push_bare(self):
483 def test_local_push_bare(self):
484 target_repo = self.get_empty_repo(bare=True)
484 target_repo = self.get_empty_repo(bare=True)
485
485
486 pushed_branch = 'pushed_branch'
486 pushed_branch = 'pushed_branch'
487 self.repo._local_push('master', target_repo.path, pushed_branch)
487 self.repo._local_push('master', target_repo.path, pushed_branch)
488 # Fix the HEAD of the target repo, or otherwise GitRepository won't
488 # Fix the HEAD of the target repo, or otherwise GitRepository won't
489 # report any branches.
489 # report any branches.
490 with open(os.path.join(target_repo.path, 'HEAD'), 'w') as f:
490 with open(os.path.join(target_repo.path, 'HEAD'), 'w') as f:
491 f.write('ref: refs/heads/%s' % pushed_branch)
491 f.write('ref: refs/heads/%s' % pushed_branch)
492
492
493 target_repo = GitRepository(target_repo.path)
493 target_repo = GitRepository(target_repo.path)
494
494
495 assert (target_repo.branches[pushed_branch] ==
495 assert (target_repo.branches[pushed_branch] ==
496 self.repo.branches['master'])
496 self.repo.branches['master'])
497
497
498 def test_local_push_non_bare_target_branch_is_checked_out(self):
498 def test_local_push_non_bare_target_branch_is_checked_out(self):
499 target_repo = self.get_clone_repo()
499 target_repo = self.get_clone_repo()
500
500
501 pushed_branch = 'pushed_branch'
501 pushed_branch = 'pushed_branch'
502 # Create a new branch in source repo
502 # Create a new branch in source repo
503 new_branch_commit = target_repo.commit_ids[-3]
503 new_branch_commit = target_repo.commit_ids[-3]
504 target_repo._checkout(new_branch_commit)
504 target_repo._checkout(new_branch_commit)
505 target_repo._checkout(pushed_branch, create=True)
505 target_repo._checkout(pushed_branch, create=True)
506
506
507 self.repo._local_push('master', target_repo.path, pushed_branch)
507 self.repo._local_push('master', target_repo.path, pushed_branch)
508
508
509 target_repo = GitRepository(target_repo.path)
509 target_repo = GitRepository(target_repo.path)
510
510
511 assert (target_repo.branches[pushed_branch] ==
511 assert (target_repo.branches[pushed_branch] ==
512 self.repo.branches['master'])
512 self.repo.branches['master'])
513
513
514 def test_local_push_raises_exception_on_conflict(self, vcsbackend_git):
514 def test_local_push_raises_exception_on_conflict(self, vcsbackend_git):
515 target_repo = vcsbackend_git.create_repo(number_of_commits=1)
515 target_repo = vcsbackend_git.create_repo(number_of_commits=1)
516 with pytest.raises(RepositoryError):
516 with pytest.raises(RepositoryError):
517 self.repo._local_push('master', target_repo.path, 'master')
517 self.repo._local_push('master', target_repo.path, 'master')
518
518
519 def test_hooks_can_be_enabled_via_env_variable_for_local_push(self):
519 def test_hooks_can_be_enabled_via_env_variable_for_local_push(self):
520 target_repo = self.get_empty_repo(bare=True)
520 target_repo = self.get_empty_repo(bare=True)
521
521
522 with mock.patch.object(self.repo, 'run_git_command') as run_mock:
522 with mock.patch.object(self.repo, 'run_git_command') as run_mock:
523 self.repo._local_push(
523 self.repo._local_push(
524 'master', target_repo.path, 'master', enable_hooks=True)
524 'master', target_repo.path, 'master', enable_hooks=True)
525 env = run_mock.call_args[1]['extra_env']
525 env = run_mock.call_args[1]['extra_env']
526 assert 'RC_SKIP_HOOKS' not in env
526 assert 'RC_SKIP_HOOKS' not in env
527
527
528 def _add_failing_hook(self, repo_path, hook_name, bare=False):
528 def _add_failing_hook(self, repo_path, hook_name, bare=False):
529 path_components = (
529 path_components = (
530 ['hooks', hook_name] if bare else ['.git', 'hooks', hook_name])
530 ['hooks', hook_name] if bare else ['.git', 'hooks', hook_name])
531 hook_path = os.path.join(repo_path, *path_components)
531 hook_path = os.path.join(repo_path, *path_components)
532 with open(hook_path, 'w') as f:
532 with open(hook_path, 'w') as f:
533 script_lines = [
533 script_lines = [
534 '#!%s' % sys.executable,
534 '#!%s' % sys.executable,
535 'import os',
535 'import os',
536 'import sys',
536 'import sys',
537 'if os.environ.get("RC_SKIP_HOOKS"):',
537 'if os.environ.get("RC_SKIP_HOOKS"):',
538 ' sys.exit(0)',
538 ' sys.exit(0)',
539 'sys.exit(1)',
539 'sys.exit(1)',
540 ]
540 ]
541 f.write('\n'.join(script_lines))
541 f.write('\n'.join(script_lines))
542 os.chmod(hook_path, 0755)
542 os.chmod(hook_path, 0755)
543
543
544 def test_local_push_does_not_execute_hook(self):
544 def test_local_push_does_not_execute_hook(self):
545 target_repo = self.get_empty_repo()
545 target_repo = self.get_empty_repo()
546
546
547 pushed_branch = 'pushed_branch'
547 pushed_branch = 'pushed_branch'
548 self._add_failing_hook(target_repo.path, 'pre-receive')
548 self._add_failing_hook(target_repo.path, 'pre-receive')
549 self.repo._local_push('master', target_repo.path, pushed_branch)
549 self.repo._local_push('master', target_repo.path, pushed_branch)
550 # Fix the HEAD of the target repo, or otherwise GitRepository won't
550 # Fix the HEAD of the target repo, or otherwise GitRepository won't
551 # report any branches.
551 # report any branches.
552 with open(os.path.join(target_repo.path, '.git', 'HEAD'), 'w') as f:
552 with open(os.path.join(target_repo.path, '.git', 'HEAD'), 'w') as f:
553 f.write('ref: refs/heads/%s' % pushed_branch)
553 f.write('ref: refs/heads/%s' % pushed_branch)
554
554
555 target_repo = GitRepository(target_repo.path)
555 target_repo = GitRepository(target_repo.path)
556
556
557 assert (target_repo.branches[pushed_branch] ==
557 assert (target_repo.branches[pushed_branch] ==
558 self.repo.branches['master'])
558 self.repo.branches['master'])
559
559
560 def test_local_push_executes_hook(self):
560 def test_local_push_executes_hook(self):
561 target_repo = self.get_empty_repo(bare=True)
561 target_repo = self.get_empty_repo(bare=True)
562 self._add_failing_hook(target_repo.path, 'pre-receive', bare=True)
562 self._add_failing_hook(target_repo.path, 'pre-receive', bare=True)
563 with pytest.raises(RepositoryError):
563 with pytest.raises(RepositoryError):
564 self.repo._local_push(
564 self.repo._local_push(
565 'master', target_repo.path, 'master', enable_hooks=True)
565 'master', target_repo.path, 'master', enable_hooks=True)
566
566
567 def test_maybe_prepare_merge_workspace(self):
567 def test_maybe_prepare_merge_workspace(self):
568 workspace = self.repo._maybe_prepare_merge_workspace(
568 workspace = self.repo._maybe_prepare_merge_workspace(
569 'pr2', Reference('branch', 'master', 'unused'))
569 'pr2', Reference('branch', 'master', 'unused'))
570
570
571 assert os.path.isdir(workspace)
571 assert os.path.isdir(workspace)
572 workspace_repo = GitRepository(workspace)
572 workspace_repo = GitRepository(workspace)
573 assert workspace_repo.branches == self.repo.branches
573 assert workspace_repo.branches == self.repo.branches
574
574
575 # Calling it a second time should also succeed
575 # Calling it a second time should also succeed
576 workspace = self.repo._maybe_prepare_merge_workspace(
576 workspace = self.repo._maybe_prepare_merge_workspace(
577 'pr2', Reference('branch', 'master', 'unused'))
577 'pr2', Reference('branch', 'master', 'unused'))
578 assert os.path.isdir(workspace)
578 assert os.path.isdir(workspace)
579
579
580 def test_cleanup_merge_workspace(self):
580 def test_cleanup_merge_workspace(self):
581 workspace = self.repo._maybe_prepare_merge_workspace(
581 workspace = self.repo._maybe_prepare_merge_workspace(
582 'pr3', Reference('branch', 'master', 'unused'))
582 'pr3', Reference('branch', 'master', 'unused'))
583 self.repo.cleanup_merge_workspace('pr3')
583 self.repo.cleanup_merge_workspace('pr3')
584
584
585 assert not os.path.exists(workspace)
585 assert not os.path.exists(workspace)
586
586
587 def test_cleanup_merge_workspace_invalid_workspace_id(self):
587 def test_cleanup_merge_workspace_invalid_workspace_id(self):
588 # No assert: because in case of an inexistent workspace this function
588 # No assert: because in case of an inexistent workspace this function
589 # should still succeed.
589 # should still succeed.
590 self.repo.cleanup_merge_workspace('pr4')
590 self.repo.cleanup_merge_workspace('pr4')
591
591
592 def test_set_refs(self):
592 def test_set_refs(self):
593 test_ref = 'refs/test-refs/abcde'
593 test_ref = 'refs/test-refs/abcde'
594 test_commit_id = 'ecb86e1f424f2608262b130db174a7dfd25a6623'
594 test_commit_id = 'ecb86e1f424f2608262b130db174a7dfd25a6623'
595
595
596 self.repo.set_refs(test_ref, test_commit_id)
596 self.repo.set_refs(test_ref, test_commit_id)
597 stdout, _ = self.repo.run_git_command(['show-ref'])
597 stdout, _ = self.repo.run_git_command(['show-ref'])
598 assert test_ref in stdout
598 assert test_ref in stdout
599 assert test_commit_id in stdout
599 assert test_commit_id in stdout
600
600
601 def test_remove_ref(self):
601 def test_remove_ref(self):
602 test_ref = 'refs/test-refs/abcde'
602 test_ref = 'refs/test-refs/abcde'
603 test_commit_id = 'ecb86e1f424f2608262b130db174a7dfd25a6623'
603 test_commit_id = 'ecb86e1f424f2608262b130db174a7dfd25a6623'
604 self.repo.set_refs(test_ref, test_commit_id)
604 self.repo.set_refs(test_ref, test_commit_id)
605 stdout, _ = self.repo.run_git_command(['show-ref'])
605 stdout, _ = self.repo.run_git_command(['show-ref'])
606 assert test_ref in stdout
606 assert test_ref in stdout
607 assert test_commit_id in stdout
607 assert test_commit_id in stdout
608
608
609 self.repo.remove_ref(test_ref)
609 self.repo.remove_ref(test_ref)
610 stdout, _ = self.repo.run_git_command(['show-ref'])
610 stdout, _ = self.repo.run_git_command(['show-ref'])
611 assert test_ref not in stdout
611 assert test_ref not in stdout
612 assert test_commit_id not in stdout
612 assert test_commit_id not in stdout
613
613
614
614
615 class TestGitCommit(object):
615 class TestGitCommit(object):
616
616
617 @pytest.fixture(autouse=True)
617 @pytest.fixture(autouse=True)
618 def prepare(self):
618 def prepare(self):
619 self.repo = GitRepository(TEST_GIT_REPO)
619 self.repo = GitRepository(TEST_GIT_REPO)
620
620
621 def test_default_commit(self):
621 def test_default_commit(self):
622 tip = self.repo.get_commit()
622 tip = self.repo.get_commit()
623 assert tip == self.repo.get_commit(None)
623 assert tip == self.repo.get_commit(None)
624 assert tip == self.repo.get_commit('tip')
624 assert tip == self.repo.get_commit('tip')
625
625
626 def test_root_node(self):
626 def test_root_node(self):
627 tip = self.repo.get_commit()
627 tip = self.repo.get_commit()
628 assert tip.root is tip.get_node('')
628 assert tip.root is tip.get_node('')
629
629
630 def test_lazy_fetch(self):
630 def test_lazy_fetch(self):
631 """
631 """
632 Test if commit's nodes expands and are cached as we walk through
632 Test if commit's nodes expands and are cached as we walk through
633 the commit. This test is somewhat hard to write as order of tests
633 the commit. This test is somewhat hard to write as order of tests
634 is a key here. Written by running command after command in a shell.
634 is a key here. Written by running command after command in a shell.
635 """
635 """
636 commit_id = '2a13f185e4525f9d4b59882791a2d397b90d5ddc'
636 commit_id = '2a13f185e4525f9d4b59882791a2d397b90d5ddc'
637 assert commit_id in self.repo.commit_ids
637 assert commit_id in self.repo.commit_ids
638 commit = self.repo.get_commit(commit_id)
638 commit = self.repo.get_commit(commit_id)
639 assert len(commit.nodes) == 0
639 assert len(commit.nodes) == 0
640 root = commit.root
640 root = commit.root
641 assert len(commit.nodes) == 1
641 assert len(commit.nodes) == 1
642 assert len(root.nodes) == 8
642 assert len(root.nodes) == 8
643 # accessing root.nodes updates commit.nodes
643 # accessing root.nodes updates commit.nodes
644 assert len(commit.nodes) == 9
644 assert len(commit.nodes) == 9
645
645
646 docs = root.get_node('docs')
646 docs = root.get_node('docs')
647 # we haven't yet accessed anything new as docs dir was already cached
647 # we haven't yet accessed anything new as docs dir was already cached
648 assert len(commit.nodes) == 9
648 assert len(commit.nodes) == 9
649 assert len(docs.nodes) == 8
649 assert len(docs.nodes) == 8
650 # accessing docs.nodes updates commit.nodes
650 # accessing docs.nodes updates commit.nodes
651 assert len(commit.nodes) == 17
651 assert len(commit.nodes) == 17
652
652
653 assert docs is commit.get_node('docs')
653 assert docs is commit.get_node('docs')
654 assert docs is root.nodes[0]
654 assert docs is root.nodes[0]
655 assert docs is root.dirs[0]
655 assert docs is root.dirs[0]
656 assert docs is commit.get_node('docs')
656 assert docs is commit.get_node('docs')
657
657
658 def test_nodes_with_commit(self):
658 def test_nodes_with_commit(self):
659 commit_id = '2a13f185e4525f9d4b59882791a2d397b90d5ddc'
659 commit_id = '2a13f185e4525f9d4b59882791a2d397b90d5ddc'
660 commit = self.repo.get_commit(commit_id)
660 commit = self.repo.get_commit(commit_id)
661 root = commit.root
661 root = commit.root
662 docs = root.get_node('docs')
662 docs = root.get_node('docs')
663 assert docs is commit.get_node('docs')
663 assert docs is commit.get_node('docs')
664 api = docs.get_node('api')
664 api = docs.get_node('api')
665 assert api is commit.get_node('docs/api')
665 assert api is commit.get_node('docs/api')
666 index = api.get_node('index.rst')
666 index = api.get_node('index.rst')
667 assert index is commit.get_node('docs/api/index.rst')
667 assert index is commit.get_node('docs/api/index.rst')
668 assert index is commit.get_node('docs')\
668 assert index is commit.get_node('docs')\
669 .get_node('api')\
669 .get_node('api')\
670 .get_node('index.rst')
670 .get_node('index.rst')
671
671
672 def test_branch_and_tags(self):
672 def test_branch_and_tags(self):
673 """
673 """
674 rev0 = self.repo.commit_ids[0]
674 rev0 = self.repo.commit_ids[0]
675 commit0 = self.repo.get_commit(rev0)
675 commit0 = self.repo.get_commit(rev0)
676 assert commit0.branch == 'master'
676 assert commit0.branch == 'master'
677 assert commit0.tags == []
677 assert commit0.tags == []
678
678
679 rev10 = self.repo.commit_ids[10]
679 rev10 = self.repo.commit_ids[10]
680 commit10 = self.repo.get_commit(rev10)
680 commit10 = self.repo.get_commit(rev10)
681 assert commit10.branch == 'master'
681 assert commit10.branch == 'master'
682 assert commit10.tags == []
682 assert commit10.tags == []
683
683
684 rev44 = self.repo.commit_ids[44]
684 rev44 = self.repo.commit_ids[44]
685 commit44 = self.repo.get_commit(rev44)
685 commit44 = self.repo.get_commit(rev44)
686 assert commit44.branch == 'web-branch'
686 assert commit44.branch == 'web-branch'
687
687
688 tip = self.repo.get_commit('tip')
688 tip = self.repo.get_commit('tip')
689 assert 'tip' in tip.tags
689 assert 'tip' in tip.tags
690 """
690 """
691 # Those tests would fail - branches are now going
691 # Those tests would fail - branches are now going
692 # to be changed at main API in order to support git backend
692 # to be changed at main API in order to support git backend
693 pass
693 pass
694
694
695 def test_file_size(self):
695 def test_file_size(self):
696 to_check = (
696 to_check = (
697 ('c1214f7e79e02fc37156ff215cd71275450cffc3',
697 ('c1214f7e79e02fc37156ff215cd71275450cffc3',
698 'vcs/backends/BaseRepository.py', 502),
698 'vcs/backends/BaseRepository.py', 502),
699 ('d7e0d30fbcae12c90680eb095a4f5f02505ce501',
699 ('d7e0d30fbcae12c90680eb095a4f5f02505ce501',
700 'vcs/backends/hg.py', 854),
700 'vcs/backends/hg.py', 854),
701 ('6e125e7c890379446e98980d8ed60fba87d0f6d1',
701 ('6e125e7c890379446e98980d8ed60fba87d0f6d1',
702 'setup.py', 1068),
702 'setup.py', 1068),
703
703
704 ('d955cd312c17b02143c04fa1099a352b04368118',
704 ('d955cd312c17b02143c04fa1099a352b04368118',
705 'vcs/backends/base.py', 2921),
705 'vcs/backends/base.py', 2921),
706 ('ca1eb7957a54bce53b12d1a51b13452f95bc7c7e',
706 ('ca1eb7957a54bce53b12d1a51b13452f95bc7c7e',
707 'vcs/backends/base.py', 3936),
707 'vcs/backends/base.py', 3936),
708 ('f50f42baeed5af6518ef4b0cb2f1423f3851a941',
708 ('f50f42baeed5af6518ef4b0cb2f1423f3851a941',
709 'vcs/backends/base.py', 6189),
709 'vcs/backends/base.py', 6189),
710 )
710 )
711 for commit_id, path, size in to_check:
711 for commit_id, path, size in to_check:
712 node = self.repo.get_commit(commit_id).get_node(path)
712 node = self.repo.get_commit(commit_id).get_node(path)
713 assert node.is_file()
713 assert node.is_file()
714 assert node.size == size
714 assert node.size == size
715
715
716 def test_file_history_from_commits(self):
716 def test_file_history_from_commits(self):
717 node = self.repo[10].get_node('setup.py')
717 node = self.repo[10].get_node('setup.py')
718 commit_ids = [commit.raw_id for commit in node.history]
718 commit_ids = [commit.raw_id for commit in node.history]
719 assert ['ff7ca51e58c505fec0dd2491de52c622bb7a806b'] == commit_ids
719 assert ['ff7ca51e58c505fec0dd2491de52c622bb7a806b'] == commit_ids
720
720
721 node = self.repo[20].get_node('setup.py')
721 node = self.repo[20].get_node('setup.py')
722 node_ids = [commit.raw_id for commit in node.history]
722 node_ids = [commit.raw_id for commit in node.history]
723 assert ['191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e',
723 assert ['191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e',
724 'ff7ca51e58c505fec0dd2491de52c622bb7a806b'] == node_ids
724 'ff7ca51e58c505fec0dd2491de52c622bb7a806b'] == node_ids
725
725
726 # special case we check history from commit that has this particular
726 # special case we check history from commit that has this particular
727 # file changed this means we check if it's included as well
727 # file changed this means we check if it's included as well
728 node = self.repo.get_commit('191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e') \
728 node = self.repo.get_commit('191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e') \
729 .get_node('setup.py')
729 .get_node('setup.py')
730 node_ids = [commit.raw_id for commit in node.history]
730 node_ids = [commit.raw_id for commit in node.history]
731 assert ['191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e',
731 assert ['191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e',
732 'ff7ca51e58c505fec0dd2491de52c622bb7a806b'] == node_ids
732 'ff7ca51e58c505fec0dd2491de52c622bb7a806b'] == node_ids
733
733
734 def test_file_history(self):
734 def test_file_history(self):
735 # we can only check if those commits are present in the history
735 # we can only check if those commits are present in the history
736 # as we cannot update this test every time file is changed
736 # as we cannot update this test every time file is changed
737 files = {
737 files = {
738 'setup.py': [
738 'setup.py': [
739 '54386793436c938cff89326944d4c2702340037d',
739 '54386793436c938cff89326944d4c2702340037d',
740 '51d254f0ecf5df2ce50c0b115741f4cf13985dab',
740 '51d254f0ecf5df2ce50c0b115741f4cf13985dab',
741 '998ed409c795fec2012b1c0ca054d99888b22090',
741 '998ed409c795fec2012b1c0ca054d99888b22090',
742 '5e0eb4c47f56564395f76333f319d26c79e2fb09',
742 '5e0eb4c47f56564395f76333f319d26c79e2fb09',
743 '0115510b70c7229dbc5dc49036b32e7d91d23acd',
743 '0115510b70c7229dbc5dc49036b32e7d91d23acd',
744 '7cb3fd1b6d8c20ba89e2264f1c8baebc8a52d36e',
744 '7cb3fd1b6d8c20ba89e2264f1c8baebc8a52d36e',
745 '2a13f185e4525f9d4b59882791a2d397b90d5ddc',
745 '2a13f185e4525f9d4b59882791a2d397b90d5ddc',
746 '191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e',
746 '191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e',
747 'ff7ca51e58c505fec0dd2491de52c622bb7a806b',
747 'ff7ca51e58c505fec0dd2491de52c622bb7a806b',
748 ],
748 ],
749 'vcs/nodes.py': [
749 'vcs/nodes.py': [
750 '33fa3223355104431402a888fa77a4e9956feb3e',
750 '33fa3223355104431402a888fa77a4e9956feb3e',
751 'fa014c12c26d10ba682fadb78f2a11c24c8118e1',
751 'fa014c12c26d10ba682fadb78f2a11c24c8118e1',
752 'e686b958768ee96af8029fe19c6050b1a8dd3b2b',
752 'e686b958768ee96af8029fe19c6050b1a8dd3b2b',
753 'ab5721ca0a081f26bf43d9051e615af2cc99952f',
753 'ab5721ca0a081f26bf43d9051e615af2cc99952f',
754 'c877b68d18e792a66b7f4c529ea02c8f80801542',
754 'c877b68d18e792a66b7f4c529ea02c8f80801542',
755 '4313566d2e417cb382948f8d9d7c765330356054',
755 '4313566d2e417cb382948f8d9d7c765330356054',
756 '6c2303a793671e807d1cfc70134c9ca0767d98c2',
756 '6c2303a793671e807d1cfc70134c9ca0767d98c2',
757 '54386793436c938cff89326944d4c2702340037d',
757 '54386793436c938cff89326944d4c2702340037d',
758 '54000345d2e78b03a99d561399e8e548de3f3203',
758 '54000345d2e78b03a99d561399e8e548de3f3203',
759 '1c6b3677b37ea064cb4b51714d8f7498f93f4b2b',
759 '1c6b3677b37ea064cb4b51714d8f7498f93f4b2b',
760 '2d03ca750a44440fb5ea8b751176d1f36f8e8f46',
760 '2d03ca750a44440fb5ea8b751176d1f36f8e8f46',
761 '2a08b128c206db48c2f0b8f70df060e6db0ae4f8',
761 '2a08b128c206db48c2f0b8f70df060e6db0ae4f8',
762 '30c26513ff1eb8e5ce0e1c6b477ee5dc50e2f34b',
762 '30c26513ff1eb8e5ce0e1c6b477ee5dc50e2f34b',
763 'ac71e9503c2ca95542839af0ce7b64011b72ea7c',
763 'ac71e9503c2ca95542839af0ce7b64011b72ea7c',
764 '12669288fd13adba2a9b7dd5b870cc23ffab92d2',
764 '12669288fd13adba2a9b7dd5b870cc23ffab92d2',
765 '5a0c84f3e6fe3473e4c8427199d5a6fc71a9b382',
765 '5a0c84f3e6fe3473e4c8427199d5a6fc71a9b382',
766 '12f2f5e2b38e6ff3fbdb5d722efed9aa72ecb0d5',
766 '12f2f5e2b38e6ff3fbdb5d722efed9aa72ecb0d5',
767 '5eab1222a7cd4bfcbabc218ca6d04276d4e27378',
767 '5eab1222a7cd4bfcbabc218ca6d04276d4e27378',
768 'f50f42baeed5af6518ef4b0cb2f1423f3851a941',
768 'f50f42baeed5af6518ef4b0cb2f1423f3851a941',
769 'd7e390a45f6aa96f04f5e7f583ad4f867431aa25',
769 'd7e390a45f6aa96f04f5e7f583ad4f867431aa25',
770 'f15c21f97864b4f071cddfbf2750ec2e23859414',
770 'f15c21f97864b4f071cddfbf2750ec2e23859414',
771 'e906ef056cf539a4e4e5fc8003eaf7cf14dd8ade',
771 'e906ef056cf539a4e4e5fc8003eaf7cf14dd8ade',
772 'ea2b108b48aa8f8c9c4a941f66c1a03315ca1c3b',
772 'ea2b108b48aa8f8c9c4a941f66c1a03315ca1c3b',
773 '84dec09632a4458f79f50ddbbd155506c460b4f9',
773 '84dec09632a4458f79f50ddbbd155506c460b4f9',
774 '0115510b70c7229dbc5dc49036b32e7d91d23acd',
774 '0115510b70c7229dbc5dc49036b32e7d91d23acd',
775 '2a13f185e4525f9d4b59882791a2d397b90d5ddc',
775 '2a13f185e4525f9d4b59882791a2d397b90d5ddc',
776 '3bf1c5868e570e39569d094f922d33ced2fa3b2b',
776 '3bf1c5868e570e39569d094f922d33ced2fa3b2b',
777 'b8d04012574729d2c29886e53b1a43ef16dd00a1',
777 'b8d04012574729d2c29886e53b1a43ef16dd00a1',
778 '6970b057cffe4aab0a792aa634c89f4bebf01441',
778 '6970b057cffe4aab0a792aa634c89f4bebf01441',
779 'dd80b0f6cf5052f17cc738c2951c4f2070200d7f',
779 'dd80b0f6cf5052f17cc738c2951c4f2070200d7f',
780 'ff7ca51e58c505fec0dd2491de52c622bb7a806b',
780 'ff7ca51e58c505fec0dd2491de52c622bb7a806b',
781 ],
781 ],
782 'vcs/backends/git.py': [
782 'vcs/backends/git.py': [
783 '4cf116ad5a457530381135e2f4c453e68a1b0105',
783 '4cf116ad5a457530381135e2f4c453e68a1b0105',
784 '9a751d84d8e9408e736329767387f41b36935153',
784 '9a751d84d8e9408e736329767387f41b36935153',
785 'cb681fb539c3faaedbcdf5ca71ca413425c18f01',
785 'cb681fb539c3faaedbcdf5ca71ca413425c18f01',
786 '428f81bb652bcba8d631bce926e8834ff49bdcc6',
786 '428f81bb652bcba8d631bce926e8834ff49bdcc6',
787 '180ab15aebf26f98f714d8c68715e0f05fa6e1c7',
787 '180ab15aebf26f98f714d8c68715e0f05fa6e1c7',
788 '2b8e07312a2e89e92b90426ab97f349f4bce2a3a',
788 '2b8e07312a2e89e92b90426ab97f349f4bce2a3a',
789 '50e08c506174d8645a4bb517dd122ac946a0f3bf',
789 '50e08c506174d8645a4bb517dd122ac946a0f3bf',
790 '54000345d2e78b03a99d561399e8e548de3f3203',
790 '54000345d2e78b03a99d561399e8e548de3f3203',
791 ],
791 ],
792 }
792 }
793 for path, commit_ids in files.items():
793 for path, commit_ids in files.items():
794 node = self.repo.get_commit(commit_ids[0]).get_node(path)
794 node = self.repo.get_commit(commit_ids[0]).get_node(path)
795 node_ids = [commit.raw_id for commit in node.history]
795 node_ids = [commit.raw_id for commit in node.history]
796 assert set(commit_ids).issubset(set(node_ids)), (
796 assert set(commit_ids).issubset(set(node_ids)), (
797 "We assumed that %s is subset of commit_ids for which file %s "
797 "We assumed that %s is subset of commit_ids for which file %s "
798 "has been changed, and history of that node returned: %s"
798 "has been changed, and history of that node returned: %s"
799 % (commit_ids, path, node_ids))
799 % (commit_ids, path, node_ids))
800
800
801 def test_file_annotate(self):
801 def test_file_annotate(self):
802 files = {
802 files = {
803 'vcs/backends/__init__.py': {
803 'vcs/backends/__init__.py': {
804 'c1214f7e79e02fc37156ff215cd71275450cffc3': {
804 'c1214f7e79e02fc37156ff215cd71275450cffc3': {
805 'lines_no': 1,
805 'lines_no': 1,
806 'commits': [
806 'commits': [
807 'c1214f7e79e02fc37156ff215cd71275450cffc3',
807 'c1214f7e79e02fc37156ff215cd71275450cffc3',
808 ],
808 ],
809 },
809 },
810 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647': {
810 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647': {
811 'lines_no': 21,
811 'lines_no': 21,
812 'commits': [
812 'commits': [
813 '49d3fd156b6f7db46313fac355dca1a0b94a0017',
813 '49d3fd156b6f7db46313fac355dca1a0b94a0017',
814 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
814 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
815 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
815 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
816 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
816 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
817 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
817 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
818 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
818 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
819 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
819 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
820 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
820 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
821 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
821 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
822 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
822 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
823 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
823 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
824 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
824 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
825 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
825 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
826 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
826 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
827 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
827 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
828 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
828 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
829 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
829 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
830 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
830 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
831 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
831 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
832 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
832 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
833 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
833 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
834 ],
834 ],
835 },
835 },
836 'e29b67bd158580fc90fc5e9111240b90e6e86064': {
836 'e29b67bd158580fc90fc5e9111240b90e6e86064': {
837 'lines_no': 32,
837 'lines_no': 32,
838 'commits': [
838 'commits': [
839 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
839 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
840 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
840 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
841 '5eab1222a7cd4bfcbabc218ca6d04276d4e27378',
841 '5eab1222a7cd4bfcbabc218ca6d04276d4e27378',
842 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
842 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
843 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
843 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
844 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
844 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
845 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
845 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
846 '54000345d2e78b03a99d561399e8e548de3f3203',
846 '54000345d2e78b03a99d561399e8e548de3f3203',
847 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
847 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
848 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
848 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
849 '78c3f0c23b7ee935ec276acb8b8212444c33c396',
849 '78c3f0c23b7ee935ec276acb8b8212444c33c396',
850 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
850 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
851 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
851 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
852 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
852 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
853 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
853 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
854 '2a13f185e4525f9d4b59882791a2d397b90d5ddc',
854 '2a13f185e4525f9d4b59882791a2d397b90d5ddc',
855 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
855 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
856 '78c3f0c23b7ee935ec276acb8b8212444c33c396',
856 '78c3f0c23b7ee935ec276acb8b8212444c33c396',
857 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
857 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
858 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
858 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
859 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
859 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
860 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
860 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
861 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
861 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
862 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
862 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
863 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
863 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
864 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
864 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
865 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
865 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
866 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
866 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
867 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
867 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
868 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
868 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
869 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
869 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
870 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
870 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
871 ],
871 ],
872 },
872 },
873 },
873 },
874 }
874 }
875
875
876 for fname, commit_dict in files.items():
876 for fname, commit_dict in files.items():
877 for commit_id, __ in commit_dict.items():
877 for commit_id, __ in commit_dict.items():
878 commit = self.repo.get_commit(commit_id)
878 commit = self.repo.get_commit(commit_id)
879
879
880 l1_1 = [x[1] for x in commit.get_file_annotate(fname)]
880 l1_1 = [x[1] for x in commit.get_file_annotate(fname)]
881 l1_2 = [x[2]().raw_id for x in commit.get_file_annotate(fname)]
881 l1_2 = [x[2]().raw_id for x in commit.get_file_annotate(fname)]
882 assert l1_1 == l1_2
882 assert l1_1 == l1_2
883 l1 = l1_1
883 l1 = l1_1
884 l2 = files[fname][commit_id]['commits']
884 l2 = files[fname][commit_id]['commits']
885 assert l1 == l2, (
885 assert l1 == l2, (
886 "The lists of commit_ids for %s@commit_id %s"
886 "The lists of commit_ids for %s@commit_id %s"
887 "from annotation list should match each other, "
887 "from annotation list should match each other, "
888 "got \n%s \nvs \n%s " % (fname, commit_id, l1, l2))
888 "got \n%s \nvs \n%s " % (fname, commit_id, l1, l2))
889
889
890 def test_files_state(self):
890 def test_files_state(self):
891 """
891 """
892 Tests state of FileNodes.
892 Tests state of FileNodes.
893 """
893 """
894 node = self.repo\
894 node = self.repo\
895 .get_commit('e6ea6d16e2f26250124a1f4b4fe37a912f9d86a0')\
895 .get_commit('e6ea6d16e2f26250124a1f4b4fe37a912f9d86a0')\
896 .get_node('vcs/utils/diffs.py')
896 .get_node('vcs/utils/diffs.py')
897 assert node.state, NodeState.ADDED
897 assert node.state, NodeState.ADDED
898 assert node.added
898 assert node.added
899 assert not node.changed
899 assert not node.changed
900 assert not node.not_changed
900 assert not node.not_changed
901 assert not node.removed
901 assert not node.removed
902
902
903 node = self.repo\
903 node = self.repo\
904 .get_commit('33fa3223355104431402a888fa77a4e9956feb3e')\
904 .get_commit('33fa3223355104431402a888fa77a4e9956feb3e')\
905 .get_node('.hgignore')
905 .get_node('.hgignore')
906 assert node.state, NodeState.CHANGED
906 assert node.state, NodeState.CHANGED
907 assert not node.added
907 assert not node.added
908 assert node.changed
908 assert node.changed
909 assert not node.not_changed
909 assert not node.not_changed
910 assert not node.removed
910 assert not node.removed
911
911
912 node = self.repo\
912 node = self.repo\
913 .get_commit('e29b67bd158580fc90fc5e9111240b90e6e86064')\
913 .get_commit('e29b67bd158580fc90fc5e9111240b90e6e86064')\
914 .get_node('setup.py')
914 .get_node('setup.py')
915 assert node.state, NodeState.NOT_CHANGED
915 assert node.state, NodeState.NOT_CHANGED
916 assert not node.added
916 assert not node.added
917 assert not node.changed
917 assert not node.changed
918 assert node.not_changed
918 assert node.not_changed
919 assert not node.removed
919 assert not node.removed
920
920
921 # If node has REMOVED state then trying to fetch it would raise
921 # If node has REMOVED state then trying to fetch it would raise
922 # CommitError exception
922 # CommitError exception
923 commit = self.repo.get_commit(
923 commit = self.repo.get_commit(
924 'fa6600f6848800641328adbf7811fd2372c02ab2')
924 'fa6600f6848800641328adbf7811fd2372c02ab2')
925 path = 'vcs/backends/BaseRepository.py'
925 path = 'vcs/backends/BaseRepository.py'
926 with pytest.raises(NodeDoesNotExistError):
926 with pytest.raises(NodeDoesNotExistError):
927 commit.get_node(path)
927 commit.get_node(path)
928 # but it would be one of ``removed`` (commit's attribute)
928 # but it would be one of ``removed`` (commit's attribute)
929 assert path in [rf.path for rf in commit.removed]
929 assert path in [rf.path for rf in commit.removed]
930
930
931 commit = self.repo.get_commit(
931 commit = self.repo.get_commit(
932 '54386793436c938cff89326944d4c2702340037d')
932 '54386793436c938cff89326944d4c2702340037d')
933 changed = [
933 changed = [
934 'setup.py', 'tests/test_nodes.py', 'vcs/backends/hg.py',
934 'setup.py', 'tests/test_nodes.py', 'vcs/backends/hg.py',
935 'vcs/nodes.py']
935 'vcs/nodes.py']
936 assert set(changed) == set([f.path for f in commit.changed])
936 assert set(changed) == set([f.path for f in commit.changed])
937
937
938 def test_unicode_branch_refs(self):
938 def test_unicode_branch_refs(self):
939 unicode_branches = {
939 unicode_branches = {
940 'refs/heads/unicode': '6c0ce52b229aa978889e91b38777f800e85f330b',
940 'refs/heads/unicode': '6c0ce52b229aa978889e91b38777f800e85f330b',
941 u'refs/heads/uniçö∂e': 'ürl',
941 u'refs/heads/uniçö∂e': 'ürl',
942 }
942 }
943 with mock.patch(
943 with mock.patch(
944 ("rhodecode.lib.vcs.backends.git.repository"
944 ("rhodecode.lib.vcs.backends.git.repository"
945 ".GitRepository._refs"),
945 ".GitRepository._refs"),
946 unicode_branches):
946 unicode_branches):
947 branches = self.repo.branches
947 branches = self.repo.branches
948
948
949 assert 'unicode' in branches
949 assert 'unicode' in branches
950 assert u'uniçö∂e' in branches
950 assert u'uniçö∂e' in branches
951
951
952 def test_unicode_tag_refs(self):
952 def test_unicode_tag_refs(self):
953 unicode_tags = {
953 unicode_tags = {
954 'refs/tags/unicode': '6c0ce52b229aa978889e91b38777f800e85f330b',
954 'refs/tags/unicode': '6c0ce52b229aa978889e91b38777f800e85f330b',
955 u'refs/tags/uniçö∂e': '6c0ce52b229aa978889e91b38777f800e85f330b',
955 u'refs/tags/uniçö∂e': '6c0ce52b229aa978889e91b38777f800e85f330b',
956 }
956 }
957 with mock.patch(
957 with mock.patch(
958 ("rhodecode.lib.vcs.backends.git.repository"
958 ("rhodecode.lib.vcs.backends.git.repository"
959 ".GitRepository._refs"),
959 ".GitRepository._refs"),
960 unicode_tags):
960 unicode_tags):
961 tags = self.repo.tags
961 tags = self.repo.tags
962
962
963 assert 'unicode' in tags
963 assert 'unicode' in tags
964 assert u'uniçö∂e' in tags
964 assert u'uniçö∂e' in tags
965
965
966 def test_commit_message_is_unicode(self):
966 def test_commit_message_is_unicode(self):
967 for commit in self.repo:
967 for commit in self.repo:
968 assert type(commit.message) == unicode
968 assert type(commit.message) == unicode
969
969
970 def test_commit_author_is_unicode(self):
970 def test_commit_author_is_unicode(self):
971 for commit in self.repo:
971 for commit in self.repo:
972 assert type(commit.author) == unicode
972 assert type(commit.author) == unicode
973
973
974 def test_repo_files_content_is_unicode(self):
974 def test_repo_files_content_is_unicode(self):
975 commit = self.repo.get_commit()
975 commit = self.repo.get_commit()
976 for node in commit.get_node('/'):
976 for node in commit.get_node('/'):
977 if node.is_file():
977 if node.is_file():
978 assert type(node.content) == unicode
978 assert type(node.content) == unicode
979
979
980 def test_wrong_path(self):
980 def test_wrong_path(self):
981 # There is 'setup.py' in the root dir but not there:
981 # There is 'setup.py' in the root dir but not there:
982 path = 'foo/bar/setup.py'
982 path = 'foo/bar/setup.py'
983 tip = self.repo.get_commit()
983 tip = self.repo.get_commit()
984 with pytest.raises(VCSError):
984 with pytest.raises(VCSError):
985 tip.get_node(path)
985 tip.get_node(path)
986
986
987 @pytest.mark.parametrize("author_email, commit_id", [
987 @pytest.mark.parametrize("author_email, commit_id", [
988 ('marcin@python-blog.com', 'c1214f7e79e02fc37156ff215cd71275450cffc3'),
988 ('marcin@python-blog.com', 'c1214f7e79e02fc37156ff215cd71275450cffc3'),
989 ('lukasz.balcerzak@python-center.pl',
989 ('lukasz.balcerzak@python-center.pl',
990 'ff7ca51e58c505fec0dd2491de52c622bb7a806b'),
990 'ff7ca51e58c505fec0dd2491de52c622bb7a806b'),
991 ('none@none', '8430a588b43b5d6da365400117c89400326e7992'),
991 ('none@none', '8430a588b43b5d6da365400117c89400326e7992'),
992 ])
992 ])
993 def test_author_email(self, author_email, commit_id):
993 def test_author_email(self, author_email, commit_id):
994 commit = self.repo.get_commit(commit_id)
994 commit = self.repo.get_commit(commit_id)
995 assert author_email == commit.author_email
995 assert author_email == commit.author_email
996
996
997 @pytest.mark.parametrize("author, commit_id", [
997 @pytest.mark.parametrize("author, commit_id", [
998 ('Marcin Kuzminski', 'c1214f7e79e02fc37156ff215cd71275450cffc3'),
998 ('Marcin Kuzminski', 'c1214f7e79e02fc37156ff215cd71275450cffc3'),
999 ('Lukasz Balcerzak', 'ff7ca51e58c505fec0dd2491de52c622bb7a806b'),
999 ('Lukasz Balcerzak', 'ff7ca51e58c505fec0dd2491de52c622bb7a806b'),
1000 ('marcink', '8430a588b43b5d6da365400117c89400326e7992'),
1000 ('marcink', '8430a588b43b5d6da365400117c89400326e7992'),
1001 ])
1001 ])
1002 def test_author_username(self, author, commit_id):
1002 def test_author_username(self, author, commit_id):
1003 commit = self.repo.get_commit(commit_id)
1003 commit = self.repo.get_commit(commit_id)
1004 assert author == commit.author_name
1004 assert author == commit.author_name
1005
1005
1006
1006
1007 class TestLargeFileRepo(object):
1007 class TestLargeFileRepo(object):
1008
1008
1009 def test_large_file(self, backend_git):
1009 def test_large_file(self, backend_git):
1010 conf = make_db_config()
1010 conf = make_db_config()
1011 repo = backend_git.create_test_repo('largefiles', conf)
1011 repo = backend_git.create_test_repo('largefiles', conf)
1012
1012
1013 tip = repo.scm_instance().get_commit()
1013 tip = repo.scm_instance().get_commit()
1014
1014
1015 # extract stored LF node into the origin cache
1015 # extract stored LF node into the origin cache
1016 lfs_store = os.path.join(repo.repo_path, repo.repo_name, 'lfs_store')
1016 lfs_store = os.path.join(repo.repo_path, repo.repo_name, 'lfs_store')
1017
1017
1018 oid = '7b331c02e313c7599d5a90212e17e6d3cb729bd2e1c9b873c302a63c95a2f9bf'
1018 oid = '7b331c02e313c7599d5a90212e17e6d3cb729bd2e1c9b873c302a63c95a2f9bf'
1019 oid_path = os.path.join(lfs_store, oid)
1019 oid_path = os.path.join(lfs_store, oid)
1020 oid_destination = os.path.join(
1020 oid_destination = os.path.join(
1021 conf.get('vcs_git_lfs', 'store_location'), oid)
1021 conf.get('vcs_git_lfs', 'store_location'), oid)
1022 shutil.copy(oid_path, oid_destination)
1022 shutil.copy(oid_path, oid_destination)
1023
1023
1024 node = tip.get_node('1MB.zip')
1024 node = tip.get_node('1MB.zip')
1025
1025
1026 lf_node = node.get_largefile_node()
1026 lf_node = node.get_largefile_node()
1027
1027
1028 assert lf_node.is_largefile() is True
1028 assert lf_node.is_largefile() is True
1029 assert lf_node.size == 1024000
1029 assert lf_node.size == 1024000
1030 assert lf_node.name == '1MB.zip'
1030 assert lf_node.name == '1MB.zip'
1031
1031
1032
1032
1033 @pytest.mark.usefixtures("vcs_repository_support")
1033 class TestGitSpecificWithRepo(BackendTestMixin):
1034 class TestGitSpecificWithRepo(BackendTestMixin):
1034
1035
1035 @classmethod
1036 @classmethod
1036 def _get_commits(cls):
1037 def _get_commits(cls):
1037 return [
1038 return [
1038 {
1039 {
1039 'message': 'Initial',
1040 'message': 'Initial',
1040 'author': 'Joe Doe <joe.doe@example.com>',
1041 'author': 'Joe Doe <joe.doe@example.com>',
1041 'date': datetime.datetime(2010, 1, 1, 20),
1042 'date': datetime.datetime(2010, 1, 1, 20),
1042 'added': [
1043 'added': [
1043 FileNode('foobar/static/js/admin/base.js', content='base'),
1044 FileNode('foobar/static/js/admin/base.js', content='base'),
1044 FileNode(
1045 FileNode(
1045 'foobar/static/admin', content='admin',
1046 'foobar/static/admin', content='admin',
1046 mode=0120000), # this is a link
1047 mode=0120000), # this is a link
1047 FileNode('foo', content='foo'),
1048 FileNode('foo', content='foo'),
1048 ],
1049 ],
1049 },
1050 },
1050 {
1051 {
1051 'message': 'Second',
1052 'message': 'Second',
1052 'author': 'Joe Doe <joe.doe@example.com>',
1053 'author': 'Joe Doe <joe.doe@example.com>',
1053 'date': datetime.datetime(2010, 1, 1, 22),
1054 'date': datetime.datetime(2010, 1, 1, 22),
1054 'added': [
1055 'added': [
1055 FileNode('foo2', content='foo2'),
1056 FileNode('foo2', content='foo2'),
1056 ],
1057 ],
1057 },
1058 },
1058 ]
1059 ]
1059
1060
1060 def test_paths_slow_traversing(self):
1061 def test_paths_slow_traversing(self):
1061 commit = self.repo.get_commit()
1062 commit = self.repo.get_commit()
1062 assert commit.get_node('foobar').get_node('static').get_node('js')\
1063 assert commit.get_node('foobar').get_node('static').get_node('js')\
1063 .get_node('admin').get_node('base.js').content == 'base'
1064 .get_node('admin').get_node('base.js').content == 'base'
1064
1065
1065 def test_paths_fast_traversing(self):
1066 def test_paths_fast_traversing(self):
1066 commit = self.repo.get_commit()
1067 commit = self.repo.get_commit()
1067 assert (
1068 assert (
1068 commit.get_node('foobar/static/js/admin/base.js').content ==
1069 commit.get_node('foobar/static/js/admin/base.js').content ==
1069 'base')
1070 'base')
1070
1071
1071 def test_get_diff_runs_git_command_with_hashes(self):
1072 def test_get_diff_runs_git_command_with_hashes(self):
1072 self.repo.run_git_command = mock.Mock(return_value=['', ''])
1073 self.repo.run_git_command = mock.Mock(return_value=['', ''])
1073 self.repo.get_diff(self.repo[0], self.repo[1])
1074 self.repo.get_diff(self.repo[0], self.repo[1])
1074 self.repo.run_git_command.assert_called_once_with(
1075 self.repo.run_git_command.assert_called_once_with(
1075 ['diff', '-U3', '--full-index', '--binary', '-p', '-M',
1076 ['diff', '-U3', '--full-index', '--binary', '-p', '-M',
1076 '--abbrev=40', self.repo._get_commit_id(0),
1077 '--abbrev=40', self.repo._get_commit_id(0),
1077 self.repo._get_commit_id(1)])
1078 self.repo._get_commit_id(1)])
1078
1079
1079 def test_get_diff_runs_git_command_with_str_hashes(self):
1080 def test_get_diff_runs_git_command_with_str_hashes(self):
1080 self.repo.run_git_command = mock.Mock(return_value=['', ''])
1081 self.repo.run_git_command = mock.Mock(return_value=['', ''])
1081 self.repo.get_diff(self.repo.EMPTY_COMMIT, self.repo[1])
1082 self.repo.get_diff(self.repo.EMPTY_COMMIT, self.repo[1])
1082 self.repo.run_git_command.assert_called_once_with(
1083 self.repo.run_git_command.assert_called_once_with(
1083 ['show', '-U3', '--full-index', '--binary', '-p', '-M',
1084 ['show', '-U3', '--full-index', '--binary', '-p', '-M',
1084 '--abbrev=40', self.repo._get_commit_id(1)])
1085 '--abbrev=40', self.repo._get_commit_id(1)])
1085
1086
1086 def test_get_diff_runs_git_command_with_path_if_its_given(self):
1087 def test_get_diff_runs_git_command_with_path_if_its_given(self):
1087 self.repo.run_git_command = mock.Mock(return_value=['', ''])
1088 self.repo.run_git_command = mock.Mock(return_value=['', ''])
1088 self.repo.get_diff(self.repo[0], self.repo[1], 'foo')
1089 self.repo.get_diff(self.repo[0], self.repo[1], 'foo')
1089 self.repo.run_git_command.assert_called_once_with(
1090 self.repo.run_git_command.assert_called_once_with(
1090 ['diff', '-U3', '--full-index', '--binary', '-p', '-M',
1091 ['diff', '-U3', '--full-index', '--binary', '-p', '-M',
1091 '--abbrev=40', self.repo._get_commit_id(0),
1092 '--abbrev=40', self.repo._get_commit_id(0),
1092 self.repo._get_commit_id(1), '--', 'foo'])
1093 self.repo._get_commit_id(1), '--', 'foo'])
1093
1094
1094
1095
1096 @pytest.mark.usefixtures("vcs_repository_support")
1095 class TestGitRegression(BackendTestMixin):
1097 class TestGitRegression(BackendTestMixin):
1096
1098
1097 @classmethod
1099 @classmethod
1098 def _get_commits(cls):
1100 def _get_commits(cls):
1099 return [
1101 return [
1100 {
1102 {
1101 'message': 'Initial',
1103 'message': 'Initial',
1102 'author': 'Joe Doe <joe.doe@example.com>',
1104 'author': 'Joe Doe <joe.doe@example.com>',
1103 'date': datetime.datetime(2010, 1, 1, 20),
1105 'date': datetime.datetime(2010, 1, 1, 20),
1104 'added': [
1106 'added': [
1105 FileNode('bot/__init__.py', content='base'),
1107 FileNode('bot/__init__.py', content='base'),
1106 FileNode('bot/templates/404.html', content='base'),
1108 FileNode('bot/templates/404.html', content='base'),
1107 FileNode('bot/templates/500.html', content='base'),
1109 FileNode('bot/templates/500.html', content='base'),
1108 ],
1110 ],
1109 },
1111 },
1110 {
1112 {
1111 'message': 'Second',
1113 'message': 'Second',
1112 'author': 'Joe Doe <joe.doe@example.com>',
1114 'author': 'Joe Doe <joe.doe@example.com>',
1113 'date': datetime.datetime(2010, 1, 1, 22),
1115 'date': datetime.datetime(2010, 1, 1, 22),
1114 'added': [
1116 'added': [
1115 FileNode('bot/build/migrations/1.py', content='foo2'),
1117 FileNode('bot/build/migrations/1.py', content='foo2'),
1116 FileNode('bot/build/migrations/2.py', content='foo2'),
1118 FileNode('bot/build/migrations/2.py', content='foo2'),
1117 FileNode(
1119 FileNode(
1118 'bot/build/static/templates/f.html', content='foo2'),
1120 'bot/build/static/templates/f.html', content='foo2'),
1119 FileNode(
1121 FileNode(
1120 'bot/build/static/templates/f1.html', content='foo2'),
1122 'bot/build/static/templates/f1.html', content='foo2'),
1121 FileNode('bot/build/templates/err.html', content='foo2'),
1123 FileNode('bot/build/templates/err.html', content='foo2'),
1122 FileNode('bot/build/templates/err2.html', content='foo2'),
1124 FileNode('bot/build/templates/err2.html', content='foo2'),
1123 ],
1125 ],
1124 },
1126 },
1125 ]
1127 ]
1126
1128
1127 @pytest.mark.parametrize("path, expected_paths", [
1129 @pytest.mark.parametrize("path, expected_paths", [
1128 ('bot', [
1130 ('bot', [
1129 'bot/build',
1131 'bot/build',
1130 'bot/templates',
1132 'bot/templates',
1131 'bot/__init__.py']),
1133 'bot/__init__.py']),
1132 ('bot/build', [
1134 ('bot/build', [
1133 'bot/build/migrations',
1135 'bot/build/migrations',
1134 'bot/build/static',
1136 'bot/build/static',
1135 'bot/build/templates']),
1137 'bot/build/templates']),
1136 ('bot/build/static', [
1138 ('bot/build/static', [
1137 'bot/build/static/templates']),
1139 'bot/build/static/templates']),
1138 ('bot/build/static/templates', [
1140 ('bot/build/static/templates', [
1139 'bot/build/static/templates/f.html',
1141 'bot/build/static/templates/f.html',
1140 'bot/build/static/templates/f1.html']),
1142 'bot/build/static/templates/f1.html']),
1141 ('bot/build/templates', [
1143 ('bot/build/templates', [
1142 'bot/build/templates/err.html',
1144 'bot/build/templates/err.html',
1143 'bot/build/templates/err2.html']),
1145 'bot/build/templates/err2.html']),
1144 ('bot/templates/', [
1146 ('bot/templates/', [
1145 'bot/templates/404.html',
1147 'bot/templates/404.html',
1146 'bot/templates/500.html']),
1148 'bot/templates/500.html']),
1147 ])
1149 ])
1148 def test_similar_paths(self, path, expected_paths):
1150 def test_similar_paths(self, path, expected_paths):
1149 commit = self.repo.get_commit()
1151 commit = self.repo.get_commit()
1150 paths = [n.path for n in commit.get_nodes(path)]
1152 paths = [n.path for n in commit.get_nodes(path)]
1151 assert paths == expected_paths
1153 assert paths == expected_paths
1152
1154
1153
1155
1154 class TestDiscoverGitVersion:
1156 class TestDiscoverGitVersion:
1155
1157
1156 def test_returns_git_version(self, baseapp):
1158 def test_returns_git_version(self, baseapp):
1157 version = discover_git_version()
1159 version = discover_git_version()
1158 assert version
1160 assert version
1159
1161
1160 def test_returns_empty_string_without_vcsserver(self):
1162 def test_returns_empty_string_without_vcsserver(self):
1161 mock_connection = mock.Mock()
1163 mock_connection = mock.Mock()
1162 mock_connection.discover_git_version = mock.Mock(
1164 mock_connection.discover_git_version = mock.Mock(
1163 side_effect=Exception)
1165 side_effect=Exception)
1164 with mock.patch('rhodecode.lib.vcs.connection.Git', mock_connection):
1166 with mock.patch('rhodecode.lib.vcs.connection.Git', mock_connection):
1165 version = discover_git_version()
1167 version = discover_git_version()
1166 assert version == ''
1168 assert version == ''
1167
1169
1168
1170
1169 class TestGetSubmoduleUrl(object):
1171 class TestGetSubmoduleUrl(object):
1170 def test_submodules_file_found(self):
1172 def test_submodules_file_found(self):
1171 commit = GitCommit(repository=mock.Mock(), raw_id='abcdef12', idx=1)
1173 commit = GitCommit(repository=mock.Mock(), raw_id='abcdef12', idx=1)
1172 node = mock.Mock()
1174 node = mock.Mock()
1173 with mock.patch.object(
1175 with mock.patch.object(
1174 commit, 'get_node', return_value=node) as get_node_mock:
1176 commit, 'get_node', return_value=node) as get_node_mock:
1175 node.content = (
1177 node.content = (
1176 '[submodule "subrepo1"]\n'
1178 '[submodule "subrepo1"]\n'
1177 '\tpath = subrepo1\n'
1179 '\tpath = subrepo1\n'
1178 '\turl = https://code.rhodecode.com/dulwich\n'
1180 '\turl = https://code.rhodecode.com/dulwich\n'
1179 )
1181 )
1180 result = commit._get_submodule_url('subrepo1')
1182 result = commit._get_submodule_url('subrepo1')
1181 get_node_mock.assert_called_once_with('.gitmodules')
1183 get_node_mock.assert_called_once_with('.gitmodules')
1182 assert result == 'https://code.rhodecode.com/dulwich'
1184 assert result == 'https://code.rhodecode.com/dulwich'
1183
1185
1184 def test_complex_submodule_path(self):
1186 def test_complex_submodule_path(self):
1185 commit = GitCommit(repository=mock.Mock(), raw_id='abcdef12', idx=1)
1187 commit = GitCommit(repository=mock.Mock(), raw_id='abcdef12', idx=1)
1186 node = mock.Mock()
1188 node = mock.Mock()
1187 with mock.patch.object(
1189 with mock.patch.object(
1188 commit, 'get_node', return_value=node) as get_node_mock:
1190 commit, 'get_node', return_value=node) as get_node_mock:
1189 node.content = (
1191 node.content = (
1190 '[submodule "complex/subrepo/path"]\n'
1192 '[submodule "complex/subrepo/path"]\n'
1191 '\tpath = complex/subrepo/path\n'
1193 '\tpath = complex/subrepo/path\n'
1192 '\turl = https://code.rhodecode.com/dulwich\n'
1194 '\turl = https://code.rhodecode.com/dulwich\n'
1193 )
1195 )
1194 result = commit._get_submodule_url('complex/subrepo/path')
1196 result = commit._get_submodule_url('complex/subrepo/path')
1195 get_node_mock.assert_called_once_with('.gitmodules')
1197 get_node_mock.assert_called_once_with('.gitmodules')
1196 assert result == 'https://code.rhodecode.com/dulwich'
1198 assert result == 'https://code.rhodecode.com/dulwich'
1197
1199
1198 def test_submodules_file_not_found(self):
1200 def test_submodules_file_not_found(self):
1199 commit = GitCommit(repository=mock.Mock(), raw_id='abcdef12', idx=1)
1201 commit = GitCommit(repository=mock.Mock(), raw_id='abcdef12', idx=1)
1200 with mock.patch.object(
1202 with mock.patch.object(
1201 commit, 'get_node', side_effect=NodeDoesNotExistError):
1203 commit, 'get_node', side_effect=NodeDoesNotExistError):
1202 result = commit._get_submodule_url('complex/subrepo/path')
1204 result = commit._get_submodule_url('complex/subrepo/path')
1203 assert result is None
1205 assert result is None
1204
1206
1205 def test_path_not_found(self):
1207 def test_path_not_found(self):
1206 commit = GitCommit(repository=mock.Mock(), raw_id='abcdef12', idx=1)
1208 commit = GitCommit(repository=mock.Mock(), raw_id='abcdef12', idx=1)
1207 node = mock.Mock()
1209 node = mock.Mock()
1208 with mock.patch.object(
1210 with mock.patch.object(
1209 commit, 'get_node', return_value=node) as get_node_mock:
1211 commit, 'get_node', return_value=node) as get_node_mock:
1210 node.content = (
1212 node.content = (
1211 '[submodule "subrepo1"]\n'
1213 '[submodule "subrepo1"]\n'
1212 '\tpath = subrepo1\n'
1214 '\tpath = subrepo1\n'
1213 '\turl = https://code.rhodecode.com/dulwich\n'
1215 '\turl = https://code.rhodecode.com/dulwich\n'
1214 )
1216 )
1215 result = commit._get_submodule_url('subrepo2')
1217 result = commit._get_submodule_url('subrepo2')
1216 get_node_mock.assert_called_once_with('.gitmodules')
1218 get_node_mock.assert_called_once_with('.gitmodules')
1217 assert result is None
1219 assert result is None
1218
1220
1219 def test_returns_cached_values(self):
1221 def test_returns_cached_values(self):
1220 commit = GitCommit(repository=mock.Mock(), raw_id='abcdef12', idx=1)
1222 commit = GitCommit(repository=mock.Mock(), raw_id='abcdef12', idx=1)
1221 node = mock.Mock()
1223 node = mock.Mock()
1222 with mock.patch.object(
1224 with mock.patch.object(
1223 commit, 'get_node', return_value=node) as get_node_mock:
1225 commit, 'get_node', return_value=node) as get_node_mock:
1224 node.content = (
1226 node.content = (
1225 '[submodule "subrepo1"]\n'
1227 '[submodule "subrepo1"]\n'
1226 '\tpath = subrepo1\n'
1228 '\tpath = subrepo1\n'
1227 '\turl = https://code.rhodecode.com/dulwich\n'
1229 '\turl = https://code.rhodecode.com/dulwich\n'
1228 )
1230 )
1229 for _ in range(3):
1231 for _ in range(3):
1230 commit._get_submodule_url('subrepo1')
1232 commit._get_submodule_url('subrepo1')
1231 get_node_mock.assert_called_once_with('.gitmodules')
1233 get_node_mock.assert_called_once_with('.gitmodules')
1232
1234
1233 def test_get_node_returns_a_link(self):
1235 def test_get_node_returns_a_link(self):
1234 repository = mock.Mock()
1236 repository = mock.Mock()
1235 repository.alias = 'git'
1237 repository.alias = 'git'
1236 commit = GitCommit(repository=repository, raw_id='abcdef12', idx=1)
1238 commit = GitCommit(repository=repository, raw_id='abcdef12', idx=1)
1237 submodule_url = 'https://code.rhodecode.com/dulwich'
1239 submodule_url = 'https://code.rhodecode.com/dulwich'
1238 get_id_patch = mock.patch.object(
1240 get_id_patch = mock.patch.object(
1239 commit, '_get_id_for_path', return_value=(1, 'link'))
1241 commit, '_get_id_for_path', return_value=(1, 'link'))
1240 get_submodule_patch = mock.patch.object(
1242 get_submodule_patch = mock.patch.object(
1241 commit, '_get_submodule_url', return_value=submodule_url)
1243 commit, '_get_submodule_url', return_value=submodule_url)
1242
1244
1243 with get_id_patch, get_submodule_patch as submodule_mock:
1245 with get_id_patch, get_submodule_patch as submodule_mock:
1244 node = commit.get_node('/abcde')
1246 node = commit.get_node('/abcde')
1245
1247
1246 submodule_mock.assert_called_once_with('/abcde')
1248 submodule_mock.assert_called_once_with('/abcde')
1247 assert type(node) == SubModuleNode
1249 assert type(node) == SubModuleNode
1248 assert node.url == submodule_url
1250 assert node.url == submodule_url
1249
1251
1250 def test_get_nodes_returns_links(self):
1252 def test_get_nodes_returns_links(self):
1251 repository = mock.MagicMock()
1253 repository = mock.MagicMock()
1252 repository.alias = 'git'
1254 repository.alias = 'git'
1253 repository._remote.tree_items.return_value = [
1255 repository._remote.tree_items.return_value = [
1254 ('subrepo', 'stat', 1, 'link')
1256 ('subrepo', 'stat', 1, 'link')
1255 ]
1257 ]
1256 commit = GitCommit(repository=repository, raw_id='abcdef12', idx=1)
1258 commit = GitCommit(repository=repository, raw_id='abcdef12', idx=1)
1257 submodule_url = 'https://code.rhodecode.com/dulwich'
1259 submodule_url = 'https://code.rhodecode.com/dulwich'
1258 get_id_patch = mock.patch.object(
1260 get_id_patch = mock.patch.object(
1259 commit, '_get_id_for_path', return_value=(1, 'tree'))
1261 commit, '_get_id_for_path', return_value=(1, 'tree'))
1260 get_submodule_patch = mock.patch.object(
1262 get_submodule_patch = mock.patch.object(
1261 commit, '_get_submodule_url', return_value=submodule_url)
1263 commit, '_get_submodule_url', return_value=submodule_url)
1262
1264
1263 with get_id_patch, get_submodule_patch as submodule_mock:
1265 with get_id_patch, get_submodule_patch as submodule_mock:
1264 nodes = commit.get_nodes('/abcde')
1266 nodes = commit.get_nodes('/abcde')
1265
1267
1266 submodule_mock.assert_called_once_with('/abcde/subrepo')
1268 submodule_mock.assert_called_once_with('/abcde/subrepo')
1267 assert len(nodes) == 1
1269 assert len(nodes) == 1
1268 assert type(nodes[0]) == SubModuleNode
1270 assert type(nodes[0]) == SubModuleNode
1269 assert nodes[0].url == submodule_url
1271 assert nodes[0].url == submodule_url
@@ -1,344 +1,345 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Tests so called "in memory commits" commit API of vcs.
22 Tests so called "in memory commits" commit API of vcs.
23 """
23 """
24 import datetime
24 import datetime
25
25
26 import pytest
26 import pytest
27
27
28 from rhodecode.lib.utils2 import safe_unicode
28 from rhodecode.lib.utils2 import safe_unicode
29 from rhodecode.lib.vcs.exceptions import (
29 from rhodecode.lib.vcs.exceptions import (
30 EmptyRepositoryError, NodeAlreadyAddedError, NodeAlreadyExistsError,
30 EmptyRepositoryError, NodeAlreadyAddedError, NodeAlreadyExistsError,
31 NodeAlreadyRemovedError, NodeAlreadyChangedError, NodeDoesNotExistError,
31 NodeAlreadyRemovedError, NodeAlreadyChangedError, NodeDoesNotExistError,
32 NodeNotChangedError)
32 NodeNotChangedError)
33 from rhodecode.lib.vcs.nodes import DirNode, FileNode
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 @pytest.fixture
37 @pytest.fixture
38 def nodes():
38 def nodes():
39 nodes = [
39 nodes = [
40 FileNode('foobar', content='Foo & bar'),
40 FileNode('foobar', content='Foo & bar'),
41 FileNode('foobar2', content='Foo & bar, doubled!'),
41 FileNode('foobar2', content='Foo & bar, doubled!'),
42 FileNode('foo bar with spaces', content=''),
42 FileNode('foo bar with spaces', content=''),
43 FileNode('foo/bar/baz', content='Inside'),
43 FileNode('foo/bar/baz', content='Inside'),
44 FileNode(
44 FileNode(
45 'foo/bar/file.bin',
45 'foo/bar/file.bin',
46 content=(
46 content=(
47 '\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1\x00\x00\x00\x00\x00\x00'
47 '\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1\x00\x00\x00\x00\x00\x00'
48 '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00;\x00\x03\x00\xfe'
48 '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00;\x00\x03\x00\xfe'
49 '\xff\t\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
49 '\xff\t\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
50 '\x01\x00\x00\x00\x1a\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00'
50 '\x01\x00\x00\x00\x1a\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00'
51 '\x00\x18\x00\x00\x00\x01\x00\x00\x00\xfe\xff\xff\xff\x00\x00'
51 '\x00\x18\x00\x00\x00\x01\x00\x00\x00\xfe\xff\xff\xff\x00\x00'
52 '\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff'
52 '\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff'
53 '\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
53 '\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
54 )
54 )
55 ),
55 ),
56 ]
56 ]
57 return nodes
57 return nodes
58
58
59
59
60 @pytest.mark.usefixtures("vcs_repository_support")
60 class TestInMemoryCommit(BackendTestMixin):
61 class TestInMemoryCommit(BackendTestMixin):
61 """
62 """
62 This is a backend independent test case class which should be created
63 This is a backend independent test case class which should be created
63 with ``type`` method.
64 with ``type`` method.
64
65
65 It is required to set following attributes at subclass:
66 It is required to set following attributes at subclass:
66
67
67 - ``backend_alias``: alias of used backend (see ``vcs.BACKENDS``)
68 - ``backend_alias``: alias of used backend (see ``vcs.BACKENDS``)
68 """
69 """
69
70
70 @classmethod
71 @classmethod
71 def _get_commits(cls):
72 def _get_commits(cls):
72 return []
73 return []
73
74
74 def test_add(self, nodes):
75 def test_add(self, nodes):
75 for node in nodes:
76 for node in nodes:
76 self.imc.add(node)
77 self.imc.add(node)
77
78
78 self.commit()
79 self.commit()
79 self.assert_succesful_commit(nodes)
80 self.assert_succesful_commit(nodes)
80
81
81 @pytest.mark.skip_backends(
82 @pytest.mark.skip_backends(
82 'svn', reason="Svn does not support commits on branches.")
83 'svn', reason="Svn does not support commits on branches.")
83 def test_add_on_branch(self, nodes):
84 def test_add_on_branch(self, nodes):
84 for node in nodes:
85 for node in nodes:
85 self.imc.add(node)
86 self.imc.add(node)
86 self.commit(branch=u'stable')
87 self.commit(branch=u'stable')
87 self.assert_succesful_commit(nodes)
88 self.assert_succesful_commit(nodes)
88
89
89 def test_add_in_bulk(self, nodes):
90 def test_add_in_bulk(self, nodes):
90 self.imc.add(*nodes)
91 self.imc.add(*nodes)
91
92
92 self.commit()
93 self.commit()
93 self.assert_succesful_commit(nodes)
94 self.assert_succesful_commit(nodes)
94
95
95 def test_add_non_ascii_files(self):
96 def test_add_non_ascii_files(self):
96 nodes = [
97 nodes = [
97 FileNode('żółwik/zwierzątko_utf8_str', content='ćććć'),
98 FileNode('żółwik/zwierzątko_utf8_str', content='ćććć'),
98 FileNode(u'żółwik/zwierzątko_unicode', content=u'ćććć'),
99 FileNode(u'żółwik/zwierzątko_unicode', content=u'ćććć'),
99 ]
100 ]
100
101
101 for node in nodes:
102 for node in nodes:
102 self.imc.add(node)
103 self.imc.add(node)
103
104
104 self.commit()
105 self.commit()
105 self.assert_succesful_commit(nodes)
106 self.assert_succesful_commit(nodes)
106
107
107 def commit(self, branch=None):
108 def commit(self, branch=None):
108 self.old_commit_count = len(self.repo.commit_ids)
109 self.old_commit_count = len(self.repo.commit_ids)
109 self.commit_message = u'Test commit with unicode: żółwik'
110 self.commit_message = u'Test commit with unicode: żółwik'
110 self.commit_author = unicode(self.__class__)
111 self.commit_author = unicode(self.__class__)
111 self.commit = self.imc.commit(
112 self.commit = self.imc.commit(
112 message=self.commit_message, author=self.commit_author,
113 message=self.commit_message, author=self.commit_author,
113 branch=branch)
114 branch=branch)
114
115
115 def test_add_actually_adds_all_nodes_at_second_commit_too(self):
116 def test_add_actually_adds_all_nodes_at_second_commit_too(self):
116 to_add = [
117 to_add = [
117 FileNode('foo/bar/image.png', content='\0'),
118 FileNode('foo/bar/image.png', content='\0'),
118 FileNode('foo/README.txt', content='readme!'),
119 FileNode('foo/README.txt', content='readme!'),
119 ]
120 ]
120 self.imc.add(*to_add)
121 self.imc.add(*to_add)
121 commit = self.imc.commit(u'Initial', u'joe.doe@example.com')
122 commit = self.imc.commit(u'Initial', u'joe.doe@example.com')
122 assert isinstance(commit.get_node('foo'), DirNode)
123 assert isinstance(commit.get_node('foo'), DirNode)
123 assert isinstance(commit.get_node('foo/bar'), DirNode)
124 assert isinstance(commit.get_node('foo/bar'), DirNode)
124 self.assert_nodes_in_commit(commit, to_add)
125 self.assert_nodes_in_commit(commit, to_add)
125
126
126 # commit some more files again
127 # commit some more files again
127 to_add = [
128 to_add = [
128 FileNode('foo/bar/foobaz/bar', content='foo'),
129 FileNode('foo/bar/foobaz/bar', content='foo'),
129 FileNode('foo/bar/another/bar', content='foo'),
130 FileNode('foo/bar/another/bar', content='foo'),
130 FileNode('foo/baz.txt', content='foo'),
131 FileNode('foo/baz.txt', content='foo'),
131 FileNode('foobar/foobaz/file', content='foo'),
132 FileNode('foobar/foobaz/file', content='foo'),
132 FileNode('foobar/barbaz', content='foo'),
133 FileNode('foobar/barbaz', content='foo'),
133 ]
134 ]
134 self.imc.add(*to_add)
135 self.imc.add(*to_add)
135 commit = self.imc.commit(u'Another', u'joe.doe@example.com')
136 commit = self.imc.commit(u'Another', u'joe.doe@example.com')
136 self.assert_nodes_in_commit(commit, to_add)
137 self.assert_nodes_in_commit(commit, to_add)
137
138
138 def test_add_raise_already_added(self):
139 def test_add_raise_already_added(self):
139 node = FileNode('foobar', content='baz')
140 node = FileNode('foobar', content='baz')
140 self.imc.add(node)
141 self.imc.add(node)
141 with pytest.raises(NodeAlreadyAddedError):
142 with pytest.raises(NodeAlreadyAddedError):
142 self.imc.add(node)
143 self.imc.add(node)
143
144
144 def test_check_integrity_raise_already_exist(self):
145 def test_check_integrity_raise_already_exist(self):
145 node = FileNode('foobar', content='baz')
146 node = FileNode('foobar', content='baz')
146 self.imc.add(node)
147 self.imc.add(node)
147 self.imc.commit(message=u'Added foobar', author=unicode(self))
148 self.imc.commit(message=u'Added foobar', author=unicode(self))
148 self.imc.add(node)
149 self.imc.add(node)
149 with pytest.raises(NodeAlreadyExistsError):
150 with pytest.raises(NodeAlreadyExistsError):
150 self.imc.commit(message='new message', author=str(self))
151 self.imc.commit(message='new message', author=str(self))
151
152
152 def test_change(self):
153 def test_change(self):
153 self.imc.add(FileNode('foo/bar/baz', content='foo'))
154 self.imc.add(FileNode('foo/bar/baz', content='foo'))
154 self.imc.add(FileNode('foo/fbar', content='foobar'))
155 self.imc.add(FileNode('foo/fbar', content='foobar'))
155 tip = self.imc.commit(u'Initial', u'joe.doe@example.com')
156 tip = self.imc.commit(u'Initial', u'joe.doe@example.com')
156
157
157 # Change node's content
158 # Change node's content
158 node = FileNode('foo/bar/baz', content='My **changed** content')
159 node = FileNode('foo/bar/baz', content='My **changed** content')
159 self.imc.change(node)
160 self.imc.change(node)
160 self.imc.commit(u'Changed %s' % node.path, u'joe.doe@example.com')
161 self.imc.commit(u'Changed %s' % node.path, u'joe.doe@example.com')
161
162
162 newtip = self.repo.get_commit()
163 newtip = self.repo.get_commit()
163 assert tip != newtip
164 assert tip != newtip
164 assert tip.id != newtip.id
165 assert tip.id != newtip.id
165 self.assert_nodes_in_commit(newtip, (node,))
166 self.assert_nodes_in_commit(newtip, (node,))
166
167
167 def test_change_non_ascii(self):
168 def test_change_non_ascii(self):
168 to_add = [
169 to_add = [
169 FileNode('żółwik/zwierzątko', content='ćććć'),
170 FileNode('żółwik/zwierzątko', content='ćććć'),
170 FileNode(u'żółwik/zwierzątko_uni', content=u'ćććć'),
171 FileNode(u'żółwik/zwierzątko_uni', content=u'ćććć'),
171 ]
172 ]
172 for node in to_add:
173 for node in to_add:
173 self.imc.add(node)
174 self.imc.add(node)
174
175
175 tip = self.imc.commit(u'Initial', u'joe.doe@example.com')
176 tip = self.imc.commit(u'Initial', u'joe.doe@example.com')
176
177
177 # Change node's content
178 # Change node's content
178 node = FileNode('żółwik/zwierzątko', content='My **changed** content')
179 node = FileNode('żółwik/zwierzątko', content='My **changed** content')
179 self.imc.change(node)
180 self.imc.change(node)
180 self.imc.commit(u'Changed %s' % safe_unicode(node.path),
181 self.imc.commit(u'Changed %s' % safe_unicode(node.path),
181 u'joe.doe@example.com')
182 u'joe.doe@example.com')
182
183
183 node_uni = FileNode(
184 node_uni = FileNode(
184 u'żółwik/zwierzątko_uni', content=u'My **changed** content')
185 u'żółwik/zwierzątko_uni', content=u'My **changed** content')
185 self.imc.change(node_uni)
186 self.imc.change(node_uni)
186 self.imc.commit(u'Changed %s' % safe_unicode(node_uni.path),
187 self.imc.commit(u'Changed %s' % safe_unicode(node_uni.path),
187 u'joe.doe@example.com')
188 u'joe.doe@example.com')
188
189
189 newtip = self.repo.get_commit()
190 newtip = self.repo.get_commit()
190 assert tip != newtip
191 assert tip != newtip
191 assert tip.id != newtip.id
192 assert tip.id != newtip.id
192
193
193 self.assert_nodes_in_commit(newtip, (node, node_uni))
194 self.assert_nodes_in_commit(newtip, (node, node_uni))
194
195
195 def test_change_raise_empty_repository(self):
196 def test_change_raise_empty_repository(self):
196 node = FileNode('foobar')
197 node = FileNode('foobar')
197 with pytest.raises(EmptyRepositoryError):
198 with pytest.raises(EmptyRepositoryError):
198 self.imc.change(node)
199 self.imc.change(node)
199
200
200 def test_check_integrity_change_raise_node_does_not_exist(self):
201 def test_check_integrity_change_raise_node_does_not_exist(self):
201 node = FileNode('foobar', content='baz')
202 node = FileNode('foobar', content='baz')
202 self.imc.add(node)
203 self.imc.add(node)
203 self.imc.commit(message=u'Added foobar', author=unicode(self))
204 self.imc.commit(message=u'Added foobar', author=unicode(self))
204 node = FileNode('not-foobar', content='')
205 node = FileNode('not-foobar', content='')
205 self.imc.change(node)
206 self.imc.change(node)
206 with pytest.raises(NodeDoesNotExistError):
207 with pytest.raises(NodeDoesNotExistError):
207 self.imc.commit(
208 self.imc.commit(
208 message='Changed not existing node',
209 message='Changed not existing node',
209 author=str(self))
210 author=str(self))
210
211
211 def test_change_raise_node_already_changed(self):
212 def test_change_raise_node_already_changed(self):
212 node = FileNode('foobar', content='baz')
213 node = FileNode('foobar', content='baz')
213 self.imc.add(node)
214 self.imc.add(node)
214 self.imc.commit(message=u'Added foobar', author=unicode(self))
215 self.imc.commit(message=u'Added foobar', author=unicode(self))
215 node = FileNode('foobar', content='more baz')
216 node = FileNode('foobar', content='more baz')
216 self.imc.change(node)
217 self.imc.change(node)
217 with pytest.raises(NodeAlreadyChangedError):
218 with pytest.raises(NodeAlreadyChangedError):
218 self.imc.change(node)
219 self.imc.change(node)
219
220
220 def test_check_integrity_change_raise_node_not_changed(self, nodes):
221 def test_check_integrity_change_raise_node_not_changed(self, nodes):
221 self.test_add(nodes) # Performs first commit
222 self.test_add(nodes) # Performs first commit
222
223
223 node = FileNode(nodes[0].path, content=nodes[0].content)
224 node = FileNode(nodes[0].path, content=nodes[0].content)
224 self.imc.change(node)
225 self.imc.change(node)
225 with pytest.raises(NodeNotChangedError):
226 with pytest.raises(NodeNotChangedError):
226 self.imc.commit(
227 self.imc.commit(
227 message=u'Trying to mark node as changed without touching it',
228 message=u'Trying to mark node as changed without touching it',
228 author=unicode(self))
229 author=unicode(self))
229
230
230 def test_change_raise_node_already_removed(self):
231 def test_change_raise_node_already_removed(self):
231 node = FileNode('foobar', content='baz')
232 node = FileNode('foobar', content='baz')
232 self.imc.add(node)
233 self.imc.add(node)
233 self.imc.commit(message=u'Added foobar', author=unicode(self))
234 self.imc.commit(message=u'Added foobar', author=unicode(self))
234 self.imc.remove(FileNode('foobar'))
235 self.imc.remove(FileNode('foobar'))
235 with pytest.raises(NodeAlreadyRemovedError):
236 with pytest.raises(NodeAlreadyRemovedError):
236 self.imc.change(node)
237 self.imc.change(node)
237
238
238 def test_remove(self, nodes):
239 def test_remove(self, nodes):
239 self.test_add(nodes) # Performs first commit
240 self.test_add(nodes) # Performs first commit
240
241
241 tip = self.repo.get_commit()
242 tip = self.repo.get_commit()
242 node = nodes[0]
243 node = nodes[0]
243 assert node.content == tip.get_node(node.path).content
244 assert node.content == tip.get_node(node.path).content
244 self.imc.remove(node)
245 self.imc.remove(node)
245 self.imc.commit(
246 self.imc.commit(
246 message=u'Removed %s' % node.path, author=unicode(self))
247 message=u'Removed %s' % node.path, author=unicode(self))
247
248
248 newtip = self.repo.get_commit()
249 newtip = self.repo.get_commit()
249 assert tip != newtip
250 assert tip != newtip
250 assert tip.id != newtip.id
251 assert tip.id != newtip.id
251 with pytest.raises(NodeDoesNotExistError):
252 with pytest.raises(NodeDoesNotExistError):
252 newtip.get_node(node.path)
253 newtip.get_node(node.path)
253
254
254 def test_remove_last_file_from_directory(self):
255 def test_remove_last_file_from_directory(self):
255 node = FileNode('omg/qwe/foo/bar', content='foobar')
256 node = FileNode('omg/qwe/foo/bar', content='foobar')
256 self.imc.add(node)
257 self.imc.add(node)
257 self.imc.commit(u'added', u'joe doe')
258 self.imc.commit(u'added', u'joe doe')
258
259
259 self.imc.remove(node)
260 self.imc.remove(node)
260 tip = self.imc.commit(u'removed', u'joe doe')
261 tip = self.imc.commit(u'removed', u'joe doe')
261 with pytest.raises(NodeDoesNotExistError):
262 with pytest.raises(NodeDoesNotExistError):
262 tip.get_node('omg/qwe/foo/bar')
263 tip.get_node('omg/qwe/foo/bar')
263
264
264 def test_remove_raise_node_does_not_exist(self, nodes):
265 def test_remove_raise_node_does_not_exist(self, nodes):
265 self.imc.remove(nodes[0])
266 self.imc.remove(nodes[0])
266 with pytest.raises(NodeDoesNotExistError):
267 with pytest.raises(NodeDoesNotExistError):
267 self.imc.commit(
268 self.imc.commit(
268 message='Trying to remove node at empty repository',
269 message='Trying to remove node at empty repository',
269 author=str(self))
270 author=str(self))
270
271
271 def test_check_integrity_remove_raise_node_does_not_exist(self, nodes):
272 def test_check_integrity_remove_raise_node_does_not_exist(self, nodes):
272 self.test_add(nodes) # Performs first commit
273 self.test_add(nodes) # Performs first commit
273
274
274 node = FileNode('no-such-file')
275 node = FileNode('no-such-file')
275 self.imc.remove(node)
276 self.imc.remove(node)
276 with pytest.raises(NodeDoesNotExistError):
277 with pytest.raises(NodeDoesNotExistError):
277 self.imc.commit(
278 self.imc.commit(
278 message=u'Trying to remove not existing node',
279 message=u'Trying to remove not existing node',
279 author=unicode(self))
280 author=unicode(self))
280
281
281 def test_remove_raise_node_already_removed(self, nodes):
282 def test_remove_raise_node_already_removed(self, nodes):
282 self.test_add(nodes) # Performs first commit
283 self.test_add(nodes) # Performs first commit
283
284
284 node = FileNode(nodes[0].path)
285 node = FileNode(nodes[0].path)
285 self.imc.remove(node)
286 self.imc.remove(node)
286 with pytest.raises(NodeAlreadyRemovedError):
287 with pytest.raises(NodeAlreadyRemovedError):
287 self.imc.remove(node)
288 self.imc.remove(node)
288
289
289 def test_remove_raise_node_already_changed(self, nodes):
290 def test_remove_raise_node_already_changed(self, nodes):
290 self.test_add(nodes) # Performs first commit
291 self.test_add(nodes) # Performs first commit
291
292
292 node = FileNode(nodes[0].path, content='Bending time')
293 node = FileNode(nodes[0].path, content='Bending time')
293 self.imc.change(node)
294 self.imc.change(node)
294 with pytest.raises(NodeAlreadyChangedError):
295 with pytest.raises(NodeAlreadyChangedError):
295 self.imc.remove(node)
296 self.imc.remove(node)
296
297
297 def test_reset(self):
298 def test_reset(self):
298 self.imc.add(FileNode('foo', content='bar'))
299 self.imc.add(FileNode('foo', content='bar'))
299 # self.imc.change(FileNode('baz', content='new'))
300 # self.imc.change(FileNode('baz', content='new'))
300 # self.imc.remove(FileNode('qwe'))
301 # self.imc.remove(FileNode('qwe'))
301 self.imc.reset()
302 self.imc.reset()
302 assert not any((self.imc.added, self.imc.changed, self.imc.removed))
303 assert not any((self.imc.added, self.imc.changed, self.imc.removed))
303
304
304 def test_multiple_commits(self):
305 def test_multiple_commits(self):
305 N = 3 # number of commits to perform
306 N = 3 # number of commits to perform
306 last = None
307 last = None
307 for x in xrange(N):
308 for x in xrange(N):
308 fname = 'file%s' % str(x).rjust(5, '0')
309 fname = 'file%s' % str(x).rjust(5, '0')
309 content = 'foobar\n' * x
310 content = 'foobar\n' * x
310 node = FileNode(fname, content=content)
311 node = FileNode(fname, content=content)
311 self.imc.add(node)
312 self.imc.add(node)
312 commit = self.imc.commit(u"Commit no. %s" % (x + 1), author=u'vcs')
313 commit = self.imc.commit(u"Commit no. %s" % (x + 1), author=u'vcs')
313 assert last != commit
314 assert last != commit
314 last = commit
315 last = commit
315
316
316 # Check commit number for same repo
317 # Check commit number for same repo
317 assert len(self.repo.commit_ids) == N
318 assert len(self.repo.commit_ids) == N
318
319
319 # Check commit number for recreated repo
320 # Check commit number for recreated repo
320 repo = self.Backend(self.repo_path)
321 repo = self.Backend(self.repo_path)
321 assert len(repo.commit_ids) == N
322 assert len(repo.commit_ids) == N
322
323
323 def test_date_attr(self, local_dt_to_utc):
324 def test_date_attr(self, local_dt_to_utc):
324 node = FileNode('foobar.txt', content='Foobared!')
325 node = FileNode('foobar.txt', content='Foobared!')
325 self.imc.add(node)
326 self.imc.add(node)
326 date = datetime.datetime(1985, 1, 30, 1, 45)
327 date = datetime.datetime(1985, 1, 30, 1, 45)
327 commit = self.imc.commit(
328 commit = self.imc.commit(
328 u"Committed at time when I was born ;-)",
329 u"Committed at time when I was born ;-)",
329 author=u'lb', date=date)
330 author=u'lb', date=date)
330
331
331 assert commit.date == local_dt_to_utc(date)
332 assert commit.date == local_dt_to_utc(date)
332
333
333 def assert_succesful_commit(self, added_nodes):
334 def assert_succesful_commit(self, added_nodes):
334 newtip = self.repo.get_commit()
335 newtip = self.repo.get_commit()
335 assert self.commit == newtip
336 assert self.commit == newtip
336 assert self.old_commit_count + 1 == len(self.repo.commit_ids)
337 assert self.old_commit_count + 1 == len(self.repo.commit_ids)
337 assert newtip.message == self.commit_message
338 assert newtip.message == self.commit_message
338 assert newtip.author == self.commit_author
339 assert newtip.author == self.commit_author
339 assert not any((self.imc.added, self.imc.changed, self.imc.removed))
340 assert not any((self.imc.added, self.imc.changed, self.imc.removed))
340 self.assert_nodes_in_commit(newtip, added_nodes)
341 self.assert_nodes_in_commit(newtip, added_nodes)
341
342
342 def assert_nodes_in_commit(self, commit, nodes):
343 def assert_nodes_in_commit(self, commit, nodes):
343 for node in nodes:
344 for node in nodes:
344 assert commit.get_node(node.path).content == node.content
345 assert commit.get_node(node.path).content == node.content
@@ -1,274 +1,275 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import stat
21 import stat
22
22
23 import pytest
23 import pytest
24
24
25 from rhodecode.lib.vcs.nodes import DirNode
25 from rhodecode.lib.vcs.nodes import DirNode
26 from rhodecode.lib.vcs.nodes import FileNode
26 from rhodecode.lib.vcs.nodes import FileNode
27 from rhodecode.lib.vcs.nodes import Node
27 from rhodecode.lib.vcs.nodes import Node
28 from rhodecode.lib.vcs.nodes import NodeError
28 from rhodecode.lib.vcs.nodes import NodeError
29 from rhodecode.lib.vcs.nodes import NodeKind
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 @pytest.fixture()
33 @pytest.fixture()
34 def binary_filenode():
34 def binary_filenode():
35 def node_maker(filename):
35 def node_maker(filename):
36 data = (
36 data = (
37 "\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00"
37 "\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00"
38 "\x10\x08\x06\x00\x00\x00\x1f??a\x00\x00\x00\x04gAMA\x00\x00\xaf?7"
38 "\x10\x08\x06\x00\x00\x00\x1f??a\x00\x00\x00\x04gAMA\x00\x00\xaf?7"
39 "\x05\x8a?\x00\x00\x00\x19tEXtSoftware\x00Adobe ImageReadyq?e<\x00"
39 "\x05\x8a?\x00\x00\x00\x19tEXtSoftware\x00Adobe ImageReadyq?e<\x00"
40 "\x00\x025IDAT8?\xa5\x93?K\x94Q\x14\x87\x9f\xf7?Q\x1bs4?\x03\x9a"
40 "\x00\x025IDAT8?\xa5\x93?K\x94Q\x14\x87\x9f\xf7?Q\x1bs4?\x03\x9a"
41 "\xa8?B\x02\x8b$\x10[U;i\x13?6h?&h[?\"\x14j?\xa2M\x7fB\x14F\x9aQ?&"
41 "\xa8?B\x02\x8b$\x10[U;i\x13?6h?&h[?\"\x14j?\xa2M\x7fB\x14F\x9aQ?&"
42 "\x842?\x0b\x89\"\x82??!?\x9c!\x9c2l??{N\x8bW\x9dY\xb4\t/\x1c?="
42 "\x842?\x0b\x89\"\x82??!?\x9c!\x9c2l??{N\x8bW\x9dY\xb4\t/\x1c?="
43 "\x9b?}????\xa9*;9!?\x83\x91?[?\\v*?D\x04\'`EpNp\xa2X\'U?pVq\"Sw."
43 "\x9b?}????\xa9*;9!?\x83\x91?[?\\v*?D\x04\'`EpNp\xa2X\'U?pVq\"Sw."
44 "\x1e?\x08\x01D?jw????\xbc??7{|\x9b?\x89$\x01??W@\x15\x9c\x05q`Lt/"
44 "\x1e?\x08\x01D?jw????\xbc??7{|\x9b?\x89$\x01??W@\x15\x9c\x05q`Lt/"
45 "\x97?\x94\xa1d?\x18~?\x18?\x18W[%\xb0?\x83??\x14\x88\x8dB?\xa6H"
45 "\x97?\x94\xa1d?\x18~?\x18?\x18W[%\xb0?\x83??\x14\x88\x8dB?\xa6H"
46 "\tL\tl\x19>/\x01`\xac\xabx?\x9cl\nx\xb0\x98\x07\x95\x88D$\"q["
46 "\tL\tl\x19>/\x01`\xac\xabx?\x9cl\nx\xb0\x98\x07\x95\x88D$\"q["
47 "\x19?d\x00(o\n\xa0??\x7f\xb9\xa4?\x1bF\x1f\x8e\xac\xa8?j??eUU}?.?"
47 "\x19?d\x00(o\n\xa0??\x7f\xb9\xa4?\x1bF\x1f\x8e\xac\xa8?j??eUU}?.?"
48 "\x9f\x8cE??x\x94??\r\xbdtoJU5\"0N\x10U?\x00??V\t\x02\x9f\x81?U?"
48 "\x9f\x8cE??x\x94??\r\xbdtoJU5\"0N\x10U?\x00??V\t\x02\x9f\x81?U?"
49 "\x00\x9eM\xae2?r\x9b7\x83\x82\x8aP3????.?&\"?\xb7ZP \x0c<?O"
49 "\x00\x9eM\xae2?r\x9b7\x83\x82\x8aP3????.?&\"?\xb7ZP \x0c<?O"
50 "\xa5\t}\xb8?\x99\xa6?\x87?\x1di|/\xa0??0\xbe\x1fp?d&\x1a\xad"
50 "\xa5\t}\xb8?\x99\xa6?\x87?\x1di|/\xa0??0\xbe\x1fp?d&\x1a\xad"
51 "\x95\x8a\x07?\t*\x10??b:?d?.\x13C\x8a?\x12\xbe\xbf\x8e?{???"
51 "\x95\x8a\x07?\t*\x10??b:?d?.\x13C\x8a?\x12\xbe\xbf\x8e?{???"
52 "\x08?\x80\xa7\x13+d\x13>J?\x80\x15T\x95\x9a\x00??S\x8c\r?\xa1"
52 "\x08?\x80\xa7\x13+d\x13>J?\x80\x15T\x95\x9a\x00??S\x8c\r?\xa1"
53 "\x03\x07?\x96\x9b\xa7\xab=E??\xa4\xb3?\x19q??B\x91=\x8d??k?J"
53 "\x03\x07?\x96\x9b\xa7\xab=E??\xa4\xb3?\x19q??B\x91=\x8d??k?J"
54 "\x0bV\"??\xf7x?\xa1\x00?\\.\x87\x87???\x02F@D\x99],??\x10#?X"
54 "\x0bV\"??\xf7x?\xa1\x00?\\.\x87\x87???\x02F@D\x99],??\x10#?X"
55 "\xb7=\xb9\x10?Z\x1by???cI??\x1ag?\x92\xbc?T?t[\x92\x81?<_\x17~"
55 "\xb7=\xb9\x10?Z\x1by???cI??\x1ag?\x92\xbc?T?t[\x92\x81?<_\x17~"
56 "\x92\x88?H%?\x10Q\x02\x9f\n\x81qQ\x0bm?\x1bX?\xb1AK\xa6\x9e\xb9?u"
56 "\x92\x88?H%?\x10Q\x02\x9f\n\x81qQ\x0bm?\x1bX?\xb1AK\xa6\x9e\xb9?u"
57 "\xb2?1\xbe|/\x92M@\xa2!F?\xa9>\"\r<DT?>\x92\x8e?>\x9a9Qv\x127?a"
57 "\xb2?1\xbe|/\x92M@\xa2!F?\xa9>\"\r<DT?>\x92\x8e?>\x9a9Qv\x127?a"
58 "\xac?Y?8?:??]X???9\x80\xb7?u?\x0b#BZ\x8d=\x1d?p\x00\x00\x00\x00"
58 "\xac?Y?8?:??]X???9\x80\xb7?u?\x0b#BZ\x8d=\x1d?p\x00\x00\x00\x00"
59 "IEND\xaeB`\x82")
59 "IEND\xaeB`\x82")
60 return FileNode(filename, content=data)
60 return FileNode(filename, content=data)
61 return node_maker
61 return node_maker
62
62
63
63
64 class TestNodeBasics:
64 class TestNodeBasics:
65
65
66 @pytest.mark.parametrize("path", ['/foo', '/foo/bar'])
66 @pytest.mark.parametrize("path", ['/foo', '/foo/bar'])
67 @pytest.mark.parametrize(
67 @pytest.mark.parametrize(
68 "kind", [NodeKind.FILE, NodeKind.DIR], ids=["FILE", "DIR"])
68 "kind", [NodeKind.FILE, NodeKind.DIR], ids=["FILE", "DIR"])
69 def test_init_wrong_paths(self, path, kind):
69 def test_init_wrong_paths(self, path, kind):
70 """
70 """
71 Cannot innitialize Node objects with path with slash at the beginning.
71 Cannot innitialize Node objects with path with slash at the beginning.
72 """
72 """
73 with pytest.raises(NodeError):
73 with pytest.raises(NodeError):
74 Node(path, kind)
74 Node(path, kind)
75
75
76 @pytest.mark.parametrize("path", ['path', 'some/path'])
76 @pytest.mark.parametrize("path", ['path', 'some/path'])
77 @pytest.mark.parametrize(
77 @pytest.mark.parametrize(
78 "kind", [NodeKind.FILE, NodeKind.DIR], ids=["FILE", "DIR"])
78 "kind", [NodeKind.FILE, NodeKind.DIR], ids=["FILE", "DIR"])
79 def test_name(self, path, kind):
79 def test_name(self, path, kind):
80 node = Node(path, kind)
80 node = Node(path, kind)
81 assert node.name == 'path'
81 assert node.name == 'path'
82
82
83 def test_name_root(self):
83 def test_name_root(self):
84 node = Node('', NodeKind.DIR)
84 node = Node('', NodeKind.DIR)
85 assert node.name == ''
85 assert node.name == ''
86
86
87 def test_root_node_cannot_be_file(self):
87 def test_root_node_cannot_be_file(self):
88 with pytest.raises(NodeError):
88 with pytest.raises(NodeError):
89 Node('', NodeKind.FILE)
89 Node('', NodeKind.FILE)
90
90
91 def test_kind_setter(self):
91 def test_kind_setter(self):
92 node = Node('', NodeKind.DIR)
92 node = Node('', NodeKind.DIR)
93 with pytest.raises(NodeError):
93 with pytest.raises(NodeError):
94 node.kind = NodeKind.FILE
94 node.kind = NodeKind.FILE
95
95
96 def test_compare_equal(self):
96 def test_compare_equal(self):
97 node1 = FileNode('test', content='')
97 node1 = FileNode('test', content='')
98 node2 = FileNode('test', content='')
98 node2 = FileNode('test', content='')
99 assert node1 == node2
99 assert node1 == node2
100 assert not node1 != node2
100 assert not node1 != node2
101
101
102 def test_compare_unequal(self):
102 def test_compare_unequal(self):
103 node1 = FileNode('test', content='a')
103 node1 = FileNode('test', content='a')
104 node2 = FileNode('test', content='b')
104 node2 = FileNode('test', content='b')
105 assert node1 != node2
105 assert node1 != node2
106 assert not node1 == node2
106 assert not node1 == node2
107
107
108 @pytest.mark.parametrize("node_path, expected_parent_path", [
108 @pytest.mark.parametrize("node_path, expected_parent_path", [
109 ('', ''),
109 ('', ''),
110 ('some/path/', 'some/'),
110 ('some/path/', 'some/'),
111 ('some/longer/path/', 'some/longer/'),
111 ('some/longer/path/', 'some/longer/'),
112 ])
112 ])
113 def test_parent_path_new(self, node_path, expected_parent_path):
113 def test_parent_path_new(self, node_path, expected_parent_path):
114 """
114 """
115 Tests if node's parent path are properly computed.
115 Tests if node's parent path are properly computed.
116 """
116 """
117 node = Node(node_path, NodeKind.DIR)
117 node = Node(node_path, NodeKind.DIR)
118 parent_path = node.get_parent_path()
118 parent_path = node.get_parent_path()
119 assert (parent_path.endswith('/') or
119 assert (parent_path.endswith('/') or
120 node.is_root() and parent_path == '')
120 node.is_root() and parent_path == '')
121 assert parent_path == expected_parent_path
121 assert parent_path == expected_parent_path
122
122
123 '''
123 '''
124 def _test_trailing_slash(self, path):
124 def _test_trailing_slash(self, path):
125 if not path.endswith('/'):
125 if not path.endswith('/'):
126 pytest.fail("Trailing slash tests needs paths to end with slash")
126 pytest.fail("Trailing slash tests needs paths to end with slash")
127 for kind in NodeKind.FILE, NodeKind.DIR:
127 for kind in NodeKind.FILE, NodeKind.DIR:
128 with pytest.raises(NodeError):
128 with pytest.raises(NodeError):
129 Node(path=path, kind=kind)
129 Node(path=path, kind=kind)
130
130
131 def test_trailing_slash(self):
131 def test_trailing_slash(self):
132 for path in ('/', 'foo/', 'foo/bar/', 'foo/bar/biz/'):
132 for path in ('/', 'foo/', 'foo/bar/', 'foo/bar/biz/'):
133 self._test_trailing_slash(path)
133 self._test_trailing_slash(path)
134 '''
134 '''
135
135
136 def test_is_file(self):
136 def test_is_file(self):
137 node = Node('any', NodeKind.FILE)
137 node = Node('any', NodeKind.FILE)
138 assert node.is_file()
138 assert node.is_file()
139
139
140 node = FileNode('any')
140 node = FileNode('any')
141 assert node.is_file()
141 assert node.is_file()
142 with pytest.raises(AttributeError):
142 with pytest.raises(AttributeError):
143 node.nodes
143 node.nodes
144
144
145 def test_is_dir(self):
145 def test_is_dir(self):
146 node = Node('any_dir', NodeKind.DIR)
146 node = Node('any_dir', NodeKind.DIR)
147 assert node.is_dir()
147 assert node.is_dir()
148
148
149 node = DirNode('any_dir')
149 node = DirNode('any_dir')
150
150
151 assert node.is_dir()
151 assert node.is_dir()
152 with pytest.raises(NodeError):
152 with pytest.raises(NodeError):
153 node.content
153 node.content
154
154
155 def test_dir_node_iter(self):
155 def test_dir_node_iter(self):
156 nodes = [
156 nodes = [
157 DirNode('docs'),
157 DirNode('docs'),
158 DirNode('tests'),
158 DirNode('tests'),
159 FileNode('bar'),
159 FileNode('bar'),
160 FileNode('foo'),
160 FileNode('foo'),
161 FileNode('readme.txt'),
161 FileNode('readme.txt'),
162 FileNode('setup.py'),
162 FileNode('setup.py'),
163 ]
163 ]
164 dirnode = DirNode('', nodes=nodes)
164 dirnode = DirNode('', nodes=nodes)
165 for node in dirnode:
165 for node in dirnode:
166 assert node == dirnode.get_node(node.path)
166 assert node == dirnode.get_node(node.path)
167
167
168 def test_node_state(self):
168 def test_node_state(self):
169 """
169 """
170 Without link to commit nodes should raise NodeError.
170 Without link to commit nodes should raise NodeError.
171 """
171 """
172 node = FileNode('anything')
172 node = FileNode('anything')
173 with pytest.raises(NodeError):
173 with pytest.raises(NodeError):
174 node.state
174 node.state
175 node = DirNode('anything')
175 node = DirNode('anything')
176 with pytest.raises(NodeError):
176 with pytest.raises(NodeError):
177 node.state
177 node.state
178
178
179 def test_file_node_stat(self):
179 def test_file_node_stat(self):
180 node = FileNode('foobar', 'empty... almost')
180 node = FileNode('foobar', 'empty... almost')
181 mode = node.mode # default should be 0100644
181 mode = node.mode # default should be 0100644
182 assert mode & stat.S_IRUSR
182 assert mode & stat.S_IRUSR
183 assert mode & stat.S_IWUSR
183 assert mode & stat.S_IWUSR
184 assert mode & stat.S_IRGRP
184 assert mode & stat.S_IRGRP
185 assert mode & stat.S_IROTH
185 assert mode & stat.S_IROTH
186 assert not mode & stat.S_IWGRP
186 assert not mode & stat.S_IWGRP
187 assert not mode & stat.S_IWOTH
187 assert not mode & stat.S_IWOTH
188 assert not mode & stat.S_IXUSR
188 assert not mode & stat.S_IXUSR
189 assert not mode & stat.S_IXGRP
189 assert not mode & stat.S_IXGRP
190 assert not mode & stat.S_IXOTH
190 assert not mode & stat.S_IXOTH
191
191
192 def test_file_node_is_executable(self):
192 def test_file_node_is_executable(self):
193 node = FileNode('foobar', 'empty... almost', mode=0100755)
193 node = FileNode('foobar', 'empty... almost', mode=0100755)
194 assert node.is_executable
194 assert node.is_executable
195
195
196 node = FileNode('foobar', 'empty... almost', mode=0100500)
196 node = FileNode('foobar', 'empty... almost', mode=0100500)
197 assert node.is_executable
197 assert node.is_executable
198
198
199 node = FileNode('foobar', 'empty... almost', mode=0100644)
199 node = FileNode('foobar', 'empty... almost', mode=0100644)
200 assert not node.is_executable
200 assert not node.is_executable
201
201
202 def test_file_node_is_not_symlink(self):
202 def test_file_node_is_not_symlink(self):
203 node = FileNode('foobar', 'empty...')
203 node = FileNode('foobar', 'empty...')
204 assert not node.is_link()
204 assert not node.is_link()
205
205
206 def test_mimetype(self):
206 def test_mimetype(self):
207 py_node = FileNode('test.py')
207 py_node = FileNode('test.py')
208 tar_node = FileNode('test.tar.gz')
208 tar_node = FileNode('test.tar.gz')
209
209
210 ext = 'CustomExtension'
210 ext = 'CustomExtension'
211
211
212 my_node2 = FileNode('myfile2')
212 my_node2 = FileNode('myfile2')
213 my_node2._mimetype = [ext]
213 my_node2._mimetype = [ext]
214
214
215 my_node3 = FileNode('myfile3')
215 my_node3 = FileNode('myfile3')
216 my_node3._mimetype = [ext, ext]
216 my_node3._mimetype = [ext, ext]
217
217
218 assert py_node.mimetype == 'text/x-python'
218 assert py_node.mimetype == 'text/x-python'
219 assert py_node.get_mimetype() == ('text/x-python', None)
219 assert py_node.get_mimetype() == ('text/x-python', None)
220
220
221 assert tar_node.mimetype == 'application/x-tar'
221 assert tar_node.mimetype == 'application/x-tar'
222 assert tar_node.get_mimetype() == ('application/x-tar', 'gzip')
222 assert tar_node.get_mimetype() == ('application/x-tar', 'gzip')
223
223
224 with pytest.raises(NodeError):
224 with pytest.raises(NodeError):
225 my_node2.get_mimetype()
225 my_node2.get_mimetype()
226
226
227 assert my_node3.mimetype == ext
227 assert my_node3.mimetype == ext
228 assert my_node3.get_mimetype() == [ext, ext]
228 assert my_node3.get_mimetype() == [ext, ext]
229
229
230 def test_lines_counts(self):
230 def test_lines_counts(self):
231 lines = [
231 lines = [
232 'line1\n',
232 'line1\n',
233 'line2\n',
233 'line2\n',
234 'line3\n',
234 'line3\n',
235 '\n',
235 '\n',
236 '\n',
236 '\n',
237 'line4\n',
237 'line4\n',
238 ]
238 ]
239 py_node = FileNode('test.py', ''.join(lines))
239 py_node = FileNode('test.py', ''.join(lines))
240
240
241 assert (len(lines), len(lines)) == py_node.lines()
241 assert (len(lines), len(lines)) == py_node.lines()
242 assert (len(lines), len(lines) - 2) == py_node.lines(count_empty=True)
242 assert (len(lines), len(lines) - 2) == py_node.lines(count_empty=True)
243
243
244 def test_lines_no_newline(self):
244 def test_lines_no_newline(self):
245 py_node = FileNode('test.py', 'oneline')
245 py_node = FileNode('test.py', 'oneline')
246
246
247 assert (1, 1) == py_node.lines()
247 assert (1, 1) == py_node.lines()
248 assert (1, 1) == py_node.lines(count_empty=True)
248 assert (1, 1) == py_node.lines(count_empty=True)
249
249
250
250
251 class TestNodeContent:
251 class TestNodeContent(object):
252
252
253 def test_if_binary(self, binary_filenode):
253 def test_if_binary(self, binary_filenode):
254 filenode = binary_filenode('calendar.jpg')
254 filenode = binary_filenode('calendar.jpg')
255 assert filenode.is_binary
255 assert filenode.is_binary
256
256
257 def test_binary_line_counts(self, binary_filenode):
257 def test_binary_line_counts(self, binary_filenode):
258 tar_node = binary_filenode('archive.tar.gz')
258 tar_node = binary_filenode('archive.tar.gz')
259 assert (0, 0) == tar_node.lines(count_empty=True)
259 assert (0, 0) == tar_node.lines(count_empty=True)
260
260
261 def test_binary_mimetype(self, binary_filenode):
261 def test_binary_mimetype(self, binary_filenode):
262 tar_node = binary_filenode('archive.tar.gz')
262 tar_node = binary_filenode('archive.tar.gz')
263 assert tar_node.mimetype == 'application/x-tar'
263 assert tar_node.mimetype == 'application/x-tar'
264
264
265
265
266 @pytest.mark.usefixtures("vcs_repository_support")
266 class TestNodesCommits(BackendTestMixin):
267 class TestNodesCommits(BackendTestMixin):
267
268
268 def test_node_last_commit(self, generate_repo_with_commits):
269 def test_node_last_commit(self, generate_repo_with_commits):
269 repo = generate_repo_with_commits(20)
270 repo = generate_repo_with_commits(20)
270 last_commit = repo.get_commit()
271 last_commit = repo.get_commit()
271
272
272 for x in xrange(3):
273 for x in xrange(3):
273 node = last_commit.get_node('file_%s.txt' % x)
274 node = last_commit.get_node('file_%s.txt' % x)
274 assert node.last_commit == repo[x]
275 assert node.last_commit == repo[x]
@@ -1,534 +1,537 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import datetime
21 import datetime
22 from urllib2 import URLError
22 from urllib2 import URLError
23
23
24 import mock
24 import mock
25 import pytest
25 import pytest
26
26
27 from rhodecode.lib.vcs import backends
27 from rhodecode.lib.vcs import backends
28 from rhodecode.lib.vcs.backends.base import (
28 from rhodecode.lib.vcs.backends.base import (
29 Config, BaseInMemoryCommit, Reference, MergeResponse, MergeFailureReason)
29 Config, BaseInMemoryCommit, Reference, MergeResponse, MergeFailureReason)
30 from rhodecode.lib.vcs.exceptions import VCSError, RepositoryError
30 from rhodecode.lib.vcs.exceptions import VCSError, RepositoryError
31 from rhodecode.lib.vcs.nodes import FileNode
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 class TestRepositoryBase(BackendTestMixin):
36 class TestRepositoryBase(BackendTestMixin):
36 recreate_repo_per_test = False
37 recreate_repo_per_test = False
37
38
38 def test_init_accepts_unicode_path(self, tmpdir):
39 def test_init_accepts_unicode_path(self, tmpdir):
39 path = unicode(tmpdir.join(u'unicode ä'))
40 path = unicode(tmpdir.join(u'unicode ä'))
40 self.Backend(path, create=True)
41 self.Backend(path, create=True)
41
42
42 def test_init_accepts_str_path(self, tmpdir):
43 def test_init_accepts_str_path(self, tmpdir):
43 path = str(tmpdir.join('str ä'))
44 path = str(tmpdir.join('str ä'))
44 self.Backend(path, create=True)
45 self.Backend(path, create=True)
45
46
46 def test_init_fails_if_path_does_not_exist(self, tmpdir):
47 def test_init_fails_if_path_does_not_exist(self, tmpdir):
47 path = unicode(tmpdir.join('i-do-not-exist'))
48 path = unicode(tmpdir.join('i-do-not-exist'))
48 with pytest.raises(VCSError):
49 with pytest.raises(VCSError):
49 self.Backend(path)
50 self.Backend(path)
50
51
51 def test_init_fails_if_path_is_not_a_valid_repository(self, tmpdir):
52 def test_init_fails_if_path_is_not_a_valid_repository(self, tmpdir):
52 path = unicode(tmpdir.mkdir(u'unicode ä'))
53 path = unicode(tmpdir.mkdir(u'unicode ä'))
53 with pytest.raises(VCSError):
54 with pytest.raises(VCSError):
54 self.Backend(path)
55 self.Backend(path)
55
56
56 def test_has_commits_attribute(self):
57 def test_has_commits_attribute(self):
57 self.repo.commit_ids
58 self.repo.commit_ids
58
59
59 def test_name(self):
60 def test_name(self):
60 assert self.repo.name.startswith('vcs-test')
61 assert self.repo.name.startswith('vcs-test')
61
62
62 @pytest.mark.backends("hg", "git")
63 @pytest.mark.backends("hg", "git")
63 def test_has_default_branch_name(self):
64 def test_has_default_branch_name(self):
64 assert self.repo.DEFAULT_BRANCH_NAME is not None
65 assert self.repo.DEFAULT_BRANCH_NAME is not None
65
66
66 @pytest.mark.backends("svn")
67 @pytest.mark.backends("svn")
67 def test_has_no_default_branch_name(self):
68 def test_has_no_default_branch_name(self):
68 assert self.repo.DEFAULT_BRANCH_NAME is None
69 assert self.repo.DEFAULT_BRANCH_NAME is None
69
70
70 def test_has_empty_commit(self):
71 def test_has_empty_commit(self):
71 assert self.repo.EMPTY_COMMIT_ID is not None
72 assert self.repo.EMPTY_COMMIT_ID is not None
72 assert self.repo.EMPTY_COMMIT is not None
73 assert self.repo.EMPTY_COMMIT is not None
73
74
74 def test_empty_changeset_is_deprecated(self):
75 def test_empty_changeset_is_deprecated(self):
75 def get_empty_changeset(repo):
76 def get_empty_changeset(repo):
76 return repo.EMPTY_CHANGESET
77 return repo.EMPTY_CHANGESET
77 pytest.deprecated_call(get_empty_changeset, self.repo)
78 pytest.deprecated_call(get_empty_changeset, self.repo)
78
79
79 def test_bookmarks(self):
80 def test_bookmarks(self):
80 assert len(self.repo.bookmarks) == 0
81 assert len(self.repo.bookmarks) == 0
81
82
82 # TODO: Cover two cases: Local repo path, remote URL
83 # TODO: Cover two cases: Local repo path, remote URL
83 def test_check_url(self):
84 def test_check_url(self):
84 config = Config()
85 config = Config()
85 assert self.Backend.check_url(self.repo.path, config)
86 assert self.Backend.check_url(self.repo.path, config)
86
87
87 def test_check_url_invalid(self):
88 def test_check_url_invalid(self):
88 config = Config()
89 config = Config()
89 with pytest.raises(URLError):
90 with pytest.raises(URLError):
90 self.Backend.check_url(self.repo.path + "invalid", config)
91 self.Backend.check_url(self.repo.path + "invalid", config)
91
92
92 def test_get_contact(self):
93 def test_get_contact(self):
93 assert self.repo.contact
94 assert self.repo.contact
94
95
95 def test_get_description(self):
96 def test_get_description(self):
96 assert self.repo.description
97 assert self.repo.description
97
98
98 def test_get_hook_location(self):
99 def test_get_hook_location(self):
99 assert len(self.repo.get_hook_location()) != 0
100 assert len(self.repo.get_hook_location()) != 0
100
101
101 def test_last_change(self, local_dt_to_utc):
102 def test_last_change(self, local_dt_to_utc):
102 assert self.repo.last_change >= local_dt_to_utc(
103 assert self.repo.last_change >= local_dt_to_utc(
103 datetime.datetime(2010, 1, 1, 21, 0))
104 datetime.datetime(2010, 1, 1, 21, 0))
104
105
105 def test_last_change_in_empty_repository(self, vcsbackend, local_dt_to_utc):
106 def test_last_change_in_empty_repository(self, vcsbackend, local_dt_to_utc):
106 delta = datetime.timedelta(seconds=1)
107 delta = datetime.timedelta(seconds=1)
107
108
108 start = local_dt_to_utc(datetime.datetime.now())
109 start = local_dt_to_utc(datetime.datetime.now())
109 empty_repo = vcsbackend.create_repo()
110 empty_repo = vcsbackend.create_repo()
110 now = local_dt_to_utc(datetime.datetime.now())
111 now = local_dt_to_utc(datetime.datetime.now())
111 assert empty_repo.last_change >= start - delta
112 assert empty_repo.last_change >= start - delta
112 assert empty_repo.last_change <= now + delta
113 assert empty_repo.last_change <= now + delta
113
114
114 def test_repo_equality(self):
115 def test_repo_equality(self):
115 assert self.repo == self.repo
116 assert self.repo == self.repo
116
117
117 def test_repo_equality_broken_object(self):
118 def test_repo_equality_broken_object(self):
118 import copy
119 import copy
119 _repo = copy.copy(self.repo)
120 _repo = copy.copy(self.repo)
120 delattr(_repo, 'path')
121 delattr(_repo, 'path')
121 assert self.repo != _repo
122 assert self.repo != _repo
122
123
123 def test_repo_equality_other_object(self):
124 def test_repo_equality_other_object(self):
124 class dummy(object):
125 class dummy(object):
125 path = self.repo.path
126 path = self.repo.path
126 assert self.repo != dummy()
127 assert self.repo != dummy()
127
128
128 def test_get_commit_is_implemented(self):
129 def test_get_commit_is_implemented(self):
129 self.repo.get_commit()
130 self.repo.get_commit()
130
131
131 def test_get_commits_is_implemented(self):
132 def test_get_commits_is_implemented(self):
132 commit_iter = iter(self.repo.get_commits())
133 commit_iter = iter(self.repo.get_commits())
133 commit = next(commit_iter)
134 commit = next(commit_iter)
134 assert commit.idx == 0
135 assert commit.idx == 0
135
136
136 def test_supports_iteration(self):
137 def test_supports_iteration(self):
137 repo_iter = iter(self.repo)
138 repo_iter = iter(self.repo)
138 commit = next(repo_iter)
139 commit = next(repo_iter)
139 assert commit.idx == 0
140 assert commit.idx == 0
140
141
141 def test_in_memory_commit(self):
142 def test_in_memory_commit(self):
142 imc = self.repo.in_memory_commit
143 imc = self.repo.in_memory_commit
143 assert isinstance(imc, BaseInMemoryCommit)
144 assert isinstance(imc, BaseInMemoryCommit)
144
145
145 @pytest.mark.backends("hg")
146 @pytest.mark.backends("hg")
146 def test__get_url_unicode(self):
147 def test__get_url_unicode(self):
147 url = u'/home/repos/malmö'
148 url = u'/home/repos/malmö'
148 assert self.repo._get_url(url)
149 assert self.repo._get_url(url)
149
150
150
151
152 @pytest.mark.usefixtures("vcs_repository_support")
151 class TestDeprecatedRepositoryAPI(BackendTestMixin):
153 class TestDeprecatedRepositoryAPI(BackendTestMixin):
152 recreate_repo_per_test = False
154 recreate_repo_per_test = False
153
155
154 def test_revisions_is_deprecated(self):
156 def test_revisions_is_deprecated(self):
155 def get_revisions(repo):
157 def get_revisions(repo):
156 return repo.revisions
158 return repo.revisions
157 pytest.deprecated_call(get_revisions, self.repo)
159 pytest.deprecated_call(get_revisions, self.repo)
158
160
159 def test_get_changeset_is_deprecated(self):
161 def test_get_changeset_is_deprecated(self):
160 pytest.deprecated_call(self.repo.get_changeset)
162 pytest.deprecated_call(self.repo.get_changeset)
161
163
162 def test_get_changesets_is_deprecated(self):
164 def test_get_changesets_is_deprecated(self):
163 pytest.deprecated_call(self.repo.get_changesets)
165 pytest.deprecated_call(self.repo.get_changesets)
164
166
165 def test_in_memory_changeset_is_deprecated(self):
167 def test_in_memory_changeset_is_deprecated(self):
166 def get_imc(repo):
168 def get_imc(repo):
167 return repo.in_memory_changeset
169 return repo.in_memory_changeset
168 pytest.deprecated_call(get_imc, self.repo)
170 pytest.deprecated_call(get_imc, self.repo)
169
171
170
172
171 # TODO: these tests are incomplete, must check the resulting compare result for
173 # TODO: these tests are incomplete, must check the resulting compare result for
172 # correcteness
174 # correcteness
173 class TestRepositoryCompare:
175 class TestRepositoryCompare:
174
176
175 @pytest.mark.parametrize('merge', [True, False])
177 @pytest.mark.parametrize('merge', [True, False])
176 def test_compare_commits_of_same_repository(self, vcsbackend, merge):
178 def test_compare_commits_of_same_repository(self, vcsbackend, merge):
177 target_repo = vcsbackend.create_repo(number_of_commits=5)
179 target_repo = vcsbackend.create_repo(number_of_commits=5)
178 target_repo.compare(
180 target_repo.compare(
179 target_repo[1].raw_id, target_repo[3].raw_id, target_repo,
181 target_repo[1].raw_id, target_repo[3].raw_id, target_repo,
180 merge=merge)
182 merge=merge)
181
183
182 @pytest.mark.xfail_backends('svn')
184 @pytest.mark.xfail_backends('svn')
183 @pytest.mark.parametrize('merge', [True, False])
185 @pytest.mark.parametrize('merge', [True, False])
184 def test_compare_cloned_repositories(self, vcsbackend, merge):
186 def test_compare_cloned_repositories(self, vcsbackend, merge):
185 target_repo = vcsbackend.create_repo(number_of_commits=5)
187 target_repo = vcsbackend.create_repo(number_of_commits=5)
186 source_repo = vcsbackend.clone_repo(target_repo)
188 source_repo = vcsbackend.clone_repo(target_repo)
187 assert target_repo != source_repo
189 assert target_repo != source_repo
188
190
189 vcsbackend.add_file(source_repo, 'newfile', 'somecontent')
191 vcsbackend.add_file(source_repo, 'newfile', 'somecontent')
190 source_commit = source_repo.get_commit()
192 source_commit = source_repo.get_commit()
191
193
192 target_repo.compare(
194 target_repo.compare(
193 target_repo[1].raw_id, source_repo[3].raw_id, source_repo,
195 target_repo[1].raw_id, source_repo[3].raw_id, source_repo,
194 merge=merge)
196 merge=merge)
195
197
196 @pytest.mark.xfail_backends('svn')
198 @pytest.mark.xfail_backends('svn')
197 @pytest.mark.parametrize('merge', [True, False])
199 @pytest.mark.parametrize('merge', [True, False])
198 def test_compare_unrelated_repositories(self, vcsbackend, merge):
200 def test_compare_unrelated_repositories(self, vcsbackend, merge):
199 orig = vcsbackend.create_repo(number_of_commits=5)
201 orig = vcsbackend.create_repo(number_of_commits=5)
200 unrelated = vcsbackend.create_repo(number_of_commits=5)
202 unrelated = vcsbackend.create_repo(number_of_commits=5)
201 assert orig != unrelated
203 assert orig != unrelated
202
204
203 orig.compare(
205 orig.compare(
204 orig[1].raw_id, unrelated[3].raw_id, unrelated, merge=merge)
206 orig[1].raw_id, unrelated[3].raw_id, unrelated, merge=merge)
205
207
206
208
207 class TestRepositoryGetCommonAncestor:
209 class TestRepositoryGetCommonAncestor:
208
210
209 def test_get_common_ancestor_from_same_repo_existing(self, vcsbackend):
211 def test_get_common_ancestor_from_same_repo_existing(self, vcsbackend):
210 target_repo = vcsbackend.create_repo(number_of_commits=5)
212 target_repo = vcsbackend.create_repo(number_of_commits=5)
211
213
212 expected_ancestor = target_repo[2].raw_id
214 expected_ancestor = target_repo[2].raw_id
213
215
214 assert target_repo.get_common_ancestor(
216 assert target_repo.get_common_ancestor(
215 commit_id1=target_repo[2].raw_id,
217 commit_id1=target_repo[2].raw_id,
216 commit_id2=target_repo[4].raw_id,
218 commit_id2=target_repo[4].raw_id,
217 repo2=target_repo
219 repo2=target_repo
218 ) == expected_ancestor
220 ) == expected_ancestor
219
221
220 assert target_repo.get_common_ancestor(
222 assert target_repo.get_common_ancestor(
221 commit_id1=target_repo[4].raw_id,
223 commit_id1=target_repo[4].raw_id,
222 commit_id2=target_repo[2].raw_id,
224 commit_id2=target_repo[2].raw_id,
223 repo2=target_repo
225 repo2=target_repo
224 ) == expected_ancestor
226 ) == expected_ancestor
225
227
226 @pytest.mark.xfail_backends("svn")
228 @pytest.mark.xfail_backends("svn")
227 def test_get_common_ancestor_from_cloned_repo_existing(self, vcsbackend):
229 def test_get_common_ancestor_from_cloned_repo_existing(self, vcsbackend):
228 target_repo = vcsbackend.create_repo(number_of_commits=5)
230 target_repo = vcsbackend.create_repo(number_of_commits=5)
229 source_repo = vcsbackend.clone_repo(target_repo)
231 source_repo = vcsbackend.clone_repo(target_repo)
230 assert target_repo != source_repo
232 assert target_repo != source_repo
231
233
232 vcsbackend.add_file(source_repo, 'newfile', 'somecontent')
234 vcsbackend.add_file(source_repo, 'newfile', 'somecontent')
233 source_commit = source_repo.get_commit()
235 source_commit = source_repo.get_commit()
234
236
235 expected_ancestor = target_repo[4].raw_id
237 expected_ancestor = target_repo[4].raw_id
236
238
237 assert target_repo.get_common_ancestor(
239 assert target_repo.get_common_ancestor(
238 commit_id1=target_repo[4].raw_id,
240 commit_id1=target_repo[4].raw_id,
239 commit_id2=source_commit.raw_id,
241 commit_id2=source_commit.raw_id,
240 repo2=source_repo
242 repo2=source_repo
241 ) == expected_ancestor
243 ) == expected_ancestor
242
244
243 assert target_repo.get_common_ancestor(
245 assert target_repo.get_common_ancestor(
244 commit_id1=source_commit.raw_id,
246 commit_id1=source_commit.raw_id,
245 commit_id2=target_repo[4].raw_id,
247 commit_id2=target_repo[4].raw_id,
246 repo2=target_repo
248 repo2=target_repo
247 ) == expected_ancestor
249 ) == expected_ancestor
248
250
249 @pytest.mark.xfail_backends("svn")
251 @pytest.mark.xfail_backends("svn")
250 def test_get_common_ancestor_from_unrelated_repo_missing(self, vcsbackend):
252 def test_get_common_ancestor_from_unrelated_repo_missing(self, vcsbackend):
251 original = vcsbackend.create_repo(number_of_commits=5)
253 original = vcsbackend.create_repo(number_of_commits=5)
252 unrelated = vcsbackend.create_repo(number_of_commits=5)
254 unrelated = vcsbackend.create_repo(number_of_commits=5)
253 assert original != unrelated
255 assert original != unrelated
254
256
255 assert original.get_common_ancestor(
257 assert original.get_common_ancestor(
256 commit_id1=original[0].raw_id,
258 commit_id1=original[0].raw_id,
257 commit_id2=unrelated[0].raw_id,
259 commit_id2=unrelated[0].raw_id,
258 repo2=unrelated
260 repo2=unrelated
259 ) == None
261 ) == None
260
262
261 assert original.get_common_ancestor(
263 assert original.get_common_ancestor(
262 commit_id1=original[-1].raw_id,
264 commit_id1=original[-1].raw_id,
263 commit_id2=unrelated[-1].raw_id,
265 commit_id2=unrelated[-1].raw_id,
264 repo2=unrelated
266 repo2=unrelated
265 ) == None
267 ) == None
266
268
267
269
268 @pytest.mark.backends("git", "hg")
270 @pytest.mark.backends("git", "hg")
269 class TestRepositoryMerge:
271 class TestRepositoryMerge:
270 def prepare_for_success(self, vcsbackend):
272 def prepare_for_success(self, vcsbackend):
271 self.target_repo = vcsbackend.create_repo(number_of_commits=1)
273 self.target_repo = vcsbackend.create_repo(number_of_commits=1)
272 self.source_repo = vcsbackend.clone_repo(self.target_repo)
274 self.source_repo = vcsbackend.clone_repo(self.target_repo)
273 vcsbackend.add_file(self.target_repo, 'README_MERGE1', 'Version 1')
275 vcsbackend.add_file(self.target_repo, 'README_MERGE1', 'Version 1')
274 vcsbackend.add_file(self.source_repo, 'README_MERGE2', 'Version 2')
276 vcsbackend.add_file(self.source_repo, 'README_MERGE2', 'Version 2')
275 imc = self.source_repo.in_memory_commit
277 imc = self.source_repo.in_memory_commit
276 imc.add(FileNode('file_x', content=self.source_repo.name))
278 imc.add(FileNode('file_x', content=self.source_repo.name))
277 imc.commit(
279 imc.commit(
278 message=u'Automatic commit from repo merge test',
280 message=u'Automatic commit from repo merge test',
279 author=u'Automatic')
281 author=u'Automatic')
280 self.target_commit = self.target_repo.get_commit()
282 self.target_commit = self.target_repo.get_commit()
281 self.source_commit = self.source_repo.get_commit()
283 self.source_commit = self.source_repo.get_commit()
282 # This only works for Git and Mercurial
284 # This only works for Git and Mercurial
283 default_branch = self.target_repo.DEFAULT_BRANCH_NAME
285 default_branch = self.target_repo.DEFAULT_BRANCH_NAME
284 self.target_ref = Reference(
286 self.target_ref = Reference(
285 'branch', default_branch, self.target_commit.raw_id)
287 'branch', default_branch, self.target_commit.raw_id)
286 self.source_ref = Reference(
288 self.source_ref = Reference(
287 'branch', default_branch, self.source_commit.raw_id)
289 'branch', default_branch, self.source_commit.raw_id)
288 self.workspace = 'test-merge'
290 self.workspace = 'test-merge'
289
291
290 def prepare_for_conflict(self, vcsbackend):
292 def prepare_for_conflict(self, vcsbackend):
291 self.target_repo = vcsbackend.create_repo(number_of_commits=1)
293 self.target_repo = vcsbackend.create_repo(number_of_commits=1)
292 self.source_repo = vcsbackend.clone_repo(self.target_repo)
294 self.source_repo = vcsbackend.clone_repo(self.target_repo)
293 vcsbackend.add_file(self.target_repo, 'README_MERGE', 'Version 1')
295 vcsbackend.add_file(self.target_repo, 'README_MERGE', 'Version 1')
294 vcsbackend.add_file(self.source_repo, 'README_MERGE', 'Version 2')
296 vcsbackend.add_file(self.source_repo, 'README_MERGE', 'Version 2')
295 self.target_commit = self.target_repo.get_commit()
297 self.target_commit = self.target_repo.get_commit()
296 self.source_commit = self.source_repo.get_commit()
298 self.source_commit = self.source_repo.get_commit()
297 # This only works for Git and Mercurial
299 # This only works for Git and Mercurial
298 default_branch = self.target_repo.DEFAULT_BRANCH_NAME
300 default_branch = self.target_repo.DEFAULT_BRANCH_NAME
299 self.target_ref = Reference(
301 self.target_ref = Reference(
300 'branch', default_branch, self.target_commit.raw_id)
302 'branch', default_branch, self.target_commit.raw_id)
301 self.source_ref = Reference(
303 self.source_ref = Reference(
302 'branch', default_branch, self.source_commit.raw_id)
304 'branch', default_branch, self.source_commit.raw_id)
303 self.workspace = 'test-merge'
305 self.workspace = 'test-merge'
304
306
305 def test_merge_success(self, vcsbackend):
307 def test_merge_success(self, vcsbackend):
306 self.prepare_for_success(vcsbackend)
308 self.prepare_for_success(vcsbackend)
307
309
308 merge_response = self.target_repo.merge(
310 merge_response = self.target_repo.merge(
309 self.target_ref, self.source_repo, self.source_ref, self.workspace,
311 self.target_ref, self.source_repo, self.source_ref, self.workspace,
310 'test user', 'test@rhodecode.com', 'merge message 1',
312 'test user', 'test@rhodecode.com', 'merge message 1',
311 dry_run=False)
313 dry_run=False)
312 expected_merge_response = MergeResponse(
314 expected_merge_response = MergeResponse(
313 True, True, merge_response.merge_ref,
315 True, True, merge_response.merge_ref,
314 MergeFailureReason.NONE)
316 MergeFailureReason.NONE)
315 assert merge_response == expected_merge_response
317 assert merge_response == expected_merge_response
316
318
317 target_repo = backends.get_backend(vcsbackend.alias)(
319 target_repo = backends.get_backend(vcsbackend.alias)(
318 self.target_repo.path)
320 self.target_repo.path)
319 target_commits = list(target_repo.get_commits())
321 target_commits = list(target_repo.get_commits())
320 commit_ids = [c.raw_id for c in target_commits[:-1]]
322 commit_ids = [c.raw_id for c in target_commits[:-1]]
321 assert self.source_ref.commit_id in commit_ids
323 assert self.source_ref.commit_id in commit_ids
322 assert self.target_ref.commit_id in commit_ids
324 assert self.target_ref.commit_id in commit_ids
323
325
324 merge_commit = target_commits[-1]
326 merge_commit = target_commits[-1]
325 assert merge_commit.raw_id == merge_response.merge_ref.commit_id
327 assert merge_commit.raw_id == merge_response.merge_ref.commit_id
326 assert merge_commit.message.strip() == 'merge message 1'
328 assert merge_commit.message.strip() == 'merge message 1'
327 assert merge_commit.author == 'test user <test@rhodecode.com>'
329 assert merge_commit.author == 'test user <test@rhodecode.com>'
328
330
329 # We call it twice so to make sure we can handle updates
331 # We call it twice so to make sure we can handle updates
330 target_ref = Reference(
332 target_ref = Reference(
331 self.target_ref.type, self.target_ref.name,
333 self.target_ref.type, self.target_ref.name,
332 merge_response.merge_ref.commit_id)
334 merge_response.merge_ref.commit_id)
333
335
334 merge_response = target_repo.merge(
336 merge_response = target_repo.merge(
335 target_ref, self.source_repo, self.source_ref, self.workspace,
337 target_ref, self.source_repo, self.source_ref, self.workspace,
336 'test user', 'test@rhodecode.com', 'merge message 2',
338 'test user', 'test@rhodecode.com', 'merge message 2',
337 dry_run=False)
339 dry_run=False)
338 expected_merge_response = MergeResponse(
340 expected_merge_response = MergeResponse(
339 True, True, merge_response.merge_ref,
341 True, True, merge_response.merge_ref,
340 MergeFailureReason.NONE)
342 MergeFailureReason.NONE)
341 assert merge_response == expected_merge_response
343 assert merge_response == expected_merge_response
342
344
343 target_repo = backends.get_backend(
345 target_repo = backends.get_backend(
344 vcsbackend.alias)(self.target_repo.path)
346 vcsbackend.alias)(self.target_repo.path)
345 merge_commit = target_repo.get_commit(
347 merge_commit = target_repo.get_commit(
346 merge_response.merge_ref.commit_id)
348 merge_response.merge_ref.commit_id)
347 assert merge_commit.message.strip() == 'merge message 1'
349 assert merge_commit.message.strip() == 'merge message 1'
348 assert merge_commit.author == 'test user <test@rhodecode.com>'
350 assert merge_commit.author == 'test user <test@rhodecode.com>'
349
351
350 def test_merge_success_dry_run(self, vcsbackend):
352 def test_merge_success_dry_run(self, vcsbackend):
351 self.prepare_for_success(vcsbackend)
353 self.prepare_for_success(vcsbackend)
352
354
353 merge_response = self.target_repo.merge(
355 merge_response = self.target_repo.merge(
354 self.target_ref, self.source_repo, self.source_ref, self.workspace,
356 self.target_ref, self.source_repo, self.source_ref, self.workspace,
355 dry_run=True)
357 dry_run=True)
356
358
357 # We call it twice so to make sure we can handle updates
359 # We call it twice so to make sure we can handle updates
358 merge_response_update = self.target_repo.merge(
360 merge_response_update = self.target_repo.merge(
359 self.target_ref, self.source_repo, self.source_ref, self.workspace,
361 self.target_ref, self.source_repo, self.source_ref, self.workspace,
360 dry_run=True)
362 dry_run=True)
361
363
362 # Multiple merges may differ in their commit id. Therefore we set the
364 # Multiple merges may differ in their commit id. Therefore we set the
363 # commit id to `None` before comparing the merge responses.
365 # commit id to `None` before comparing the merge responses.
364 merge_response = merge_response._replace(
366 merge_response = merge_response._replace(
365 merge_ref=merge_response.merge_ref._replace(commit_id=None))
367 merge_ref=merge_response.merge_ref._replace(commit_id=None))
366 merge_response_update = merge_response_update._replace(
368 merge_response_update = merge_response_update._replace(
367 merge_ref=merge_response_update.merge_ref._replace(commit_id=None))
369 merge_ref=merge_response_update.merge_ref._replace(commit_id=None))
368
370
369 assert merge_response == merge_response_update
371 assert merge_response == merge_response_update
370 assert merge_response.possible is True
372 assert merge_response.possible is True
371 assert merge_response.executed is False
373 assert merge_response.executed is False
372 assert merge_response.merge_ref
374 assert merge_response.merge_ref
373 assert merge_response.failure_reason is MergeFailureReason.NONE
375 assert merge_response.failure_reason is MergeFailureReason.NONE
374
376
375 @pytest.mark.parametrize('dry_run', [True, False])
377 @pytest.mark.parametrize('dry_run', [True, False])
376 def test_merge_conflict(self, vcsbackend, dry_run):
378 def test_merge_conflict(self, vcsbackend, dry_run):
377 self.prepare_for_conflict(vcsbackend)
379 self.prepare_for_conflict(vcsbackend)
378 expected_merge_response = MergeResponse(
380 expected_merge_response = MergeResponse(
379 False, False, None, MergeFailureReason.MERGE_FAILED)
381 False, False, None, MergeFailureReason.MERGE_FAILED)
380
382
381 merge_response = self.target_repo.merge(
383 merge_response = self.target_repo.merge(
382 self.target_ref, self.source_repo, self.source_ref, self.workspace,
384 self.target_ref, self.source_repo, self.source_ref, self.workspace,
383 'test_user', 'test@rhodecode.com', 'test message', dry_run=dry_run)
385 'test_user', 'test@rhodecode.com', 'test message', dry_run=dry_run)
384 assert merge_response == expected_merge_response
386 assert merge_response == expected_merge_response
385
387
386 # We call it twice so to make sure we can handle updates
388 # We call it twice so to make sure we can handle updates
387 merge_response = self.target_repo.merge(
389 merge_response = self.target_repo.merge(
388 self.target_ref, self.source_repo, self.source_ref, self.workspace,
390 self.target_ref, self.source_repo, self.source_ref, self.workspace,
389 'test_user', 'test@rhodecode.com', 'test message', dry_run=dry_run)
391 'test_user', 'test@rhodecode.com', 'test message', dry_run=dry_run)
390 assert merge_response == expected_merge_response
392 assert merge_response == expected_merge_response
391
393
392 def test_merge_target_is_not_head(self, vcsbackend):
394 def test_merge_target_is_not_head(self, vcsbackend):
393 self.prepare_for_success(vcsbackend)
395 self.prepare_for_success(vcsbackend)
394 expected_merge_response = MergeResponse(
396 expected_merge_response = MergeResponse(
395 False, False, None, MergeFailureReason.TARGET_IS_NOT_HEAD)
397 False, False, None, MergeFailureReason.TARGET_IS_NOT_HEAD)
396
398
397 target_ref = Reference(
399 target_ref = Reference(
398 self.target_ref.type, self.target_ref.name, '0' * 40)
400 self.target_ref.type, self.target_ref.name, '0' * 40)
399
401
400 merge_response = self.target_repo.merge(
402 merge_response = self.target_repo.merge(
401 target_ref, self.source_repo, self.source_ref, self.workspace,
403 target_ref, self.source_repo, self.source_ref, self.workspace,
402 dry_run=True)
404 dry_run=True)
403
405
404 assert merge_response == expected_merge_response
406 assert merge_response == expected_merge_response
405
407
406 def test_merge_missing_source_reference(self, vcsbackend):
408 def test_merge_missing_source_reference(self, vcsbackend):
407 self.prepare_for_success(vcsbackend)
409 self.prepare_for_success(vcsbackend)
408 expected_merge_response = MergeResponse(
410 expected_merge_response = MergeResponse(
409 False, False, None, MergeFailureReason.MISSING_SOURCE_REF)
411 False, False, None, MergeFailureReason.MISSING_SOURCE_REF)
410
412
411 source_ref = Reference(
413 source_ref = Reference(
412 self.source_ref.type, 'not_existing', self.source_ref.commit_id)
414 self.source_ref.type, 'not_existing', self.source_ref.commit_id)
413
415
414 merge_response = self.target_repo.merge(
416 merge_response = self.target_repo.merge(
415 self.target_ref, self.source_repo, source_ref, self.workspace,
417 self.target_ref, self.source_repo, source_ref, self.workspace,
416 dry_run=True)
418 dry_run=True)
417
419
418 assert merge_response == expected_merge_response
420 assert merge_response == expected_merge_response
419
421
420 def test_merge_raises_exception(self, vcsbackend):
422 def test_merge_raises_exception(self, vcsbackend):
421 self.prepare_for_success(vcsbackend)
423 self.prepare_for_success(vcsbackend)
422 expected_merge_response = MergeResponse(
424 expected_merge_response = MergeResponse(
423 False, False, None, MergeFailureReason.UNKNOWN)
425 False, False, None, MergeFailureReason.UNKNOWN)
424
426
425 with mock.patch.object(self.target_repo, '_merge_repo',
427 with mock.patch.object(self.target_repo, '_merge_repo',
426 side_effect=RepositoryError()):
428 side_effect=RepositoryError()):
427 merge_response = self.target_repo.merge(
429 merge_response = self.target_repo.merge(
428 self.target_ref, self.source_repo, self.source_ref,
430 self.target_ref, self.source_repo, self.source_ref,
429 self.workspace, dry_run=True)
431 self.workspace, dry_run=True)
430
432
431 assert merge_response == expected_merge_response
433 assert merge_response == expected_merge_response
432
434
433 def test_merge_invalid_user_name(self, vcsbackend):
435 def test_merge_invalid_user_name(self, vcsbackend):
434 repo = vcsbackend.create_repo(number_of_commits=1)
436 repo = vcsbackend.create_repo(number_of_commits=1)
435 ref = Reference('branch', 'master', 'not_used')
437 ref = Reference('branch', 'master', 'not_used')
436 with pytest.raises(ValueError):
438 with pytest.raises(ValueError):
437 repo.merge(ref, self, ref, 'workspace_id')
439 repo.merge(ref, self, ref, 'workspace_id')
438
440
439 def test_merge_invalid_user_email(self, vcsbackend):
441 def test_merge_invalid_user_email(self, vcsbackend):
440 repo = vcsbackend.create_repo(number_of_commits=1)
442 repo = vcsbackend.create_repo(number_of_commits=1)
441 ref = Reference('branch', 'master', 'not_used')
443 ref = Reference('branch', 'master', 'not_used')
442 with pytest.raises(ValueError):
444 with pytest.raises(ValueError):
443 repo.merge(ref, self, ref, 'workspace_id', 'user name')
445 repo.merge(ref, self, ref, 'workspace_id', 'user name')
444
446
445 def test_merge_invalid_message(self, vcsbackend):
447 def test_merge_invalid_message(self, vcsbackend):
446 repo = vcsbackend.create_repo(number_of_commits=1)
448 repo = vcsbackend.create_repo(number_of_commits=1)
447 ref = Reference('branch', 'master', 'not_used')
449 ref = Reference('branch', 'master', 'not_used')
448 with pytest.raises(ValueError):
450 with pytest.raises(ValueError):
449 repo.merge(
451 repo.merge(
450 ref, self, ref, 'workspace_id', 'user name', 'user@email.com')
452 ref, self, ref, 'workspace_id', 'user name', 'user@email.com')
451
453
452
454
455 @pytest.mark.usefixtures("vcs_repository_support")
453 class TestRepositoryStrip(BackendTestMixin):
456 class TestRepositoryStrip(BackendTestMixin):
454 recreate_repo_per_test = True
457 recreate_repo_per_test = True
455
458
456 @classmethod
459 @classmethod
457 def _get_commits(cls):
460 def _get_commits(cls):
458 commits = [
461 commits = [
459 {
462 {
460 'message': 'Initial commit',
463 'message': 'Initial commit',
461 'author': 'Joe Doe <joe.doe@example.com>',
464 'author': 'Joe Doe <joe.doe@example.com>',
462 'date': datetime.datetime(2010, 1, 1, 20),
465 'date': datetime.datetime(2010, 1, 1, 20),
463 'branch': 'master',
466 'branch': 'master',
464 'added': [
467 'added': [
465 FileNode('foobar', content='foobar'),
468 FileNode('foobar', content='foobar'),
466 FileNode('foobar2', content='foobar2'),
469 FileNode('foobar2', content='foobar2'),
467 ],
470 ],
468 },
471 },
469 ]
472 ]
470 for x in xrange(10):
473 for x in xrange(10):
471 commit_data = {
474 commit_data = {
472 'message': 'Changed foobar - commit%s' % x,
475 'message': 'Changed foobar - commit%s' % x,
473 'author': 'Jane Doe <jane.doe@example.com>',
476 'author': 'Jane Doe <jane.doe@example.com>',
474 'date': datetime.datetime(2010, 1, 1, 21, x),
477 'date': datetime.datetime(2010, 1, 1, 21, x),
475 'branch': 'master',
478 'branch': 'master',
476 'changed': [
479 'changed': [
477 FileNode('foobar', 'FOOBAR - %s' % x),
480 FileNode('foobar', 'FOOBAR - %s' % x),
478 ],
481 ],
479 }
482 }
480 commits.append(commit_data)
483 commits.append(commit_data)
481 return commits
484 return commits
482
485
483 @pytest.mark.backends("git", "hg")
486 @pytest.mark.backends("git", "hg")
484 def test_strip_commit(self):
487 def test_strip_commit(self):
485 tip = self.repo.get_commit()
488 tip = self.repo.get_commit()
486 assert tip.idx == 10
489 assert tip.idx == 10
487 self.repo.strip(tip.raw_id, self.repo.DEFAULT_BRANCH_NAME)
490 self.repo.strip(tip.raw_id, self.repo.DEFAULT_BRANCH_NAME)
488
491
489 tip = self.repo.get_commit()
492 tip = self.repo.get_commit()
490 assert tip.idx == 9
493 assert tip.idx == 9
491
494
492 @pytest.mark.backends("git", "hg")
495 @pytest.mark.backends("git", "hg")
493 def test_strip_multiple_commits(self):
496 def test_strip_multiple_commits(self):
494 tip = self.repo.get_commit()
497 tip = self.repo.get_commit()
495 assert tip.idx == 10
498 assert tip.idx == 10
496
499
497 old = self.repo.get_commit(commit_idx=5)
500 old = self.repo.get_commit(commit_idx=5)
498 self.repo.strip(old.raw_id, self.repo.DEFAULT_BRANCH_NAME)
501 self.repo.strip(old.raw_id, self.repo.DEFAULT_BRANCH_NAME)
499
502
500 tip = self.repo.get_commit()
503 tip = self.repo.get_commit()
501 assert tip.idx == 4
504 assert tip.idx == 4
502
505
503
506
504 @pytest.mark.backends('hg', 'git')
507 @pytest.mark.backends('hg', 'git')
505 class TestRepositoryPull:
508 class TestRepositoryPull:
506
509
507 def test_pull(self, vcsbackend):
510 def test_pull(self, vcsbackend):
508 source_repo = vcsbackend.repo
511 source_repo = vcsbackend.repo
509 target_repo = vcsbackend.create_repo()
512 target_repo = vcsbackend.create_repo()
510 assert len(source_repo.commit_ids) > len(target_repo.commit_ids)
513 assert len(source_repo.commit_ids) > len(target_repo.commit_ids)
511
514
512 target_repo.pull(source_repo.path)
515 target_repo.pull(source_repo.path)
513 # Note: Get a fresh instance, avoids caching trouble
516 # Note: Get a fresh instance, avoids caching trouble
514 target_repo = vcsbackend.backend(target_repo.path)
517 target_repo = vcsbackend.backend(target_repo.path)
515 assert len(source_repo.commit_ids) == len(target_repo.commit_ids)
518 assert len(source_repo.commit_ids) == len(target_repo.commit_ids)
516
519
517 def test_pull_wrong_path(self, vcsbackend):
520 def test_pull_wrong_path(self, vcsbackend):
518 target_repo = vcsbackend.create_repo()
521 target_repo = vcsbackend.create_repo()
519 with pytest.raises(RepositoryError):
522 with pytest.raises(RepositoryError):
520 target_repo.pull(target_repo.path + "wrong")
523 target_repo.pull(target_repo.path + "wrong")
521
524
522 def test_pull_specific_commits(self, vcsbackend):
525 def test_pull_specific_commits(self, vcsbackend):
523 source_repo = vcsbackend.repo
526 source_repo = vcsbackend.repo
524 target_repo = vcsbackend.create_repo()
527 target_repo = vcsbackend.create_repo()
525
528
526 second_commit = source_repo[1].raw_id
529 second_commit = source_repo[1].raw_id
527 if vcsbackend.alias == 'git':
530 if vcsbackend.alias == 'git':
528 second_commit_ref = 'refs/test-refs/a'
531 second_commit_ref = 'refs/test-refs/a'
529 source_repo.set_refs(second_commit_ref, second_commit)
532 source_repo.set_refs(second_commit_ref, second_commit)
530
533
531 target_repo.pull(source_repo.path, commit_ids=[second_commit])
534 target_repo.pull(source_repo.path, commit_ids=[second_commit])
532 target_repo = vcsbackend.backend(target_repo.path)
535 target_repo = vcsbackend.backend(target_repo.path)
533 assert 2 == len(target_repo.commit_ids)
536 assert 2 == len(target_repo.commit_ids)
534 assert second_commit == target_repo.get_commit().raw_id
537 assert second_commit == target_repo.get_commit().raw_id
@@ -1,69 +1,70 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.tests.vcs.base import BackendTestMixin
23 from rhodecode.tests.vcs.conftest import BackendTestMixin
24 from rhodecode.lib.vcs.exceptions import (
24 from rhodecode.lib.vcs.exceptions import (
25 TagAlreadyExistError, TagDoesNotExistError)
25 TagAlreadyExistError, TagDoesNotExistError)
26
26
27
27
28 pytestmark = pytest.mark.backends("git", "hg")
28 pytestmark = pytest.mark.backends("git", "hg")
29
29
30
30
31 @pytest.mark.usefixtures("vcs_repository_support")
31 class TestTags(BackendTestMixin):
32 class TestTags(BackendTestMixin):
32
33
33 def test_new_tag(self):
34 def test_new_tag(self):
34 tip = self.repo.get_commit()
35 tip = self.repo.get_commit()
35 tagsize = len(self.repo.tags)
36 tagsize = len(self.repo.tags)
36 tag = self.repo.tag('last-commit', 'joe', tip.raw_id)
37 tag = self.repo.tag('last-commit', 'joe', tip.raw_id)
37
38
38 assert len(self.repo.tags) == tagsize + 1
39 assert len(self.repo.tags) == tagsize + 1
39 for top, __, __ in tip.walk():
40 for top, __, __ in tip.walk():
40 assert top == tag.get_node(top.path)
41 assert top == tag.get_node(top.path)
41
42
42 def test_tag_already_exist(self):
43 def test_tag_already_exist(self):
43 tip = self.repo.get_commit()
44 tip = self.repo.get_commit()
44 self.repo.tag('last-commit', 'joe', tip.raw_id)
45 self.repo.tag('last-commit', 'joe', tip.raw_id)
45
46
46 with pytest.raises(TagAlreadyExistError):
47 with pytest.raises(TagAlreadyExistError):
47 self.repo.tag('last-commit', 'joe', tip.raw_id)
48 self.repo.tag('last-commit', 'joe', tip.raw_id)
48
49
49 commit = self.repo.get_commit(commit_idx=0)
50 commit = self.repo.get_commit(commit_idx=0)
50 with pytest.raises(TagAlreadyExistError):
51 with pytest.raises(TagAlreadyExistError):
51 self.repo.tag('last-commit', 'jane', commit.raw_id)
52 self.repo.tag('last-commit', 'jane', commit.raw_id)
52
53
53 def test_remove_tag(self):
54 def test_remove_tag(self):
54 tip = self.repo.get_commit()
55 tip = self.repo.get_commit()
55 self.repo.tag('last-commit', 'joe', tip.raw_id)
56 self.repo.tag('last-commit', 'joe', tip.raw_id)
56 tagsize = len(self.repo.tags)
57 tagsize = len(self.repo.tags)
57
58
58 self.repo.remove_tag('last-commit', user='evil joe')
59 self.repo.remove_tag('last-commit', user='evil joe')
59 assert len(self.repo.tags) == tagsize - 1
60 assert len(self.repo.tags) == tagsize - 1
60
61
61 def test_remove_tag_which_does_not_exist(self):
62 def test_remove_tag_which_does_not_exist(self):
62 with pytest.raises(TagDoesNotExistError):
63 with pytest.raises(TagDoesNotExistError):
63 self.repo.remove_tag('last-commit', user='evil joe')
64 self.repo.remove_tag('last-commit', user='evil joe')
64
65
65 def test_name_with_slash(self):
66 def test_name_with_slash(self):
66 self.repo.tag('19/10/11', 'joe')
67 self.repo.tag('19/10/11', 'joe')
67 assert '19/10/11' in self.repo.tags
68 assert '19/10/11' in self.repo.tags
68 self.repo.tag('11', 'joe')
69 self.repo.tag('11', 'joe')
69 assert '11' in self.repo.tags
70 assert '11' in self.repo.tags
@@ -1,195 +1,195 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import datetime
22 import datetime
22 import os
23 import subprocess32
23 import subprocess32
24
24
25 import pytest
25 import pytest
26
26
27 from rhodecode.lib.vcs.exceptions import VCSError
27 from rhodecode.lib.vcs.exceptions import VCSError
28 from rhodecode.lib.vcs.utils import author_email, author_name
28 from rhodecode.lib.vcs.utils import author_email, author_name
29 from rhodecode.lib.vcs.utils.helpers import get_scm
29 from rhodecode.lib.vcs.utils.helpers import get_scm
30 from rhodecode.lib.vcs.utils.helpers import get_scms_for_path
30 from rhodecode.lib.vcs.utils.helpers import get_scms_for_path
31 from rhodecode.lib.vcs.utils.helpers import parse_datetime
31 from rhodecode.lib.vcs.utils.helpers import parse_datetime
32 from rhodecode.lib.vcs.utils.paths import get_dirs_for_path
32 from rhodecode.lib.vcs.utils.paths import get_dirs_for_path
33
33
34
34
35 @pytest.mark.usefixtures("baseapp")
35 @pytest.mark.usefixtures("baseapp")
36 class TestPaths:
36 class TestPaths(object):
37
37
38 def _test_get_dirs_for_path(self, path, expected):
38 def _test_get_dirs_for_path(self, path, expected):
39 """
39 """
40 Tests if get_dirs_for_path returns same as expected.
40 Tests if get_dirs_for_path returns same as expected.
41 """
41 """
42 expected = sorted(expected)
42 expected = sorted(expected)
43 result = sorted(get_dirs_for_path(path))
43 result = sorted(get_dirs_for_path(path))
44 assert result == expected, (
44 assert result == expected, (
45 "%s != %s which was expected result for path %s"
45 "%s != %s which was expected result for path %s"
46 % (result, expected, path))
46 % (result, expected, path))
47
47
48 def test_get_dirs_for_path(self):
48 def test_get_dirs_for_path(self):
49 path = 'foo/bar/baz/file'
49 path = 'foo/bar/baz/file'
50 paths_and_results = (
50 paths_and_results = (
51 ('foo/bar/baz/file', ['foo', 'foo/bar', 'foo/bar/baz']),
51 ('foo/bar/baz/file', ['foo', 'foo/bar', 'foo/bar/baz']),
52 ('foo/bar/', ['foo', 'foo/bar']),
52 ('foo/bar/', ['foo', 'foo/bar']),
53 ('foo/bar', ['foo']),
53 ('foo/bar', ['foo']),
54 )
54 )
55 for path, expected in paths_and_results:
55 for path, expected in paths_and_results:
56 self._test_get_dirs_for_path(path, expected)
56 self._test_get_dirs_for_path(path, expected)
57
57
58 def test_get_scms_for_path(self, tmpdir):
58 def test_get_scms_for_path(self, tmpdir):
59 new = tmpdir.strpath
59 new = tmpdir.strpath
60 assert get_scms_for_path(new) == []
60 assert get_scms_for_path(new) == []
61
61
62 os.mkdir(os.path.join(new, '.tux'))
62 os.mkdir(os.path.join(new, '.tux'))
63 assert get_scms_for_path(new) == []
63 assert get_scms_for_path(new) == []
64
64
65 os.mkdir(os.path.join(new, '.git'))
65 os.mkdir(os.path.join(new, '.git'))
66 assert set(get_scms_for_path(new)) == set(['git'])
66 assert set(get_scms_for_path(new)) == set(['git'])
67
67
68 os.mkdir(os.path.join(new, '.hg'))
68 os.mkdir(os.path.join(new, '.hg'))
69 assert set(get_scms_for_path(new)) == set(['git', 'hg'])
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 def test_existing_repository(self, vcs_repository_support):
74 def test_existing_repository(self, vcs_repository_support):
75 alias, repo = vcs_repository_support
75 alias, repo = vcs_repository_support
76 assert (alias, repo.path) == get_scm(repo.path)
76 assert (alias, repo.path) == get_scm(repo.path)
77
77
78 def test_raises_if_path_is_empty(self, tmpdir):
78 def test_raises_if_path_is_empty(self, tmpdir):
79 with pytest.raises(VCSError):
79 with pytest.raises(VCSError):
80 get_scm(str(tmpdir))
80 get_scm(str(tmpdir))
81
81
82 def test_get_scm_error_path(self):
82 def test_get_scm_error_path(self):
83 with pytest.raises(VCSError):
83 with pytest.raises(VCSError):
84 get_scm('err')
84 get_scm('err')
85
85
86 def test_get_two_scms_for_path(self, tmpdir):
86 def test_get_two_scms_for_path(self, tmpdir):
87 multialias_repo_path = str(tmpdir)
87 multialias_repo_path = str(tmpdir)
88
88
89 subprocess32.check_call(['hg', 'init', multialias_repo_path])
89 subprocess32.check_call(['hg', 'init', multialias_repo_path])
90 subprocess32.check_call(['git', 'init', multialias_repo_path])
90 subprocess32.check_call(['git', 'init', multialias_repo_path])
91
91
92 with pytest.raises(VCSError):
92 with pytest.raises(VCSError):
93 get_scm(multialias_repo_path)
93 get_scm(multialias_repo_path)
94
94
95 def test_ignores_svn_working_copy(self, tmpdir):
95 def test_ignores_svn_working_copy(self, tmpdir):
96 tmpdir.mkdir('.svn')
96 tmpdir.mkdir('.svn')
97 with pytest.raises(VCSError):
97 with pytest.raises(VCSError):
98 get_scm(tmpdir.strpath)
98 get_scm(tmpdir.strpath)
99
99
100
100
101 class TestParseDatetime:
101 class TestParseDatetime(object):
102
102
103 def test_datetime_text(self):
103 def test_datetime_text(self):
104 assert parse_datetime('2010-04-07 21:29:41') == \
104 assert parse_datetime('2010-04-07 21:29:41') == \
105 datetime.datetime(2010, 4, 7, 21, 29, 41)
105 datetime.datetime(2010, 4, 7, 21, 29, 41)
106
106
107 def test_no_seconds(self):
107 def test_no_seconds(self):
108 assert parse_datetime('2010-04-07 21:29') == \
108 assert parse_datetime('2010-04-07 21:29') == \
109 datetime.datetime(2010, 4, 7, 21, 29)
109 datetime.datetime(2010, 4, 7, 21, 29)
110
110
111 def test_date_only(self):
111 def test_date_only(self):
112 assert parse_datetime('2010-04-07') == \
112 assert parse_datetime('2010-04-07') == \
113 datetime.datetime(2010, 4, 7)
113 datetime.datetime(2010, 4, 7)
114
114
115 def test_another_format(self):
115 def test_another_format(self):
116 assert parse_datetime('04/07/10 21:29:41') == \
116 assert parse_datetime('04/07/10 21:29:41') == \
117 datetime.datetime(2010, 4, 7, 21, 29, 41)
117 datetime.datetime(2010, 4, 7, 21, 29, 41)
118
118
119 def test_now(self):
119 def test_now(self):
120 assert parse_datetime('now') - datetime.datetime.now() < \
120 assert parse_datetime('now') - datetime.datetime.now() < \
121 datetime.timedelta(seconds=1)
121 datetime.timedelta(seconds=1)
122
122
123 def test_today(self):
123 def test_today(self):
124 today = datetime.date.today()
124 today = datetime.date.today()
125 assert parse_datetime('today') == \
125 assert parse_datetime('today') == \
126 datetime.datetime(*today.timetuple()[:3])
126 datetime.datetime(*today.timetuple()[:3])
127
127
128 def test_yesterday(self):
128 def test_yesterday(self):
129 yesterday = datetime.date.today() - datetime.timedelta(days=1)
129 yesterday = datetime.date.today() - datetime.timedelta(days=1)
130 assert parse_datetime('yesterday') == \
130 assert parse_datetime('yesterday') == \
131 datetime.datetime(*yesterday.timetuple()[:3])
131 datetime.datetime(*yesterday.timetuple()[:3])
132
132
133 def test_tomorrow(self):
133 def test_tomorrow(self):
134 tomorrow = datetime.date.today() + datetime.timedelta(days=1)
134 tomorrow = datetime.date.today() + datetime.timedelta(days=1)
135 args = tomorrow.timetuple()[:3] + (23, 59, 59)
135 args = tomorrow.timetuple()[:3] + (23, 59, 59)
136 assert parse_datetime('tomorrow') == datetime.datetime(*args)
136 assert parse_datetime('tomorrow') == datetime.datetime(*args)
137
137
138 def test_days(self):
138 def test_days(self):
139 timestamp = datetime.datetime.today() - datetime.timedelta(days=3)
139 timestamp = datetime.datetime.today() - datetime.timedelta(days=3)
140 args = timestamp.timetuple()[:3] + (0, 0, 0, 0)
140 args = timestamp.timetuple()[:3] + (0, 0, 0, 0)
141 expected = datetime.datetime(*args)
141 expected = datetime.datetime(*args)
142 assert parse_datetime('3d') == expected
142 assert parse_datetime('3d') == expected
143 assert parse_datetime('3 d') == expected
143 assert parse_datetime('3 d') == expected
144 assert parse_datetime('3 day') == expected
144 assert parse_datetime('3 day') == expected
145 assert parse_datetime('3 days') == expected
145 assert parse_datetime('3 days') == expected
146
146
147 def test_weeks(self):
147 def test_weeks(self):
148 timestamp = datetime.datetime.today() - datetime.timedelta(days=3 * 7)
148 timestamp = datetime.datetime.today() - datetime.timedelta(days=3 * 7)
149 args = timestamp.timetuple()[:3] + (0, 0, 0, 0)
149 args = timestamp.timetuple()[:3] + (0, 0, 0, 0)
150 expected = datetime.datetime(*args)
150 expected = datetime.datetime(*args)
151 assert parse_datetime('3w') == expected
151 assert parse_datetime('3w') == expected
152 assert parse_datetime('3 w') == expected
152 assert parse_datetime('3 w') == expected
153 assert parse_datetime('3 week') == expected
153 assert parse_datetime('3 week') == expected
154 assert parse_datetime('3 weeks') == expected
154 assert parse_datetime('3 weeks') == expected
155
155
156 def test_mixed(self):
156 def test_mixed(self):
157 timestamp = (
157 timestamp = (
158 datetime.datetime.today() - datetime.timedelta(days=2 * 7 + 3))
158 datetime.datetime.today() - datetime.timedelta(days=2 * 7 + 3))
159 args = timestamp.timetuple()[:3] + (0, 0, 0, 0)
159 args = timestamp.timetuple()[:3] + (0, 0, 0, 0)
160 expected = datetime.datetime(*args)
160 expected = datetime.datetime(*args)
161 assert parse_datetime('2w3d') == expected
161 assert parse_datetime('2w3d') == expected
162 assert parse_datetime('2w 3d') == expected
162 assert parse_datetime('2w 3d') == expected
163 assert parse_datetime('2w 3 days') == expected
163 assert parse_datetime('2w 3 days') == expected
164 assert parse_datetime('2 weeks 3 days') == expected
164 assert parse_datetime('2 weeks 3 days') == expected
165
165
166
166
167 @pytest.mark.parametrize("test_str, name, email", [
167 @pytest.mark.parametrize("test_str, name, email", [
168 ('Marcin Kuzminski <marcin@python-works.com>',
168 ('Marcin Kuzminski <marcin@python-works.com>',
169 'Marcin Kuzminski', 'marcin@python-works.com'),
169 'Marcin Kuzminski', 'marcin@python-works.com'),
170 ('Marcin Kuzminski Spaces < marcin@python-works.com >',
170 ('Marcin Kuzminski Spaces < marcin@python-works.com >',
171 'Marcin Kuzminski Spaces', 'marcin@python-works.com'),
171 'Marcin Kuzminski Spaces', 'marcin@python-works.com'),
172 ('Marcin Kuzminski <marcin.kuzminski@python-works.com>',
172 ('Marcin Kuzminski <marcin.kuzminski@python-works.com>',
173 'Marcin Kuzminski', 'marcin.kuzminski@python-works.com'),
173 'Marcin Kuzminski', 'marcin.kuzminski@python-works.com'),
174 ('mrf RFC_SPEC <marcin+kuzminski@python-works.com>',
174 ('mrf RFC_SPEC <marcin+kuzminski@python-works.com>',
175 'mrf RFC_SPEC', 'marcin+kuzminski@python-works.com'),
175 'mrf RFC_SPEC', 'marcin+kuzminski@python-works.com'),
176 ('username <user@email.com>',
176 ('username <user@email.com>',
177 'username', 'user@email.com'),
177 'username', 'user@email.com'),
178 ('username <user@email.com',
178 ('username <user@email.com',
179 'username', 'user@email.com'),
179 'username', 'user@email.com'),
180 ('broken missing@email.com',
180 ('broken missing@email.com',
181 'broken', 'missing@email.com'),
181 'broken', 'missing@email.com'),
182 ('<justemail@mail.com>',
182 ('<justemail@mail.com>',
183 '', 'justemail@mail.com'),
183 '', 'justemail@mail.com'),
184 ('justname',
184 ('justname',
185 'justname', ''),
185 'justname', ''),
186 ('Mr Double Name withemail@email.com ',
186 ('Mr Double Name withemail@email.com ',
187 'Mr Double Name', 'withemail@email.com'),
187 'Mr Double Name', 'withemail@email.com'),
188 ])
188 ])
189 class TestAuthorExtractors:
189 class TestAuthorExtractors(object):
190
190
191 def test_author_email(self, test_str, name, email):
191 def test_author_email(self, test_str, name, email):
192 assert email == author_email(test_str)
192 assert email == author_email(test_str)
193
193
194 def test_author_name(self, test_str, name, email):
194 def test_author_name(self, test_str, name, email):
195 assert name == author_name(test_str)
195 assert name == author_name(test_str)
@@ -1,77 +1,77 b''
1 ################################################################################
1 ################################################################################
2 # RhodeCode VCSServer with HTTP Backend - configuration #
2 # RhodeCode VCSServer with HTTP Backend - configuration #
3 # #
3 # #
4 ################################################################################
4 ################################################################################
5
5
6 [app:main]
6 [app:main]
7 use = egg:rhodecode-vcsserver
7 use = egg:rhodecode-vcsserver
8
8
9 pyramid.default_locale_name = en
9 pyramid.default_locale_name = en
10 pyramid.includes =
10 pyramid.includes =
11 pyramid.reload_templates = true
12
11
13 # default locale used by VCS systems
12 # default locale used by VCS systems
14 locale = en_US.UTF-8
13 locale = en_US.UTF-8
15
14
16 # cache regions, please don't change
15 # cache regions, please don't change
17 beaker.cache.regions = repo_object
16 beaker.cache.regions = repo_object
18 beaker.cache.repo_object.type = memorylru
17 beaker.cache.repo_object.type = memorylru
19 beaker.cache.repo_object.max_items = 100
18 beaker.cache.repo_object.max_items = 100
20 # cache auto-expires after N seconds
19 # cache auto-expires after N seconds
21 beaker.cache.repo_object.expire = 300
20 beaker.cache.repo_object.expire = 300
22 beaker.cache.repo_object.enabled = true
21 beaker.cache.repo_object.enabled = true
23
22
24 [server:main]
23 [server:main]
25 use = egg:waitress#main
26 host = 127.0.0.1
24 host = 127.0.0.1
27 port = 9900
25 port = 9900
28
26
27 use = egg:gunicorn#main
28
29 ################################
29 ################################
30 ### LOGGING CONFIGURATION ####
30 ### LOGGING CONFIGURATION ####
31 ################################
31 ################################
32 [loggers]
32 [loggers]
33 keys = root, vcsserver, beaker
33 keys = root, vcsserver, beaker
34
34
35 [handlers]
35 [handlers]
36 keys = console
36 keys = console
37
37
38 [formatters]
38 [formatters]
39 keys = generic
39 keys = generic
40
40
41 #############
41 #############
42 ## LOGGERS ##
42 ## LOGGERS ##
43 #############
43 #############
44 [logger_root]
44 [logger_root]
45 level = NOTSET
45 level = NOTSET
46 handlers = console
46 handlers = console
47
47
48 [logger_vcsserver]
48 [logger_vcsserver]
49 level = DEBUG
49 level = DEBUG
50 handlers =
50 handlers =
51 qualname = vcsserver
51 qualname = vcsserver
52 propagate = 1
52 propagate = 1
53
53
54 [logger_beaker]
54 [logger_beaker]
55 level = DEBUG
55 level = DEBUG
56 handlers =
56 handlers =
57 qualname = beaker
57 qualname = beaker
58 propagate = 1
58 propagate = 1
59
59
60
60
61 ##############
61 ##############
62 ## HANDLERS ##
62 ## HANDLERS ##
63 ##############
63 ##############
64
64
65 [handler_console]
65 [handler_console]
66 class = StreamHandler
66 class = StreamHandler
67 args = (sys.stderr,)
67 args = (sys.stderr,)
68 level = INFO
68 level = INFO
69 formatter = generic
69 formatter = generic
70
70
71 ################
71 ################
72 ## FORMATTERS ##
72 ## FORMATTERS ##
73 ################
73 ################
74
74
75 [formatter_generic]
75 [formatter_generic]
76 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
76 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
77 datefmt = %Y-%m-%d %H:%M:%S
77 datefmt = %Y-%m-%d %H:%M:%S
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now