##// END OF EJS Templates
fixes issue #436 git push error
marcink -
r2236:37c143aa beta
parent child Browse files
Show More
@@ -1,190 +1,190 b''
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
28 28 from mercurial.scmutil import revrange
29 29 from mercurial.node import nullrev
30 30 from rhodecode import EXTENSIONS
31 31 from rhodecode.lib import helpers as h
32 32 from rhodecode.lib.utils import action_logger
33 33 from inspect import isfunction
34 34
35 35
36 36 def _get_scm_size(alias, root_path):
37 37
38 38 if not alias.startswith('.'):
39 39 alias += '.'
40 40
41 41 size_scm, size_root = 0, 0
42 42 for path, dirs, files in os.walk(root_path):
43 43 if path.find(alias) != -1:
44 44 for f in files:
45 45 try:
46 46 size_scm += os.path.getsize(os.path.join(path, f))
47 47 except OSError:
48 48 pass
49 49 else:
50 50 for f in files:
51 51 try:
52 52 size_root += os.path.getsize(os.path.join(path, f))
53 53 except OSError:
54 54 pass
55 55
56 56 size_scm_f = h.format_byte_size(size_scm)
57 57 size_root_f = h.format_byte_size(size_root)
58 58 size_total_f = h.format_byte_size(size_root + size_scm)
59 59
60 60 return size_scm_f, size_root_f, size_total_f
61 61
62 62
63 63 def repo_size(ui, repo, hooktype=None, **kwargs):
64 64 """
65 65 Presents size of repository after push
66 66
67 67 :param ui:
68 68 :param repo:
69 69 :param hooktype:
70 70 """
71 71
72 72 size_hg_f, size_root_f, size_total_f = _get_scm_size('.hg', repo.root)
73 73
74 74 last_cs = repo[len(repo) - 1]
75 75
76 76 msg = ('Repository size .hg:%s repo:%s total:%s\n'
77 77 'Last revision is now r%s:%s\n') % (
78 78 size_hg_f, size_root_f, size_total_f, last_cs.rev(), last_cs.hex()[:12]
79 79 )
80 80
81 81 sys.stdout.write(msg)
82 82
83 83
84 84 def log_pull_action(ui, repo, **kwargs):
85 85 """
86 86 Logs user last pull action
87 87
88 88 :param ui:
89 89 :param repo:
90 90 """
91 91
92 92 extras = dict(repo.ui.configitems('rhodecode_extras'))
93 93 username = extras['username']
94 94 repository = extras['repository']
95 95 scm = extras['scm']
96 96 action = 'pull'
97 97
98 98 action_logger(username, action, repository, extras['ip'], commit=True)
99 99 # extension hook call
100 100 callback = getattr(EXTENSIONS, 'PULL_HOOK', None)
101 101
102 102 if isfunction(callback):
103 103 kw = {}
104 104 kw.update(extras)
105 105 callback(**kw)
106 106 return 0
107 107
108 108
109 109 def log_push_action(ui, repo, **kwargs):
110 110 """
111 111 Maps user last push action to new changeset id, from mercurial
112 112
113 113 :param ui:
114 :param repo:
114 :param repo: repo object containing the `ui` object
115 115 """
116 116
117 117 extras = dict(repo.ui.configitems('rhodecode_extras'))
118 118 username = extras['username']
119 119 repository = extras['repository']
120 120 action = extras['action'] + ':%s'
121 121 scm = extras['scm']
122 122
123 123 if scm == 'hg':
124 124 node = kwargs['node']
125 125
126 126 def get_revs(repo, rev_opt):
127 127 if rev_opt:
128 128 revs = revrange(repo, rev_opt)
129 129
130 130 if len(revs) == 0:
131 131 return (nullrev, nullrev)
132 132 return (max(revs), min(revs))
133 133 else:
134 134 return (len(repo) - 1, 0)
135 135
136 136 stop, start = get_revs(repo, [node + ':'])
137 137
138 138 revs = (str(repo[r]) for r in xrange(start, stop + 1))
139 139 elif scm == 'git':
140 140 revs = []
141 141
142 142 action = action % ','.join(revs)
143 143
144 144 action_logger(username, action, repository, extras['ip'], commit=True)
145 145
146 146 # extension hook call
147 147 callback = getattr(EXTENSIONS, 'PUSH_HOOK', None)
148 148 if isfunction(callback):
149 149 kw = {'pushed_revs': revs}
150 150 kw.update(extras)
151 151 callback(**kw)
152 152 return 0
153 153
154 154
155 155 def log_create_repository(repository_dict, created_by, **kwargs):
156 156 """
157 157 Post create repository Hook. This is a dummy function for admins to re-use
158 158 if needed. It's taken from rhodecode-extensions module and executed
159 159 if present
160 160
161 161 :param repository: dict dump of repository object
162 162 :param created_by: username who created repository
163 163 :param created_date: date of creation
164 164
165 165 available keys of repository_dict:
166 166
167 167 'repo_type',
168 168 'description',
169 169 'private',
170 170 'created_on',
171 171 'enable_downloads',
172 172 'repo_id',
173 173 'user_id',
174 174 'enable_statistics',
175 175 'clone_uri',
176 176 'fork_id',
177 177 'group_id',
178 178 'repo_name'
179 179
180 180 """
181 181
182 182 callback = getattr(EXTENSIONS, 'CREATE_REPO_HOOK', None)
183 183 if isfunction(callback):
184 184 kw = {}
185 185 kw.update(repository_dict)
186 186 kw.update({'created_by': created_by})
187 187 kw.update(kwargs)
188 188 return callback(**kw)
189 189
190 190 return 0
@@ -1,300 +1,300 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.middleware.simplegit
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 SimpleGit middleware for handling git protocol request (push/clone etc.)
7 7 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 re
29 29 import logging
30 30 import traceback
31 31
32 32 from dulwich import server as dulserver
33 33
34 34
35 35 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
36 36
37 37 def handle(self):
38 38 write = lambda x: self.proto.write_sideband(1, x)
39 39
40 40 graph_walker = dulserver.ProtocolGraphWalker(self,
41 41 self.repo.object_store,
42 42 self.repo.get_peeled)
43 43 objects_iter = self.repo.fetch_objects(
44 44 graph_walker.determine_wants, graph_walker, self.progress,
45 45 get_tagged=self.get_tagged)
46 46
47 47 # Did the process short-circuit (e.g. in a stateless RPC call)? Note
48 48 # that the client still expects a 0-object pack in most cases.
49 49 if objects_iter is None:
50 50 return
51 51
52 52 self.progress("counting objects: %d, done.\n" % len(objects_iter))
53 53 dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
54 54 objects_iter)
55 55 messages = []
56 56 messages.append('thank you for using rhodecode')
57 57
58 58 for msg in messages:
59 59 self.progress(msg + "\n")
60 60 # we are done
61 61 self.proto.write("0000")
62 62
63 63
64 64 dulserver.DEFAULT_HANDLERS = {
65 65 'git-upload-pack': SimpleGitUploadPackHandler,
66 66 'git-receive-pack': dulserver.ReceivePackHandler,
67 67 }
68 68
69 69 from dulwich.repo import Repo
70 70 from dulwich.web import make_wsgi_chain
71 71
72 72 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
73 73
74 74 from rhodecode.lib.utils2 import safe_str
75 75 from rhodecode.lib.base import BaseVCSController
76 76 from rhodecode.lib.auth import get_container_username
77 77 from rhodecode.lib.utils import is_valid_repo, make_ui
78 78 from rhodecode.model.db import User
79 79
80 80 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
81 81
82 82 log = logging.getLogger(__name__)
83 83
84 84
85 85 GIT_PROTO_PAT = re.compile(r'^/(.+)/(info/refs|git-upload-pack|git-receive-pack)')
86 86
87 87
88 88 def is_git(environ):
89 89 path_info = environ['PATH_INFO']
90 90 isgit_path = GIT_PROTO_PAT.match(path_info)
91 91 log.debug('pathinfo: %s detected as GIT %s' % (
92 92 path_info, isgit_path != None)
93 93 )
94 94 return isgit_path
95 95
96 96
97 97 class SimpleGit(BaseVCSController):
98 98
99 99 def _handle_request(self, environ, start_response):
100 100
101 101 if not is_git(environ):
102 102 return self.application(environ, start_response)
103 103
104 104 ipaddr = self._get_ip_addr(environ)
105 105 username = None
106 106 self._git_first_op = False
107 107 # skip passing error to error controller
108 108 environ['pylons.status_code_redirect'] = True
109 109
110 110 #======================================================================
111 111 # EXTRACT REPOSITORY NAME FROM ENV
112 112 #======================================================================
113 113 try:
114 114 repo_name = self.__get_repository(environ)
115 115 log.debug('Extracted repo name is %s' % repo_name)
116 116 except:
117 117 return HTTPInternalServerError()(environ, start_response)
118 118
119 119 # quick check if that dir exists...
120 120 if is_valid_repo(repo_name, self.basepath) is False:
121 121 return HTTPNotFound()(environ, start_response)
122 122
123 123 #======================================================================
124 124 # GET ACTION PULL or PUSH
125 125 #======================================================================
126 126 action = self.__get_action(environ)
127 127
128 128 #======================================================================
129 129 # CHECK ANONYMOUS PERMISSION
130 130 #======================================================================
131 131 if action in ['pull', 'push']:
132 132 anonymous_user = self.__get_user('default')
133 133 username = anonymous_user.username
134 134 anonymous_perm = self._check_permission(action, anonymous_user,
135 135 repo_name)
136 136
137 137 if anonymous_perm is not True or anonymous_user.active is False:
138 138 if anonymous_perm is not True:
139 139 log.debug('Not enough credentials to access this '
140 140 'repository as anonymous user')
141 141 if anonymous_user.active is False:
142 142 log.debug('Anonymous access is disabled, running '
143 143 'authentication')
144 144 #==============================================================
145 145 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
146 146 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
147 147 #==============================================================
148 148
149 149 # Attempting to retrieve username from the container
150 150 username = get_container_username(environ, self.config)
151 151
152 152 # If not authenticated by the container, running basic auth
153 153 if not username:
154 154 self.authenticate.realm = \
155 155 safe_str(self.config['rhodecode_realm'])
156 156 result = self.authenticate(environ)
157 157 if isinstance(result, str):
158 158 AUTH_TYPE.update(environ, 'basic')
159 159 REMOTE_USER.update(environ, result)
160 160 username = result
161 161 else:
162 162 return result.wsgi_application(environ, start_response)
163 163
164 164 #==============================================================
165 165 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
166 166 #==============================================================
167 167 if action in ['pull', 'push']:
168 168 try:
169 169 user = self.__get_user(username)
170 170 if user is None or not user.active:
171 171 return HTTPForbidden()(environ, start_response)
172 172 username = user.username
173 173 except:
174 174 log.error(traceback.format_exc())
175 175 return HTTPInternalServerError()(environ,
176 176 start_response)
177 177
178 178 #check permissions for this repository
179 179 perm = self._check_permission(action, user, repo_name)
180 180 if perm is not True:
181 181 return HTTPForbidden()(environ, start_response)
182 182 extras = {
183 183 'ip': ipaddr,
184 184 'username': username,
185 185 'action': action,
186 186 'repository': repo_name,
187 187 'scm': 'git',
188 188 }
189 189
190 190 #===================================================================
191 191 # GIT REQUEST HANDLING
192 192 #===================================================================
193 193 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
194 194 log.debug('Repository path is %s' % repo_path)
195 195
196 196 baseui = make_ui('db')
197 197 self.__inject_extras(repo_path, baseui, extras)
198 198
199 199
200 200 try:
201 201 # invalidate cache on push
202 202 if action == 'push':
203 203 self._invalidate_cache(repo_name)
204 self._handle_githooks(action, baseui, environ)
204 self._handle_githooks(repo_name, action, baseui, environ)
205 205
206 206 log.info('%s action on GIT repo "%s"' % (action, repo_name))
207 207 app = self.__make_app(repo_name, repo_path)
208 208 return app(environ, start_response)
209 209 except Exception:
210 210 log.error(traceback.format_exc())
211 211 return HTTPInternalServerError()(environ, start_response)
212 212
213 213 def __make_app(self, repo_name, repo_path):
214 214 """
215 215 Make an wsgi application using dulserver
216 216
217 217 :param repo_name: name of the repository
218 218 :param repo_path: full path to the repository
219 219 """
220 220 _d = {'/' + repo_name: Repo(repo_path)}
221 221 backend = dulserver.DictBackend(_d)
222 222 gitserve = make_wsgi_chain(backend)
223 223
224 224 return gitserve
225 225
226 226 def __get_repository(self, environ):
227 227 """
228 228 Get's repository name out of PATH_INFO header
229 229
230 230 :param environ: environ where PATH_INFO is stored
231 231 """
232 232 try:
233 233 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
234 234 repo_name = GIT_PROTO_PAT.match(environ['PATH_INFO']).group(1)
235 235 except:
236 236 log.error(traceback.format_exc())
237 237 raise
238 238
239 239 return repo_name
240 240
241 241 def __get_user(self, username):
242 242 return User.get_by_username(username)
243 243
244 244 def __get_action(self, environ):
245 245 """
246 246 Maps git request commands into a pull or push command.
247 247
248 248 :param environ:
249 249 """
250 250 service = environ['QUERY_STRING'].split('=')
251 251
252 252 if len(service) > 1:
253 253 service_cmd = service[1]
254 254 mapping = {
255 255 'git-receive-pack': 'push',
256 256 'git-upload-pack': 'pull',
257 257 }
258 258 op = mapping[service_cmd]
259 259 self._git_stored_op = op
260 260 return op
261 261 else:
262 262 # try to fallback to stored variable as we don't know if the last
263 263 # operation is pull/push
264 264 op = getattr(self, '_git_stored_op', 'pull')
265 265 return op
266 266
267 def _handle_githooks(self, action, baseui, environ):
267 def _handle_githooks(self, repo_name, action, baseui, environ):
268 268 from rhodecode.lib.hooks import log_pull_action, log_push_action
269 269 service = environ['QUERY_STRING'].split('=')
270 270 if len(service) < 2:
271 271 return
272 272
273 273 from rhodecode.model.db import Repository
274 274 _repo = Repository.get_by_repo_name(repo_name)
275 275 _repo = _repo.scm_instance
276 276 _repo._repo.ui = baseui
277 277
278 278 push_hook = 'pretxnchangegroup.push_logger'
279 279 pull_hook = 'preoutgoing.pull_logger'
280 280 _hooks = dict(baseui.configitems('hooks')) or {}
281 281 if action == 'push' and _hooks.get(push_hook):
282 log_push_action(ui=baseui, repo=repo._repo)
282 log_push_action(ui=baseui, repo=_repo._repo)
283 283 elif action == 'pull' and _hooks.get(pull_hook):
284 log_pull_action(ui=baseui, repo=repo._repo)
284 log_pull_action(ui=baseui, repo=_repo._repo)
285 285
286 286 def __inject_extras(self, repo_path, baseui, extras={}):
287 287 """
288 288 Injects some extra params into baseui instance
289 289
290 290 :param baseui: baseui instance
291 291 :param extras: dict with extra params to put into baseui
292 292 """
293 293
294 294 # make our hgweb quiet so it doesn't print output
295 295 baseui.setconfig('ui', 'quiet', 'true')
296 296
297 297 #inject some additional parameters that will be available in ui
298 298 #for hooks
299 299 for k, v in extras.items():
300 300 baseui.setconfig('rhodecode_extras', k, v)
General Comments 0
You need to be logged in to leave comments. Login now