##// END OF EJS Templates
webinterface file commiting executes push hooks ref #594
marcink -
r3478:796738bb beta
parent child Browse files
Show More
@@ -1,435 +1,437 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 import time
28 28 import binascii
29 29 from inspect import isfunction
30 30
31 31 from mercurial.scmutil import revrange
32 32 from mercurial.node import nullrev
33 33
34 34 from rhodecode.lib import helpers as h
35 35 from rhodecode.lib.utils import action_logger
36 36 from rhodecode.lib.vcs.backends.base import EmptyChangeset
37 37 from rhodecode.lib.compat import json
38 38 from rhodecode.lib.exceptions import HTTPLockedRC
39 39 from rhodecode.lib.utils2 import safe_str, datetime_to_time
40 40 from rhodecode.model.db import Repository, User
41 41
42 42
43 43 def _get_scm_size(alias, root_path):
44 44
45 45 if not alias.startswith('.'):
46 46 alias += '.'
47 47
48 48 size_scm, size_root = 0, 0
49 49 for path, dirs, files in os.walk(safe_str(root_path)):
50 50 if path.find(alias) != -1:
51 51 for f in files:
52 52 try:
53 53 size_scm += os.path.getsize(os.path.join(path, f))
54 54 except OSError:
55 55 pass
56 56 else:
57 57 for f in files:
58 58 try:
59 59 size_root += os.path.getsize(os.path.join(path, f))
60 60 except OSError:
61 61 pass
62 62
63 63 size_scm_f = h.format_byte_size(size_scm)
64 64 size_root_f = h.format_byte_size(size_root)
65 65 size_total_f = h.format_byte_size(size_root + size_scm)
66 66
67 67 return size_scm_f, size_root_f, size_total_f
68 68
69 69
70 70 def repo_size(ui, repo, hooktype=None, **kwargs):
71 71 """
72 72 Presents size of repository after push
73 73
74 74 :param ui:
75 75 :param repo:
76 76 :param hooktype:
77 77 """
78 78
79 79 size_hg_f, size_root_f, size_total_f = _get_scm_size('.hg', repo.root)
80 80
81 81 last_cs = repo[len(repo) - 1]
82 82
83 83 msg = ('Repository size .hg:%s repo:%s total:%s\n'
84 84 'Last revision is now r%s:%s\n') % (
85 85 size_hg_f, size_root_f, size_total_f, last_cs.rev(), last_cs.hex()[:12]
86 86 )
87 87
88 88 sys.stdout.write(msg)
89 89
90 90
91 91 def pre_push(ui, repo, **kwargs):
92 92 # pre push function, currently used to ban pushing when
93 93 # repository is locked
94 94 try:
95 95 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
96 96 except:
97 97 rc_extras = {}
98 98 extras = dict(repo.ui.configitems('rhodecode_extras'))
99 99
100 100 if 'username' in extras:
101 101 username = extras['username']
102 102 repository = extras['repository']
103 103 scm = extras['scm']
104 104 locked_by = extras['locked_by']
105 105 elif 'username' in rc_extras:
106 106 username = rc_extras['username']
107 107 repository = rc_extras['repository']
108 108 scm = rc_extras['scm']
109 109 locked_by = rc_extras['locked_by']
110 110 else:
111 111 raise Exception('Missing data in repo.ui and os.environ')
112 112
113 113 usr = User.get_by_username(username)
114 114 if locked_by[0] and usr.user_id != int(locked_by[0]):
115 115 locked_by = User.get(locked_by[0]).username
116 116 raise HTTPLockedRC(repository, locked_by)
117 117
118 118
119 119 def pre_pull(ui, repo, **kwargs):
120 120 # pre push function, currently used to ban pushing when
121 121 # repository is locked
122 122 try:
123 123 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
124 124 except:
125 125 rc_extras = {}
126 126 extras = dict(repo.ui.configitems('rhodecode_extras'))
127 127 if 'username' in extras:
128 128 username = extras['username']
129 129 repository = extras['repository']
130 130 scm = extras['scm']
131 131 locked_by = extras['locked_by']
132 132 elif 'username' in rc_extras:
133 133 username = rc_extras['username']
134 134 repository = rc_extras['repository']
135 135 scm = rc_extras['scm']
136 136 locked_by = rc_extras['locked_by']
137 137 else:
138 138 raise Exception('Missing data in repo.ui and os.environ')
139 139
140 140 if locked_by[0]:
141 141 locked_by = User.get(locked_by[0]).username
142 142 raise HTTPLockedRC(repository, locked_by)
143 143
144 144
145 145 def log_pull_action(ui, repo, **kwargs):
146 146 """
147 147 Logs user last pull action
148 148
149 149 :param ui:
150 150 :param repo:
151 151 """
152 152 try:
153 153 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
154 154 except:
155 155 rc_extras = {}
156 156 extras = dict(repo.ui.configitems('rhodecode_extras'))
157 157 if 'username' in extras:
158 158 username = extras['username']
159 159 repository = extras['repository']
160 160 scm = extras['scm']
161 161 make_lock = extras['make_lock']
162 162 ip = extras['ip']
163 163 elif 'username' in rc_extras:
164 164 username = rc_extras['username']
165 165 repository = rc_extras['repository']
166 166 scm = rc_extras['scm']
167 167 make_lock = rc_extras['make_lock']
168 168 ip = rc_extras['ip']
169 169 else:
170 170 raise Exception('Missing data in repo.ui and os.environ')
171 171 user = User.get_by_username(username)
172 172 action = 'pull'
173 173 action_logger(user, action, repository, ip, commit=True)
174 174 # extension hook call
175 175 from rhodecode import EXTENSIONS
176 176 callback = getattr(EXTENSIONS, 'PULL_HOOK', None)
177 177
178 178 if isfunction(callback):
179 179 kw = {}
180 180 kw.update(extras)
181 181 callback(**kw)
182 182
183 183 if make_lock is True:
184 184 Repository.lock(Repository.get_by_repo_name(repository), user.user_id)
185 185 #msg = 'Made lock on repo `%s`' % repository
186 186 #sys.stdout.write(msg)
187 187
188 188 return 0
189 189
190 190
191 191 def log_push_action(ui, repo, **kwargs):
192 192 """
193 193 Maps user last push action to new changeset id, from mercurial
194 194
195 195 :param ui:
196 196 :param repo: repo object containing the `ui` object
197 197 """
198 198
199 199 try:
200 200 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
201 201 except:
202 202 rc_extras = {}
203 203
204 204 extras = dict(repo.ui.configitems('rhodecode_extras'))
205 205 if 'username' in extras:
206 206 username = extras['username']
207 207 repository = extras['repository']
208 208 scm = extras['scm']
209 209 make_lock = extras['make_lock']
210 action = extras['action']
210 211 elif 'username' in rc_extras:
211 212 username = rc_extras['username']
212 213 repository = rc_extras['repository']
213 214 scm = rc_extras['scm']
214 215 make_lock = rc_extras['make_lock']
216 action = extras['action']
215 217 else:
216 218 raise Exception('Missing data in repo.ui and os.environ')
217 219
218 action = 'push' + ':%s'
220 action = action + ':%s'
219 221
220 222 if scm == 'hg':
221 223 node = kwargs['node']
222 224
223 225 def get_revs(repo, rev_opt):
224 226 if rev_opt:
225 227 revs = revrange(repo, rev_opt)
226 228
227 229 if len(revs) == 0:
228 230 return (nullrev, nullrev)
229 231 return (max(revs), min(revs))
230 232 else:
231 233 return (len(repo) - 1, 0)
232 234
233 235 stop, start = get_revs(repo, [node + ':'])
234 236 h = binascii.hexlify
235 237 revs = [h(repo[r].node()) for r in xrange(start, stop + 1)]
236 238 elif scm == 'git':
237 239 revs = kwargs.get('_git_revs', [])
238 240 if '_git_revs' in kwargs:
239 241 kwargs.pop('_git_revs')
240 242
241 243 action = action % ','.join(revs)
242 244
243 245 action_logger(username, action, repository, extras['ip'], commit=True)
244 246
245 247 # extension hook call
246 248 from rhodecode import EXTENSIONS
247 249 callback = getattr(EXTENSIONS, 'PUSH_HOOK', None)
248 250 if isfunction(callback):
249 251 kw = {'pushed_revs': revs}
250 252 kw.update(extras)
251 253 callback(**kw)
252 254
253 255 if make_lock is False:
254 256 Repository.unlock(Repository.get_by_repo_name(repository))
255 257 msg = 'Released lock on repo `%s`\n' % repository
256 258 sys.stdout.write(msg)
257 259
258 260 return 0
259 261
260 262
261 263 def log_create_repository(repository_dict, created_by, **kwargs):
262 264 """
263 265 Post create repository Hook. This is a dummy function for admins to re-use
264 266 if needed. It's taken from rhodecode-extensions module and executed
265 267 if present
266 268
267 269 :param repository: dict dump of repository object
268 270 :param created_by: username who created repository
269 271
270 272 available keys of repository_dict:
271 273
272 274 'repo_type',
273 275 'description',
274 276 'private',
275 277 'created_on',
276 278 'enable_downloads',
277 279 'repo_id',
278 280 'user_id',
279 281 'enable_statistics',
280 282 'clone_uri',
281 283 'fork_id',
282 284 'group_id',
283 285 'repo_name'
284 286
285 287 """
286 288 from rhodecode import EXTENSIONS
287 289 callback = getattr(EXTENSIONS, 'CREATE_REPO_HOOK', None)
288 290 if isfunction(callback):
289 291 kw = {}
290 292 kw.update(repository_dict)
291 293 kw.update({'created_by': created_by})
292 294 kw.update(kwargs)
293 295 return callback(**kw)
294 296
295 297 return 0
296 298
297 299
298 300 def log_delete_repository(repository_dict, deleted_by, **kwargs):
299 301 """
300 302 Post delete repository Hook. This is a dummy function for admins to re-use
301 303 if needed. It's taken from rhodecode-extensions module and executed
302 304 if present
303 305
304 306 :param repository: dict dump of repository object
305 307 :param deleted_by: username who deleted the repository
306 308
307 309 available keys of repository_dict:
308 310
309 311 'repo_type',
310 312 'description',
311 313 'private',
312 314 'created_on',
313 315 'enable_downloads',
314 316 'repo_id',
315 317 'user_id',
316 318 'enable_statistics',
317 319 'clone_uri',
318 320 'fork_id',
319 321 'group_id',
320 322 'repo_name'
321 323
322 324 """
323 325 from rhodecode import EXTENSIONS
324 326 callback = getattr(EXTENSIONS, 'DELETE_REPO_HOOK', None)
325 327 if isfunction(callback):
326 328 kw = {}
327 329 kw.update(repository_dict)
328 330 kw.update({'deleted_by': deleted_by,
329 331 'deleted_on': time.time()})
330 332 kw.update(kwargs)
331 333 return callback(**kw)
332 334
333 335 return 0
334 336
335 337
336 338 handle_git_pre_receive = (lambda repo_path, revs, env:
337 339 handle_git_receive(repo_path, revs, env, hook_type='pre'))
338 340 handle_git_post_receive = (lambda repo_path, revs, env:
339 341 handle_git_receive(repo_path, revs, env, hook_type='post'))
340 342
341 343
342 344 def handle_git_receive(repo_path, revs, env, hook_type='post'):
343 345 """
344 346 A really hacky method that is runned by git post-receive hook and logs
345 347 an push action together with pushed revisions. It's executed by subprocess
346 348 thus needs all info to be able to create a on the fly pylons enviroment,
347 349 connect to database and run the logging code. Hacky as sh*t but works.
348 350
349 351 :param repo_path:
350 352 :type repo_path:
351 353 :param revs:
352 354 :type revs:
353 355 :param env:
354 356 :type env:
355 357 """
356 358 from paste.deploy import appconfig
357 359 from sqlalchemy import engine_from_config
358 360 from rhodecode.config.environment import load_environment
359 361 from rhodecode.model import init_model
360 362 from rhodecode.model.db import RhodeCodeUi
361 363 from rhodecode.lib.utils import make_ui
362 364 extras = json.loads(env['RHODECODE_EXTRAS'])
363 365
364 366 path, ini_name = os.path.split(extras['config'])
365 367 conf = appconfig('config:%s' % ini_name, relative_to=path)
366 368 load_environment(conf.global_conf, conf.local_conf)
367 369
368 370 engine = engine_from_config(conf, 'sqlalchemy.db1.')
369 371 init_model(engine)
370 372
371 373 baseui = make_ui('db')
372 374 # fix if it's not a bare repo
373 375 if repo_path.endswith(os.sep + '.git'):
374 376 repo_path = repo_path[:-5]
375 377
376 378 repo = Repository.get_by_full_path(repo_path)
377 379 if not repo:
378 380 raise OSError('Repository %s not found in database'
379 381 % (safe_str(repo_path)))
380 382
381 383 _hooks = dict(baseui.configitems('hooks')) or {}
382 384
383 385 for k, v in extras.items():
384 386 baseui.setconfig('rhodecode_extras', k, v)
385 387 if hook_type == 'pre':
386 388 repo = repo.scm_instance
387 389 else:
388 390 #post push shouldn't use the cached instance never
389 391 repo = repo.scm_instance_no_cache
390 392
391 393 repo.ui = baseui
392 394
393 395 if hook_type == 'pre':
394 396 pre_push(baseui, repo)
395 397
396 398 # if push hook is enabled via web interface
397 399 elif hook_type == 'post' and _hooks.get(RhodeCodeUi.HOOK_PUSH):
398 400
399 401 rev_data = []
400 402 for l in revs:
401 403 old_rev, new_rev, ref = l.split(' ')
402 404 _ref_data = ref.split('/')
403 405 if _ref_data[1] in ['tags', 'heads']:
404 406 rev_data.append({'old_rev': old_rev,
405 407 'new_rev': new_rev,
406 408 'ref': ref,
407 409 'type': _ref_data[1],
408 410 'name': _ref_data[2].strip()})
409 411
410 412 git_revs = []
411 413 for push_ref in rev_data:
412 414 _type = push_ref['type']
413 415 if _type == 'heads':
414 416 if push_ref['old_rev'] == EmptyChangeset().raw_id:
415 417 cmd = "for-each-ref --format='%(refname)' 'refs/heads/*'"
416 418 heads = repo.run_git_command(cmd)[0]
417 419 heads = heads.replace(push_ref['ref'], '')
418 420 heads = ' '.join(map(lambda c: c.strip('\n').strip(),
419 421 heads.splitlines()))
420 422 cmd = (('log %(new_rev)s' % push_ref) +
421 423 ' --reverse --pretty=format:"%H" --not ' + heads)
422 424 git_revs += repo.run_git_command(cmd)[0].splitlines()
423 425
424 426 elif push_ref['new_rev'] == EmptyChangeset().raw_id:
425 427 #delete branch case
426 428 git_revs += ['delete_branch=>%s' % push_ref['name']]
427 429 else:
428 430 cmd = (('log %(old_rev)s..%(new_rev)s' % push_ref) +
429 431 ' --reverse --pretty=format:"%H"')
430 432 git_revs += repo.run_git_command(cmd)[0].splitlines()
431 433
432 434 elif _type == 'tags':
433 435 git_revs += ['tag=>%s' % push_ref['name']]
434 436
435 437 log_push_action(baseui, repo, _git_revs=git_revs)
@@ -1,631 +1,651 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.scm
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Scm model for RhodeCode
7 7
8 8 :created_on: Apr 9, 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 from __future__ import with_statement
26 26 import os
27 27 import re
28 28 import time
29 29 import traceback
30 30 import logging
31 31 import cStringIO
32 32 import pkg_resources
33 33 from os.path import dirname as dn, join as jn
34 34
35 35 from sqlalchemy import func
36 36 from pylons.i18n.translation import _
37 37
38 38 import rhodecode
39 39 from rhodecode.lib.vcs import get_backend
40 40 from rhodecode.lib.vcs.exceptions import RepositoryError
41 41 from rhodecode.lib.vcs.utils.lazy import LazyProperty
42 42 from rhodecode.lib.vcs.nodes import FileNode
43 43 from rhodecode.lib.vcs.backends.base import EmptyChangeset
44 44
45 45 from rhodecode import BACKENDS
46 46 from rhodecode.lib import helpers as h
47 from rhodecode.lib.utils2 import safe_str, safe_unicode
47 from rhodecode.lib.utils2 import safe_str, safe_unicode, get_server_url
48 48 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny
49 49 from rhodecode.lib.utils import get_filesystem_repos, make_ui, \
50 50 action_logger, REMOVED_REPO_PAT
51 51 from rhodecode.model import BaseModel
52 52 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
53 53 UserFollowing, UserLog, User, RepoGroup, PullRequest
54 from rhodecode.lib.hooks import log_push_action
54 55
55 56 log = logging.getLogger(__name__)
56 57
57 58
58 59 class UserTemp(object):
59 60 def __init__(self, user_id):
60 61 self.user_id = user_id
61 62
62 63 def __repr__(self):
63 64 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
64 65
65 66
66 67 class RepoTemp(object):
67 68 def __init__(self, repo_id):
68 69 self.repo_id = repo_id
69 70
70 71 def __repr__(self):
71 72 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
72 73
73 74
74 75 class CachedRepoList(object):
75 76 """
76 77 Cached repo list, uses in-memory cache after initialization, that is
77 78 super fast
78 79 """
79 80
80 81 def __init__(self, db_repo_list, repos_path, order_by=None, perm_set=None):
81 82 self.db_repo_list = db_repo_list
82 83 self.repos_path = repos_path
83 84 self.order_by = order_by
84 85 self.reversed = (order_by or '').startswith('-')
85 86 if not perm_set:
86 87 perm_set = ['repository.read', 'repository.write',
87 88 'repository.admin']
88 89 self.perm_set = perm_set
89 90
90 91 def __len__(self):
91 92 return len(self.db_repo_list)
92 93
93 94 def __repr__(self):
94 95 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
95 96
96 97 def __iter__(self):
97 98 # pre-propagated cache_map to save executing select statements
98 99 # for each repo
99 100 cache_map = CacheInvalidation.get_cache_map()
100 101
101 102 for dbr in self.db_repo_list:
102 103 scmr = dbr.scm_instance_cached(cache_map)
103 104 # check permission at this level
104 105 if not HasRepoPermissionAny(
105 106 *self.perm_set
106 107 )(dbr.repo_name, 'get repo check'):
107 108 continue
108 109
109 110 try:
110 111 last_change = scmr.last_change
111 112 tip = h.get_changeset_safe(scmr, 'tip')
112 113 except Exception:
113 114 log.error(
114 115 '%s this repository is present in database but it '
115 116 'cannot be created as an scm instance, org_exc:%s'
116 117 % (dbr.repo_name, traceback.format_exc())
117 118 )
118 119 continue
119 120
120 121 tmp_d = {}
121 122 tmp_d['name'] = dbr.repo_name
122 123 tmp_d['name_sort'] = tmp_d['name'].lower()
123 124 tmp_d['raw_name'] = tmp_d['name'].lower()
124 125 tmp_d['description'] = dbr.description
125 126 tmp_d['description_sort'] = tmp_d['description'].lower()
126 127 tmp_d['last_change'] = last_change
127 128 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
128 129 tmp_d['tip'] = tip.raw_id
129 130 tmp_d['tip_sort'] = tip.revision
130 131 tmp_d['rev'] = tip.revision
131 132 tmp_d['contact'] = dbr.user.full_contact
132 133 tmp_d['contact_sort'] = tmp_d['contact']
133 134 tmp_d['owner_sort'] = tmp_d['contact']
134 135 tmp_d['repo_archives'] = list(scmr._get_archives())
135 136 tmp_d['last_msg'] = tip.message
136 137 tmp_d['author'] = tip.author
137 138 tmp_d['dbrepo'] = dbr.get_dict()
138 139 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
139 140 yield tmp_d
140 141
141 142
142 143 class SimpleCachedRepoList(CachedRepoList):
143 144 """
144 145 Lighter version of CachedRepoList without the scm initialisation
145 146 """
146 147
147 148 def __iter__(self):
148 149 for dbr in self.db_repo_list:
149 150 # check permission at this level
150 151 if not HasRepoPermissionAny(
151 152 *self.perm_set
152 153 )(dbr.repo_name, 'get repo check'):
153 154 continue
154 155
155 156 tmp_d = {}
156 157 tmp_d['name'] = dbr.repo_name
157 158 tmp_d['name_sort'] = tmp_d['name'].lower()
158 159 tmp_d['raw_name'] = tmp_d['name'].lower()
159 160 tmp_d['description'] = dbr.description
160 161 tmp_d['description_sort'] = tmp_d['description'].lower()
161 162 tmp_d['dbrepo'] = dbr.get_dict()
162 163 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
163 164 yield tmp_d
164 165
165 166
166 167 class GroupList(object):
167 168
168 169 def __init__(self, db_repo_group_list, perm_set=None):
169 170 """
170 171 Creates iterator from given list of group objects, additionally
171 172 checking permission for them from perm_set var
172 173
173 174 :param db_repo_group_list:
174 175 :param perm_set: list of permissons to check
175 176 """
176 177 self.db_repo_group_list = db_repo_group_list
177 178 if not perm_set:
178 179 perm_set = ['group.read', 'group.write', 'group.admin']
179 180 self.perm_set = perm_set
180 181
181 182 def __len__(self):
182 183 return len(self.db_repo_group_list)
183 184
184 185 def __repr__(self):
185 186 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
186 187
187 188 def __iter__(self):
188 189 for dbgr in self.db_repo_group_list:
189 190 # check permission at this level
190 191 if not HasReposGroupPermissionAny(
191 192 *self.perm_set
192 193 )(dbgr.group_name, 'get group repo check'):
193 194 continue
194 195
195 196 yield dbgr
196 197
197 198
198 199 class ScmModel(BaseModel):
199 200 """
200 201 Generic Scm Model
201 202 """
202 203
203 204 def __get_repo(self, instance):
204 205 cls = Repository
205 206 if isinstance(instance, cls):
206 207 return instance
207 208 elif isinstance(instance, int) or safe_str(instance).isdigit():
208 209 return cls.get(instance)
209 210 elif isinstance(instance, basestring):
210 211 return cls.get_by_repo_name(instance)
211 212 elif instance:
212 213 raise Exception('given object must be int, basestr or Instance'
213 214 ' of %s got %s' % (type(cls), type(instance)))
214 215
215 216 @LazyProperty
216 217 def repos_path(self):
217 218 """
218 219 Get's the repositories root path from database
219 220 """
220 221
221 222 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
222 223
223 224 return q.ui_value
224 225
225 226 def repo_scan(self, repos_path=None):
226 227 """
227 228 Listing of repositories in given path. This path should not be a
228 229 repository itself. Return a dictionary of repository objects
229 230
230 231 :param repos_path: path to directory containing repositories
231 232 """
232 233
233 234 if repos_path is None:
234 235 repos_path = self.repos_path
235 236
236 237 log.info('scanning for repositories in %s' % repos_path)
237 238
238 239 baseui = make_ui('db')
239 240 repos = {}
240 241
241 242 for name, path in get_filesystem_repos(repos_path, recursive=True):
242 243 # name need to be decomposed and put back together using the /
243 244 # since this is internal storage separator for rhodecode
244 245 name = Repository.normalize_repo_name(name)
245 246
246 247 try:
247 248 if name in repos:
248 249 raise RepositoryError('Duplicate repository name %s '
249 250 'found in %s' % (name, path))
250 251 else:
251 252
252 253 klass = get_backend(path[0])
253 254
254 255 if path[0] == 'hg' and path[0] in BACKENDS.keys():
255 256 repos[name] = klass(safe_str(path[1]), baseui=baseui)
256 257
257 258 if path[0] == 'git' and path[0] in BACKENDS.keys():
258 259 repos[name] = klass(path[1])
259 260 except OSError:
260 261 continue
261 262 log.debug('found %s paths with repositories' % (len(repos)))
262 263 return repos
263 264
264 265 def get_repos(self, all_repos=None, sort_key=None, simple=False):
265 266 """
266 267 Get all repos from db and for each repo create it's
267 268 backend instance and fill that backed with information from database
268 269
269 270 :param all_repos: list of repository names as strings
270 271 give specific repositories list, good for filtering
271 272
272 273 :param sort_key: initial sorting of repos
273 274 :param simple: use SimpleCachedList - one without the SCM info
274 275 """
275 276 if all_repos is None:
276 277 all_repos = self.sa.query(Repository)\
277 278 .filter(Repository.group_id == None)\
278 279 .order_by(func.lower(Repository.repo_name)).all()
279 280 if simple:
280 281 repo_iter = SimpleCachedRepoList(all_repos,
281 282 repos_path=self.repos_path,
282 283 order_by=sort_key)
283 284 else:
284 285 repo_iter = CachedRepoList(all_repos,
285 286 repos_path=self.repos_path,
286 287 order_by=sort_key)
287 288
288 289 return repo_iter
289 290
290 291 def get_repos_groups(self, all_groups=None):
291 292 if all_groups is None:
292 293 all_groups = RepoGroup.query()\
293 294 .filter(RepoGroup.group_parent_id == None).all()
294 295 return [x for x in GroupList(all_groups)]
295 296
296 297 def mark_for_invalidation(self, repo_name):
297 298 """
298 299 Puts cache invalidation task into db for
299 300 further global cache invalidation
300 301
301 302 :param repo_name: this repo that should invalidation take place
302 303 """
303 304 invalidated_keys = CacheInvalidation.set_invalidate(repo_name=repo_name)
304 305 repo = Repository.get_by_repo_name(repo_name)
305 306 if repo:
306 307 repo.update_changeset_cache()
307 308 return invalidated_keys
308 309
309 310 def toggle_following_repo(self, follow_repo_id, user_id):
310 311
311 312 f = self.sa.query(UserFollowing)\
312 313 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
313 314 .filter(UserFollowing.user_id == user_id).scalar()
314 315
315 316 if f is not None:
316 317 try:
317 318 self.sa.delete(f)
318 319 action_logger(UserTemp(user_id),
319 320 'stopped_following_repo',
320 321 RepoTemp(follow_repo_id))
321 322 return
322 323 except:
323 324 log.error(traceback.format_exc())
324 325 raise
325 326
326 327 try:
327 328 f = UserFollowing()
328 329 f.user_id = user_id
329 330 f.follows_repo_id = follow_repo_id
330 331 self.sa.add(f)
331 332
332 333 action_logger(UserTemp(user_id),
333 334 'started_following_repo',
334 335 RepoTemp(follow_repo_id))
335 336 except:
336 337 log.error(traceback.format_exc())
337 338 raise
338 339
339 340 def toggle_following_user(self, follow_user_id, user_id):
340 341 f = self.sa.query(UserFollowing)\
341 342 .filter(UserFollowing.follows_user_id == follow_user_id)\
342 343 .filter(UserFollowing.user_id == user_id).scalar()
343 344
344 345 if f is not None:
345 346 try:
346 347 self.sa.delete(f)
347 348 return
348 349 except:
349 350 log.error(traceback.format_exc())
350 351 raise
351 352
352 353 try:
353 354 f = UserFollowing()
354 355 f.user_id = user_id
355 356 f.follows_user_id = follow_user_id
356 357 self.sa.add(f)
357 358 except:
358 359 log.error(traceback.format_exc())
359 360 raise
360 361
361 362 def is_following_repo(self, repo_name, user_id, cache=False):
362 363 r = self.sa.query(Repository)\
363 364 .filter(Repository.repo_name == repo_name).scalar()
364 365
365 366 f = self.sa.query(UserFollowing)\
366 367 .filter(UserFollowing.follows_repository == r)\
367 368 .filter(UserFollowing.user_id == user_id).scalar()
368 369
369 370 return f is not None
370 371
371 372 def is_following_user(self, username, user_id, cache=False):
372 373 u = User.get_by_username(username)
373 374
374 375 f = self.sa.query(UserFollowing)\
375 376 .filter(UserFollowing.follows_user == u)\
376 377 .filter(UserFollowing.user_id == user_id).scalar()
377 378
378 379 return f is not None
379 380
380 381 def get_followers(self, repo):
381 382 repo = self._get_repo(repo)
382 383
383 384 return self.sa.query(UserFollowing)\
384 385 .filter(UserFollowing.follows_repository == repo).count()
385 386
386 387 def get_forks(self, repo):
387 388 repo = self._get_repo(repo)
388 389 return self.sa.query(Repository)\
389 390 .filter(Repository.fork == repo).count()
390 391
391 392 def get_pull_requests(self, repo):
392 393 repo = self._get_repo(repo)
393 394 return self.sa.query(PullRequest)\
394 395 .filter(PullRequest.other_repo == repo).count()
395 396
396 397 def mark_as_fork(self, repo, fork, user):
397 398 repo = self.__get_repo(repo)
398 399 fork = self.__get_repo(fork)
399 400 if fork and repo.repo_id == fork.repo_id:
400 401 raise Exception("Cannot set repository as fork of itself")
401 402 repo.fork = fork
402 403 self.sa.add(repo)
403 404 return repo
404 405
406 def _handle_push(self, repo, username, action, repo_name, repo_scm, revisions):
407 from rhodecode import CONFIG
408 from rhodecode.lib.base import _get_ip_addr
409 from pylons import request
410 environ = request.environ
411
412 #trigger push hook
413 extras = {
414 'ip': _get_ip_addr(environ),
415 'username': username,
416 'action': 'push_local',
417 'repository': repo_name,
418 'scm': repo_scm,
419 'config': CONFIG['__file__'],
420 'server_url': get_server_url(environ),
421 'make_lock': None,
422 'locked_by': [None, None]
423 }
424 _scm_repo = repo._repo
425 repo.inject_ui(**extras)
426 if repo_scm == 'hg':
427 log_push_action(_scm_repo.ui, _scm_repo, node=revisions[0])
428 elif repo_scm == 'git':
429 log_push_action(_scm_repo.ui, _scm_repo, _git_revs=revisions)
430
405 431 def pull_changes(self, repo, username):
406 432 dbrepo = self.__get_repo(repo)
407 433 clone_uri = dbrepo.clone_uri
408 434 if not clone_uri:
409 435 raise Exception("This repository doesn't have a clone uri")
410 436
411 437 repo = dbrepo.scm_instance
412 from rhodecode import CONFIG
438 repo_name = dbrepo.repo_name
413 439 try:
414 extras = {
415 'ip': '',
416 'username': username,
417 'action': 'push_remote',
418 'repository': dbrepo.repo_name,
419 'scm': repo.alias,
420 'config': CONFIG['__file__'],
421 'make_lock': None,
422 'locked_by': [None, None]
423 }
424
425 Repository.inject_ui(repo, extras=extras)
426
427 440 if repo.alias == 'git':
428 441 repo.fetch(clone_uri)
429 442 else:
430 443 repo.pull(clone_uri)
431 self.mark_for_invalidation(dbrepo.repo_name)
444 self.mark_for_invalidation(repo_name)
432 445 except:
433 446 log.error(traceback.format_exc())
434 447 raise
435 448
436 449 def commit_change(self, repo, repo_name, cs, user, author, message,
437 450 content, f_path):
438 451 """
439 452 Commits changes
440 453
441 454 :param repo: SCM instance
442 455
443 456 """
444
445 457 if repo.alias == 'hg':
446 458 from rhodecode.lib.vcs.backends.hg import \
447 459 MercurialInMemoryChangeset as IMC
448 460 elif repo.alias == 'git':
449 461 from rhodecode.lib.vcs.backends.git import \
450 462 GitInMemoryChangeset as IMC
451 463
452 464 # decoding here will force that we have proper encoded values
453 465 # in any other case this will throw exceptions and deny commit
454 466 content = safe_str(content)
455 467 path = safe_str(f_path)
456 468 # message and author needs to be unicode
457 469 # proper backend should then translate that into required type
458 470 message = safe_unicode(message)
459 471 author = safe_unicode(author)
460 472 m = IMC(repo)
461 473 m.change(FileNode(path, content))
462 474 tip = m.commit(message=message,
463 475 author=author,
464 476 parents=[cs], branch=cs.branch)
465 477
466 action = 'push_local:%s' % tip.raw_id
467 action_logger(user, action, repo_name)
468 478 self.mark_for_invalidation(repo_name)
479 self._handle_push(repo,
480 username=user.username,
481 action='push_local',
482 repo_name=repo_name,
483 repo_scm=repo.alias,
484 revisions=[tip.raw_id])
469 485 return tip
470 486
471 487 def create_node(self, repo, repo_name, cs, user, author, message, content,
472 488 f_path):
473 489 if repo.alias == 'hg':
474 490 from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset as IMC
475 491 elif repo.alias == 'git':
476 492 from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset as IMC
477 493 # decoding here will force that we have proper encoded values
478 494 # in any other case this will throw exceptions and deny commit
479 495
480 496 if isinstance(content, (basestring,)):
481 497 content = safe_str(content)
482 498 elif isinstance(content, (file, cStringIO.OutputType,)):
483 499 content = content.read()
484 500 else:
485 501 raise Exception('Content is of unrecognized type %s' % (
486 502 type(content)
487 503 ))
488 504
489 505 message = safe_unicode(message)
490 506 author = safe_unicode(author)
491 507 path = safe_str(f_path)
492 508 m = IMC(repo)
493 509
494 510 if isinstance(cs, EmptyChangeset):
495 511 # EmptyChangeset means we we're editing empty repository
496 512 parents = None
497 513 else:
498 514 parents = [cs]
499 515
500 516 m.add(FileNode(path, content=content))
501 517 tip = m.commit(message=message,
502 518 author=author,
503 519 parents=parents, branch=cs.branch)
504 520
505 action = 'push_local:%s' % tip.raw_id
506 action_logger(user, action, repo_name)
507 521 self.mark_for_invalidation(repo_name)
522 self._handle_push(repo,
523 username=user.username,
524 action='push_local',
525 repo_name=repo_name,
526 repo_scm=repo.alias,
527 revisions=[tip.raw_id])
508 528 return tip
509 529
510 530 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
511 531 """
512 532 recursive walk in root dir and return a set of all path in that dir
513 533 based on repository walk function
514 534
515 535 :param repo_name: name of repository
516 536 :param revision: revision for which to list nodes
517 537 :param root_path: root path to list
518 538 :param flat: return as a list, if False returns a dict with decription
519 539
520 540 """
521 541 _files = list()
522 542 _dirs = list()
523 543 try:
524 544 _repo = self.__get_repo(repo_name)
525 545 changeset = _repo.scm_instance.get_changeset(revision)
526 546 root_path = root_path.lstrip('/')
527 547 for topnode, dirs, files in changeset.walk(root_path):
528 548 for f in files:
529 549 _files.append(f.path if flat else {"name": f.path,
530 550 "type": "file"})
531 551 for d in dirs:
532 552 _dirs.append(d.path if flat else {"name": d.path,
533 553 "type": "dir"})
534 554 except RepositoryError:
535 555 log.debug(traceback.format_exc())
536 556 raise
537 557
538 558 return _dirs, _files
539 559
540 560 def get_unread_journal(self):
541 561 return self.sa.query(UserLog).count()
542 562
543 563 def get_repo_landing_revs(self, repo=None):
544 564 """
545 565 Generates select option with tags branches and bookmarks (for hg only)
546 566 grouped by type
547 567
548 568 :param repo:
549 569 :type repo:
550 570 """
551 571
552 572 hist_l = []
553 573 choices = []
554 574 repo = self.__get_repo(repo)
555 575 hist_l.append(['tip', _('latest tip')])
556 576 choices.append('tip')
557 577 if not repo:
558 578 return choices, hist_l
559 579
560 580 repo = repo.scm_instance
561 581
562 582 branches_group = ([(k, k) for k, v in
563 583 repo.branches.iteritems()], _("Branches"))
564 584 hist_l.append(branches_group)
565 585 choices.extend([x[0] for x in branches_group[0]])
566 586
567 587 if repo.alias == 'hg':
568 588 bookmarks_group = ([(k, k) for k, v in
569 589 repo.bookmarks.iteritems()], _("Bookmarks"))
570 590 hist_l.append(bookmarks_group)
571 591 choices.extend([x[0] for x in bookmarks_group[0]])
572 592
573 593 tags_group = ([(k, k) for k, v in
574 594 repo.tags.iteritems()], _("Tags"))
575 595 hist_l.append(tags_group)
576 596 choices.extend([x[0] for x in tags_group[0]])
577 597
578 598 return choices, hist_l
579 599
580 600 def install_git_hook(self, repo, force_create=False):
581 601 """
582 602 Creates a rhodecode hook inside a git repository
583 603
584 604 :param repo: Instance of VCS repo
585 605 :param force_create: Create even if same name hook exists
586 606 """
587 607
588 608 loc = jn(repo.path, 'hooks')
589 609 if not repo.bare:
590 610 loc = jn(repo.path, '.git', 'hooks')
591 611 if not os.path.isdir(loc):
592 612 os.makedirs(loc)
593 613
594 614 tmpl_post = pkg_resources.resource_string(
595 615 'rhodecode', jn('config', 'post_receive_tmpl.py')
596 616 )
597 617 tmpl_pre = pkg_resources.resource_string(
598 618 'rhodecode', jn('config', 'pre_receive_tmpl.py')
599 619 )
600 620
601 621 for h_type, tmpl in [('pre', tmpl_pre), ('post', tmpl_post)]:
602 622 _hook_file = jn(loc, '%s-receive' % h_type)
603 623 _rhodecode_hook = False
604 624 log.debug('Installing git hook in repo %s' % repo)
605 625 if os.path.exists(_hook_file):
606 626 # let's take a look at this hook, maybe it's rhodecode ?
607 627 log.debug('hook exists, checking if it is from rhodecode')
608 628 _HOOK_VER_PAT = re.compile(r'^RC_HOOK_VER')
609 629 with open(_hook_file, 'rb') as f:
610 630 data = f.read()
611 631 matches = re.compile(r'(?:%s)\s*=\s*(.*)'
612 632 % 'RC_HOOK_VER').search(data)
613 633 if matches:
614 634 try:
615 635 ver = matches.groups()[0]
616 636 log.debug('got %s it is rhodecode' % (ver))
617 637 _rhodecode_hook = True
618 638 except:
619 639 log.error(traceback.format_exc())
620 640 else:
621 641 # there is no hook in this dir, so we want to create one
622 642 _rhodecode_hook = True
623 643
624 644 if _rhodecode_hook or force_create:
625 645 log.debug('writing %s hook file !' % h_type)
626 646 with open(_hook_file, 'wb') as f:
627 647 tmpl = tmpl.replace('_TMPL_', rhodecode.__version__)
628 648 f.write(tmpl)
629 649 os.chmod(_hook_file, 0755)
630 650 else:
631 651 log.debug('skipping writing hook file')
General Comments 0
You need to be logged in to leave comments. Login now