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 | 8 | # all config files |
|
9 | 9 | recursive-include configs * |
|
10 | 10 | |
|
11 | # hook templates | |
|
12 | recursive-include vcsserver/hook_utils/hook_templates * | |
|
13 | ||
|
11 | 14 | # skip any tests files |
|
12 | 15 | recursive-exclude vcsserver/tests * |
|
13 | 16 |
@@ -20,6 +20,10 b' beaker.cache.repo_object.max_items = 100' | |||
|
20 | 20 | beaker.cache.repo_object.expire = 300 |
|
21 | 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 | 27 | [server:main] |
|
24 | 28 | ## COMMON ## |
|
25 | 29 | host = 0.0.0.0 |
@@ -39,7 +39,7 b' use = egg:rhodecode-vcsserver' | |||
|
39 | 39 | pyramid.default_locale_name = en |
|
40 | 40 | pyramid.includes = |
|
41 | 41 | |
|
42 |
|
|
|
42 | # default locale used by VCS systems | |
|
43 | 43 | locale = en_US.UTF-8 |
|
44 | 44 | |
|
45 | 45 | # cache regions, please don't change |
@@ -50,6 +50,10 b' beaker.cache.repo_object.max_items = 100' | |||
|
50 | 50 | beaker.cache.repo_object.expire = 300 |
|
51 | 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 | 59 | ### LOGGING CONFIGURATION #### |
@@ -655,6 +655,12 b' class GitRemote(object):' | |||
|
655 | 655 | else: |
|
656 | 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 | 665 | def str_to_dulwich(value): |
|
660 | 666 | """ |
@@ -769,3 +769,8 b' class HgRemote(object):' | |||
|
769 | 769 | repo = self._factory.repo(wire) |
|
770 | 770 | baseui = self._factory._create_config(wire['config']) |
|
771 | 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 | 20 | import io |
|
21 | 21 | import os |
|
22 | 22 | import sys |
|
23 | import json | |
|
24 | 23 | import logging |
|
25 | 24 | import collections |
|
26 | 25 | import importlib |
|
26 | import base64 | |
|
27 | 27 | |
|
28 | 28 | from httplib import HTTPConnection |
|
29 | 29 | |
@@ -46,7 +46,11 b' class HooksHttpClient(object):' | |||
|
46 | 46 | def __call__(self, method, extras): |
|
47 | 47 | connection = HTTPConnection(self.hooks_uri) |
|
48 | 48 | body = self._serialize(method, extras) |
|
49 | try: | |
|
49 | 50 | connection.request('POST', '/', body) |
|
51 | except Exception: | |
|
52 | log.error('Connection failed on %s', connection) | |
|
53 | raise | |
|
50 | 54 | response = connection.getresponse() |
|
51 | 55 | return json.loads(response.read()) |
|
52 | 56 | |
@@ -97,6 +101,17 b' class GitMessageWriter(RemoteMessageWrit' | |||
|
97 | 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 | 115 | def _handle_exception(result): |
|
101 | 116 | exception_class = result.get('exception') |
|
102 | 117 | exception_traceback = result.get('exception_traceback') |
@@ -122,8 +137,9 b' def _get_hooks_client(extras):' | |||
|
122 | 137 | |
|
123 | 138 | |
|
124 | 139 | def _call_hook(hook_name, extras, writer): |
|
125 | hooks = _get_hooks_client(extras) | |
|
126 | result = hooks(hook_name, extras) | |
|
140 | hooks_client = _get_hooks_client(extras) | |
|
141 | log.debug('Hooks, using client:%s', hooks_client) | |
|
142 | result = hooks_client(hook_name, extras) | |
|
127 | 143 | log.debug('Hooks got result: %s', result) |
|
128 | 144 | writer.write(result['output']) |
|
129 | 145 | _handle_exception(result) |
@@ -465,3 +481,61 b' def git_post_receive(unused_repo_path, r' | |||
|
465 | 481 | pass |
|
466 | 482 | |
|
467 | 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 | 92 | svn_repo_cache = self.cache.get_cache_region( |
|
93 | 93 | 'svn', region='repo_object') |
|
94 | 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 | 99 | self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory) |
|
96 | 100 | else: |
|
97 | 101 | log.info("Subversion client import failed") |
@@ -190,6 +194,9 b' class HTTPApplication(object):' | |||
|
190 | 194 | git_path = app_settings.get('git_path', None) |
|
191 | 195 | if git_path: |
|
192 | 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 | 201 | def _configure(self): |
|
195 | 202 | self.config.add_renderer( |
@@ -17,3 +17,4 b'' | |||
|
17 | 17 | |
|
18 | 18 | WIRE_ENCODING = 'UTF-8' |
|
19 | 19 | GIT_EXECUTABLE = 'git' |
|
20 | BINARY_DIR = '' |
@@ -32,7 +32,7 b' import svn.diff' | |||
|
32 | 32 | import svn.fs |
|
33 | 33 | import svn.repos |
|
34 | 34 | |
|
35 | from vcsserver import svn_diff, exceptions, subprocessio | |
|
35 | from vcsserver import svn_diff, exceptions, subprocessio, settings | |
|
36 | 36 | from vcsserver.base import RepoFactory, raise_from_original |
|
37 | 37 | |
|
38 | 38 | log = logging.getLogger(__name__) |
@@ -414,6 +414,17 b' class SvnRemote(object):' | |||
|
414 | 414 | def is_large_file(self, wire, path): |
|
415 | 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 | 429 | class SvnDiffer(object): |
|
419 | 430 | """ |
@@ -576,6 +587,7 b' class SvnDiffer(object):' | |||
|
576 | 587 | return content.splitlines(True) |
|
577 | 588 | |
|
578 | 589 | |
|
590 | ||
|
579 | 591 | class DiffChangeEditor(svn.delta.Editor): |
|
580 | 592 | """ |
|
581 | 593 | Records changes between two given revisions |
@@ -55,3 +55,4 b' def get_available_port():' | |||
|
55 | 55 | mysocket.close() |
|
56 | 56 | del mysocket |
|
57 | 57 | return port |
|
58 |
@@ -69,3 +69,18 b' class ContextINI(object):' | |||
|
69 | 69 | def __exit__(self, exc_type, exc_val, exc_tb): |
|
70 | 70 | if self.destroy: |
|
71 | 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 | 73 | return unicode_.encode(encoding) |
|
74 | 74 | except (ImportError, UnicodeEncodeError): |
|
75 | 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