##// END OF EJS Templates
use raw_id in push_local action
marcink -
r2655:5a39eb37 beta
parent child Browse files
Show More
@@ -1,600 +1,595
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
44 44 from rhodecode import BACKENDS
45 45 from rhodecode.lib import helpers as h
46 46 from rhodecode.lib.utils2 import safe_str, safe_unicode
47 47 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny
48 48 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
49 49 action_logger, EmptyChangeset, REMOVED_REPO_PAT
50 50 from rhodecode.model import BaseModel
51 51 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
52 52 UserFollowing, UserLog, User, RepoGroup, PullRequest
53 53
54 54 log = logging.getLogger(__name__)
55 55
56 56
57 57 class UserTemp(object):
58 58 def __init__(self, user_id):
59 59 self.user_id = user_id
60 60
61 61 def __repr__(self):
62 62 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
63 63
64 64
65 65 class RepoTemp(object):
66 66 def __init__(self, repo_id):
67 67 self.repo_id = repo_id
68 68
69 69 def __repr__(self):
70 70 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
71 71
72 72
73 73 class CachedRepoList(object):
74 74 """
75 75 Cached repo list, uses in-memory cache after initialization, that is
76 76 super fast
77 77 """
78 78
79 79 def __init__(self, db_repo_list, repos_path, order_by=None):
80 80 self.db_repo_list = db_repo_list
81 81 self.repos_path = repos_path
82 82 self.order_by = order_by
83 83 self.reversed = (order_by or '').startswith('-')
84 84
85 85 def __len__(self):
86 86 return len(self.db_repo_list)
87 87
88 88 def __repr__(self):
89 89 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
90 90
91 91 def __iter__(self):
92 92 # pre-propagated cache_map to save executing select statements
93 93 # for each repo
94 94 cache_map = CacheInvalidation.get_cache_map()
95 95
96 96 for dbr in self.db_repo_list:
97 97 scmr = dbr.scm_instance_cached(cache_map)
98 98 # check permission at this level
99 99 if not HasRepoPermissionAny(
100 100 'repository.read', 'repository.write', 'repository.admin'
101 101 )(dbr.repo_name, 'get repo check'):
102 102 continue
103 103
104 104 if scmr is None:
105 105 log.error(
106 106 '%s this repository is present in database but it '
107 107 'cannot be created as an scm instance' % dbr.repo_name
108 108 )
109 109 continue
110 110
111 111 last_change = scmr.last_change
112 112 tip = h.get_changeset_safe(scmr, 'tip')
113 113
114 114 tmp_d = {}
115 115 tmp_d['name'] = dbr.repo_name
116 116 tmp_d['name_sort'] = tmp_d['name'].lower()
117 117 tmp_d['description'] = dbr.description
118 118 tmp_d['description_sort'] = tmp_d['description'].lower()
119 119 tmp_d['last_change'] = last_change
120 120 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
121 121 tmp_d['tip'] = tip.raw_id
122 122 tmp_d['tip_sort'] = tip.revision
123 123 tmp_d['rev'] = tip.revision
124 124 tmp_d['contact'] = dbr.user.full_contact
125 125 tmp_d['contact_sort'] = tmp_d['contact']
126 126 tmp_d['owner_sort'] = tmp_d['contact']
127 127 tmp_d['repo_archives'] = list(scmr._get_archives())
128 128 tmp_d['last_msg'] = tip.message
129 129 tmp_d['author'] = tip.author
130 130 tmp_d['dbrepo'] = dbr.get_dict()
131 131 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
132 132 yield tmp_d
133 133
134 134
135 135 class SimpleCachedRepoList(CachedRepoList):
136 136 """
137 137 Lighter version of CachedRepoList without the scm initialisation
138 138 """
139 139
140 140 def __iter__(self):
141 141 for dbr in self.db_repo_list:
142 142 # check permission at this level
143 143 if not HasRepoPermissionAny(
144 144 'repository.read', 'repository.write', 'repository.admin'
145 145 )(dbr.repo_name, 'get repo check'):
146 146 continue
147 147
148 148 tmp_d = {}
149 149 tmp_d['name'] = dbr.repo_name
150 150 tmp_d['name_sort'] = tmp_d['name'].lower()
151 151 tmp_d['description'] = dbr.description
152 152 tmp_d['description_sort'] = tmp_d['description'].lower()
153 153 tmp_d['dbrepo'] = dbr.get_dict()
154 154 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
155 155 yield tmp_d
156 156
157 157
158 158 class GroupList(object):
159 159
160 160 def __init__(self, db_repo_group_list):
161 161 self.db_repo_group_list = db_repo_group_list
162 162
163 163 def __len__(self):
164 164 return len(self.db_repo_group_list)
165 165
166 166 def __repr__(self):
167 167 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
168 168
169 169 def __iter__(self):
170 170 for dbgr in self.db_repo_group_list:
171 171 # check permission at this level
172 172 if not HasReposGroupPermissionAny(
173 173 'group.read', 'group.write', 'group.admin'
174 174 )(dbgr.group_name, 'get group repo check'):
175 175 continue
176 176
177 177 yield dbgr
178 178
179 179
180 180 class ScmModel(BaseModel):
181 181 """
182 182 Generic Scm Model
183 183 """
184 184
185 185 def __get_repo(self, instance):
186 186 cls = Repository
187 187 if isinstance(instance, cls):
188 188 return instance
189 189 elif isinstance(instance, int) or str(instance).isdigit():
190 190 return cls.get(instance)
191 191 elif isinstance(instance, basestring):
192 192 return cls.get_by_repo_name(instance)
193 193 elif instance:
194 194 raise Exception('given object must be int, basestr or Instance'
195 195 ' of %s got %s' % (type(cls), type(instance)))
196 196
197 197 @LazyProperty
198 198 def repos_path(self):
199 199 """
200 200 Get's the repositories root path from database
201 201 """
202 202
203 203 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
204 204
205 205 return q.ui_value
206 206
207 207 def repo_scan(self, repos_path=None):
208 208 """
209 209 Listing of repositories in given path. This path should not be a
210 210 repository itself. Return a dictionary of repository objects
211 211
212 212 :param repos_path: path to directory containing repositories
213 213 """
214 214
215 215 if repos_path is None:
216 216 repos_path = self.repos_path
217 217
218 218 log.info('scanning for repositories in %s' % repos_path)
219 219
220 220 baseui = make_ui('db')
221 221 repos = {}
222 222
223 223 for name, path in get_filesystem_repos(repos_path, recursive=True):
224 224 # skip removed repos
225 225 if REMOVED_REPO_PAT.match(name):
226 226 continue
227 227
228 228 # name need to be decomposed and put back together using the /
229 229 # since this is internal storage separator for rhodecode
230 230 name = Repository.url_sep().join(name.split(os.sep))
231 231
232 232 try:
233 233 if name in repos:
234 234 raise RepositoryError('Duplicate repository name %s '
235 235 'found in %s' % (name, path))
236 236 else:
237 237
238 238 klass = get_backend(path[0])
239 239
240 240 if path[0] == 'hg' and path[0] in BACKENDS.keys():
241 241 repos[name] = klass(safe_str(path[1]), baseui=baseui)
242 242
243 243 if path[0] == 'git' and path[0] in BACKENDS.keys():
244 244 repos[name] = klass(path[1])
245 245 except OSError:
246 246 continue
247 247
248 248 return repos
249 249
250 250 def get_repos(self, all_repos=None, sort_key=None, simple=False):
251 251 """
252 252 Get all repos from db and for each repo create it's
253 253 backend instance and fill that backed with information from database
254 254
255 255 :param all_repos: list of repository names as strings
256 256 give specific repositories list, good for filtering
257 257
258 258 :param sort_key: initial sorting of repos
259 259 :param simple: use SimpleCachedList - one without the SCM info
260 260 """
261 261 if all_repos is None:
262 262 all_repos = self.sa.query(Repository)\
263 263 .filter(Repository.group_id == None)\
264 264 .order_by(func.lower(Repository.repo_name)).all()
265 265 if simple:
266 266 repo_iter = SimpleCachedRepoList(all_repos,
267 267 repos_path=self.repos_path,
268 268 order_by=sort_key)
269 269 else:
270 270 repo_iter = CachedRepoList(all_repos,
271 271 repos_path=self.repos_path,
272 272 order_by=sort_key)
273 273
274 274 return repo_iter
275 275
276 276 def get_repos_groups(self, all_groups=None):
277 277 if all_groups is None:
278 278 all_groups = RepoGroup.query()\
279 279 .filter(RepoGroup.group_parent_id == None).all()
280 280 group_iter = GroupList(all_groups)
281 281
282 282 return group_iter
283 283
284 284 def mark_for_invalidation(self, repo_name):
285 285 """
286 286 Puts cache invalidation task into db for
287 287 further global cache invalidation
288 288
289 289 :param repo_name: this repo that should invalidation take place
290 290 """
291 291 CacheInvalidation.set_invalidate(repo_name)
292 292
293 293 def toggle_following_repo(self, follow_repo_id, user_id):
294 294
295 295 f = self.sa.query(UserFollowing)\
296 296 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
297 297 .filter(UserFollowing.user_id == user_id).scalar()
298 298
299 299 if f is not None:
300 300 try:
301 301 self.sa.delete(f)
302 302 action_logger(UserTemp(user_id),
303 303 'stopped_following_repo',
304 304 RepoTemp(follow_repo_id))
305 305 return
306 306 except:
307 307 log.error(traceback.format_exc())
308 308 raise
309 309
310 310 try:
311 311 f = UserFollowing()
312 312 f.user_id = user_id
313 313 f.follows_repo_id = follow_repo_id
314 314 self.sa.add(f)
315 315
316 316 action_logger(UserTemp(user_id),
317 317 'started_following_repo',
318 318 RepoTemp(follow_repo_id))
319 319 except:
320 320 log.error(traceback.format_exc())
321 321 raise
322 322
323 323 def toggle_following_user(self, follow_user_id, user_id):
324 324 f = self.sa.query(UserFollowing)\
325 325 .filter(UserFollowing.follows_user_id == follow_user_id)\
326 326 .filter(UserFollowing.user_id == user_id).scalar()
327 327
328 328 if f is not None:
329 329 try:
330 330 self.sa.delete(f)
331 331 return
332 332 except:
333 333 log.error(traceback.format_exc())
334 334 raise
335 335
336 336 try:
337 337 f = UserFollowing()
338 338 f.user_id = user_id
339 339 f.follows_user_id = follow_user_id
340 340 self.sa.add(f)
341 341 except:
342 342 log.error(traceback.format_exc())
343 343 raise
344 344
345 345 def is_following_repo(self, repo_name, user_id, cache=False):
346 346 r = self.sa.query(Repository)\
347 347 .filter(Repository.repo_name == repo_name).scalar()
348 348
349 349 f = self.sa.query(UserFollowing)\
350 350 .filter(UserFollowing.follows_repository == r)\
351 351 .filter(UserFollowing.user_id == user_id).scalar()
352 352
353 353 return f is not None
354 354
355 355 def is_following_user(self, username, user_id, cache=False):
356 356 u = User.get_by_username(username)
357 357
358 358 f = self.sa.query(UserFollowing)\
359 359 .filter(UserFollowing.follows_user == u)\
360 360 .filter(UserFollowing.user_id == user_id).scalar()
361 361
362 362 return f is not None
363 363
364 364 def get_followers(self, repo):
365 365 repo = self._get_repo(repo)
366 366
367 367 return self.sa.query(UserFollowing)\
368 368 .filter(UserFollowing.follows_repository == repo).count()
369 369
370 370 def get_forks(self, repo):
371 371 repo = self._get_repo(repo)
372 372 return self.sa.query(Repository)\
373 373 .filter(Repository.fork == repo).count()
374 374
375 375 def get_pull_requests(self, repo):
376 376 repo = self._get_repo(repo)
377 377 return self.sa.query(PullRequest)\
378 378 .filter(PullRequest.other_repo == repo).count()
379 379
380 380 def mark_as_fork(self, repo, fork, user):
381 381 repo = self.__get_repo(repo)
382 382 fork = self.__get_repo(fork)
383 383 if fork and repo.repo_id == fork.repo_id:
384 384 raise Exception("Cannot set repository as fork of itself")
385 385 repo.fork = fork
386 386 self.sa.add(repo)
387 387 return repo
388 388
389 389 def pull_changes(self, repo, username):
390 390 dbrepo = self.__get_repo(repo)
391 391 clone_uri = dbrepo.clone_uri
392 392 if not clone_uri:
393 393 raise Exception("This repository doesn't have a clone uri")
394 394
395 395 repo = dbrepo.scm_instance
396 396 try:
397 397 extras = {
398 398 'ip': '',
399 399 'username': username,
400 400 'action': 'push_remote',
401 401 'repository': dbrepo.repo_name,
402 402 'scm': repo.alias,
403 403 }
404 404 Repository.inject_ui(repo, extras=extras)
405 405
406 406 if repo.alias == 'git':
407 407 repo.fetch(clone_uri)
408 408 else:
409 409 repo.pull(clone_uri)
410 410 self.mark_for_invalidation(dbrepo.repo_name)
411 411 except:
412 412 log.error(traceback.format_exc())
413 413 raise
414 414
415 415 def commit_change(self, repo, repo_name, cs, user, author, message,
416 416 content, f_path):
417 417
418 418 if repo.alias == 'hg':
419 419 from rhodecode.lib.vcs.backends.hg import \
420 420 MercurialInMemoryChangeset as IMC
421 421 elif repo.alias == 'git':
422 422 from rhodecode.lib.vcs.backends.git import \
423 423 GitInMemoryChangeset as IMC
424 424
425 425 # decoding here will force that we have proper encoded values
426 426 # in any other case this will throw exceptions and deny commit
427 427 content = safe_str(content)
428 428 path = safe_str(f_path)
429 429 # message and author needs to be unicode
430 430 # proper backend should then translate that into required type
431 431 message = safe_unicode(message)
432 432 author = safe_unicode(author)
433 433 m = IMC(repo)
434 434 m.change(FileNode(path, content))
435 435 tip = m.commit(message=message,
436 436 author=author,
437 437 parents=[cs], branch=cs.branch)
438 438
439 new_cs = tip.short_id
440 action = 'push_local:%s' % new_cs
441
439 action = 'push_local:%s' % tip.raw_id
442 440 action_logger(user, action, repo_name)
443
444 441 self.mark_for_invalidation(repo_name)
445 442
446 443 def create_node(self, repo, repo_name, cs, user, author, message, content,
447 444 f_path):
448 445 if repo.alias == 'hg':
449 446 from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset as IMC
450 447 elif repo.alias == 'git':
451 448 from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset as IMC
452 449 # decoding here will force that we have proper encoded values
453 450 # in any other case this will throw exceptions and deny commit
454 451
455 452 if isinstance(content, (basestring,)):
456 453 content = safe_str(content)
457 454 elif isinstance(content, (file, cStringIO.OutputType,)):
458 455 content = content.read()
459 456 else:
460 457 raise Exception('Content is of unrecognized type %s' % (
461 458 type(content)
462 459 ))
463 460
464 461 message = safe_unicode(message)
465 462 author = safe_unicode(author)
466 463 path = safe_str(f_path)
467 464 m = IMC(repo)
468 465
469 466 if isinstance(cs, EmptyChangeset):
470 467 # EmptyChangeset means we we're editing empty repository
471 468 parents = None
472 469 else:
473 470 parents = [cs]
474 471
475 472 m.add(FileNode(path, content=content))
476 473 tip = m.commit(message=message,
477 474 author=author,
478 475 parents=parents, branch=cs.branch)
479 new_cs = tip.short_id
480 action = 'push_local:%s' % new_cs
481 476
477 action = 'push_local:%s' % tip.raw_id
482 478 action_logger(user, action, repo_name)
483
484 479 self.mark_for_invalidation(repo_name)
485 480
486 481 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
487 482 """
488 483 recursive walk in root dir and return a set of all path in that dir
489 484 based on repository walk function
490 485
491 486 :param repo_name: name of repository
492 487 :param revision: revision for which to list nodes
493 488 :param root_path: root path to list
494 489 :param flat: return as a list, if False returns a dict with decription
495 490
496 491 """
497 492 _files = list()
498 493 _dirs = list()
499 494 try:
500 495 _repo = self.__get_repo(repo_name)
501 496 changeset = _repo.scm_instance.get_changeset(revision)
502 497 root_path = root_path.lstrip('/')
503 498 for topnode, dirs, files in changeset.walk(root_path):
504 499 for f in files:
505 500 _files.append(f.path if flat else {"name": f.path,
506 501 "type": "file"})
507 502 for d in dirs:
508 503 _dirs.append(d.path if flat else {"name": d.path,
509 504 "type": "dir"})
510 505 except RepositoryError:
511 506 log.debug(traceback.format_exc())
512 507 raise
513 508
514 509 return _dirs, _files
515 510
516 511 def get_unread_journal(self):
517 512 return self.sa.query(UserLog).count()
518 513
519 514 def get_repo_landing_revs(self, repo=None):
520 515 """
521 516 Generates select option with tags branches and bookmarks (for hg only)
522 517 grouped by type
523 518
524 519 :param repo:
525 520 :type repo:
526 521 """
527 522
528 523 hist_l = []
529 524 choices = []
530 525 repo = self.__get_repo(repo)
531 526 hist_l.append(['tip', _('latest tip')])
532 527 choices.append('tip')
533 528 if not repo:
534 529 return choices, hist_l
535 530
536 531 repo = repo.scm_instance
537 532
538 533 branches_group = ([(k, k) for k, v in
539 534 repo.branches.iteritems()], _("Branches"))
540 535 hist_l.append(branches_group)
541 536 choices.extend([x[0] for x in branches_group[0]])
542 537
543 538 if repo.alias == 'hg':
544 539 bookmarks_group = ([(k, k) for k, v in
545 540 repo.bookmarks.iteritems()], _("Bookmarks"))
546 541 hist_l.append(bookmarks_group)
547 542 choices.extend([x[0] for x in bookmarks_group[0]])
548 543
549 544 tags_group = ([(k, k) for k, v in
550 545 repo.tags.iteritems()], _("Tags"))
551 546 hist_l.append(tags_group)
552 547 choices.extend([x[0] for x in tags_group[0]])
553 548
554 549 return choices, hist_l
555 550
556 551 def install_git_hook(self, repo, force_create=False):
557 552 """
558 553 Creates a rhodecode hook inside a git repository
559 554
560 555 :param repo: Instance of VCS repo
561 556 :param force_create: Create even if same name hook exists
562 557 """
563 558
564 559 loc = jn(repo.path, 'hooks')
565 560 if not repo.bare:
566 561 loc = jn(repo.path, '.git', 'hooks')
567 562 if not os.path.isdir(loc):
568 563 os.makedirs(loc)
569 564
570 565 tmpl = pkg_resources.resource_string(
571 566 'rhodecode', jn('config', 'post_receive_tmpl.py')
572 567 )
573 568
574 569 _hook_file = jn(loc, 'post-receive')
575 570 _rhodecode_hook = False
576 571 log.debug('Installing git hook in repo %s' % repo)
577 572 if os.path.exists(_hook_file):
578 573 # let's take a look at this hook, maybe it's rhodecode ?
579 574 log.debug('hook exists, checking if it is from rhodecode')
580 575 _HOOK_VER_PAT = re.compile(r'^RC_HOOK_VER')
581 576 with open(_hook_file, 'rb') as f:
582 577 data = f.read()
583 578 matches = re.compile(r'(?:%s)\s*=\s*(.*)'
584 579 % 'RC_HOOK_VER').search(data)
585 580 if matches:
586 581 try:
587 582 ver = matches.groups()[0]
588 583 log.debug('got %s it is rhodecode' % (ver))
589 584 _rhodecode_hook = True
590 585 except:
591 586 log.error(traceback.format_exc())
592 587
593 588 if _rhodecode_hook or force_create:
594 589 log.debug('writing hook file !')
595 590 with open(_hook_file, 'wb') as f:
596 591 tmpl = tmpl.replace('_TMPL_', rhodecode.__version__)
597 592 f.write(tmpl)
598 593 os.chmod(_hook_file, 0755)
599 594 else:
600 595 log.debug('skipping writing hook file') No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now