##// END OF EJS Templates
release: Merge default into stable for release preparation
marcink -
r227:ddd92633 merge stable
parent child Browse files
Show More
@@ -1,5 +1,5 b''
1 1 [bumpversion]
2 current_version = 4.7.2
2 current_version = 4.8.0
3 3 message = release: Bump version {current_version} to {new_version}
4 4
5 5 [bumpversion:file:vcsserver/VERSION]
@@ -5,12 +5,10 b' done = false'
5 5 done = true
6 6
7 7 [task:fixes_on_stable]
8 done = true
9 8
10 9 [task:pip2nix_generated]
11 done = true
12 10
13 11 [release]
14 state = prepared
15 version = 4.7.2
12 state = in_progress
13 version = 4.8.0
16 14
@@ -32,7 +32,7 b' use = egg:waitress#main'
32 32 ### LOGGING CONFIGURATION ####
33 33 ################################
34 34 [loggers]
35 keys = root, vcsserver, pyro4, beaker
35 keys = root, vcsserver, beaker
36 36
37 37 [handlers]
38 38 keys = console
@@ -59,12 +59,6 b' handlers ='
59 59 qualname = beaker
60 60 propagate = 1
61 61
62 [logger_pyro4]
63 level = DEBUG
64 handlers =
65 qualname = Pyro4
66 propagate = 1
67
68 62
69 63 ##############
70 64 ## HANDLERS ##
@@ -20,8 +20,7 b' use = egg:gunicorn#main'
20 20 workers = 2
21 21 ## process name
22 22 proc_name = rhodecode_vcsserver
23 ## type of worker class, one of sync, gevent
24 ## recommended for bigger setup is using of of other than sync one
23 ## type of worker class, currently `sync` is the only option allowed.
25 24 worker_class = sync
26 25 ## The maximum number of simultaneous clients. Valid only for Gevent
27 26 #worker_connections = 10
@@ -56,7 +55,7 b' beaker.cache.repo_object.enabled = true'
56 55 ### LOGGING CONFIGURATION ####
57 56 ################################
58 57 [loggers]
59 keys = root, vcsserver, pyro4, beaker
58 keys = root, vcsserver, beaker
60 59
61 60 [handlers]
62 61 keys = console
@@ -83,12 +82,6 b' handlers ='
83 82 qualname = beaker
84 83 propagate = 1
85 84
86 [logger_pyro4]
87 level = DEBUG
88 handlers =
89 qualname = Pyro4
90 propagate = 1
91
92 85
93 86 ##############
94 87 ## HANDLERS ##
@@ -120,6 +120,7 b' let'
120 120 cp -v vcsserver/VERSION $out/nix-support/rccontrol/version
121 121 echo "DONE: Meta information for rccontrol written"
122 122
123 # python based programs need to be wrapped
123 124 ln -s ${self.pyramid}/bin/* $out/bin/
124 125 ln -s ${self.gunicorn}/bin/gunicorn $out/bin/
125 126
@@ -132,12 +133,14 b' let'
132 133 ln -s ${self.mercurial}/bin/hg $out/bin
133 134 ln -s ${pkgs.subversion}/bin/svn* $out/bin
134 135
135 for file in $out/bin/*; do
136 for file in $out/bin/*;
137 do
136 138 wrapProgram $file \
137 139 --set PATH $PATH \
138 140 --set PYTHONPATH $PYTHONPATH \
139 141 --set PYTHONHASHSEED random
140 142 done
143
141 144 '';
142 145
143 146 });
@@ -67,19 +67,6 b''
67 67 license = [ pkgs.lib.licenses.mit ];
68 68 };
69 69 };
70 Pyro4 = super.buildPythonPackage {
71 name = "Pyro4-4.41";
72 buildInputs = with self; [];
73 doCheck = false;
74 propagatedBuildInputs = with self; [serpent];
75 src = fetchurl {
76 url = "https://pypi.python.org/packages/56/2b/89b566b4bf3e7f8ba790db2d1223852f8cb454c52cab7693dd41f608ca2a/Pyro4-4.41.tar.gz";
77 md5 = "ed69e9bfafa9c06c049a87cb0c4c2b6c";
78 };
79 meta = {
80 license = [ pkgs.lib.licenses.mit ];
81 };
82 };
83 70 WebOb = super.buildPythonPackage {
84 71 name = "WebOb-1.3.1";
85 72 buildInputs = with self; [];
@@ -249,6 +236,19 b''
249 236 license = [ pkgs.lib.licenses.mit ];
250 237 };
251 238 };
239 hg-evolve = super.buildPythonPackage {
240 name = "hg-evolve-6.0.1";
241 buildInputs = with self; [];
242 doCheck = false;
243 propagatedBuildInputs = with self; [];
244 src = fetchurl {
245 url = "https://pypi.python.org/packages/c4/31/0673a5657c201ebb46e63c4bba8668f96cf5d7a8a0f8a91892d022ccc32b/hg-evolve-6.0.1.tar.gz";
246 md5 = "9c1ce7ac24792abc0eedee09a3344d06";
247 };
248 meta = {
249 license = [ { fullName = "GPLv2+"; } ];
250 };
251 };
252 252 hgsubversion = super.buildPythonPackage {
253 253 name = "hgsubversion-1.8.6";
254 254 buildInputs = with self; [];
@@ -302,13 +302,13 b''
302 302 };
303 303 };
304 304 ipython-genutils = super.buildPythonPackage {
305 name = "ipython-genutils-0.1.0";
305 name = "ipython-genutils-0.2.0";
306 306 buildInputs = with self; [];
307 307 doCheck = false;
308 308 propagatedBuildInputs = with self; [];
309 309 src = fetchurl {
310 url = "https://pypi.python.org/packages/71/b7/a64c71578521606edbbce15151358598f3dfb72a3431763edc2baf19e71f/ipython_genutils-0.1.0.tar.gz";
311 md5 = "9a8afbe0978adbcbfcb3b35b2d015a56";
310 url = "https://pypi.python.org/packages/e8/69/fbeffffc05236398ebfcfb512b6d2511c622871dca1746361006da310399/ipython_genutils-0.2.0.tar.gz";
311 md5 = "5a4f9781f78466da0ea1a648f3e1f79f";
312 312 };
313 313 meta = {
314 314 license = [ pkgs.lib.licenses.bsdOriginal ];
@@ -393,13 +393,13 b''
393 393 };
394 394 };
395 395 prompt-toolkit = super.buildPythonPackage {
396 name = "prompt-toolkit-1.0.9";
396 name = "prompt-toolkit-1.0.14";
397 397 buildInputs = with self; [];
398 398 doCheck = false;
399 399 propagatedBuildInputs = with self; [six wcwidth];
400 400 src = fetchurl {
401 url = "https://pypi.python.org/packages/83/14/5ac258da6c530eca02852ee25c7a9ff3ca78287bb4c198d0d0055845d856/prompt_toolkit-1.0.9.tar.gz";
402 md5 = "a39f91a54308fb7446b1a421c11f227c";
401 url = "https://pypi.python.org/packages/55/56/8c39509b614bda53e638b7500f12577d663ac1b868aef53426fc6a26c3f5/prompt_toolkit-1.0.14.tar.gz";
402 md5 = "f24061ae133ed32c6b764e92bd48c496";
403 403 };
404 404 meta = {
405 405 license = [ pkgs.lib.licenses.bsdOriginal ];
@@ -588,28 +588,15 b''
588 588 };
589 589 };
590 590 rhodecode-vcsserver = super.buildPythonPackage {
591 name = "rhodecode-vcsserver-4.7.2";
591 name = "rhodecode-vcsserver-4.8.0";
592 592 buildInputs = with self; [pytest py pytest-cov pytest-sugar pytest-runner pytest-catchlog pytest-profiling gprof2dot pytest-timeout mock WebTest cov-core coverage configobj];
593 593 doCheck = true;
594 propagatedBuildInputs = with self; [Beaker configobj decorator dulwich hgsubversion infrae.cache mercurial msgpack-python pyramid pyramid-jinja2 pyramid-mako repoze.lru simplejson subprocess32 subvertpy six translationstring WebOb wheel zope.deprecation zope.interface ipdb ipython gevent greenlet gunicorn waitress Pyro4 serpent pytest py pytest-cov pytest-sugar pytest-runner pytest-catchlog pytest-profiling gprof2dot pytest-timeout mock WebTest cov-core coverage];
594 propagatedBuildInputs = with self; [Beaker configobj decorator dulwich hgsubversion hg-evolve infrae.cache mercurial msgpack-python pyramid pyramid-jinja2 pyramid-mako repoze.lru simplejson subprocess32 subvertpy six translationstring WebOb wheel zope.deprecation zope.interface ipdb ipython gevent greenlet gunicorn waitress pytest py pytest-cov pytest-sugar pytest-runner pytest-catchlog pytest-profiling gprof2dot pytest-timeout mock WebTest cov-core coverage];
595 595 src = ./.;
596 596 meta = {
597 597 license = [ { fullName = "GPL V3"; } { fullName = "GNU General Public License v3 or later (GPLv3+)"; } ];
598 598 };
599 599 };
600 serpent = super.buildPythonPackage {
601 name = "serpent-1.15";
602 buildInputs = with self; [];
603 doCheck = false;
604 propagatedBuildInputs = with self; [];
605 src = fetchurl {
606 url = "https://pypi.python.org/packages/7b/38/b2b27673a882ff2ea5871bb3e3e6b496ebbaafd1612e51990ffb158b9254/serpent-1.15.tar.gz";
607 md5 = "e27b1aad5c218e16442f52abb7c7053a";
608 };
609 meta = {
610 license = [ pkgs.lib.licenses.mit ];
611 };
612 };
613 600 setuptools = super.buildPythonPackage {
614 601 name = "setuptools-30.1.0";
615 602 buildInputs = with self; [];
@@ -702,13 +689,13 b''
702 689 };
703 690 };
704 691 traitlets = super.buildPythonPackage {
705 name = "traitlets-4.3.1";
692 name = "traitlets-4.3.2";
706 693 buildInputs = with self; [];
707 694 doCheck = false;
708 695 propagatedBuildInputs = with self; [ipython-genutils six decorator enum34];
709 696 src = fetchurl {
710 url = "https://pypi.python.org/packages/b1/d6/5b5aa6d5c474691909b91493da1e8972e309c9f01ecfe4aeafd272eb3234/traitlets-4.3.1.tar.gz";
711 md5 = "dd0b1b6e5d31ce446d55a4b5e5083c98";
697 url = "https://pypi.python.org/packages/a5/98/7f5ef2fe9e9e071813aaf9cb91d1a732e0a68b6c44a32b38cb8e14c3f069/traitlets-4.3.2.tar.gz";
698 md5 = "3068663f2f38fd939a9eb3a500ccc154";
712 699 };
713 700 meta = {
714 701 license = [ pkgs.lib.licenses.bsdOriginal ];
@@ -6,6 +6,7 b' configobj==5.0.6'
6 6 decorator==4.0.11
7 7 dulwich==0.13.0
8 8 hgsubversion==1.8.6
9 hg-evolve==6.0.1
9 10 infrae.cache==1.0.1
10 11 mercurial==4.1.2
11 12 msgpack-python==0.4.8
@@ -35,9 +36,5 b' greenlet==0.4.10'
35 36 gunicorn==19.6.0
36 37 waitress==1.0.1
37 38
38 # Pyro/Deprecated TODO(Marcink): remove in 4.7 release.
39 Pyro4==4.41
40 serpent==1.15
41
42 39 ## test related requirements
43 40 -r requirements_test.txt
@@ -26,7 +26,7 b' beaker.cache.repo_object.enabled = true'
26 26 ### LOGGING CONFIGURATION ####
27 27 ################################
28 28 [loggers]
29 keys = root, vcsserver, pyro4, beaker
29 keys = root, vcsserver, beaker
30 30
31 31 [handlers]
32 32 keys = console
@@ -53,12 +53,6 b' handlers ='
53 53 qualname = beaker
54 54 propagate = 1
55 55
56 [logger_pyro4]
57 level = DEBUG
58 handlers =
59 qualname = Pyro4
60 propagate = 1
61
62 56
63 57 ##############
64 58 ## HANDLERS ##
@@ -1,1 +1,1 b''
1 4.7.2 No newline at end of file
1 4.8.0 No newline at end of file
@@ -287,6 +287,25 b' class HgRemote(object):'
287 287 return [parent.rev() for parent in ctx.parents()]
288 288
289 289 @reraise_safe_exceptions
290 def ctx_phase(self, wire, revision):
291 repo = self._factory.repo(wire)
292 ctx = repo[revision]
293 # public=0, draft=1, secret=3
294 return ctx.phase()
295
296 @reraise_safe_exceptions
297 def ctx_obsolete(self, wire, revision):
298 repo = self._factory.repo(wire)
299 ctx = repo[revision]
300 return ctx.obsolete()
301
302 @reraise_safe_exceptions
303 def ctx_hidden(self, wire, revision):
304 repo = self._factory.repo(wire)
305 ctx = repo[revision]
306 return ctx.hidden()
307
308 @reraise_safe_exceptions
290 309 def ctx_substate(self, wire, revision):
291 310 repo = self._factory.repo(wire)
292 311 ctx = repo[revision]
@@ -298,7 +317,7 b' class HgRemote(object):'
298 317 ctx = repo[revision]
299 318 status = repo[ctx.p1().node()].status(other=ctx.node())
300 319 # object of status (odd, custom named tuple in mercurial) is not
301 # correctly serializable via Pyro, we make it a list, as the underling
320 # correctly serializable, we make it a list, as the underling
302 321 # API expects this to be a list
303 322 return list(status)
304 323
@@ -30,7 +30,6 b' from httplib import HTTPConnection'
30 30
31 31 import mercurial.scmutil
32 32 import mercurial.node
33 import Pyro4
34 33 import simplejson as json
35 34
36 35 from vcsserver import exceptions
@@ -68,18 +67,9 b' class HooksDummyClient(object):'
68 67 return getattr(hooks, hook_name)(extras)
69 68
70 69
71 class HooksPyro4Client(object):
72 def __init__(self, hooks_uri):
73 self.hooks_uri = hooks_uri
74
75 def __call__(self, hook_name, extras):
76 with Pyro4.Proxy(self.hooks_uri) as hooks:
77 return getattr(hooks, hook_name)(extras)
78
79
80 70 class RemoteMessageWriter(object):
81 71 """Writer base class."""
82 def write(message):
72 def write(self, message):
83 73 raise NotImplementedError()
84 74
85 75
@@ -126,11 +116,7 b' def _handle_exception(result):'
126 116 def _get_hooks_client(extras):
127 117 if 'hooks_uri' in extras:
128 118 protocol = extras.get('hooks_protocol')
129 return (
130 HooksHttpClient(extras['hooks_uri'])
131 if protocol == 'http'
132 else HooksPyro4Client(extras['hooks_uri'])
133 )
119 return HooksHttpClient(extras['hooks_uri'])
134 120 else:
135 121 return HooksDummyClient(extras['hooks_module'])
136 122
@@ -161,13 +147,25 b' def post_pull(ui, repo, **kwargs):'
161 147 return _call_hook('post_pull', _extras_from_ui(ui), HgMessageWriter(ui))
162 148
163 149
150 def _rev_range_hash(repo, node):
151
152 commits = []
153 for rev in xrange(repo[node], len(repo)):
154 ctx = repo[rev]
155 commit_id = mercurial.node.hex(ctx.node())
156 branch = ctx.branch()
157 commits.append((commit_id, branch))
158
159 return commits
160
161
164 162 def pre_push(ui, repo, node=None, **kwargs):
165 163 extras = _extras_from_ui(ui)
166 164
167 165 rev_data = []
168 166 if node and kwargs.get('hooktype') == 'pretxnchangegroup':
169 167 branches = collections.defaultdict(list)
170 for commit_id, branch in _rev_range_hash(repo, node, with_branch=True):
168 for commit_id, branch in _rev_range_hash(repo, node):
171 169 branches[branch].append(commit_id)
172 170
173 171 for branch, commits in branches.iteritems():
@@ -184,30 +182,38 b' def pre_push(ui, repo, node=None, **kwar'
184 182 return _call_hook('pre_push', extras, HgMessageWriter(ui))
185 183
186 184
187 def _rev_range_hash(repo, node, with_branch=False):
185 def post_push(ui, repo, node, **kwargs):
186 extras = _extras_from_ui(ui)
187
188 commit_ids = []
189 branches = []
190 bookmarks = []
191 tags = []
188 192
189 commits = []
190 for rev in xrange(repo[node], len(repo)):
191 ctx = repo[rev]
192 commit_id = mercurial.node.hex(ctx.node())
193 branch = ctx.branch()
194 if with_branch:
195 commits.append((commit_id, branch))
196 else:
197 commits.append(commit_id)
193 for commit_id, branch in _rev_range_hash(repo, node):
194 commit_ids.append(commit_id)
195 if branch not in branches:
196 branches.append(branch)
198 197
199 return commits
200
198 if hasattr(ui, '_rc_pushkey_branches'):
199 bookmarks = ui._rc_pushkey_branches
201 200
202 def post_push(ui, repo, node, **kwargs):
203 commit_ids = _rev_range_hash(repo, node)
204
205 extras = _extras_from_ui(ui)
206 201 extras['commit_ids'] = commit_ids
202 extras['new_refs'] = {
203 'branches': branches,
204 'bookmarks': bookmarks,
205 'tags': tags
206 }
207 207
208 208 return _call_hook('post_push', extras, HgMessageWriter(ui))
209 209
210 210
211 def key_push(ui, repo, **kwargs):
212 if kwargs['new'] != '0' and kwargs['namespace'] == 'bookmarks':
213 # store new bookmarks in our UI object propagated later to post_push
214 ui._rc_pushkey_branches = repo[kwargs['key']].bookmarks()
215 return
216
211 217 # backward compat
212 218 log_pull_action = post_pull
213 219
@@ -327,12 +333,12 b' def _run_command(arguments):'
327 333 # Probably this should be using subprocessio.
328 334 process = subprocess.Popen(
329 335 arguments, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
330 stdout, _ = process.communicate()
336 stdout, stderr = process.communicate()
331 337
332 338 if process.returncode != 0:
333 339 raise Exception(
334 'Command %s exited with exit code %s' % (arguments,
335 process.returncode))
340 'Command %s exited with exit code %s: stderr:%s' % (
341 arguments, process.returncode, stderr))
336 342
337 343 return stdout
338 344
@@ -359,10 +365,16 b' def git_post_receive(unused_repo_path, r'
359 365 # subcommand sets the PATH environment variable so that it point to the
360 366 # correct version of the git executable.
361 367 empty_commit_id = '0' * 40
368 branches = []
369 tags = []
362 370 for push_ref in rev_data:
363 371 type_ = push_ref['type']
372
364 373 if type_ == 'heads':
365 374 if push_ref['old_rev'] == empty_commit_id:
375 # starting new branch case
376 if push_ref['name'] not in branches:
377 branches.append(push_ref['name'])
366 378
367 379 # Fix up head revision if needed
368 380 cmd = ['git', 'show', 'HEAD']
@@ -386,14 +398,24 b' def git_post_receive(unused_repo_path, r'
386 398 # delete branch case
387 399 git_revs.append('delete_branch=>%s' % push_ref['name'])
388 400 else:
401 if push_ref['name'] not in branches:
402 branches.append(push_ref['name'])
403
389 404 cmd = ['git', 'log',
390 405 '{old_rev}..{new_rev}'.format(**push_ref),
391 406 '--reverse', '--pretty=format:%H']
392 407 git_revs.extend(_run_command(cmd).splitlines())
393 408 elif type_ == 'tags':
409 if push_ref['name'] not in tags:
410 tags.append(push_ref['name'])
394 411 git_revs.append('tag=>%s' % push_ref['name'])
395 412
396 413 extras['commit_ids'] = git_revs
414 extras['new_refs'] = {
415 'branches': branches,
416 'bookmarks': [],
417 'tags': tags,
418 }
397 419
398 420 if 'repo_size' in extras['hooks']:
399 421 try:
@@ -95,7 +95,13 b' class GitRepository(object):'
95 95
96 96 :param path:
97 97 """
98 return path.split(self.repo_name, 1)[-1].strip('/')
98 path = path.split(self.repo_name, 1)[-1]
99 if path.startswith('.git'):
100 # for bare repos we still get the .git prefix inside, we skip it
101 # here, and remove from the service command
102 path = path[4:]
103
104 return path.strip('/')
99 105
100 106 def inforefs(self, request, unused_environ):
101 107 """
@@ -15,16 +15,5 b''
15 15 # along with this program; if not, write to the Free Software Foundation,
16 16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 17
18
19 PYRO_PORT = 9900
20
21 PYRO_GIT = 'git_remote'
22 PYRO_HG = 'hg_remote'
23 PYRO_SVN = 'svn_remote'
24 PYRO_VCSSERVER = 'vcs_server'
25 PYRO_GIT_REMOTE_WSGI = 'git_remote_wsgi'
26 PYRO_HG_REMOTE_WSGI = 'hg_remote_wsgi'
27
28 18 WIRE_ENCODING = 'UTF-8'
29
30 19 GIT_EXECUTABLE = 'git'
@@ -23,6 +23,7 b' import posixpath as vcspath'
23 23 import StringIO
24 24 import subprocess
25 25 import urllib
26 import traceback
26 27
27 28 import svn.client
28 29 import svn.core
@@ -46,8 +47,17 b' svn_compatible_versions = set(['
46 47 'pre-1.5-compatible',
47 48 'pre-1.6-compatible',
48 49 'pre-1.8-compatible',
50 'pre-1.9-compatible',
49 51 ])
50 52
53 svn_compatible_versions_map = {
54 'pre-1.4-compatible': '1.3',
55 'pre-1.5-compatible': '1.4',
56 'pre-1.6-compatible': '1.5',
57 'pre-1.8-compatible': '1.7',
58 'pre-1.9-compatible': '1.8',
59 }
60
51 61
52 62 def reraise_safe_exceptions(func):
53 63 """Decorator for converting svn exceptions to something neutral."""
@@ -67,14 +77,15 b' class SubversionFactory(RepoFactory):'
67 77 def _create_repo(self, wire, create, compatible_version):
68 78 path = svn.core.svn_path_canonicalize(wire['path'])
69 79 if create:
70 fs_config = {}
80 fs_config = {'compatible-version': '1.9'}
71 81 if compatible_version:
72 82 if compatible_version not in svn_compatible_versions:
73 83 raise Exception('Unknown SVN compatible version "{}"'
74 84 .format(compatible_version))
75 log.debug('Create SVN repo with compatible version "%s"',
76 compatible_version)
77 fs_config[compatible_version] = '1'
85 fs_config['compatible-version'] = \
86 svn_compatible_versions_map[compatible_version]
87
88 log.debug('Create SVN repo with config "%s"', fs_config)
78 89 repo = svn.repos.create(path, "", "", None, fs_config)
79 90 else:
80 91 repo = svn.repos.open(path)
@@ -87,7 +98,6 b' class SubversionFactory(RepoFactory):'
87 98 return self._repo(wire, create_new_repo)
88 99
89 100
90
91 101 NODE_TYPE_MAPPING = {
92 102 svn.core.svn_node_file: 'file',
93 103 svn.core.svn_node_dir: 'dir',
@@ -120,8 +130,9 b' class SvnRemote(object):'
120 130 # throws exception
121 131 try:
122 132 svnrepo.svnremoterepo(baseui, url).svn.uuid
123 except:
124 log.debug("Invalid svn url: %s", url)
133 except Exception:
134 tb = traceback.format_exc()
135 log.debug("Invalid Subversion url: `%s`, tb: %s", url, tb)
125 136 raise URLError(
126 137 '"%s" is not a valid Subversion source url.' % (url, ))
127 138 return True
@@ -130,10 +141,23 b' class SvnRemote(object):'
130 141 try:
131 142 svn.repos.open(path)
132 143 except svn.core.SubversionException:
133 log.debug("Invalid Subversion path %s", path)
144 tb = traceback.format_exc()
145 log.debug("Invalid Subversion path `%s`, tb: %s", path, tb)
134 146 return False
135 147 return True
136 148
149 @reraise_safe_exceptions
150 def verify(self, wire,):
151 repo_path = wire['path']
152 if not self.is_path_valid_repository(wire, repo_path):
153 raise Exception(
154 "Path %s is not a valid Subversion repository." % repo_path)
155
156 load = subprocess.Popen(
157 ['svnadmin', 'info', repo_path],
158 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
159 return ''.join(load.stdout)
160
137 161 def lookup(self, wire, revision):
138 162 if revision not in [-1, None, 'HEAD']:
139 163 raise NotImplementedError
@@ -29,46 +29,6 b' import simplejson as json'
29 29 from vcsserver import hooks
30 30
31 31
32 class HooksStub(object):
33 """
34 Simulates a Proy4.Proxy object.
35
36 Will always return `result`, no matter which hook has been called on it.
37 """
38
39 def __init__(self, result):
40 self._result = result
41
42 def __call__(self, hooks_uri):
43 return self
44
45 def __enter__(self):
46 return self
47
48 def __exit__(self, exc_type, exc_value, traceback):
49 pass
50
51 def __getattr__(self, name):
52 return mock.Mock(return_value=self._result)
53
54
55 @contextlib.contextmanager
56 def mock_hook_response(
57 status=0, output='', exception=None, exception_args=None):
58 response = {
59 'status': status,
60 'output': output,
61 }
62 if exception:
63 response.update({
64 'exception': exception,
65 'exception_args': exception_args,
66 })
67
68 with mock.patch('Pyro4.Proxy', HooksStub(response)):
69 yield
70
71
72 32 def get_hg_ui(extras=None):
73 33 """Create a Config object with a valid RC_SCM_DATA entry."""
74 34 extras = extras or {}
@@ -89,126 +49,6 b' def get_hg_ui(extras=None):'
89 49 return hg_ui
90 50
91 51
92 def test_call_hook_no_error(capsys):
93 extras = {
94 'hooks_uri': 'fake_hook_uri',
95 }
96 expected_output = 'My mock outptut'
97 writer = mock.Mock()
98
99 with mock_hook_response(status=1, output=expected_output):
100 hooks._call_hook('hook_name', extras, writer)
101
102 out, err = capsys.readouterr()
103
104 writer.write.assert_called_with(expected_output)
105 assert err == ''
106
107
108 def test_call_hook_with_exception(capsys):
109 extras = {
110 'hooks_uri': 'fake_hook_uri',
111 }
112 expected_output = 'My mock outptut'
113 writer = mock.Mock()
114
115 with mock_hook_response(status=1, output=expected_output,
116 exception='TypeError',
117 exception_args=('Mock exception', )):
118 with pytest.raises(Exception) as excinfo:
119 hooks._call_hook('hook_name', extras, writer)
120
121 assert excinfo.type == Exception
122 assert 'Mock exception' in str(excinfo.value)
123
124 out, err = capsys.readouterr()
125
126 writer.write.assert_called_with(expected_output)
127 assert err == ''
128
129
130 def test_call_hook_with_locked_exception(capsys):
131 extras = {
132 'hooks_uri': 'fake_hook_uri',
133 }
134 expected_output = 'My mock outptut'
135 writer = mock.Mock()
136
137 with mock_hook_response(status=1, output=expected_output,
138 exception='HTTPLockedRC',
139 exception_args=('message',)):
140 with pytest.raises(Exception) as excinfo:
141 hooks._call_hook('hook_name', extras, writer)
142
143 assert excinfo.value._vcs_kind == 'repo_locked'
144 assert 'message' == str(excinfo.value)
145
146 out, err = capsys.readouterr()
147
148 writer.write.assert_called_with(expected_output)
149 assert err == ''
150
151
152 def test_call_hook_with_stdout():
153 extras = {
154 'hooks_uri': 'fake_hook_uri',
155 }
156 expected_output = 'My mock outptut'
157
158 stdout = io.BytesIO()
159 with mock_hook_response(status=1, output=expected_output):
160 hooks._call_hook('hook_name', extras, stdout)
161
162 assert stdout.getvalue() == expected_output
163
164
165 def test_repo_size():
166 hg_ui = get_hg_ui()
167
168 with mock_hook_response(status=1):
169 assert hooks.repo_size(hg_ui, None) == 1
170
171
172 def test_pre_pull():
173 hg_ui = get_hg_ui()
174
175 with mock_hook_response(status=1):
176 assert hooks.pre_pull(hg_ui, None) == 1
177
178
179 def test_post_pull():
180 hg_ui = get_hg_ui()
181
182 with mock_hook_response(status=1):
183 assert hooks.post_pull(hg_ui, None) == 1
184
185
186 def test_pre_push():
187 hg_ui = get_hg_ui()
188
189 with mock_hook_response(status=1):
190 assert hooks.pre_push(hg_ui, None) == 1
191
192
193 def test_post_push():
194 hg_ui = get_hg_ui()
195
196 with mock_hook_response(status=1):
197 with mock.patch('vcsserver.hooks._rev_range_hash', return_value=[]):
198 assert hooks.post_push(hg_ui, None, None) == 1
199
200
201 def test_git_pre_receive():
202 extras = {
203 'hooks': ['push'],
204 'hooks_uri': 'fake_hook_uri',
205 }
206 with mock_hook_response(status=1):
207 response = hooks.git_pre_receive(None, None,
208 {'RC_SCM_DATA': json.dumps(extras)})
209 assert response == 1
210
211
212 52 def test_git_pre_receive_is_disabled():
213 53 extras = {'hooks': ['pull']}
214 54 response = hooks.git_pre_receive(None, None,
@@ -217,18 +57,6 b' def test_git_pre_receive_is_disabled():'
217 57 assert response == 0
218 58
219 59
220 def test_git_post_receive_no_subprocess_call():
221 extras = {
222 'hooks': ['push'],
223 'hooks_uri': 'fake_hook_uri',
224 }
225 # Setting revision_lines to '' avoid all subprocess_calls
226 with mock_hook_response(status=1):
227 response = hooks.git_post_receive(None, '',
228 {'RC_SCM_DATA': json.dumps(extras)})
229 assert response == 1
230
231
232 60 def test_git_post_receive_is_disabled():
233 61 extras = {'hooks': ['pull']}
234 62 response = hooks.git_post_receive(None, '',
@@ -242,7 +70,8 b' def test_git_post_receive_calls_repo_siz'
242 70 with mock.patch.object(hooks, '_call_hook') as call_hook_mock:
243 71 hooks.git_post_receive(
244 72 None, '', {'RC_SCM_DATA': json.dumps(extras)})
245 extras.update({'commit_ids': []})
73 extras.update({'commit_ids': [],
74 'new_refs': {'bookmarks': [], 'branches': [], 'tags': []}})
246 75 expected_calls = [
247 76 mock.call('repo_size', extras, mock.ANY),
248 77 mock.call('post_push', extras, mock.ANY),
@@ -255,7 +84,8 b' def test_git_post_receive_does_not_call_'
255 84 with mock.patch.object(hooks, '_call_hook') as call_hook_mock:
256 85 hooks.git_post_receive(
257 86 None, '', {'RC_SCM_DATA': json.dumps(extras)})
258 extras.update({'commit_ids': []})
87 extras.update({'commit_ids': [],
88 'new_refs': {'bookmarks': [], 'branches': [], 'tags': []}})
259 89 expected_calls = [
260 90 mock.call('post_push', extras, mock.ANY)
261 91 ]
@@ -279,122 +109,16 b' def test_repo_size_exception_does_not_af'
279 109 assert result == status
280 110
281 111
282 @mock.patch('vcsserver.hooks._run_command')
283 def test_git_post_receive_first_commit_sub_branch(cmd_mock):
284 def cmd_mock_returns(args):
285 if args == ['git', 'show', 'HEAD']:
286 raise
287 if args == ['git', 'for-each-ref', '--format=%(refname)',
288 'refs/heads/*']:
289 return 'refs/heads/test-branch2/sub-branch'
290 if args == ['git', 'log', '--reverse', '--pretty=format:%H', '--',
291 '9695eef57205c17566a3ae543be187759b310bb7', '--not',
292 'refs/heads/test-branch2/sub-branch']:
293 return ''
294
295 cmd_mock.side_effect = cmd_mock_returns
296
297 extras = {
298 'hooks': ['push'],
299 'hooks_uri': 'fake_hook_uri'
300 }
301 rev_lines = ['0000000000000000000000000000000000000000 '
302 '9695eef57205c17566a3ae543be187759b310bb7 '
303 'refs/heads/feature/sub-branch\n']
304 with mock_hook_response(status=0):
305 response = hooks.git_post_receive(None, rev_lines,
306 {'RC_SCM_DATA': json.dumps(extras)})
307
308 calls = [
309 mock.call(['git', 'show', 'HEAD']),
310 mock.call(['git', 'symbolic-ref', 'HEAD',
311 'refs/heads/feature/sub-branch']),
312 ]
313 cmd_mock.assert_has_calls(calls, any_order=True)
314 assert response == 0
315
316
317 @mock.patch('vcsserver.hooks._run_command')
318 def test_git_post_receive_first_commit_revs(cmd_mock):
319 extras = {
320 'hooks': ['push'],
321 'hooks_uri': 'fake_hook_uri'
322 }
323 rev_lines = [
324 '0000000000000000000000000000000000000000 '
325 '9695eef57205c17566a3ae543be187759b310bb7 refs/heads/master\n']
326 with mock_hook_response(status=0):
327 response = hooks.git_post_receive(
328 None, rev_lines, {'RC_SCM_DATA': json.dumps(extras)})
329
330 calls = [
331 mock.call(['git', 'show', 'HEAD']),
332 mock.call(['git', 'for-each-ref', '--format=%(refname)',
333 'refs/heads/*']),
334 mock.call(['git', 'log', '--reverse', '--pretty=format:%H',
335 '--', '9695eef57205c17566a3ae543be187759b310bb7', '--not',
336 ''])
337 ]
338 cmd_mock.assert_has_calls(calls, any_order=True)
339
340 assert response == 0
341
342
343 def test_git_pre_pull():
344 extras = {
345 'hooks': ['pull'],
346 'hooks_uri': 'fake_hook_uri',
347 }
348 with mock_hook_response(status=1, output='foo'):
349 assert hooks.git_pre_pull(extras) == hooks.HookResponse(1, 'foo')
350
351
352 def test_git_pre_pull_exception_is_caught():
353 extras = {
354 'hooks': ['pull'],
355 'hooks_uri': 'fake_hook_uri',
356 }
357 with mock_hook_response(status=2, exception=Exception('foo')):
358 assert hooks.git_pre_pull(extras).status == 128
359
360
361 112 def test_git_pre_pull_is_disabled():
362 113 assert hooks.git_pre_pull({'hooks': ['push']}) == hooks.HookResponse(0, '')
363 114
364 115
365 def test_git_post_pull():
366 extras = {
367 'hooks': ['pull'],
368 'hooks_uri': 'fake_hook_uri',
369 }
370 with mock_hook_response(status=1, output='foo'):
371 assert hooks.git_post_pull(extras) == hooks.HookResponse(1, 'foo')
372
373
374 def test_git_post_pull_exception_is_caught():
375 extras = {
376 'hooks': ['pull'],
377 'hooks_uri': 'fake_hook_uri',
378 }
379 with mock_hook_response(status=2, exception='Exception',
380 exception_args=('foo',)):
381 assert hooks.git_post_pull(extras).status == 128
382
383
384 116 def test_git_post_pull_is_disabled():
385 117 assert (
386 118 hooks.git_post_pull({'hooks': ['push']}) == hooks.HookResponse(0, ''))
387 119
388 120
389 121 class TestGetHooksClient(object):
390 def test_returns_pyro_client_when_protocol_matches(self):
391 hooks_uri = 'localhost:8000'
392 result = hooks._get_hooks_client({
393 'hooks_uri': hooks_uri,
394 'hooks_protocol': 'pyro4'
395 })
396 assert isinstance(result, hooks.HooksPyro4Client)
397 assert result.hooks_uri == hooks_uri
398 122
399 123 def test_returns_http_client_when_protocol_matches(self):
400 124 hooks_uri = 'localhost:8000'
@@ -405,14 +129,6 b' class TestGetHooksClient(object):'
405 129 assert isinstance(result, hooks.HooksHttpClient)
406 130 assert result.hooks_uri == hooks_uri
407 131
408 def test_returns_pyro4_client_when_no_protocol_is_specified(self):
409 hooks_uri = 'localhost:8000'
410 result = hooks._get_hooks_client({
411 'hooks_uri': hooks_uri
412 })
413 assert isinstance(result, hooks.HooksPyro4Client)
414 assert result.hooks_uri == hooks_uri
415
416 132 def test_returns_dummy_client_when_hooks_uri_not_specified(self):
417 133 fake_module = mock.Mock()
418 134 import_patcher = mock.patch.object(
@@ -487,30 +203,6 b' class TestHooksDummyClient(object):'
487 203 assert result == hooks_module.Hooks().__enter__().post_push()
488 204
489 205
490 class TestHooksPyro4Client(object):
491 def test_init_sets_hooks_uri(self):
492 uri = 'localhost:3000'
493 client = hooks.HooksPyro4Client(uri)
494 assert client.hooks_uri == uri
495
496 def test_call_returns_hook_value(self):
497 hooks_uri = 'localhost:3000'
498 client = hooks.HooksPyro4Client(hooks_uri)
499 hooks_module = mock.Mock()
500 context_manager = mock.MagicMock()
501 context_manager.__enter__.return_value = hooks_module
502 pyro4_patcher = mock.patch.object(
503 hooks.Pyro4, 'Proxy', return_value=context_manager)
504 extras = {
505 'test': 'test'
506 }
507 with pyro4_patcher as pyro4_mock:
508 result = client('post_push', extras)
509 pyro4_mock.assert_called_once_with(hooks_uri)
510 hooks_module.post_push.assert_called_once_with(extras)
511 assert result == hooks_module.post_push.return_value
512
513
514 206 @pytest.fixture
515 207 def http_mirror(request):
516 208 server = MirrorHttpServer()
@@ -18,24 +18,24 b''
18 18 import mock
19 19 import pytest
20 20
21 from vcsserver import main
21 from vcsserver import http_main
22 22 from vcsserver.base import obfuscate_qs
23 23
24 24
25 @mock.patch('vcsserver.main.VcsServerCommand', mock.Mock())
25 @mock.patch('vcsserver.http_main.VCS', mock.Mock())
26 26 @mock.patch('vcsserver.hgpatches.patch_largefiles_capabilities')
27 27 def test_applies_largefiles_patch(patch_largefiles_capabilities):
28 main.main([])
28 http_main.main([])
29 29 patch_largefiles_capabilities.assert_called_once_with()
30 30
31 31
32 @mock.patch('vcsserver.main.VcsServerCommand', mock.Mock())
33 @mock.patch('vcsserver.main.MercurialFactory', None)
32 @mock.patch('vcsserver.http_main.VCS', mock.Mock())
33 @mock.patch('vcsserver.http_main.MercurialFactory', None)
34 34 @mock.patch(
35 35 'vcsserver.hgpatches.patch_largefiles_capabilities',
36 36 mock.Mock(side_effect=Exception("Must not be called")))
37 37 def test_applies_largefiles_patch_only_if_mercurial_is_available():
38 main.main([])
38 http_main.main([])
39 39
40 40
41 41 @pytest.mark.parametrize('given, expected', [
@@ -46,7 +46,7 b' class RequestWrapperTween(object):'
46 46 finally:
47 47 end = time.time()
48 48
49 log.info('IP: %s Request to %s time: %.3fs' % (
49 log.info('IP: %s Request to path: `%s` time: %.3fs' % (
50 50 '127.0.0.1',
51 51 safe_str(get_access_path(request)), end - start)
52 52 )
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
This diff has been collapsed as it changes many lines, (508 lines changed) Show them Hide them
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now