Show More
@@ -0,0 +1,154 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | ||||
|
3 | # RhodeCode VCSServer provides access to different vcs backends via network. | |||
|
4 | # Copyright (C) 2014-2018 RhodeCode GmbH | |||
|
5 | # | |||
|
6 | # This program is free software; you can redistribute it and/or modify | |||
|
7 | # it under the terms of the GNU General Public License as published by | |||
|
8 | # the Free Software Foundation; either version 3 of the License, or | |||
|
9 | # (at your option) any later version. | |||
|
10 | # | |||
|
11 | # This program is distributed in the hope that it will be useful, | |||
|
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
14 | # GNU General Public License for more details. | |||
|
15 | # | |||
|
16 | # You should have received a copy of the GNU General Public License | |||
|
17 | # along with this program; if not, write to the Free Software Foundation, | |||
|
18 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |||
|
19 | ||||
|
20 | import re | |||
|
21 | import os | |||
|
22 | import sys | |||
|
23 | import datetime | |||
|
24 | import logging | |||
|
25 | import pkg_resources | |||
|
26 | ||||
|
27 | import vcsserver | |||
|
28 | ||||
|
29 | log = logging.getLogger(__name__) | |||
|
30 | ||||
|
31 | ||||
|
32 | def install_git_hooks(repo_path, bare, executable=None, force_create=False): | |||
|
33 | """ | |||
|
34 | Creates a RhodeCode hook inside a git repository | |||
|
35 | ||||
|
36 | :param repo_path: path to repository | |||
|
37 | :param executable: binary executable to put in the hooks | |||
|
38 | :param force_create: Create even if same name hook exists | |||
|
39 | """ | |||
|
40 | executable = executable or sys.executable | |||
|
41 | hooks_path = os.path.join(repo_path, 'hooks') | |||
|
42 | if not bare: | |||
|
43 | hooks_path = os.path.join(repo_path, '.git', 'hooks') | |||
|
44 | if not os.path.isdir(hooks_path): | |||
|
45 | os.makedirs(hooks_path, mode=0777) | |||
|
46 | ||||
|
47 | tmpl_post = pkg_resources.resource_string( | |||
|
48 | 'vcsserver', '/'.join( | |||
|
49 | ('hook_utils', 'hook_templates', 'git_post_receive.py.tmpl'))) | |||
|
50 | tmpl_pre = pkg_resources.resource_string( | |||
|
51 | 'vcsserver', '/'.join( | |||
|
52 | ('hook_utils', 'hook_templates', 'git_pre_receive.py.tmpl'))) | |||
|
53 | ||||
|
54 | path = '' # not used for now | |||
|
55 | timestamp = datetime.datetime.utcnow().isoformat() | |||
|
56 | ||||
|
57 | for h_type, template in [('pre', tmpl_pre), ('post', tmpl_post)]: | |||
|
58 | log.debug('Installing git hook in repo %s', repo_path) | |||
|
59 | _hook_file = os.path.join(hooks_path, '%s-receive' % h_type) | |||
|
60 | _rhodecode_hook = check_rhodecode_hook(_hook_file) | |||
|
61 | ||||
|
62 | if _rhodecode_hook or force_create: | |||
|
63 | log.debug('writing git %s hook file at %s !', h_type, _hook_file) | |||
|
64 | try: | |||
|
65 | with open(_hook_file, 'wb') as f: | |||
|
66 | template = template.replace( | |||
|
67 | '_TMPL_', vcsserver.__version__) | |||
|
68 | template = template.replace('_DATE_', timestamp) | |||
|
69 | template = template.replace('_ENV_', executable) | |||
|
70 | template = template.replace('_PATH_', path) | |||
|
71 | f.write(template) | |||
|
72 | os.chmod(_hook_file, 0755) | |||
|
73 | except IOError: | |||
|
74 | log.exception('error writing hook file %s', _hook_file) | |||
|
75 | else: | |||
|
76 | log.debug('skipping writing hook file') | |||
|
77 | ||||
|
78 | return True | |||
|
79 | ||||
|
80 | ||||
|
81 | def install_svn_hooks(repo_path, executable=None, force_create=False): | |||
|
82 | """ | |||
|
83 | Creates RhodeCode hooks inside a svn repository | |||
|
84 | ||||
|
85 | :param repo_path: path to repository | |||
|
86 | :param executable: binary executable to put in the hooks | |||
|
87 | :param force_create: Create even if same name hook exists | |||
|
88 | """ | |||
|
89 | executable = executable or sys.executable | |||
|
90 | hooks_path = os.path.join(repo_path, 'hooks') | |||
|
91 | if not os.path.isdir(hooks_path): | |||
|
92 | os.makedirs(hooks_path, mode=0777) | |||
|
93 | ||||
|
94 | tmpl_post = pkg_resources.resource_string( | |||
|
95 | 'vcsserver', '/'.join( | |||
|
96 | ('hook_utils', 'hook_templates', 'svn_post_commit_hook.py.tmpl'))) | |||
|
97 | tmpl_pre = pkg_resources.resource_string( | |||
|
98 | 'vcsserver', '/'.join( | |||
|
99 | ('hook_utils', 'hook_templates', 'svn_pre_commit_hook.py.tmpl'))) | |||
|
100 | ||||
|
101 | path = '' # not used for now | |||
|
102 | timestamp = datetime.datetime.utcnow().isoformat() | |||
|
103 | ||||
|
104 | for h_type, template in [('pre', tmpl_pre), ('post', tmpl_post)]: | |||
|
105 | log.debug('Installing svn hook in repo %s', repo_path) | |||
|
106 | _hook_file = os.path.join(hooks_path, '%s-commit' % h_type) | |||
|
107 | _rhodecode_hook = check_rhodecode_hook(_hook_file) | |||
|
108 | ||||
|
109 | if _rhodecode_hook or force_create: | |||
|
110 | log.debug('writing svn %s hook file at %s !', h_type, _hook_file) | |||
|
111 | ||||
|
112 | try: | |||
|
113 | with open(_hook_file, 'wb') as f: | |||
|
114 | template = template.replace( | |||
|
115 | '_TMPL_', vcsserver.__version__) | |||
|
116 | template = template.replace('_DATE_', timestamp) | |||
|
117 | template = template.replace('_ENV_', executable) | |||
|
118 | template = template.replace('_PATH_', path) | |||
|
119 | ||||
|
120 | f.write(template) | |||
|
121 | os.chmod(_hook_file, 0755) | |||
|
122 | except IOError: | |||
|
123 | log.exception('error writing hook file %s', _hook_file) | |||
|
124 | else: | |||
|
125 | log.debug('skipping writing hook file') | |||
|
126 | ||||
|
127 | return True | |||
|
128 | ||||
|
129 | ||||
|
130 | def check_rhodecode_hook(hook_path): | |||
|
131 | """ | |||
|
132 | Check if the hook was created by RhodeCode | |||
|
133 | """ | |||
|
134 | if not os.path.exists(hook_path): | |||
|
135 | return True | |||
|
136 | ||||
|
137 | log.debug('hook exists, checking if it is from rhodecode') | |||
|
138 | hook_content = read_hook_content(hook_path) | |||
|
139 | matches = re.search(r'(?:RC_HOOK_VER)\s*=\s*(.*)', hook_content) | |||
|
140 | if matches: | |||
|
141 | try: | |||
|
142 | version = matches.groups()[0] | |||
|
143 | log.debug('got version %s from hooks.', version) | |||
|
144 | return True | |||
|
145 | except Exception: | |||
|
146 | log.exception("Exception while reading the hook version.") | |||
|
147 | ||||
|
148 | return False | |||
|
149 | ||||
|
150 | ||||
|
151 | def read_hook_content(hook_path): | |||
|
152 | with open(hook_path, 'rb') as f: | |||
|
153 | content = f.read() | |||
|
154 | return content |
@@ -0,0 +1,51 b'' | |||||
|
1 | #!_ENV_ | |||
|
2 | import os | |||
|
3 | import sys | |||
|
4 | path_adjust = [_PATH_] | |||
|
5 | ||||
|
6 | if path_adjust: | |||
|
7 | sys.path = path_adjust | |||
|
8 | ||||
|
9 | try: | |||
|
10 | from vcsserver import hooks | |||
|
11 | except ImportError: | |||
|
12 | if os.environ.get('RC_DEBUG_GIT_HOOK'): | |||
|
13 | import traceback | |||
|
14 | print traceback.format_exc() | |||
|
15 | hooks = None | |||
|
16 | ||||
|
17 | ||||
|
18 | # TIMESTAMP: _DATE_ | |||
|
19 | RC_HOOK_VER = '_TMPL_' | |||
|
20 | ||||
|
21 | ||||
|
22 | def main(): | |||
|
23 | if hooks is None: | |||
|
24 | # exit with success if we cannot import vcsserver.hooks !! | |||
|
25 | # this allows simply push to this repo even without rhodecode | |||
|
26 | sys.exit(0) | |||
|
27 | ||||
|
28 | if os.environ.get('RC_SKIP_HOOKS'): | |||
|
29 | sys.exit(0) | |||
|
30 | ||||
|
31 | repo_path = os.getcwd() | |||
|
32 | push_data = sys.stdin.readlines() | |||
|
33 | os.environ['RC_HOOK_VER'] = RC_HOOK_VER | |||
|
34 | # os.environ is modified here by a subprocess call that | |||
|
35 | # runs git and later git executes this hook. | |||
|
36 | # Environ gets some additional info from rhodecode system | |||
|
37 | # like IP or username from basic-auth | |||
|
38 | try: | |||
|
39 | result = hooks.git_post_receive(repo_path, push_data, os.environ) | |||
|
40 | sys.exit(result) | |||
|
41 | except Exception as error: | |||
|
42 | # TODO: johbo: Improve handling of this special case | |||
|
43 | if not getattr(error, '_vcs_kind', None) == 'repo_locked': | |||
|
44 | raise | |||
|
45 | print 'ERROR:', error | |||
|
46 | sys.exit(1) | |||
|
47 | sys.exit(0) | |||
|
48 | ||||
|
49 | ||||
|
50 | if __name__ == '__main__': | |||
|
51 | main() |
@@ -0,0 +1,51 b'' | |||||
|
1 | #!_ENV_ | |||
|
2 | import os | |||
|
3 | import sys | |||
|
4 | path_adjust = [_PATH_] | |||
|
5 | ||||
|
6 | if path_adjust: | |||
|
7 | sys.path = path_adjust | |||
|
8 | ||||
|
9 | try: | |||
|
10 | from vcsserver import hooks | |||
|
11 | except ImportError: | |||
|
12 | if os.environ.get('RC_DEBUG_GIT_HOOK'): | |||
|
13 | import traceback | |||
|
14 | print traceback.format_exc() | |||
|
15 | hooks = None | |||
|
16 | ||||
|
17 | ||||
|
18 | # TIMESTAMP: _DATE_ | |||
|
19 | RC_HOOK_VER = '_TMPL_' | |||
|
20 | ||||
|
21 | ||||
|
22 | def main(): | |||
|
23 | if hooks is None: | |||
|
24 | # exit with success if we cannot import vcsserver.hooks !! | |||
|
25 | # this allows simply push to this repo even without rhodecode | |||
|
26 | sys.exit(0) | |||
|
27 | ||||
|
28 | if os.environ.get('RC_SKIP_HOOKS'): | |||
|
29 | sys.exit(0) | |||
|
30 | ||||
|
31 | repo_path = os.getcwd() | |||
|
32 | push_data = sys.stdin.readlines() | |||
|
33 | os.environ['RC_HOOK_VER'] = RC_HOOK_VER | |||
|
34 | # os.environ is modified here by a subprocess call that | |||
|
35 | # runs git and later git executes this hook. | |||
|
36 | # Environ gets some additional info from rhodecode system | |||
|
37 | # like IP or username from basic-auth | |||
|
38 | try: | |||
|
39 | result = hooks.git_pre_receive(repo_path, push_data, os.environ) | |||
|
40 | sys.exit(result) | |||
|
41 | except Exception as error: | |||
|
42 | # TODO: johbo: Improve handling of this special case | |||
|
43 | if not getattr(error, '_vcs_kind', None) == 'repo_locked': | |||
|
44 | raise | |||
|
45 | print 'ERROR:', error | |||
|
46 | sys.exit(1) | |||
|
47 | sys.exit(0) | |||
|
48 | ||||
|
49 | ||||
|
50 | if __name__ == '__main__': | |||
|
51 | main() |
@@ -0,0 +1,50 b'' | |||||
|
1 | #!_ENV_ | |||
|
2 | ||||
|
3 | import os | |||
|
4 | import sys | |||
|
5 | path_adjust = [_PATH_] | |||
|
6 | ||||
|
7 | if path_adjust: | |||
|
8 | sys.path = path_adjust | |||
|
9 | ||||
|
10 | try: | |||
|
11 | from vcsserver import hooks | |||
|
12 | except ImportError: | |||
|
13 | if os.environ.get('RC_DEBUG_SVN_HOOK'): | |||
|
14 | import traceback | |||
|
15 | print traceback.format_exc() | |||
|
16 | hooks = None | |||
|
17 | ||||
|
18 | ||||
|
19 | # TIMESTAMP: _DATE_ | |||
|
20 | RC_HOOK_VER = '_TMPL_' | |||
|
21 | ||||
|
22 | ||||
|
23 | def main(): | |||
|
24 | if hooks is None: | |||
|
25 | # exit with success if we cannot import vcsserver.hooks !! | |||
|
26 | # this allows simply push to this repo even without rhodecode | |||
|
27 | sys.exit(0) | |||
|
28 | ||||
|
29 | if os.environ.get('RC_SKIP_HOOKS'): | |||
|
30 | sys.exit(0) | |||
|
31 | repo_path = os.getcwd() | |||
|
32 | push_data = sys.argv[1:] | |||
|
33 | ||||
|
34 | os.environ['RC_HOOK_VER'] = RC_HOOK_VER | |||
|
35 | ||||
|
36 | try: | |||
|
37 | result = hooks.svn_post_commit(repo_path, push_data, os.environ) | |||
|
38 | sys.exit(result) | |||
|
39 | except Exception as error: | |||
|
40 | # TODO: johbo: Improve handling of this special case | |||
|
41 | if not getattr(error, '_vcs_kind', None) == 'repo_locked': | |||
|
42 | raise | |||
|
43 | print 'ERROR:', error | |||
|
44 | sys.exit(1) | |||
|
45 | sys.exit(0) | |||
|
46 | ||||
|
47 | ||||
|
48 | ||||
|
49 | if __name__ == '__main__': | |||
|
50 | main() |
@@ -0,0 +1,52 b'' | |||||
|
1 | #!_ENV_ | |||
|
2 | ||||
|
3 | import os | |||
|
4 | import sys | |||
|
5 | path_adjust = [_PATH_] | |||
|
6 | ||||
|
7 | if path_adjust: | |||
|
8 | sys.path = path_adjust | |||
|
9 | ||||
|
10 | try: | |||
|
11 | from vcsserver import hooks | |||
|
12 | except ImportError: | |||
|
13 | if os.environ.get('RC_DEBUG_SVN_HOOK'): | |||
|
14 | import traceback | |||
|
15 | print traceback.format_exc() | |||
|
16 | hooks = None | |||
|
17 | ||||
|
18 | ||||
|
19 | # TIMESTAMP: _DATE_ | |||
|
20 | RC_HOOK_VER = '_TMPL_' | |||
|
21 | ||||
|
22 | ||||
|
23 | def main(): | |||
|
24 | if os.environ.get('SSH_READ_ONLY') == '1': | |||
|
25 | sys.stderr.write('Only read-only access is allowed') | |||
|
26 | sys.exit(1) | |||
|
27 | ||||
|
28 | if hooks is None: | |||
|
29 | # exit with success if we cannot import vcsserver.hooks !! | |||
|
30 | # this allows simply push to this repo even without rhodecode | |||
|
31 | sys.exit(0) | |||
|
32 | if os.environ.get('RC_SKIP_HOOKS'): | |||
|
33 | sys.exit(0) | |||
|
34 | repo_path = os.getcwd() | |||
|
35 | push_data = sys.argv[1:] | |||
|
36 | ||||
|
37 | os.environ['RC_HOOK_VER'] = RC_HOOK_VER | |||
|
38 | ||||
|
39 | try: | |||
|
40 | result = hooks.svn_pre_commit(repo_path, push_data, os.environ) | |||
|
41 | sys.exit(result) | |||
|
42 | except Exception as error: | |||
|
43 | # TODO: johbo: Improve handling of this special case | |||
|
44 | if not getattr(error, '_vcs_kind', None) == 'repo_locked': | |||
|
45 | raise | |||
|
46 | print 'ERROR:', error | |||
|
47 | sys.exit(1) | |||
|
48 | sys.exit(0) | |||
|
49 | ||||
|
50 | ||||
|
51 | if __name__ == '__main__': | |||
|
52 | main() |
@@ -0,0 +1,16 b'' | |||||
|
1 | # RhodeCode VCSServer provides access to different vcs backends via network. | |||
|
2 | # Copyright (C) 2014-2018 RhodeCode GmbH | |||
|
3 | # | |||
|
4 | # This program is free software; you can redistribute it and/or modify | |||
|
5 | # it under the terms of the GNU General Public License as published by | |||
|
6 | # the Free Software Foundation; either version 3 of the License, or | |||
|
7 | # (at your option) any later version. | |||
|
8 | # | |||
|
9 | # This program is distributed in the hope that it will be useful, | |||
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
12 | # GNU General Public License for more details. | |||
|
13 | # | |||
|
14 | # You should have received a copy of the GNU General Public License | |||
|
15 | # along with this program; if not, write to the Free Software Foundation, | |||
|
16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
@@ -0,0 +1,206 b'' | |||||
|
1 | # RhodeCode VCSServer provides access to different vcs backends via network. | |||
|
2 | # Copyright (C) 2014-2018 RhodeCode GmbH | |||
|
3 | # | |||
|
4 | # This program is free software; you can redistribute it and/or modify | |||
|
5 | # it under the terms of the GNU General Public License as published by | |||
|
6 | # the Free Software Foundation; either version 3 of the License, or | |||
|
7 | # (at your option) any later version. | |||
|
8 | # | |||
|
9 | # This program is distributed in the hope that it will be useful, | |||
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
12 | # GNU General Public License for more details. | |||
|
13 | # | |||
|
14 | # You should have received a copy of the GNU General Public License | |||
|
15 | # along with this program; if not, write to the Free Software Foundation, | |||
|
16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |||
|
17 | ||||
|
18 | import os | |||
|
19 | import sys | |||
|
20 | import stat | |||
|
21 | import pytest | |||
|
22 | import vcsserver | |||
|
23 | import tempfile | |||
|
24 | from vcsserver import hook_utils | |||
|
25 | from vcsserver.tests.fixture import no_newline_id_generator | |||
|
26 | from vcsserver.utils import AttributeDict | |||
|
27 | ||||
|
28 | ||||
|
29 | class TestCheckRhodecodeHook(object): | |||
|
30 | ||||
|
31 | def test_returns_false_when_hook_file_is_wrong_found(self, tmpdir): | |||
|
32 | hook = os.path.join(str(tmpdir), 'fake_hook_file.py') | |||
|
33 | with open(hook, 'wb') as f: | |||
|
34 | f.write('dummy test') | |||
|
35 | result = hook_utils.check_rhodecode_hook(hook) | |||
|
36 | assert result is False | |||
|
37 | ||||
|
38 | def test_returns_true_when_no_hook_file_found(self, tmpdir): | |||
|
39 | hook = os.path.join(str(tmpdir), 'fake_hook_file_not_existing.py') | |||
|
40 | result = hook_utils.check_rhodecode_hook(hook) | |||
|
41 | assert result | |||
|
42 | ||||
|
43 | @pytest.mark.parametrize("file_content, expected_result", [ | |||
|
44 | ("RC_HOOK_VER = '3.3.3'\n", True), | |||
|
45 | ("RC_HOOK = '3.3.3'\n", False), | |||
|
46 | ], ids=no_newline_id_generator) | |||
|
47 | def test_signatures(self, file_content, expected_result, tmpdir): | |||
|
48 | hook = os.path.join(str(tmpdir), 'fake_hook_file_1.py') | |||
|
49 | with open(hook, 'wb') as f: | |||
|
50 | f.write(file_content) | |||
|
51 | ||||
|
52 | result = hook_utils.check_rhodecode_hook(hook) | |||
|
53 | ||||
|
54 | assert result is expected_result | |||
|
55 | ||||
|
56 | ||||
|
57 | class BaseInstallHooks(object): | |||
|
58 | HOOK_FILES = () | |||
|
59 | ||||
|
60 | def _check_hook_file_mode(self, file_path): | |||
|
61 | assert os.path.exists(file_path), 'path %s missing' % file_path | |||
|
62 | stat_info = os.stat(file_path) | |||
|
63 | ||||
|
64 | file_mode = stat.S_IMODE(stat_info.st_mode) | |||
|
65 | expected_mode = int('755', 8) | |||
|
66 | assert expected_mode == file_mode | |||
|
67 | ||||
|
68 | def _check_hook_file_content(self, file_path, executable): | |||
|
69 | executable = executable or sys.executable | |||
|
70 | with open(file_path, 'rt') as hook_file: | |||
|
71 | content = hook_file.read() | |||
|
72 | ||||
|
73 | expected_env = '#!{}'.format(executable) | |||
|
74 | expected_rc_version = "\nRC_HOOK_VER = '{}'\n".format( | |||
|
75 | vcsserver.__version__) | |||
|
76 | assert content.strip().startswith(expected_env) | |||
|
77 | assert expected_rc_version in content | |||
|
78 | ||||
|
79 | def _create_fake_hook(self, file_path, content): | |||
|
80 | with open(file_path, 'w') as hook_file: | |||
|
81 | hook_file.write(content) | |||
|
82 | ||||
|
83 | def create_dummy_repo(self, repo_type): | |||
|
84 | tmpdir = tempfile.mkdtemp() | |||
|
85 | repo = AttributeDict() | |||
|
86 | if repo_type == 'git': | |||
|
87 | repo.path = os.path.join(tmpdir, 'test_git_hooks_installation_repo') | |||
|
88 | os.makedirs(repo.path) | |||
|
89 | os.makedirs(os.path.join(repo.path, 'hooks')) | |||
|
90 | repo.bare = True | |||
|
91 | ||||
|
92 | elif repo_type == 'svn': | |||
|
93 | repo.path = os.path.join(tmpdir, 'test_svn_hooks_installation_repo') | |||
|
94 | os.makedirs(repo.path) | |||
|
95 | os.makedirs(os.path.join(repo.path, 'hooks')) | |||
|
96 | ||||
|
97 | return repo | |||
|
98 | ||||
|
99 | def check_hooks(self, repo_path, repo_bare=True): | |||
|
100 | for file_name in self.HOOK_FILES: | |||
|
101 | if repo_bare: | |||
|
102 | file_path = os.path.join(repo_path, 'hooks', file_name) | |||
|
103 | else: | |||
|
104 | file_path = os.path.join(repo_path, '.git', 'hooks', file_name) | |||
|
105 | self._check_hook_file_mode(file_path) | |||
|
106 | self._check_hook_file_content(file_path, sys.executable) | |||
|
107 | ||||
|
108 | ||||
|
109 | class TestInstallGitHooks(BaseInstallHooks): | |||
|
110 | HOOK_FILES = ('pre-receive', 'post-receive') | |||
|
111 | ||||
|
112 | def test_hooks_are_installed(self): | |||
|
113 | repo = self.create_dummy_repo('git') | |||
|
114 | result = hook_utils.install_git_hooks(repo.path, repo.bare) | |||
|
115 | assert result | |||
|
116 | self.check_hooks(repo.path, repo.bare) | |||
|
117 | ||||
|
118 | def test_hooks_are_replaced(self): | |||
|
119 | repo = self.create_dummy_repo('git') | |||
|
120 | hooks_path = os.path.join(repo.path, 'hooks') | |||
|
121 | for file_path in [os.path.join(hooks_path, f) for f in self.HOOK_FILES]: | |||
|
122 | self._create_fake_hook( | |||
|
123 | file_path, content="RC_HOOK_VER = 'abcde'\n") | |||
|
124 | ||||
|
125 | result = hook_utils.install_git_hooks(repo.path, repo.bare) | |||
|
126 | assert result | |||
|
127 | self.check_hooks(repo.path, repo.bare) | |||
|
128 | ||||
|
129 | def test_non_rc_hooks_are_not_replaced(self): | |||
|
130 | repo = self.create_dummy_repo('git') | |||
|
131 | hooks_path = os.path.join(repo.path, 'hooks') | |||
|
132 | non_rc_content = 'echo "non rc hook"\n' | |||
|
133 | for file_path in [os.path.join(hooks_path, f) for f in self.HOOK_FILES]: | |||
|
134 | self._create_fake_hook( | |||
|
135 | file_path, content=non_rc_content) | |||
|
136 | ||||
|
137 | result = hook_utils.install_git_hooks(repo.path, repo.bare) | |||
|
138 | assert result | |||
|
139 | ||||
|
140 | for file_path in [os.path.join(hooks_path, f) for f in self.HOOK_FILES]: | |||
|
141 | with open(file_path, 'rt') as hook_file: | |||
|
142 | content = hook_file.read() | |||
|
143 | assert content == non_rc_content | |||
|
144 | ||||
|
145 | def test_non_rc_hooks_are_replaced_with_force_flag(self): | |||
|
146 | repo = self.create_dummy_repo('git') | |||
|
147 | hooks_path = os.path.join(repo.path, 'hooks') | |||
|
148 | non_rc_content = 'echo "non rc hook"\n' | |||
|
149 | for file_path in [os.path.join(hooks_path, f) for f in self.HOOK_FILES]: | |||
|
150 | self._create_fake_hook( | |||
|
151 | file_path, content=non_rc_content) | |||
|
152 | ||||
|
153 | result = hook_utils.install_git_hooks( | |||
|
154 | repo.path, repo.bare, force_create=True) | |||
|
155 | assert result | |||
|
156 | self.check_hooks(repo.path, repo.bare) | |||
|
157 | ||||
|
158 | ||||
|
159 | class TestInstallSvnHooks(BaseInstallHooks): | |||
|
160 | HOOK_FILES = ('pre-commit', 'post-commit') | |||
|
161 | ||||
|
162 | def test_hooks_are_installed(self): | |||
|
163 | repo = self.create_dummy_repo('svn') | |||
|
164 | result = hook_utils.install_svn_hooks(repo.path) | |||
|
165 | assert result | |||
|
166 | self.check_hooks(repo.path) | |||
|
167 | ||||
|
168 | def test_hooks_are_replaced(self): | |||
|
169 | repo = self.create_dummy_repo('svn') | |||
|
170 | hooks_path = os.path.join(repo.path, 'hooks') | |||
|
171 | for file_path in [os.path.join(hooks_path, f) for f in self.HOOK_FILES]: | |||
|
172 | self._create_fake_hook( | |||
|
173 | file_path, content="RC_HOOK_VER = 'abcde'\n") | |||
|
174 | ||||
|
175 | result = hook_utils.install_svn_hooks(repo.path) | |||
|
176 | assert result | |||
|
177 | self.check_hooks(repo.path) | |||
|
178 | ||||
|
179 | def test_non_rc_hooks_are_not_replaced(self): | |||
|
180 | repo = self.create_dummy_repo('svn') | |||
|
181 | hooks_path = os.path.join(repo.path, 'hooks') | |||
|
182 | non_rc_content = 'echo "non rc hook"\n' | |||
|
183 | for file_path in [os.path.join(hooks_path, f) for f in self.HOOK_FILES]: | |||
|
184 | self._create_fake_hook( | |||
|
185 | file_path, content=non_rc_content) | |||
|
186 | ||||
|
187 | result = hook_utils.install_svn_hooks(repo.path) | |||
|
188 | assert result | |||
|
189 | ||||
|
190 | for file_path in [os.path.join(hooks_path, f) for f in self.HOOK_FILES]: | |||
|
191 | with open(file_path, 'rt') as hook_file: | |||
|
192 | content = hook_file.read() | |||
|
193 | assert content == non_rc_content | |||
|
194 | ||||
|
195 | def test_non_rc_hooks_are_replaced_with_force_flag(self): | |||
|
196 | repo = self.create_dummy_repo('svn') | |||
|
197 | hooks_path = os.path.join(repo.path, 'hooks') | |||
|
198 | non_rc_content = 'echo "non rc hook"\n' | |||
|
199 | for file_path in [os.path.join(hooks_path, f) for f in self.HOOK_FILES]: | |||
|
200 | self._create_fake_hook( | |||
|
201 | file_path, content=non_rc_content) | |||
|
202 | ||||
|
203 | result = hook_utils.install_svn_hooks( | |||
|
204 | repo.path, force_create=True) | |||
|
205 | assert result | |||
|
206 | self.check_hooks(repo.path, ) |
@@ -8,6 +8,9 b' include vcsserver/VERSION' | |||||
8 | # all config files |
|
8 | # all config files | |
9 | recursive-include configs * |
|
9 | recursive-include configs * | |
10 |
|
10 | |||
|
11 | # hook templates | |||
|
12 | recursive-include vcsserver/hook_utils/hook_templates * | |||
|
13 | ||||
11 | # skip any tests files |
|
14 | # skip any tests files | |
12 | recursive-exclude vcsserver/tests * |
|
15 | recursive-exclude vcsserver/tests * | |
13 |
|
16 |
@@ -20,6 +20,10 b' beaker.cache.repo_object.max_items = 100' | |||||
20 | beaker.cache.repo_object.expire = 300 |
|
20 | beaker.cache.repo_object.expire = 300 | |
21 | beaker.cache.repo_object.enabled = true |
|
21 | beaker.cache.repo_object.enabled = true | |
22 |
|
22 | |||
|
23 | # path to binaries for vcsserver, it should be set by the installer | |||
|
24 | # at installation time, e.g /home/user/vcsserver-1/profile/bin | |||
|
25 | core.binary_dir = "" | |||
|
26 | ||||
23 | [server:main] |
|
27 | [server:main] | |
24 | ## COMMON ## |
|
28 | ## COMMON ## | |
25 | host = 0.0.0.0 |
|
29 | host = 0.0.0.0 |
@@ -39,7 +39,7 b' use = egg:rhodecode-vcsserver' | |||||
39 | pyramid.default_locale_name = en |
|
39 | pyramid.default_locale_name = en | |
40 | pyramid.includes = |
|
40 | pyramid.includes = | |
41 |
|
41 | |||
42 |
|
|
42 | # default locale used by VCS systems | |
43 | locale = en_US.UTF-8 |
|
43 | locale = en_US.UTF-8 | |
44 |
|
44 | |||
45 | # cache regions, please don't change |
|
45 | # cache regions, please don't change | |
@@ -50,6 +50,10 b' beaker.cache.repo_object.max_items = 100' | |||||
50 | beaker.cache.repo_object.expire = 300 |
|
50 | beaker.cache.repo_object.expire = 300 | |
51 | beaker.cache.repo_object.enabled = true |
|
51 | beaker.cache.repo_object.enabled = true | |
52 |
|
52 | |||
|
53 | # path to binaries for vcsserver, it should be set by the installer | |||
|
54 | # at installation time, e.g /home/user/vcsserver-1/profile/bin | |||
|
55 | core.binary_dir = "" | |||
|
56 | ||||
53 |
|
57 | |||
54 | ################################ |
|
58 | ################################ | |
55 | ### LOGGING CONFIGURATION #### |
|
59 | ### LOGGING CONFIGURATION #### |
@@ -655,6 +655,12 b' class GitRemote(object):' | |||||
655 | else: |
|
655 | else: | |
656 | raise exceptions.VcsException(tb_err) |
|
656 | raise exceptions.VcsException(tb_err) | |
657 |
|
657 | |||
|
658 | @reraise_safe_exceptions | |||
|
659 | def install_hooks(self, wire, force=False): | |||
|
660 | from vcsserver.hook_utils import install_git_hooks | |||
|
661 | repo = self._factory.repo(wire) | |||
|
662 | return install_git_hooks(repo.path, repo.bare, force_create=force) | |||
|
663 | ||||
658 |
|
664 | |||
659 | def str_to_dulwich(value): |
|
665 | def str_to_dulwich(value): | |
660 | """ |
|
666 | """ |
@@ -769,3 +769,8 b' class HgRemote(object):' | |||||
769 | repo = self._factory.repo(wire) |
|
769 | repo = self._factory.repo(wire) | |
770 | baseui = self._factory._create_config(wire['config']) |
|
770 | baseui = self._factory._create_config(wire['config']) | |
771 | commands.bookmark(baseui, repo, bookmark, rev=revision, force=True) |
|
771 | commands.bookmark(baseui, repo, bookmark, rev=revision, force=True) | |
|
772 | ||||
|
773 | @reraise_safe_exceptions | |||
|
774 | def install_hooks(self, wire, force=False): | |||
|
775 | # we don't need any special hooks for Mercurial | |||
|
776 | pass |
@@ -20,10 +20,10 b'' | |||||
20 | import io |
|
20 | import io | |
21 | import os |
|
21 | import os | |
22 | import sys |
|
22 | import sys | |
23 | import json |
|
|||
24 | import logging |
|
23 | import logging | |
25 | import collections |
|
24 | import collections | |
26 | import importlib |
|
25 | import importlib | |
|
26 | import base64 | |||
27 |
|
27 | |||
28 | from httplib import HTTPConnection |
|
28 | from httplib import HTTPConnection | |
29 |
|
29 | |||
@@ -46,7 +46,11 b' class HooksHttpClient(object):' | |||||
46 | def __call__(self, method, extras): |
|
46 | def __call__(self, method, extras): | |
47 | connection = HTTPConnection(self.hooks_uri) |
|
47 | connection = HTTPConnection(self.hooks_uri) | |
48 | body = self._serialize(method, extras) |
|
48 | body = self._serialize(method, extras) | |
49 | connection.request('POST', '/', body) |
|
49 | try: | |
|
50 | connection.request('POST', '/', body) | |||
|
51 | except Exception: | |||
|
52 | log.error('Connection failed on %s', connection) | |||
|
53 | raise | |||
50 | response = connection.getresponse() |
|
54 | response = connection.getresponse() | |
51 | return json.loads(response.read()) |
|
55 | return json.loads(response.read()) | |
52 |
|
56 | |||
@@ -97,6 +101,17 b' class GitMessageWriter(RemoteMessageWrit' | |||||
97 | self.stdout.write(message.encode('utf-8')) |
|
101 | self.stdout.write(message.encode('utf-8')) | |
98 |
|
102 | |||
99 |
|
103 | |||
|
104 | class SvnMessageWriter(RemoteMessageWriter): | |||
|
105 | """Writer that knows how to send messages to svn clients.""" | |||
|
106 | ||||
|
107 | def __init__(self, stderr=None): | |||
|
108 | # SVN needs data sent to stderr for back-to-client messaging | |||
|
109 | self.stderr = stderr or sys.stderr | |||
|
110 | ||||
|
111 | def write(self, message): | |||
|
112 | self.stderr.write(message.encode('utf-8')) | |||
|
113 | ||||
|
114 | ||||
100 | def _handle_exception(result): |
|
115 | def _handle_exception(result): | |
101 | exception_class = result.get('exception') |
|
116 | exception_class = result.get('exception') | |
102 | exception_traceback = result.get('exception_traceback') |
|
117 | exception_traceback = result.get('exception_traceback') | |
@@ -122,8 +137,9 b' def _get_hooks_client(extras):' | |||||
122 |
|
137 | |||
123 |
|
138 | |||
124 | def _call_hook(hook_name, extras, writer): |
|
139 | def _call_hook(hook_name, extras, writer): | |
125 | hooks = _get_hooks_client(extras) |
|
140 | hooks_client = _get_hooks_client(extras) | |
126 | result = hooks(hook_name, extras) |
|
141 | log.debug('Hooks, using client:%s', hooks_client) | |
|
142 | result = hooks_client(hook_name, extras) | |||
127 | log.debug('Hooks got result: %s', result) |
|
143 | log.debug('Hooks got result: %s', result) | |
128 | writer.write(result['output']) |
|
144 | writer.write(result['output']) | |
129 | _handle_exception(result) |
|
145 | _handle_exception(result) | |
@@ -465,3 +481,61 b' def git_post_receive(unused_repo_path, r' | |||||
465 | pass |
|
481 | pass | |
466 |
|
482 | |||
467 | return _call_hook('post_push', extras, GitMessageWriter()) |
|
483 | return _call_hook('post_push', extras, GitMessageWriter()) | |
|
484 | ||||
|
485 | ||||
|
486 | def svn_pre_commit(repo_path, commit_data, env): | |||
|
487 | path, txn_id = commit_data | |||
|
488 | branches = [] | |||
|
489 | tags = [] | |||
|
490 | ||||
|
491 | cmd = ['svnlook', 'pget', | |||
|
492 | '-t', txn_id, | |||
|
493 | '--revprop', path, 'rc-scm-extras'] | |||
|
494 | stdout, stderr = subprocessio.run_command( | |||
|
495 | cmd, env=os.environ.copy()) | |||
|
496 | extras = json.loads(base64.urlsafe_b64decode(stdout)) | |||
|
497 | ||||
|
498 | extras['commit_ids'] = [] | |||
|
499 | extras['txn_id'] = txn_id | |||
|
500 | extras['new_refs'] = { | |||
|
501 | 'branches': branches, | |||
|
502 | 'bookmarks': [], | |||
|
503 | 'tags': tags, | |||
|
504 | } | |||
|
505 | sys.stderr.write(str(extras)) | |||
|
506 | return _call_hook('pre_push', extras, SvnMessageWriter()) | |||
|
507 | ||||
|
508 | ||||
|
509 | def svn_post_commit(repo_path, commit_data, env): | |||
|
510 | """ | |||
|
511 | commit_data is path, rev, txn_id | |||
|
512 | """ | |||
|
513 | path, commit_id, txn_id = commit_data | |||
|
514 | branches = [] | |||
|
515 | tags = [] | |||
|
516 | ||||
|
517 | cmd = ['svnlook', 'pget', | |||
|
518 | '-r', commit_id, | |||
|
519 | '--revprop', path, 'rc-scm-extras'] | |||
|
520 | stdout, stderr = subprocessio.run_command( | |||
|
521 | cmd, env=os.environ.copy()) | |||
|
522 | ||||
|
523 | extras = json.loads(base64.urlsafe_b64decode(stdout)) | |||
|
524 | ||||
|
525 | extras['commit_ids'] = [commit_id] | |||
|
526 | extras['txn_id'] = txn_id | |||
|
527 | extras['new_refs'] = { | |||
|
528 | 'branches': branches, | |||
|
529 | 'bookmarks': [], | |||
|
530 | 'tags': tags, | |||
|
531 | } | |||
|
532 | ||||
|
533 | if 'repo_size' in extras['hooks']: | |||
|
534 | try: | |||
|
535 | _call_hook('repo_size', extras, SvnMessageWriter()) | |||
|
536 | except: | |||
|
537 | pass | |||
|
538 | ||||
|
539 | return _call_hook('post_push', extras, SvnMessageWriter()) | |||
|
540 | ||||
|
541 |
@@ -92,6 +92,10 b' class VCS(object):' | |||||
92 | svn_repo_cache = self.cache.get_cache_region( |
|
92 | svn_repo_cache = self.cache.get_cache_region( | |
93 | 'svn', region='repo_object') |
|
93 | 'svn', region='repo_object') | |
94 | svn_factory = SubversionFactory(svn_repo_cache) |
|
94 | svn_factory = SubversionFactory(svn_repo_cache) | |
|
95 | # hg factory is used for svn url validation | |||
|
96 | hg_repo_cache = self.cache.get_cache_region( | |||
|
97 | 'hg', region='repo_object') | |||
|
98 | hg_factory = MercurialFactory(hg_repo_cache) | |||
95 | self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory) |
|
99 | self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory) | |
96 | else: |
|
100 | else: | |
97 | log.info("Subversion client import failed") |
|
101 | log.info("Subversion client import failed") | |
@@ -190,6 +194,9 b' class HTTPApplication(object):' | |||||
190 | git_path = app_settings.get('git_path', None) |
|
194 | git_path = app_settings.get('git_path', None) | |
191 | if git_path: |
|
195 | if git_path: | |
192 | settings.GIT_EXECUTABLE = git_path |
|
196 | settings.GIT_EXECUTABLE = git_path | |
|
197 | binary_dir = app_settings.get('core.binary_dir', None) | |||
|
198 | if binary_dir: | |||
|
199 | settings.BINARY_DIR = binary_dir | |||
193 |
|
200 | |||
194 | def _configure(self): |
|
201 | def _configure(self): | |
195 | self.config.add_renderer( |
|
202 | self.config.add_renderer( |
@@ -17,3 +17,4 b'' | |||||
17 |
|
17 | |||
18 | WIRE_ENCODING = 'UTF-8' |
|
18 | WIRE_ENCODING = 'UTF-8' | |
19 | GIT_EXECUTABLE = 'git' |
|
19 | GIT_EXECUTABLE = 'git' | |
|
20 | BINARY_DIR = '' |
@@ -32,7 +32,7 b' import svn.diff' | |||||
32 | import svn.fs |
|
32 | import svn.fs | |
33 | import svn.repos |
|
33 | import svn.repos | |
34 |
|
34 | |||
35 | from vcsserver import svn_diff, exceptions, subprocessio |
|
35 | from vcsserver import svn_diff, exceptions, subprocessio, settings | |
36 | from vcsserver.base import RepoFactory, raise_from_original |
|
36 | from vcsserver.base import RepoFactory, raise_from_original | |
37 |
|
37 | |||
38 | log = logging.getLogger(__name__) |
|
38 | log = logging.getLogger(__name__) | |
@@ -414,6 +414,17 b' class SvnRemote(object):' | |||||
414 | def is_large_file(self, wire, path): |
|
414 | def is_large_file(self, wire, path): | |
415 | return False |
|
415 | return False | |
416 |
|
416 | |||
|
417 | @reraise_safe_exceptions | |||
|
418 | def install_hooks(self, wire, force=False): | |||
|
419 | from vcsserver.hook_utils import install_svn_hooks | |||
|
420 | repo_path = wire['path'] | |||
|
421 | binary_dir = settings.BINARY_DIR | |||
|
422 | executable = None | |||
|
423 | if binary_dir: | |||
|
424 | executable = os.path.join(binary_dir, 'python') | |||
|
425 | return install_svn_hooks( | |||
|
426 | repo_path, executable=executable, force_create=force) | |||
|
427 | ||||
417 |
|
428 | |||
418 | class SvnDiffer(object): |
|
429 | class SvnDiffer(object): | |
419 | """ |
|
430 | """ | |
@@ -576,6 +587,7 b' class SvnDiffer(object):' | |||||
576 | return content.splitlines(True) |
|
587 | return content.splitlines(True) | |
577 |
|
588 | |||
578 |
|
589 | |||
|
590 | ||||
579 | class DiffChangeEditor(svn.delta.Editor): |
|
591 | class DiffChangeEditor(svn.delta.Editor): | |
580 | """ |
|
592 | """ | |
581 | Records changes between two given revisions |
|
593 | Records changes between two given revisions |
@@ -55,3 +55,4 b' def get_available_port():' | |||||
55 | mysocket.close() |
|
55 | mysocket.close() | |
56 | del mysocket |
|
56 | del mysocket | |
57 | return port |
|
57 | return port | |
|
58 |
@@ -69,3 +69,18 b' class ContextINI(object):' | |||||
69 | def __exit__(self, exc_type, exc_val, exc_tb): |
|
69 | def __exit__(self, exc_type, exc_val, exc_tb): | |
70 | if self.destroy: |
|
70 | if self.destroy: | |
71 | os.remove(self.new_path) |
|
71 | os.remove(self.new_path) | |
|
72 | ||||
|
73 | ||||
|
74 | def no_newline_id_generator(test_name): | |||
|
75 | """ | |||
|
76 | Generates a test name without spaces or newlines characters. Used for | |||
|
77 | nicer output of progress of test | |||
|
78 | """ | |||
|
79 | org_name = test_name | |||
|
80 | test_name = test_name\ | |||
|
81 | .replace('\n', '_N') \ | |||
|
82 | .replace('\r', '_N') \ | |||
|
83 | .replace('\t', '_T') \ | |||
|
84 | .replace(' ', '_S') | |||
|
85 | ||||
|
86 | return test_name or 'test-with-empty-name' |
@@ -73,3 +73,10 b" def safe_str(unicode_, to_encoding=['utf" | |||||
73 | return unicode_.encode(encoding) |
|
73 | return unicode_.encode(encoding) | |
74 | except (ImportError, UnicodeEncodeError): |
|
74 | except (ImportError, UnicodeEncodeError): | |
75 | return unicode_.encode(to_encoding[0], 'replace') |
|
75 | return unicode_.encode(to_encoding[0], 'replace') | |
|
76 | ||||
|
77 | ||||
|
78 | class AttributeDict(dict): | |||
|
79 | def __getattr__(self, attr): | |||
|
80 | return self.get(attr, None) | |||
|
81 | __setattr__ = dict.__setitem__ | |||
|
82 | __delattr__ = dict.__delitem__ |
General Comments 0
You need to be logged in to leave comments.
Login now