##// END OF EJS Templates
Don't always return action, raise an Exception if we cannot check what the action is
marcink -
r2525:c35980ae beta
parent child Browse files
Show More
@@ -1,260 +1,259
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.hooks
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Hooks runned by rhodecode
7 7
8 8 :created_on: Aug 6, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import os
26 26 import sys
27 27 import binascii
28 28 from inspect import isfunction
29 29
30 30 from mercurial.scmutil import revrange
31 31 from mercurial.node import nullrev
32 32
33 33 from rhodecode.lib import helpers as h
34 34 from rhodecode.lib.utils import action_logger
35 35 from rhodecode.lib.vcs.backends.base import EmptyChangeset
36 36
37 37
38 38 def _get_scm_size(alias, root_path):
39 39
40 40 if not alias.startswith('.'):
41 41 alias += '.'
42 42
43 43 size_scm, size_root = 0, 0
44 44 for path, dirs, files in os.walk(root_path):
45 45 if path.find(alias) != -1:
46 46 for f in files:
47 47 try:
48 48 size_scm += os.path.getsize(os.path.join(path, f))
49 49 except OSError:
50 50 pass
51 51 else:
52 52 for f in files:
53 53 try:
54 54 size_root += os.path.getsize(os.path.join(path, f))
55 55 except OSError:
56 56 pass
57 57
58 58 size_scm_f = h.format_byte_size(size_scm)
59 59 size_root_f = h.format_byte_size(size_root)
60 60 size_total_f = h.format_byte_size(size_root + size_scm)
61 61
62 62 return size_scm_f, size_root_f, size_total_f
63 63
64 64
65 65 def repo_size(ui, repo, hooktype=None, **kwargs):
66 66 """
67 67 Presents size of repository after push
68 68
69 69 :param ui:
70 70 :param repo:
71 71 :param hooktype:
72 72 """
73 73
74 74 size_hg_f, size_root_f, size_total_f = _get_scm_size('.hg', repo.root)
75 75
76 76 last_cs = repo[len(repo) - 1]
77 77
78 78 msg = ('Repository size .hg:%s repo:%s total:%s\n'
79 79 'Last revision is now r%s:%s\n') % (
80 80 size_hg_f, size_root_f, size_total_f, last_cs.rev(), last_cs.hex()[:12]
81 81 )
82 82
83 83 sys.stdout.write(msg)
84 84
85 85
86 86 def log_pull_action(ui, repo, **kwargs):
87 87 """
88 88 Logs user last pull action
89 89
90 90 :param ui:
91 91 :param repo:
92 92 """
93
94 93 extras = dict(repo.ui.configitems('rhodecode_extras'))
95 94 username = extras['username']
96 95 repository = extras['repository']
97 96 scm = extras['scm']
98 97 action = 'pull'
99 98
100 99 action_logger(username, action, repository, extras['ip'], commit=True)
101 100 # extension hook call
102 101 from rhodecode import EXTENSIONS
103 102 callback = getattr(EXTENSIONS, 'PULL_HOOK', None)
104 103
105 104 if isfunction(callback):
106 105 kw = {}
107 106 kw.update(extras)
108 107 callback(**kw)
109 108 return 0
110 109
111 110
112 111 def log_push_action(ui, repo, **kwargs):
113 112 """
114 113 Maps user last push action to new changeset id, from mercurial
115 114
116 115 :param ui:
117 116 :param repo: repo object containing the `ui` object
118 117 """
119 118
120 119 extras = dict(repo.ui.configitems('rhodecode_extras'))
121 120 username = extras['username']
122 121 repository = extras['repository']
123 122 action = extras['action'] + ':%s'
124 123 scm = extras['scm']
125 124
126 125 if scm == 'hg':
127 126 node = kwargs['node']
128 127
129 128 def get_revs(repo, rev_opt):
130 129 if rev_opt:
131 130 revs = revrange(repo, rev_opt)
132 131
133 132 if len(revs) == 0:
134 133 return (nullrev, nullrev)
135 134 return (max(revs), min(revs))
136 135 else:
137 136 return (len(repo) - 1, 0)
138 137
139 138 stop, start = get_revs(repo, [node + ':'])
140 139 h = binascii.hexlify
141 140 revs = [h(repo[r].node()) for r in xrange(start, stop + 1)]
142 141 elif scm == 'git':
143 142 revs = kwargs.get('_git_revs', [])
144 143 if '_git_revs' in kwargs:
145 144 kwargs.pop('_git_revs')
146 145
147 146 action = action % ','.join(revs)
148 147
149 148 action_logger(username, action, repository, extras['ip'], commit=True)
150 149
151 150 # extension hook call
152 151 from rhodecode import EXTENSIONS
153 152 callback = getattr(EXTENSIONS, 'PUSH_HOOK', None)
154 153 if isfunction(callback):
155 154 kw = {'pushed_revs': revs}
156 155 kw.update(extras)
157 156 callback(**kw)
158 157 return 0
159 158
160 159
161 160 def log_create_repository(repository_dict, created_by, **kwargs):
162 161 """
163 162 Post create repository Hook. This is a dummy function for admins to re-use
164 163 if needed. It's taken from rhodecode-extensions module and executed
165 164 if present
166 165
167 166 :param repository: dict dump of repository object
168 167 :param created_by: username who created repository
169 168 :param created_date: date of creation
170 169
171 170 available keys of repository_dict:
172 171
173 172 'repo_type',
174 173 'description',
175 174 'private',
176 175 'created_on',
177 176 'enable_downloads',
178 177 'repo_id',
179 178 'user_id',
180 179 'enable_statistics',
181 180 'clone_uri',
182 181 'fork_id',
183 182 'group_id',
184 183 'repo_name'
185 184
186 185 """
187 186 from rhodecode import EXTENSIONS
188 187 callback = getattr(EXTENSIONS, 'CREATE_REPO_HOOK', None)
189 188 if isfunction(callback):
190 189 kw = {}
191 190 kw.update(repository_dict)
192 191 kw.update({'created_by': created_by})
193 192 kw.update(kwargs)
194 193 return callback(**kw)
195 194
196 195 return 0
197 196
198 197
199 198 def handle_git_post_receive(repo_path, revs, env):
200 199 """
201 200 A really hacky method that is runned by git post-receive hook and logs
202 201 an push action together with pushed revisions. It's executed by subprocess
203 202 thus needs all info to be able to create a on the fly pylons enviroment,
204 203 connect to database and run the logging code. Hacky as sh*t but works.
205 204
206 205 :param repo_path:
207 206 :type repo_path:
208 207 :param revs:
209 208 :type revs:
210 209 :param env:
211 210 :type env:
212 211 """
213 212 from paste.deploy import appconfig
214 213 from sqlalchemy import engine_from_config
215 214 from rhodecode.config.environment import load_environment
216 215 from rhodecode.model import init_model
217 216 from rhodecode.model.db import RhodeCodeUi
218 217 from rhodecode.lib.utils import make_ui
219 218 from rhodecode.model.db import Repository
220 219
221 220 path, ini_name = os.path.split(env['RHODECODE_CONFIG_FILE'])
222 221 conf = appconfig('config:%s' % ini_name, relative_to=path)
223 222 load_environment(conf.global_conf, conf.local_conf)
224 223
225 224 engine = engine_from_config(conf, 'sqlalchemy.db1.')
226 225 init_model(engine)
227 226
228 227 baseui = make_ui('db')
229 228 repo = Repository.get_by_full_path(repo_path)
230 229
231 230 _hooks = dict(baseui.configitems('hooks')) or {}
232 231 # if push hook is enabled via web interface
233 232 if _hooks.get(RhodeCodeUi.HOOK_PUSH):
234 233
235 234 extras = {
236 235 'username': env['RHODECODE_USER'],
237 236 'repository': repo.repo_name,
238 237 'scm': 'git',
239 238 'action': 'push',
240 239 'ip': env['RHODECODE_CONFIG_IP'],
241 240 }
242 241 for k, v in extras.items():
243 242 baseui.setconfig('rhodecode_extras', k, v)
244 243 repo = repo.scm_instance
245 244 repo.ui = baseui
246 245 old_rev, new_rev, ref = revs
247 246 if old_rev == EmptyChangeset().raw_id:
248 247 cmd = "for-each-ref --format='%(refname)' 'refs/heads/*'"
249 248 heads = repo.run_git_command(cmd)[0]
250 249 heads = heads.replace(ref, '')
251 250 heads = ' '.join(map(lambda c: c.strip('\n').strip(),
252 251 heads.splitlines()))
253 252 cmd = ('log ' + new_rev +
254 253 ' --reverse --pretty=format:"%H" --not ' + heads)
255 254 else:
256 255 cmd = ('log ' + old_rev + '..' + new_rev +
257 256 ' --reverse --pretty=format:"%H"')
258 257 git_revs = repo.run_git_command(cmd)[0].splitlines()
259 258
260 259 log_push_action(baseui, repo, _git_revs=git_revs)
@@ -1,255 +1,258
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.middleware.simplehg
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 SimpleHG middleware for handling mercurial protocol request
7 7 (push/clone etc.). It's implemented with basic auth function
8 8
9 9 :created_on: Apr 28, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 27 import os
28 28 import logging
29 29 import traceback
30 30 import urllib
31 31
32 32 from mercurial.error import RepoError
33 33 from mercurial.hgweb import hgweb_mod
34 34
35 35 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
36 36
37 37 from rhodecode.lib.utils2 import safe_str
38 38 from rhodecode.lib.base import BaseVCSController
39 39 from rhodecode.lib.auth import get_container_username
40 40 from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections
41 41 from rhodecode.model.db import User
42 42
43 43 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
44 44
45 45 log = logging.getLogger(__name__)
46 46
47 47
48 48 def is_mercurial(environ):
49 49 """
50 50 Returns True if request's target is mercurial server - header
51 51 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
52 52 """
53 53 http_accept = environ.get('HTTP_ACCEPT')
54 54 path_info = environ['PATH_INFO']
55 55 if http_accept and http_accept.startswith('application/mercurial'):
56 56 ishg_path = True
57 57 else:
58 58 ishg_path = False
59 59
60 60 log.debug('pathinfo: %s detected as HG %s' % (
61 61 path_info, ishg_path)
62 62 )
63 63 return ishg_path
64 64
65 65
66 66 class SimpleHg(BaseVCSController):
67 67
68 68 def _handle_request(self, environ, start_response):
69 69 if not is_mercurial(environ):
70 70 return self.application(environ, start_response)
71 71
72 72 ipaddr = self._get_ip_addr(environ)
73 73 username = None
74 74 # skip passing error to error controller
75 75 environ['pylons.status_code_redirect'] = True
76 76
77 77 #======================================================================
78 78 # EXTRACT REPOSITORY NAME FROM ENV
79 79 #======================================================================
80 80 try:
81 81 repo_name = environ['REPO_NAME'] = self.__get_repository(environ)
82 82 log.debug('Extracted repo name is %s' % repo_name)
83 83 except:
84 84 return HTTPInternalServerError()(environ, start_response)
85 85
86 86 # quick check if that dir exists...
87 87 if is_valid_repo(repo_name, self.basepath) is False:
88 88 return HTTPNotFound()(environ, start_response)
89 89
90 90 #======================================================================
91 91 # GET ACTION PULL or PUSH
92 92 #======================================================================
93 93 action = self.__get_action(environ)
94 94
95 95 #======================================================================
96 96 # CHECK ANONYMOUS PERMISSION
97 97 #======================================================================
98 98 if action in ['pull', 'push']:
99 99 anonymous_user = self.__get_user('default')
100 100 username = anonymous_user.username
101 101 anonymous_perm = self._check_permission(action, anonymous_user,
102 102 repo_name)
103 103
104 104 if anonymous_perm is not True or anonymous_user.active is False:
105 105 if anonymous_perm is not True:
106 106 log.debug('Not enough credentials to access this '
107 107 'repository as anonymous user')
108 108 if anonymous_user.active is False:
109 109 log.debug('Anonymous access is disabled, running '
110 110 'authentication')
111 111 #==============================================================
112 112 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
113 113 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
114 114 #==============================================================
115 115
116 116 # Attempting to retrieve username from the container
117 117 username = get_container_username(environ, self.config)
118 118
119 119 # If not authenticated by the container, running basic auth
120 120 if not username:
121 121 self.authenticate.realm = \
122 122 safe_str(self.config['rhodecode_realm'])
123 123 result = self.authenticate(environ)
124 124 if isinstance(result, str):
125 125 AUTH_TYPE.update(environ, 'basic')
126 126 REMOTE_USER.update(environ, result)
127 127 username = result
128 128 else:
129 129 return result.wsgi_application(environ, start_response)
130 130
131 131 #==============================================================
132 132 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
133 133 #==============================================================
134 134 try:
135 135 user = self.__get_user(username)
136 136 if user is None or not user.active:
137 137 return HTTPForbidden()(environ, start_response)
138 138 username = user.username
139 139 except:
140 140 log.error(traceback.format_exc())
141 141 return HTTPInternalServerError()(environ, start_response)
142 142
143 143 #check permissions for this repository
144 144 perm = self._check_permission(action, user, repo_name)
145 145 if perm is not True:
146 146 return HTTPForbidden()(environ, start_response)
147 147
148 148 # extras are injected into mercurial UI object and later available
149 149 # in hg hooks executed by rhodecode
150 150 extras = {
151 151 'ip': ipaddr,
152 152 'username': username,
153 153 'action': action,
154 154 'repository': repo_name,
155 155 'scm': 'hg',
156 156 }
157 157
158 158 #======================================================================
159 159 # MERCURIAL REQUEST HANDLING
160 160 #======================================================================
161 161 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
162 162 log.debug('Repository path is %s' % repo_path)
163 163
164 164 baseui = make_ui('db')
165 165 self.__inject_extras(repo_path, baseui, extras)
166 166
167 167 try:
168 168 # invalidate cache on push
169 169 if action == 'push':
170 170 self._invalidate_cache(repo_name)
171 171 log.info('%s action on HG repo "%s"' % (action, repo_name))
172 172 app = self.__make_app(repo_path, baseui, extras)
173 173 return app(environ, start_response)
174 174 except RepoError, e:
175 175 if str(e).find('not found') != -1:
176 176 return HTTPNotFound()(environ, start_response)
177 177 except Exception:
178 178 log.error(traceback.format_exc())
179 179 return HTTPInternalServerError()(environ, start_response)
180 180
181 181 def __make_app(self, repo_name, baseui, extras):
182 182 """
183 183 Make an wsgi application using hgweb, and inject generated baseui
184 184 instance, additionally inject some extras into ui object
185 185 """
186 186 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
187 187
188 188 def __get_repository(self, environ):
189 189 """
190 190 Get's repository name out of PATH_INFO header
191 191
192 192 :param environ: environ where PATH_INFO is stored
193 193 """
194 194 try:
195 195 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
196 196 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
197 197 if repo_name.endswith('/'):
198 198 repo_name = repo_name.rstrip('/')
199 199 except:
200 200 log.error(traceback.format_exc())
201 201 raise
202 202
203 203 return repo_name
204 204
205 205 def __get_user(self, username):
206 206 return User.get_by_username(username)
207 207
208 208 def __get_action(self, environ):
209 209 """
210 210 Maps mercurial request commands into a clone,pull or push command.
211 211 This should always return a valid command string
212 212
213 213 :param environ:
214 214 """
215 215 mapping = {'changegroup': 'pull',
216 216 'changegroupsubset': 'pull',
217 217 'stream_out': 'pull',
218 218 'listkeys': 'pull',
219 219 'unbundle': 'push',
220 220 'pushkey': 'push', }
221 221 for qry in environ['QUERY_STRING'].split('&'):
222 222 if qry.startswith('cmd'):
223 223 cmd = qry.split('=')[-1]
224 224 if cmd in mapping:
225 225 return mapping[cmd]
226 226
227 227 return 'pull'
228 228
229 raise Exception('Unable to detect pull/push action !!'
230 'Are you using non standard command or client ?')
231
229 232 def __inject_extras(self, repo_path, baseui, extras={}):
230 233 """
231 234 Injects some extra params into baseui instance
232 235
233 236 also overwrites global settings with those takes from local hgrc file
234 237
235 238 :param baseui: baseui instance
236 239 :param extras: dict with extra params to put into baseui
237 240 """
238 241
239 242 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
240 243
241 244 # make our hgweb quiet so it doesn't print output
242 245 baseui.setconfig('ui', 'quiet', 'true')
243 246
244 247 #inject some additional parameters that will be available in ui
245 248 #for hooks
246 249 for k, v in extras.items():
247 250 baseui.setconfig('rhodecode_extras', k, v)
248 251
249 252 repoui = make_ui('file', hgrc, False)
250 253
251 254 if repoui:
252 255 #overwrite our ui instance with the section from hgrc file
253 256 for section in ui_sections:
254 257 for k, v in repoui.configitems(section):
255 258 baseui.setconfig(section, k, v)
General Comments 0
You need to be logged in to leave comments. Login now