##// END OF EJS Templates
merge 1.3 into stable
marcink -
r2031:82a88013 merge default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
@@ -0,0 +1,34 b''
1 # celeryd - run the celeryd daemon as an upstart job for rhodecode
2 # Change variables/paths as necessary and place file /etc/init/celeryd.conf
3 # start/stop/restart as normal upstart job (ie: $ start celeryd)
4
5 description "Celery for RhodeCode Mercurial Server"
6 author "Matt Zuba <matt.zuba@goodwillaz.org"
7
8 start on starting rhodecode
9 stop on stopped rhodecode
10
11 respawn
12
13 umask 0022
14
15 env PIDFILE=/tmp/celeryd.pid
16 env APPINI=/var/hg/rhodecode/production.ini
17 env HOME=/var/hg
18 env USER=hg
19 # To use group (if different from user), you must edit sudoers file and change
20 # root's entry from (ALL) to (ALL:ALL)
21 # env GROUP=hg
22
23 script
24 COMMAND="/var/hg/.virtualenvs/rhodecode/bin/paster celeryd $APPINI --pidfile=$PIDFILE"
25 if [ -z "$GROUP" ]; then
26 exec sudo -u $USER $COMMAND
27 else
28 exec sudo -u $USER -g $GROUP $COMMAND
29 fi
30 end script
31
32 post-stop script
33 rm -f $PIDFILE
34 end script
@@ -0,0 +1,26 b''
1 # rhodecode - run the rhodecode daemon as an upstart job
2 # Change variables/paths as necessary and place file /etc/init/rhodecode.conf
3 # start/stop/restart as normal upstart job (ie: $ start rhodecode)
4
5 description "RhodeCode Mercurial Server"
6 author "Matt Zuba <matt.zuba@goodwillaz.org"
7
8 start on (local-filesystems and runlevel [2345])
9 stop on runlevel [!2345]
10
11 respawn
12
13 umask 0022
14
15 env PIDFILE=/var/hg/rhodecode/rhodecode.pid
16 env LOGFILE=/var/hg/rhodecode/log/rhodecode.log
17 env APPINI=/var/hg/rhodecode/production.ini
18 env HOME=/var/hg
19 env USER=hg
20 env GROUP=hg
21
22 exec /var/hg/.virtualenvs/rhodecode/bin/paster serve --user=$USER --group=$GROUP --pid-file=$PIDFILE --log-file=$LOGFILE $APPINI
23
24 post-stop script
25 rm -f $PIDFILE
26 end script
@@ -0,0 +1,140 b''
1 # -*- coding: utf-8 -*-
2 """
3 rhodecode.controllers.admin.notifications
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
6 notifications controller for RhodeCode
7
8 :created_on: Nov 23, 2010
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
12 """
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 #
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
22 #
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
26 import logging
27 import traceback
28
29 from pylons import request
30 from pylons import tmpl_context as c, url
31 from pylons.controllers.util import redirect
32
33 from rhodecode.lib.base import BaseController, render
34 from rhodecode.model.db import Notification
35
36 from rhodecode.model.notification import NotificationModel
37 from rhodecode.lib.auth import LoginRequired, NotAnonymous
38 from rhodecode.lib import helpers as h
39 from rhodecode.model.meta import Session
40
41
42 log = logging.getLogger(__name__)
43
44
45 class NotificationsController(BaseController):
46 """REST Controller styled on the Atom Publishing Protocol"""
47 # To properly map this controller, ensure your config/routing.py
48 # file has a resource setup:
49 # map.resource('notification', 'notifications', controller='_admin/notifications',
50 # path_prefix='/_admin', name_prefix='_admin_')
51
52 @LoginRequired()
53 @NotAnonymous()
54 def __before__(self):
55 super(NotificationsController, self).__before__()
56
57 def index(self, format='html'):
58 """GET /_admin/notifications: All items in the collection"""
59 # url('notifications')
60 c.user = self.rhodecode_user
61 c.notifications = NotificationModel()\
62 .get_for_user(self.rhodecode_user.user_id)
63 return render('admin/notifications/notifications.html')
64
65 def mark_all_read(self):
66 if request.environ.get('HTTP_X_PARTIAL_XHR'):
67 nm = NotificationModel()
68 # mark all read
69 nm.mark_all_read_for_user(self.rhodecode_user.user_id)
70 Session.commit()
71 c.user = self.rhodecode_user
72 c.notifications = nm.get_for_user(self.rhodecode_user.user_id)
73 return render('admin/notifications/notifications_data.html')
74
75 def create(self):
76 """POST /_admin/notifications: Create a new item"""
77 # url('notifications')
78
79 def new(self, format='html'):
80 """GET /_admin/notifications/new: Form to create a new item"""
81 # url('new_notification')
82
83 def update(self, notification_id):
84 """PUT /_admin/notifications/id: Update an existing item"""
85 # Forms posted to this method should contain a hidden field:
86 # <input type="hidden" name="_method" value="PUT" />
87 # Or using helpers:
88 # h.form(url('notification', notification_id=ID),
89 # method='put')
90 # url('notification', notification_id=ID)
91
92 def delete(self, notification_id):
93 """DELETE /_admin/notifications/id: Delete an existing item"""
94 # Forms posted to this method should contain a hidden field:
95 # <input type="hidden" name="_method" value="DELETE" />
96 # Or using helpers:
97 # h.form(url('notification', notification_id=ID),
98 # method='delete')
99 # url('notification', notification_id=ID)
100
101 try:
102 no = Notification.get(notification_id)
103 owner = lambda: (no.notifications_to_users.user.user_id
104 == c.rhodecode_user.user_id)
105 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
106 NotificationModel().delete(c.rhodecode_user.user_id, no)
107 Session.commit()
108 return 'ok'
109 except Exception:
110 Session.rollback()
111 log.error(traceback.format_exc())
112 return 'fail'
113
114 def show(self, notification_id, format='html'):
115 """GET /_admin/notifications/id: Show a specific item"""
116 # url('notification', notification_id=ID)
117 c.user = self.rhodecode_user
118 no = Notification.get(notification_id)
119
120 owner = lambda: (no.notifications_to_users.user.user_id
121 == c.user.user_id)
122 if no and (h.HasPermissionAny('hg.admin', 'repository.admin')() or owner):
123 unotification = NotificationModel()\
124 .get_user_notification(c.user.user_id, no)
125
126 # if this association to user is not valid, we don't want to show
127 # this message
128 if unotification:
129 if unotification.read is False:
130 unotification.mark_as_read()
131 Session.commit()
132 c.notification = no
133
134 return render('admin/notifications/show_notification.html')
135
136 return redirect(url('notifications'))
137
138 def edit(self, notification_id, format='html'):
139 """GET /_admin/notifications/id/edit: Form to edit an existing item"""
140 # url('edit_notification', notification_id=ID)
@@ -0,0 +1,57 b''
1 # -*- coding: utf-8 -*-
2 """
3 rhodecode.controllers.bookmarks
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
6 Bookmarks controller for rhodecode
7
8 :created_on: Dec 1, 2011
9 :author: marcink
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
12 """
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 #
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
22 #
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import logging
26
27 from pylons import tmpl_context as c
28
29 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
30 from rhodecode.lib.base import BaseRepoController, render
31 from rhodecode.lib.compat import OrderedDict
32 from webob.exc import HTTPNotFound
33
34 log = logging.getLogger(__name__)
35
36
37 class BookmarksController(BaseRepoController):
38
39 @LoginRequired()
40 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
41 'repository.admin')
42 def __before__(self):
43 super(BookmarksController, self).__before__()
44
45 def index(self):
46 if c.rhodecode_repo.alias != 'hg':
47 raise HTTPNotFound()
48
49 c.repo_bookmarks = OrderedDict()
50
51 bookmarks = [(name, c.rhodecode_repo.get_changeset(hash_)) for \
52 name, hash_ in c.rhodecode_repo._repo._bookmarks.items()]
53 ordered_tags = sorted(bookmarks, key=lambda x: x[1].date, reverse=True)
54 for name, cs_book in ordered_tags:
55 c.repo_bookmarks[name] = cs_book
56
57 return render('bookmarks/bookmarks.html')
@@ -0,0 +1,190 b''
1 # -*- coding: utf-8 -*-
2 """
3 rhodecode.lib.annotate
4 ~~~~~~~~~~~~~~~~~~~~~~
5
6 Anontation library for usage in rhodecode, previously part of vcs
7
8 :created_on: Dec 4, 2011
9 :author: marcink
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
12 """
13
14 from rhodecode.lib.vcs.exceptions import VCSError
15 from rhodecode.lib.vcs.nodes import FileNode
16 from pygments.formatters import HtmlFormatter
17 from pygments import highlight
18
19 import StringIO
20
21
22 def annotate_highlight(filenode, annotate_from_changeset_func=None,
23 order=None, headers=None, **options):
24 """
25 Returns html portion containing annotated table with 3 columns: line
26 numbers, changeset information and pygmentized line of code.
27
28 :param filenode: FileNode object
29 :param annotate_from_changeset_func: function taking changeset and
30 returning single annotate cell; needs break line at the end
31 :param order: ordered sequence of ``ls`` (line numbers column),
32 ``annotate`` (annotate column), ``code`` (code column); Default is
33 ``['ls', 'annotate', 'code']``
34 :param headers: dictionary with headers (keys are whats in ``order``
35 parameter)
36 """
37 options['linenos'] = True
38 formatter = AnnotateHtmlFormatter(filenode=filenode, order=order,
39 headers=headers,
40 annotate_from_changeset_func=annotate_from_changeset_func, **options)
41 lexer = filenode.lexer
42 highlighted = highlight(filenode.content, lexer, formatter)
43 return highlighted
44
45
46 class AnnotateHtmlFormatter(HtmlFormatter):
47
48 def __init__(self, filenode, annotate_from_changeset_func=None,
49 order=None, **options):
50 """
51 If ``annotate_from_changeset_func`` is passed it should be a function
52 which returns string from the given changeset. For example, we may pass
53 following function as ``annotate_from_changeset_func``::
54
55 def changeset_to_anchor(changeset):
56 return '<a href="/changesets/%s/">%s</a>\n' %\
57 (changeset.id, changeset.id)
58
59 :param annotate_from_changeset_func: see above
60 :param order: (default: ``['ls', 'annotate', 'code']``); order of
61 columns;
62 :param options: standard pygment's HtmlFormatter options, there is
63 extra option tough, ``headers``. For instance we can pass::
64
65 formatter = AnnotateHtmlFormatter(filenode, headers={
66 'ls': '#',
67 'annotate': 'Annotate',
68 'code': 'Code',
69 })
70
71 """
72 super(AnnotateHtmlFormatter, self).__init__(**options)
73 self.annotate_from_changeset_func = annotate_from_changeset_func
74 self.order = order or ('ls', 'annotate', 'code')
75 headers = options.pop('headers', None)
76 if headers and not ('ls' in headers and 'annotate' in headers and
77 'code' in headers):
78 raise ValueError("If headers option dict is specified it must "
79 "all 'ls', 'annotate' and 'code' keys")
80 self.headers = headers
81 if isinstance(filenode, FileNode):
82 self.filenode = filenode
83 else:
84 raise VCSError("This formatter expect FileNode parameter, not %r"
85 % type(filenode))
86
87 def annotate_from_changeset(self, changeset):
88 """
89 Returns full html line for single changeset per annotated line.
90 """
91 if self.annotate_from_changeset_func:
92 return self.annotate_from_changeset_func(changeset)
93 else:
94 return ''.join((changeset.id, '\n'))
95
96 def _wrap_tablelinenos(self, inner):
97 dummyoutfile = StringIO.StringIO()
98 lncount = 0
99 for t, line in inner:
100 if t:
101 lncount += 1
102 dummyoutfile.write(line)
103
104 fl = self.linenostart
105 mw = len(str(lncount + fl - 1))
106 sp = self.linenospecial
107 st = self.linenostep
108 la = self.lineanchors
109 aln = self.anchorlinenos
110 if sp:
111 lines = []
112
113 for i in range(fl, fl + lncount):
114 if i % st == 0:
115 if i % sp == 0:
116 if aln:
117 lines.append('<a href="#%s-%d" class="special">'
118 '%*d</a>' %
119 (la, i, mw, i))
120 else:
121 lines.append('<span class="special">'
122 '%*d</span>' % (mw, i))
123 else:
124 if aln:
125 lines.append('<a href="#%s-%d">'
126 '%*d</a>' % (la, i, mw, i))
127 else:
128 lines.append('%*d' % (mw, i))
129 else:
130 lines.append('')
131 ls = '\n'.join(lines)
132 else:
133 lines = []
134 for i in range(fl, fl + lncount):
135 if i % st == 0:
136 if aln:
137 lines.append('<a href="#%s-%d">%*d</a>' \
138 % (la, i, mw, i))
139 else:
140 lines.append('%*d' % (mw, i))
141 else:
142 lines.append('')
143 ls = '\n'.join(lines)
144
145 annotate_changesets = [tup[1] for tup in self.filenode.annotate]
146 # If pygments cropped last lines break we need do that too
147 ln_cs = len(annotate_changesets)
148 ln_ = len(ls.splitlines())
149 if ln_cs > ln_:
150 annotate_changesets = annotate_changesets[:ln_ - ln_cs]
151 annotate = ''.join((self.annotate_from_changeset(changeset)
152 for changeset in annotate_changesets))
153 # in case you wonder about the seemingly redundant <div> here:
154 # since the content in the other cell also is wrapped in a div,
155 # some browsers in some configurations seem to mess up the formatting.
156 '''
157 yield 0, ('<table class="%stable">' % self.cssclass +
158 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
159 ls + '</pre></div></td>' +
160 '<td class="code">')
161 yield 0, dummyoutfile.getvalue()
162 yield 0, '</td></tr></table>'
163
164 '''
165 headers_row = []
166 if self.headers:
167 headers_row = ['<tr class="annotate-header">']
168 for key in self.order:
169 td = ''.join(('<td>', self.headers[key], '</td>'))
170 headers_row.append(td)
171 headers_row.append('</tr>')
172
173 body_row_start = ['<tr>']
174 for key in self.order:
175 if key == 'ls':
176 body_row_start.append(
177 '<td class="linenos"><div class="linenodiv"><pre>' +
178 ls + '</pre></div></td>')
179 elif key == 'annotate':
180 body_row_start.append(
181 '<td class="annotate"><div class="annotatediv"><pre>' +
182 annotate + '</pre></div></td>')
183 elif key == 'code':
184 body_row_start.append('<td class="code">')
185 yield 0, ('<table class="%stable">' % self.cssclass +
186 ''.join(headers_row) +
187 ''.join(body_row_start)
188 )
189 yield 0, dummyoutfile.getvalue()
190 yield 0, '</td></tr></table>'
@@ -0,0 +1,14 b''
1 # -*- coding: utf-8 -*-
2 """
3 rhodecode.lib.dbmigrate.schema
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
6
7 Schemas for migrations
8
9
10 :created_on: Nov 1, 2011
11 :author: marcink
12 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
13 :license: <name>, see LICENSE_FILE for more details.
14 """
@@ -0,0 +1,94 b''
1 from sqlalchemy import *
2 from sqlalchemy.exc import DatabaseError
3 from sqlalchemy.orm import relation, backref, class_mapper
4 from sqlalchemy.orm.session import Session
5 from rhodecode.model.meta import Base
6
7 class BaseModel(object):
8 """Base Model for all classess
9
10 """
11
12 @classmethod
13 def _get_keys(cls):
14 """return column names for this model """
15 return class_mapper(cls).c.keys()
16
17 def get_dict(self):
18 """return dict with keys and values corresponding
19 to this model data """
20
21 d = {}
22 for k in self._get_keys():
23 d[k] = getattr(self, k)
24 return d
25
26 def get_appstruct(self):
27 """return list with keys and values tupples corresponding
28 to this model data """
29
30 l = []
31 for k in self._get_keys():
32 l.append((k, getattr(self, k),))
33 return l
34
35 def populate_obj(self, populate_dict):
36 """populate model with data from given populate_dict"""
37
38 for k in self._get_keys():
39 if k in populate_dict:
40 setattr(self, k, populate_dict[k])
41
42 @classmethod
43 def query(cls):
44 return Session.query(cls)
45
46 @classmethod
47 def get(cls, id_):
48 if id_:
49 return cls.query().get(id_)
50
51 @classmethod
52 def getAll(cls):
53 return cls.query().all()
54
55 @classmethod
56 def delete(cls, id_):
57 obj = cls.query().get(id_)
58 Session.delete(obj)
59 Session.commit()
60
61
62 class UserFollowing(Base, BaseModel):
63 __tablename__ = 'user_followings'
64 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
65 UniqueConstraint('user_id', 'follows_user_id')
66 , {'useexisting':True})
67
68 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
69 user_id = Column("user_id", Integer(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
70 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey(u'repositories.repo_id'), nullable=True, unique=None, default=None)
71 follows_user_id = Column("follows_user_id", Integer(), ForeignKey(u'users.user_id'), nullable=True, unique=None, default=None)
72
73 user = relation('User', primaryjoin='User.user_id==UserFollowing.user_id')
74
75 follows_user = relation('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
76 follows_repository = relation('Repository')
77
78
79 class CacheInvalidation(Base, BaseModel):
80 __tablename__ = 'cache_invalidation'
81 __table_args__ = (UniqueConstraint('cache_key'), {'useexisting':True})
82 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
83 cache_key = Column("cache_key", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
84 cache_args = Column("cache_args", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
85 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
86
87
88 def __init__(self, cache_key, cache_args=''):
89 self.cache_key = cache_key
90 self.cache_args = cache_args
91 self.cache_active = False
92
93 def __repr__(self):
94 return "<CacheInvalidation('%s:%s')>" % (self.cache_id, self.cache_key)
This diff has been collapsed as it changes many lines, (1098 lines changed) Show them Hide them
@@ -0,0 +1,1098 b''
1 # -*- coding: utf-8 -*-
2 """
3 rhodecode.model.db
4 ~~~~~~~~~~~~~~~~~~
5
6 Database Models for RhodeCode
7
8 :created_on: Apr 08, 2010
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
12 """
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 #
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
22 #
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
26 import os
27 import logging
28 import datetime
29 import traceback
30 from datetime import date
31
32 from sqlalchemy import *
33 from sqlalchemy.ext.hybrid import hybrid_property
34 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
35 from beaker.cache import cache_region, region_invalidate
36
37 from rhodecode.lib.vcs import get_backend
38 from rhodecode.lib.vcs.utils.helpers import get_scm
39 from rhodecode.lib.vcs.exceptions import VCSError
40 from rhodecode.lib.vcs.utils.lazy import LazyProperty
41
42 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, \
43 generate_api_key, safe_unicode
44 from rhodecode.lib.exceptions import UsersGroupsAssignedException
45 from rhodecode.lib.compat import json
46
47 from rhodecode.model.meta import Base, Session
48 from rhodecode.lib.caching_query import FromCache
49
50
51 log = logging.getLogger(__name__)
52
53 #==============================================================================
54 # BASE CLASSES
55 #==============================================================================
56
57 class ModelSerializer(json.JSONEncoder):
58 """
59 Simple Serializer for JSON,
60
61 usage::
62
63 to make object customized for serialization implement a __json__
64 method that will return a dict for serialization into json
65
66 example::
67
68 class Task(object):
69
70 def __init__(self, name, value):
71 self.name = name
72 self.value = value
73
74 def __json__(self):
75 return dict(name=self.name,
76 value=self.value)
77
78 """
79
80 def default(self, obj):
81
82 if hasattr(obj, '__json__'):
83 return obj.__json__()
84 else:
85 return json.JSONEncoder.default(self, obj)
86
87 class BaseModel(object):
88 """Base Model for all classess
89
90 """
91
92 @classmethod
93 def _get_keys(cls):
94 """return column names for this model """
95 return class_mapper(cls).c.keys()
96
97 def get_dict(self):
98 """return dict with keys and values corresponding
99 to this model data """
100
101 d = {}
102 for k in self._get_keys():
103 d[k] = getattr(self, k)
104 return d
105
106 def get_appstruct(self):
107 """return list with keys and values tupples corresponding
108 to this model data """
109
110 l = []
111 for k in self._get_keys():
112 l.append((k, getattr(self, k),))
113 return l
114
115 def populate_obj(self, populate_dict):
116 """populate model with data from given populate_dict"""
117
118 for k in self._get_keys():
119 if k in populate_dict:
120 setattr(self, k, populate_dict[k])
121
122 @classmethod
123 def query(cls):
124 return Session.query(cls)
125
126 @classmethod
127 def get(cls, id_):
128 if id_:
129 return cls.query().get(id_)
130
131 @classmethod
132 def getAll(cls):
133 return cls.query().all()
134
135 @classmethod
136 def delete(cls, id_):
137 obj = cls.query().get(id_)
138 Session.delete(obj)
139 Session.commit()
140
141
142 class RhodeCodeSetting(Base, BaseModel):
143 __tablename__ = 'rhodecode_settings'
144 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
145 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
146 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
147 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
148
149 def __init__(self, k='', v=''):
150 self.app_settings_name = k
151 self.app_settings_value = v
152
153
154 @validates('_app_settings_value')
155 def validate_settings_value(self, key, val):
156 assert type(val) == unicode
157 return val
158
159 @hybrid_property
160 def app_settings_value(self):
161 v = self._app_settings_value
162 if v == 'ldap_active':
163 v = str2bool(v)
164 return v
165
166 @app_settings_value.setter
167 def app_settings_value(self, val):
168 """
169 Setter that will always make sure we use unicode in app_settings_value
170
171 :param val:
172 """
173 self._app_settings_value = safe_unicode(val)
174
175 def __repr__(self):
176 return "<%s('%s:%s')>" % (self.__class__.__name__,
177 self.app_settings_name, self.app_settings_value)
178
179
180 @classmethod
181 def get_by_name(cls, ldap_key):
182 return cls.query()\
183 .filter(cls.app_settings_name == ldap_key).scalar()
184
185 @classmethod
186 def get_app_settings(cls, cache=False):
187
188 ret = cls.query()
189
190 if cache:
191 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
192
193 if not ret:
194 raise Exception('Could not get application settings !')
195 settings = {}
196 for each in ret:
197 settings['rhodecode_' + each.app_settings_name] = \
198 each.app_settings_value
199
200 return settings
201
202 @classmethod
203 def get_ldap_settings(cls, cache=False):
204 ret = cls.query()\
205 .filter(cls.app_settings_name.startswith('ldap_')).all()
206 fd = {}
207 for row in ret:
208 fd.update({row.app_settings_name:row.app_settings_value})
209
210 return fd
211
212
213 class RhodeCodeUi(Base, BaseModel):
214 __tablename__ = 'rhodecode_ui'
215 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
216
217 HOOK_UPDATE = 'changegroup.update'
218 HOOK_REPO_SIZE = 'changegroup.repo_size'
219 HOOK_PUSH = 'pretxnchangegroup.push_logger'
220 HOOK_PULL = 'preoutgoing.pull_logger'
221
222 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
223 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
224 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
225 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
226 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
227
228
229 @classmethod
230 def get_by_key(cls, key):
231 return cls.query().filter(cls.ui_key == key)
232
233
234 @classmethod
235 def get_builtin_hooks(cls):
236 q = cls.query()
237 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
238 cls.HOOK_REPO_SIZE,
239 cls.HOOK_PUSH, cls.HOOK_PULL]))
240 return q.all()
241
242 @classmethod
243 def get_custom_hooks(cls):
244 q = cls.query()
245 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
246 cls.HOOK_REPO_SIZE,
247 cls.HOOK_PUSH, cls.HOOK_PULL]))
248 q = q.filter(cls.ui_section == 'hooks')
249 return q.all()
250
251 @classmethod
252 def create_or_update_hook(cls, key, val):
253 new_ui = cls.get_by_key(key).scalar() or cls()
254 new_ui.ui_section = 'hooks'
255 new_ui.ui_active = True
256 new_ui.ui_key = key
257 new_ui.ui_value = val
258
259 Session.add(new_ui)
260 Session.commit()
261
262
263 class User(Base, BaseModel):
264 __tablename__ = 'users'
265 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
266 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
267 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
268 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
269 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
270 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
271 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
272 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
273 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
274 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
275 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
276 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
277
278 user_log = relationship('UserLog', cascade='all')
279 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
280
281 repositories = relationship('Repository')
282 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
283 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
284
285 group_member = relationship('UsersGroupMember', cascade='all')
286
287 @property
288 def full_contact(self):
289 return '%s %s <%s>' % (self.name, self.lastname, self.email)
290
291 @property
292 def short_contact(self):
293 return '%s %s' % (self.name, self.lastname)
294
295 @property
296 def is_admin(self):
297 return self.admin
298
299 def __repr__(self):
300 try:
301 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
302 self.user_id, self.username)
303 except:
304 return self.__class__.__name__
305
306 @classmethod
307 def get_by_username(cls, username, case_insensitive=False):
308 if case_insensitive:
309 return Session.query(cls).filter(cls.username.ilike(username)).scalar()
310 else:
311 return Session.query(cls).filter(cls.username == username).scalar()
312
313 @classmethod
314 def get_by_api_key(cls, api_key):
315 return cls.query().filter(cls.api_key == api_key).one()
316
317 def update_lastlogin(self):
318 """Update user lastlogin"""
319
320 self.last_login = datetime.datetime.now()
321 Session.add(self)
322 Session.commit()
323 log.debug('updated user %s lastlogin' % self.username)
324
325 @classmethod
326 def create(cls, form_data):
327 from rhodecode.lib.auth import get_crypt_password
328
329 try:
330 new_user = cls()
331 for k, v in form_data.items():
332 if k == 'password':
333 v = get_crypt_password(v)
334 setattr(new_user, k, v)
335
336 new_user.api_key = generate_api_key(form_data['username'])
337 Session.add(new_user)
338 Session.commit()
339 return new_user
340 except:
341 log.error(traceback.format_exc())
342 Session.rollback()
343 raise
344
345 class UserLog(Base, BaseModel):
346 __tablename__ = 'user_logs'
347 __table_args__ = {'extend_existing':True}
348 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
349 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
350 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
351 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
352 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
353 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
354 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
355
356 @property
357 def action_as_day(self):
358 return date(*self.action_date.timetuple()[:3])
359
360 user = relationship('User')
361 repository = relationship('Repository')
362
363
364 class UsersGroup(Base, BaseModel):
365 __tablename__ = 'users_groups'
366 __table_args__ = {'extend_existing':True}
367
368 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
369 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
370 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
371
372 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
373
374 def __repr__(self):
375 return '<userGroup(%s)>' % (self.users_group_name)
376
377 @classmethod
378 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
379 if case_insensitive:
380 gr = cls.query()\
381 .filter(cls.users_group_name.ilike(group_name))
382 else:
383 gr = cls.query()\
384 .filter(cls.users_group_name == group_name)
385 if cache:
386 gr = gr.options(FromCache("sql_cache_short",
387 "get_user_%s" % group_name))
388 return gr.scalar()
389
390
391 @classmethod
392 def get(cls, users_group_id, cache=False):
393 users_group = cls.query()
394 if cache:
395 users_group = users_group.options(FromCache("sql_cache_short",
396 "get_users_group_%s" % users_group_id))
397 return users_group.get(users_group_id)
398
399 @classmethod
400 def create(cls, form_data):
401 try:
402 new_users_group = cls()
403 for k, v in form_data.items():
404 setattr(new_users_group, k, v)
405
406 Session.add(new_users_group)
407 Session.commit()
408 return new_users_group
409 except:
410 log.error(traceback.format_exc())
411 Session.rollback()
412 raise
413
414 @classmethod
415 def update(cls, users_group_id, form_data):
416
417 try:
418 users_group = cls.get(users_group_id, cache=False)
419
420 for k, v in form_data.items():
421 if k == 'users_group_members':
422 users_group.members = []
423 Session.flush()
424 members_list = []
425 if v:
426 v = [v] if isinstance(v, basestring) else v
427 for u_id in set(v):
428 member = UsersGroupMember(users_group_id, u_id)
429 members_list.append(member)
430 setattr(users_group, 'members', members_list)
431 setattr(users_group, k, v)
432
433 Session.add(users_group)
434 Session.commit()
435 except:
436 log.error(traceback.format_exc())
437 Session.rollback()
438 raise
439
440 @classmethod
441 def delete(cls, users_group_id):
442 try:
443
444 # check if this group is not assigned to repo
445 assigned_groups = UsersGroupRepoToPerm.query()\
446 .filter(UsersGroupRepoToPerm.users_group_id ==
447 users_group_id).all()
448
449 if assigned_groups:
450 raise UsersGroupsAssignedException('RepoGroup assigned to %s' %
451 assigned_groups)
452
453 users_group = cls.get(users_group_id, cache=False)
454 Session.delete(users_group)
455 Session.commit()
456 except:
457 log.error(traceback.format_exc())
458 Session.rollback()
459 raise
460
461 class UsersGroupMember(Base, BaseModel):
462 __tablename__ = 'users_groups_members'
463 __table_args__ = {'extend_existing':True}
464
465 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
466 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
467 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
468
469 user = relationship('User', lazy='joined')
470 users_group = relationship('UsersGroup')
471
472 def __init__(self, gr_id='', u_id=''):
473 self.users_group_id = gr_id
474 self.user_id = u_id
475
476 @staticmethod
477 def add_user_to_group(group, user):
478 ugm = UsersGroupMember()
479 ugm.users_group = group
480 ugm.user = user
481 Session.add(ugm)
482 Session.commit()
483 return ugm
484
485 class Repository(Base, BaseModel):
486 __tablename__ = 'repositories'
487 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
488
489 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
490 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
491 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
492 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
493 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
494 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
495 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
496 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
497 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
498 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
499
500 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
501 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
502
503
504 user = relationship('User')
505 fork = relationship('Repository', remote_side=repo_id)
506 group = relationship('RepoGroup')
507 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
508 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
509 stats = relationship('Statistics', cascade='all', uselist=False)
510
511 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
512
513 logs = relationship('UserLog', cascade='all')
514
515 def __repr__(self):
516 return "<%s('%s:%s')>" % (self.__class__.__name__,
517 self.repo_id, self.repo_name)
518
519 @classmethod
520 def url_sep(cls):
521 return '/'
522
523 @classmethod
524 def get_by_repo_name(cls, repo_name):
525 q = Session.query(cls).filter(cls.repo_name == repo_name)
526 q = q.options(joinedload(Repository.fork))\
527 .options(joinedload(Repository.user))\
528 .options(joinedload(Repository.group))
529 return q.one()
530
531 @classmethod
532 def get_repo_forks(cls, repo_id):
533 return cls.query().filter(Repository.fork_id == repo_id)
534
535 @classmethod
536 def base_path(cls):
537 """
538 Returns base path when all repos are stored
539
540 :param cls:
541 """
542 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
543 cls.url_sep())
544 q.options(FromCache("sql_cache_short", "repository_repo_path"))
545 return q.one().ui_value
546
547 @property
548 def just_name(self):
549 return self.repo_name.split(Repository.url_sep())[-1]
550
551 @property
552 def groups_with_parents(self):
553 groups = []
554 if self.group is None:
555 return groups
556
557 cur_gr = self.group
558 groups.insert(0, cur_gr)
559 while 1:
560 gr = getattr(cur_gr, 'parent_group', None)
561 cur_gr = cur_gr.parent_group
562 if gr is None:
563 break
564 groups.insert(0, gr)
565
566 return groups
567
568 @property
569 def groups_and_repo(self):
570 return self.groups_with_parents, self.just_name
571
572 @LazyProperty
573 def repo_path(self):
574 """
575 Returns base full path for that repository means where it actually
576 exists on a filesystem
577 """
578 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
579 Repository.url_sep())
580 q.options(FromCache("sql_cache_short", "repository_repo_path"))
581 return q.one().ui_value
582
583 @property
584 def repo_full_path(self):
585 p = [self.repo_path]
586 # we need to split the name by / since this is how we store the
587 # names in the database, but that eventually needs to be converted
588 # into a valid system path
589 p += self.repo_name.split(Repository.url_sep())
590 return os.path.join(*p)
591
592 def get_new_name(self, repo_name):
593 """
594 returns new full repository name based on assigned group and new new
595
596 :param group_name:
597 """
598 path_prefix = self.group.full_path_splitted if self.group else []
599 return Repository.url_sep().join(path_prefix + [repo_name])
600
601 @property
602 def _ui(self):
603 """
604 Creates an db based ui object for this repository
605 """
606 from mercurial import ui
607 from mercurial import config
608 baseui = ui.ui()
609
610 #clean the baseui object
611 baseui._ocfg = config.config()
612 baseui._ucfg = config.config()
613 baseui._tcfg = config.config()
614
615
616 ret = RhodeCodeUi.query()\
617 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
618
619 hg_ui = ret
620 for ui_ in hg_ui:
621 if ui_.ui_active:
622 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
623 ui_.ui_key, ui_.ui_value)
624 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
625
626 return baseui
627
628 @classmethod
629 def is_valid(cls, repo_name):
630 """
631 returns True if given repo name is a valid filesystem repository
632
633 :param cls:
634 :param repo_name:
635 """
636 from rhodecode.lib.utils import is_valid_repo
637
638 return is_valid_repo(repo_name, cls.base_path())
639
640
641 #==========================================================================
642 # SCM PROPERTIES
643 #==========================================================================
644
645 def get_changeset(self, rev):
646 return get_changeset_safe(self.scm_instance, rev)
647
648 @property
649 def tip(self):
650 return self.get_changeset('tip')
651
652 @property
653 def author(self):
654 return self.tip.author
655
656 @property
657 def last_change(self):
658 return self.scm_instance.last_change
659
660 #==========================================================================
661 # SCM CACHE INSTANCE
662 #==========================================================================
663
664 @property
665 def invalidate(self):
666 return CacheInvalidation.invalidate(self.repo_name)
667
668 def set_invalidate(self):
669 """
670 set a cache for invalidation for this instance
671 """
672 CacheInvalidation.set_invalidate(self.repo_name)
673
674 @LazyProperty
675 def scm_instance(self):
676 return self.__get_instance()
677
678 @property
679 def scm_instance_cached(self):
680 @cache_region('long_term')
681 def _c(repo_name):
682 return self.__get_instance()
683 rn = self.repo_name
684
685 inv = self.invalidate
686 if inv is not None:
687 region_invalidate(_c, None, rn)
688 # update our cache
689 CacheInvalidation.set_valid(inv.cache_key)
690 return _c(rn)
691
692 def __get_instance(self):
693
694 repo_full_path = self.repo_full_path
695
696 try:
697 alias = get_scm(repo_full_path)[0]
698 log.debug('Creating instance of %s repository' % alias)
699 backend = get_backend(alias)
700 except VCSError:
701 log.error(traceback.format_exc())
702 log.error('Perhaps this repository is in db and not in '
703 'filesystem run rescan repositories with '
704 '"destroy old data " option from admin panel')
705 return
706
707 if alias == 'hg':
708
709 repo = backend(safe_str(repo_full_path), create=False,
710 baseui=self._ui)
711 # skip hidden web repository
712 if repo._get_hidden():
713 return
714 else:
715 repo = backend(repo_full_path, create=False)
716
717 return repo
718
719
720 class RepoGroup(Base, BaseModel):
721 __tablename__ = 'groups'
722 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
723 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
724 __mapper_args__ = {'order_by':'group_name'}
725
726 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
727 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
728 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
729 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
730
731 parent_group = relationship('RepoGroup', remote_side=group_id)
732
733
734 def __init__(self, group_name='', parent_group=None):
735 self.group_name = group_name
736 self.parent_group = parent_group
737
738 def __repr__(self):
739 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
740 self.group_name)
741
742 @classmethod
743 def groups_choices(cls):
744 from webhelpers.html import literal as _literal
745 repo_groups = [('', '')]
746 sep = ' &raquo; '
747 _name = lambda k: _literal(sep.join(k))
748
749 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
750 for x in cls.query().all()])
751
752 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
753 return repo_groups
754
755 @classmethod
756 def url_sep(cls):
757 return '/'
758
759 @classmethod
760 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
761 if case_insensitive:
762 gr = cls.query()\
763 .filter(cls.group_name.ilike(group_name))
764 else:
765 gr = cls.query()\
766 .filter(cls.group_name == group_name)
767 if cache:
768 gr = gr.options(FromCache("sql_cache_short",
769 "get_group_%s" % group_name))
770 return gr.scalar()
771
772 @property
773 def parents(self):
774 parents_recursion_limit = 5
775 groups = []
776 if self.parent_group is None:
777 return groups
778 cur_gr = self.parent_group
779 groups.insert(0, cur_gr)
780 cnt = 0
781 while 1:
782 cnt += 1
783 gr = getattr(cur_gr, 'parent_group', None)
784 cur_gr = cur_gr.parent_group
785 if gr is None:
786 break
787 if cnt == parents_recursion_limit:
788 # this will prevent accidental infinit loops
789 log.error('group nested more than %s' %
790 parents_recursion_limit)
791 break
792
793 groups.insert(0, gr)
794 return groups
795
796 @property
797 def children(self):
798 return Group.query().filter(Group.parent_group == self)
799
800 @property
801 def name(self):
802 return self.group_name.split(Group.url_sep())[-1]
803
804 @property
805 def full_path(self):
806 return self.group_name
807
808 @property
809 def full_path_splitted(self):
810 return self.group_name.split(Group.url_sep())
811
812 @property
813 def repositories(self):
814 return Repository.query().filter(Repository.group == self)
815
816 @property
817 def repositories_recursive_count(self):
818 cnt = self.repositories.count()
819
820 def children_count(group):
821 cnt = 0
822 for child in group.children:
823 cnt += child.repositories.count()
824 cnt += children_count(child)
825 return cnt
826
827 return cnt + children_count(self)
828
829
830 def get_new_name(self, group_name):
831 """
832 returns new full group name based on parent and new name
833
834 :param group_name:
835 """
836 path_prefix = (self.parent_group.full_path_splitted if
837 self.parent_group else [])
838 return Group.url_sep().join(path_prefix + [group_name])
839
840
841 class Permission(Base, BaseModel):
842 __tablename__ = 'permissions'
843 __table_args__ = {'extend_existing':True}
844 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
845 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
846 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
847
848 def __repr__(self):
849 return "<%s('%s:%s')>" % (self.__class__.__name__,
850 self.permission_id, self.permission_name)
851
852 @classmethod
853 def get_by_key(cls, key):
854 return cls.query().filter(cls.permission_name == key).scalar()
855
856 class UserRepoToPerm(Base, BaseModel):
857 __tablename__ = 'repo_to_perm'
858 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
859 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
860 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
861 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
862 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
863
864 user = relationship('User')
865 permission = relationship('Permission')
866 repository = relationship('Repository')
867
868 class UserToPerm(Base, BaseModel):
869 __tablename__ = 'user_to_perm'
870 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
871 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
872 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
873 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
874
875 user = relationship('User')
876 permission = relationship('Permission')
877
878 @classmethod
879 def has_perm(cls, user_id, perm):
880 if not isinstance(perm, Permission):
881 raise Exception('perm needs to be an instance of Permission class')
882
883 return cls.query().filter(cls.user_id == user_id)\
884 .filter(cls.permission == perm).scalar() is not None
885
886 @classmethod
887 def grant_perm(cls, user_id, perm):
888 if not isinstance(perm, Permission):
889 raise Exception('perm needs to be an instance of Permission class')
890
891 new = cls()
892 new.user_id = user_id
893 new.permission = perm
894 try:
895 Session.add(new)
896 Session.commit()
897 except:
898 Session.rollback()
899
900
901 @classmethod
902 def revoke_perm(cls, user_id, perm):
903 if not isinstance(perm, Permission):
904 raise Exception('perm needs to be an instance of Permission class')
905
906 try:
907 cls.query().filter(cls.user_id == user_id)\
908 .filter(cls.permission == perm).delete()
909 Session.commit()
910 except:
911 Session.rollback()
912
913 class UsersGroupRepoToPerm(Base, BaseModel):
914 __tablename__ = 'users_group_repo_to_perm'
915 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
916 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
917 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
918 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
919 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
920
921 users_group = relationship('UsersGroup')
922 permission = relationship('Permission')
923 repository = relationship('Repository')
924
925 def __repr__(self):
926 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
927
928 class UsersGroupToPerm(Base, BaseModel):
929 __tablename__ = 'users_group_to_perm'
930 __table_args__ = {'extend_existing':True}
931 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
932 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
933 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
934
935 users_group = relationship('UsersGroup')
936 permission = relationship('Permission')
937
938
939 @classmethod
940 def has_perm(cls, users_group_id, perm):
941 if not isinstance(perm, Permission):
942 raise Exception('perm needs to be an instance of Permission class')
943
944 return cls.query().filter(cls.users_group_id ==
945 users_group_id)\
946 .filter(cls.permission == perm)\
947 .scalar() is not None
948
949 @classmethod
950 def grant_perm(cls, users_group_id, perm):
951 if not isinstance(perm, Permission):
952 raise Exception('perm needs to be an instance of Permission class')
953
954 new = cls()
955 new.users_group_id = users_group_id
956 new.permission = perm
957 try:
958 Session.add(new)
959 Session.commit()
960 except:
961 Session.rollback()
962
963
964 @classmethod
965 def revoke_perm(cls, users_group_id, perm):
966 if not isinstance(perm, Permission):
967 raise Exception('perm needs to be an instance of Permission class')
968
969 try:
970 cls.query().filter(cls.users_group_id == users_group_id)\
971 .filter(cls.permission == perm).delete()
972 Session.commit()
973 except:
974 Session.rollback()
975
976
977 class UserRepoGroupToPerm(Base, BaseModel):
978 __tablename__ = 'group_to_perm'
979 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
980
981 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
982 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
983 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
984 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
985
986 user = relationship('User')
987 permission = relationship('Permission')
988 group = relationship('RepoGroup')
989
990 class Statistics(Base, BaseModel):
991 __tablename__ = 'statistics'
992 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
993 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
994 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
995 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
996 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
997 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
998 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
999
1000 repository = relationship('Repository', single_parent=True)
1001
1002 class UserFollowing(Base, BaseModel):
1003 __tablename__ = 'user_followings'
1004 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
1005 UniqueConstraint('user_id', 'follows_user_id')
1006 , {'extend_existing':True})
1007
1008 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1009 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1010 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1011 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1012 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1013
1014 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1015
1016 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1017 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1018
1019
1020 @classmethod
1021 def get_repo_followers(cls, repo_id):
1022 return cls.query().filter(cls.follows_repo_id == repo_id)
1023
1024 class CacheInvalidation(Base, BaseModel):
1025 __tablename__ = 'cache_invalidation'
1026 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
1027 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1028 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1029 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1030 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1031
1032
1033 def __init__(self, cache_key, cache_args=''):
1034 self.cache_key = cache_key
1035 self.cache_args = cache_args
1036 self.cache_active = False
1037
1038 def __repr__(self):
1039 return "<%s('%s:%s')>" % (self.__class__.__name__,
1040 self.cache_id, self.cache_key)
1041
1042 @classmethod
1043 def invalidate(cls, key):
1044 """
1045 Returns Invalidation object if this given key should be invalidated
1046 None otherwise. `cache_active = False` means that this cache
1047 state is not valid and needs to be invalidated
1048
1049 :param key:
1050 """
1051 return cls.query()\
1052 .filter(CacheInvalidation.cache_key == key)\
1053 .filter(CacheInvalidation.cache_active == False)\
1054 .scalar()
1055
1056 @classmethod
1057 def set_invalidate(cls, key):
1058 """
1059 Mark this Cache key for invalidation
1060
1061 :param key:
1062 """
1063
1064 log.debug('marking %s for invalidation' % key)
1065 inv_obj = Session.query(cls)\
1066 .filter(cls.cache_key == key).scalar()
1067 if inv_obj:
1068 inv_obj.cache_active = False
1069 else:
1070 log.debug('cache key not found in invalidation db -> creating one')
1071 inv_obj = CacheInvalidation(key)
1072
1073 try:
1074 Session.add(inv_obj)
1075 Session.commit()
1076 except Exception:
1077 log.error(traceback.format_exc())
1078 Session.rollback()
1079
1080 @classmethod
1081 def set_valid(cls, key):
1082 """
1083 Mark this cache key as active and currently cached
1084
1085 :param key:
1086 """
1087 inv_obj = Session.query(CacheInvalidation)\
1088 .filter(CacheInvalidation.cache_key == key).scalar()
1089 inv_obj.cache_active = True
1090 Session.add(inv_obj)
1091 Session.commit()
1092
1093 class DbMigrateVersion(Base, BaseModel):
1094 __tablename__ = 'db_migrate_version'
1095 __table_args__ = {'extend_existing':True}
1096 repository_id = Column('repository_id', String(250), primary_key=True)
1097 repository_path = Column('repository_path', Text)
1098 version = Column('version', Integer)
@@ -0,0 +1,28 b''
1 # -*- coding: utf-8 -*-
2 """
3 rhodecode.model.db
4 ~~~~~~~~~~~~~~~~~~
5
6 Database Models for RhodeCode
7
8 :created_on: Apr 08, 2010
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
12 """
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 #
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
22 #
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
26 #TODO: when branch 1.3 is finished replacem with db.py content
27
28 from rhodecode.model.db import *
@@ -0,0 +1,74 b''
1 import logging
2 import datetime
3
4 from sqlalchemy import *
5 from sqlalchemy.exc import DatabaseError
6 from sqlalchemy.orm import relation, backref, class_mapper
7 from sqlalchemy.orm.session import Session
8
9 from rhodecode.lib.dbmigrate.migrate import *
10 from rhodecode.lib.dbmigrate.migrate.changeset import *
11
12 from rhodecode.model.meta import Base
13
14 log = logging.getLogger(__name__)
15
16
17 def upgrade(migrate_engine):
18 """ Upgrade operations go here.
19 Don't create your own engine; bind migrate_engine to your metadata
20 """
21 #==========================================================================
22 # Add table `users_group_repo_group_to_perm`
23 #==========================================================================
24 from rhodecode.lib.dbmigrate.schema.db_1_3_0 import UsersGroupRepoGroupToPerm
25 UsersGroupRepoGroupToPerm().__table__.create()
26
27 #==========================================================================
28 # Add table `changeset_comments`
29 #==========================================================================
30 from rhodecode.lib.dbmigrate.schema.db_1_3_0 import ChangesetComment
31 ChangesetComment().__table__.create()
32
33 #==========================================================================
34 # Add table `notifications`
35 #==========================================================================
36 from rhodecode.lib.dbmigrate.schema.db_1_3_0 import Notification
37 Notification().__table__.create()
38
39 #==========================================================================
40 # Add table `user_to_notification`
41 #==========================================================================
42 from rhodecode.lib.dbmigrate.schema.db_1_3_0 import UserNotification
43 UserNotification().__table__.create()
44
45 #==========================================================================
46 # Add unique to table `users_group_to_perm`
47 #==========================================================================
48 from rhodecode.lib.dbmigrate.schema.db_1_3_0 import UsersGroupToPerm
49 tbl = UsersGroupToPerm().__table__
50 cons = UniqueConstraint('users_group_id', 'permission_id', table=tbl)
51 cons.create()
52
53 #==========================================================================
54 # Fix unique constrain on table `user_logs`
55 #==========================================================================
56 from rhodecode.lib.dbmigrate.schema.db_1_3_0 import UserLog
57 tbl = UserLog().__table__
58 col = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'),
59 nullable=False, unique=None, default=None)
60 col.alter(nullable=True, table=tbl)
61
62 #==========================================================================
63 # Rename table `group_to_perm` to `user_repo_group_to_perm`
64 #==========================================================================
65 tbl = Table('group_to_perm', MetaData(bind=migrate_engine), autoload=True,
66 autoload_with=migrate_engine)
67 tbl.rename('user_repo_group_to_perm')
68
69 return
70
71
72 def downgrade(migrate_engine):
73 meta = MetaData()
74 meta.bind = migrate_engine
@@ -0,0 +1,65 b''
1 import logging
2 import datetime
3
4 from sqlalchemy import *
5 from sqlalchemy.exc import DatabaseError
6 from sqlalchemy.orm import relation, backref, class_mapper
7 from sqlalchemy.orm.session import Session
8
9 from rhodecode.lib.dbmigrate.migrate import *
10 from rhodecode.lib.dbmigrate.migrate.changeset import *
11
12 from rhodecode.model.meta import Base
13
14 log = logging.getLogger(__name__)
15
16
17 def upgrade(migrate_engine):
18 """ Upgrade operations go here.
19 Don't create your own engine; bind migrate_engine to your metadata
20 """
21
22 #==========================================================================
23 # Change unique constraints of table `repo_to_perm`
24 #==========================================================================
25 from rhodecode.lib.dbmigrate.schema.db_1_3_0 import UserRepoToPerm
26 tbl = UserRepoToPerm().__table__
27 new_cons = UniqueConstraint('user_id', 'repository_id', 'permission_id', table=tbl)
28 new_cons.create()
29
30 if migrate_engine.name in ['mysql']:
31 old_cons = UniqueConstraint('user_id', 'repository_id', table=tbl, name="user_id")
32 old_cons.drop()
33 elif migrate_engine.name in ['postgresql']:
34 old_cons = UniqueConstraint('user_id', 'repository_id', table=tbl)
35 old_cons.drop()
36 else:
37 # sqlite doesn't support dropping constraints...
38 print """Please manually drop UniqueConstraint('user_id', 'repository_id')"""
39
40 #==========================================================================
41 # fix uniques of table `user_repo_group_to_perm`
42 #==========================================================================
43 from rhodecode.lib.dbmigrate.schema.db_1_3_0 import UserRepoGroupToPerm
44 tbl = UserRepoGroupToPerm().__table__
45 new_cons = UniqueConstraint('group_id', 'permission_id', 'user_id', table=tbl)
46 new_cons.create()
47
48 # fix uniqueConstraints
49 if migrate_engine.name in ['mysql']:
50 #mysql is givinig troubles here...
51 old_cons = UniqueConstraint('group_id', 'permission_id', table=tbl, name="group_id")
52 old_cons.drop()
53 elif migrate_engine.name in ['postgresql']:
54 old_cons = UniqueConstraint('group_id', 'permission_id', table=tbl, name='group_to_perm_group_id_permission_id_key')
55 old_cons.drop()
56 else:
57 # sqlite doesn't support dropping constraints...
58 print """Please manually drop UniqueConstraint('group_id', 'permission_id')"""
59
60 return
61
62
63 def downgrade(migrate_engine):
64 meta = MetaData()
65 meta.bind = migrate_engine
This diff has been collapsed as it changes many lines, (517 lines changed) Show them Hide them
@@ -0,0 +1,517 b''
1 # -*- coding: utf-8 -*-
2 """
3 rhodecode.lib.diffs
4 ~~~~~~~~~~~~~~~~~~~
5
6 Set of diffing helpers, previously part of vcs
7
8
9 :created_on: Dec 4, 2011
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :original copyright: 2007-2008 by Armin Ronacher
13 :license: GPLv3, see COPYING for more details.
14 """
15 # This program is free software: you can redistribute it and/or modify
16 # it under the terms of the GNU General Public License as published by
17 # the Free Software Foundation, either version 3 of the License, or
18 # (at your option) any later version.
19 #
20 # This program is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 # GNU General Public License for more details.
24 #
25 # You should have received a copy of the GNU General Public License
26 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27
28 import re
29 import difflib
30 import markupsafe
31 from itertools import tee, imap
32
33 from pylons.i18n.translation import _
34
35 from rhodecode.lib.vcs.exceptions import VCSError
36 from rhodecode.lib.vcs.nodes import FileNode
37
38 from rhodecode.lib.utils import EmptyChangeset
39
40
41 def wrap_to_table(str_):
42 return '''<table class="code-difftable">
43 <tr class="line no-comment">
44 <td class="lineno new"></td>
45 <td class="code no-comment"><pre>%s</pre></td>
46 </tr>
47 </table>''' % str_
48
49
50 def wrapped_diff(filenode_old, filenode_new, cut_off_limit=None,
51 ignore_whitespace=True, line_context=3,
52 enable_comments=False):
53 """
54 returns a wrapped diff into a table, checks for cut_off_limit and presents
55 proper message
56 """
57
58 if filenode_old is None:
59 filenode_old = FileNode(filenode_new.path, '', EmptyChangeset())
60
61 if filenode_old.is_binary or filenode_new.is_binary:
62 diff = wrap_to_table(_('binary file'))
63 stats = (0, 0)
64 size = 0
65
66 elif cut_off_limit != -1 and (cut_off_limit is None or
67 (filenode_old.size < cut_off_limit and filenode_new.size < cut_off_limit)):
68
69 f_gitdiff = get_gitdiff(filenode_old, filenode_new,
70 ignore_whitespace=ignore_whitespace,
71 context=line_context)
72 diff_processor = DiffProcessor(f_gitdiff, format='gitdiff')
73
74 diff = diff_processor.as_html(enable_comments=enable_comments)
75 stats = diff_processor.stat()
76 size = len(diff or '')
77 else:
78 diff = wrap_to_table(_('Changeset was to big and was cut off, use '
79 'diff menu to display this diff'))
80 stats = (0, 0)
81 size = 0
82
83 if not diff:
84 diff = wrap_to_table(_('No changes detected'))
85
86 cs1 = filenode_old.last_changeset.raw_id
87 cs2 = filenode_new.last_changeset.raw_id
88
89 return size, cs1, cs2, diff, stats
90
91
92 def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True, context=3):
93 """
94 Returns git style diff between given ``filenode_old`` and ``filenode_new``.
95
96 :param ignore_whitespace: ignore whitespaces in diff
97 """
98 # make sure we pass in default context
99 context = context or 3
100
101 for filenode in (filenode_old, filenode_new):
102 if not isinstance(filenode, FileNode):
103 raise VCSError("Given object should be FileNode object, not %s"
104 % filenode.__class__)
105
106 repo = filenode_new.changeset.repository
107 old_raw_id = getattr(filenode_old.changeset, 'raw_id', repo.EMPTY_CHANGESET)
108 new_raw_id = getattr(filenode_new.changeset, 'raw_id', repo.EMPTY_CHANGESET)
109
110 vcs_gitdiff = repo.get_diff(old_raw_id, new_raw_id, filenode_new.path,
111 ignore_whitespace, context)
112
113 return vcs_gitdiff
114
115
116 class DiffProcessor(object):
117 """
118 Give it a unified diff and it returns a list of the files that were
119 mentioned in the diff together with a dict of meta information that
120 can be used to render it in a HTML template.
121 """
122 _chunk_re = re.compile(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
123
124 def __init__(self, diff, differ='diff', format='udiff'):
125 """
126 :param diff: a text in diff format or generator
127 :param format: format of diff passed, `udiff` or `gitdiff`
128 """
129 if isinstance(diff, basestring):
130 diff = [diff]
131
132 self.__udiff = diff
133 self.__format = format
134 self.adds = 0
135 self.removes = 0
136
137 if isinstance(self.__udiff, basestring):
138 self.lines = iter(self.__udiff.splitlines(1))
139
140 elif self.__format == 'gitdiff':
141 udiff_copy = self.copy_iterator()
142 self.lines = imap(self.escaper, self._parse_gitdiff(udiff_copy))
143 else:
144 udiff_copy = self.copy_iterator()
145 self.lines = imap(self.escaper, udiff_copy)
146
147 # Select a differ.
148 if differ == 'difflib':
149 self.differ = self._highlight_line_difflib
150 else:
151 self.differ = self._highlight_line_udiff
152
153 def escaper(self, string):
154 return markupsafe.escape(string)
155
156 def copy_iterator(self):
157 """
158 make a fresh copy of generator, we should not iterate thru
159 an original as it's needed for repeating operations on
160 this instance of DiffProcessor
161 """
162 self.__udiff, iterator_copy = tee(self.__udiff)
163 return iterator_copy
164
165 def _extract_rev(self, line1, line2):
166 """
167 Extract the filename and revision hint from a line.
168 """
169
170 try:
171 if line1.startswith('--- ') and line2.startswith('+++ '):
172 l1 = line1[4:].split(None, 1)
173 old_filename = (l1[0].replace('a/', '', 1)
174 if len(l1) >= 1 else None)
175 old_rev = l1[1] if len(l1) == 2 else 'old'
176
177 l2 = line2[4:].split(None, 1)
178 new_filename = (l2[0].replace('b/', '', 1)
179 if len(l1) >= 1 else None)
180 new_rev = l2[1] if len(l2) == 2 else 'new'
181
182 filename = (old_filename
183 if old_filename != '/dev/null' else new_filename)
184
185 return filename, new_rev, old_rev
186 except (ValueError, IndexError):
187 pass
188
189 return None, None, None
190
191 def _parse_gitdiff(self, diffiterator):
192 def line_decoder(l):
193 if l.startswith('+') and not l.startswith('+++'):
194 self.adds += 1
195 elif l.startswith('-') and not l.startswith('---'):
196 self.removes += 1
197 return l.decode('utf8', 'replace')
198
199 output = list(diffiterator)
200 size = len(output)
201
202 if size == 2:
203 l = []
204 l.extend([output[0]])
205 l.extend(output[1].splitlines(1))
206 return map(line_decoder, l)
207 elif size == 1:
208 return map(line_decoder, output[0].splitlines(1))
209 elif size == 0:
210 return []
211
212 raise Exception('wrong size of diff %s' % size)
213
214 def _highlight_line_difflib(self, line, next_):
215 """
216 Highlight inline changes in both lines.
217 """
218
219 if line['action'] == 'del':
220 old, new = line, next_
221 else:
222 old, new = next_, line
223
224 oldwords = re.split(r'(\W)', old['line'])
225 newwords = re.split(r'(\W)', new['line'])
226
227 sequence = difflib.SequenceMatcher(None, oldwords, newwords)
228
229 oldfragments, newfragments = [], []
230 for tag, i1, i2, j1, j2 in sequence.get_opcodes():
231 oldfrag = ''.join(oldwords[i1:i2])
232 newfrag = ''.join(newwords[j1:j2])
233 if tag != 'equal':
234 if oldfrag:
235 oldfrag = '<del>%s</del>' % oldfrag
236 if newfrag:
237 newfrag = '<ins>%s</ins>' % newfrag
238 oldfragments.append(oldfrag)
239 newfragments.append(newfrag)
240
241 old['line'] = "".join(oldfragments)
242 new['line'] = "".join(newfragments)
243
244 def _highlight_line_udiff(self, line, next_):
245 """
246 Highlight inline changes in both lines.
247 """
248 start = 0
249 limit = min(len(line['line']), len(next_['line']))
250 while start < limit and line['line'][start] == next_['line'][start]:
251 start += 1
252 end = -1
253 limit -= start
254 while -end <= limit and line['line'][end] == next_['line'][end]:
255 end -= 1
256 end += 1
257 if start or end:
258 def do(l):
259 last = end + len(l['line'])
260 if l['action'] == 'add':
261 tag = 'ins'
262 else:
263 tag = 'del'
264 l['line'] = '%s<%s>%s</%s>%s' % (
265 l['line'][:start],
266 tag,
267 l['line'][start:last],
268 tag,
269 l['line'][last:]
270 )
271 do(line)
272 do(next_)
273
274 def _parse_udiff(self):
275 """
276 Parse the diff an return data for the template.
277 """
278 lineiter = self.lines
279 files = []
280 try:
281 line = lineiter.next()
282 # skip first context
283 skipfirst = True
284 while 1:
285 # continue until we found the old file
286 if not line.startswith('--- '):
287 line = lineiter.next()
288 continue
289
290 chunks = []
291 filename, old_rev, new_rev = \
292 self._extract_rev(line, lineiter.next())
293 files.append({
294 'filename': filename,
295 'old_revision': old_rev,
296 'new_revision': new_rev,
297 'chunks': chunks
298 })
299
300 line = lineiter.next()
301 while line:
302 match = self._chunk_re.match(line)
303 if not match:
304 break
305
306 lines = []
307 chunks.append(lines)
308
309 old_line, old_end, new_line, new_end = \
310 [int(x or 1) for x in match.groups()[:-1]]
311 old_line -= 1
312 new_line -= 1
313 context = len(match.groups()) == 5
314 old_end += old_line
315 new_end += new_line
316
317 if context:
318 if not skipfirst:
319 lines.append({
320 'old_lineno': '...',
321 'new_lineno': '...',
322 'action': 'context',
323 'line': line,
324 })
325 else:
326 skipfirst = False
327
328 line = lineiter.next()
329 while old_line < old_end or new_line < new_end:
330 if line:
331 command, line = line[0], line[1:]
332 else:
333 command = ' '
334 affects_old = affects_new = False
335
336 # ignore those if we don't expect them
337 if command in '#@':
338 continue
339 elif command == '+':
340 affects_new = True
341 action = 'add'
342 elif command == '-':
343 affects_old = True
344 action = 'del'
345 else:
346 affects_old = affects_new = True
347 action = 'unmod'
348
349 old_line += affects_old
350 new_line += affects_new
351 lines.append({
352 'old_lineno': affects_old and old_line or '',
353 'new_lineno': affects_new and new_line or '',
354 'action': action,
355 'line': line
356 })
357 line = lineiter.next()
358
359 except StopIteration:
360 pass
361
362 # highlight inline changes
363 for _ in files:
364 for chunk in chunks:
365 lineiter = iter(chunk)
366 #first = True
367 try:
368 while 1:
369 line = lineiter.next()
370 if line['action'] != 'unmod':
371 nextline = lineiter.next()
372 if nextline['action'] == 'unmod' or \
373 nextline['action'] == line['action']:
374 continue
375 self.differ(line, nextline)
376 except StopIteration:
377 pass
378
379 return files
380
381 def prepare(self):
382 """
383 Prepare the passed udiff for HTML rendering. It'l return a list
384 of dicts
385 """
386 return self._parse_udiff()
387
388 def _safe_id(self, idstring):
389 """Make a string safe for including in an id attribute.
390
391 The HTML spec says that id attributes 'must begin with
392 a letter ([A-Za-z]) and may be followed by any number
393 of letters, digits ([0-9]), hyphens ("-"), underscores
394 ("_"), colons (":"), and periods (".")'. These regexps
395 are slightly over-zealous, in that they remove colons
396 and periods unnecessarily.
397
398 Whitespace is transformed into underscores, and then
399 anything which is not a hyphen or a character that
400 matches \w (alphanumerics and underscore) is removed.
401
402 """
403 # Transform all whitespace to underscore
404 idstring = re.sub(r'\s', "_", '%s' % idstring)
405 # Remove everything that is not a hyphen or a member of \w
406 idstring = re.sub(r'(?!-)\W', "", idstring).lower()
407 return idstring
408
409 def raw_diff(self):
410 """
411 Returns raw string as udiff
412 """
413 udiff_copy = self.copy_iterator()
414 if self.__format == 'gitdiff':
415 udiff_copy = self._parse_gitdiff(udiff_copy)
416 return u''.join(udiff_copy)
417
418 def as_html(self, table_class='code-difftable', line_class='line',
419 new_lineno_class='lineno old', old_lineno_class='lineno new',
420 code_class='code', enable_comments=False):
421 """
422 Return udiff as html table with customized css classes
423 """
424 def _link_to_if(condition, label, url):
425 """
426 Generates a link if condition is meet or just the label if not.
427 """
428
429 if condition:
430 return '''<a href="%(url)s">%(label)s</a>''' % {
431 'url': url,
432 'label': label
433 }
434 else:
435 return label
436 diff_lines = self.prepare()
437 _html_empty = True
438 _html = []
439 _html.append('''<table class="%(table_class)s">\n''' % {
440 'table_class': table_class
441 })
442 for diff in diff_lines:
443 for line in diff['chunks']:
444 _html_empty = False
445 for change in line:
446 _html.append('''<tr class="%(lc)s %(action)s">\n''' % {
447 'lc': line_class,
448 'action': change['action']
449 })
450 anchor_old_id = ''
451 anchor_new_id = ''
452 anchor_old = "%(filename)s_o%(oldline_no)s" % {
453 'filename': self._safe_id(diff['filename']),
454 'oldline_no': change['old_lineno']
455 }
456 anchor_new = "%(filename)s_n%(oldline_no)s" % {
457 'filename': self._safe_id(diff['filename']),
458 'oldline_no': change['new_lineno']
459 }
460 cond_old = (change['old_lineno'] != '...' and
461 change['old_lineno'])
462 cond_new = (change['new_lineno'] != '...' and
463 change['new_lineno'])
464 if cond_old:
465 anchor_old_id = 'id="%s"' % anchor_old
466 if cond_new:
467 anchor_new_id = 'id="%s"' % anchor_new
468 ###########################################################
469 # OLD LINE NUMBER
470 ###########################################################
471 _html.append('''\t<td %(a_id)s class="%(olc)s">''' % {
472 'a_id': anchor_old_id,
473 'olc': old_lineno_class
474 })
475
476 _html.append('''%(link)s''' % {
477 'link': _link_to_if(True, change['old_lineno'],
478 '#%s' % anchor_old)
479 })
480 _html.append('''</td>\n''')
481 ###########################################################
482 # NEW LINE NUMBER
483 ###########################################################
484
485 _html.append('''\t<td %(a_id)s class="%(nlc)s">''' % {
486 'a_id': anchor_new_id,
487 'nlc': new_lineno_class
488 })
489
490 _html.append('''%(link)s''' % {
491 'link': _link_to_if(True, change['new_lineno'],
492 '#%s' % anchor_new)
493 })
494 _html.append('''</td>\n''')
495 ###########################################################
496 # CODE
497 ###########################################################
498 comments = '' if enable_comments else 'no-comment'
499 _html.append('''\t<td class="%(cc)s %(inc)s">''' % {
500 'cc': code_class,
501 'inc': comments
502 })
503 _html.append('''\n\t\t<pre>%(code)s</pre>\n''' % {
504 'code': change['line']
505 })
506 _html.append('''\t</td>''')
507 _html.append('''\n</tr>\n''')
508 _html.append('''</table>''')
509 if _html_empty:
510 return None
511 return ''.join(_html)
512
513 def stat(self):
514 """
515 Returns tuple of added, and removed lines for this instance
516 """
517 return self.adds, self.removes
@@ -0,0 +1,137 b''
1 # -*- coding: utf-8 -*-
2 """
3 rhodecode.lib.markup_renderer
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
6
7 Renderer for markup languages with ability to parse using rst or markdown
8
9 :created_on: Oct 27, 2011
10 :author: marcink
11 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
13 """
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
18 #
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
23 #
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
27 import re
28 import logging
29
30 from rhodecode.lib import safe_unicode
31
32 log = logging.getLogger(__name__)
33
34
35 class MarkupRenderer(object):
36 RESTRUCTUREDTEXT_DISALLOWED_DIRECTIVES = ['include', 'meta', 'raw']
37
38 MARKDOWN_PAT = re.compile(r'md|mkdn?|mdown|markdown', re.IGNORECASE)
39 RST_PAT = re.compile(r're?st', re.IGNORECASE)
40 PLAIN_PAT = re.compile(r'readme', re.IGNORECASE)
41
42 def __detect_renderer(self, source, filename=None):
43 """
44 runs detection of what renderer should be used for generating html
45 from a markup language
46
47 filename can be also explicitly a renderer name
48
49 :param source:
50 :param filename:
51 """
52
53 if MarkupRenderer.MARKDOWN_PAT.findall(filename):
54 detected_renderer = 'markdown'
55 elif MarkupRenderer.RST_PAT.findall(filename):
56 detected_renderer = 'rst'
57 elif MarkupRenderer.PLAIN_PAT.findall(filename):
58 detected_renderer = 'rst'
59 else:
60 detected_renderer = 'plain'
61
62 return getattr(MarkupRenderer, detected_renderer)
63
64 def render(self, source, filename=None):
65 """
66 Renders a given filename using detected renderer
67 it detects renderers based on file extension or mimetype.
68 At last it will just do a simple html replacing new lines with <br/>
69
70 :param file_name:
71 :param source:
72 """
73
74 renderer = self.__detect_renderer(source, filename)
75 readme_data = renderer(source)
76 return readme_data
77
78 @classmethod
79 def plain(cls, source):
80 source = safe_unicode(source)
81
82 def urlify_text(text):
83 url_pat = re.compile(r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'
84 '|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)')
85
86 def url_func(match_obj):
87 url_full = match_obj.groups()[0]
88 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
89
90 return url_pat.sub(url_func, text)
91
92 source = urlify_text(source)
93 return '<br />' + source.replace("\n", '<br />')
94
95 @classmethod
96 def markdown(cls, source):
97 source = safe_unicode(source)
98 try:
99 import markdown as __markdown
100 return __markdown.markdown(source, ['codehilite'])
101 except ImportError:
102 log.warning('Install markdown to use this function')
103 return cls.plain(source)
104
105 @classmethod
106 def rst(cls, source):
107 source = safe_unicode(source)
108 try:
109 from docutils.core import publish_parts
110 from docutils.parsers.rst import directives
111 docutils_settings = dict([(alias, None) for alias in
112 cls.RESTRUCTUREDTEXT_DISALLOWED_DIRECTIVES])
113
114 docutils_settings.update({'input_encoding': 'unicode',
115 'report_level': 4})
116
117 for k, v in docutils_settings.iteritems():
118 directives.register_directive(k, v)
119
120 parts = publish_parts(source=source,
121 writer_name="html4css1",
122 settings_overrides=docutils_settings)
123
124 return parts['html_title'] + parts["fragment"]
125 except ImportError:
126 log.warning('Install docutils to use this function')
127 return cls.plain(source)
128
129 @classmethod
130 def rst_with_mentions(cls, source):
131 mention_pat = re.compile(r'(?:^@|\s@)(\w+)')
132
133 def wrapp(match_obj):
134 uname = match_obj.groups()[0]
135 return ' **@%(uname)s** ' % {'uname':uname}
136 mention_hl = mention_pat.sub(wrapp, source).strip()
137 return cls.rst(mention_hl)
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
@@ -0,0 +1,13 b''
1
2
3 class InvalidMessage(RuntimeError):
4 """
5 Raised if message is missing vital headers, such
6 as recipients or sender address.
7 """
8
9
10 class BadHeaders(RuntimeError):
11 """
12 Raised if message contains newlines in headers.
13 """
@@ -0,0 +1,182 b''
1 from rhodecode.lib.rcmail.response import MailResponse
2
3 from rhodecode.lib.rcmail.exceptions import BadHeaders
4 from rhodecode.lib.rcmail.exceptions import InvalidMessage
5
6 class Attachment(object):
7 """
8 Encapsulates file attachment information.
9
10 :param filename: filename of attachment
11 :param content_type: file mimetype
12 :param data: the raw file data, either as string or file obj
13 :param disposition: content-disposition (if any)
14 """
15
16 def __init__(self,
17 filename=None,
18 content_type=None,
19 data=None,
20 disposition=None):
21
22 self.filename = filename
23 self.content_type = content_type
24 self.disposition = disposition or 'attachment'
25 self._data = data
26
27 @property
28 def data(self):
29 if isinstance(self._data, basestring):
30 return self._data
31 self._data = self._data.read()
32 return self._data
33
34
35 class Message(object):
36 """
37 Encapsulates an email message.
38
39 :param subject: email subject header
40 :param recipients: list of email addresses
41 :param body: plain text message
42 :param html: HTML message
43 :param sender: email sender address
44 :param cc: CC list
45 :param bcc: BCC list
46 :param extra_headers: dict of extra email headers
47 :param attachments: list of Attachment instances
48 :param recipients_separator: alternative separator for any of
49 'From', 'To', 'Delivered-To', 'Cc', 'Bcc' fields
50 """
51
52 def __init__(self,
53 subject=None,
54 recipients=None,
55 body=None,
56 html=None,
57 sender=None,
58 cc=None,
59 bcc=None,
60 extra_headers=None,
61 attachments=None,
62 recipients_separator="; "):
63
64 self.subject = subject or ''
65 self.sender = sender
66 self.body = body
67 self.html = html
68
69 self.recipients = recipients or []
70 self.attachments = attachments or []
71 self.cc = cc or []
72 self.bcc = bcc or []
73 self.extra_headers = extra_headers or {}
74
75 self.recipients_separator = recipients_separator
76
77 @property
78 def send_to(self):
79 return set(self.recipients) | set(self.bcc or ()) | set(self.cc or ())
80
81 def to_message(self):
82 """
83 Returns raw email.Message instance.Validates message first.
84 """
85
86 self.validate()
87
88 return self.get_response().to_message()
89
90 def get_response(self):
91 """
92 Creates a Lamson MailResponse instance
93 """
94
95 response = MailResponse(Subject=self.subject,
96 To=self.recipients,
97 From=self.sender,
98 Body=self.body,
99 Html=self.html,
100 separator=self.recipients_separator)
101
102 if self.cc:
103 response.base['Cc'] = self.cc
104
105 for attachment in self.attachments:
106
107 response.attach(attachment.filename,
108 attachment.content_type,
109 attachment.data,
110 attachment.disposition)
111
112 response.update(self.extra_headers)
113
114 return response
115
116 def is_bad_headers(self):
117 """
118 Checks for bad headers i.e. newlines in subject, sender or recipients.
119 """
120
121 headers = [self.subject, self.sender]
122 headers += list(self.send_to)
123 headers += self.extra_headers.values()
124
125 for val in headers:
126 for c in '\r\n':
127 if c in val:
128 return True
129 return False
130
131 def validate(self):
132 """
133 Checks if message is valid and raises appropriate exception.
134 """
135
136 if not self.recipients:
137 raise InvalidMessage, "No recipients have been added"
138
139 if not self.body and not self.html:
140 raise InvalidMessage, "No body has been set"
141
142 if not self.sender:
143 raise InvalidMessage, "No sender address has been set"
144
145 if self.is_bad_headers():
146 raise BadHeaders
147
148 def add_recipient(self, recipient):
149 """
150 Adds another recipient to the message.
151
152 :param recipient: email address of recipient.
153 """
154
155 self.recipients.append(recipient)
156
157 def add_cc(self, recipient):
158 """
159 Adds an email address to the CC list.
160
161 :param recipient: email address of recipient.
162 """
163
164 self.cc.append(recipient)
165
166 def add_bcc(self, recipient):
167 """
168 Adds an email address to the BCC list.
169
170 :param recipient: email address of recipient.
171 """
172
173 self.bcc.append(recipient)
174
175 def attach(self, attachment):
176 """
177 Adds an attachment to the message.
178
179 :param attachment: an **Attachment** instance.
180 """
181
182 self.attachments.append(attachment)
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,6 +1,7 b''
1 syntax: glob
1 syntax: glob
2 *.pyc
2 *.pyc
3 *.swp
3 *.swp
4 *.sqlite
4 *.egg-info
5 *.egg-info
5 *.egg
6 *.egg
6
7
@@ -12,6 +13,9 b' syntax: regexp'
12 ^\.settings$
13 ^\.settings$
13 ^\.project$
14 ^\.project$
14 ^\.pydevproject$
15 ^\.pydevproject$
16 ^\.coverage$
15 ^rhodecode\.db$
17 ^rhodecode\.db$
16 ^test\.db$
18 ^test\.db$
17 ^repositories\.config$
19 ^RhodeCode\.egg-info$
20 ^rc\.ini$
21 ^fabfile.py
@@ -13,3 +13,6 b' List of contributors to RhodeCode projec'
13 Ankit Solanki <ankit.solanki@gmail.com>
13 Ankit Solanki <ankit.solanki@gmail.com>
14 Liad Shani <liadff@gmail.com>
14 Liad Shani <liadff@gmail.com>
15 Les Peabody <lpeabody@gmail.com>
15 Les Peabody <lpeabody@gmail.com>
16 Jonas Oberschweiber <jonas.oberschweiber@d-velop.de>
17 Matt Zuba <matt.zuba@goodwillaz.org>
18 Aras Pranckevicius <aras@unity3d.com> No newline at end of file
@@ -1,22 +1,40 b''
1 =================================================
1 =========
2 Welcome to RhodeCode (RhodiumCode) documentation!
2 RhodeCode
3 =================================================
3 =========
4
4
5 ``RhodeCode`` is a Pylons framework based Mercurial repository
5 About
6 browser/management tool with a built in push/pull server and full text search.
6 -----
7
8 ``RhodeCode`` is a fast and powerful management tool for Mercurial_ and GIT_
9 with a built in push/pull server and full text search and code-review.
7 It works on http/https and has a built in permission/authentication system with
10 It works on http/https and has a built in permission/authentication system with
8 the ability to authenticate via LDAP or ActiveDirectory. RhodeCode also supports
11 the ability to authenticate via LDAP or ActiveDirectory. RhodeCode also provides
9 simple API so it's easy integrable with existing systems.
12 simple API so it's easy integrable with existing external systems.
10
13
11 RhodeCode is similar in some respects to github or bitbucket_,
14 RhodeCode is similar in some respects to github or bitbucket_,
12 however RhodeCode can be run as standalone hosted application on your own server.
15 however RhodeCode can be run as standalone hosted application on your own server.
13 It is open source and donation ware and focuses more on providing a customized,
16 It is open source and donation ware and focuses more on providing a customized,
14 self administered interface for Mercurial(and soon GIT) repositories.
17 self administered interface for Mercurial and GIT repositories.
15 RhodeCode is powered by a vcs_ library that Lukasz Balcerzak and I created to
18 RhodeCode is powered by a vcs_ library that Lukasz Balcerzak and I created to
16 handle multiple different version control systems.
19 handle multiple different version control systems.
17
20
18 RhodeCode uses `Semantic Versioning <http://semver.org/>`_
21 RhodeCode uses `Semantic Versioning <http://semver.org/>`_
19
22
23 Installation
24 ------------
25 Stable releases of RhodeCode are best installed via::
26
27 easy_install rhodecode
28
29 Or::
30
31 pip install rhodecode
32
33 Detailed instructions and links may be found on the Installation page.
34
35 Please visit http://packages.python.org/RhodeCode/installation.html for
36 more details
37
20 RhodeCode demo
38 RhodeCode demo
21 --------------
39 --------------
22
40
@@ -45,16 +63,11 b' Sources at github_'
45
63
46 https://github.com/marcinkuzminski/rhodecode
64 https://github.com/marcinkuzminski/rhodecode
47
65
48 Installation
49 ------------
50
51 Please visit http://packages.python.org/RhodeCode/installation.html
52
53
66
54 RhodeCode Features
67 RhodeCode Features
55 ------------------
68 ------------------
56
69
57 - Has it's own middleware to handle mercurial_ protocol requests.
70 - Has its own middleware to handle mercurial_ protocol requests.
58 Each request can be logged and authenticated.
71 Each request can be logged and authenticated.
59 - Runs on threads unlike hgweb. You can make multiple pulls/pushes simultaneous.
72 - Runs on threads unlike hgweb. You can make multiple pulls/pushes simultaneous.
60 Supports http/https and LDAP
73 Supports http/https and LDAP
@@ -75,6 +88,9 b' RhodeCode Features'
75 - Server side forks. It is possible to fork a project and modify it freely
88 - Server side forks. It is possible to fork a project and modify it freely
76 without breaking the main repository. You can even write Your own hooks
89 without breaking the main repository. You can even write Your own hooks
77 and install them
90 and install them
91 - code review with notification system, inline commenting, all parsed using
92 rst syntax
93 - rst and markdown README support for repositories
78 - Full text search powered by Whoosh on the source files, and file names.
94 - Full text search powered by Whoosh on the source files, and file names.
79 Build in indexing daemons, with optional incremental index build
95 Build in indexing daemons, with optional incremental index build
80 (no external search servers required all in one application)
96 (no external search servers required all in one application)
@@ -88,20 +104,14 b' RhodeCode Features'
88 location
104 location
89 - Based on pylons / sqlalchemy / sqlite / whoosh / vcs
105 - Based on pylons / sqlalchemy / sqlite / whoosh / vcs
90
106
91
92 .. include:: ./docs/screenshots.rst
93
94
107
95 Incoming / Plans
108 Incoming / Plans
96 ----------------
109 ----------------
97
110
98 - Finer granular permissions per branch, repo group or subrepo
111 - Finer granular permissions per branch, repo group or subrepo
99 - pull requests and web based merges
112 - pull requests and web based merges
100 - notification and message system
113 - per line file history
101 - SSH based authentication with server side key management
114 - SSH based authentication with server side key management
102 - Code review (probably based on hg-review)
103 - Full git_ support, with push/pull server (currently in beta tests)
104 - Redmine and other bugtrackers integration
105 - Commit based built in wiki system
115 - Commit based built in wiki system
106 - More statistics and graph (global annotation + some more statistics)
116 - More statistics and graph (global annotation + some more statistics)
107 - Other advancements as development continues (or you can of course make
117 - Other advancements as development continues (or you can of course make
@@ -113,21 +123,35 b' License'
113 ``RhodeCode`` is released under the GPLv3 license.
123 ``RhodeCode`` is released under the GPLv3 license.
114
124
115
125
116 Mailing group Q&A
126 Getting help
117 -----------------
127 ------------
118
128
119 Join the `Google group <http://groups.google.com/group/rhodecode>`_
129 Listed bellow are various support resources that should help.
120
130
121 Open an issue at `issue tracker <http://bitbucket.org/marcinkuzminski/rhodecode/issues>`_
131 .. note::
132
133 Please try to read the documentation before posting any issues
134
135 - Join the `Google group <http://groups.google.com/group/rhodecode>`_ and ask
136 any questions.
122
137
123 Join #rhodecode on FreeNode (irc.freenode.net)
138 - Open an issue at `issue tracker <http://bitbucket.org/marcinkuzminski/rhodecode/issues>`_
124 or use http://webchat.freenode.net/?channels=rhodecode for web access to irc.
139
140
141 - Join #rhodecode on FreeNode (irc.freenode.net)
142 or use http://webchat.freenode.net/?channels=rhodecode for web access to irc.
143
144 - You can also follow me on twitter @marcinkuzminski where i often post some
145 news about RhodeCode
146
125
147
126 Online documentation
148 Online documentation
127 --------------------
149 --------------------
128
150
129 Online documentation for the current version of RhodeCode is available at
151 Online documentation for the current version of RhodeCode is available at
130 http://packages.python.org/RhodeCode/.
152 - http://packages.python.org/RhodeCode/
153 - http://rhodecode.readthedocs.org/en/latest/index.html
154
131 You may also build the documentation for yourself - go into ``docs/`` and run::
155 You may also build the documentation for yourself - go into ``docs/`` and run::
132
156
133 make html
157 make html
@@ -17,6 +17,7 b' pdebug = false'
17 #error_email_from = paste_error@localhost
17 #error_email_from = paste_error@localhost
18 #app_email_from = rhodecode-noreply@localhost
18 #app_email_from = rhodecode-noreply@localhost
19 #error_message =
19 #error_message =
20 #email_prefix = [RhodeCode]
20
21
21 #smtp_server = mail.server.com
22 #smtp_server = mail.server.com
22 #smtp_username =
23 #smtp_username =
@@ -32,7 +33,7 b' pdebug = false'
32 threadpool_workers = 5
33 threadpool_workers = 5
33
34
34 ##max request before thread respawn
35 ##max request before thread respawn
35 threadpool_max_requests = 6
36 threadpool_max_requests = 10
36
37
37 ##option to use threads of process
38 ##option to use threads of process
38 use_threadpool = true
39 use_threadpool = true
@@ -45,14 +46,52 b' port = 5000'
45 use = egg:rhodecode
46 use = egg:rhodecode
46 full_stack = true
47 full_stack = true
47 static_files = true
48 static_files = true
48 lang=en
49 lang = en
49 cache_dir = %(here)s/data
50 cache_dir = %(here)s/data
50 index_dir = %(here)s/data/index
51 index_dir = %(here)s/data/index
51 app_instance_uuid = develop
52 app_instance_uuid = rc-develop
52 cut_off_limit = 256000
53 cut_off_limit = 256000
53 force_https = false
54 force_https = false
54 commit_parse_limit = 25
55 commit_parse_limit = 25
55 use_gravatar = true
56 use_gravatar = true
57 container_auth_enabled = false
58 proxypass_auth_enabled = false
59 default_encoding = utf8
60
61 ## overwrite schema of clone url
62 ## available vars:
63 ## scheme - http/https
64 ## user - current user
65 ## pass - password
66 ## netloc - network location
67 ## path - usually repo_name
68
69 #clone_uri = {scheme}://{user}{pass}{netloc}{path}
70
71 ## issue tracking mapping for commits messages
72 ## comment out issue_pat, issue_server, issue_prefix to enable
73
74 ## pattern to get the issues from commit messages
75 ## default one used here is #<numbers> with a regex passive group for `#`
76 ## {id} will be all groups matched from this pattern
77
78 issue_pat = (?:\s*#)(\d+)
79
80 ## server url to the issue, each {id} will be replaced with match
81 ## fetched from the regex and {repo} is replaced with repository name
82
83 issue_server_link = https://myissueserver.com/{repo}/issue/{id}
84
85 ## prefix to add to link to indicate it's an url
86 ## #314 will be replaced by <issue_prefix><id>
87
88 issue_prefix = #
89
90 ## instance-id prefix
91 ## a prefix key for this instance used for cache invalidation when running
92 ## multiple instances of rhodecode, make sure it's globally unique for
93 ## all running rhodecode instances. Leave empty if you don't use it
94 instance_id =
56
95
57 ####################################
96 ####################################
58 ### CELERY CONFIG ####
97 ### CELERY CONFIG ####
@@ -91,21 +130,27 b' beaker.cache.regions=super_short_term,sh'
91
130
92 beaker.cache.super_short_term.type=memory
131 beaker.cache.super_short_term.type=memory
93 beaker.cache.super_short_term.expire=10
132 beaker.cache.super_short_term.expire=10
133 beaker.cache.super_short_term.key_length = 256
94
134
95 beaker.cache.short_term.type=memory
135 beaker.cache.short_term.type=memory
96 beaker.cache.short_term.expire=60
136 beaker.cache.short_term.expire=60
137 beaker.cache.short_term.key_length = 256
97
138
98 beaker.cache.long_term.type=memory
139 beaker.cache.long_term.type=memory
99 beaker.cache.long_term.expire=36000
140 beaker.cache.long_term.expire=36000
141 beaker.cache.long_term.key_length = 256
100
142
101 beaker.cache.sql_cache_short.type=memory
143 beaker.cache.sql_cache_short.type=memory
102 beaker.cache.sql_cache_short.expire=10
144 beaker.cache.sql_cache_short.expire=10
145 beaker.cache.sql_cache_short.key_length = 256
103
146
104 beaker.cache.sql_cache_med.type=memory
147 beaker.cache.sql_cache_med.type=memory
105 beaker.cache.sql_cache_med.expire=360
148 beaker.cache.sql_cache_med.expire=360
149 beaker.cache.sql_cache_med.key_length = 256
106
150
107 beaker.cache.sql_cache_long.type=file
151 beaker.cache.sql_cache_long.type=file
108 beaker.cache.sql_cache_long.expire=3600
152 beaker.cache.sql_cache_long.expire=3600
153 beaker.cache.sql_cache_long.key_length = 256
109
154
110 ####################################
155 ####################################
111 ### BEAKER SESSION ####
156 ### BEAKER SESSION ####
@@ -113,12 +158,26 b' beaker.cache.sql_cache_long.expire=3600'
113 ## Type of storage used for the session, current types are
158 ## Type of storage used for the session, current types are
114 ## dbm, file, memcached, database, and memory.
159 ## dbm, file, memcached, database, and memory.
115 ## The storage uses the Container API
160 ## The storage uses the Container API
116 ##that is also used by the cache system.
161 ## that is also used by the cache system.
117 beaker.session.type = file
162
163 ## db session example
164
165 #beaker.session.type = ext:database
166 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/rhodecode
167 #beaker.session.table_name = db_session
118
168
169 ## encrypted cookie session, good for many instances
170 #beaker.session.type = cookie
171
172 beaker.session.type = file
119 beaker.session.key = rhodecode
173 beaker.session.key = rhodecode
120 beaker.session.secret = g654dcno0-9873jhgfreyu
174 #beaker.session.encrypt_key = g654dcno0-9873jhgfreyu
175 #beaker.session.validate_key = 9712sds2212c--zxc123
121 beaker.session.timeout = 36000
176 beaker.session.timeout = 36000
177 beaker.session.httponly = true
178
179 ## uncomment for https secure cookie
180 beaker.session.secure = false
122
181
123 ##auto save the session to not to use .save()
182 ##auto save the session to not to use .save()
124 beaker.session.auto = False
183 beaker.session.auto = False
@@ -126,7 +185,7 b' beaker.session.auto = False'
126 ##true exire at browser close
185 ##true exire at browser close
127 #beaker.session.cookie_expires = 3600
186 #beaker.session.cookie_expires = 3600
128
187
129
188
130 ################################################################################
189 ################################################################################
131 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
190 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
132 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
191 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
@@ -26,16 +26,18 b' API ACCESS'
26
26
27 All clients are required to send JSON-RPC spec JSON data::
27 All clients are required to send JSON-RPC spec JSON data::
28
28
29 {
29 {
30 "id:<id>,
30 "api_key":"<api_key>",
31 "api_key":"<api_key>",
31 "method":"<method_name>",
32 "method":"<method_name>",
32 "args":{"<arg_key>":"<arg_val>"}
33 "args":{"<arg_key>":"<arg_val>"}
33 }
34 }
34
35
35 Example call for autopulling remotes repos using curl::
36 Example call for autopulling remotes repos using curl::
36 curl https://server.com/_admin/api -X POST -H 'content-type:text/plain' --data-binary '{"api_key":"xe7cdb2v278e4evbdf5vs04v832v0efvcbcve4a3","method":"pull","args":{"repo":"CPython"}}'
37 curl https://server.com/_admin/api -X POST -H 'content-type:text/plain' --data-binary '{"id":1,"api_key":"xe7cdb2v278e4evbdf5vs04v832v0efvcbcve4a3","method":"pull","args":{"repo":"CPython"}}'
37
38
38 Simply provide
39 Simply provide
40 - *id* A value of any type, which is used to match the response with the request that it is replying to.
39 - *api_key* for access and permission validation.
41 - *api_key* for access and permission validation.
40 - *method* is name of method to call
42 - *method* is name of method to call
41 - *args* is an key:value list of arguments to pass to method
43 - *args* is an key:value list of arguments to pass to method
@@ -47,7 +49,8 b' Simply provide'
47
49
48 RhodeCode API will return always a JSON-RPC response::
50 RhodeCode API will return always a JSON-RPC response::
49
51
50 {
52 {
53 "id":<id>,
51 "result": "<result>",
54 "result": "<result>",
52 "error": null
55 "error": null
53 }
56 }
@@ -72,21 +75,55 b' INPUT::'
72 api_key : "<api_key>"
75 api_key : "<api_key>"
73 method : "pull"
76 method : "pull"
74 args : {
77 args : {
75 "repo" : "<repo_name>"
78 "repo_name" : "<reponame>"
76 }
79 }
77
80
78 OUTPUT::
81 OUTPUT::
79
82
80 result : "Pulled from <repo_name>"
83 result : "Pulled from <reponame>"
81 error : null
84 error : null
82
85
83
86
87 get_user
88 --------
89
90 Get's an user by username or user_id, Returns empty result if user is not found.
91 This command can be executed only using api_key belonging to user with admin
92 rights.
93
94
95 INPUT::
96
97 api_key : "<api_key>"
98 method : "get_user"
99 args : {
100 "userid" : "<username or user_id>"
101 }
102
103 OUTPUT::
104
105 result: None if user does not exist or
106 {
107 "id" : "<id>",
108 "username" : "<username>",
109 "firstname": "<firstname>",
110 "lastname" : "<lastname>",
111 "email" : "<email>",
112 "active" : "<bool>",
113 "admin" :  "<bool>",
114 "ldap_dn" : "<ldap_dn>"
115 }
116
117 error: null
118
119
84 get_users
120 get_users
85 ---------
121 ---------
86
122
87 Lists all existing users. This command can be executed only using api_key
123 Lists all existing users. This command can be executed only using api_key
88 belonging to user with admin rights.
124 belonging to user with admin rights.
89
125
126
90 INPUT::
127 INPUT::
91
128
92 api_key : "<api_key>"
129 api_key : "<api_key>"
@@ -104,18 +141,20 b' OUTPUT::'
104 "email" : "<email>",
141 "email" : "<email>",
105 "active" : "<bool>",
142 "active" : "<bool>",
106 "admin" :  "<bool>",
143 "admin" :  "<bool>",
107 "ldap" : "<ldap_dn>"
144 "ldap_dn" : "<ldap_dn>"
108 },
145 },
109
146
110 ]
147 ]
111 error: null
148 error: null
112
149
150
113 create_user
151 create_user
114 -----------
152 -----------
115
153
116 Creates new user or updates current one if such user exists. This command can
154 Creates new user. This command can
117 be executed only using api_key belonging to user with admin rights.
155 be executed only using api_key belonging to user with admin rights.
118
156
157
119 INPUT::
158 INPUT::
120
159
121 api_key : "<api_key>"
160 api_key : "<api_key>"
@@ -123,9 +162,9 b' INPUT::'
123 args : {
162 args : {
124 "username" : "<username>",
163 "username" : "<username>",
125 "password" : "<password>",
164 "password" : "<password>",
126 "firstname" : "<firstname>",
165 "email" : "<useremail>",
127 "lastname" : "<lastname>",
166 "firstname" : "<firstname> = None",
128 "email" : "<useremail>"
167 "lastname" : "<lastname> = None",
129 "active" : "<bool> = True",
168 "active" : "<bool> = True",
130 "admin" : "<bool> = False",
169 "admin" : "<bool> = False",
131 "ldap_dn" : "<ldap_dn> = None"
170 "ldap_dn" : "<ldap_dn> = None"
@@ -134,15 +173,88 b' INPUT::'
134 OUTPUT::
173 OUTPUT::
135
174
136 result: {
175 result: {
176 "id" : "<new_user_id>",
137 "msg" : "created new user <username>"
177 "msg" : "created new user <username>"
138 }
178 }
139 error: null
179 error: null
140
180
181
182 update_user
183 -----------
184
185 updates current one if such user exists. This command can
186 be executed only using api_key belonging to user with admin rights.
187
188
189 INPUT::
190
191 api_key : "<api_key>"
192 method : "update_user"
193 args : {
194 "userid" : "<user_id or username>",
195 "username" : "<username>",
196 "password" : "<password>",
197 "email" : "<useremail>",
198 "firstname" : "<firstname>",
199 "lastname" : "<lastname>",
200 "active" : "<bool>",
201 "admin" : "<bool>",
202 "ldap_dn" : "<ldap_dn>"
203 }
204
205 OUTPUT::
206
207 result: {
208 "id" : "<edited_user_id>",
209 "msg" : "updated user <username>"
210 }
211 error: null
212
213
214 get_users_group
215 ---------------
216
217 Gets an existing users group. This command can be executed only using api_key
218 belonging to user with admin rights.
219
220
221 INPUT::
222
223 api_key : "<api_key>"
224 method : "get_users_group"
225 args : {
226 "group_name" : "<name>"
227 }
228
229 OUTPUT::
230
231 result : None if group not exist
232 {
233 "id" : "<id>",
234 "group_name" : "<groupname>",
235 "active": "<bool>",
236 "members" : [
237 { "id" : "<userid>",
238 "username" : "<username>",
239 "firstname": "<firstname>",
240 "lastname" : "<lastname>",
241 "email" : "<email>",
242 "active" : "<bool>",
243 "admin" :  "<bool>",
244 "ldap" : "<ldap_dn>"
245 },
246
247 ]
248 }
249 error : null
250
251
141 get_users_groups
252 get_users_groups
142 ----------------
253 ----------------
143
254
144 Lists all existing users groups. This command can be executed only using api_key
255 Lists all existing users groups. This command can be executed only using
145 belonging to user with admin rights.
256 api_key belonging to user with admin rights.
257
146
258
147 INPUT::
259 INPUT::
148
260
@@ -154,9 +266,9 b' OUTPUT::'
154
266
155 result : [
267 result : [
156 {
268 {
157 "id" : "<id>",
269 "id" : "<id>",
158 "name" : "<name>",
270 "group_name" : "<groupname>",
159 "active": "<bool>",
271 "active": "<bool>",
160 "members" : [
272 "members" : [
161 {
273 {
162 "id" : "<userid>",
274 "id" : "<userid>",
@@ -174,41 +286,6 b' OUTPUT::'
174 ]
286 ]
175 error : null
287 error : null
176
288
177 get_users_group
178 ---------------
179
180 Gets an existing users group. This command can be executed only using api_key
181 belonging to user with admin rights.
182
183 INPUT::
184
185 api_key : "<api_key>"
186 method : "get_users_group"
187 args : {
188 "group_name" : "<name>"
189 }
190
191 OUTPUT::
192
193 result : None if group not exist
194 {
195 "id" : "<id>",
196 "name" : "<name>",
197 "active": "<bool>",
198 "members" : [
199 { "id" : "<userid>",
200 "username" : "<username>",
201 "firstname": "<firstname>",
202 "lastname" : "<lastname>",
203 "email" : "<email>",
204 "active" : "<bool>",
205 "admin" :  "<bool>",
206 "ldap" : "<ldap_dn>"
207 },
208
209 ]
210 }
211 error : null
212
289
213 create_users_group
290 create_users_group
214 ------------------
291 ------------------
@@ -216,12 +293,13 b' create_users_group'
216 Creates new users group. This command can be executed only using api_key
293 Creates new users group. This command can be executed only using api_key
217 belonging to user with admin rights
294 belonging to user with admin rights
218
295
296
219 INPUT::
297 INPUT::
220
298
221 api_key : "<api_key>"
299 api_key : "<api_key>"
222 method : "create_users_group"
300 method : "create_users_group"
223 args: {
301 args: {
224 "name": "<name>",
302 "group_name": "<groupname>",
225 "active":"<bool> = True"
303 "active":"<bool> = True"
226 }
304 }
227
305
@@ -229,39 +307,120 b' OUTPUT::'
229
307
230 result: {
308 result: {
231 "id": "<newusersgroupid>",
309 "id": "<newusersgroupid>",
232 "msg": "created new users group <name>"
310 "msg": "created new users group <groupname>"
233 }
311 }
234 error: null
312 error: null
235
313
314
236 add_user_to_users_group
315 add_user_to_users_group
237 -----------------------
316 -----------------------
238
317
239 Adds a user to a users group. This command can be executed only using api_key
318 Adds a user to a users group. If user exists in that group success will be
319 `false`. This command can be executed only using api_key
240 belonging to user with admin rights
320 belonging to user with admin rights
241
321
322
242 INPUT::
323 INPUT::
243
324
244 api_key : "<api_key>"
325 api_key : "<api_key>"
245 method : "add_user_users_group"
326 method : "add_user_users_group"
246 args: {
327 args: {
247 "group_name" : "<groupname>",
328 "group_name" : "<groupname>",
248 "user_name" : "<username>"
329 "username" : "<username>"
249 }
330 }
250
331
251 OUTPUT::
332 OUTPUT::
252
333
253 result: {
334 result: {
254 "id": "<newusersgroupmemberid>",
335 "id": "<newusersgroupmemberid>",
255 "msg": "created new users group member"
336 "success": True|False # depends on if member is in group
337 "msg": "added member <username> to users group <groupname> |
338 User is already in that group"
339 }
340 error: null
341
342
343 remove_user_from_users_group
344 ----------------------------
345
346 Removes a user from a users group. If user is not in given group success will
347 be `false`. This command can be executed only
348 using api_key belonging to user with admin rights
349
350
351 INPUT::
352
353 api_key : "<api_key>"
354 method : "remove_user_from_users_group"
355 args: {
356 "group_name" : "<groupname>",
357 "username" : "<username>"
358 }
359
360 OUTPUT::
361
362 result: {
363 "success": True|False, # depends on if member is in group
364 "msg": "removed member <username> from users group <groupname> |
365 User wasn't in group"
256 }
366 }
257 error: null
367 error: null
258
368
369
370 get_repo
371 --------
372
373 Gets an existing repository by it's name or repository_id. This command can
374 be executed only using api_key belonging to user with admin rights.
375
376
377 INPUT::
378
379 api_key : "<api_key>"
380 method : "get_repo"
381 args: {
382 "repoid" : "<reponame or repo_id>"
383 }
384
385 OUTPUT::
386
387 result: None if repository does not exist or
388 {
389 "id" : "<id>",
390 "repo_name" : "<reponame>"
391 "type" : "<type>",
392 "description" : "<description>",
393 "members" : [
394 { "id" : "<userid>",
395 "username" : "<username>",
396 "firstname": "<firstname>",
397 "lastname" : "<lastname>",
398 "email" : "<email>",
399 "active" : "<bool>",
400 "admin" :  "<bool>",
401 "ldap" : "<ldap_dn>",
402 "permission" : "repository.(read|write|admin)"
403 },
404
405 {
406 "id" : "<usersgroupid>",
407 "name" : "<usersgroupname>",
408 "active": "<bool>",
409 "permission" : "repository.(read|write|admin)"
410 },
411
412 ]
413 }
414 error: null
415
416
259 get_repos
417 get_repos
260 ---------
418 ---------
261
419
262 Lists all existing repositories. This command can be executed only using api_key
420 Lists all existing repositories. This command can be executed only using api_key
263 belonging to user with admin rights
421 belonging to user with admin rights
264
422
423
265 INPUT::
424 INPUT::
266
425
267 api_key : "<api_key>"
426 api_key : "<api_key>"
@@ -273,7 +432,7 b' OUTPUT::'
273 result: [
432 result: [
274 {
433 {
275 "id" : "<id>",
434 "id" : "<id>",
276 "name" : "<name>"
435 "repo_name" : "<reponame>"
277 "type" : "<type>",
436 "type" : "<type>",
278 "description" : "<description>"
437 "description" : "<description>"
279 },
438 },
@@ -281,51 +440,39 b' OUTPUT::'
281 ]
440 ]
282 error: null
441 error: null
283
442
284 get_repo
443
285 --------
444 get_repo_nodes
445 --------------
286
446
287 Gets an existing repository. This command can be executed only using api_key
447 returns a list of nodes and it's children in a flat list for a given path
288 belonging to user with admin rights
448 at given revision. It's possible to specify ret_type to show only `files` or
449 `dirs`. This command can be executed only using api_key belonging to user
450 with admin rights
451
289
452
290 INPUT::
453 INPUT::
291
454
292 api_key : "<api_key>"
455 api_key : "<api_key>"
293 method : "get_repo"
456 method : "get_repo_nodes"
294 args: {
457 args: {
295 "name" : "<name>"
458 "repo_name" : "<reponame>",
459 "revision" : "<revision>",
460 "root_path" : "<root_path>",
461 "ret_type" : "<ret_type>" = 'all'
296 }
462 }
297
463
298 OUTPUT::
464 OUTPUT::
299
465
300 result: None if repository not exist
466 result: [
301 {
467 {
302 "id" : "<id>",
303 "name" : "<name>"
468 "name" : "<name>"
304 "type" : "<type>",
469 "type" : "<type>",
305 "description" : "<description>",
470 },
306 "members" : [
471
307 { "id" : "<userid>",
472 ]
308 "username" : "<username>",
309 "firstname": "<firstname>",
310 "lastname" : "<lastname>",
311 "email" : "<email>",
312 "active" : "<bool>",
313 "admin" :  "<bool>",
314 "ldap" : "<ldap_dn>",
315 "permission" : "repository.(read|write|admin)"
316 },
317
318 {
319 "id" : "<usersgroupid>",
320 "name" : "<usersgroupname>",
321 "active": "<bool>",
322 "permission" : "repository.(read|write|admin)"
323 },
324
325 ]
326 }
327 error: null
473 error: null
328
474
475
329 create_repo
476 create_repo
330 -----------
477 -----------
331
478
@@ -335,58 +482,146 b' If repository name contains "/", all nee'
335 For example "foo/bar/baz" will create groups "foo", "bar" (with "foo" as parent),
482 For example "foo/bar/baz" will create groups "foo", "bar" (with "foo" as parent),
336 and create "baz" repository with "bar" as group.
483 and create "baz" repository with "bar" as group.
337
484
485
338 INPUT::
486 INPUT::
339
487
340 api_key : "<api_key>"
488 api_key : "<api_key>"
341 method : "create_repo"
489 method : "create_repo"
342 args: {
490 args: {
343 "name" : "<name>",
491 "repo_name" : "<reponame>",
344 "owner_name" : "<ownername>",
492 "owner_name" : "<ownername>",
345 "description" : "<description> = ''",
493 "description" : "<description> = ''",
346 "repo_type" : "<type> = 'hg'",
494 "repo_type" : "<type> = 'hg'",
347 "private" : "<bool> = False"
495 "private" : "<bool> = False",
496 "clone_uri" : "<clone_uri> = None",
348 }
497 }
349
498
350 OUTPUT::
499 OUTPUT::
351
500
352 result: None
501 result: {
502 "id": "<newrepoid>",
503 "msg": "Created new repository <reponame>",
504 }
353 error: null
505 error: null
354
506
355 add_user_to_repo
507
356 ----------------
508 delete_repo
509 -----------
510
511 Deletes a repository. This command can be executed only using api_key
512 belonging to user with admin rights.
513
514
515 INPUT::
516
517 api_key : "<api_key>"
518 method : "delete_repo"
519 args: {
520 "repo_name" : "<reponame>",
521 }
357
522
358 Add a user to a repository. This command can be executed only using api_key
523 OUTPUT::
359 belonging to user with admin rights.
524
360 If "perm" is None, user will be removed from the repository.
525 result: {
526 "msg": "Deleted repository <reponame>",
527 }
528 error: null
529
530
531 grant_user_permission
532 ---------------------
533
534 Grant permission for user on given repository, or update existing one
535 if found. This command can be executed only using api_key belonging to user
536 with admin rights.
537
361
538
362 INPUT::
539 INPUT::
363
540
364 api_key : "<api_key>"
541 api_key : "<api_key>"
365 method : "add_user_to_repo"
542 method : "grant_user_permission"
366 args: {
543 args: {
367 "repo_name" : "<reponame>",
544 "repo_name" : "<reponame>",
368 "user_name" : "<username>",
545 "username" : "<username>",
369 "perm" : "(None|repository.(read|write|admin))",
546 "perm" : "(repository.(none|read|write|admin))",
547 }
548
549 OUTPUT::
550
551 result: {
552 "msg" : "Granted perm: <perm> for user: <username> in repo: <reponame>"
553 }
554 error: null
555
556
557 revoke_user_permission
558 ----------------------
559
560 Revoke permission for user on given repository. This command can be executed
561 only using api_key belonging to user with admin rights.
562
563
564 INPUT::
565
566 api_key : "<api_key>"
567 method : "revoke_user_permission"
568 args: {
569 "repo_name" : "<reponame>",
570 "username" : "<username>",
370 }
571 }
371
572
372 OUTPUT::
573 OUTPUT::
373
574
374 result: None
575 result: {
576 "msg" : "Revoked perm for user: <suername> in repo: <reponame>"
577 }
375 error: null
578 error: null
376
579
377 add_users_group_to_repo
580
378 -----------------------
581 grant_users_group_permission
582 ----------------------------
379
583
380 Add a users group to a repository. This command can be executed only using
584 Grant permission for users group on given repository, or update
381 api_key belonging to user with admin rights. If "perm" is None, group will
585 existing one if found. This command can be executed only using
382 be removed from the repository.
586 api_key belonging to user with admin rights.
587
383
588
384 INPUT::
589 INPUT::
385
590
386 api_key : "<api_key>"
591 api_key : "<api_key>"
387 method : "add_users_group_to_repo"
592 method : "grant_users_group_permission"
593 args: {
594 "repo_name" : "<reponame>",
595 "group_name" : "<usersgroupname>",
596 "perm" : "(repository.(none|read|write|admin))",
597 }
598
599 OUTPUT::
600
601 result: {
602 "msg" : "Granted perm: <perm> for group: <usersgroupname> in repo: <reponame>"
603 }
604 error: null
605
606
607 revoke_users_group_permission
608 -----------------------------
609
610 Revoke permission for users group on given repository.This command can be
611 executed only using api_key belonging to user with admin rights.
612
613 INPUT::
614
615 api_key : "<api_key>"
616 method : "revoke_users_group_permission"
388 args: {
617 args: {
389 "repo_name" : "<reponame>",
618 "repo_name" : "<reponame>",
390 "group_name" : "<groupname>",
619 "users_group" : "<usersgroupname>",
391 "perm" : "(None|repository.(read|write|admin))",
620 }
392 } No newline at end of file
621
622 OUTPUT::
623
624 result: {
625 "msg" : "Revoked perm for group: <usersgroupname> in repo: <reponame>"
626 }
627 error: null No newline at end of file
@@ -6,14 +6,29 b' The :mod:`models` Module'
6 .. automodule:: rhodecode.model
6 .. automodule:: rhodecode.model
7 :members:
7 :members:
8
8
9 .. automodule:: rhodecode.model.comment
10 :members:
11
12 .. automodule:: rhodecode.model.notification
13 :members:
14
9 .. automodule:: rhodecode.model.permission
15 .. automodule:: rhodecode.model.permission
10 :members:
16 :members:
11
17
18 .. automodule:: rhodecode.model.repo_permission
19 :members:
20
12 .. automodule:: rhodecode.model.repo
21 .. automodule:: rhodecode.model.repo
13 :members:
22 :members:
14
23
24 .. automodule:: rhodecode.model.repos_group
25 :members:
26
15 .. automodule:: rhodecode.model.scm
27 .. automodule:: rhodecode.model.scm
16 :members:
28 :members:
17
29
18 .. automodule:: rhodecode.model.user
30 .. automodule:: rhodecode.model.user
19 :members:
31 :members:
32
33 .. automodule:: rhodecode.model.users_group
34 :members: No newline at end of file
@@ -4,14 +4,73 b' Changelog'
4 =========
4 =========
5
5
6
6
7 1.2.5 (**2012-01-28**)
7 1.3.0 (**2012-02-XX**)
8 ======================
8 ----------------------
9
10 :status: in-progress
11 :branch: beta
9
12
10 news
13 news
11 ----
14 ++++
15
16 - code review, inspired by github code-comments
17 - #215 rst and markdown README files support
18 - #252 Container-based and proxy pass-through authentication support
19 - #44 branch browser. Filtering of changelog by branches
20 - mercurial bookmarks support
21 - new hover top menu, optimized to add maximum size for important views
22 - configurable clone url template with possibility to specify protocol like
23 ssh:// or http:// and also manually alter other parts of clone_url.
24 - enabled largefiles extension by default
25 - optimized summary file pages and saved a lot of unused space in them
26 - #239 option to manually mark repository as fork
27 - #320 mapping of commit authors to RhodeCode users
28 - #304 hashes are displayed using monospace font
29 - diff configuration, toggle white lines and context lines
30 - #307 configurable diffs, whitespace toggle, increasing context lines
31 - sorting on branches, tags and bookmarks using YUI datatable
32 - improved file filter on files page
33 - implements #330 api method for listing nodes ar particular revision
34 - #73 added linking issues in commit messages to chosen issue tracker url
35 based on user defined regular expression
36 - added linking of changesets in commit messages
37 - new compact changelog with expandable commit messages
38 - firstname and lastname are optional in user creation
39 - #348 added post-create repository hook
40 - #212 global encoding settings is now configurable from .ini files
41 - #227 added repository groups permissions
42 - markdown gets codehilite extensions
43 - new API methods, delete_repositories, grante/revoke permissions for groups
44 and repos
45
46
47 fixes
48 +++++
49
50 - rewrote dbsession management for atomic operations, and better error handling
51 - fixed sorting of repo tables
52 - #326 escape of special html entities in diffs
53 - normalized user_name => username in api attributes
54 - fixes #298 ldap created users with mixed case emails created conflicts
55 on saving a form
56 - fixes issue when owner of a repo couldn't revoke permissions for users
57 and groups
58 - fixes #271 rare JSON serialization problem with statistics
59 - fixes #337 missing validation check for conflicting names of a group with a
60 repositories group
61 - #340 fixed session problem for mysql and celery tasks
62 - fixed #331 RhodeCode mangles repository names if the a repository group
63 contains the "full path" to the repositories
64 - #355 RhodeCode doesn't store encrypted LDAP passwords
65
66 1.2.5 (**2012-01-28**)
67 ----------------------
68
69 news
70 ++++
12
71
13 fixes
72 fixes
14 -----
73 +++++
15
74
16 - #340 Celery complains about MySQL server gone away, added session cleanup
75 - #340 Celery complains about MySQL server gone away, added session cleanup
17 for celery tasks
76 for celery tasks
@@ -24,10 +83,10 b' fixes'
24 forking on windows impossible
83 forking on windows impossible
25
84
26 1.2.4 (**2012-01-19**)
85 1.2.4 (**2012-01-19**)
27 ======================
86 ----------------------
28
87
29 news
88 news
30 ----
89 ++++
31
90
32 - RhodeCode is bundled with mercurial series 2.0.X by default, with
91 - RhodeCode is bundled with mercurial series 2.0.X by default, with
33 full support to largefiles extension. Enabled by default in new installations
92 full support to largefiles extension. Enabled by default in new installations
@@ -35,7 +94,7 b' news'
35 - added requires.txt file with requirements
94 - added requires.txt file with requirements
36
95
37 fixes
96 fixes
38 -----
97 +++++
39
98
40 - fixes db session issues with celery when emailing admins
99 - fixes db session issues with celery when emailing admins
41 - #331 RhodeCode mangles repository names if the a repository group
100 - #331 RhodeCode mangles repository names if the a repository group
@@ -52,10 +111,10 b' fixes'
52 - #316 fixes issues with web description in hgrc files
111 - #316 fixes issues with web description in hgrc files
53
112
54 1.2.3 (**2011-11-02**)
113 1.2.3 (**2011-11-02**)
55 ======================
114 ----------------------
56
115
57 news
116 news
58 ----
117 ++++
59
118
60 - added option to manage repos group for non admin users
119 - added option to manage repos group for non admin users
61 - added following API methods for get_users, create_user, get_users_groups,
120 - added following API methods for get_users, create_user, get_users_groups,
@@ -67,24 +126,23 b' news'
67 administrator users, and global config email.
126 administrator users, and global config email.
68
127
69 fixes
128 fixes
70 -----
129 +++++
71
130
72 - added option for passing auth method for smtp mailer
131 - added option for passing auth method for smtp mailer
73 - #276 issue with adding a single user with id>10 to usergroups
132 - #276 issue with adding a single user with id>10 to usergroups
74 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
133 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
75 - #288 fixes managing of repos in a group for non admin user
134 - #288 fixes managing of repos in a group for non admin user
76
135
77
78 1.2.2 (**2011-10-17**)
136 1.2.2 (**2011-10-17**)
79 ======================
137 ----------------------
80
138
81 news
139 news
82 ----
140 ++++
83
141
84 - #226 repo groups are available by path instead of numerical id
142 - #226 repo groups are available by path instead of numerical id
85
143
86 fixes
144 fixes
87 -----
145 +++++
88
146
89 - #259 Groups with the same name but with different parent group
147 - #259 Groups with the same name but with different parent group
90 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
148 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
@@ -98,27 +156,25 b' fixes'
98 - fixes #248 cannot edit repos inside a group on windows
156 - fixes #248 cannot edit repos inside a group on windows
99 - fixes #219 forking problems on windows
157 - fixes #219 forking problems on windows
100
158
101
102 1.2.1 (**2011-10-08**)
159 1.2.1 (**2011-10-08**)
103 ======================
160 ----------------------
104
161
105 news
162 news
106 ----
163 ++++
107
164
108
165
109 fixes
166 fixes
110 -----
167 +++++
111
168
112 - fixed problems with basic auth and push problems
169 - fixed problems with basic auth and push problems
113 - gui fixes
170 - gui fixes
114 - fixed logger
171 - fixed logger
115
172
116
117 1.2.0 (**2011-10-07**)
173 1.2.0 (**2011-10-07**)
118 ======================
174 ----------------------
119
175
120 news
176 news
121 ----
177 ++++
122
178
123 - implemented #47 repository groups
179 - implemented #47 repository groups
124 - implemented #89 Can setup google analytics code from settings menu
180 - implemented #89 Can setup google analytics code from settings menu
@@ -158,7 +214,7 b' news'
158 - Implemented advanced hook management
214 - Implemented advanced hook management
159
215
160 fixes
216 fixes
161 -----
217 +++++
162
218
163 - fixed file browser bug, when switching into given form revision the url was
219 - fixed file browser bug, when switching into given form revision the url was
164 not changing
220 not changing
@@ -185,18 +241,17 b' fixes'
185 - fixes #218 os.kill patch for windows was missing sig param
241 - fixes #218 os.kill patch for windows was missing sig param
186 - improved rendering of dag (they are not trimmed anymore when number of
242 - improved rendering of dag (they are not trimmed anymore when number of
187 heads exceeds 5)
243 heads exceeds 5)
188
244
189
190 1.1.8 (**2011-04-12**)
245 1.1.8 (**2011-04-12**)
191 ======================
246 ----------------------
192
247
193 news
248 news
194 ----
249 ++++
195
250
196 - improved windows support
251 - improved windows support
197
252
198 fixes
253 fixes
199 -----
254 +++++
200
255
201 - fixed #140 freeze of python dateutil library, since new version is python2.x
256 - fixed #140 freeze of python dateutil library, since new version is python2.x
202 incompatible
257 incompatible
@@ -219,40 +274,40 b' fixes'
219
274
220
275
221 1.1.7 (**2011-03-23**)
276 1.1.7 (**2011-03-23**)
222 ======================
277 ----------------------
223
278
224 news
279 news
225 ----
280 ++++
226
281
227 fixes
282 fixes
228 -----
283 +++++
229
284
230 - fixed (again) #136 installation support for FreeBSD
285 - fixed (again) #136 installation support for FreeBSD
231
286
232
287
233 1.1.6 (**2011-03-21**)
288 1.1.6 (**2011-03-21**)
234 ======================
289 ----------------------
235
290
236 news
291 news
237 ----
292 ++++
238
293
239 fixes
294 fixes
240 -----
295 +++++
241
296
242 - fixed #136 installation support for FreeBSD
297 - fixed #136 installation support for FreeBSD
243 - RhodeCode will check for python version during installation
298 - RhodeCode will check for python version during installation
244
299
245 1.1.5 (**2011-03-17**)
300 1.1.5 (**2011-03-17**)
246 ======================
301 ----------------------
247
302
248 news
303 news
249 ----
304 ++++
250
305
251 - basic windows support, by exchanging pybcrypt into sha256 for windows only
306 - basic windows support, by exchanging pybcrypt into sha256 for windows only
252 highly inspired by idea of mantis406
307 highly inspired by idea of mantis406
253
308
254 fixes
309 fixes
255 -----
310 +++++
256
311
257 - fixed sorting by author in main page
312 - fixed sorting by author in main page
258 - fixed crashes with diffs on binary files
313 - fixed crashes with diffs on binary files
@@ -264,13 +319,13 b' fixes'
264 - cleaned out docs, big thanks to Jason Harris
319 - cleaned out docs, big thanks to Jason Harris
265
320
266 1.1.4 (**2011-02-19**)
321 1.1.4 (**2011-02-19**)
267 ======================
322 ----------------------
268
323
269 news
324 news
270 ----
325 ++++
271
326
272 fixes
327 fixes
273 -----
328 +++++
274
329
275 - fixed formencode import problem on settings page, that caused server crash
330 - fixed formencode import problem on settings page, that caused server crash
276 when that page was accessed as first after server start
331 when that page was accessed as first after server start
@@ -278,17 +333,17 b' fixes'
278 - fixed option to access repository just by entering http://server/<repo_name>
333 - fixed option to access repository just by entering http://server/<repo_name>
279
334
280 1.1.3 (**2011-02-16**)
335 1.1.3 (**2011-02-16**)
281 ======================
336 ----------------------
282
337
283 news
338 news
284 ----
339 ++++
285
340
286 - implemented #102 allowing the '.' character in username
341 - implemented #102 allowing the '.' character in username
287 - added option to access repository just by entering http://server/<repo_name>
342 - added option to access repository just by entering http://server/<repo_name>
288 - celery task ignores result for better performance
343 - celery task ignores result for better performance
289
344
290 fixes
345 fixes
291 -----
346 +++++
292
347
293 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
348 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
294 apollo13 and Johan Walles
349 apollo13 and Johan Walles
@@ -304,31 +359,31 b' fixes'
304 - fixed static files paths links to use of url() method
359 - fixed static files paths links to use of url() method
305
360
306 1.1.2 (**2011-01-12**)
361 1.1.2 (**2011-01-12**)
307 ======================
362 ----------------------
308
363
309 news
364 news
310 ----
365 ++++
311
366
312
367
313 fixes
368 fixes
314 -----
369 +++++
315
370
316 - fixes #98 protection against float division of percentage stats
371 - fixes #98 protection against float division of percentage stats
317 - fixed graph bug
372 - fixed graph bug
318 - forced webhelpers version since it was making troubles during installation
373 - forced webhelpers version since it was making troubles during installation
319
374
320 1.1.1 (**2011-01-06**)
375 1.1.1 (**2011-01-06**)
321 ======================
376 ----------------------
322
377
323 news
378 news
324 ----
379 ++++
325
380
326 - added force https option into ini files for easier https usage (no need to
381 - added force https option into ini files for easier https usage (no need to
327 set server headers with this options)
382 set server headers with this options)
328 - small css updates
383 - small css updates
329
384
330 fixes
385 fixes
331 -----
386 +++++
332
387
333 - fixed #96 redirect loop on files view on repositories without changesets
388 - fixed #96 redirect loop on files view on repositories without changesets
334 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
389 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
@@ -337,16 +392,16 b' fixes'
337 - fixed #92 whoosh indexer is more error proof
392 - fixed #92 whoosh indexer is more error proof
338
393
339 1.1.0 (**2010-12-18**)
394 1.1.0 (**2010-12-18**)
340 ======================
395 ----------------------
341
396
342 news
397 news
343 ----
398 ++++
344
399
345 - rewrite of internals for vcs >=0.1.10
400 - rewrite of internals for vcs >=0.1.10
346 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
401 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
347 with older clients
402 with older clients
348 - anonymous access, authentication via ldap
403 - anonymous access, authentication via ldap
349 - performance upgrade for cached repos list - each repository has it's own
404 - performance upgrade for cached repos list - each repository has its own
350 cache that's invalidated when needed.
405 cache that's invalidated when needed.
351 - performance upgrades on repositories with large amount of commits (20K+)
406 - performance upgrades on repositories with large amount of commits (20K+)
352 - main page quick filter for filtering repositories
407 - main page quick filter for filtering repositories
@@ -365,7 +420,7 b' news'
365 - other than sqlite database backends can be used
420 - other than sqlite database backends can be used
366
421
367 fixes
422 fixes
368 -----
423 +++++
369
424
370 - fixes #61 forked repo was showing only after cache expired
425 - fixes #61 forked repo was showing only after cache expired
371 - fixes #76 no confirmation on user deletes
426 - fixes #76 no confirmation on user deletes
@@ -385,16 +440,16 b' fixes'
385
440
386
441
387 1.0.2 (**2010-11-12**)
442 1.0.2 (**2010-11-12**)
388 ======================
443 ----------------------
389
444
390 news
445 news
391 ----
446 ++++
392
447
393 - tested under python2.7
448 - tested under python2.7
394 - bumped sqlalchemy and celery versions
449 - bumped sqlalchemy and celery versions
395
450
396 fixes
451 fixes
397 -----
452 +++++
398
453
399 - fixed #59 missing graph.js
454 - fixed #59 missing graph.js
400 - fixed repo_size crash when repository had broken symlinks
455 - fixed repo_size crash when repository had broken symlinks
@@ -402,15 +457,15 b' fixes'
402
457
403
458
404 1.0.1 (**2010-11-10**)
459 1.0.1 (**2010-11-10**)
405 ======================
460 ----------------------
406
461
407 news
462 news
408 ----
463 ++++
409
464
410 - small css updated
465 - small css updated
411
466
412 fixes
467 fixes
413 -----
468 +++++
414
469
415 - fixed #53 python2.5 incompatible enumerate calls
470 - fixed #53 python2.5 incompatible enumerate calls
416 - fixed #52 disable mercurial extension for web
471 - fixed #52 disable mercurial extension for web
@@ -418,7 +473,7 b' fixes'
418
473
419
474
420 1.0.0 (**2010-11-02**)
475 1.0.0 (**2010-11-02**)
421 ======================
476 ----------------------
422
477
423 - security bugfix simplehg wasn't checking for permissions on commands
478 - security bugfix simplehg wasn't checking for permissions on commands
424 other than pull or push.
479 other than pull or push.
@@ -428,7 +483,7 b' 1.0.0 (**2010-11-02**)'
428 - permissions cached queries
483 - permissions cached queries
429
484
430 1.0.0rc4 (**2010-10-12**)
485 1.0.0rc4 (**2010-10-12**)
431 ==========================
486 --------------------------
432
487
433 - fixed python2.5 missing simplejson imports (thanks to Jens Bäckman)
488 - fixed python2.5 missing simplejson imports (thanks to Jens Bäckman)
434 - removed cache_manager settings from sqlalchemy meta
489 - removed cache_manager settings from sqlalchemy meta
@@ -438,12 +493,12 b' 1.0.0rc4 (**2010-10-12**)'
438
493
439
494
440 1.0.0rc3 (**2010-10-11**)
495 1.0.0rc3 (**2010-10-11**)
441 =========================
496 -------------------------
442
497
443 - fixed i18n during installation.
498 - fixed i18n during installation.
444
499
445 1.0.0rc2 (**2010-10-11**)
500 1.0.0rc2 (**2010-10-11**)
446 =========================
501 -------------------------
447
502
448 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
503 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
449 occure. After vcs is fixed it'll be put back again.
504 occure. After vcs is fixed it'll be put back again.
@@ -2,8 +2,8 b''
2
2
3 .. include:: ./../README.rst
3 .. include:: ./../README.rst
4
4
5 Documentation
5 Users Guide
6 -------------
6 -----------
7
7
8 **Installation:**
8 **Installation:**
9
9
@@ -20,10 +20,9 b' Documentation'
20 :maxdepth: 1
20 :maxdepth: 1
21
21
22 usage/general
22 usage/general
23 usage/enable_git
23 usage/git_support
24 usage/statistics
24 usage/statistics
25 usage/backup
25 usage/backup
26 usage/api_key_access
27
26
28 **Develop**
27 **Develop**
29
28
@@ -36,9 +35,10 b' Documentation'
36 **API**
35 **API**
37
36
38 .. toctree::
37 .. toctree::
39 :maxdepth: 2
38 :maxdepth: 1
40
39
41 api/index
40 api/api
41 api/models
42
42
43
43
44 Other topics
44 Other topics
@@ -346,6 +346,106 b' All other LDAP settings will likely be s'
346 appropriately configured.
346 appropriately configured.
347
347
348
348
349 Authentication by container or reverse-proxy
350 --------------------------------------------
351
352 Starting with version 1.3, RhodeCode supports delegating the authentication
353 of users to its WSGI container, or to a reverse-proxy server through which all
354 clients access the application.
355
356 When these authentication methods are enabled in RhodeCode, it uses the
357 username that the container/proxy (Apache/Nginx/etc) authenticated and doesn't
358 perform the authentication itself. The authorization, however, is still done by
359 RhodeCode according to its settings.
360
361 When a user logs in for the first time using these authentication methods,
362 a matching user account is created in RhodeCode with default permissions. An
363 administrator can then modify it using RhodeCode's admin interface.
364 It's also possible for an administrator to create accounts and configure their
365 permissions before the user logs in for the first time.
366
367 Container-based authentication
368 ''''''''''''''''''''''''''''''
369
370 In a container-based authentication setup, RhodeCode reads the user name from
371 the ``REMOTE_USER`` server variable provided by the WSGI container.
372
373 After setting up your container (see `Apache's WSGI config`_), you'd need
374 to configure it to require authentication on the location configured for
375 RhodeCode.
376
377 In order for RhodeCode to start using the provided username, you should set the
378 following in the [app:main] section of your .ini file::
379
380 container_auth_enabled = true
381
382
383 Proxy pass-through authentication
384 '''''''''''''''''''''''''''''''''
385
386 In a proxy pass-through authentication setup, RhodeCode reads the user name
387 from the ``X-Forwarded-User`` request header, which should be configured to be
388 sent by the reverse-proxy server.
389
390 After setting up your proxy solution (see `Apache virtual host reverse proxy example`_,
391 `Apache as subdirectory`_ or `Nginx virtual host example`_), you'd need to
392 configure the authentication and add the username in a request header named
393 ``X-Forwarded-User``.
394
395 For example, the following config section for Apache sets a subdirectory in a
396 reverse-proxy setup with basic auth::
397
398 <Location /<someprefix> >
399 ProxyPass http://127.0.0.1:5000/<someprefix>
400 ProxyPassReverse http://127.0.0.1:5000/<someprefix>
401 SetEnvIf X-Url-Scheme https HTTPS=1
402
403 AuthType Basic
404 AuthName "RhodeCode authentication"
405 AuthUserFile /home/web/rhodecode/.htpasswd
406 require valid-user
407
408 RequestHeader unset X-Forwarded-User
409
410 RewriteEngine On
411 RewriteCond %{LA-U:REMOTE_USER} (.+)
412 RewriteRule .* - [E=RU:%1]
413 RequestHeader set X-Forwarded-User %{RU}e
414 </Location>
415
416 In order for RhodeCode to start using the forwarded username, you should set
417 the following in the [app:main] section of your .ini file::
418
419 proxypass_auth_enabled = true
420
421 .. note::
422 If you enable proxy pass-through authentication, make sure your server is
423 only accessible through the proxy. Otherwise, any client would be able to
424 forge the authentication header and could effectively become authenticated
425 using any account of their liking.
426
427 Integration with Issue trackers
428 -------------------------------
429
430 RhodeCode provides a simple integration with issue trackers. It's possible
431 to define a regular expression that will fetch issue id stored in commit
432 messages and replace that with an url to this issue. To enable this simply
433 uncomment following variables in the ini file::
434
435 url_pat = (?:^#|\s#)(\w+)
436 issue_server_link = https://myissueserver.com/{repo}/issue/{id}
437 issue_prefix = #
438
439 `url_pat` is the regular expression that will fetch issues from commit messages.
440 Default regex will match issues in format of #<number> eg. #300.
441
442 Matched issues will be replace with the link specified as `issue_server_link`
443 {id} will be replaced with issue id, and {repo} with repository name.
444 Since the # is striped `issue_prefix` is added as a prefix to url.
445 `issue_prefix` can be something different than # if you pass
446 ISSUE- as issue prefix this will generate an url in format::
447
448 <a href="https://myissueserver.com/example_repo/issue/300">ISSUE-300</a>
349
449
350 Hook management
450 Hook management
351 ---------------
451 ---------------
@@ -361,6 +461,17 b' To add another custom hook simply fill i'
361 can be found at *rhodecode.lib.hooks*.
461 can be found at *rhodecode.lib.hooks*.
362
462
363
463
464 Changing default encoding
465 -------------------------
466
467 By default RhodeCode uses utf8 encoding, starting from 1.3 series this
468 can be changed, simply edit default_encoding in .ini file to desired one.
469 This affects many parts in rhodecode including commiters names, filenames,
470 encoding of commit messages. In addition RhodeCode can detect if `chardet`
471 library is installed. If `chardet` is detected RhodeCode will fallback to it
472 when there are encode/decode errors.
473
474
364 Setting Up Celery
475 Setting Up Celery
365 -----------------
476 -----------------
366
477
@@ -397,27 +508,36 b' Nginx virtual host example'
397
508
398 Sample config for nginx using proxy::
509 Sample config for nginx using proxy::
399
510
511 upstream rc {
512 server 127.0.0.1:5000;
513 # add more instances for load balancing
514 #server 127.0.0.1:5001;
515 #server 127.0.0.1:5002;
516 }
517
400 server {
518 server {
401 listen 80;
519 listen 80;
402 server_name hg.myserver.com;
520 server_name hg.myserver.com;
403 access_log /var/log/nginx/rhodecode.access.log;
521 access_log /var/log/nginx/rhodecode.access.log;
404 error_log /var/log/nginx/rhodecode.error.log;
522 error_log /var/log/nginx/rhodecode.error.log;
523
405 location / {
524 location / {
406 root /var/www/rhodecode/rhodecode/public/;
525 try_files $uri @rhode;
407 if (!-f $request_filename){
408 proxy_pass http://127.0.0.1:5000;
409 }
410 #this is important if you want to use https !!!
411 proxy_set_header X-Url-Scheme $scheme;
412 include /etc/nginx/proxy.conf;
413 }
526 }
527
528 location @rhode {
529 proxy_pass http://rc;
530 include /etc/nginx/proxy.conf;
531 }
532
414 }
533 }
415
534
416 Here's the proxy.conf. It's tuned so it will not timeout on long
535 Here's the proxy.conf. It's tuned so it will not timeout on long
417 pushes or large pushes::
536 pushes or large pushes::
418
537
419 proxy_redirect off;
538 proxy_redirect off;
420 proxy_set_header Host $host;
539 proxy_set_header Host $host;
540 proxy_set_header X-Url-Scheme $scheme;
421 proxy_set_header X-Host $http_host;
541 proxy_set_header X-Host $http_host;
422 proxy_set_header X-Real-IP $remote_addr;
542 proxy_set_header X-Real-IP $remote_addr;
423 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
543 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
@@ -1,12 +1,12 b''
1 {% extends "basic/layout.html" %}
1 {% extends "basic/layout.html" %}
2
2
3 {% block sidebarlogo %}
3 {% block sidebarlogo %}
4 <h3>Support my development effort.</h3>
4 <h3>Support RhodeCode development.</h3>
5 <div style="text-align:center">
5 <div style="text-align:center">
6 <form action="https://www.paypal.com/cgi-bin/webscr" method="post">
6 <form action="https://www.paypal.com/cgi-bin/webscr" method="post">
7 <input type="hidden" name="cmd" value="_s-xclick">
7 <input type="hidden" name="cmd" value="_s-xclick">
8 <input type="hidden" name="hosted_button_id" value="8U2LLRPLBKWDU">
8 <input type="hidden" name="hosted_button_id" value="8U2LLRPLBKWDU">
9 <input style="border:0px !important" type="image" src="https://www.paypal.com/en_US/i/btn/btn_donate_SM.gif"
9 <input style="border:0px !important" type="image" src="https://www.paypal.com/en_US/i/btn/btn_donate_SM.gif"
10 border="0" name="submit" alt="PayPal - The safer, easier way to pay online!">
10 border="0" name="submit" alt="PayPal - The safer, easier way to pay online!">
11 <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
11 <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
12 </form>
12 </form>
@@ -36,6 +36,31 b' Compare view is also available from the '
36 one changeset
36 one changeset
37
37
38
38
39 Non changeable repository urls
40 ------------------------------
41
42 Due to complicated nature of repository grouping, often urls of repositories
43 can change.
44
45 example::
46
47 #before
48 http://server.com/repo_name
49 # after insertion to test_group group the url will be
50 http://server.com/test_group/repo_name
51
52 This can be an issue for build systems and any other hardcoded scripts, moving
53 repository to a group leads to a need for changing external systems. To
54 overcome this RhodeCode introduces a non changable replacement url. It's
55 simply an repository ID prefixed with `_` above urls are also accessible as::
56
57 http://server.com/_<ID>
58
59 Since ID are always the same moving the repository will not affect such url.
60 the _<ID> syntax can be used anywhere in the system so urls with repo_name
61 for changelogs, files and other can be exchanged with _<ID> syntax.
62
63
39
64
40 Mailing
65 Mailing
41 -------
66 -------
@@ -1,13 +1,41 b''
1 .. _enable_git:
1 .. _git_support:
2
2
3 Enabling GIT support (beta)
3 GIT support
4 ===========================
4 ===========
5
5
6
6
7 Git support in RhodeCode 1.1 was disabled due to current instability issues.
7 Git support in RhodeCode 1.3 was enabled by default.
8 However,if you would like to test git support please feel free to re-enable it.
8 Although There are some limitations on git usage.
9 To re-enable GIT support just uncomment the git line in the
9
10 file **rhodecode/__init__.py**
10 - No hooks are runned for git push/pull actions.
11 - logs in action journals don't have git operations
12 - large pushes needs http server with chunked encoding support.
13
14 if you plan to use git you need to run RhodeCode with some
15 http server that supports chunked encoding which git http protocol uses,
16 i recommend using waitress_ or gunicorn_ (linux only) for `paste` wsgi app
17 replacement.
18
19 To use waitress simply change change the following in the .ini file::
20
21 use = egg:Paste#http
22
23 To::
24
25 use = egg:waitress#main
26
27 And comment out bellow options::
28
29 threadpool_workers =
30 threadpool_max_requests =
31 use_threadpool =
32
33
34 You can simply run `paster serve` as usual.
35
36
37 You can always disable git/hg support by editing a
38 file **rhodecode/__init__.py** and commenting out backends
11
39
12 .. code-block:: python
40 .. code-block:: python
13
41
@@ -16,9 +44,5 b' file **rhodecode/__init__.py**'
16 #'git': 'Git repository',
44 #'git': 'Git repository',
17 }
45 }
18
46
19 .. note::
47 .. _waitress: http://pypi.python.org/pypi/waitress
20 Please note that the git support provided by RhodeCode is not yet fully
48 .. _gunicorn: http://pypi.python.org/pypi/gunicorn No newline at end of file
21 stable and RhodeCode might crash while using git repositories. (That is why
22 it is currently disabled.) Thus be careful about enabling git support, and
23 certainly don't use it in a production setting!
24 No newline at end of file
1 NO CONTENT: modified file chmod 100755 => 100644
NO CONTENT: modified file chmod 100755 => 100644
@@ -17,6 +17,7 b' pdebug = false'
17 #error_email_from = paste_error@localhost
17 #error_email_from = paste_error@localhost
18 #app_email_from = rhodecode-noreply@localhost
18 #app_email_from = rhodecode-noreply@localhost
19 #error_message =
19 #error_message =
20 #email_prefix = [RhodeCode]
20
21
21 #smtp_server = mail.server.com
22 #smtp_server = mail.server.com
22 #smtp_username =
23 #smtp_username =
@@ -45,14 +46,52 b' port = 8001'
45 use = egg:rhodecode
46 use = egg:rhodecode
46 full_stack = true
47 full_stack = true
47 static_files = true
48 static_files = true
48 lang=en
49 lang = en
49 cache_dir = %(here)s/data
50 cache_dir = %(here)s/data
50 index_dir = %(here)s/data/index
51 index_dir = %(here)s/data/index
51 app_instance_uuid = prod1234
52 app_instance_uuid = rc-production
52 cut_off_limit = 256000
53 cut_off_limit = 256000
53 force_https = false
54 force_https = false
54 commit_parse_limit = 50
55 commit_parse_limit = 50
55 use_gravatar = true
56 use_gravatar = true
57 container_auth_enabled = false
58 proxypass_auth_enabled = false
59 default_encoding = utf8
60
61 ## overwrite schema of clone url
62 ## available vars:
63 ## scheme - http/https
64 ## user - current user
65 ## pass - password
66 ## netloc - network location
67 ## path - usually repo_name
68
69 #clone_uri = {scheme}://{user}{pass}{netloc}{path}
70
71 ## issue tracking mapping for commits messages
72 ## comment out issue_pat, issue_server, issue_prefix to enable
73
74 ## pattern to get the issues from commit messages
75 ## default one used here is #<numbers> with a regex passive group for `#`
76 ## {id} will be all groups matched from this pattern
77
78 issue_pat = (?:\s*#)(\d+)
79
80 ## server url to the issue, each {id} will be replaced with match
81 ## fetched from the regex and {repo} is replaced with repository name
82
83 issue_server_link = https://myissueserver.com/{repo}/issue/{id}
84
85 ## prefix to add to link to indicate it's an url
86 ## #314 will be replaced by <issue_prefix><id>
87
88 issue_prefix = #
89
90 ## instance-id prefix
91 ## a prefix key for this instance used for cache invalidation when running
92 ## multiple instances of rhodecode, make sure it's globally unique for
93 ## all running rhodecode instances. Leave empty if you don't use it
94 instance_id =
56
95
57 ####################################
96 ####################################
58 ### CELERY CONFIG ####
97 ### CELERY CONFIG ####
@@ -91,21 +130,27 b' beaker.cache.regions=super_short_term,sh'
91
130
92 beaker.cache.super_short_term.type=memory
131 beaker.cache.super_short_term.type=memory
93 beaker.cache.super_short_term.expire=10
132 beaker.cache.super_short_term.expire=10
133 beaker.cache.super_short_term.key_length = 256
94
134
95 beaker.cache.short_term.type=memory
135 beaker.cache.short_term.type=memory
96 beaker.cache.short_term.expire=60
136 beaker.cache.short_term.expire=60
137 beaker.cache.short_term.key_length = 256
97
138
98 beaker.cache.long_term.type=memory
139 beaker.cache.long_term.type=memory
99 beaker.cache.long_term.expire=36000
140 beaker.cache.long_term.expire=36000
141 beaker.cache.long_term.key_length = 256
100
142
101 beaker.cache.sql_cache_short.type=memory
143 beaker.cache.sql_cache_short.type=memory
102 beaker.cache.sql_cache_short.expire=10
144 beaker.cache.sql_cache_short.expire=10
145 beaker.cache.sql_cache_short.key_length = 256
103
146
104 beaker.cache.sql_cache_med.type=memory
147 beaker.cache.sql_cache_med.type=memory
105 beaker.cache.sql_cache_med.expire=360
148 beaker.cache.sql_cache_med.expire=360
149 beaker.cache.sql_cache_med.key_length = 256
106
150
107 beaker.cache.sql_cache_long.type=file
151 beaker.cache.sql_cache_long.type=file
108 beaker.cache.sql_cache_long.expire=3600
152 beaker.cache.sql_cache_long.expire=3600
153 beaker.cache.sql_cache_long.key_length = 256
109
154
110 ####################################
155 ####################################
111 ### BEAKER SESSION ####
156 ### BEAKER SESSION ####
@@ -113,12 +158,27 b' beaker.cache.sql_cache_long.expire=3600'
113 ## Type of storage used for the session, current types are
158 ## Type of storage used for the session, current types are
114 ## dbm, file, memcached, database, and memory.
159 ## dbm, file, memcached, database, and memory.
115 ## The storage uses the Container API
160 ## The storage uses the Container API
116 ##that is also used by the cache system.
161 ## that is also used by the cache system.
117 beaker.session.type = file
162
163 ## db session example
164
165 #beaker.session.type = ext:database
166 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/rhodecode
167 #beaker.session.table_name = db_session
168
169 ## encrypted cookie session, good for many instances
170 #beaker.session.type = cookie
118
171
172 beaker.session.type = file
119 beaker.session.key = rhodecode
173 beaker.session.key = rhodecode
120 beaker.session.secret = g654dcno0-9873jhgfreyu
174 # secure cookie requires AES python libraries
175 #beaker.session.encrypt_key = g654dcno0-9873jhgfreyu
176 #beaker.session.validate_key = 9712sds2212c--zxc123
121 beaker.session.timeout = 36000
177 beaker.session.timeout = 36000
178 beaker.session.httponly = true
179
180 ## uncomment for https secure cookie
181 beaker.session.secure = false
122
182
123 ##auto save the session to not to use .save()
183 ##auto save the session to not to use .save()
124 beaker.session.auto = False
184 beaker.session.auto = False
@@ -126,7 +186,7 b' beaker.session.auto = False'
126 ##true exire at browser close
186 ##true exire at browser close
127 #beaker.session.cookie_expires = 3600
187 #beaker.session.cookie_expires = 3600
128
188
129
189
130 ################################################################################
190 ################################################################################
131 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
191 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
132 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
192 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
@@ -232,4 +292,4 b' datefmt = %Y-%m-%d %H:%M:%S'
232 [formatter_color_formatter_sql]
292 [formatter_color_formatter_sql]
233 class=rhodecode.lib.colored_formatter.ColorFormatterSql
293 class=rhodecode.lib.colored_formatter.ColorFormatterSql
234 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
294 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
235 datefmt = %Y-%m-%d %H:%M:%S No newline at end of file
295 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,16 +1,17 b''
1 Pylons==1.0.0
1 Pylons==1.0.0
2 Beaker==1.5.4
2 Beaker==1.6.2
3 WebHelpers>=1.2
3 WebHelpers>=1.2
4 formencode==1.2.4
4 formencode==1.2.4
5 SQLAlchemy==0.7.4
5 SQLAlchemy==0.7.4
6 Mako==0.5.0
6 Mako==0.5.0
7 pygments>=1.4
7 pygments>=1.4
8 whoosh<1.8
8 whoosh>=2.3.0,<2.4
9 celery>=2.2.5,<2.3
9 celery>=2.2.5,<2.3
10 babel
10 babel
11 python-dateutil>=1.5.0,<2.0.0
11 python-dateutil>=1.5.0,<2.0.0
12 dulwich>=0.8.0,<0.9.0
12 dulwich>=0.8.0,<0.9.0
13 vcs==0.2.2
14 webob==1.0.8
13 webob==1.0.8
14 markdown==2.1.1
15 docutils==0.8.1
15 py-bcrypt
16 py-bcrypt
16 mercurial==2.0.2 No newline at end of file
17 mercurial>=2.1,<2.2 No newline at end of file
@@ -26,9 +26,9 b''
26 import sys
26 import sys
27 import platform
27 import platform
28
28
29 VERSION = (1, 2, 5)
29 VERSION = (1, 3, 0)
30 __version__ = '.'.join((str(each) for each in VERSION[:4]))
30 __version__ = '.'.join((str(each) for each in VERSION[:4]))
31 __dbversion__ = 3 # defines current db version for migrations
31 __dbversion__ = 5 # defines current db version for migrations
32 __platform__ = platform.system()
32 __platform__ = platform.system()
33 __license__ = 'GPLv3'
33 __license__ = 'GPLv3'
34 __py_version__ = sys.version_info
34 __py_version__ = sys.version_info
@@ -38,19 +38,20 b" PLATFORM_OTHERS = ('Linux', 'Darwin', 'F"
38
38
39 requirements = [
39 requirements = [
40 "Pylons==1.0.0",
40 "Pylons==1.0.0",
41 "Beaker==1.5.4",
41 "Beaker==1.6.2",
42 "WebHelpers>=1.2",
42 "WebHelpers>=1.2",
43 "formencode==1.2.4",
43 "formencode==1.2.4",
44 "SQLAlchemy==0.7.4",
44 "SQLAlchemy==0.7.4",
45 "Mako==0.5.0",
45 "Mako==0.5.0",
46 "pygments>=1.4",
46 "pygments>=1.4",
47 "whoosh<1.8",
47 "whoosh>=2.3.0,<2.4",
48 "celery>=2.2.5,<2.3",
48 "celery>=2.2.5,<2.3",
49 "babel",
49 "babel",
50 "python-dateutil>=1.5.0,<2.0.0",
50 "python-dateutil>=1.5.0,<2.0.0",
51 "dulwich>=0.8.0,<0.9.0",
51 "dulwich>=0.8.0,<0.9.0",
52 "vcs==0.2.2",
52 "webob==1.0.8",
53 "webob==1.0.8"
53 "markdown==2.1.1",
54 "docutils==0.8.1",
54 ]
55 ]
55
56
56 if __py_version__ < (2, 6):
57 if __py_version__ < (2, 6):
@@ -58,15 +59,15 b' if __py_version__ < (2, 6):'
58 requirements.append("pysqlite")
59 requirements.append("pysqlite")
59
60
60 if __platform__ in PLATFORM_WIN:
61 if __platform__ in PLATFORM_WIN:
61 requirements.append("mercurial==2.0.1")
62 requirements.append("mercurial>=2.1,<2.2")
62 else:
63 else:
63 requirements.append("py-bcrypt")
64 requirements.append("py-bcrypt")
64 requirements.append("mercurial==2.0.2")
65 requirements.append("mercurial>=2.1,<2.2")
65
66
66
67
67 try:
68 try:
68 from rhodecode.lib import get_current_revision
69 from rhodecode.lib import get_current_revision
69 _rev = get_current_revision(quiet=True)
70 _rev = get_current_revision()
70 except ImportError:
71 except ImportError:
71 # this is needed when doing some setup.py operations
72 # this is needed when doing some setup.py operations
72 _rev = False
73 _rev = False
@@ -82,5 +83,10 b' def get_version():'
82
83
83 BACKENDS = {
84 BACKENDS = {
84 'hg': 'Mercurial repository',
85 'hg': 'Mercurial repository',
85 #'git': 'Git repository',
86 'git': 'Git repository',
86 }
87 }
88
89 CELERY_ON = False
90
91 # link to config for pylons
92 CONFIG = {}
@@ -17,6 +17,7 b' pdebug = false'
17 #error_email_from = paste_error@localhost
17 #error_email_from = paste_error@localhost
18 #app_email_from = rhodecode-noreply@localhost
18 #app_email_from = rhodecode-noreply@localhost
19 #error_message =
19 #error_message =
20 #email_prefix = [RhodeCode]
20
21
21 #smtp_server = mail.server.com
22 #smtp_server = mail.server.com
22 #smtp_username =
23 #smtp_username =
@@ -45,14 +46,52 b' port = 5000'
45 use = egg:rhodecode
46 use = egg:rhodecode
46 full_stack = true
47 full_stack = true
47 static_files = true
48 static_files = true
48 lang=en
49 lang = en
49 cache_dir = %(here)s/data
50 cache_dir = %(here)s/data
50 index_dir = %(here)s/data/index
51 index_dir = %(here)s/data/index
51 app_instance_uuid = ${app_instance_uuid}
52 app_instance_uuid = ${app_instance_uuid}
52 cut_off_limit = 256000
53 cut_off_limit = 256000
53 force_https = false
54 force_https = false
54 commit_parse_limit = 50
55 commit_parse_limit = 50
55 use_gravatar = true
56 use_gravatar = true
57 container_auth_enabled = false
58 proxypass_auth_enabled = false
59 default_encoding = utf8
60
61 ## overwrite schema of clone url
62 ## available vars:
63 ## scheme - http/https
64 ## user - current user
65 ## pass - password
66 ## netloc - network location
67 ## path - usually repo_name
68
69 #clone_uri = {scheme}://{user}{pass}{netloc}{path}
70
71 ## issue tracking mapping for commits messages
72 ## comment out issue_pat, issue_server, issue_prefix to enable
73
74 ## pattern to get the issues from commit messages
75 ## default one used here is #<numbers> with a regex passive group for `#`
76 ## {id} will be all groups matched from this pattern
77
78 issue_pat = (?:\s*#)(\d+)
79
80 ## server url to the issue, each {id} will be replaced with match
81 ## fetched from the regex and {repo} is replaced with repository name
82
83 issue_server_link = https://myissueserver.com/{repo}/issue/{id}
84
85 ## prefix to add to link to indicate it's an url
86 ## #314 will be replaced by <issue_prefix><id>
87
88 issue_prefix = #
89
90 ## instance-id prefix
91 ## a prefix key for this instance used for cache invalidation when running
92 ## multiple instances of rhodecode, make sure it's globally unique for
93 ## all running rhodecode instances. Leave empty if you don't use it
94 instance_id =
56
95
57 ####################################
96 ####################################
58 ### CELERY CONFIG ####
97 ### CELERY CONFIG ####
@@ -91,21 +130,27 b' beaker.cache.regions=super_short_term,sh'
91
130
92 beaker.cache.super_short_term.type=memory
131 beaker.cache.super_short_term.type=memory
93 beaker.cache.super_short_term.expire=10
132 beaker.cache.super_short_term.expire=10
133 beaker.cache.super_short_term.key_length = 256
94
134
95 beaker.cache.short_term.type=memory
135 beaker.cache.short_term.type=memory
96 beaker.cache.short_term.expire=60
136 beaker.cache.short_term.expire=60
137 beaker.cache.short_term.key_length = 256
97
138
98 beaker.cache.long_term.type=memory
139 beaker.cache.long_term.type=memory
99 beaker.cache.long_term.expire=36000
140 beaker.cache.long_term.expire=36000
141 beaker.cache.long_term.key_length = 256
100
142
101 beaker.cache.sql_cache_short.type=memory
143 beaker.cache.sql_cache_short.type=memory
102 beaker.cache.sql_cache_short.expire=10
144 beaker.cache.sql_cache_short.expire=10
145 beaker.cache.sql_cache_short.key_length = 256
103
146
104 beaker.cache.sql_cache_med.type=memory
147 beaker.cache.sql_cache_med.type=memory
105 beaker.cache.sql_cache_med.expire=360
148 beaker.cache.sql_cache_med.expire=360
149 beaker.cache.sql_cache_med.key_length = 256
106
150
107 beaker.cache.sql_cache_long.type=file
151 beaker.cache.sql_cache_long.type=file
108 beaker.cache.sql_cache_long.expire=3600
152 beaker.cache.sql_cache_long.expire=3600
153 beaker.cache.sql_cache_long.key_length = 256
109
154
110 ####################################
155 ####################################
111 ### BEAKER SESSION ####
156 ### BEAKER SESSION ####
@@ -113,12 +158,27 b' beaker.cache.sql_cache_long.expire=3600'
113 ## Type of storage used for the session, current types are
158 ## Type of storage used for the session, current types are
114 ## dbm, file, memcached, database, and memory.
159 ## dbm, file, memcached, database, and memory.
115 ## The storage uses the Container API
160 ## The storage uses the Container API
116 ##that is also used by the cache system.
161 ## that is also used by the cache system.
117 beaker.session.type = file
162
163 ## db session example
164
165 #beaker.session.type = ext:database
166 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/rhodecode
167 #beaker.session.table_name = db_session
168
169 ## encrypted cookie session, good for many instances
170 #beaker.session.type = cookie
118
171
172 beaker.session.type = file
119 beaker.session.key = rhodecode
173 beaker.session.key = rhodecode
120 beaker.session.secret = ${app_instance_secret}
174 # secure cookie requires AES python libraries
175 #beaker.session.encrypt_key = ${app_instance_secret}
176 #beaker.session.validate_key = ${app_instance_secret}
121 beaker.session.timeout = 36000
177 beaker.session.timeout = 36000
178 beaker.session.httponly = true
179
180 ## uncomment for https secure cookie
181 beaker.session.secure = false
122
182
123 ##auto save the session to not to use .save()
183 ##auto save the session to not to use .save()
124 beaker.session.auto = False
184 beaker.session.auto = False
@@ -126,7 +186,7 b' beaker.session.auto = False'
126 ##true exire at browser close
186 ##true exire at browser close
127 #beaker.session.cookie_expires = 3600
187 #beaker.session.cookie_expires = 3600
128
188
129
189
130 ################################################################################
190 ################################################################################
131 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
191 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
132 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
192 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
@@ -154,6 +214,7 b' sqlalchemy.db1.url = sqlite:///%(here)s/'
154 # MySQL
214 # MySQL
155 # sqlalchemy.db1.url = mysql://user:pass@localhost/rhodecode
215 # sqlalchemy.db1.url = mysql://user:pass@localhost/rhodecode
156
216
217 # see sqlalchemy docs for others
157
218
158 sqlalchemy.db1.echo = false
219 sqlalchemy.db1.echo = false
159 sqlalchemy.db1.pool_recycle = 3600
220 sqlalchemy.db1.pool_recycle = 3600
@@ -217,13 +278,13 b' propagate = 0'
217 class = StreamHandler
278 class = StreamHandler
218 args = (sys.stderr,)
279 args = (sys.stderr,)
219 level = INFO
280 level = INFO
220 formatter = color_formatter
281 formatter = generic
221
282
222 [handler_console_sql]
283 [handler_console_sql]
223 class = StreamHandler
284 class = StreamHandler
224 args = (sys.stderr,)
285 args = (sys.stderr,)
225 level = WARN
286 level = WARN
226 formatter = color_formatter_sql
287 formatter = generic
227
288
228 ################
289 ################
229 ## FORMATTERS ##
290 ## FORMATTERS ##
@@ -241,4 +302,4 b' datefmt = %Y-%m-%d %H:%M:%S'
241 [formatter_color_formatter_sql]
302 [formatter_color_formatter_sql]
242 class=rhodecode.lib.colored_formatter.ColorFormatterSql
303 class=rhodecode.lib.colored_formatter.ColorFormatterSql
243 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
304 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
244 datefmt = %Y-%m-%d %H:%M:%S No newline at end of file
305 datefmt = %Y-%m-%d %H:%M:%S
@@ -7,13 +7,14 b' from mako.lookup import TemplateLookup'
7 from pylons.configuration import PylonsConfig
7 from pylons.configuration import PylonsConfig
8 from pylons.error import handle_mako_error
8 from pylons.error import handle_mako_error
9
9
10 import rhodecode
10 import rhodecode.lib.app_globals as app_globals
11 import rhodecode.lib.app_globals as app_globals
11 import rhodecode.lib.helpers
12 import rhodecode.lib.helpers
12
13
13 from rhodecode.config.routing import make_map
14 from rhodecode.config.routing import make_map
14 from rhodecode.lib import celerypylons
15 # don't remove this import it does magic for celery
16 from rhodecode.lib import celerypylons, str2bool
15 from rhodecode.lib import engine_from_config
17 from rhodecode.lib import engine_from_config
16 from rhodecode.lib.timerproxy import TimerProxy
17 from rhodecode.lib.auth import set_available_permissions
18 from rhodecode.lib.auth import set_available_permissions
18 from rhodecode.lib.utils import repo2db_mapper, make_ui, set_rhodecode_config
19 from rhodecode.lib.utils import repo2db_mapper, make_ui, set_rhodecode_config
19 from rhodecode.model import init_model
20 from rhodecode.model import init_model
@@ -38,10 +39,13 b' def load_environment(global_conf, app_co'
38 # Initialize config with the basic options
39 # Initialize config with the basic options
39 config.init_app(global_conf, app_conf, package='rhodecode', paths=paths)
40 config.init_app(global_conf, app_conf, package='rhodecode', paths=paths)
40
41
42 # store some globals into rhodecode
43 rhodecode.CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
44
41 config['routes.map'] = make_map(config)
45 config['routes.map'] = make_map(config)
42 config['pylons.app_globals'] = app_globals.Globals(config)
46 config['pylons.app_globals'] = app_globals.Globals(config)
43 config['pylons.h'] = rhodecode.lib.helpers
47 config['pylons.h'] = rhodecode.lib.helpers
44
48 rhodecode.CONFIG = config
45 # Setup cache object as early as possible
49 # Setup cache object as early as possible
46 import pylons
50 import pylons
47 pylons.cache._push_object(config['pylons.app_globals'].cache)
51 pylons.cache._push_object(config['pylons.app_globals'].cache)
@@ -54,7 +58,7 b' def load_environment(global_conf, app_co'
54 input_encoding='utf-8', default_filters=['escape'],
58 input_encoding='utf-8', default_filters=['escape'],
55 imports=['from webhelpers.html import escape'])
59 imports=['from webhelpers.html import escape'])
56
60
57 #sets the c attribute access when don't existing attribute are accessed
61 # sets the c attribute access when don't existing attribute are accessed
58 config['pylons.strict_tmpl_context'] = True
62 config['pylons.strict_tmpl_context'] = True
59 test = os.path.split(config['__file__'])[-1] == 'test.ini'
63 test = os.path.split(config['__file__'])[-1] == 'test.ini'
60 if test:
64 if test:
@@ -63,7 +67,7 b' def load_environment(global_conf, app_co'
63 create_test_env(TESTS_TMP_PATH, config)
67 create_test_env(TESTS_TMP_PATH, config)
64 create_test_index(TESTS_TMP_PATH, config, True)
68 create_test_index(TESTS_TMP_PATH, config, True)
65
69
66 #MULTIPLE DB configs
70 # MULTIPLE DB configs
67 # Setup the SQLAlchemy database engine
71 # Setup the SQLAlchemy database engine
68 sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.')
72 sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.')
69
73
@@ -77,4 +81,7 b' def load_environment(global_conf, app_co'
77 # CONFIGURATION OPTIONS HERE (note: all config options will override
81 # CONFIGURATION OPTIONS HERE (note: all config options will override
78 # any Pylons config options)
82 # any Pylons config options)
79
83
84 # store config reference into our module to skip import magic of
85 # pylons
86 rhodecode.CONFIG.update(config)
80 return config
87 return config
@@ -51,15 +51,16 b' def make_app(global_conf, full_stack=Tru'
51 from rhodecode.lib.profiler import ProfilingMiddleware
51 from rhodecode.lib.profiler import ProfilingMiddleware
52 app = ProfilingMiddleware(app)
52 app = ProfilingMiddleware(app)
53
53
54 # we want our low level middleware to get to the request ASAP. We don't
54 if asbool(full_stack):
55 # need any pylons stack middleware in them
56 app = SimpleHg(app, config)
57 app = SimpleGit(app, config)
58
55
59 if asbool(full_stack):
60 # Handle Python exceptions
56 # Handle Python exceptions
61 app = ErrorHandler(app, global_conf, **config['pylons.errorware'])
57 app = ErrorHandler(app, global_conf, **config['pylons.errorware'])
62
58
59 # we want our low level middleware to get to the request ASAP. We don't
60 # need any pylons stack middleware in them
61 app = SimpleHg(app, config)
62 app = SimpleGit(app, config)
63
63 # Display error documents for 401, 403, 404 status codes (and
64 # Display error documents for 401, 403, 404 status codes (and
64 # 500 when debug is disabled)
65 # 500 when debug is disabled)
65 if asbool(config['debug']):
66 if asbool(config['debug']):
@@ -8,7 +8,6 b' refer to the routes manual at http://rou'
8 from __future__ import with_statement
8 from __future__ import with_statement
9 from routes import Mapper
9 from routes import Mapper
10
10
11
12 # prefix for non repository related links needs to be prefixed with `/`
11 # prefix for non repository related links needs to be prefixed with `/`
13 ADMIN_PREFIX = '/_admin'
12 ADMIN_PREFIX = '/_admin'
14
13
@@ -26,18 +25,27 b' def make_map(config):'
26 def check_repo(environ, match_dict):
25 def check_repo(environ, match_dict):
27 """
26 """
28 check for valid repository for proper 404 handling
27 check for valid repository for proper 404 handling
29
28
30 :param environ:
29 :param environ:
31 :param match_dict:
30 :param match_dict:
32 """
31 """
32 from rhodecode.model.db import Repository
33 repo_name = match_dict.get('repo_name')
33
34
34 repo_name = match_dict.get('repo_name')
35 try:
36 by_id = repo_name.split('_')
37 if len(by_id) == 2 and by_id[1].isdigit():
38 repo_name = Repository.get(by_id[1]).repo_name
39 match_dict['repo_name'] = repo_name
40 except:
41 pass
42
35 return is_valid_repo(repo_name, config['base_path'])
43 return is_valid_repo(repo_name, config['base_path'])
36
44
37 def check_group(environ, match_dict):
45 def check_group(environ, match_dict):
38 """
46 """
39 check for valid repositories group for proper 404 handling
47 check for valid repositories group for proper 404 handling
40
48
41 :param environ:
49 :param environ:
42 :param match_dict:
50 :param match_dict:
43 """
51 """
@@ -45,7 +53,6 b' def make_map(config):'
45
53
46 return is_valid_repos_group(repos_group_name, config['base_path'])
54 return is_valid_repos_group(repos_group_name, config['base_path'])
47
55
48
49 def check_int(environ, match_dict):
56 def check_int(environ, match_dict):
50 return match_dict.get('id').isdigit()
57 return match_dict.get('id').isdigit()
51
58
@@ -62,9 +69,14 b' def make_map(config):'
62 rmap.connect('home', '/', controller='home', action='index')
69 rmap.connect('home', '/', controller='home', action='index')
63 rmap.connect('repo_switcher', '/repos', controller='home',
70 rmap.connect('repo_switcher', '/repos', controller='home',
64 action='repo_switcher')
71 action='repo_switcher')
72 rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*}',
73 controller='home', action='branch_tag_switcher')
65 rmap.connect('bugtracker',
74 rmap.connect('bugtracker',
66 "http://bitbucket.org/marcinkuzminski/rhodecode/issues",
75 "http://bitbucket.org/marcinkuzminski/rhodecode/issues",
67 _static=True)
76 _static=True)
77 rmap.connect('rst_help',
78 "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
79 _static=True)
68 rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
80 rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
69
81
70 #ADMIN REPOSITORY REST ROUTES
82 #ADMIN REPOSITORY REST ROUTES
@@ -101,8 +113,9 b' def make_map(config):'
101 function=check_repo))
113 function=check_repo))
102 #ajax delete repo perm user
114 #ajax delete repo perm user
103 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*}",
115 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*}",
104 action="delete_perm_user", conditions=dict(method=["DELETE"],
116 action="delete_perm_user",
105 function=check_repo))
117 conditions=dict(method=["DELETE"], function=check_repo))
118
106 #ajax delete repo perm users_group
119 #ajax delete repo perm users_group
107 m.connect('delete_repo_users_group',
120 m.connect('delete_repo_users_group',
108 "/repos_delete_users_group/{repo_name:.*}",
121 "/repos_delete_users_group/{repo_name:.*}",
@@ -111,18 +124,20 b' def make_map(config):'
111
124
112 #settings actions
125 #settings actions
113 m.connect('repo_stats', "/repos_stats/{repo_name:.*}",
126 m.connect('repo_stats', "/repos_stats/{repo_name:.*}",
114 action="repo_stats", conditions=dict(method=["DELETE"],
127 action="repo_stats", conditions=dict(method=["DELETE"],
115 function=check_repo))
128 function=check_repo))
116 m.connect('repo_cache', "/repos_cache/{repo_name:.*}",
129 m.connect('repo_cache', "/repos_cache/{repo_name:.*}",
117 action="repo_cache", conditions=dict(method=["DELETE"],
130 action="repo_cache", conditions=dict(method=["DELETE"],
131 function=check_repo))
132 m.connect('repo_public_journal', "/repos_public_journal/{repo_name:.*}",
133 action="repo_public_journal", conditions=dict(method=["PUT"],
118 function=check_repo))
134 function=check_repo))
119 m.connect('repo_public_journal',
120 "/repos_public_journal/{repo_name:.*}",
121 action="repo_public_journal", conditions=dict(method=["PUT"],
122 function=check_repo))
123 m.connect('repo_pull', "/repo_pull/{repo_name:.*}",
135 m.connect('repo_pull', "/repo_pull/{repo_name:.*}",
124 action="repo_pull", conditions=dict(method=["PUT"],
136 action="repo_pull", conditions=dict(method=["PUT"],
125 function=check_repo))
137 function=check_repo))
138 m.connect('repo_as_fork', "/repo_as_fork/{repo_name:.*}",
139 action="repo_as_fork", conditions=dict(method=["PUT"],
140 function=check_repo))
126
141
127 with rmap.submapper(path_prefix=ADMIN_PREFIX,
142 with rmap.submapper(path_prefix=ADMIN_PREFIX,
128 controller='admin/repos_groups') as m:
143 controller='admin/repos_groups') as m:
@@ -155,6 +170,17 b' def make_map(config):'
155 m.connect("formatted_repos_group", "/repos_groups/{id}.{format}",
170 m.connect("formatted_repos_group", "/repos_groups/{id}.{format}",
156 action="show", conditions=dict(method=["GET"],
171 action="show", conditions=dict(method=["GET"],
157 function=check_int))
172 function=check_int))
173 # ajax delete repos group perm user
174 m.connect('delete_repos_group_user_perm',
175 "/delete_repos_group_user_perm/{group_name:.*}",
176 action="delete_repos_group_user_perm",
177 conditions=dict(method=["DELETE"], function=check_group))
178
179 # ajax delete repos group perm users_group
180 m.connect('delete_repos_group_users_group_perm',
181 "/delete_repos_group_users_group_perm/{group_name:.*}",
182 action="delete_repos_group_users_group_perm",
183 conditions=dict(method=["DELETE"], function=check_group))
158
184
159 #ADMIN USER REST ROUTES
185 #ADMIN USER REST ROUTES
160 with rmap.submapper(path_prefix=ADMIN_PREFIX,
186 with rmap.submapper(path_prefix=ADMIN_PREFIX,
@@ -267,6 +293,34 b' def make_map(config):'
267 m.connect("admin_settings_create_repository", "/create_repository",
293 m.connect("admin_settings_create_repository", "/create_repository",
268 action="create_repository", conditions=dict(method=["GET"]))
294 action="create_repository", conditions=dict(method=["GET"]))
269
295
296 #NOTIFICATION REST ROUTES
297 with rmap.submapper(path_prefix=ADMIN_PREFIX,
298 controller='admin/notifications') as m:
299 m.connect("notifications", "/notifications",
300 action="create", conditions=dict(method=["POST"]))
301 m.connect("notifications", "/notifications",
302 action="index", conditions=dict(method=["GET"]))
303 m.connect("notifications_mark_all_read", "/notifications/mark_all_read",
304 action="mark_all_read", conditions=dict(method=["GET"]))
305 m.connect("formatted_notifications", "/notifications.{format}",
306 action="index", conditions=dict(method=["GET"]))
307 m.connect("new_notification", "/notifications/new",
308 action="new", conditions=dict(method=["GET"]))
309 m.connect("formatted_new_notification", "/notifications/new.{format}",
310 action="new", conditions=dict(method=["GET"]))
311 m.connect("/notification/{notification_id}",
312 action="update", conditions=dict(method=["PUT"]))
313 m.connect("/notification/{notification_id}",
314 action="delete", conditions=dict(method=["DELETE"]))
315 m.connect("edit_notification", "/notification/{notification_id}/edit",
316 action="edit", conditions=dict(method=["GET"]))
317 m.connect("formatted_edit_notification",
318 "/notification/{notification_id}.{format}/edit",
319 action="edit", conditions=dict(method=["GET"]))
320 m.connect("notification", "/notification/{notification_id}",
321 action="show", conditions=dict(method=["GET"]))
322 m.connect("formatted_notification", "/notifications/{notification_id}.{format}",
323 action="show", conditions=dict(method=["GET"]))
270
324
271 #ADMIN MAIN PAGES
325 #ADMIN MAIN PAGES
272 with rmap.submapper(path_prefix=ADMIN_PREFIX,
326 with rmap.submapper(path_prefix=ADMIN_PREFIX,
@@ -276,13 +330,12 b' def make_map(config):'
276 action='add_repo')
330 action='add_repo')
277
331
278 #==========================================================================
332 #==========================================================================
279 # API V1
333 # API V2
280 #==========================================================================
334 #==========================================================================
281 with rmap.submapper(path_prefix=ADMIN_PREFIX,
335 with rmap.submapper(path_prefix=ADMIN_PREFIX,
282 controller='api/api') as m:
336 controller='api/api') as m:
283 m.connect('api', '/api')
337 m.connect('api', '/api')
284
338
285
286 #USER JOURNAL
339 #USER JOURNAL
287 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX, controller='journal')
340 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX, controller='journal')
288
341
@@ -344,6 +397,16 b' def make_map(config):'
344 controller='changeset', revision='tip',
397 controller='changeset', revision='tip',
345 conditions=dict(function=check_repo))
398 conditions=dict(function=check_repo))
346
399
400 rmap.connect('changeset_comment',
401 '/{repo_name:.*}/changeset/{revision}/comment',
402 controller='changeset', revision='tip', action='comment',
403 conditions=dict(function=check_repo))
404
405 rmap.connect('changeset_comment_delete',
406 '/{repo_name:.*}/changeset/comment/{comment_id}/delete',
407 controller='changeset', action='delete_comment',
408 conditions=dict(function=check_repo, method=["DELETE"]))
409
347 rmap.connect('raw_changeset_home',
410 rmap.connect('raw_changeset_home',
348 '/{repo_name:.*}/raw-changeset/{revision}',
411 '/{repo_name:.*}/raw-changeset/{revision}',
349 controller='changeset', action='raw_changeset',
412 controller='changeset', action='raw_changeset',
@@ -361,6 +424,9 b' def make_map(config):'
361 rmap.connect('tags_home', '/{repo_name:.*}/tags',
424 rmap.connect('tags_home', '/{repo_name:.*}/tags',
362 controller='tags', conditions=dict(function=check_repo))
425 controller='tags', conditions=dict(function=check_repo))
363
426
427 rmap.connect('bookmarks_home', '/{repo_name:.*}/bookmarks',
428 controller='bookmarks', conditions=dict(function=check_repo))
429
364 rmap.connect('changelog_home', '/{repo_name:.*}/changelog',
430 rmap.connect('changelog_home', '/{repo_name:.*}/changelog',
365 controller='changelog', conditions=dict(function=check_repo))
431 controller='changelog', conditions=dict(function=check_repo))
366
432
@@ -423,19 +489,19 b' def make_map(config):'
423 conditions=dict(function=check_repo))
489 conditions=dict(function=check_repo))
424
490
425 rmap.connect('repo_fork_create_home', '/{repo_name:.*}/fork',
491 rmap.connect('repo_fork_create_home', '/{repo_name:.*}/fork',
426 controller='settings', action='fork_create',
492 controller='forks', action='fork_create',
427 conditions=dict(function=check_repo, method=["POST"]))
493 conditions=dict(function=check_repo, method=["POST"]))
428
494
429 rmap.connect('repo_fork_home', '/{repo_name:.*}/fork',
495 rmap.connect('repo_fork_home', '/{repo_name:.*}/fork',
430 controller='settings', action='fork',
496 controller='forks', action='fork',
431 conditions=dict(function=check_repo))
497 conditions=dict(function=check_repo))
432
498
499 rmap.connect('repo_forks_home', '/{repo_name:.*}/forks',
500 controller='forks', action='forks',
501 conditions=dict(function=check_repo))
502
433 rmap.connect('repo_followers_home', '/{repo_name:.*}/followers',
503 rmap.connect('repo_followers_home', '/{repo_name:.*}/followers',
434 controller='followers', action='followers',
504 controller='followers', action='followers',
435 conditions=dict(function=check_repo))
505 conditions=dict(function=check_repo))
436
506
437 rmap.connect('repo_forks_home', '/{repo_name:.*}/forks',
438 controller='forks', action='forks',
439 conditions=dict(function=check_repo))
440
441 return rmap
507 return rmap
@@ -7,7 +7,7 b''
7
7
8 :created_on: Apr 7, 2010
8 :created_on: Apr 7, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
@@ -7,7 +7,7 b''
7
7
8 :created_on: Nov 26, 2010
8 :created_on: Nov 26, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
@@ -39,7 +39,7 b' from rhodecode.lib import helpers as h'
39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
40 from rhodecode.lib.exceptions import LdapImportError
40 from rhodecode.lib.exceptions import LdapImportError
41 from rhodecode.model.forms import LdapSettingsForm
41 from rhodecode.model.forms import LdapSettingsForm
42 from rhodecode.model.db import RhodeCodeSettings
42 from rhodecode.model.db import RhodeCodeSetting
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
@@ -83,7 +83,7 b' class LdapSettingsController(BaseControl'
83 super(LdapSettingsController, self).__before__()
83 super(LdapSettingsController, self).__before__()
84
84
85 def index(self):
85 def index(self):
86 defaults = RhodeCodeSettings.get_ldap_settings()
86 defaults = RhodeCodeSetting.get_ldap_settings()
87 c.search_scope_cur = defaults.get('ldap_search_scope')
87 c.search_scope_cur = defaults.get('ldap_search_scope')
88 c.tls_reqcert_cur = defaults.get('ldap_tls_reqcert')
88 c.tls_reqcert_cur = defaults.get('ldap_tls_reqcert')
89 c.tls_kind_cur = defaults.get('ldap_tls_kind')
89 c.tls_kind_cur = defaults.get('ldap_tls_kind')
@@ -107,7 +107,7 b' class LdapSettingsController(BaseControl'
107
107
108 for k, v in form_result.items():
108 for k, v in form_result.items():
109 if k.startswith('ldap_'):
109 if k.startswith('ldap_'):
110 setting = RhodeCodeSettings.get_by_name(k)
110 setting = RhodeCodeSetting.get_by_name(k)
111 setting.app_settings_value = v
111 setting.app_settings_value = v
112 self.sa.add(setting)
112 self.sa.add(setting)
113
113
@@ -7,7 +7,7 b''
7
7
8 :created_on: Apr 27, 2010
8 :created_on: Apr 27, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
@@ -38,6 +38,7 b' from rhodecode.lib.base import BaseContr'
38 from rhodecode.model.forms import DefaultPermissionsForm
38 from rhodecode.model.forms import DefaultPermissionsForm
39 from rhodecode.model.permission import PermissionModel
39 from rhodecode.model.permission import PermissionModel
40 from rhodecode.model.db import User
40 from rhodecode.model.db import User
41 from rhodecode.model.meta import Session
41
42
42 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
43
44
@@ -101,6 +102,7 b' class PermissionsController(BaseControll'
101 form_result = _form.to_python(dict(request.POST))
102 form_result = _form.to_python(dict(request.POST))
102 form_result.update({'perm_user_name': id})
103 form_result.update({'perm_user_name': id})
103 permission_model.update(form_result)
104 permission_model.update(form_result)
105 Session.commit()
104 h.flash(_('Default permissions updated successfully'),
106 h.flash(_('Default permissions updated successfully'),
105 category='success')
107 category='success')
106
108
@@ -3,11 +3,11 b''
3 rhodecode.controllers.admin.repos
3 rhodecode.controllers.admin.repos
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Admin controller for RhodeCode
6 Repositories controller for RhodeCode
7
7
8 :created_on: Apr 7, 2010
8 :created_on: Apr 7, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
@@ -29,9 +29,10 b' import formencode'
29 from formencode import htmlfill
29 from formencode import htmlfill
30
30
31 from paste.httpexceptions import HTTPInternalServerError
31 from paste.httpexceptions import HTTPInternalServerError
32 from pylons import request, response, session, tmpl_context as c, url
32 from pylons import request, session, tmpl_context as c, url
33 from pylons.controllers.util import abort, redirect
33 from pylons.controllers.util import redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35 from sqlalchemy.exc import IntegrityError
35
36
36 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
37 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
38 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
@@ -39,11 +40,11 b' from rhodecode.lib.auth import LoginRequ'
39 from rhodecode.lib.base import BaseController, render
40 from rhodecode.lib.base import BaseController, render
40 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
41 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
41 from rhodecode.lib.helpers import get_token
42 from rhodecode.lib.helpers import get_token
42 from rhodecode.model.db import User, Repository, UserFollowing, Group
43 from rhodecode.model.meta import Session
44 from rhodecode.model.db import User, Repository, UserFollowing, RepoGroup
43 from rhodecode.model.forms import RepoForm
45 from rhodecode.model.forms import RepoForm
44 from rhodecode.model.scm import ScmModel
46 from rhodecode.model.scm import ScmModel
45 from rhodecode.model.repo import RepoModel
47 from rhodecode.model.repo import RepoModel
46 from sqlalchemy.exc import IntegrityError
47
48
48 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
49
50
@@ -63,9 +64,9 b' class ReposController(BaseController):'
63 super(ReposController, self).__before__()
64 super(ReposController, self).__before__()
64
65
65 def __load_defaults(self):
66 def __load_defaults(self):
66 c.repo_groups = Group.groups_choices()
67 c.repo_groups = RepoGroup.groups_choices()
67 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
68 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
68
69
69 repo_model = RepoModel()
70 repo_model = RepoModel()
70 c.users_array = repo_model.get_users_js()
71 c.users_array = repo_model.get_users_js()
71 c.users_groups_array = repo_model.get_users_groups_js()
72 c.users_groups_array = repo_model.get_users_groups_js()
@@ -96,12 +97,13 b' class ReposController(BaseController):'
96 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
97 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
97
98
98 if c.repo_info.stats:
99 if c.repo_info.stats:
99 last_rev = c.repo_info.stats.stat_on_revision
100 # this is on what revision we ended up so we add +1 for count
101 last_rev = c.repo_info.stats.stat_on_revision + 1
100 else:
102 else:
101 last_rev = 0
103 last_rev = 0
102 c.stats_revision = last_rev
104 c.stats_revision = last_rev
103
105
104 c.repo_last_rev = repo.count() - 1 if repo.revisions else 0
106 c.repo_last_rev = repo.count() if repo.revisions else 0
105
107
106 if last_rev == 0 or c.repo_last_rev == 0:
108 if last_rev == 0 or c.repo_last_rev == 0:
107 c.stats_percentage = 0
109 c.stats_percentage = 0
@@ -110,6 +112,10 b' class ReposController(BaseController):'
110 c.repo_last_rev) * 100)
112 c.repo_last_rev) * 100)
111
113
112 defaults = RepoModel()._get_defaults(repo_name)
114 defaults = RepoModel()._get_defaults(repo_name)
115
116 c.repos_list = [('', _('--REMOVE FORK--'))]
117 c.repos_list += [(x.repo_id, x.repo_name) for x in
118 Repository.query().order_by(Repository.repo_name).all()]
113 return defaults
119 return defaults
114
120
115 @HasPermissionAllDecorator('hg.admin')
121 @HasPermissionAllDecorator('hg.admin')
@@ -127,13 +133,13 b' class ReposController(BaseController):'
127 """
133 """
128 POST /repos: Create a new item"""
134 POST /repos: Create a new item"""
129 # url('repos')
135 # url('repos')
130 repo_model = RepoModel()
136
131 self.__load_defaults()
137 self.__load_defaults()
132 form_result = {}
138 form_result = {}
133 try:
139 try:
134 form_result = RepoForm(repo_groups=c.repo_groups_choices)()\
140 form_result = RepoForm(repo_groups=c.repo_groups_choices)()\
135 .to_python(dict(request.POST))
141 .to_python(dict(request.POST))
136 repo_model.create(form_result, self.rhodecode_user)
142 RepoModel().create(form_result, self.rhodecode_user)
137 if form_result['clone_uri']:
143 if form_result['clone_uri']:
138 h.flash(_('created repository %s from %s') \
144 h.flash(_('created repository %s from %s') \
139 % (form_result['repo_name'], form_result['clone_uri']),
145 % (form_result['repo_name'], form_result['clone_uri']),
@@ -143,13 +149,13 b' class ReposController(BaseController):'
143 category='success')
149 category='success')
144
150
145 if request.POST.get('user_created'):
151 if request.POST.get('user_created'):
146 #created by regular non admin user
152 # created by regular non admin user
147 action_logger(self.rhodecode_user, 'user_created_repo',
153 action_logger(self.rhodecode_user, 'user_created_repo',
148 form_result['repo_name_full'], '', self.sa)
154 form_result['repo_name_full'], '', self.sa)
149 else:
155 else:
150 action_logger(self.rhodecode_user, 'admin_created_repo',
156 action_logger(self.rhodecode_user, 'admin_created_repo',
151 form_result['repo_name_full'], '', self.sa)
157 form_result['repo_name_full'], '', self.sa)
152
158 Session.commit()
153 except formencode.Invalid, errors:
159 except formencode.Invalid, errors:
154
160
155 c.new_repo = errors.value['repo_name']
161 c.new_repo = errors.value['repo_name']
@@ -207,7 +213,7 b' class ReposController(BaseController):'
207 changed_name = repo.repo_name
213 changed_name = repo.repo_name
208 action_logger(self.rhodecode_user, 'admin_updated_repo',
214 action_logger(self.rhodecode_user, 'admin_updated_repo',
209 changed_name, '', self.sa)
215 changed_name, '', self.sa)
210
216 Session.commit()
211 except formencode.Invalid, errors:
217 except formencode.Invalid, errors:
212 defaults = self.__load_data(repo_name)
218 defaults = self.__load_data(repo_name)
213 defaults.update(errors.value)
219 defaults.update(errors.value)
@@ -251,9 +257,9 b' class ReposController(BaseController):'
251 repo_model.delete(repo)
257 repo_model.delete(repo)
252 invalidate_cache('get_repo_cached_%s' % repo_name)
258 invalidate_cache('get_repo_cached_%s' % repo_name)
253 h.flash(_('deleted repository %s') % repo_name, category='success')
259 h.flash(_('deleted repository %s') % repo_name, category='success')
254
260 Session.commit()
255 except IntegrityError, e:
261 except IntegrityError, e:
256 if e.message.find('repositories_fork_id_fkey'):
262 if e.message.find('repositories_fork_id_fkey') != -1:
257 log.error(traceback.format_exc())
263 log.error(traceback.format_exc())
258 h.flash(_('Cannot delete %s it still contains attached '
264 h.flash(_('Cannot delete %s it still contains attached '
259 'forks') % repo_name,
265 'forks') % repo_name,
@@ -271,8 +277,7 b' class ReposController(BaseController):'
271
277
272 return redirect(url('repos'))
278 return redirect(url('repos'))
273
279
274
280 @HasRepoPermissionAllDecorator('repository.admin')
275 @HasRepoPermissionAllDecorator('repository.admin')
276 def delete_perm_user(self, repo_name):
281 def delete_perm_user(self, repo_name):
277 """
282 """
278 DELETE an existing repository permission user
283 DELETE an existing repository permission user
@@ -281,9 +286,11 b' class ReposController(BaseController):'
281 """
286 """
282
287
283 try:
288 try:
284 repo_model = RepoModel()
289 RepoModel().revoke_user_permission(repo=repo_name,
285 repo_model.delete_perm_user(request.POST, repo_name)
290 user=request.POST['user_id'])
286 except Exception, e:
291 Session.commit()
292 except Exception:
293 log.error(traceback.format_exc())
287 h.flash(_('An error occurred during deletion of repository user'),
294 h.flash(_('An error occurred during deletion of repository user'),
288 category='error')
295 category='error')
289 raise HTTPInternalServerError()
296 raise HTTPInternalServerError()
@@ -295,10 +302,14 b' class ReposController(BaseController):'
295
302
296 :param repo_name:
303 :param repo_name:
297 """
304 """
305
298 try:
306 try:
299 repo_model = RepoModel()
307 RepoModel().revoke_users_group_permission(
300 repo_model.delete_perm_users_group(request.POST, repo_name)
308 repo=repo_name, group_name=request.POST['users_group_id']
301 except Exception, e:
309 )
310 Session.commit()
311 except Exception:
312 log.error(traceback.format_exc())
302 h.flash(_('An error occurred during deletion of repository'
313 h.flash(_('An error occurred during deletion of repository'
303 ' users groups'),
314 ' users groups'),
304 category='error')
315 category='error')
@@ -313,8 +324,8 b' class ReposController(BaseController):'
313 """
324 """
314
325
315 try:
326 try:
316 repo_model = RepoModel()
327 RepoModel().delete_stats(repo_name)
317 repo_model.delete_stats(repo_name)
328 Session.commit()
318 except Exception, e:
329 except Exception, e:
319 h.flash(_('An error occurred during deletion of repository stats'),
330 h.flash(_('An error occurred during deletion of repository stats'),
320 category='error')
331 category='error')
@@ -330,6 +341,7 b' class ReposController(BaseController):'
330
341
331 try:
342 try:
332 ScmModel().mark_for_invalidation(repo_name)
343 ScmModel().mark_for_invalidation(repo_name)
344 Session.commit()
333 except Exception, e:
345 except Exception, e:
334 h.flash(_('An error occurred during cache invalidation'),
346 h.flash(_('An error occurred during cache invalidation'),
335 category='error')
347 category='error')
@@ -353,6 +365,7 b' class ReposController(BaseController):'
353 self.scm_model.toggle_following_repo(repo_id, user_id)
365 self.scm_model.toggle_following_repo(repo_id, user_id)
354 h.flash(_('Updated repository visibility in public journal'),
366 h.flash(_('Updated repository visibility in public journal'),
355 category='success')
367 category='success')
368 Session.commit()
356 except:
369 except:
357 h.flash(_('An error occurred during setting this'
370 h.flash(_('An error occurred during setting this'
358 ' repository in public journal'),
371 ' repository in public journal'),
@@ -380,6 +393,28 b' class ReposController(BaseController):'
380 return redirect(url('edit_repo', repo_name=repo_name))
393 return redirect(url('edit_repo', repo_name=repo_name))
381
394
382 @HasPermissionAllDecorator('hg.admin')
395 @HasPermissionAllDecorator('hg.admin')
396 def repo_as_fork(self, repo_name):
397 """
398 Mark given repository as a fork of another
399
400 :param repo_name:
401 """
402 try:
403 fork_id = request.POST.get('id_fork_of')
404 repo = ScmModel().mark_as_fork(repo_name, fork_id,
405 self.rhodecode_user.username)
406 fork = repo.fork.repo_name if repo.fork else _('Nothing')
407 Session.commit()
408 h.flash(_('Marked repo %s as fork of %s' % (repo_name,fork)),
409 category='success')
410 except Exception, e:
411 raise
412 h.flash(_('An error occurred during this operation'),
413 category='error')
414
415 return redirect(url('edit_repo', repo_name=repo_name))
416
417 @HasPermissionAllDecorator('hg.admin')
383 def show(self, repo_name, format='html'):
418 def show(self, repo_name, format='html'):
384 """GET /repos/repo_name: Show a specific item"""
419 """GET /repos/repo_name: Show a specific item"""
385 # url('repo', repo_name=ID)
420 # url('repo', repo_name=ID)
@@ -1,22 +1,50 b''
1 # -*- coding: utf-8 -*-
2 """
3 rhodecode.controllers.admin.repos_groups
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
6 Repositories groups controller for RhodeCode
7
8 :created_on: Mar 23, 2010
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
12 """
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 #
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
22 #
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
1 import logging
26 import logging
2 import traceback
27 import traceback
3 import formencode
28 import formencode
4
29
5 from formencode import htmlfill
30 from formencode import htmlfill
6 from operator import itemgetter
7
31
8 from pylons import request, response, session, tmpl_context as c, url
32 from pylons import request, tmpl_context as c, url
9 from pylons.controllers.util import abort, redirect
33 from pylons.controllers.util import redirect
10 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
11
35
12 from sqlalchemy.exc import IntegrityError
36 from sqlalchemy.exc import IntegrityError
13
37
14 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
15 from rhodecode.lib.auth import LoginRequired, HasPermissionAnyDecorator
39 from rhodecode.lib.auth import LoginRequired, HasPermissionAnyDecorator,\
40 HasReposGroupPermissionAnyDecorator
16 from rhodecode.lib.base import BaseController, render
41 from rhodecode.lib.base import BaseController, render
17 from rhodecode.model.db import Group
42 from rhodecode.model.db import RepoGroup
18 from rhodecode.model.repos_group import ReposGroupModel
43 from rhodecode.model.repos_group import ReposGroupModel
19 from rhodecode.model.forms import ReposGroupForm
44 from rhodecode.model.forms import ReposGroupForm
45 from rhodecode.model.meta import Session
46 from rhodecode.model.repo import RepoModel
47 from webob.exc import HTTPInternalServerError
20
48
21 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
22
50
@@ -32,9 +60,13 b' class ReposGroupsController(BaseControll'
32 super(ReposGroupsController, self).__before__()
60 super(ReposGroupsController, self).__before__()
33
61
34 def __load_defaults(self):
62 def __load_defaults(self):
35 c.repo_groups = Group.groups_choices()
63 c.repo_groups = RepoGroup.groups_choices()
36 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
64 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
37
65
66 repo_model = RepoModel()
67 c.users_array = repo_model.get_users_js()
68 c.users_groups_array = repo_model.get_users_groups_js()
69
38 def __load_data(self, group_id):
70 def __load_data(self, group_id):
39 """
71 """
40 Load defaults settings for edit, and update
72 Load defaults settings for edit, and update
@@ -43,21 +75,30 b' class ReposGroupsController(BaseControll'
43 """
75 """
44 self.__load_defaults()
76 self.__load_defaults()
45
77
46 repo_group = Group.get(group_id)
78 repo_group = RepoGroup.get(group_id)
47
79
48 data = repo_group.get_dict()
80 data = repo_group.get_dict()
49
81
50 data['group_name'] = repo_group.name
82 data['group_name'] = repo_group.name
51
83
84 # fill repository users
85 for p in repo_group.repo_group_to_perm:
86 data.update({'u_perm_%s' % p.user.username:
87 p.permission.permission_name})
88
89 # fill repository groups
90 for p in repo_group.users_group_to_perm:
91 data.update({'g_perm_%s' % p.users_group.users_group_name:
92 p.permission.permission_name})
93
52 return data
94 return data
53
95
54 @HasPermissionAnyDecorator('hg.admin')
96 @HasPermissionAnyDecorator('hg.admin')
55 def index(self, format='html'):
97 def index(self, format='html'):
56 """GET /repos_groups: All items in the collection"""
98 """GET /repos_groups: All items in the collection"""
57 # url('repos_groups')
99 # url('repos_groups')
58
100 sk = lambda g: g.parents[0].group_name if g.parents else g.group_name
59 sk = lambda g:g.parents[0].group_name if g.parents else g.group_name
101 c.groups = sorted(RepoGroup.query().all(), key=sk)
60 c.groups = sorted(Group.query().all(), key=sk)
61 return render('admin/repos_groups/repos_groups_show.html')
102 return render('admin/repos_groups/repos_groups_show.html')
62
103
63 @HasPermissionAnyDecorator('hg.admin')
104 @HasPermissionAnyDecorator('hg.admin')
@@ -65,12 +106,16 b' class ReposGroupsController(BaseControll'
65 """POST /repos_groups: Create a new item"""
106 """POST /repos_groups: Create a new item"""
66 # url('repos_groups')
107 # url('repos_groups')
67 self.__load_defaults()
108 self.__load_defaults()
68 repos_group_model = ReposGroupModel()
109 repos_group_form = ReposGroupForm(available_groups =
69 repos_group_form = ReposGroupForm(available_groups=
70 c.repo_groups_choices)()
110 c.repo_groups_choices)()
71 try:
111 try:
72 form_result = repos_group_form.to_python(dict(request.POST))
112 form_result = repos_group_form.to_python(dict(request.POST))
73 repos_group_model.create(form_result)
113 ReposGroupModel().create(
114 group_name=form_result['group_name'],
115 group_description=form_result['group_description'],
116 parent=form_result['group_parent_id']
117 )
118 Session.commit()
74 h.flash(_('created repos group %s') \
119 h.flash(_('created repos group %s') \
75 % form_result['group_name'], category='success')
120 % form_result['group_name'], category='success')
76 #TODO: in futureaction_logger(, '', '', '', self.sa)
121 #TODO: in futureaction_logger(, '', '', '', self.sa)
@@ -89,7 +134,6 b' class ReposGroupsController(BaseControll'
89
134
90 return redirect(url('repos_groups'))
135 return redirect(url('repos_groups'))
91
136
92
93 @HasPermissionAnyDecorator('hg.admin')
137 @HasPermissionAnyDecorator('hg.admin')
94 def new(self, format='html'):
138 def new(self, format='html'):
95 """GET /repos_groups/new: Form to create a new item"""
139 """GET /repos_groups/new: Form to create a new item"""
@@ -108,16 +152,17 b' class ReposGroupsController(BaseControll'
108 # url('repos_group', id=ID)
152 # url('repos_group', id=ID)
109
153
110 self.__load_defaults()
154 self.__load_defaults()
111 c.repos_group = Group.get(id)
155 c.repos_group = RepoGroup.get(id)
112
156
113 repos_group_model = ReposGroupModel()
157 repos_group_form = ReposGroupForm(
114 repos_group_form = ReposGroupForm(edit=True,
158 edit=True,
115 old_data=c.repos_group.get_dict(),
159 old_data=c.repos_group.get_dict(),
116 available_groups=
160 available_groups=c.repo_groups_choices
117 c.repo_groups_choices)()
161 )()
118 try:
162 try:
119 form_result = repos_group_form.to_python(dict(request.POST))
163 form_result = repos_group_form.to_python(dict(request.POST))
120 repos_group_model.update(id, form_result)
164 ReposGroupModel().update(id, form_result)
165 Session.commit()
121 h.flash(_('updated repos group %s') \
166 h.flash(_('updated repos group %s') \
122 % form_result['group_name'], category='success')
167 % form_result['group_name'], category='success')
123 #TODO: in futureaction_logger(, '', '', '', self.sa)
168 #TODO: in futureaction_logger(, '', '', '', self.sa)
@@ -136,7 +181,6 b' class ReposGroupsController(BaseControll'
136
181
137 return redirect(url('repos_groups'))
182 return redirect(url('repos_groups'))
138
183
139
140 @HasPermissionAnyDecorator('hg.admin')
184 @HasPermissionAnyDecorator('hg.admin')
141 def delete(self, id):
185 def delete(self, id):
142 """DELETE /repos_groups/id: Delete an existing item"""
186 """DELETE /repos_groups/id: Delete an existing item"""
@@ -147,8 +191,7 b' class ReposGroupsController(BaseControll'
147 # method='delete')
191 # method='delete')
148 # url('repos_group', id=ID)
192 # url('repos_group', id=ID)
149
193
150 repos_group_model = ReposGroupModel()
194 gr = RepoGroup.get(id)
151 gr = Group.get(id)
152 repos = gr.repositories.all()
195 repos = gr.repositories.all()
153 if repos:
196 if repos:
154 h.flash(_('This group contains %s repositores and cannot be '
197 h.flash(_('This group contains %s repositores and cannot be '
@@ -157,11 +200,12 b' class ReposGroupsController(BaseControll'
157 return redirect(url('repos_groups'))
200 return redirect(url('repos_groups'))
158
201
159 try:
202 try:
160 repos_group_model.delete(id)
203 ReposGroupModel().delete(id)
204 Session.commit()
161 h.flash(_('removed repos group %s' % gr.group_name), category='success')
205 h.flash(_('removed repos group %s' % gr.group_name), category='success')
162 #TODO: in future action_logger(, '', '', '', self.sa)
206 #TODO: in future action_logger(, '', '', '', self.sa)
163 except IntegrityError, e:
207 except IntegrityError, e:
164 if e.message.find('groups_group_parent_id_fkey'):
208 if e.message.find('groups_group_parent_id_fkey') != -1:
165 log.error(traceback.format_exc())
209 log.error(traceback.format_exc())
166 h.flash(_('Cannot delete this group it still contains '
210 h.flash(_('Cannot delete this group it still contains '
167 'subgroups'),
211 'subgroups'),
@@ -178,15 +222,57 b' class ReposGroupsController(BaseControll'
178
222
179 return redirect(url('repos_groups'))
223 return redirect(url('repos_groups'))
180
224
225 @HasReposGroupPermissionAnyDecorator('group.admin')
226 def delete_repos_group_user_perm(self, group_name):
227 """
228 DELETE an existing repositories group permission user
229
230 :param group_name:
231 """
232
233 try:
234 ReposGroupModel().revoke_user_permission(
235 repos_group=group_name, user=request.POST['user_id']
236 )
237 Session.commit()
238 except Exception:
239 log.error(traceback.format_exc())
240 h.flash(_('An error occurred during deletion of group user'),
241 category='error')
242 raise HTTPInternalServerError()
243
244 @HasReposGroupPermissionAnyDecorator('group.admin')
245 def delete_repos_group_users_group_perm(self, group_name):
246 """
247 DELETE an existing repositories group permission users group
248
249 :param group_name:
250 """
251
252 try:
253 ReposGroupModel().revoke_users_group_permission(
254 repos_group=group_name,
255 group_name=request.POST['users_group_id']
256 )
257 Session.commit()
258 except Exception:
259 log.error(traceback.format_exc())
260 h.flash(_('An error occurred during deletion of group'
261 ' users groups'),
262 category='error')
263 raise HTTPInternalServerError()
264
181 def show_by_name(self, group_name):
265 def show_by_name(self, group_name):
182 id_ = Group.get_by_group_name(group_name).group_id
266 id_ = RepoGroup.get_by_group_name(group_name).group_id
183 return self.show(id_)
267 return self.show(id_)
184
268
269 @HasReposGroupPermissionAnyDecorator('group.read', 'group.write',
270 'group.admin')
185 def show(self, id, format='html'):
271 def show(self, id, format='html'):
186 """GET /repos_groups/id: Show a specific item"""
272 """GET /repos_groups/id: Show a specific item"""
187 # url('repos_group', id=ID)
273 # url('repos_group', id=ID)
188
274
189 c.group = Group.get(id)
275 c.group = RepoGroup.get(id)
190
276
191 if c.group:
277 if c.group:
192 c.group_repos = c.group.repositories.all()
278 c.group_repos = c.group.repositories.all()
@@ -201,8 +287,8 b' class ReposGroupsController(BaseControll'
201
287
202 c.repo_cnt = 0
288 c.repo_cnt = 0
203
289
204 c.groups = self.sa.query(Group).order_by(Group.group_name)\
290 c.groups = self.sa.query(RepoGroup).order_by(RepoGroup.group_name)\
205 .filter(Group.group_parent_id == id).all()
291 .filter(RepoGroup.group_parent_id == id).all()
206
292
207 return render('admin/repos_groups/repos_groups.html')
293 return render('admin/repos_groups/repos_groups.html')
208
294
@@ -213,11 +299,11 b' class ReposGroupsController(BaseControll'
213
299
214 id_ = int(id)
300 id_ = int(id)
215
301
216 c.repos_group = Group.get(id_)
302 c.repos_group = RepoGroup.get(id_)
217 defaults = self.__load_data(id_)
303 defaults = self.__load_data(id_)
218
304
219 # we need to exclude this group from the group list for editing
305 # we need to exclude this group from the group list for editing
220 c.repo_groups = filter(lambda x:x[0] != id_, c.repo_groups)
306 c.repo_groups = filter(lambda x: x[0] != id_, c.repo_groups)
221
307
222 return htmlfill.render(
308 return htmlfill.render(
223 render('admin/repos_groups/repos_groups_edit.html'),
309 render('admin/repos_groups/repos_groups_edit.html'),
@@ -225,5 +311,3 b' class ReposGroupsController(BaseControll'
225 encoding="UTF-8",
311 encoding="UTF-8",
226 force_defaults=False
312 force_defaults=False
227 )
313 )
228
229
@@ -7,7 +7,7 b''
7
7
8 :created_on: Jul 14, 2010
8 :created_on: Jul 14, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
@@ -40,13 +40,15 b' from rhodecode.lib.base import BaseContr'
40 from rhodecode.lib.celerylib import tasks, run_task
40 from rhodecode.lib.celerylib import tasks, run_task
41 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
41 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
42 set_rhodecode_config, repo_name_slug
42 set_rhodecode_config, repo_name_slug
43 from rhodecode.model.db import RhodeCodeUi, Repository, Group, \
43 from rhodecode.model.db import RhodeCodeUi, Repository, RepoGroup, \
44 RhodeCodeSettings
44 RhodeCodeSetting
45 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
45 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
46 ApplicationUiSettingsForm
46 ApplicationUiSettingsForm
47 from rhodecode.model.scm import ScmModel
47 from rhodecode.model.scm import ScmModel
48 from rhodecode.model.user import UserModel
48 from rhodecode.model.user import UserModel
49 from rhodecode.model.db import User
49 from rhodecode.model.db import User
50 from rhodecode.model.notification import EmailNotificationModel
51 from rhodecode.model.meta import Session
50
52
51 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
52
54
@@ -69,7 +71,7 b' class SettingsController(BaseController)'
69 """GET /admin/settings: All items in the collection"""
71 """GET /admin/settings: All items in the collection"""
70 # url('admin_settings')
72 # url('admin_settings')
71
73
72 defaults = RhodeCodeSettings.get_app_settings()
74 defaults = RhodeCodeSetting.get_app_settings()
73 defaults.update(self.get_hg_ui_settings())
75 defaults.update(self.get_hg_ui_settings())
74 return htmlfill.render(
76 return htmlfill.render(
75 render('admin/settings/settings.html'),
77 render('admin/settings/settings.html'),
@@ -99,7 +101,7 b' class SettingsController(BaseController)'
99 # url('admin_setting', setting_id=ID)
101 # url('admin_setting', setting_id=ID)
100 if setting_id == 'mapping':
102 if setting_id == 'mapping':
101 rm_obsolete = request.POST.get('destroy', False)
103 rm_obsolete = request.POST.get('destroy', False)
102 log.debug('Rescanning directories with destroy=%s', rm_obsolete)
104 log.debug('Rescanning directories with destroy=%s' % rm_obsolete)
103 initial = ScmModel().repo_scan()
105 initial = ScmModel().repo_scan()
104 log.debug('invalidating all repositories')
106 log.debug('invalidating all repositories')
105 for repo_name in initial.keys():
107 for repo_name in initial.keys():
@@ -124,15 +126,15 b' class SettingsController(BaseController)'
124 form_result = application_form.to_python(dict(request.POST))
126 form_result = application_form.to_python(dict(request.POST))
125
127
126 try:
128 try:
127 hgsettings1 = RhodeCodeSettings.get_by_name('title')
129 hgsettings1 = RhodeCodeSetting.get_by_name('title')
128 hgsettings1.app_settings_value = \
130 hgsettings1.app_settings_value = \
129 form_result['rhodecode_title']
131 form_result['rhodecode_title']
130
132
131 hgsettings2 = RhodeCodeSettings.get_by_name('realm')
133 hgsettings2 = RhodeCodeSetting.get_by_name('realm')
132 hgsettings2.app_settings_value = \
134 hgsettings2.app_settings_value = \
133 form_result['rhodecode_realm']
135 form_result['rhodecode_realm']
134
136
135 hgsettings3 = RhodeCodeSettings.get_by_name('ga_code')
137 hgsettings3 = RhodeCodeSetting.get_by_name('ga_code')
136 hgsettings3.app_settings_value = \
138 hgsettings3.app_settings_value = \
137 form_result['rhodecode_ga_code']
139 form_result['rhodecode_ga_code']
138
140
@@ -226,12 +228,11 b' class SettingsController(BaseController)'
226 prefix_error=False,
228 prefix_error=False,
227 encoding="UTF-8")
229 encoding="UTF-8")
228
230
229
230 if setting_id == 'hooks':
231 if setting_id == 'hooks':
231 ui_key = request.POST.get('new_hook_ui_key')
232 ui_key = request.POST.get('new_hook_ui_key')
232 ui_value = request.POST.get('new_hook_ui_value')
233 ui_value = request.POST.get('new_hook_ui_value')
233 try:
234 try:
234
235
235 if ui_value and ui_key:
236 if ui_value and ui_key:
236 RhodeCodeUi.create_or_update_hook(ui_key, ui_value)
237 RhodeCodeUi.create_or_update_hook(ui_key, ui_value)
237 h.flash(_('Added new hook'),
238 h.flash(_('Added new hook'),
@@ -240,13 +241,14 b' class SettingsController(BaseController)'
240 # check for edits
241 # check for edits
241 update = False
242 update = False
242 _d = request.POST.dict_of_lists()
243 _d = request.POST.dict_of_lists()
243 for k, v in zip(_d.get('hook_ui_key',[]), _d.get('hook_ui_value_new',[])):
244 for k, v in zip(_d.get('hook_ui_key', []),
245 _d.get('hook_ui_value_new', [])):
244 RhodeCodeUi.create_or_update_hook(k, v)
246 RhodeCodeUi.create_or_update_hook(k, v)
245 update = True
247 update = True
246
248
247 if update:
249 if update:
248 h.flash(_('Updated hooks'), category='success')
250 h.flash(_('Updated hooks'), category='success')
249
251 Session.commit()
250 except:
252 except:
251 log.error(traceback.format_exc())
253 log.error(traceback.format_exc())
252 h.flash(_('error occurred during hook creation'),
254 h.flash(_('error occurred during hook creation'),
@@ -254,6 +256,21 b' class SettingsController(BaseController)'
254
256
255 return redirect(url('admin_edit_setting', setting_id='hooks'))
257 return redirect(url('admin_edit_setting', setting_id='hooks'))
256
258
259 if setting_id == 'email':
260 test_email = request.POST.get('test_email')
261 test_email_subj = 'RhodeCode TestEmail'
262 test_email_body = 'RhodeCode Email test'
263
264 test_email_html_body = EmailNotificationModel()\
265 .get_email_tmpl(EmailNotificationModel.TYPE_DEFAULT,
266 body=test_email_body)
267
268 recipients = [test_email] if [test_email] else None
269
270 run_task(tasks.send_email, recipients, test_email_subj,
271 test_email_body, test_email_html_body)
272
273 h.flash(_('Email task created'), category='success')
257 return redirect(url('admin_settings'))
274 return redirect(url('admin_settings'))
258
275
259 @HasPermissionAllDecorator('hg.admin')
276 @HasPermissionAllDecorator('hg.admin')
@@ -268,8 +285,8 b' class SettingsController(BaseController)'
268 if setting_id == 'hooks':
285 if setting_id == 'hooks':
269 hook_id = request.POST.get('hook_id')
286 hook_id = request.POST.get('hook_id')
270 RhodeCodeUi.delete(hook_id)
287 RhodeCodeUi.delete(hook_id)
271
288
272
289
273 @HasPermissionAllDecorator('hg.admin')
290 @HasPermissionAllDecorator('hg.admin')
274 def show(self, setting_id, format='html'):
291 def show(self, setting_id, format='html'):
275 """
292 """
@@ -339,7 +356,7 b' class SettingsController(BaseController)'
339 user_model.update_my_account(uid, form_result)
356 user_model.update_my_account(uid, form_result)
340 h.flash(_('Your account was updated successfully'),
357 h.flash(_('Your account was updated successfully'),
341 category='success')
358 category='success')
342
359 Session.commit()
343 except formencode.Invalid, errors:
360 except formencode.Invalid, errors:
344 c.user = User.get(self.rhodecode_user.user_id)
361 c.user = User.get(self.rhodecode_user.user_id)
345 all_repos = self.sa.query(Repository)\
362 all_repos = self.sa.query(Repository)\
@@ -366,7 +383,7 b' class SettingsController(BaseController)'
366 def create_repository(self):
383 def create_repository(self):
367 """GET /_admin/create_repository: Form to create a new item"""
384 """GET /_admin/create_repository: Form to create a new item"""
368
385
369 c.repo_groups = Group.groups_choices()
386 c.repo_groups = RepoGroup.groups_choices()
370 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
387 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
371
388
372 new_repo = request.GET.get('repo', '')
389 new_repo = request.GET.get('repo', '')
@@ -7,7 +7,7 b''
7
7
8 :created_on: Apr 4, 2010
8 :created_on: Apr 4, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
@@ -29,7 +29,7 b' import formencode'
29
29
30 from formencode import htmlfill
30 from formencode import htmlfill
31 from pylons import request, session, tmpl_context as c, url, config
31 from pylons import request, session, tmpl_context as c, url, config
32 from pylons.controllers.util import abort, redirect
32 from pylons.controllers.util import redirect
33 from pylons.i18n.translation import _
33 from pylons.i18n.translation import _
34
34
35 from rhodecode.lib.exceptions import DefaultUserException, \
35 from rhodecode.lib.exceptions import DefaultUserException, \
@@ -38,9 +38,10 b' from rhodecode.lib import helpers as h'
38 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
38 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
39 from rhodecode.lib.base import BaseController, render
39 from rhodecode.lib.base import BaseController, render
40
40
41 from rhodecode.model.db import User, RepoToPerm, UserToPerm, Permission
41 from rhodecode.model.db import User, Permission
42 from rhodecode.model.forms import UserForm
42 from rhodecode.model.forms import UserForm
43 from rhodecode.model.user import UserModel
43 from rhodecode.model.user import UserModel
44 from rhodecode.model.meta import Session
44
45
45 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
46
47
@@ -71,12 +72,13 b' class UsersController(BaseController):'
71 # url('users')
72 # url('users')
72
73
73 user_model = UserModel()
74 user_model = UserModel()
74 login_form = UserForm()()
75 user_form = UserForm()()
75 try:
76 try:
76 form_result = login_form.to_python(dict(request.POST))
77 form_result = user_form.to_python(dict(request.POST))
77 user_model.create(form_result)
78 user_model.create(form_result)
78 h.flash(_('created user %s') % form_result['username'],
79 h.flash(_('created user %s') % form_result['username'],
79 category='success')
80 category='success')
81 Session.commit()
80 #action_logger(self.rhodecode_user, 'new_user', '', '', self.sa)
82 #action_logger(self.rhodecode_user, 'new_user', '', '', self.sa)
81 except formencode.Invalid, errors:
83 except formencode.Invalid, errors:
82 return htmlfill.render(
84 return htmlfill.render(
@@ -114,11 +116,11 b' class UsersController(BaseController):'
114 form_result = _form.to_python(dict(request.POST))
116 form_result = _form.to_python(dict(request.POST))
115 user_model.update(id, form_result)
117 user_model.update(id, form_result)
116 h.flash(_('User updated successfully'), category='success')
118 h.flash(_('User updated successfully'), category='success')
117
119 Session.commit()
118 except formencode.Invalid, errors:
120 except formencode.Invalid, errors:
119 e = errors.error_dict or {}
121 e = errors.error_dict or {}
120 perm = Permission.get_by_key('hg.create.repository')
122 perm = Permission.get_by_key('hg.create.repository')
121 e.update({'create_repo_perm': UserToPerm.has_perm(id, perm)})
123 e.update({'create_repo_perm': user_model.has_perm(id, perm)})
122 return htmlfill.render(
124 return htmlfill.render(
123 render('admin/users/user_edit.html'),
125 render('admin/users/user_edit.html'),
124 defaults=errors.value,
126 defaults=errors.value,
@@ -144,6 +146,7 b' class UsersController(BaseController):'
144 try:
146 try:
145 user_model.delete(id)
147 user_model.delete(id)
146 h.flash(_('successfully deleted user'), category='success')
148 h.flash(_('successfully deleted user'), category='success')
149 Session.commit()
147 except (UserOwnsReposException, DefaultUserException), e:
150 except (UserOwnsReposException, DefaultUserException), e:
148 h.flash(str(e), category='warning')
151 h.flash(str(e), category='warning')
149 except Exception:
152 except Exception:
@@ -158,20 +161,19 b' class UsersController(BaseController):'
158 def edit(self, id, format='html'):
161 def edit(self, id, format='html'):
159 """GET /users/id/edit: Form to edit an existing item"""
162 """GET /users/id/edit: Form to edit an existing item"""
160 # url('edit_user', id=ID)
163 # url('edit_user', id=ID)
161 user_model = UserModel()
164 c.user = User.get(id)
162 c.user = user_model.get(id)
163 if not c.user:
165 if not c.user:
164 return redirect(url('users'))
166 return redirect(url('users'))
165 if c.user.username == 'default':
167 if c.user.username == 'default':
166 h.flash(_("You can't edit this user"), category='warning')
168 h.flash(_("You can't edit this user"), category='warning')
167 return redirect(url('users'))
169 return redirect(url('users'))
168 c.user.permissions = {}
170 c.user.permissions = {}
169 c.granted_permissions = user_model.fill_perms(c.user)\
171 c.granted_permissions = UserModel().fill_perms(c.user)\
170 .permissions['global']
172 .permissions['global']
171
173
172 defaults = c.user.get_dict()
174 defaults = c.user.get_dict()
173 perm = Permission.get_by_key('hg.create.repository')
175 perm = Permission.get_by_key('hg.create.repository')
174 defaults.update({'create_repo_perm': UserToPerm.has_perm(id, perm)})
176 defaults.update({'create_repo_perm': UserModel().has_perm(id, perm)})
175
177
176 return htmlfill.render(
178 return htmlfill.render(
177 render('admin/users/user_edit.html'),
179 render('admin/users/user_edit.html'),
@@ -185,23 +187,24 b' class UsersController(BaseController):'
185 # url('user_perm', id=ID, method='put')
187 # url('user_perm', id=ID, method='put')
186
188
187 grant_perm = request.POST.get('create_repo_perm', False)
189 grant_perm = request.POST.get('create_repo_perm', False)
190 user_model = UserModel()
188
191
189 if grant_perm:
192 if grant_perm:
190 perm = Permission.get_by_key('hg.create.none')
193 perm = Permission.get_by_key('hg.create.none')
191 UserToPerm.revoke_perm(id, perm)
194 user_model.revoke_perm(id, perm)
192
195
193 perm = Permission.get_by_key('hg.create.repository')
196 perm = Permission.get_by_key('hg.create.repository')
194 UserToPerm.grant_perm(id, perm)
197 user_model.grant_perm(id, perm)
195 h.flash(_("Granted 'repository create' permission to user"),
198 h.flash(_("Granted 'repository create' permission to user"),
196 category='success')
199 category='success')
197
200 Session.commit()
198 else:
201 else:
199 perm = Permission.get_by_key('hg.create.repository')
202 perm = Permission.get_by_key('hg.create.repository')
200 UserToPerm.revoke_perm(id, perm)
203 user_model.revoke_perm(id, perm)
201
204
202 perm = Permission.get_by_key('hg.create.none')
205 perm = Permission.get_by_key('hg.create.none')
203 UserToPerm.grant_perm(id, perm)
206 user_model.grant_perm(id, perm)
204 h.flash(_("Revoked 'repository create' permission to user"),
207 h.flash(_("Revoked 'repository create' permission to user"),
205 category='success')
208 category='success')
206
209 Session.commit()
207 return redirect(url('edit_user', id=id))
210 return redirect(url('edit_user', id=id))
@@ -7,7 +7,7 b''
7
7
8 :created_on: Jan 25, 2011
8 :created_on: Jan 25, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
@@ -33,12 +33,15 b' from pylons.controllers.util import abor'
33 from pylons.i18n.translation import _
33 from pylons.i18n.translation import _
34
34
35 from rhodecode.lib.exceptions import UsersGroupsAssignedException
35 from rhodecode.lib.exceptions import UsersGroupsAssignedException
36 from rhodecode.lib import helpers as h
36 from rhodecode.lib import helpers as h, safe_unicode
37 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
37 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
38 from rhodecode.lib.base import BaseController, render
38 from rhodecode.lib.base import BaseController, render
39
39
40 from rhodecode.model.users_group import UsersGroupModel
41
40 from rhodecode.model.db import User, UsersGroup, Permission, UsersGroupToPerm
42 from rhodecode.model.db import User, UsersGroup, Permission, UsersGroupToPerm
41 from rhodecode.model.forms import UserForm, UsersGroupForm
43 from rhodecode.model.forms import UsersGroupForm
44 from rhodecode.model.meta import Session
42
45
43 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
44
47
@@ -70,10 +73,12 b' class UsersGroupsController(BaseControll'
70 users_group_form = UsersGroupForm()()
73 users_group_form = UsersGroupForm()()
71 try:
74 try:
72 form_result = users_group_form.to_python(dict(request.POST))
75 form_result = users_group_form.to_python(dict(request.POST))
73 UsersGroup.create(form_result)
76 UsersGroupModel().create(name=form_result['users_group_name'],
77 active=form_result['users_group_active'])
74 h.flash(_('created users group %s') \
78 h.flash(_('created users group %s') \
75 % form_result['users_group_name'], category='success')
79 % form_result['users_group_name'], category='success')
76 #action_logger(self.rhodecode_user, 'new_user', '', '', self.sa)
80 #action_logger(self.rhodecode_user, 'new_user', '', '', self.sa)
81 Session.commit()
77 except formencode.Invalid, errors:
82 except formencode.Invalid, errors:
78 return htmlfill.render(
83 return htmlfill.render(
79 render('admin/users_groups/users_group_add.html'),
84 render('admin/users_groups/users_group_add.html'),
@@ -103,29 +108,33 b' class UsersGroupsController(BaseControll'
103 # url('users_group', id=ID)
108 # url('users_group', id=ID)
104
109
105 c.users_group = UsersGroup.get(id)
110 c.users_group = UsersGroup.get(id)
106 c.group_members = [(x.user_id, x.user.username) for x in
111 c.group_members_obj = [x.user for x in c.users_group.members]
107 c.users_group.members]
112 c.group_members = [(x.user_id, x.username) for x in
113 c.group_members_obj]
108
114
109 c.available_members = [(x.user_id, x.username) for x in
115 c.available_members = [(x.user_id, x.username) for x in
110 self.sa.query(User).all()]
116 self.sa.query(User).all()]
117
118 available_members = [safe_unicode(x[0]) for x in c.available_members]
119
111 users_group_form = UsersGroupForm(edit=True,
120 users_group_form = UsersGroupForm(edit=True,
112 old_data=c.users_group.get_dict(),
121 old_data=c.users_group.get_dict(),
113 available_members=[str(x[0]) for x
122 available_members=available_members)()
114 in c.available_members])()
115
123
116 try:
124 try:
117 form_result = users_group_form.to_python(request.POST)
125 form_result = users_group_form.to_python(request.POST)
118 UsersGroup.update(id, form_result)
126 UsersGroupModel().update(c.users_group, form_result)
119 h.flash(_('updated users group %s') \
127 h.flash(_('updated users group %s') \
120 % form_result['users_group_name'],
128 % form_result['users_group_name'],
121 category='success')
129 category='success')
122 #action_logger(self.rhodecode_user, 'new_user', '', '', self.sa)
130 #action_logger(self.rhodecode_user, 'new_user', '', '', self.sa)
131 Session.commit()
123 except formencode.Invalid, errors:
132 except formencode.Invalid, errors:
124 e = errors.error_dict or {}
133 e = errors.error_dict or {}
125
134
126 perm = Permission.get_by_key('hg.create.repository')
135 perm = Permission.get_by_key('hg.create.repository')
127 e.update({'create_repo_perm':
136 e.update({'create_repo_perm':
128 UsersGroupToPerm.has_perm(id, perm)})
137 UsersGroupModel().has_perm(id, perm)})
129
138
130 return htmlfill.render(
139 return htmlfill.render(
131 render('admin/users_groups/users_group_edit.html'),
140 render('admin/users_groups/users_group_edit.html'),
@@ -150,8 +159,9 b' class UsersGroupsController(BaseControll'
150 # url('users_group', id=ID)
159 # url('users_group', id=ID)
151
160
152 try:
161 try:
153 UsersGroup.delete(id)
162 UsersGroupModel().delete(id)
154 h.flash(_('successfully deleted users group'), category='success')
163 h.flash(_('successfully deleted users group'), category='success')
164 Session.commit()
155 except UsersGroupsAssignedException, e:
165 except UsersGroupsAssignedException, e:
156 h.flash(e, category='error')
166 h.flash(e, category='error')
157 except Exception:
167 except Exception:
@@ -172,14 +182,15 b' class UsersGroupsController(BaseControll'
172 return redirect(url('users_groups'))
182 return redirect(url('users_groups'))
173
183
174 c.users_group.permissions = {}
184 c.users_group.permissions = {}
175 c.group_members = [(x.user_id, x.user.username) for x in
185 c.group_members_obj = [x.user for x in c.users_group.members]
176 c.users_group.members]
186 c.group_members = [(x.user_id, x.username) for x in
187 c.group_members_obj]
177 c.available_members = [(x.user_id, x.username) for x in
188 c.available_members = [(x.user_id, x.username) for x in
178 self.sa.query(User).all()]
189 self.sa.query(User).all()]
179 defaults = c.users_group.get_dict()
190 defaults = c.users_group.get_dict()
180 perm = Permission.get_by_key('hg.create.repository')
191 perm = Permission.get_by_key('hg.create.repository')
181 defaults.update({'create_repo_perm':
192 defaults.update({'create_repo_perm':
182 UsersGroupToPerm.has_perm(id, perm)})
193 UsersGroupModel().has_perm(c.users_group, perm)})
183 return htmlfill.render(
194 return htmlfill.render(
184 render('admin/users_groups/users_group_edit.html'),
195 render('admin/users_groups/users_group_edit.html'),
185 defaults=defaults,
196 defaults=defaults,
@@ -195,20 +206,21 b' class UsersGroupsController(BaseControll'
195
206
196 if grant_perm:
207 if grant_perm:
197 perm = Permission.get_by_key('hg.create.none')
208 perm = Permission.get_by_key('hg.create.none')
198 UsersGroupToPerm.revoke_perm(id, perm)
209 UsersGroupModel().revoke_perm(id, perm)
199
210
200 perm = Permission.get_by_key('hg.create.repository')
211 perm = Permission.get_by_key('hg.create.repository')
201 UsersGroupToPerm.grant_perm(id, perm)
212 UsersGroupModel().grant_perm(id, perm)
202 h.flash(_("Granted 'repository create' permission to user"),
213 h.flash(_("Granted 'repository create' permission to user"),
203 category='success')
214 category='success')
204
215
216 Session.commit()
205 else:
217 else:
206 perm = Permission.get_by_key('hg.create.repository')
218 perm = Permission.get_by_key('hg.create.repository')
207 UsersGroupToPerm.revoke_perm(id, perm)
219 UsersGroupModel().revoke_perm(id, perm)
208
220
209 perm = Permission.get_by_key('hg.create.none')
221 perm = Permission.get_by_key('hg.create.none')
210 UsersGroupToPerm.grant_perm(id, perm)
222 UsersGroupModel().grant_perm(id, perm)
211 h.flash(_("Revoked 'repository create' permission to user"),
223 h.flash(_("Revoked 'repository create' permission to user"),
212 category='success')
224 category='success')
213
225 Session.commit()
214 return redirect(url('edit_users_group', id=id))
226 return redirect(url('edit_users_group', id=id))
@@ -7,19 +7,19 b''
7
7
8 :created_on: Aug 20, 2011
8 :created_on: Aug 20, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software; you can redistribute it and/or
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
@@ -62,7 +62,7 b' def jsonrpc_error(message, code=None):'
62 Generate a Response object with a JSON-RPC error body
62 Generate a Response object with a JSON-RPC error body
63 """
63 """
64 from pylons.controllers.util import Response
64 from pylons.controllers.util import Response
65 resp = Response(body=json.dumps(dict(result=None, error=message)),
65 resp = Response(body=json.dumps(dict(id=None, result=None, error=message)),
66 status=code,
66 status=code,
67 content_type='application/json')
67 content_type='application/json')
68 return resp
68 return resp
@@ -100,7 +100,7 b' class JSONRPCController(WSGIController):'
100 else:
100 else:
101 length = environ['CONTENT_LENGTH'] or 0
101 length = environ['CONTENT_LENGTH'] or 0
102 length = int(environ['CONTENT_LENGTH'])
102 length = int(environ['CONTENT_LENGTH'])
103 log.debug('Content-Length: %s', length)
103 log.debug('Content-Length: %s' % length)
104
104
105 if length == 0:
105 if length == 0:
106 log.debug("Content-Length is 0")
106 log.debug("Content-Length is 0")
@@ -118,11 +118,13 b' class JSONRPCController(WSGIController):'
118 # check AUTH based on API KEY
118 # check AUTH based on API KEY
119 try:
119 try:
120 self._req_api_key = json_body['api_key']
120 self._req_api_key = json_body['api_key']
121 self._req_id = json_body['id']
121 self._req_method = json_body['method']
122 self._req_method = json_body['method']
122 self._request_params = json_body['args']
123 self._request_params = json_body['args']
123 log.debug('method: %s, params: %s',
124 log.debug(
124 self._req_method,
125 'method: %s, params: %s' % (self._req_method,
125 self._request_params)
126 self._request_params)
127 )
126 except KeyError, e:
128 except KeyError, e:
127 return jsonrpc_error(message='Incorrect JSON query missing %s' % e)
129 return jsonrpc_error(message='Incorrect JSON query missing %s' % e)
128
130
@@ -225,21 +227,26 b' class JSONRPCController(WSGIController):'
225 if self._error is not None:
227 if self._error is not None:
226 raw_response = None
228 raw_response = None
227
229
228 response = dict(result=raw_response,
230 response = dict(id=self._req_id, result=raw_response,
229 error=self._error)
231 error=self._error)
230
232
231 try:
233 try:
232 return json.dumps(response)
234 return json.dumps(response)
233 except TypeError, e:
235 except TypeError, e:
234 log.debug('Error encoding response: %s', e)
236 log.debug('Error encoding response: %s' % e)
235 return json.dumps(dict(result=None,
237 return json.dumps(
236 error="Error encoding response"))
238 dict(
239 self._req_id,
240 result=None,
241 error="Error encoding response"
242 )
243 )
237
244
238 def _find_method(self):
245 def _find_method(self):
239 """
246 """
240 Return method named by `self._req_method` in controller if able
247 Return method named by `self._req_method` in controller if able
241 """
248 """
242 log.debug('Trying to find JSON-RPC method: %s', self._req_method)
249 log.debug('Trying to find JSON-RPC method: %s' % self._req_method)
243 if self._req_method.startswith('_'):
250 if self._req_method.startswith('_'):
244 raise AttributeError("Method not allowed")
251 raise AttributeError("Method not allowed")
245
252
@@ -253,4 +260,3 b' class JSONRPCController(WSGIController):'
253 return func
260 return func
254 else:
261 else:
255 raise AttributeError("No such method: %s" % self._req_method)
262 raise AttributeError("No such method: %s" % self._req_method)
256
@@ -30,17 +30,15 b' import logging'
30
30
31 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
31 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
32 from rhodecode.lib.auth import HasPermissionAllDecorator, \
32 from rhodecode.lib.auth import HasPermissionAllDecorator, \
33 HasPermissionAnyDecorator
33 HasPermissionAnyDecorator, PasswordGenerator
34
35 from rhodecode.model.meta import Session
34 from rhodecode.model.scm import ScmModel
36 from rhodecode.model.scm import ScmModel
35
37 from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository
36 from rhodecode.model.db import User, UsersGroup, Group, Repository
37 from rhodecode.model.repo import RepoModel
38 from rhodecode.model.repo import RepoModel
38 from rhodecode.model.user import UserModel
39 from rhodecode.model.user import UserModel
39 from rhodecode.model.repo_permission import RepositoryPermissionModel
40 from rhodecode.model.users_group import UsersGroupModel
40 from rhodecode.model.users_group import UsersGroupModel
41 from rhodecode.model import users_group
42 from rhodecode.model.repos_group import ReposGroupModel
41 from rhodecode.model.repos_group import ReposGroupModel
43 from sqlalchemy.orm.exc import NoResultFound
44
42
45
43
46 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
@@ -63,26 +61,26 b' class ApiController(JSONRPCController):'
63 """
61 """
64
62
65 @HasPermissionAllDecorator('hg.admin')
63 @HasPermissionAllDecorator('hg.admin')
66 def pull(self, apiuser, repo):
64 def pull(self, apiuser, repo_name):
67 """
65 """
68 Dispatch pull action on given repo
66 Dispatch pull action on given repo
69
67
70
68
71 :param user:
69 :param user:
72 :param repo:
70 :param repo_name:
73 """
71 """
74
72
75 if Repository.is_valid(repo) is False:
73 if Repository.is_valid(repo_name) is False:
76 raise JSONRPCError('Unknown repo "%s"' % repo)
74 raise JSONRPCError('Unknown repo "%s"' % repo_name)
77
75
78 try:
76 try:
79 ScmModel().pull_changes(repo, self.rhodecode_user.username)
77 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
80 return 'Pulled from %s' % repo
78 return 'Pulled from %s' % repo_name
81 except Exception:
79 except Exception:
82 raise JSONRPCError('Unable to pull changes from "%s"' % repo)
80 raise JSONRPCError('Unable to pull changes from "%s"' % repo_name)
83
81
84 @HasPermissionAllDecorator('hg.admin')
82 @HasPermissionAllDecorator('hg.admin')
85 def get_user(self, apiuser, username):
83 def get_user(self, apiuser, userid):
86 """"
84 """"
87 Get a user by username
85 Get a user by username
88
86
@@ -90,9 +88,9 b' class ApiController(JSONRPCController):'
90 :param username:
88 :param username:
91 """
89 """
92
90
93 user = User.get_by_username(username)
91 user = UserModel().get_user(userid)
94 if not user:
92 if user is None:
95 return None
93 return user
96
94
97 return dict(
95 return dict(
98 id=user.user_id,
96 id=user.user_id,
@@ -102,7 +100,7 b' class ApiController(JSONRPCController):'
102 email=user.email,
100 email=user.email,
103 active=user.active,
101 active=user.active,
104 admin=user.admin,
102 admin=user.admin,
105 ldap=user.ldap_dn
103 ldap_dn=user.ldap_dn
106 )
104 )
107
105
108 @HasPermissionAllDecorator('hg.admin')
106 @HasPermissionAllDecorator('hg.admin')
@@ -124,47 +122,85 b' class ApiController(JSONRPCController):'
124 email=user.email,
122 email=user.email,
125 active=user.active,
123 active=user.active,
126 admin=user.admin,
124 admin=user.admin,
127 ldap=user.ldap_dn
125 ldap_dn=user.ldap_dn
128 )
126 )
129 )
127 )
130 return result
128 return result
131
129
132 @HasPermissionAllDecorator('hg.admin')
130 @HasPermissionAllDecorator('hg.admin')
133 def create_user(self, apiuser, username, password, firstname,
131 def create_user(self, apiuser, username, email, password, firstname=None,
134 lastname, email, active=True, admin=False, ldap_dn=None):
132 lastname=None, active=True, admin=False, ldap_dn=None):
135 """
133 """
136 Create new user
134 Create new user
137
135
138 :param apiuser:
136 :param apiuser:
139 :param username:
137 :param username:
140 :param password:
138 :param password:
139 :param email:
141 :param name:
140 :param name:
142 :param lastname:
141 :param lastname:
143 :param email:
144 :param active:
142 :param active:
145 :param admin:
143 :param admin:
146 :param ldap_dn:
144 :param ldap_dn:
147 """
145 """
148
149 if User.get_by_username(username):
146 if User.get_by_username(username):
150 raise JSONRPCError("user %s already exist" % username)
147 raise JSONRPCError("user %s already exist" % username)
151
148
149 if User.get_by_email(email, case_insensitive=True):
150 raise JSONRPCError("email %s already exist" % email)
151
152 if ldap_dn:
153 # generate temporary password if ldap_dn
154 password = PasswordGenerator().gen_password(length=8)
155
152 try:
156 try:
153 form_data = dict(username=username,
157 usr = UserModel().create_or_update(
154 password=password,
158 username, password, email, firstname,
155 active=active,
159 lastname, active, admin, ldap_dn
156 admin=admin,
160 )
157 name=firstname,
161 Session.commit()
158 lastname=lastname,
162 return dict(
159 email=email,
163 id=usr.user_id,
160 ldap_dn=ldap_dn)
164 msg='created new user %s' % username
161 UserModel().create_ldap(username, password, ldap_dn, form_data)
165 )
162 return dict(msg='created new user %s' % username)
163 except Exception:
166 except Exception:
164 log.error(traceback.format_exc())
167 log.error(traceback.format_exc())
165 raise JSONRPCError('failed to create user %s' % username)
168 raise JSONRPCError('failed to create user %s' % username)
166
169
167 @HasPermissionAllDecorator('hg.admin')
170 @HasPermissionAllDecorator('hg.admin')
171 def update_user(self, apiuser, userid, username, password, email,
172 firstname, lastname, active, admin, ldap_dn):
173 """
174 Updates given user
175
176 :param apiuser:
177 :param username:
178 :param password:
179 :param email:
180 :param name:
181 :param lastname:
182 :param active:
183 :param admin:
184 :param ldap_dn:
185 """
186 if not UserModel().get_user(userid):
187 raise JSONRPCError("user %s does not exist" % username)
188
189 try:
190 usr = UserModel().create_or_update(
191 username, password, email, firstname,
192 lastname, active, admin, ldap_dn
193 )
194 Session.commit()
195 return dict(
196 id=usr.user_id,
197 msg='updated user %s' % username
198 )
199 except Exception:
200 log.error(traceback.format_exc())
201 raise JSONRPCError('failed to update user %s' % username)
202
203 @HasPermissionAllDecorator('hg.admin')
168 def get_users_group(self, apiuser, group_name):
204 def get_users_group(self, apiuser, group_name):
169 """"
205 """"
170 Get users group by name
206 Get users group by name
@@ -190,7 +226,7 b' class ApiController(JSONRPCController):'
190 ldap=user.ldap_dn))
226 ldap=user.ldap_dn))
191
227
192 return dict(id=users_group.users_group_id,
228 return dict(id=users_group.users_group_id,
193 name=users_group.users_group_name,
229 group_name=users_group.users_group_name,
194 active=users_group.users_group_active,
230 active=users_group.users_group_active,
195 members=members)
231 members=members)
196
232
@@ -217,41 +253,40 b' class ApiController(JSONRPCController):'
217 ldap=user.ldap_dn))
253 ldap=user.ldap_dn))
218
254
219 result.append(dict(id=users_group.users_group_id,
255 result.append(dict(id=users_group.users_group_id,
220 name=users_group.users_group_name,
256 group_name=users_group.users_group_name,
221 active=users_group.users_group_active,
257 active=users_group.users_group_active,
222 members=members))
258 members=members))
223 return result
259 return result
224
260
225 @HasPermissionAllDecorator('hg.admin')
261 @HasPermissionAllDecorator('hg.admin')
226 def create_users_group(self, apiuser, name, active=True):
262 def create_users_group(self, apiuser, group_name, active=True):
227 """
263 """
228 Creates an new usergroup
264 Creates an new usergroup
229
265
230 :param name:
266 :param group_name:
231 :param active:
267 :param active:
232 """
268 """
233
269
234 if self.get_users_group(apiuser, name):
270 if self.get_users_group(apiuser, group_name):
235 raise JSONRPCError("users group %s already exist" % name)
271 raise JSONRPCError("users group %s already exist" % group_name)
236
272
237 try:
273 try:
238 form_data = dict(users_group_name=name,
274 ug = UsersGroupModel().create(name=group_name, active=active)
239 users_group_active=active)
275 Session.commit()
240 ug = UsersGroup.create(form_data)
241 return dict(id=ug.users_group_id,
276 return dict(id=ug.users_group_id,
242 msg='created new users group %s' % name)
277 msg='created new users group %s' % group_name)
243 except Exception:
278 except Exception:
244 log.error(traceback.format_exc())
279 log.error(traceback.format_exc())
245 raise JSONRPCError('failed to create group %s' % name)
280 raise JSONRPCError('failed to create group %s' % group_name)
246
281
247 @HasPermissionAllDecorator('hg.admin')
282 @HasPermissionAllDecorator('hg.admin')
248 def add_user_to_users_group(self, apiuser, group_name, user_name):
283 def add_user_to_users_group(self, apiuser, group_name, username):
249 """"
284 """"
250 Add a user to a group
285 Add a user to a group
251
286
252 :param apiuser
287 :param apiuser:
253 :param group_name
288 :param group_name:
254 :param user_name
289 :param username:
255 """
290 """
256
291
257 try:
292 try:
@@ -259,32 +294,65 b' class ApiController(JSONRPCController):'
259 if not users_group:
294 if not users_group:
260 raise JSONRPCError('unknown users group %s' % group_name)
295 raise JSONRPCError('unknown users group %s' % group_name)
261
296
262 try:
297 user = User.get_by_username(username)
263 user = User.get_by_username(user_name)
298 if user is None:
264 except NoResultFound:
299 raise JSONRPCError('unknown user %s' % username)
265 raise JSONRPCError('unknown user %s' % user_name)
266
300
267 ugm = UsersGroupModel().add_user_to_group(users_group, user)
301 ugm = UsersGroupModel().add_user_to_group(users_group, user)
302 success = True if ugm != True else False
303 msg = 'added member %s to users group %s' % (username, group_name)
304 msg = msg if success else 'User is already in that group'
305 Session.commit()
268
306
269 return dict(id=ugm.users_group_member_id,
307 return dict(
270 msg='created new users group member')
308 id=ugm.users_group_member_id if ugm != True else None,
309 success=success,
310 msg=msg
311 )
271 except Exception:
312 except Exception:
272 log.error(traceback.format_exc())
313 log.error(traceback.format_exc())
273 raise JSONRPCError('failed to create users group member')
314 raise JSONRPCError('failed to add users group member')
315
316 @HasPermissionAllDecorator('hg.admin')
317 def remove_user_from_users_group(self, apiuser, group_name, username):
318 """
319 Remove user from a group
320
321 :param apiuser
322 :param group_name
323 :param username
324 """
325
326 try:
327 users_group = UsersGroup.get_by_group_name(group_name)
328 if not users_group:
329 raise JSONRPCError('unknown users group %s' % group_name)
330
331 user = User.get_by_username(username)
332 if user is None:
333 raise JSONRPCError('unknown user %s' % username)
334
335 success = UsersGroupModel().remove_user_from_group(users_group, user)
336 msg = 'removed member %s from users group %s' % (username, group_name)
337 msg = msg if success else "User wasn't in group"
338 Session.commit()
339 return dict(success=success, msg=msg)
340 except Exception:
341 log.error(traceback.format_exc())
342 raise JSONRPCError('failed to remove user from group')
274
343
275 @HasPermissionAnyDecorator('hg.admin')
344 @HasPermissionAnyDecorator('hg.admin')
276 def get_repo(self, apiuser, name):
345 def get_repo(self, apiuser, repoid):
277 """"
346 """"
278 Get repository by name
347 Get repository by name
279
348
280 :param apiuser
349 :param apiuser:
281 :param repo_name
350 :param repo_name:
282 """
351 """
283
352
284 try:
353 repo = RepoModel().get_repo(repoid)
285 repo = Repository.get_by_repo_name(name)
354 if repo is None:
286 except NoResultFound:
355 raise JSONRPCError('unknown repository %s' % repo)
287 return None
288
356
289 members = []
357 members = []
290 for user in repo.repo_to_perm:
358 for user in repo.repo_to_perm:
@@ -319,7 +387,7 b' class ApiController(JSONRPCController):'
319
387
320 return dict(
388 return dict(
321 id=repo.repo_id,
389 id=repo.repo_id,
322 name=repo.repo_name,
390 repo_name=repo.repo_name,
323 type=repo.repo_type,
391 type=repo.repo_type,
324 description=repo.description,
392 description=repo.description,
325 members=members
393 members=members
@@ -330,7 +398,7 b' class ApiController(JSONRPCController):'
330 """"
398 """"
331 Get all repositories
399 Get all repositories
332
400
333 :param apiuser
401 :param apiuser:
334 """
402 """
335
403
336 result = []
404 result = []
@@ -338,85 +406,255 b' class ApiController(JSONRPCController):'
338 result.append(
406 result.append(
339 dict(
407 dict(
340 id=repository.repo_id,
408 id=repository.repo_id,
341 name=repository.repo_name,
409 repo_name=repository.repo_name,
342 type=repository.repo_type,
410 type=repository.repo_type,
343 description=repository.description
411 description=repository.description
344 )
412 )
345 )
413 )
346 return result
414 return result
347
415
348 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
416 @HasPermissionAnyDecorator('hg.admin')
349 def create_repo(self, apiuser, name, owner_name, description='',
417 def get_repo_nodes(self, apiuser, repo_name, revision, root_path,
350 repo_type='hg', private=False):
418 ret_type='all'):
351 """
419 """
352 Create a repository
420 returns a list of nodes and it's children
421 for a given path at given revision. It's possible to specify ret_type
422 to show only files or dirs
353
423
354 :param apiuser
424 :param apiuser:
355 :param name
425 :param repo_name: name of repository
356 :param description
426 :param revision: revision for which listing should be done
357 :param type
427 :param root_path: path from which start displaying
358 :param private
428 :param ret_type: return type 'all|files|dirs' nodes
359 :param owner_name
429 """
430 try:
431 _d, _f = ScmModel().get_nodes(repo_name, revision, root_path,
432 flat=False)
433 _map = {
434 'all': _d + _f,
435 'files': _f,
436 'dirs': _d,
437 }
438 return _map[ret_type]
439 except KeyError:
440 raise JSONRPCError('ret_type must be one of %s' % _map.keys())
441 except Exception, e:
442 raise JSONRPCError(e)
443
444 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
445 def create_repo(self, apiuser, repo_name, owner_name, description='',
446 repo_type='hg', private=False, clone_uri=None):
447 """
448 Create repository, if clone_url is given it makes a remote clone
449
450 :param apiuser:
451 :param repo_name:
452 :param owner_name:
453 :param description:
454 :param repo_type:
455 :param private:
456 :param clone_uri:
360 """
457 """
361
458
362 try:
459 try:
363 try:
460 owner = User.get_by_username(owner_name)
364 owner = User.get_by_username(owner_name)
461 if owner is None:
365 except NoResultFound:
462 raise JSONRPCError('unknown user %s' % owner_name)
366 raise JSONRPCError('unknown user %s' % owner)
367
463
368 if self.get_repo(apiuser, name):
464 if Repository.get_by_repo_name(repo_name):
369 raise JSONRPCError("repo %s already exist" % name)
465 raise JSONRPCError("repo %s already exist" % repo_name)
370
466
371 groups = name.split('/')
467 groups = repo_name.split('/')
372 real_name = groups[-1]
468 real_name = groups[-1]
373 groups = groups[:-1]
469 groups = groups[:-1]
374 parent_id = None
470 parent_id = None
375 for g in groups:
471 for g in groups:
376 group = Group.get_by_group_name(g)
472 group = RepoGroup.get_by_group_name(g)
377 if not group:
473 if not group:
378 group = ReposGroupModel().create(dict(group_name=g,
474 group = ReposGroupModel().create(g, '', parent_id)
379 group_description='',
380 group_parent_id=parent_id))
381 parent_id = group.group_id
475 parent_id = group.group_id
382
476
383 RepoModel().create(dict(repo_name=real_name,
477 repo = RepoModel().create(
384 repo_name_full=name,
478 dict(
385 description=description,
479 repo_name=real_name,
386 private=private,
480 repo_name_full=repo_name,
387 repo_type=repo_type,
481 description=description,
388 repo_group=parent_id,
482 private=private,
389 clone_uri=None), owner)
483 repo_type=repo_type,
484 repo_group=parent_id,
485 clone_uri=clone_uri
486 ),
487 owner
488 )
489 Session.commit()
490
491 return dict(
492 id=repo.repo_id,
493 msg="Created new repository %s" % repo.repo_name
494 )
495
496 except Exception:
497 log.error(traceback.format_exc())
498 raise JSONRPCError('failed to create repository %s' % repo_name)
499
500 @HasPermissionAnyDecorator('hg.admin')
501 def delete_repo(self, apiuser, repo_name):
502 """
503 Deletes a given repository
504
505 :param repo_name:
506 """
507 if not Repository.get_by_repo_name(repo_name):
508 raise JSONRPCError("repo %s does not exist" % repo_name)
509 try:
510 RepoModel().delete(repo_name)
511 Session.commit()
512 return dict(
513 msg='Deleted repository %s' % repo_name
514 )
390 except Exception:
515 except Exception:
391 log.error(traceback.format_exc())
516 log.error(traceback.format_exc())
392 raise JSONRPCError('failed to create repository %s' % name)
517 raise JSONRPCError('failed to delete repository %s' % repo_name)
393
518
394 @HasPermissionAnyDecorator('hg.admin')
519 @HasPermissionAnyDecorator('hg.admin')
395 def add_user_to_repo(self, apiuser, repo_name, user_name, perm):
520 def grant_user_permission(self, apiuser, repo_name, username, perm):
521 """
522 Grant permission for user on given repository, or update existing one
523 if found
524
525 :param repo_name:
526 :param username:
527 :param perm:
396 """
528 """
397 Add permission for a user to a repository
529
530 try:
531 repo = Repository.get_by_repo_name(repo_name)
532 if repo is None:
533 raise JSONRPCError('unknown repository %s' % repo)
534
535 user = User.get_by_username(username)
536 if user is None:
537 raise JSONRPCError('unknown user %s' % username)
538
539 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
398
540
399 :param apiuser
541 Session.commit()
400 :param repo_name
542 return dict(
401 :param user_name
543 msg='Granted perm: %s for user: %s in repo: %s' % (
402 :param perm
544 perm, username, repo_name
545 )
546 )
547 except Exception:
548 log.error(traceback.format_exc())
549 raise JSONRPCError(
550 'failed to edit permission %(repo)s for %(user)s' % dict(
551 user=username, repo=repo_name
552 )
553 )
554
555 @HasPermissionAnyDecorator('hg.admin')
556 def revoke_user_permission(self, apiuser, repo_name, username):
557 """
558 Revoke permission for user on given repository
559
560 :param repo_name:
561 :param username:
403 """
562 """
404
563
405 try:
564 try:
406 try:
565 repo = Repository.get_by_repo_name(repo_name)
407 repo = Repository.get_by_repo_name(repo_name)
566 if repo is None:
408 except NoResultFound:
567 raise JSONRPCError('unknown repository %s' % repo)
568
569 user = User.get_by_username(username)
570 if user is None:
571 raise JSONRPCError('unknown user %s' % username)
572
573 RepoModel().revoke_user_permission(repo=repo_name, user=username)
574
575 Session.commit()
576 return dict(
577 msg='Revoked perm for user: %s in repo: %s' % (
578 username, repo_name
579 )
580 )
581 except Exception:
582 log.error(traceback.format_exc())
583 raise JSONRPCError(
584 'failed to edit permission %(repo)s for %(user)s' % dict(
585 user=username, repo=repo_name
586 )
587 )
588
589 @HasPermissionAnyDecorator('hg.admin')
590 def grant_users_group_permission(self, apiuser, repo_name, group_name, perm):
591 """
592 Grant permission for users group on given repository, or update
593 existing one if found
594
595 :param repo_name:
596 :param group_name:
597 :param perm:
598 """
599
600 try:
601 repo = Repository.get_by_repo_name(repo_name)
602 if repo is None:
409 raise JSONRPCError('unknown repository %s' % repo)
603 raise JSONRPCError('unknown repository %s' % repo)
410
604
411 try:
605 user_group = UsersGroup.get_by_group_name(group_name)
412 user = User.get_by_username(user_name)
606 if user_group is None:
413 except NoResultFound:
607 raise JSONRPCError('unknown users group %s' % user_group)
414 raise JSONRPCError('unknown user %s' % user)
415
608
416 RepositoryPermissionModel()\
609 RepoModel().grant_users_group_permission(repo=repo_name,
417 .update_or_delete_user_permission(repo, user, perm)
610 group_name=group_name,
611 perm=perm)
612
613 Session.commit()
614 return dict(
615 msg='Granted perm: %s for group: %s in repo: %s' % (
616 perm, group_name, repo_name
617 )
618 )
418 except Exception:
619 except Exception:
419 log.error(traceback.format_exc())
620 log.error(traceback.format_exc())
420 raise JSONRPCError('failed to edit permission %(repo)s for %(user)s'
621 raise JSONRPCError(
421 % dict(user=user_name, repo=repo_name))
622 'failed to edit permission %(repo)s for %(usersgr)s' % dict(
623 usersgr=group_name, repo=repo_name
624 )
625 )
626
627 @HasPermissionAnyDecorator('hg.admin')
628 def revoke_users_group_permission(self, apiuser, repo_name, group_name):
629 """
630 Revoke permission for users group on given repository
631
632 :param repo_name:
633 :param group_name:
634 """
635
636 try:
637 repo = Repository.get_by_repo_name(repo_name)
638 if repo is None:
639 raise JSONRPCError('unknown repository %s' % repo)
422
640
641 user_group = UsersGroup.get_by_group_name(group_name)
642 if user_group is None:
643 raise JSONRPCError('unknown users group %s' % user_group)
644
645 RepoModel().revoke_users_group_permission(repo=repo_name,
646 group_name=group_name)
647
648 Session.commit()
649 return dict(
650 msg='Revoked perm for group: %s in repo: %s' % (
651 group_name, repo_name
652 )
653 )
654 except Exception:
655 log.error(traceback.format_exc())
656 raise JSONRPCError(
657 'failed to edit permission %(repo)s for %(usersgr)s' % dict(
658 usersgr=group_name, repo=repo_name
659 )
660 )
@@ -7,7 +7,7 b''
7
7
8 :created_on: Apr 21, 2010
8 :created_on: Apr 21, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
@@ -46,33 +46,30 b' class BranchesController(BaseRepoControl'
46 def index(self):
46 def index(self):
47
47
48 def _branchtags(localrepo):
48 def _branchtags(localrepo):
49
50 bt = {}
51 bt_closed = {}
49 bt_closed = {}
52
53 for bn, heads in localrepo.branchmap().iteritems():
50 for bn, heads in localrepo.branchmap().iteritems():
54 tip = heads[-1]
51 tip = heads[-1]
55 if 'close' not in localrepo.changelog.read(tip)[5]:
52 if 'close' in localrepo.changelog.read(tip)[5]:
56 bt[bn] = tip
57 else:
58 bt_closed[bn] = tip
53 bt_closed[bn] = tip
59 return bt, bt_closed
54 return bt_closed
60
55
56 cs_g = c.rhodecode_repo.get_changeset
61
57
62 bt, bt_closed = _branchtags(c.rhodecode_repo._repo)
58 c.repo_closed_branches = {}
63 cs_g = c.rhodecode_repo.get_changeset
59 if c.rhodecode_db_repo.repo_type == 'hg':
64 _branches = [(safe_unicode(n), cs_g(binascii.hexlify(h)),) for n, h in
60 bt_closed = _branchtags(c.rhodecode_repo._repo)
65 bt.items()]
61 _closed_branches = [(safe_unicode(n), cs_g(binascii.hexlify(h)),)
62 for n, h in bt_closed.items()]
66
63
67 _closed_branches = [(safe_unicode(n), cs_g(binascii.hexlify(h)),) for n, h in
64 c.repo_closed_branches = OrderedDict(sorted(_closed_branches,
68 bt_closed.items()]
65 key=lambda ctx: ctx[0],
66 reverse=False))
69
67
68 _branches = [(safe_unicode(n), cs_g(h))
69 for n, h in c.rhodecode_repo.branches.items()]
70 c.repo_branches = OrderedDict(sorted(_branches,
70 c.repo_branches = OrderedDict(sorted(_branches,
71 key=lambda ctx: ctx[0],
71 key=lambda ctx: ctx[0],
72 reverse=False))
72 reverse=False))
73 c.repo_closed_branches = OrderedDict(sorted(_closed_branches,
74 key=lambda ctx: ctx[0],
75 reverse=False))
76
73
77
74
78 return render('branches/branches.html')
75 return render('branches/branches.html')
@@ -7,7 +7,7 b''
7
7
8 :created_on: Apr 21, 2010
8 :created_on: Apr 21, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
@@ -24,15 +24,22 b''
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27
28
28 from mercurial import graphmod
29 from mercurial import graphmod
29 from pylons import request, session, tmpl_context as c
30 from pylons import request, url, session, tmpl_context as c
31 from pylons.controllers.util import redirect
32 from pylons.i18n.translation import _
30
33
34 import rhodecode.lib.helpers as h
31 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
35 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
32 from rhodecode.lib.base import BaseRepoController, render
36 from rhodecode.lib.base import BaseRepoController, render
33 from rhodecode.lib.helpers import RepoPage
37 from rhodecode.lib.helpers import RepoPage
34 from rhodecode.lib.compat import json
38 from rhodecode.lib.compat import json
35
39
40 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError
41 from rhodecode.model.db import Repository
42
36 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
37
44
38
45
@@ -62,12 +69,32 b' class ChangelogController(BaseRepoContro'
62
69
63 p = int(request.params.get('page', 1))
70 p = int(request.params.get('page', 1))
64 branch_name = request.params.get('branch', None)
71 branch_name = request.params.get('branch', None)
65 c.total_cs = len(c.rhodecode_repo)
72 try:
66 c.pagination = RepoPage(c.rhodecode_repo, page=p,
73 if branch_name:
67 item_count=c.total_cs, items_per_page=c.size,
74 collection = [z for z in
68 branch_name=branch_name)
75 c.rhodecode_repo.get_changesets(start=0,
76 branch_name=branch_name)]
77 c.total_cs = len(collection)
78 else:
79 collection = c.rhodecode_repo
80 c.total_cs = len(c.rhodecode_repo)
69
81
70 self._graph(c.rhodecode_repo, c.total_cs, c.size, p)
82 c.pagination = RepoPage(collection, page=p, item_count=c.total_cs,
83 items_per_page=c.size, branch=branch_name)
84 collection = list(c.pagination)
85 page_revisions = [x.raw_id for x in collection]
86 c.comments = c.rhodecode_db_repo.comments(page_revisions)
87
88 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
89 log.error(traceback.format_exc())
90 h.flash(str(e), category='warning')
91 return redirect(url('home'))
92
93 self._graph(c.rhodecode_repo, collection, c.total_cs, c.size, p)
94
95 c.branch_name = branch_name
96 c.branch_filters = [('', _('All Branches'))] + \
97 [(k, k) for k in c.rhodecode_repo.branches.keys()]
71
98
72 return render('changelog/changelog.html')
99 return render('changelog/changelog.html')
73
100
@@ -76,7 +103,7 b' class ChangelogController(BaseRepoContro'
76 c.cs = c.rhodecode_repo.get_changeset(cs)
103 c.cs = c.rhodecode_repo.get_changeset(cs)
77 return render('changelog/changelog_details.html')
104 return render('changelog/changelog_details.html')
78
105
79 def _graph(self, repo, repo_size, size, p):
106 def _graph(self, repo, collection, repo_size, size, p):
80 """
107 """
81 Generates a DAG graph for mercurial
108 Generates a DAG graph for mercurial
82
109
@@ -84,29 +111,20 b' class ChangelogController(BaseRepoContro'
84 :param size: number of commits to show
111 :param size: number of commits to show
85 :param p: page number
112 :param p: page number
86 """
113 """
87 if not repo.revisions:
114 if not collection:
88 c.jsdata = json.dumps([])
115 c.jsdata = json.dumps([])
89 return
116 return
90
117
91 revcount = min(repo_size, size)
92 offset = 1 if p == 1 else ((p - 1) * revcount + 1)
93 try:
94 rev_end = repo.revisions.index(repo.revisions[(-1 * offset)])
95 except IndexError:
96 rev_end = repo.revisions.index(repo.revisions[-1])
97 rev_start = max(0, rev_end - revcount)
98
99 data = []
118 data = []
100 rev_end += 1
119 revs = [x.revision for x in collection]
101
120
102 if repo.alias == 'git':
121 if repo.alias == 'git':
103 for _ in xrange(rev_start, rev_end):
122 for _ in revs:
104 vtx = [0, 1]
123 vtx = [0, 1]
105 edges = [[0, 0, 1]]
124 edges = [[0, 0, 1]]
106 data.append(['', vtx, edges])
125 data.append(['', vtx, edges])
107
126
108 elif repo.alias == 'hg':
127 elif repo.alias == 'hg':
109 revs = list(reversed(xrange(rev_start, rev_end)))
110 c.dag = graphmod.colored(graphmod.dagwalker(repo._repo, revs))
128 c.dag = graphmod.colored(graphmod.dagwalker(repo._repo, revs))
111 for (id, type, ctx, vtx, edges) in c.dag:
129 for (id, type, ctx, vtx, edges) in c.dag:
112 if type != graphmod.CHANGESET:
130 if type != graphmod.CHANGESET:
@@ -8,7 +8,7 b''
8
8
9 :created_on: Apr 25, 2010
9 :created_on: Apr 25, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
@@ -25,25 +25,129 b''
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 import logging
26 import logging
27 import traceback
27 import traceback
28 from collections import defaultdict
29 from webob.exc import HTTPForbidden
28
30
29 from pylons import tmpl_context as c, url, request, response
31 from pylons import tmpl_context as c, url, request, response
30 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
31 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.decorators import jsonify
35
36 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetError, \
37 ChangesetDoesNotExistError
38 from rhodecode.lib.vcs.nodes import FileNode
32
39
33 import rhodecode.lib.helpers as h
40 import rhodecode.lib.helpers as h
34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
35 from rhodecode.lib.base import BaseRepoController, render
42 from rhodecode.lib.base import BaseRepoController, render
36 from rhodecode.lib.utils import EmptyChangeset
43 from rhodecode.lib.utils import EmptyChangeset
37 from rhodecode.lib.compat import OrderedDict
44 from rhodecode.lib.compat import OrderedDict
38
45 from rhodecode.lib import diffs
39 from vcs.exceptions import RepositoryError, ChangesetError, \
46 from rhodecode.model.db import ChangesetComment
40 ChangesetDoesNotExistError
47 from rhodecode.model.comment import ChangesetCommentsModel
41 from vcs.nodes import FileNode
48 from rhodecode.model.meta import Session
42 from vcs.utils import diffs as differ
49 from rhodecode.lib.diffs import wrapped_diff
43
50
44 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
45
52
46
53
54 def anchor_url(revision, path):
55 fid = h.FID(revision, path)
56 return h.url.current(anchor=fid, **request.GET)
57
58
59 def get_ignore_ws(fid, GET):
60 ig_ws_global = request.GET.get('ignorews')
61 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
62 if ig_ws:
63 try:
64 return int(ig_ws[0].split(':')[-1])
65 except:
66 pass
67 return ig_ws_global
68
69
70 def _ignorews_url(fileid=None):
71
72 params = defaultdict(list)
73 lbl = _('show white space')
74 ig_ws = get_ignore_ws(fileid, request.GET)
75 ln_ctx = get_line_ctx(fileid, request.GET)
76 # global option
77 if fileid is None:
78 if ig_ws is None:
79 params['ignorews'] += [1]
80 lbl = _('ignore white space')
81 ctx_key = 'context'
82 ctx_val = ln_ctx
83 # per file options
84 else:
85 if ig_ws is None:
86 params[fileid] += ['WS:1']
87 lbl = _('ignore white space')
88
89 ctx_key = fileid
90 ctx_val = 'C:%s' % ln_ctx
91 # if we have passed in ln_ctx pass it along to our params
92 if ln_ctx:
93 params[ctx_key] += [ctx_val]
94
95 params['anchor'] = fileid
96 img = h.image('/images/icons/text_strikethrough.png', lbl, class_='icon')
97 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
98
99
100 def get_line_ctx(fid, GET):
101 ln_ctx_global = request.GET.get('context')
102 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
103
104 if ln_ctx:
105 retval = ln_ctx[0].split(':')[-1]
106 else:
107 retval = ln_ctx_global
108
109 try:
110 return int(retval)
111 except:
112 return
113
114
115 def _context_url(fileid=None):
116 """
117 Generates url for context lines
118
119 :param fileid:
120 """
121 ig_ws = get_ignore_ws(fileid, request.GET)
122 ln_ctx = (get_line_ctx(fileid, request.GET) or 3) * 2
123
124 params = defaultdict(list)
125
126 # global option
127 if fileid is None:
128 if ln_ctx > 0:
129 params['context'] += [ln_ctx]
130
131 if ig_ws:
132 ig_ws_key = 'ignorews'
133 ig_ws_val = 1
134
135 # per file option
136 else:
137 params[fileid] += ['C:%s' % ln_ctx]
138 ig_ws_key = fileid
139 ig_ws_val = 'WS:%s' % 1
140
141 if ig_ws:
142 params[ig_ws_key] += [ig_ws_val]
143
144 lbl = _('%s line context') % ln_ctx
145
146 params['anchor'] = fileid
147 img = h.image('/images/icons/table_add.png', lbl, class_='icon')
148 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
149
150
47 class ChangesetController(BaseRepoController):
151 class ChangesetController(BaseRepoController):
48
152
49 @LoginRequired()
153 @LoginRequired()
@@ -55,20 +159,16 b' class ChangesetController(BaseRepoContro'
55
159
56 def index(self, revision):
160 def index(self, revision):
57
161
58 def wrap_to_table(str):
162 c.anchor_url = anchor_url
59
163 c.ignorews_url = _ignorews_url
60 return '''<table class="code-difftable">
164 c.context_url = _context_url
61 <tr class="line">
62 <td class="lineno new"></td>
63 <td class="code"><pre>%s</pre></td>
64 </tr>
65 </table>''' % str
66
165
67 #get ranges of revisions if preset
166 #get ranges of revisions if preset
68 rev_range = revision.split('...')[:2]
167 rev_range = revision.split('...')[:2]
69
168 enable_comments = True
70 try:
169 try:
71 if len(rev_range) == 2:
170 if len(rev_range) == 2:
171 enable_comments = False
72 rev_start = rev_range[0]
172 rev_start = rev_range[0]
73 rev_end = rev_range[1]
173 rev_end = rev_range[1]
74 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
174 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
@@ -77,6 +177,8 b' class ChangesetController(BaseRepoContro'
77 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
177 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
78
178
79 c.cs_ranges = list(rev_ranges)
179 c.cs_ranges = list(rev_ranges)
180 if not c.cs_ranges:
181 raise RepositoryError('Changeset range returned empty result')
80
182
81 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
183 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
82 log.error(traceback.format_exc())
184 log.error(traceback.format_exc())
@@ -84,14 +186,25 b' class ChangesetController(BaseRepoContro'
84 return redirect(url('home'))
186 return redirect(url('home'))
85
187
86 c.changes = OrderedDict()
188 c.changes = OrderedDict()
87 c.sum_added = 0
189
88 c.sum_removed = 0
190 c.lines_added = 0 # count of lines added
89 c.lines_added = 0
191 c.lines_deleted = 0 # count of lines removes
90 c.lines_deleted = 0
192
193 cumulative_diff = 0
91 c.cut_off = False # defines if cut off limit is reached
194 c.cut_off = False # defines if cut off limit is reached
92
195
196 c.comments = []
197 c.inline_comments = []
198 c.inline_cnt = 0
93 # Iterate over ranges (default changeset view is always one changeset)
199 # Iterate over ranges (default changeset view is always one changeset)
94 for changeset in c.cs_ranges:
200 for changeset in c.cs_ranges:
201 c.comments.extend(ChangesetCommentsModel()\
202 .get_comments(c.rhodecode_db_repo.repo_id,
203 changeset.raw_id))
204 inlines = ChangesetCommentsModel()\
205 .get_inline_comments(c.rhodecode_db_repo.repo_id,
206 changeset.raw_id)
207 c.inline_comments.extend(inlines)
95 c.changes[changeset.raw_id] = []
208 c.changes[changeset.raw_id] = []
96 try:
209 try:
97 changeset_parent = changeset.parents[0]
210 changeset_parent = changeset.parents[0]
@@ -102,32 +215,19 b' class ChangesetController(BaseRepoContro'
102 # ADDED FILES
215 # ADDED FILES
103 #==================================================================
216 #==================================================================
104 for node in changeset.added:
217 for node in changeset.added:
105
218 fid = h.FID(revision, node.path)
106 filenode_old = FileNode(node.path, '', EmptyChangeset())
219 line_context_lcl = get_line_ctx(fid, request.GET)
107 if filenode_old.is_binary or node.is_binary:
220 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
108 diff = wrap_to_table(_('binary file'))
221 lim = self.cut_off_limit
109 st = (0, 0)
222 if cumulative_diff > self.cut_off_limit:
110 else:
223 lim = -1
111 # in this case node.size is good parameter since those are
224 size, cs1, cs2, diff, st = wrapped_diff(filenode_old=None,
112 # added nodes and their size defines how many changes were
225 filenode_new=node,
113 # made
226 cut_off_limit=lim,
114 c.sum_added += node.size
227 ignore_whitespace=ign_whitespace_lcl,
115 if c.sum_added < self.cut_off_limit:
228 line_context=line_context_lcl,
116 f_gitdiff = differ.get_gitdiff(filenode_old, node)
229 enable_comments=enable_comments)
117 d = differ.DiffProcessor(f_gitdiff, format='gitdiff')
230 cumulative_diff += size
118
119 st = d.stat()
120 diff = d.as_html()
121
122 else:
123 diff = wrap_to_table(_('Changeset is to big and '
124 'was cut off, see raw '
125 'changeset instead'))
126 c.cut_off = True
127 break
128
129 cs1 = None
130 cs2 = node.last_changeset.raw_id
131 c.lines_added += st[0]
231 c.lines_added += st[0]
132 c.lines_deleted += st[1]
232 c.lines_deleted += st[1]
133 c.changes[changeset.raw_id].append(('added', node, diff,
233 c.changes[changeset.raw_id].append(('added', node, diff,
@@ -136,55 +236,42 b' class ChangesetController(BaseRepoContro'
136 #==================================================================
236 #==================================================================
137 # CHANGED FILES
237 # CHANGED FILES
138 #==================================================================
238 #==================================================================
139 if not c.cut_off:
239 for node in changeset.changed:
140 for node in changeset.changed:
240 try:
141 try:
241 filenode_old = changeset_parent.get_node(node.path)
142 filenode_old = changeset_parent.get_node(node.path)
242 except ChangesetError:
143 except ChangesetError:
243 log.warning('Unable to fetch parent node for diff')
144 log.warning('Unable to fetch parent node for diff')
244 filenode_old = FileNode(node.path, '', EmptyChangeset())
145 filenode_old = FileNode(node.path, '',
146 EmptyChangeset())
147
148 if filenode_old.is_binary or node.is_binary:
149 diff = wrap_to_table(_('binary file'))
150 st = (0, 0)
151 else:
152
245
153 if c.sum_removed < self.cut_off_limit:
246 fid = h.FID(revision, node.path)
154 f_gitdiff = differ.get_gitdiff(filenode_old, node)
247 line_context_lcl = get_line_ctx(fid, request.GET)
155 d = differ.DiffProcessor(f_gitdiff,
248 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
156 format='gitdiff')
249 lim = self.cut_off_limit
157 st = d.stat()
250 if cumulative_diff > self.cut_off_limit:
158 if (st[0] + st[1]) * 256 > self.cut_off_limit:
251 lim = -1
159 diff = wrap_to_table(_('Diff is to big '
252 size, cs1, cs2, diff, st = wrapped_diff(filenode_old=filenode_old,
160 'and was cut off, see '
253 filenode_new=node,
161 'raw diff instead'))
254 cut_off_limit=lim,
162 else:
255 ignore_whitespace=ign_whitespace_lcl,
163 diff = d.as_html()
256 line_context=line_context_lcl,
164
257 enable_comments=enable_comments)
165 if diff:
258 cumulative_diff += size
166 c.sum_removed += len(diff)
259 c.lines_added += st[0]
167 else:
260 c.lines_deleted += st[1]
168 diff = wrap_to_table(_('Changeset is to big and '
261 c.changes[changeset.raw_id].append(('changed', node, diff,
169 'was cut off, see raw '
262 cs1, cs2, st))
170 'changeset instead'))
171 c.cut_off = True
172 break
173
174 cs1 = filenode_old.last_changeset.raw_id
175 cs2 = node.last_changeset.raw_id
176 c.lines_added += st[0]
177 c.lines_deleted += st[1]
178 c.changes[changeset.raw_id].append(('changed', node, diff,
179 cs1, cs2, st))
180
263
181 #==================================================================
264 #==================================================================
182 # REMOVED FILES
265 # REMOVED FILES
183 #==================================================================
266 #==================================================================
184 if not c.cut_off:
267 for node in changeset.removed:
185 for node in changeset.removed:
268 c.changes[changeset.raw_id].append(('removed', node, None,
186 c.changes[changeset.raw_id].append(('removed', node, None,
269 None, None, (0, 0)))
187 None, None, (0, 0)))
270
271 # count inline comments
272 for path, lines in c.inline_comments:
273 for comments in lines.values():
274 c.inline_cnt += len(comments)
188
275
189 if len(c.cs_ranges) == 1:
276 if len(c.cs_ranges) == 1:
190 c.changeset = c.cs_ranges[0]
277 c.changeset = c.cs_ranges[0]
@@ -197,6 +284,8 b' class ChangesetController(BaseRepoContro'
197 def raw_changeset(self, revision):
284 def raw_changeset(self, revision):
198
285
199 method = request.GET.get('diff', 'show')
286 method = request.GET.get('diff', 'show')
287 ignore_whitespace = request.GET.get('ignorews') == '1'
288 line_context = request.GET.get('context', 3)
200 try:
289 try:
201 c.scm_type = c.rhodecode_repo.alias
290 c.scm_type = c.rhodecode_repo.alias
202 c.changeset = c.rhodecode_repo.get_changeset(revision)
291 c.changeset = c.rhodecode_repo.get_changeset(revision)
@@ -215,8 +304,10 b' class ChangesetController(BaseRepoContro'
215 if filenode_old.is_binary or node.is_binary:
304 if filenode_old.is_binary or node.is_binary:
216 diff = _('binary file') + '\n'
305 diff = _('binary file') + '\n'
217 else:
306 else:
218 f_gitdiff = differ.get_gitdiff(filenode_old, node)
307 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
219 diff = differ.DiffProcessor(f_gitdiff,
308 ignore_whitespace=ignore_whitespace,
309 context=line_context)
310 diff = diffs.DiffProcessor(f_gitdiff,
220 format='gitdiff').raw_diff()
311 format='gitdiff').raw_diff()
221
312
222 cs1 = None
313 cs1 = None
@@ -228,8 +319,10 b' class ChangesetController(BaseRepoContro'
228 if filenode_old.is_binary or node.is_binary:
319 if filenode_old.is_binary or node.is_binary:
229 diff = _('binary file')
320 diff = _('binary file')
230 else:
321 else:
231 f_gitdiff = differ.get_gitdiff(filenode_old, node)
322 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
232 diff = differ.DiffProcessor(f_gitdiff,
323 ignore_whitespace=ignore_whitespace,
324 context=line_context)
325 diff = diffs.DiffProcessor(f_gitdiff,
233 format='gitdiff').raw_diff()
326 format='gitdiff').raw_diff()
234
327
235 cs1 = filenode_old.last_changeset.raw_id
328 cs1 = filenode_old.last_changeset.raw_id
@@ -250,3 +343,25 b' class ChangesetController(BaseRepoContro'
250 c.diffs += x[2]
343 c.diffs += x[2]
251
344
252 return render('changeset/raw_changeset.html')
345 return render('changeset/raw_changeset.html')
346
347 def comment(self, repo_name, revision):
348 ChangesetCommentsModel().create(text=request.POST.get('text'),
349 repo_id=c.rhodecode_db_repo.repo_id,
350 user_id=c.rhodecode_user.user_id,
351 revision=revision,
352 f_path=request.POST.get('f_path'),
353 line_no=request.POST.get('line'))
354 Session.commit()
355 return redirect(h.url('changeset_home', repo_name=repo_name,
356 revision=revision))
357
358 @jsonify
359 def delete_comment(self, repo_name, comment_id):
360 co = ChangesetComment.get(comment_id)
361 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
362 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
363 ChangesetCommentsModel().delete(comment=co)
364 Session.commit()
365 return True
366 else:
367 raise HTTPForbidden()
@@ -7,7 +7,7 b''
7
7
8 :created_on: Dec 8, 2010
8 :created_on: Dec 8, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
@@ -54,7 +54,7 b' class ErrorController(BaseController):'
54 resp = request.environ.get('pylons.original_response')
54 resp = request.environ.get('pylons.original_response')
55 c.rhodecode_name = config.get('rhodecode_title')
55 c.rhodecode_name = config.get('rhodecode_title')
56
56
57 log.debug('### %s ###', resp.status)
57 log.debug('### %s ###' % resp.status)
58
58
59 e = request.environ
59 e = request.environ
60 c.serv_p = r'%(protocol)s://%(host)s/' \
60 c.serv_p = r'%(protocol)s://%(host)s/' \
@@ -7,7 +7,7 b''
7
7
8 :created_on: Apr 23, 2010
8 :created_on: Apr 23, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
@@ -51,6 +51,11 b' class FeedController(BaseRepoController)'
51 self.ttl = "5"
51 self.ttl = "5"
52 self.feed_nr = 10
52 self.feed_nr = 10
53
53
54 def _get_title(self, cs):
55 return "R%s:%s - %s" % (
56 cs.revision, cs.short_id, cs.message
57 )
58
54 def __changes(self, cs):
59 def __changes(self, cs):
55 changes = []
60 changes = []
56
61
@@ -72,18 +77,21 b' class FeedController(BaseRepoController)'
72
77
73 def atom(self, repo_name):
78 def atom(self, repo_name):
74 """Produce an atom-1.0 feed via feedgenerator module"""
79 """Produce an atom-1.0 feed via feedgenerator module"""
75 feed = Atom1Feed(title=self.title % repo_name,
80 feed = Atom1Feed(
76 link=url('summary_home', repo_name=repo_name,
81 title=self.title % repo_name,
77 qualified=True),
82 link=url('summary_home', repo_name=repo_name,
78 description=self.description % repo_name,
83 qualified=True),
79 language=self.language,
84 description=self.description % repo_name,
80 ttl=self.ttl)
85 language=self.language,
81 desc_msg = []
86 ttl=self.ttl
87 )
88
82 for cs in reversed(list(c.rhodecode_repo[-self.feed_nr:])):
89 for cs in reversed(list(c.rhodecode_repo[-self.feed_nr:])):
90 desc_msg = []
83 desc_msg.append('%s - %s<br/><pre>' % (cs.author, cs.date))
91 desc_msg.append('%s - %s<br/><pre>' % (cs.author, cs.date))
84 desc_msg.append(self.__changes(cs))
92 desc_msg.append(self.__changes(cs))
85
93
86 feed.add_item(title=cs.message,
94 feed.add_item(title=self._get_title(cs),
87 link=url('changeset_home', repo_name=repo_name,
95 link=url('changeset_home', repo_name=repo_name,
88 revision=cs.raw_id, qualified=True),
96 revision=cs.raw_id, qualified=True),
89 author_name=cs.author,
97 author_name=cs.author,
@@ -94,18 +102,21 b' class FeedController(BaseRepoController)'
94
102
95 def rss(self, repo_name):
103 def rss(self, repo_name):
96 """Produce an rss2 feed via feedgenerator module"""
104 """Produce an rss2 feed via feedgenerator module"""
97 feed = Rss201rev2Feed(title=self.title % repo_name,
105 feed = Rss201rev2Feed(
98 link=url('summary_home', repo_name=repo_name,
106 title=self.title % repo_name,
99 qualified=True),
107 link=url('summary_home', repo_name=repo_name,
100 description=self.description % repo_name,
108 qualified=True),
101 language=self.language,
109 description=self.description % repo_name,
102 ttl=self.ttl)
110 language=self.language,
103 desc_msg = []
111 ttl=self.ttl
112 )
113
104 for cs in reversed(list(c.rhodecode_repo[-self.feed_nr:])):
114 for cs in reversed(list(c.rhodecode_repo[-self.feed_nr:])):
115 desc_msg = []
105 desc_msg.append('%s - %s<br/><pre>' % (cs.author, cs.date))
116 desc_msg.append('%s - %s<br/><pre>' % (cs.author, cs.date))
106 desc_msg.append(self.__changes(cs))
117 desc_msg.append(self.__changes(cs))
107
118
108 feed.add_item(title=cs.message,
119 feed.add_item(title=self._get_title(cs),
109 link=url('changeset_home', repo_name=repo_name,
120 link=url('changeset_home', repo_name=repo_name,
110 revision=cs.raw_id, qualified=True),
121 revision=cs.raw_id, qualified=True),
111 author_name=cs.author,
122 author_name=cs.author,
@@ -27,25 +27,29 b' import os'
27 import logging
27 import logging
28 import traceback
28 import traceback
29
29
30 from os.path import join as jn
30 from pylons import request, response, tmpl_context as c, url
31
32 from pylons import request, response, session, tmpl_context as c, url
33 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
34 from pylons.controllers.util import redirect
32 from pylons.controllers.util import redirect
35 from pylons.decorators import jsonify
33 from pylons.decorators import jsonify
36
34
37 from vcs.conf import settings
35 from rhodecode.lib.vcs.conf import settings
38 from vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \
36 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \
39 EmptyRepositoryError, ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError
37 EmptyRepositoryError, ImproperArchiveTypeError, VCSError, \
40 from vcs.nodes import FileNode, NodeKind
38 NodeAlreadyExistsError
41 from vcs.utils import diffs as differ
39 from rhodecode.lib.vcs.nodes import FileNode
42
40
41 from rhodecode.lib.compat import OrderedDict
43 from rhodecode.lib import convert_line_endings, detect_mode, safe_str
42 from rhodecode.lib import convert_line_endings, detect_mode, safe_str
44 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
43 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
45 from rhodecode.lib.base import BaseRepoController, render
44 from rhodecode.lib.base import BaseRepoController, render
46 from rhodecode.lib.utils import EmptyChangeset
45 from rhodecode.lib.utils import EmptyChangeset
46 from rhodecode.lib import diffs
47 import rhodecode.lib.helpers as h
47 import rhodecode.lib.helpers as h
48 from rhodecode.model.repo import RepoModel
48 from rhodecode.model.repo import RepoModel
49 from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
50 _context_url, get_line_ctx, get_ignore_ws
51 from rhodecode.lib.diffs import wrapped_diff
52 from rhodecode.model.scm import ScmModel
49
53
50 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
51
55
@@ -104,26 +108,6 b' class FilesController(BaseRepoController'
104
108
105 return file_node
109 return file_node
106
110
107
108 def __get_paths(self, changeset, starting_path):
109 """recursive walk in root dir and return a set of all path in that dir
110 based on repository walk function
111 """
112 _files = list()
113 _dirs = list()
114
115 try:
116 tip = changeset
117 for topnode, dirs, files in tip.walk(starting_path):
118 for f in files:
119 _files.append(f.path)
120 for d in dirs:
121 _dirs.append(d.path)
122 except RepositoryError, e:
123 log.debug(traceback.format_exc())
124 pass
125 return _dirs, _files
126
127 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
111 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
128 'repository.admin')
112 'repository.admin')
129 def index(self, repo_name, revision, f_path):
113 def index(self, repo_name, revision, f_path):
@@ -162,9 +146,9 b' class FilesController(BaseRepoController'
162
146
163 # files or dirs
147 # files or dirs
164 try:
148 try:
165 c.files_list = c.changeset.get_node(f_path)
149 c.file = c.changeset.get_node(f_path)
166
150
167 if c.files_list.is_file():
151 if c.file.is_file():
168 c.file_history = self._get_node_history(c.changeset, f_path)
152 c.file_history = self._get_node_history(c.changeset, f_path)
169 else:
153 else:
170 c.file_history = []
154 c.file_history = []
@@ -405,13 +389,19 b' class FilesController(BaseRepoController'
405 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
389 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
406 'repository.admin')
390 'repository.admin')
407 def diff(self, repo_name, f_path):
391 def diff(self, repo_name, f_path):
408 diff1 = request.GET.get('diff1')
392 ignore_whitespace = request.GET.get('ignorews') == '1'
409 diff2 = request.GET.get('diff2')
393 line_context = request.GET.get('context', 3)
394 diff1 = request.GET.get('diff1', '')
395 diff2 = request.GET.get('diff2', '')
410 c.action = request.GET.get('diff')
396 c.action = request.GET.get('diff')
411 c.no_changes = diff1 == diff2
397 c.no_changes = diff1 == diff2
412 c.f_path = f_path
398 c.f_path = f_path
413 c.big_diff = False
399 c.big_diff = False
414
400 c.anchor_url = anchor_url
401 c.ignorews_url = _ignorews_url
402 c.context_url = _context_url
403 c.changes = OrderedDict()
404 c.changes[diff2] = []
415 try:
405 try:
416 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
406 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
417 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
407 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
@@ -427,12 +417,14 b' class FilesController(BaseRepoController'
427 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
417 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
428 node2 = FileNode('.', '', changeset=c.changeset_2)
418 node2 = FileNode('.', '', changeset=c.changeset_2)
429 except RepositoryError:
419 except RepositoryError:
430 return redirect(url('files_home',
420 return redirect(url('files_home', repo_name=c.repo_name,
431 repo_name=c.repo_name, f_path=f_path))
421 f_path=f_path))
432
422
433 if c.action == 'download':
423 if c.action == 'download':
434 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
424 _diff = diffs.get_gitdiff(node1, node2,
435 format='gitdiff')
425 ignore_whitespace=ignore_whitespace,
426 context=line_context)
427 diff = diffs.DiffProcessor(_diff, format='gitdiff')
436
428
437 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
429 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
438 response.content_type = 'text/plain'
430 response.content_type = 'text/plain'
@@ -441,39 +433,28 b' class FilesController(BaseRepoController'
441 return diff.raw_diff()
433 return diff.raw_diff()
442
434
443 elif c.action == 'raw':
435 elif c.action == 'raw':
444 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
436 _diff = diffs.get_gitdiff(node1, node2,
445 format='gitdiff')
437 ignore_whitespace=ignore_whitespace,
438 context=line_context)
439 diff = diffs.DiffProcessor(_diff, format='gitdiff')
446 response.content_type = 'text/plain'
440 response.content_type = 'text/plain'
447 return diff.raw_diff()
441 return diff.raw_diff()
448
442
449 elif c.action == 'diff':
450 if node1.is_binary or node2.is_binary:
451 c.cur_diff = _('Binary file')
452 elif node1.size > self.cut_off_limit or \
453 node2.size > self.cut_off_limit:
454 c.cur_diff = ''
455 c.big_diff = True
456 else:
457 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
458 format='gitdiff')
459 c.cur_diff = diff.as_html()
460 else:
443 else:
444 fid = h.FID(diff2, node2.path)
445 line_context_lcl = get_line_ctx(fid, request.GET)
446 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
461
447
462 #default option
448 lim = request.GET.get('fulldiff') or self.cut_off_limit
463 if node1.is_binary or node2.is_binary:
449 _, cs1, cs2, diff, st = wrapped_diff(filenode_old=node1,
464 c.cur_diff = _('Binary file')
450 filenode_new=node2,
465 elif node1.size > self.cut_off_limit or \
451 cut_off_limit=lim,
466 node2.size > self.cut_off_limit:
452 ignore_whitespace=ign_whitespace_lcl,
467 c.cur_diff = ''
453 line_context=line_context_lcl,
468 c.big_diff = True
454 enable_comments=False)
469
455
470 else:
456 c.changes = [('', node2, diff, cs1, cs2, st,)]
471 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
472 format='gitdiff')
473 c.cur_diff = diff.as_html()
474
457
475 if not c.cur_diff and not c.big_diff:
476 c.no_changes = True
477 return render('files/file_diff.html')
458 return render('files/file_diff.html')
478
459
479 def _get_node_history(self, cs, f_path):
460 def _get_node_history(self, cs, f_path):
@@ -485,18 +466,16 b' class FilesController(BaseRepoController'
485 tags_group = ([], _("Tags"))
466 tags_group = ([], _("Tags"))
486
467
487 for chs in changesets:
468 for chs in changesets:
488 n_desc = 'r%s:%s' % (chs.revision, chs.short_id)
469 n_desc = 'r%s:%s (%s)' % (chs.revision, chs.short_id, chs.branch)
489 changesets_group[0].append((chs.raw_id, n_desc,))
470 changesets_group[0].append((chs.raw_id, n_desc,))
490
471
491 hist_l.append(changesets_group)
472 hist_l.append(changesets_group)
492
473
493 for name, chs in c.rhodecode_repo.branches.items():
474 for name, chs in c.rhodecode_repo.branches.items():
494 #chs = chs.split(':')[-1]
495 branches_group[0].append((chs, name),)
475 branches_group[0].append((chs, name),)
496 hist_l.append(branches_group)
476 hist_l.append(branches_group)
497
477
498 for name, chs in c.rhodecode_repo.tags.items():
478 for name, chs in c.rhodecode_repo.tags.items():
499 #chs = chs.split(':')[-1]
500 tags_group[0].append((chs, name),)
479 tags_group[0].append((chs, name),)
501 hist_l.append(tags_group)
480 hist_l.append(tags_group)
502
481
@@ -508,6 +487,6 b' class FilesController(BaseRepoController'
508 def nodelist(self, repo_name, revision, f_path):
487 def nodelist(self, repo_name, revision, f_path):
509 if request.environ.get('HTTP_X_PARTIAL_XHR'):
488 if request.environ.get('HTTP_X_PARTIAL_XHR'):
510 cs = self.__get_cs_or_redirect(revision, repo_name)
489 cs = self.__get_cs_or_redirect(revision, repo_name)
511 _d, _f = self.__get_paths(cs, f_path)
490 _d, _f = ScmModel().get_nodes(repo_name, cs.raw_id, f_path,
491 flat=False)
512 return _d + _f
492 return _d + _f
513
@@ -7,7 +7,7 b''
7
7
8 :created_on: Apr 23, 2011
8 :created_on: Apr 23, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
@@ -7,7 +7,7 b''
7
7
8 :created_on: Apr 23, 2011
8 :created_on: Apr 23, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
@@ -23,13 +23,23 b''
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import logging
25 import logging
26 import formencode
27 import traceback
28 from formencode import htmlfill
26
29
27 from pylons import tmpl_context as c, request
30 from pylons import tmpl_context as c, request, url
31 from pylons.controllers.util import redirect
32 from pylons.i18n.translation import _
33
34 import rhodecode.lib.helpers as h
28
35
29 from rhodecode.lib.helpers import Page
36 from rhodecode.lib.helpers import Page
30 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \
38 NotAnonymous
31 from rhodecode.lib.base import BaseRepoController, render
39 from rhodecode.lib.base import BaseRepoController, render
32 from rhodecode.model.db import Repository, User, UserFollowing
40 from rhodecode.model.db import Repository, RepoGroup, UserFollowing, User
41 from rhodecode.model.repo import RepoModel
42 from rhodecode.model.forms import RepoForkForm
33
43
34 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
35
45
@@ -37,11 +47,59 b' log = logging.getLogger(__name__)'
37 class ForksController(BaseRepoController):
47 class ForksController(BaseRepoController):
38
48
39 @LoginRequired()
49 @LoginRequired()
40 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
41 'repository.admin')
42 def __before__(self):
50 def __before__(self):
43 super(ForksController, self).__before__()
51 super(ForksController, self).__before__()
44
52
53 def __load_defaults(self):
54 c.repo_groups = RepoGroup.groups_choices()
55 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
56
57 def __load_data(self, repo_name=None):
58 """
59 Load defaults settings for edit, and update
60
61 :param repo_name:
62 """
63 self.__load_defaults()
64
65 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
66 repo = db_repo.scm_instance
67
68 if c.repo_info is None:
69 h.flash(_('%s repository is not mapped to db perhaps'
70 ' it was created or renamed from the filesystem'
71 ' please run the application again'
72 ' in order to rescan repositories') % repo_name,
73 category='error')
74
75 return redirect(url('repos'))
76
77 c.default_user_id = User.get_by_username('default').user_id
78 c.in_public_journal = UserFollowing.query()\
79 .filter(UserFollowing.user_id == c.default_user_id)\
80 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
81
82 if c.repo_info.stats:
83 last_rev = c.repo_info.stats.stat_on_revision+1
84 else:
85 last_rev = 0
86 c.stats_revision = last_rev
87
88 c.repo_last_rev = repo.count() if repo.revisions else 0
89
90 if last_rev == 0 or c.repo_last_rev == 0:
91 c.stats_percentage = 0
92 else:
93 c.stats_percentage = '%.2f' % ((float((last_rev)) /
94 c.repo_last_rev) * 100)
95
96 defaults = RepoModel()._get_defaults(repo_name)
97 # add prefix to fork
98 defaults['repo_name'] = 'fork-' + defaults['repo_name']
99 return defaults
100
101 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
102 'repository.admin')
45 def forks(self, repo_name):
103 def forks(self, repo_name):
46 p = int(request.params.get('page', 1))
104 p = int(request.params.get('page', 1))
47 repo_id = c.rhodecode_db_repo.repo_id
105 repo_id = c.rhodecode_db_repo.repo_id
@@ -54,3 +112,63 b' class ForksController(BaseRepoController'
54 return c.forks_data
112 return c.forks_data
55
113
56 return render('/forks/forks.html')
114 return render('/forks/forks.html')
115
116 @NotAnonymous()
117 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
118 'repository.admin')
119 def fork(self, repo_name):
120 c.repo_info = Repository.get_by_repo_name(repo_name)
121 if not c.repo_info:
122 h.flash(_('%s repository is not mapped to db perhaps'
123 ' it was created or renamed from the file system'
124 ' please run the application again'
125 ' in order to rescan repositories') % repo_name,
126 category='error')
127
128 return redirect(url('home'))
129
130 defaults = self.__load_data(repo_name)
131
132 return htmlfill.render(
133 render('forks/fork.html'),
134 defaults=defaults,
135 encoding="UTF-8",
136 force_defaults=False
137 )
138
139
140 @NotAnonymous()
141 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
142 'repository.admin')
143 def fork_create(self, repo_name):
144 self.__load_defaults()
145 c.repo_info = Repository.get_by_repo_name(repo_name)
146 _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type},
147 repo_groups=c.repo_groups_choices,)()
148 form_result = {}
149 try:
150 form_result = _form.to_python(dict(request.POST))
151 # add org_path of repo so we can do a clone from it later
152 form_result['org_path'] = c.repo_info.repo_name
153
154 # create fork is done sometimes async on celery, db transaction
155 # management is handled there.
156 RepoModel().create_fork(form_result, self.rhodecode_user)
157 h.flash(_('forked %s repository as %s') \
158 % (repo_name, form_result['repo_name']),
159 category='success')
160 except formencode.Invalid, errors:
161 c.new_repo = errors.value['repo_name']
162
163 return htmlfill.render(
164 render('forks/fork.html'),
165 defaults=errors.value,
166 errors=errors.error_dict or {},
167 prefix_error=False,
168 encoding="UTF-8")
169 except Exception:
170 log.error(traceback.format_exc())
171 h.flash(_('An error occurred during repository forking %s') %
172 repo_name, category='error')
173
174 return redirect(url('home'))
@@ -7,7 +7,7 b''
7
7
8 :created_on: Feb 18, 2010
8 :created_on: Feb 18, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
@@ -24,14 +24,13 b''
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 from operator import itemgetter
28
27
29 from pylons import tmpl_context as c, request
28 from pylons import tmpl_context as c, request
30 from paste.httpexceptions import HTTPBadRequest
29 from paste.httpexceptions import HTTPBadRequest
31
30
32 from rhodecode.lib.auth import LoginRequired
31 from rhodecode.lib.auth import LoginRequired
33 from rhodecode.lib.base import BaseController, render
32 from rhodecode.lib.base import BaseController, render
34 from rhodecode.model.db import Group, Repository
33 from rhodecode.model.db import Repository
35
34
36 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
37
36
@@ -43,10 +42,8 b' class HomeController(BaseController):'
43 super(HomeController, self).__before__()
42 super(HomeController, self).__before__()
44
43
45 def index(self):
44 def index(self):
46
47 c.repos_list = self.scm_model.get_repos()
45 c.repos_list = self.scm_model.get_repos()
48
46 c.groups = self.scm_model.get_repos_groups()
49 c.groups = Group.query().filter(Group.group_parent_id == None).all()
50
47
51 return render('/index.html')
48 return render('/index.html')
52
49
@@ -58,3 +55,11 b' class HomeController(BaseController):'
58 return render('/repo_switcher_list.html')
55 return render('/repo_switcher_list.html')
59 else:
56 else:
60 return HTTPBadRequest()
57 return HTTPBadRequest()
58
59 def branch_tag_switcher(self, repo_name):
60 if request.is_xhr:
61 c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
62 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
63 return render('/switch_to_list.html')
64 else:
65 return HTTPBadRequest()
@@ -7,7 +7,7 b''
7
7
8 :created_on: Nov 21, 2010
8 :created_on: Nov 21, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
@@ -23,21 +23,24 b''
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import logging
25 import logging
26 from itertools import groupby
26
27
27 from sqlalchemy import or_
28 from sqlalchemy import or_
28 from sqlalchemy.orm import joinedload, make_transient
29 from sqlalchemy.orm import joinedload
29 from webhelpers.paginate import Page
30 from webhelpers.paginate import Page
30 from itertools import groupby
31 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
31
32
32 from paste.httpexceptions import HTTPBadRequest
33 from paste.httpexceptions import HTTPBadRequest
33 from pylons import request, tmpl_context as c, response, url
34 from pylons import request, tmpl_context as c, response, url
34 from pylons.i18n.translation import _
35 from pylons.i18n.translation import _
35 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
36
36
37 import rhodecode.lib.helpers as h
37 import rhodecode.lib.helpers as h
38 from rhodecode.lib.auth import LoginRequired, NotAnonymous
38 from rhodecode.lib.auth import LoginRequired, NotAnonymous
39 from rhodecode.lib.base import BaseController, render
39 from rhodecode.lib.base import BaseController, render
40 from rhodecode.model.db import UserLog, UserFollowing
40 from rhodecode.model.db import UserLog, UserFollowing, Repository, User
41 from rhodecode.model.meta import Session
42 from sqlalchemy.sql.expression import func
43 from rhodecode.model.scm import ScmModel
41
44
42 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
43
46
@@ -58,6 +61,13 b' class JournalController(BaseController):'
58 # Return a rendered template
61 # Return a rendered template
59 p = int(request.params.get('page', 1))
62 p = int(request.params.get('page', 1))
60
63
64 c.user = User.get(self.rhodecode_user.user_id)
65 all_repos = self.sa.query(Repository)\
66 .filter(Repository.user_id == c.user.user_id)\
67 .order_by(func.lower(Repository.repo_name)).all()
68
69 c.user_repos = ScmModel().get_repos(all_repos)
70
61 c.following = self.sa.query(UserFollowing)\
71 c.following = self.sa.query(UserFollowing)\
62 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
72 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
63 .options(joinedload(UserFollowing.follows_repository))\
73 .options(joinedload(UserFollowing.follows_repository))\
@@ -124,6 +134,7 b' class JournalController(BaseController):'
124 try:
134 try:
125 self.scm_model.toggle_following_user(user_id,
135 self.scm_model.toggle_following_user(user_id,
126 self.rhodecode_user.user_id)
136 self.rhodecode_user.user_id)
137 Session.commit()
127 return 'ok'
138 return 'ok'
128 except:
139 except:
129 raise HTTPBadRequest()
140 raise HTTPBadRequest()
@@ -133,11 +144,12 b' class JournalController(BaseController):'
133 try:
144 try:
134 self.scm_model.toggle_following_repo(repo_id,
145 self.scm_model.toggle_following_repo(repo_id,
135 self.rhodecode_user.user_id)
146 self.rhodecode_user.user_id)
147 Session.commit()
136 return 'ok'
148 return 'ok'
137 except:
149 except:
138 raise HTTPBadRequest()
150 raise HTTPBadRequest()
139
151
140 log.debug('token mismatch %s vs %s', cur_token, token)
152 log.debug('token mismatch %s vs %s' % (cur_token, token))
141 raise HTTPBadRequest()
153 raise HTTPBadRequest()
142
154
143 @LoginRequired()
155 @LoginRequired()
@@ -7,7 +7,7 b''
7
7
8 :created_on: Apr 22, 2010
8 :created_on: Apr 22, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
@@ -38,6 +38,7 b' from rhodecode.lib.base import BaseContr'
38 from rhodecode.model.db import User
38 from rhodecode.model.db import User
39 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
39 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
40 from rhodecode.model.user import UserModel
40 from rhodecode.model.user import UserModel
41 from rhodecode.model.meta import Session
41
42
42
43
43 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
@@ -49,7 +50,7 b' class LoginController(BaseController):'
49 super(LoginController, self).__before__()
50 super(LoginController, self).__before__()
50
51
51 def index(self):
52 def index(self):
52 #redirect if already logged in
53 # redirect if already logged in
53 c.came_from = request.GET.get('came_from', None)
54 c.came_from = request.GET.get('came_from', None)
54
55
55 if self.rhodecode_user.is_authenticated \
56 if self.rhodecode_user.is_authenticated \
@@ -58,21 +59,28 b' class LoginController(BaseController):'
58 return redirect(url('home'))
59 return redirect(url('home'))
59
60
60 if request.POST:
61 if request.POST:
61 #import Login Form validator class
62 # import Login Form validator class
62 login_form = LoginForm()
63 login_form = LoginForm()
63 try:
64 try:
64 c.form_result = login_form.to_python(dict(request.POST))
65 c.form_result = login_form.to_python(dict(request.POST))
65 #form checks for username/password, now we're authenticated
66 # form checks for username/password, now we're authenticated
66 username = c.form_result['username']
67 username = c.form_result['username']
67 user = User.get_by_username(username, case_insensitive=True)
68 user = User.get_by_username(username, case_insensitive=True)
68 auth_user = AuthUser(user.user_id)
69 auth_user = AuthUser(user.user_id)
69 auth_user.set_authenticated()
70 auth_user.set_authenticated()
70 session['rhodecode_user'] = auth_user
71 cs = auth_user.get_cookie_store()
72 session['rhodecode_user'] = cs
73 # If they want to be remembered, update the cookie
74 if c.form_result['remember'] is not False:
75 session.cookie_expires = False
76 session._set_cookie_values()
77 session._update_cookie_out()
71 session.save()
78 session.save()
72
79
73 log.info('user %s is now authenticated and stored in session',
80 log.info('user %s is now authenticated and stored in '
74 username)
81 'session, session attrs %s' % (username, cs))
75 user.update_lastlogin()
82 user.update_lastlogin()
83 Session.commit()
76
84
77 if c.came_from:
85 if c.came_from:
78 return redirect(c.came_from)
86 return redirect(c.came_from)
@@ -92,7 +100,6 b' class LoginController(BaseController):'
92 @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
100 @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
93 'hg.register.manual_activate')
101 'hg.register.manual_activate')
94 def register(self):
102 def register(self):
95 user_model = UserModel()
96 c.auto_active = False
103 c.auto_active = False
97 for perm in User.get_by_username('default').user_perms:
104 for perm in User.get_by_username('default').user_perms:
98 if perm.permission.permission_name == 'hg.register.auto_activate':
105 if perm.permission.permission_name == 'hg.register.auto_activate':
@@ -105,9 +112,10 b' class LoginController(BaseController):'
105 try:
112 try:
106 form_result = register_form.to_python(dict(request.POST))
113 form_result = register_form.to_python(dict(request.POST))
107 form_result['active'] = c.auto_active
114 form_result['active'] = c.auto_active
108 user_model.create_registration(form_result)
115 UserModel().create_registration(form_result)
109 h.flash(_('You have successfully registered into rhodecode'),
116 h.flash(_('You have successfully registered into rhodecode'),
110 category='success')
117 category='success')
118 Session.commit()
111 return redirect(url('login_home'))
119 return redirect(url('login_home'))
112
120
113 except formencode.Invalid, errors:
121 except formencode.Invalid, errors:
@@ -121,13 +129,11 b' class LoginController(BaseController):'
121 return render('/register.html')
129 return render('/register.html')
122
130
123 def password_reset(self):
131 def password_reset(self):
124 user_model = UserModel()
125 if request.POST:
132 if request.POST:
126
127 password_reset_form = PasswordResetForm()()
133 password_reset_form = PasswordResetForm()()
128 try:
134 try:
129 form_result = password_reset_form.to_python(dict(request.POST))
135 form_result = password_reset_form.to_python(dict(request.POST))
130 user_model.reset_password_link(form_result)
136 UserModel().reset_password_link(form_result)
131 h.flash(_('Your password reset link was sent'),
137 h.flash(_('Your password reset link was sent'),
132 category='success')
138 category='success')
133 return redirect(url('login_home'))
139 return redirect(url('login_home'))
@@ -143,13 +149,11 b' class LoginController(BaseController):'
143 return render('/password_reset.html')
149 return render('/password_reset.html')
144
150
145 def password_reset_confirmation(self):
151 def password_reset_confirmation(self):
146
147 if request.GET and request.GET.get('key'):
152 if request.GET and request.GET.get('key'):
148 try:
153 try:
149 user_model = UserModel()
150 user = User.get_by_api_key(request.GET.get('key'))
154 user = User.get_by_api_key(request.GET.get('key'))
151 data = dict(email=user.email)
155 data = dict(email=user.email)
152 user_model.reset_password(data)
156 UserModel().reset_password(data)
153 h.flash(_('Your password reset was successful, '
157 h.flash(_('Your password reset was successful, '
154 'new password has been sent to your email'),
158 'new password has been sent to your email'),
155 category='success')
159 category='success')
@@ -160,7 +164,6 b' class LoginController(BaseController):'
160 return redirect(url('login_home'))
164 return redirect(url('login_home'))
161
165
162 def logout(self):
166 def logout(self):
163 del session['rhodecode_user']
167 session.delete()
164 session.save()
168 log.info('Logging out and deleting session for user')
165 log.info('Logging out and setting user as Empty')
166 redirect(url('home'))
169 redirect(url('home'))
@@ -7,7 +7,7 b''
7
7
8 :created_on: Aug 7, 2010
8 :created_on: Aug 7, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
@@ -26,7 +26,7 b' import logging'
26 import traceback
26 import traceback
27
27
28 from pylons.i18n.translation import _
28 from pylons.i18n.translation import _
29 from pylons import request, config, session, tmpl_context as c
29 from pylons import request, config, tmpl_context as c
30
30
31 from rhodecode.lib.auth import LoginRequired
31 from rhodecode.lib.auth import LoginRequired
32 from rhodecode.lib.base import BaseController, render
32 from rhodecode.lib.base import BaseController, render
@@ -76,7 +76,7 b' class SearchController(BaseController):'
76 cur_query = u'repository:%s %s' % (c.repo_name, cur_query)
76 cur_query = u'repository:%s %s' % (c.repo_name, cur_query)
77 try:
77 try:
78 query = qp.parse(unicode(cur_query))
78 query = qp.parse(unicode(cur_query))
79
79 # extract words for highlight
80 if isinstance(query, Phrase):
80 if isinstance(query, Phrase):
81 highlight_items.update(query.words)
81 highlight_items.update(query.words)
82 elif isinstance(query, Prefix):
82 elif isinstance(query, Prefix):
@@ -92,18 +92,22 b' class SearchController(BaseController):'
92 log.debug(highlight_items)
92 log.debug(highlight_items)
93 results = searcher.search(query)
93 results = searcher.search(query)
94 res_ln = len(results)
94 res_ln = len(results)
95 c.runtime = '%s results (%.3f seconds)' \
95 c.runtime = '%s results (%.3f seconds)' % (
96 % (res_ln, results.runtime)
96 res_ln, results.runtime
97 )
97
98
98 def url_generator(**kw):
99 def url_generator(**kw):
99 return update_params("?q=%s&type=%s" \
100 return update_params("?q=%s&type=%s" \
100 % (c.cur_query, c.cur_search), **kw)
101 % (c.cur_query, c.cur_search), **kw)
101
102
102 c.formated_results = Page(
103 c.formated_results = Page(
103 ResultWrapper(search_type, searcher, matcher,
104 ResultWrapper(search_type, searcher, matcher,
104 highlight_items),
105 highlight_items),
105 page=p, item_count=res_ln,
106 page=p,
106 items_per_page=10, url=url_generator)
107 item_count=res_ln,
108 items_per_page=10,
109 url=url_generator
110 )
107
111
108 except QueryParserError:
112 except QueryParserError:
109 c.runtime = _('Invalid search query. Try quoting it.')
113 c.runtime = _('Invalid search query. Try quoting it.')
@@ -117,5 +121,6 b' class SearchController(BaseController):'
117 log.error(traceback.format_exc())
121 log.error(traceback.format_exc())
118 c.runtime = _('An error occurred during this search operation')
122 c.runtime = _('An error occurred during this search operation')
119
123
124
120 # Return a rendered template
125 # Return a rendered template
121 return render('/search/search.html')
126 return render('/search/search.html')
@@ -7,7 +7,7 b''
7
7
8 :created_on: Jun 30, 2010
8 :created_on: Jun 30, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
@@ -35,14 +35,14 b' from pylons.i18n.translation import _'
35
35
36 import rhodecode.lib.helpers as h
36 import rhodecode.lib.helpers as h
37
37
38 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAllDecorator, \
38 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAllDecorator
39 HasRepoPermissionAnyDecorator, NotAnonymous
40 from rhodecode.lib.base import BaseRepoController, render
39 from rhodecode.lib.base import BaseRepoController, render
41 from rhodecode.lib.utils import invalidate_cache, action_logger
40 from rhodecode.lib.utils import invalidate_cache, action_logger
42
41
43 from rhodecode.model.forms import RepoSettingsForm, RepoForkForm
42 from rhodecode.model.forms import RepoSettingsForm
44 from rhodecode.model.repo import RepoModel
43 from rhodecode.model.repo import RepoModel
45 from rhodecode.model.db import Group
44 from rhodecode.model.db import RepoGroup
45 from rhodecode.model.meta import Session
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
@@ -52,15 +52,15 b' class SettingsController(BaseRepoControl'
52 @LoginRequired()
52 @LoginRequired()
53 def __before__(self):
53 def __before__(self):
54 super(SettingsController, self).__before__()
54 super(SettingsController, self).__before__()
55
55
56 def __load_defaults(self):
56 def __load_defaults(self):
57 c.repo_groups = Group.groups_choices()
57 c.repo_groups = RepoGroup.groups_choices()
58 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
58 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
59
59
60 repo_model = RepoModel()
60 repo_model = RepoModel()
61 c.users_array = repo_model.get_users_js()
61 c.users_array = repo_model.get_users_js()
62 c.users_groups_array = repo_model.get_users_groups_js()
62 c.users_groups_array = repo_model.get_users_groups_js()
63
63
64 @HasRepoPermissionAllDecorator('repository.admin')
64 @HasRepoPermissionAllDecorator('repository.admin')
65 def index(self, repo_name):
65 def index(self, repo_name):
66 repo_model = RepoModel()
66 repo_model = RepoModel()
@@ -89,15 +89,15 b' class SettingsController(BaseRepoControl'
89 def update(self, repo_name):
89 def update(self, repo_name):
90 repo_model = RepoModel()
90 repo_model = RepoModel()
91 changed_name = repo_name
91 changed_name = repo_name
92
92
93 self.__load_defaults()
93 self.__load_defaults()
94
94
95 _form = RepoSettingsForm(edit=True,
95 _form = RepoSettingsForm(edit=True,
96 old_data={'repo_name': repo_name},
96 old_data={'repo_name': repo_name},
97 repo_groups=c.repo_groups_choices)()
97 repo_groups=c.repo_groups_choices)()
98 try:
98 try:
99 form_result = _form.to_python(dict(request.POST))
99 form_result = _form.to_python(dict(request.POST))
100
100
101 repo_model.update(repo_name, form_result)
101 repo_model.update(repo_name, form_result)
102 invalidate_cache('get_repo_cached_%s' % repo_name)
102 invalidate_cache('get_repo_cached_%s' % repo_name)
103 h.flash(_('Repository %s updated successfully' % repo_name),
103 h.flash(_('Repository %s updated successfully' % repo_name),
@@ -105,6 +105,7 b' class SettingsController(BaseRepoControl'
105 changed_name = form_result['repo_name_full']
105 changed_name = form_result['repo_name_full']
106 action_logger(self.rhodecode_user, 'user_updated_repo',
106 action_logger(self.rhodecode_user, 'user_updated_repo',
107 changed_name, '', self.sa)
107 changed_name, '', self.sa)
108 Session.commit()
108 except formencode.Invalid, errors:
109 except formencode.Invalid, errors:
109 c.repo_info = repo_model.get_by_repo_name(repo_name)
110 c.repo_info = repo_model.get_by_repo_name(repo_name)
110 c.users_array = repo_model.get_users_js()
111 c.users_array = repo_model.get_users_js()
@@ -148,61 +149,10 b' class SettingsController(BaseRepoControl'
148 repo_model.delete(repo)
149 repo_model.delete(repo)
149 invalidate_cache('get_repo_cached_%s' % repo_name)
150 invalidate_cache('get_repo_cached_%s' % repo_name)
150 h.flash(_('deleted repository %s') % repo_name, category='success')
151 h.flash(_('deleted repository %s') % repo_name, category='success')
152 Session.commit()
151 except Exception:
153 except Exception:
152 log.error(traceback.format_exc())
154 log.error(traceback.format_exc())
153 h.flash(_('An error occurred during deletion of %s') % repo_name,
155 h.flash(_('An error occurred during deletion of %s') % repo_name,
154 category='error')
156 category='error')
155
157
156 return redirect(url('home'))
158 return redirect(url('home'))
157
158 @NotAnonymous()
159 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
160 'repository.admin')
161 def fork(self, repo_name):
162 repo_model = RepoModel()
163 c.repo_info = repo = repo_model.get_by_repo_name(repo_name)
164 if not repo:
165 h.flash(_('%s repository is not mapped to db perhaps'
166 ' it was created or renamed from the file system'
167 ' please run the application again'
168 ' in order to rescan repositories') % repo_name,
169 category='error')
170
171 return redirect(url('home'))
172
173 return render('settings/repo_fork.html')
174
175 @NotAnonymous()
176 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
177 'repository.admin')
178 def fork_create(self, repo_name):
179 repo_model = RepoModel()
180 c.repo_info = repo_model.get_by_repo_name(repo_name)
181 _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type})()
182 form_result = {}
183 try:
184 form_result = _form.to_python(dict(request.POST))
185 form_result.update({'repo_name': repo_name})
186 repo_model.create_fork(form_result, self.rhodecode_user)
187 h.flash(_('forked %s repository as %s') \
188 % (repo_name, form_result['fork_name']),
189 category='success')
190 action_logger(self.rhodecode_user,
191 'user_forked_repo:%s' % form_result['fork_name'],
192 repo_name, '', self.sa)
193 except formencode.Invalid, errors:
194 c.new_repo = errors.value['fork_name']
195 r = render('settings/repo_fork.html')
196
197 return htmlfill.render(
198 r,
199 defaults=errors.value,
200 errors=errors.error_dict or {},
201 prefix_error=False,
202 encoding="UTF-8")
203 except Exception:
204 log.error(traceback.format_exc())
205 h.flash(_('An error occurred during repository forking %s') %
206 repo_name, category='error')
207
208 return redirect(url('home'))
@@ -7,7 +7,7 b''
7
7
8 :created_on: Apr 18, 2010
8 :created_on: Apr 18, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
@@ -30,6 +30,7 b' from pylons import tmpl_context as c, re'
30 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
30 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
31 from rhodecode.lib.base import BaseRepoController, render
31 from rhodecode.lib.base import BaseRepoController, render
32 from rhodecode.lib.helpers import RepoPage
32 from rhodecode.lib.helpers import RepoPage
33 from pylons.controllers.util import redirect
33
34
34 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
35
36
@@ -50,8 +51,11 b' class ShortlogController(BaseRepoControl'
50 return url('shortlog_home', repo_name=repo_name, size=size, **kw)
51 return url('shortlog_home', repo_name=repo_name, size=size, **kw)
51
52
52 c.repo_changesets = RepoPage(c.rhodecode_repo, page=p,
53 c.repo_changesets = RepoPage(c.rhodecode_repo, page=p,
53 items_per_page=size,
54 items_per_page=size, url=url_generator)
54 url=url_generator)
55
56 if not c.repo_changesets:
57 return redirect(url('summary_home', repo_name=repo_name))
58
55 c.shortlog_data = render('shortlog/shortlog_data.html')
59 c.shortlog_data = render('shortlog/shortlog_data.html')
56 if request.environ.get('HTTP_X_PARTIAL_XHR'):
60 if request.environ.get('HTTP_X_PARTIAL_XHR'):
57 return c.shortlog_data
61 return c.shortlog_data
@@ -7,7 +7,7 b''
7
7
8 :created_on: Apr 18, 2010
8 :created_on: Apr 18, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
@@ -23,23 +23,28 b''
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import traceback
26 import calendar
27 import calendar
27 import logging
28 import logging
28 from time import mktime
29 from time import mktime
29 from datetime import datetime, timedelta, date
30 from datetime import timedelta, date
31 from itertools import product
32 from urlparse import urlparse
30
33
31 from vcs.exceptions import ChangesetError
34 from rhodecode.lib.vcs.exceptions import ChangesetError, EmptyRepositoryError, \
35 NodeDoesNotExistError
32
36
33 from pylons import tmpl_context as c, request, url
37 from pylons import tmpl_context as c, request, url, config
34 from pylons.i18n.translation import _
38 from pylons.i18n.translation import _
35
39
36 from rhodecode.model.db import Statistics, Repository
40 from beaker.cache import cache_region, region_invalidate
37 from rhodecode.model.repo import RepoModel
38
41
42 from rhodecode.model.db import Statistics, CacheInvalidation
43 from rhodecode.lib import ALL_READMES, ALL_EXTS
39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
44 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
40 from rhodecode.lib.base import BaseRepoController, render
45 from rhodecode.lib.base import BaseRepoController, render
41 from rhodecode.lib.utils import EmptyChangeset
46 from rhodecode.lib.utils import EmptyChangeset
42
47 from rhodecode.lib.markup_renderer import MarkupRenderer
43 from rhodecode.lib.celerylib import run_task
48 from rhodecode.lib.celerylib import run_task
44 from rhodecode.lib.celerylib.tasks import get_commits_stats, \
49 from rhodecode.lib.celerylib.tasks import get_commits_stats, \
45 LANGUAGES_EXTENSIONS_MAP
50 LANGUAGES_EXTENSIONS_MAP
@@ -48,6 +53,10 b' from rhodecode.lib.compat import json, O'
48
53
49 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
50
55
56 README_FILES = [''.join([x[0][0], x[1][0]]) for x in
57 sorted(list(product(ALL_READMES, ALL_EXTS)),
58 key=lambda y:y[0][1] + y[1][1])]
59
51
60
52 class SummaryController(BaseRepoController):
61 class SummaryController(BaseRepoController):
53
62
@@ -58,10 +67,7 b' class SummaryController(BaseRepoControll'
58 super(SummaryController, self).__before__()
67 super(SummaryController, self).__before__()
59
68
60 def index(self, repo_name):
69 def index(self, repo_name):
61
62 e = request.environ
63 c.dbrepo = dbrepo = c.rhodecode_db_repo
70 c.dbrepo = dbrepo = c.rhodecode_db_repo
64
65 c.following = self.scm_model.is_following_repo(repo_name,
71 c.following = self.scm_model.is_following_repo(repo_name,
66 self.rhodecode_user.user_id)
72 self.rhodecode_user.user_id)
67
73
@@ -72,26 +78,34 b' class SummaryController(BaseRepoControll'
72 items_per_page=10, url=url_generator)
78 items_per_page=10, url=url_generator)
73
79
74 if self.rhodecode_user.username == 'default':
80 if self.rhodecode_user.username == 'default':
75 #for default(anonymous) user we don't need to pass credentials
81 # for default(anonymous) user we don't need to pass credentials
76 username = ''
82 username = ''
77 password = ''
83 password = ''
78 else:
84 else:
79 username = str(self.rhodecode_user.username)
85 username = str(self.rhodecode_user.username)
80 password = '@'
86 password = '@'
81
87
82 if e.get('wsgi.url_scheme') == 'https':
88 parsed_url = urlparse(url.current(qualified=True))
83 split_s = 'https://'
89
84 else:
90 default_clone_uri = '{scheme}://{user}{pass}{netloc}{path}'
85 split_s = 'http://'
91
92 uri_tmpl = config.get('clone_uri', default_clone_uri)
93 uri_tmpl = uri_tmpl.replace('{', '%(').replace('}', ')s')
86
94
87 qualified_uri = [split_s] + [url.current(qualified=True)\
95 uri_dict = {
88 .split(split_s)[-1]]
96 'user': username,
89 uri = u'%(proto)s%(user)s%(pass)s%(rest)s' \
97 'pass': password,
90 % {'user': username,
98 'scheme': parsed_url.scheme,
91 'pass': password,
99 'netloc': parsed_url.netloc,
92 'proto': qualified_uri[0],
100 'path': parsed_url.path
93 'rest': qualified_uri[1]}
101 }
102 uri = uri_tmpl % uri_dict
103 # generate another clone url by id
104 uri_dict.update({'path': '/_%s' % c.dbrepo.repo_id})
105 uri_id = uri_tmpl % uri_dict
106
94 c.clone_repo_url = uri
107 c.clone_repo_url = uri
108 c.clone_repo_url_id = uri_id
95 c.repo_tags = OrderedDict()
109 c.repo_tags = OrderedDict()
96 for name, hash in c.rhodecode_repo.tags.items()[:10]:
110 for name, hash in c.rhodecode_repo.tags.items()[:10]:
97 try:
111 try:
@@ -161,8 +175,44 b' class SummaryController(BaseRepoControll'
161 if c.enable_downloads:
175 if c.enable_downloads:
162 c.download_options = self._get_download_links(c.rhodecode_repo)
176 c.download_options = self._get_download_links(c.rhodecode_repo)
163
177
178 c.readme_data, c.readme_file = self.__get_readme_data(c.rhodecode_repo)
164 return render('summary/summary.html')
179 return render('summary/summary.html')
165
180
181 def __get_readme_data(self, repo):
182
183 @cache_region('long_term')
184 def _get_readme_from_cache(key):
185 readme_data = None
186 readme_file = None
187 log.debug('Fetching readme file')
188 try:
189 cs = repo.get_changeset('tip')
190 renderer = MarkupRenderer()
191 for f in README_FILES:
192 try:
193 readme = cs.get_node(f)
194 readme_file = f
195 readme_data = renderer.render(readme.content, f)
196 log.debug('Found readme %s' % readme_file)
197 break
198 except NodeDoesNotExistError:
199 continue
200 except ChangesetError:
201 pass
202 except EmptyRepositoryError:
203 pass
204 except Exception:
205 log.error(traceback.format_exc())
206
207 return readme_data, readme_file
208
209 key = repo.name + '_README'
210 inv = CacheInvalidation.invalidate(key)
211 if inv is not None:
212 region_invalidate(_get_readme_from_cache, None, key)
213 CacheInvalidation.set_valid(inv.cache_key)
214 return _get_readme_from_cache(key)
215
166 def _get_download_links(self, repo):
216 def _get_download_links(self, repo):
167
217
168 download_l = []
218 download_l = []
@@ -7,7 +7,7 b''
7
7
8 :created_on: Apr 21, 2010
8 :created_on: Apr 21, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
@@ -24,6 +24,9 b''
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import re
28 from rhodecode.lib.vcs.utils.lazy import LazyProperty
29
27
30
28 def __get_lem():
31 def __get_lem():
29 from pygments import lexers
32 from pygments import lexers
@@ -66,6 +69,34 b" ADDITIONAL_MAPPINGS = {'xaml': 'XAML'}"
66
69
67 LANGUAGES_EXTENSIONS_MAP.update(ADDITIONAL_MAPPINGS)
70 LANGUAGES_EXTENSIONS_MAP.update(ADDITIONAL_MAPPINGS)
68
71
72 # list of readme files to search in file tree and display in summary
73 # attached weights defines the search order lower is first
74 ALL_READMES = [
75 ('readme', 0), ('README', 0), ('Readme', 0),
76 ('doc/readme', 1), ('doc/README', 1), ('doc/Readme', 1),
77 ('Docs/readme', 2), ('Docs/README', 2), ('Docs/Readme', 2),
78 ('DOCS/readme', 2), ('DOCS/README', 2), ('DOCS/Readme', 2),
79 ('docs/readme', 2), ('docs/README', 2), ('docs/Readme', 2),
80 ]
81
82 # extension together with weights to search lower is first
83 RST_EXTS = [
84 ('', 0), ('.rst', 1), ('.rest', 1),
85 ('.RST', 2), ('.REST', 2),
86 ('.txt', 3), ('.TXT', 3)
87 ]
88
89 MARKDOWN_EXTS = [
90 ('.md', 1), ('.MD', 1),
91 ('.mkdn', 2), ('.MKDN', 2),
92 ('.mdown', 3), ('.MDOWN', 3),
93 ('.markdown', 4), ('.MARKDOWN', 4)
94 ]
95
96 PLAIN_EXTS = [('.text', 2), ('.TEXT', 2)]
97
98 ALL_EXTS = MARKDOWN_EXTS + RST_EXTS + PLAIN_EXTS
99
69
100
70 def str2bool(_str):
101 def str2bool(_str):
71 """
102 """
@@ -107,7 +138,6 b' def convert_line_endings(line, mode):'
107 line = replace(line, '\r\n', '\r')
138 line = replace(line, '\r\n', '\r')
108 line = replace(line, '\n', '\r')
139 line = replace(line, '\n', '\r')
109 elif mode == 2:
140 elif mode == 2:
110 import re
111 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
141 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
112 return line
142 return line
113
143
@@ -151,7 +181,7 b' def generate_api_key(username, salt=None'
151 return hashlib.sha1(username + salt).hexdigest()
181 return hashlib.sha1(username + salt).hexdigest()
152
182
153
183
154 def safe_unicode(str_, from_encoding='utf8'):
184 def safe_unicode(str_, from_encoding=None):
155 """
185 """
156 safe unicode function. Does few trick to turn str_ into unicode
186 safe unicode function. Does few trick to turn str_ into unicode
157
187
@@ -165,6 +195,11 b" def safe_unicode(str_, from_encoding='ut"
165 if isinstance(str_, unicode):
195 if isinstance(str_, unicode):
166 return str_
196 return str_
167
197
198 if not from_encoding:
199 import rhodecode
200 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
201 from_encoding = DEFAULT_ENCODING
202
168 try:
203 try:
169 return unicode(str_)
204 return unicode(str_)
170 except UnicodeDecodeError:
205 except UnicodeDecodeError:
@@ -184,10 +219,11 b" def safe_unicode(str_, from_encoding='ut"
184 except (ImportError, UnicodeDecodeError, Exception):
219 except (ImportError, UnicodeDecodeError, Exception):
185 return unicode(str_, from_encoding, 'replace')
220 return unicode(str_, from_encoding, 'replace')
186
221
187 def safe_str(unicode_, to_encoding='utf8'):
222
223 def safe_str(unicode_, to_encoding=None):
188 """
224 """
189 safe str function. Does few trick to turn unicode_ into string
225 safe str function. Does few trick to turn unicode_ into string
190
226
191 In case of UnicodeEncodeError we try to return it with encoding detected
227 In case of UnicodeEncodeError we try to return it with encoding detected
192 by chardet library if it fails fallback to string with errors replaced
228 by chardet library if it fails fallback to string with errors replaced
193
229
@@ -199,9 +235,17 b" def safe_str(unicode_, to_encoding='utf8"
199 if not isinstance(unicode_, basestring):
235 if not isinstance(unicode_, basestring):
200 return str(unicode_)
236 return str(unicode_)
201
237
238 if not isinstance(unicode_, basestring):
239 return str(unicode_)
240
202 if isinstance(unicode_, str):
241 if isinstance(unicode_, str):
203 return unicode_
242 return unicode_
204
243
244 if not to_encoding:
245 import rhodecode
246 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
247 to_encoding = DEFAULT_ENCODING
248
205 try:
249 try:
206 return unicode_.encode(to_encoding)
250 return unicode_.encode(to_encoding)
207 except UnicodeEncodeError:
251 except UnicodeEncodeError:
@@ -221,11 +265,10 b" def safe_str(unicode_, to_encoding='utf8"
221 return safe_str
265 return safe_str
222
266
223
267
224
225 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
268 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
226 """
269 """
227 Custom engine_from_config functions that makes sure we use NullPool for
270 Custom engine_from_config functions that makes sure we use NullPool for
228 file based sqlite databases. This prevents errors on sqlite. This only
271 file based sqlite databases. This prevents errors on sqlite. This only
229 applies to sqlalchemy versions < 0.7.0
272 applies to sqlalchemy versions < 0.7.0
230
273
231 """
274 """
@@ -284,7 +327,7 b' def engine_from_config(configuration, pr'
284 def age(curdate):
327 def age(curdate):
285 """
328 """
286 turns a datetime into an age string.
329 turns a datetime into an age string.
287
330
288 :param curdate: datetime object
331 :param curdate: datetime object
289 :rtype: unicode
332 :rtype: unicode
290 :returns: unicode words describing age
333 :returns: unicode words describing age
@@ -293,7 +336,7 b' def age(curdate):'
293 from datetime import datetime
336 from datetime import datetime
294 from webhelpers.date import time_ago_in_words
337 from webhelpers.date import time_ago_in_words
295
338
296 _ = lambda s:s
339 _ = lambda s: s
297
340
298 if not curdate:
341 if not curdate:
299 return ''
342 return ''
@@ -310,7 +353,8 b' def age(curdate):'
310 pos = 1
353 pos = 1
311 for scale in agescales:
354 for scale in agescales:
312 if scale[1] <= age_seconds:
355 if scale[1] <= age_seconds:
313 if pos == 6:pos = 5
356 if pos == 6:
357 pos = 5
314 return '%s %s' % (time_ago_in_words(curdate,
358 return '%s %s' % (time_ago_in_words(curdate,
315 agescales[pos][0]), _('ago'))
359 agescales[pos][0]), _('ago'))
316 pos += 1
360 pos += 1
@@ -321,10 +365,10 b' def age(curdate):'
321 def uri_filter(uri):
365 def uri_filter(uri):
322 """
366 """
323 Removes user:password from given url string
367 Removes user:password from given url string
324
368
325 :param uri:
369 :param uri:
326 :rtype: unicode
370 :rtype: unicode
327 :returns: filtered list of strings
371 :returns: filtered list of strings
328 """
372 """
329 if not uri:
373 if not uri:
330 return ''
374 return ''
@@ -353,7 +397,7 b' def uri_filter(uri):'
353 def credentials_filter(uri):
397 def credentials_filter(uri):
354 """
398 """
355 Returns a url with removed credentials
399 Returns a url with removed credentials
356
400
357 :param uri:
401 :param uri:
358 """
402 """
359
403
@@ -364,16 +408,17 b' def credentials_filter(uri):'
364
408
365 return ''.join(uri)
409 return ''.join(uri)
366
410
411
367 def get_changeset_safe(repo, rev):
412 def get_changeset_safe(repo, rev):
368 """
413 """
369 Safe version of get_changeset if this changeset doesn't exists for a
414 Safe version of get_changeset if this changeset doesn't exists for a
370 repo it returns a Dummy one instead
415 repo it returns a Dummy one instead
371
416
372 :param repo:
417 :param repo:
373 :param rev:
418 :param rev:
374 """
419 """
375 from vcs.backends.base import BaseRepository
420 from rhodecode.lib.vcs.backends.base import BaseRepository
376 from vcs.exceptions import RepositoryError
421 from rhodecode.lib.vcs.exceptions import RepositoryError
377 if not isinstance(repo, BaseRepository):
422 if not isinstance(repo, BaseRepository):
378 raise Exception('You must pass an Repository '
423 raise Exception('You must pass an Repository '
379 'object as first argument got %s', type(repo))
424 'object as first argument got %s', type(repo))
@@ -390,13 +435,13 b' def get_current_revision(quiet=False):'
390 """
435 """
391 Returns tuple of (number, id) from repository containing this package
436 Returns tuple of (number, id) from repository containing this package
392 or None if repository could not be found.
437 or None if repository could not be found.
393
438
394 :param quiet: prints error for fetching revision if True
439 :param quiet: prints error for fetching revision if True
395 """
440 """
396
441
397 try:
442 try:
398 from vcs import get_repo
443 from rhodecode.lib.vcs import get_repo
399 from vcs.utils.helpers import get_scm
444 from rhodecode.lib.vcs.utils.helpers import get_scm
400 repopath = os.path.join(os.path.dirname(__file__), '..', '..')
445 repopath = os.path.join(os.path.dirname(__file__), '..', '..')
401 scm = get_scm(repopath)[0]
446 scm = get_scm(repopath)[0]
402 repo = get_repo(path=repopath, alias=scm)
447 repo = get_repo(path=repopath, alias=scm)
@@ -408,3 +453,15 b' def get_current_revision(quiet=False):'
408 "was: %s" % err)
453 "was: %s" % err)
409 return None
454 return None
410
455
456
457 def extract_mentioned_users(s):
458 """
459 Returns unique usernames from given string s that have @mention
460
461 :param s: string to get mentions
462 """
463 usrs = {}
464 for username in re.findall(r'(?:^@|\s@)(\w+)', s):
465 usrs[username] = username
466
467 return sorted(usrs.keys())
@@ -6,7 +6,8 b''
6 authentication and permission libraries
6 authentication and permission libraries
7
7
8 :created_on: Apr 4, 2010
8 :created_on: Apr 4, 2010
9 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
11 """
12 """
12 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
@@ -30,11 +31,12 b' import hashlib'
30 from tempfile import _RandomNameSequence
31 from tempfile import _RandomNameSequence
31 from decorator import decorator
32 from decorator import decorator
32
33
33 from pylons import config, session, url, request
34 from pylons import config, url, request
34 from pylons.controllers.util import abort, redirect
35 from pylons.controllers.util import abort, redirect
35 from pylons.i18n.translation import _
36 from pylons.i18n.translation import _
36
37
37 from rhodecode import __platform__, PLATFORM_WIN, PLATFORM_OTHERS
38 from rhodecode import __platform__, PLATFORM_WIN, PLATFORM_OTHERS
39 from rhodecode.model.meta import Session
38
40
39 if __platform__ in PLATFORM_WIN:
41 if __platform__ in PLATFORM_WIN:
40 from hashlib import sha256
42 from hashlib import sha256
@@ -43,20 +45,22 b' if __platform__ in PLATFORM_OTHERS:'
43
45
44 from rhodecode.lib import str2bool, safe_unicode
46 from rhodecode.lib import str2bool, safe_unicode
45 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
47 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
46 from rhodecode.lib.utils import get_repo_slug
48 from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug
47 from rhodecode.lib.auth_ldap import AuthLdap
49 from rhodecode.lib.auth_ldap import AuthLdap
48
50
49 from rhodecode.model import meta
51 from rhodecode.model import meta
50 from rhodecode.model.user import UserModel
52 from rhodecode.model.user import UserModel
51 from rhodecode.model.db import Permission, RhodeCodeSettings, User
53 from rhodecode.model.db import Permission, RhodeCodeSetting, User
52
54
53 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
54
56
55
57
56 class PasswordGenerator(object):
58 class PasswordGenerator(object):
57 """This is a simple class for generating password from
59 """
58 different sets of characters
60 This is a simple class for generating password from different sets of
59 usage:
61 characters
62 usage::
63
60 passwd_gen = PasswordGenerator()
64 passwd_gen = PasswordGenerator()
61 #print 8-letter password containing only big and small letters
65 #print 8-letter password containing only big and small letters
62 of alphabet
66 of alphabet
@@ -128,15 +132,24 b' def check_password(password, hashed):'
128 return RhodeCodeCrypto.hash_check(password, hashed)
132 return RhodeCodeCrypto.hash_check(password, hashed)
129
133
130
134
131 def generate_api_key(username, salt=None):
135 def generate_api_key(str_, salt=None):
136 """
137 Generates API KEY from given string
138
139 :param str_:
140 :param salt:
141 """
142
132 if salt is None:
143 if salt is None:
133 salt = _RandomNameSequence().next()
144 salt = _RandomNameSequence().next()
134
145
135 return hashlib.sha1(username + salt).hexdigest()
146 return hashlib.sha1(str_ + salt).hexdigest()
136
147
137
148
138 def authfunc(environ, username, password):
149 def authfunc(environ, username, password):
139 """Dummy authentication function used in Mercurial/Git/ and access control,
150 """
151 Dummy authentication wrapper function used in Mercurial and Git for
152 access control.
140
153
141 :param environ: needed only for using in Basic auth
154 :param environ: needed only for using in Basic auth
142 """
155 """
@@ -144,7 +157,8 b' def authfunc(environ, username, password'
144
157
145
158
146 def authenticate(username, password):
159 def authenticate(username, password):
147 """Authentication function used for access control,
160 """
161 Authentication function used for access control,
148 firstly checks for db authentication then if ldap is enabled for ldap
162 firstly checks for db authentication then if ldap is enabled for ldap
149 authentication, also creates ldap user if not in database
163 authentication, also creates ldap user if not in database
150
164
@@ -159,16 +173,16 b' def authenticate(username, password):'
159 if user is not None and not user.ldap_dn:
173 if user is not None and not user.ldap_dn:
160 if user.active:
174 if user.active:
161 if user.username == 'default' and user.active:
175 if user.username == 'default' and user.active:
162 log.info('user %s authenticated correctly as anonymous user',
176 log.info('user %s authenticated correctly as anonymous user' %
163 username)
177 username)
164 return True
178 return True
165
179
166 elif user.username == username and check_password(password,
180 elif user.username == username and check_password(password,
167 user.password):
181 user.password):
168 log.info('user %s authenticated correctly', username)
182 log.info('user %s authenticated correctly' % username)
169 return True
183 return True
170 else:
184 else:
171 log.warning('user %s is disabled', username)
185 log.warning('user %s tried auth but is disabled' % username)
172
186
173 else:
187 else:
174 log.debug('Regular authentication failed')
188 log.debug('Regular authentication failed')
@@ -178,7 +192,7 b' def authenticate(username, password):'
178 log.debug('this user already exists as non ldap')
192 log.debug('this user already exists as non ldap')
179 return False
193 return False
180
194
181 ldap_settings = RhodeCodeSettings.get_ldap_settings()
195 ldap_settings = RhodeCodeSetting.get_ldap_settings()
182 #======================================================================
196 #======================================================================
183 # FALLBACK TO LDAP AUTH IF ENABLE
197 # FALLBACK TO LDAP AUTH IF ENABLE
184 #======================================================================
198 #======================================================================
@@ -202,7 +216,7 b' def authenticate(username, password):'
202 aldap = AuthLdap(**kwargs)
216 aldap = AuthLdap(**kwargs)
203 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
217 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
204 password)
218 password)
205 log.debug('Got ldap DN response %s', user_dn)
219 log.debug('Got ldap DN response %s' % user_dn)
206
220
207 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
221 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
208 .get(k), [''])[0]
222 .get(k), [''])[0]
@@ -222,6 +236,7 b' def authenticate(username, password):'
222 user_attrs):
236 user_attrs):
223 log.info('created new ldap user %s' % username)
237 log.info('created new ldap user %s' % username)
224
238
239 Session.commit()
225 return True
240 return True
226 except (LdapUsernameError, LdapPasswordError,):
241 except (LdapUsernameError, LdapPasswordError,):
227 pass
242 pass
@@ -231,6 +246,64 b' def authenticate(username, password):'
231 return False
246 return False
232
247
233
248
249 def login_container_auth(username):
250 user = User.get_by_username(username)
251 if user is None:
252 user_attrs = {
253 'name': username,
254 'lastname': None,
255 'email': None,
256 }
257 user = UserModel().create_for_container_auth(username, user_attrs)
258 if not user:
259 return None
260 log.info('User %s was created by container authentication' % username)
261
262 if not user.active:
263 return None
264
265 user.update_lastlogin()
266 Session.commit()
267
268 log.debug('User %s is now logged in by container authentication',
269 user.username)
270 return user
271
272
273 def get_container_username(environ, config):
274 username = None
275
276 if str2bool(config.get('container_auth_enabled', False)):
277 from paste.httpheaders import REMOTE_USER
278 username = REMOTE_USER(environ)
279
280 if not username and str2bool(config.get('proxypass_auth_enabled', False)):
281 username = environ.get('HTTP_X_FORWARDED_USER')
282
283 if username:
284 # Removing realm and domain from username
285 username = username.partition('@')[0]
286 username = username.rpartition('\\')[2]
287 log.debug('Received username %s from container' % username)
288
289 return username
290
291
292 class CookieStoreWrapper(object):
293
294 def __init__(self, cookie_store):
295 self.cookie_store = cookie_store
296
297 def __repr__(self):
298 return 'CookieStore<%s>' % (self.cookie_store)
299
300 def get(self, key, other=None):
301 if isinstance(self.cookie_store, dict):
302 return self.cookie_store.get(key, other)
303 elif isinstance(self.cookie_store, AuthUser):
304 return self.cookie_store.__dict__.get(key, other)
305
306
234 class AuthUser(object):
307 class AuthUser(object):
235 """
308 """
236 A simple object that handles all attributes of user in RhodeCode
309 A simple object that handles all attributes of user in RhodeCode
@@ -241,12 +314,12 b' class AuthUser(object):'
241 in
314 in
242 """
315 """
243
316
244 def __init__(self, user_id=None, api_key=None):
317 def __init__(self, user_id=None, api_key=None, username=None):
245
318
246 self.user_id = user_id
319 self.user_id = user_id
247 self.api_key = None
320 self.api_key = None
321 self.username = username
248
322
249 self.username = 'None'
250 self.name = ''
323 self.name = ''
251 self.lastname = ''
324 self.lastname = ''
252 self.email = ''
325 self.email = ''
@@ -255,51 +328,85 b' class AuthUser(object):'
255 self.permissions = {}
328 self.permissions = {}
256 self._api_key = api_key
329 self._api_key = api_key
257 self.propagate_data()
330 self.propagate_data()
331 self._instance = None
258
332
259 def propagate_data(self):
333 def propagate_data(self):
260 user_model = UserModel()
334 user_model = UserModel()
261 self.anonymous_user = User.get_by_username('default')
335 self.anonymous_user = User.get_by_username('default', cache=True)
336 is_user_loaded = False
337
338 # try go get user by api key
262 if self._api_key and self._api_key != self.anonymous_user.api_key:
339 if self._api_key and self._api_key != self.anonymous_user.api_key:
263 #try go get user by api key
340 log.debug('Auth User lookup by API KEY %s' % self._api_key)
264 log.debug('Auth User lookup by API KEY %s', self._api_key)
341 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
265 user_model.fill_data(self, api_key=self._api_key)
342 # lookup by userid
266 else:
343 elif (self.user_id is not None and
267 log.debug('Auth User lookup by USER ID %s', self.user_id)
344 self.user_id != self.anonymous_user.user_id):
268 if self.user_id is not None \
345 log.debug('Auth User lookup by USER ID %s' % self.user_id)
269 and self.user_id != self.anonymous_user.user_id:
346 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
270 user_model.fill_data(self, user_id=self.user_id)
347 # lookup by username
348 elif self.username and \
349 str2bool(config.get('container_auth_enabled', False)):
350
351 log.debug('Auth User lookup by USER NAME %s' % self.username)
352 dbuser = login_container_auth(self.username)
353 if dbuser is not None:
354 for k, v in dbuser.get_dict().items():
355 setattr(self, k, v)
356 self.set_authenticated()
357 is_user_loaded = True
358
359 if not is_user_loaded:
360 # if we cannot authenticate user try anonymous
361 if self.anonymous_user.active is True:
362 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
363 # then we set this user is logged in
364 self.is_authenticated = True
271 else:
365 else:
272 if self.anonymous_user.active is True:
366 self.user_id = None
273 user_model.fill_data(self,
367 self.username = None
274 user_id=self.anonymous_user.user_id)
368 self.is_authenticated = False
275 #then we set this user is logged in
276 self.is_authenticated = True
277 else:
278 self.is_authenticated = False
279
369
280 log.debug('Auth User is now %s', self)
370 if not self.username:
371 self.username = 'None'
372
373 log.debug('Auth User is now %s' % self)
281 user_model.fill_perms(self)
374 user_model.fill_perms(self)
282
375
283 @property
376 @property
284 def is_admin(self):
377 def is_admin(self):
285 return self.admin
378 return self.admin
286
379
287 @property
288 def full_contact(self):
289 return '%s %s <%s>' % (self.name, self.lastname, self.email)
290
291 def __repr__(self):
380 def __repr__(self):
292 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
381 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
293 self.is_authenticated)
382 self.is_authenticated)
294
383
295 def set_authenticated(self, authenticated=True):
384 def set_authenticated(self, authenticated=True):
296
297 if self.user_id != self.anonymous_user.user_id:
385 if self.user_id != self.anonymous_user.user_id:
298 self.is_authenticated = authenticated
386 self.is_authenticated = authenticated
299
387
388 def get_cookie_store(self):
389 return {'username': self.username,
390 'user_id': self.user_id,
391 'is_authenticated': self.is_authenticated}
392
393 @classmethod
394 def from_cookie_store(cls, cookie_store):
395 """
396 Creates AuthUser from a cookie store
397
398 :param cls:
399 :param cookie_store:
400 """
401 user_id = cookie_store.get('user_id')
402 username = cookie_store.get('username')
403 api_key = cookie_store.get('api_key')
404 return AuthUser(user_id, api_key, username)
405
300
406
301 def set_available_permissions(config):
407 def set_available_permissions(config):
302 """This function will propagate pylons globals with all available defined
408 """
409 This function will propagate pylons globals with all available defined
303 permission given in db. We don't want to check each time from db for new
410 permission given in db. We don't want to check each time from db for new
304 permissions since adding a new permission also requires application restart
411 permissions since adding a new permission also requires application restart
305 ie. to decorate new views with the newly created permission
412 ie. to decorate new views with the newly created permission
@@ -309,9 +416,9 b' def set_available_permissions(config):'
309 """
416 """
310 log.info('getting information about all available permissions')
417 log.info('getting information about all available permissions')
311 try:
418 try:
312 sa = meta.Session()
419 sa = meta.Session
313 all_perms = sa.query(Permission).all()
420 all_perms = sa.query(Permission).all()
314 except:
421 except Exception:
315 pass
422 pass
316 finally:
423 finally:
317 meta.Session.remove()
424 meta.Session.remove()
@@ -343,26 +450,31 b' class LoginRequired(object):'
343
450
344 api_access_ok = False
451 api_access_ok = False
345 if self.api_access:
452 if self.api_access:
346 log.debug('Checking API KEY access for %s', cls)
453 log.debug('Checking API KEY access for %s' % cls)
347 if user.api_key == request.GET.get('api_key'):
454 if user.api_key == request.GET.get('api_key'):
348 api_access_ok = True
455 api_access_ok = True
349 else:
456 else:
350 log.debug("API KEY token not valid")
457 log.debug("API KEY token not valid")
351
458 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
352 log.debug('Checking if %s is authenticated @ %s', user.username, cls)
459 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
353 if user.is_authenticated or api_access_ok:
460 if user.is_authenticated or api_access_ok:
354 log.debug('user %s is authenticated', user.username)
461 log.info('user %s is authenticated and granted access to %s' % (
462 user.username, loc)
463 )
355 return func(*fargs, **fkwargs)
464 return func(*fargs, **fkwargs)
356 else:
465 else:
357 log.warn('user %s NOT authenticated', user.username)
466 log.warn('user %s NOT authenticated on func: %s' % (
467 user, loc)
468 )
358 p = url.current()
469 p = url.current()
359
470
360 log.debug('redirecting to login page with %s', p)
471 log.debug('redirecting to login page with %s' % p)
361 return redirect(url('login_home', came_from=p))
472 return redirect(url('login_home', came_from=p))
362
473
363
474
364 class NotAnonymous(object):
475 class NotAnonymous(object):
365 """Must be logged in to execute this function else
476 """
477 Must be logged in to execute this function else
366 redirect to login page"""
478 redirect to login page"""
367
479
368 def __call__(self, func):
480 def __call__(self, func):
@@ -372,7 +484,7 b' class NotAnonymous(object):'
372 cls = fargs[0]
484 cls = fargs[0]
373 self.user = cls.rhodecode_user
485 self.user = cls.rhodecode_user
374
486
375 log.debug('Checking if user is not anonymous @%s', cls)
487 log.debug('Checking if user is not anonymous @%s' % cls)
376
488
377 anonymous = self.user.username == 'default'
489 anonymous = self.user.username == 'default'
378
490
@@ -411,13 +523,11 b' class PermsDecorator(object):'
411 self.user)
523 self.user)
412
524
413 if self.check_permissions():
525 if self.check_permissions():
414 log.debug('Permission granted for %s %s', cls, self.user)
526 log.debug('Permission granted for %s %s' % (cls, self.user))
415 return func(*fargs, **fkwargs)
527 return func(*fargs, **fkwargs)
416
528
417 else:
529 else:
418 log.warning('Permission denied for %s %s', cls, self.user)
530 log.debug('Permission denied for %s %s' % (cls, self.user))
419
420
421 anonymous = self.user.username == 'default'
531 anonymous = self.user.username == 'default'
422
532
423 if anonymous:
533 if anonymous:
@@ -430,7 +540,7 b' class PermsDecorator(object):'
430 return redirect(url('login_home', came_from=p))
540 return redirect(url('login_home', came_from=p))
431
541
432 else:
542 else:
433 #redirect with forbidden ret code
543 # redirect with forbidden ret code
434 return abort(403)
544 return abort(403)
435
545
436 def check_permissions(self):
546 def check_permissions(self):
@@ -439,7 +549,8 b' class PermsDecorator(object):'
439
549
440
550
441 class HasPermissionAllDecorator(PermsDecorator):
551 class HasPermissionAllDecorator(PermsDecorator):
442 """Checks for access permission for all given predicates. All of them
552 """
553 Checks for access permission for all given predicates. All of them
443 have to be meet in order to fulfill the request
554 have to be meet in order to fulfill the request
444 """
555 """
445
556
@@ -450,7 +561,8 b' class HasPermissionAllDecorator(PermsDec'
450
561
451
562
452 class HasPermissionAnyDecorator(PermsDecorator):
563 class HasPermissionAnyDecorator(PermsDecorator):
453 """Checks for access permission for any of given predicates. In order to
564 """
565 Checks for access permission for any of given predicates. In order to
454 fulfill the request any of predicates must be meet
566 fulfill the request any of predicates must be meet
455 """
567 """
456
568
@@ -461,7 +573,8 b' class HasPermissionAnyDecorator(PermsDec'
461
573
462
574
463 class HasRepoPermissionAllDecorator(PermsDecorator):
575 class HasRepoPermissionAllDecorator(PermsDecorator):
464 """Checks for access permission for all given predicates for specific
576 """
577 Checks for access permission for all given predicates for specific
465 repository. All of them have to be meet in order to fulfill the request
578 repository. All of them have to be meet in order to fulfill the request
466 """
579 """
467
580
@@ -477,7 +590,8 b' class HasRepoPermissionAllDecorator(Perm'
477
590
478
591
479 class HasRepoPermissionAnyDecorator(PermsDecorator):
592 class HasRepoPermissionAnyDecorator(PermsDecorator):
480 """Checks for access permission for any of given predicates for specific
593 """
594 Checks for access permission for any of given predicates for specific
481 repository. In order to fulfill the request any of predicates must be meet
595 repository. In order to fulfill the request any of predicates must be meet
482 """
596 """
483
597
@@ -493,6 +607,41 b' class HasRepoPermissionAnyDecorator(Perm'
493 return False
607 return False
494
608
495
609
610 class HasReposGroupPermissionAllDecorator(PermsDecorator):
611 """
612 Checks for access permission for all given predicates for specific
613 repository. All of them have to be meet in order to fulfill the request
614 """
615
616 def check_permissions(self):
617 group_name = get_repos_group_slug(request)
618 try:
619 user_perms = set([self.user_perms['repositories_groups'][group_name]])
620 except KeyError:
621 return False
622 if self.required_perms.issubset(user_perms):
623 return True
624 return False
625
626
627 class HasReposGroupPermissionAnyDecorator(PermsDecorator):
628 """
629 Checks for access permission for any of given predicates for specific
630 repository. In order to fulfill the request any of predicates must be meet
631 """
632
633 def check_permissions(self):
634 group_name = get_repos_group_slug(request)
635
636 try:
637 user_perms = set([self.user_perms['repositories_groups'][group_name]])
638 except KeyError:
639 return False
640 if self.required_perms.intersection(user_perms):
641 return True
642 return False
643
644
496 #==============================================================================
645 #==============================================================================
497 # CHECK FUNCTIONS
646 # CHECK FUNCTIONS
498 #==============================================================================
647 #==============================================================================
@@ -511,7 +660,7 b' class PermsFunction(object):'
511 self.repo_name = None
660 self.repo_name = None
512
661
513 def __call__(self, check_Location=''):
662 def __call__(self, check_Location=''):
514 user = session.get('rhodecode_user', False)
663 user = request.user
515 if not user:
664 if not user:
516 return False
665 return False
517 self.user_perms = user.permissions
666 self.user_perms = user.permissions
@@ -525,7 +674,7 b' class PermsFunction(object):'
525 return True
674 return True
526
675
527 else:
676 else:
528 log.warning('Permission denied for %s @ %s', self.granted_for,
677 log.debug('Permission denied for %s @ %s', self.granted_for,
529 check_Location or 'unspecified location')
678 check_Location or 'unspecified location')
530 return False
679 return False
531
680
@@ -559,8 +708,9 b' class HasRepoPermissionAll(PermsFunction'
559 self.repo_name = get_repo_slug(request)
708 self.repo_name = get_repo_slug(request)
560
709
561 try:
710 try:
562 self.user_perms = set([self.user_perms['reposit'
711 self.user_perms = set(
563 'ories'][self.repo_name]])
712 [self.user_perms['repositories'][self.repo_name]]
713 )
564 except KeyError:
714 except KeyError:
565 return False
715 return False
566 self.granted_for = self.repo_name
716 self.granted_for = self.repo_name
@@ -580,8 +730,9 b' class HasRepoPermissionAny(PermsFunction'
580 self.repo_name = get_repo_slug(request)
730 self.repo_name = get_repo_slug(request)
581
731
582 try:
732 try:
583 self.user_perms = set([self.user_perms['reposi'
733 self.user_perms = set(
584 'tories'][self.repo_name]])
734 [self.user_perms['repositories'][self.repo_name]]
735 )
585 except KeyError:
736 except KeyError:
586 return False
737 return False
587 self.granted_for = self.repo_name
738 self.granted_for = self.repo_name
@@ -590,6 +741,42 b' class HasRepoPermissionAny(PermsFunction'
590 return False
741 return False
591
742
592
743
744 class HasReposGroupPermissionAny(PermsFunction):
745 def __call__(self, group_name=None, check_Location=''):
746 self.group_name = group_name
747 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
748
749 def check_permissions(self):
750 try:
751 self.user_perms = set(
752 [self.user_perms['repositories_groups'][self.group_name]]
753 )
754 except KeyError:
755 return False
756 self.granted_for = self.repo_name
757 if self.required_perms.intersection(self.user_perms):
758 return True
759 return False
760
761
762 class HasReposGroupPermissionAll(PermsFunction):
763 def __call__(self, group_name=None, check_Location=''):
764 self.group_name = group_name
765 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
766
767 def check_permissions(self):
768 try:
769 self.user_perms = set(
770 [self.user_perms['repositories_groups'][self.group_name]]
771 )
772 except KeyError:
773 return False
774 self.granted_for = self.repo_name
775 if self.required_perms.issubset(self.user_perms):
776 return True
777 return False
778
779
593 #==============================================================================
780 #==============================================================================
594 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
781 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
595 #==============================================================================
782 #==============================================================================
@@ -7,7 +7,7 b''
7
7
8 :created_on: Created on Nov 17, 2010
8 :created_on: Created on Nov 17, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
@@ -43,8 +43,7 b' class AuthLdap(object):'
43 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
43 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
44 tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3,
44 tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3,
45 ldap_filter='(&(objectClass=user)(!(objectClass=computer)))',
45 ldap_filter='(&(objectClass=user)(!(objectClass=computer)))',
46 search_scope='SUBTREE',
46 search_scope='SUBTREE', attr_login='uid'):
47 attr_login='uid'):
48 self.ldap_version = ldap_version
47 self.ldap_version = ldap_version
49 ldap_server_type = 'ldap'
48 ldap_server_type = 'ldap'
50
49
@@ -53,14 +52,14 b' class AuthLdap(object):'
53 if self.TLS_KIND == 'LDAPS':
52 if self.TLS_KIND == 'LDAPS':
54 port = port or 689
53 port = port or 689
55 ldap_server_type = ldap_server_type + 's'
54 ldap_server_type = ldap_server_type + 's'
56
55
57 OPT_X_TLS_DEMAND = 2
56 OPT_X_TLS_DEMAND = 2
58 self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert,
57 self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert,
59 OPT_X_TLS_DEMAND)
58 OPT_X_TLS_DEMAND)
60 self.LDAP_SERVER_ADDRESS = server
59 self.LDAP_SERVER_ADDRESS = server
61 self.LDAP_SERVER_PORT = port
60 self.LDAP_SERVER_PORT = port
62
61
63 #USE FOR READ ONLY BIND TO LDAP SERVER
62 # USE FOR READ ONLY BIND TO LDAP SERVER
64 self.LDAP_BIND_DN = bind_dn
63 self.LDAP_BIND_DN = bind_dn
65 self.LDAP_BIND_PASS = bind_pass
64 self.LDAP_BIND_PASS = bind_pass
66
65
@@ -74,7 +73,8 b' class AuthLdap(object):'
74 self.attr_login = attr_login
73 self.attr_login = attr_login
75
74
76 def authenticate_ldap(self, username, password):
75 def authenticate_ldap(self, username, password):
77 """Authenticate a user via LDAP and return his/her LDAP properties.
76 """
77 Authenticate a user via LDAP and return his/her LDAP properties.
78
78
79 Raises AuthenticationError if the credentials are rejected, or
79 Raises AuthenticationError if the credentials are rejected, or
80 EnvironmentError if the LDAP server can't be reached.
80 EnvironmentError if the LDAP server can't be reached.
@@ -87,11 +87,15 b' class AuthLdap(object):'
87
87
88 uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS)
88 uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS)
89
89
90 if not password:
91 log.debug("Attempt to authenticate LDAP user "
92 "with blank password rejected.")
93 raise LdapPasswordError()
90 if "," in username:
94 if "," in username:
91 raise LdapUsernameError("invalid character in username: ,")
95 raise LdapUsernameError("invalid character in username: ,")
92 try:
96 try:
93 if hasattr(ldap,'OPT_X_TLS_CACERTDIR'):
97 if hasattr(ldap, 'OPT_X_TLS_CACERTDIR'):
94 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR,
98 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR,
95 '/etc/openldap/cacerts')
99 '/etc/openldap/cacerts')
96 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
100 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
97 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
101 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
@@ -112,12 +116,12 b' class AuthLdap(object):'
112 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
116 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
113 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
117 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
114
118
115 filt = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login,
119 filter_ = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login,
116 username)
120 username)
117 log.debug("Authenticating %r filt %s at %s", self.BASE_DN,
121 log.debug("Authenticating %r filter %s at %s", self.BASE_DN,
118 filt, self.LDAP_SERVER)
122 filter_, self.LDAP_SERVER)
119 lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE,
123 lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE,
120 filt)
124 filter_)
121
125
122 if not lobjects:
126 if not lobjects:
123 raise ldap.NO_SUCH_OBJECT()
127 raise ldap.NO_SUCH_OBJECT()
@@ -127,24 +131,28 b' class AuthLdap(object):'
127 continue
131 continue
128
132
129 try:
133 try:
134 log.debug('Trying simple bind with %s' % dn)
130 server.simple_bind_s(dn, password)
135 server.simple_bind_s(dn, password)
131 attrs = server.search_ext_s(dn, ldap.SCOPE_BASE,
136 attrs = server.search_ext_s(dn, ldap.SCOPE_BASE,
132 '(objectClass=*)')[0][1]
137 '(objectClass=*)')[0][1]
133 break
138 break
134
139
135 except ldap.INVALID_CREDENTIALS, e:
140 except ldap.INVALID_CREDENTIALS:
136 log.debug("LDAP rejected password for user '%s' (%s): %s",
141 log.debug(
137 uid, username, dn)
142 "LDAP rejected password for user '%s' (%s): %s" % (
143 uid, username, dn
144 )
145 )
138
146
139 else:
147 else:
140 log.debug("No matching LDAP objects for authentication "
148 log.debug("No matching LDAP objects for authentication "
141 "of '%s' (%s)", uid, username)
149 "of '%s' (%s)", uid, username)
142 raise LdapPasswordError()
150 raise LdapPasswordError()
143
151
144 except ldap.NO_SUCH_OBJECT, e:
152 except ldap.NO_SUCH_OBJECT:
145 log.debug("LDAP says no such user '%s' (%s)", uid, username)
153 log.debug("LDAP says no such user '%s' (%s)" % (uid, username))
146 raise LdapUsernameError()
154 raise LdapUsernameError()
147 except ldap.SERVER_DOWN, e:
155 except ldap.SERVER_DOWN:
148 raise LdapConnectionError("LDAP can't access "
156 raise LdapConnectionError("LDAP can't access "
149 "authentication server")
157 "authentication server")
150
158
@@ -3,11 +3,12 b''
3 rhodecode.lib.backup_manager
3 rhodecode.lib.backup_manager
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Mercurial repositories backup manager, it allows to backups all
6 Mercurial repositories backup manager, it allows to backups all
7 repositories and send it to backup server using RSA key via ssh.
7 repositories and send it to backup server using RSA key via ssh.
8
8
9 :created_on: Feb 28, 2010
9 :created_on: Feb 28, 2010
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
12 """
13 """
13 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
@@ -3,35 +3,125 b''
3 Provides the BaseController class for subclassing.
3 Provides the BaseController class for subclassing.
4 """
4 """
5 import logging
5 import logging
6 import time
7 import traceback
8
9 from paste.auth.basic import AuthBasicAuthenticator
6
10
7 from pylons import config, tmpl_context as c, request, session, url
11 from pylons import config, tmpl_context as c, request, session, url
8 from pylons.controllers import WSGIController
12 from pylons.controllers import WSGIController
9 from pylons.controllers.util import redirect
13 from pylons.controllers.util import redirect
10 from pylons.templating import render_mako as render
14 from pylons.templating import render_mako as render
11
15
12 from rhodecode import __version__
16 from rhodecode import __version__, BACKENDS
13 from rhodecode.lib import str2bool
17
14 from rhodecode.lib.auth import AuthUser
18 from rhodecode.lib import str2bool, safe_unicode
15 from rhodecode.lib.utils import get_repo_slug
19 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
20 HasPermissionAnyMiddleware, CookieStoreWrapper
21 from rhodecode.lib.utils import get_repo_slug, invalidate_cache
16 from rhodecode.model import meta
22 from rhodecode.model import meta
23
24 from rhodecode.model.db import Repository
25 from rhodecode.model.notification import NotificationModel
17 from rhodecode.model.scm import ScmModel
26 from rhodecode.model.scm import ScmModel
18 from rhodecode import BACKENDS
19 from rhodecode.model.db import Repository
20
27
21 log = logging.getLogger(__name__)
28 log = logging.getLogger(__name__)
22
29
30
31 class BaseVCSController(object):
32
33 def __init__(self, application, config):
34 self.application = application
35 self.config = config
36 # base path of repo locations
37 self.basepath = self.config['base_path']
38 #authenticate this mercurial request using authfunc
39 self.authenticate = AuthBasicAuthenticator('', authfunc)
40 self.ipaddr = '0.0.0.0'
41
42 def _handle_request(self, environ, start_response):
43 raise NotImplementedError()
44
45 def _get_by_id(self, repo_name):
46 """
47 Get's a special pattern _<ID> from clone url and tries to replace it
48 with a repository_name for support of _<ID> non changable urls
49
50 :param repo_name:
51 """
52 try:
53 data = repo_name.split('/')
54 if len(data) >= 2:
55 by_id = data[1].split('_')
56 if len(by_id) == 2 and by_id[1].isdigit():
57 _repo_name = Repository.get(by_id[1]).repo_name
58 data[1] = _repo_name
59 except:
60 log.debug('Failed to extract repo_name from id %s' % (
61 traceback.format_exc()
62 )
63 )
64
65 return '/'.join(data)
66
67 def _invalidate_cache(self, repo_name):
68 """
69 Set's cache for this repository for invalidation on next access
70
71 :param repo_name: full repo name, also a cache key
72 """
73 invalidate_cache('get_repo_cached_%s' % repo_name)
74
75 def _check_permission(self, action, user, repo_name):
76 """
77 Checks permissions using action (push/pull) user and repository
78 name
79
80 :param action: push or pull action
81 :param user: user instance
82 :param repo_name: repository name
83 """
84 if action == 'push':
85 if not HasPermissionAnyMiddleware('repository.write',
86 'repository.admin')(user,
87 repo_name):
88 return False
89
90 else:
91 #any other action need at least read permission
92 if not HasPermissionAnyMiddleware('repository.read',
93 'repository.write',
94 'repository.admin')(user,
95 repo_name):
96 return False
97
98 return True
99
100 def __call__(self, environ, start_response):
101 start = time.time()
102 try:
103 return self._handle_request(environ, start_response)
104 finally:
105 log = logging.getLogger('rhodecode.' + self.__class__.__name__)
106 log.debug('Request time: %.3fs' % (time.time() - start))
107 meta.Session.remove()
108
109
23 class BaseController(WSGIController):
110 class BaseController(WSGIController):
24
111
25 def __before__(self):
112 def __before__(self):
26 c.rhodecode_version = __version__
113 c.rhodecode_version = __version__
114 c.rhodecode_instanceid = config.get('instance_id')
27 c.rhodecode_name = config.get('rhodecode_title')
115 c.rhodecode_name = config.get('rhodecode_title')
28 c.use_gravatar = str2bool(config.get('use_gravatar'))
116 c.use_gravatar = str2bool(config.get('use_gravatar'))
29 c.ga_code = config.get('rhodecode_ga_code')
117 c.ga_code = config.get('rhodecode_ga_code')
30 c.repo_name = get_repo_slug(request)
118 c.repo_name = get_repo_slug(request)
31 c.backends = BACKENDS.keys()
119 c.backends = BACKENDS.keys()
120 c.unread_notifications = NotificationModel()\
121 .get_unread_cnt_for_user(c.rhodecode_user.user_id)
32 self.cut_off_limit = int(config.get('cut_off_limit'))
122 self.cut_off_limit = int(config.get('cut_off_limit'))
33
123
34 self.sa = meta.Session()
124 self.sa = meta.Session
35 self.scm_model = ScmModel(self.sa)
125 self.scm_model = ScmModel(self.sa)
36
126
37 def __call__(self, environ, start_response):
127 def __call__(self, environ, start_response):
@@ -39,18 +129,30 b' class BaseController(WSGIController):'
39 # WSGIController.__call__ dispatches to the Controller method
129 # WSGIController.__call__ dispatches to the Controller method
40 # the request is routed to. This routing information is
130 # the request is routed to. This routing information is
41 # available in environ['pylons.routes_dict']
131 # available in environ['pylons.routes_dict']
132 start = time.time()
42 try:
133 try:
43 # putting this here makes sure that we update permissions each time
134 # make sure that we update permissions each time we call controller
44 api_key = request.GET.get('api_key')
135 api_key = request.GET.get('api_key')
45 user_id = getattr(session.get('rhodecode_user'), 'user_id', None)
136 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
46 self.rhodecode_user = c.rhodecode_user = AuthUser(user_id, api_key)
137 user_id = cookie_store.get('user_id', None)
47 self.rhodecode_user.set_authenticated(
138 username = get_container_username(environ, config)
48 getattr(session.get('rhodecode_user'),
139
49 'is_authenticated', False))
140 auth_user = AuthUser(user_id, api_key, username)
50 session['rhodecode_user'] = self.rhodecode_user
141 request.user = auth_user
51 session.save()
142 self.rhodecode_user = c.rhodecode_user = auth_user
143 if not self.rhodecode_user.is_authenticated and \
144 self.rhodecode_user.user_id is not None:
145 self.rhodecode_user.set_authenticated(
146 cookie_store.get('is_authenticated')
147 )
148 log.info('User: %s accessed %s' % (
149 auth_user, safe_unicode(environ.get('PATH_INFO')))
150 )
52 return WSGIController.__call__(self, environ, start_response)
151 return WSGIController.__call__(self, environ, start_response)
53 finally:
152 finally:
153 log.info('Request to %s time: %.3fs' % (
154 safe_unicode(environ.get('PATH_INFO')), time.time() - start)
155 )
54 meta.Session.remove()
156 meta.Session.remove()
55
157
56
158
@@ -80,4 +182,3 b' class BaseRepoController(BaseController)'
80
182
81 c.repository_followers = self.scm_model.get_followers(c.repo_name)
183 c.repository_followers = self.scm_model.get_followers(c.repo_name)
82 c.repository_forks = self.scm_model.get_forks(c.repo_name)
184 c.repository_forks = self.scm_model.get_forks(c.repo_name)
83
@@ -137,8 +137,13 b' def _get_cache_parameters(query):'
137
137
138 if cache_key is None:
138 if cache_key is None:
139 # cache key - the value arguments from this query's parameters.
139 # cache key - the value arguments from this query's parameters.
140 args = _params_from_query(query)
140 args = [str(x) for x in _params_from_query(query)]
141 cache_key = " ".join([str(x) for x in args])
141 args.extend(filter(lambda k:k not in ['None', None, u'None'],
142 [str(query._limit), str(query._offset)]))
143 cache_key = " ".join(args)
144
145 if cache_key is None:
146 raise Exception('Cache key cannot be None')
142
147
143 # get cache
148 # get cache
144 #cache = query.cache_manager.get_cache_region(namespace, region)
149 #cache = query.cache_manager.get_cache_region(namespace, region)
@@ -275,15 +280,20 b' def _params_from_query(query):'
275 """
280 """
276 v = []
281 v = []
277 def visit_bindparam(bind):
282 def visit_bindparam(bind):
278 value = query._params.get(bind.key, bind.value)
279
283
280 # lazyloader may dig a callable in here, intended
284 if bind.key in query._params:
281 # to late-evaluate params after autoflush is called.
285 value = query._params[bind.key]
282 # convert to a scalar value.
286 elif bind.callable:
283 if callable(value):
287 # lazyloader may dig a callable in here, intended
284 value = value()
288 # to late-evaluate params after autoflush is called.
289 # convert to a scalar value.
290 value = bind.callable()
291 else:
292 value = bind.value
285
293
286 v.append(value)
294 v.append(value)
287 if query._criterion is not None:
295 if query._criterion is not None:
288 visitors.traverse(query._criterion, {}, {'bindparam':visit_bindparam})
296 visitors.traverse(query._criterion, {}, {'bindparam':visit_bindparam})
297 for f in query._from_obj:
298 visitors.traverse(f, {}, {'bindparam':visit_bindparam})
289 return v
299 return v
@@ -34,8 +34,8 b' from pylons import config'
34 from hashlib import md5
34 from hashlib import md5
35 from decorator import decorator
35 from decorator import decorator
36
36
37 from vcs.utils.lazy import LazyProperty
37 from rhodecode.lib.vcs.utils.lazy import LazyProperty
38
38 from rhodecode import CELERY_ON
39 from rhodecode.lib import str2bool, safe_str
39 from rhodecode.lib import str2bool, safe_str
40 from rhodecode.lib.pidlock import DaemonLock, LockHeld
40 from rhodecode.lib.pidlock import DaemonLock, LockHeld
41 from rhodecode.model import init_model
41 from rhodecode.model import init_model
@@ -48,11 +48,6 b' from celery.messaging import establish_c'
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51 try:
52 CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
53 except KeyError:
54 CELERY_ON = False
55
56
51
57 class ResultWrapper(object):
52 class ResultWrapper(object):
58 def __init__(self, task):
53 def __init__(self, task):
@@ -67,7 +62,7 b' def run_task(task, *args, **kwargs):'
67 if CELERY_ON:
62 if CELERY_ON:
68 try:
63 try:
69 t = task.apply_async(args=args, kwargs=kwargs)
64 t = task.apply_async(args=args, kwargs=kwargs)
70 log.info('running task %s:%s', t.task_id, task)
65 log.info('running task %s:%s' % (t.task_id, task))
71 return t
66 return t
72
67
73 except socket.error, e:
68 except socket.error, e:
@@ -80,7 +75,7 b' def run_task(task, *args, **kwargs):'
80 except Exception, e:
75 except Exception, e:
81 log.error(traceback.format_exc())
76 log.error(traceback.format_exc())
82
77
83 log.debug('executing task %s in sync mode', task)
78 log.debug('executing task %s in sync mode' % task)
84 return ResultWrapper(task(*args, **kwargs))
79 return ResultWrapper(task(*args, **kwargs))
85
80
86
81
@@ -100,7 +95,7 b' def locked_task(func):'
100 lockkey = __get_lockkey(func, *fargs, **fkwargs)
95 lockkey = __get_lockkey(func, *fargs, **fkwargs)
101 lockkey_path = config['here']
96 lockkey_path = config['here']
102
97
103 log.info('running task with lockkey %s', lockkey)
98 log.info('running task with lockkey %s' % lockkey)
104 try:
99 try:
105 l = DaemonLock(file_=jn(lockkey_path, lockkey))
100 l = DaemonLock(file_=jn(lockkey_path, lockkey))
106 ret = func(*fargs, **fkwargs)
101 ret = func(*fargs, **fkwargs)
@@ -28,7 +28,7 b' from celery.decorators import task'
28 import os
28 import os
29 import traceback
29 import traceback
30 import logging
30 import logging
31 from os.path import dirname as dn, join as jn
31 from os.path import join as jn
32
32
33 from time import mktime
33 from time import mktime
34 from operator import itemgetter
34 from operator import itemgetter
@@ -37,69 +37,76 b' from string import lower'
37 from pylons import config, url
37 from pylons import config, url
38 from pylons.i18n.translation import _
38 from pylons.i18n.translation import _
39
39
40 from rhodecode.lib.vcs import get_backend
41
42 from rhodecode import CELERY_ON
40 from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, safe_str
43 from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, safe_str
41 from rhodecode.lib.celerylib import run_task, locked_task, str2bool, \
44 from rhodecode.lib.celerylib import run_task, locked_task, dbsession, \
42 __get_lockkey, LockHeld, DaemonLock, get_session, dbsession
45 str2bool, __get_lockkey, LockHeld, DaemonLock, get_session
43 from rhodecode.lib.helpers import person
46 from rhodecode.lib.helpers import person
44 from rhodecode.lib.smtp_mailer import SmtpMailer
47 from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
45 from rhodecode.lib.utils import add_cache
48 from rhodecode.lib.utils import add_cache, action_logger
46 from rhodecode.lib.compat import json, OrderedDict
49 from rhodecode.lib.compat import json, OrderedDict
47
50
48 from rhodecode.model.db import RhodeCodeUi, Statistics, Repository, User
51 from rhodecode.model.db import Statistics, Repository, User
49
52
50 from vcs.backends import get_repo
51 from vcs import get_backend
52
53
53 add_cache(config)
54 add_cache(config)
54
55
55 __all__ = ['whoosh_index', 'get_commits_stats',
56 __all__ = ['whoosh_index', 'get_commits_stats',
56 'reset_user_password', 'send_email']
57 'reset_user_password', 'send_email']
57
58
58 CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
59
60
59
61 def get_repos_path():
60 def get_logger(cls):
62 sa = get_session()
61 if CELERY_ON:
63 q = sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
62 try:
64 return q.ui_value
63 log = cls.get_logger()
64 except:
65 log = logging.getLogger(__name__)
66 else:
67 log = logging.getLogger(__name__)
68
69 return log
65
70
66
71
67 @task(ignore_result=True)
72 @task(ignore_result=True)
68 @locked_task
73 @locked_task
69 @dbsession
74 @dbsession
70 def whoosh_index(repo_location, full_index):
75 def whoosh_index(repo_location, full_index):
71 #log = whoosh_index.get_logger()
72 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
76 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
77 log = whoosh_index.get_logger(whoosh_index)
78 DBS = get_session()
79
73 index_location = config['index_dir']
80 index_location = config['index_dir']
74 WhooshIndexingDaemon(index_location=index_location,
81 WhooshIndexingDaemon(index_location=index_location,
75 repo_location=repo_location, sa=get_session())\
82 repo_location=repo_location, sa=DBS)\
76 .run(full_index=full_index)
83 .run(full_index=full_index)
77
84
78
85
79 @task(ignore_result=True)
86 @task(ignore_result=True)
80 @dbsession
87 @dbsession
81 def get_commits_stats(repo_name, ts_min_y, ts_max_y):
88 def get_commits_stats(repo_name, ts_min_y, ts_max_y):
82 try:
89 log = get_logger(get_commits_stats)
83 log = get_commits_stats.get_logger()
90 DBS = get_session()
84 except:
85 log = logging.getLogger(__name__)
86
87 lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
91 lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
88 ts_max_y)
92 ts_max_y)
89 lockkey_path = config['here']
93 lockkey_path = config['here']
90
94
91 log.info('running task with lockkey %s', lockkey)
95 log.info('running task with lockkey %s' % lockkey)
96
92 try:
97 try:
93 sa = get_session()
94 lock = l = DaemonLock(file_=jn(lockkey_path, lockkey))
98 lock = l = DaemonLock(file_=jn(lockkey_path, lockkey))
95
99
96 # for js data compatibilty cleans the key for person from '
100 # for js data compatibility cleans the key for person from '
97 akc = lambda k: person(k).replace('"', "")
101 akc = lambda k: person(k).replace('"', "")
98
102
99 co_day_auth_aggr = {}
103 co_day_auth_aggr = {}
100 commits_by_day_aggregate = {}
104 commits_by_day_aggregate = {}
101 repos_path = get_repos_path()
105 repo = Repository.get_by_repo_name(repo_name)
102 repo = get_repo(safe_str(os.path.join(repos_path, repo_name)))
106 if repo is None:
107 return True
108
109 repo = repo.scm_instance
103 repo_size = repo.count()
110 repo_size = repo.count()
104 # return if repo have no revisions
111 # return if repo have no revisions
105 if repo_size < 1:
112 if repo_size < 1:
@@ -112,9 +119,9 b' def get_commits_stats(repo_name, ts_min_'
112 last_cs = None
119 last_cs = None
113 timegetter = itemgetter('time')
120 timegetter = itemgetter('time')
114
121
115 dbrepo = sa.query(Repository)\
122 dbrepo = DBS.query(Repository)\
116 .filter(Repository.repo_name == repo_name).scalar()
123 .filter(Repository.repo_name == repo_name).scalar()
117 cur_stats = sa.query(Statistics)\
124 cur_stats = DBS.query(Statistics)\
118 .filter(Statistics.repository == dbrepo).scalar()
125 .filter(Statistics.repository == dbrepo).scalar()
119
126
120 if cur_stats is not None:
127 if cur_stats is not None:
@@ -132,7 +139,7 b' def get_commits_stats(repo_name, ts_min_'
132 cur_stats.commit_activity_combined))
139 cur_stats.commit_activity_combined))
133 co_day_auth_aggr = json.loads(cur_stats.commit_activity)
140 co_day_auth_aggr = json.loads(cur_stats.commit_activity)
134
141
135 log.debug('starting parsing %s', parse_limit)
142 log.debug('starting parsing %s' % parse_limit)
136 lmktime = mktime
143 lmktime = mktime
137
144
138 last_rev = last_rev + 1 if last_rev >= 0 else 0
145 last_rev = last_rev + 1 if last_rev >= 0 else 0
@@ -207,9 +214,9 b' def get_commits_stats(repo_name, ts_min_'
207 stats.commit_activity = json.dumps(co_day_auth_aggr)
214 stats.commit_activity = json.dumps(co_day_auth_aggr)
208 stats.commit_activity_combined = json.dumps(overview_data)
215 stats.commit_activity_combined = json.dumps(overview_data)
209
216
210 log.debug('last revison %s', last_rev)
217 log.debug('last revison %s' % last_rev)
211 leftovers = len(repo.revisions[last_rev:])
218 leftovers = len(repo.revisions[last_rev:])
212 log.debug('revisions to parse %s', leftovers)
219 log.debug('revisions to parse %s' % leftovers)
213
220
214 if last_rev == 0 or leftovers < parse_limit:
221 if last_rev == 0 or leftovers < parse_limit:
215 log.debug('getting code trending stats')
222 log.debug('getting code trending stats')
@@ -218,18 +225,18 b' def get_commits_stats(repo_name, ts_min_'
218 try:
225 try:
219 stats.repository = dbrepo
226 stats.repository = dbrepo
220 stats.stat_on_revision = last_cs.revision if last_cs else 0
227 stats.stat_on_revision = last_cs.revision if last_cs else 0
221 sa.add(stats)
228 DBS.add(stats)
222 sa.commit()
229 DBS.commit()
223 except:
230 except:
224 log.error(traceback.format_exc())
231 log.error(traceback.format_exc())
225 sa.rollback()
232 DBS.rollback()
226 lock.release()
233 lock.release()
227 return False
234 return False
228
235
229 # final release
236 #final release
230 lock.release()
237 lock.release()
231
238
232 # execute another task if celery is enabled
239 #execute another task if celery is enabled
233 if len(repo.revisions) > 1 and CELERY_ON:
240 if len(repo.revisions) > 1 and CELERY_ON:
234 run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
241 run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
235 return True
242 return True
@@ -240,38 +247,28 b' def get_commits_stats(repo_name, ts_min_'
240 @task(ignore_result=True)
247 @task(ignore_result=True)
241 @dbsession
248 @dbsession
242 def send_password_link(user_email):
249 def send_password_link(user_email):
243 try:
250 from rhodecode.model.notification import EmailNotificationModel
244 log = reset_user_password.get_logger()
245 except:
246 log = logging.getLogger(__name__)
247
251
248 from rhodecode.lib import auth
252 log = get_logger(send_password_link)
253 DBS = get_session()
249
254
250 try:
255 try:
251 sa = get_session()
256 user = User.get_by_email(user_email)
252 user = sa.query(User).filter(User.email == user_email).scalar()
253
254 if user:
257 if user:
258 log.debug('password reset user found %s' % user)
255 link = url('reset_password_confirmation', key=user.api_key,
259 link = url('reset_password_confirmation', key=user.api_key,
256 qualified=True)
260 qualified=True)
257 tmpl = """
261 reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
258 Hello %s
262 body = EmailNotificationModel().get_email_tmpl(reg_type,
259
263 **{'user':user.short_contact,
260 We received a request to create a new password for your account.
264 'reset_url':link})
261
265 log.debug('sending email')
262 You can generate it by clicking following URL:
263
264 %s
265
266 If you didn't request new password please ignore this email.
267 """
268 run_task(send_email, user_email,
266 run_task(send_email, user_email,
269 "RhodeCode password reset link",
267 _("password reset link"), body)
270 tmpl % (user.short_contact, link))
268 log.info('send new password mail to %s' % user_email)
271 log.info('send new password mail to %s', user_email)
269 else:
272
270 log.debug("password reset email %s not found" % user_email)
273 except:
271 except:
274 log.error('Failed to update user password')
275 log.error(traceback.format_exc())
272 log.error(traceback.format_exc())
276 return False
273 return False
277
274
@@ -280,36 +277,32 b" If you didn't request new password pleas"
280 @task(ignore_result=True)
277 @task(ignore_result=True)
281 @dbsession
278 @dbsession
282 def reset_user_password(user_email):
279 def reset_user_password(user_email):
283 try:
280 from rhodecode.lib import auth
284 log = reset_user_password.get_logger()
285 except:
286 log = logging.getLogger(__name__)
287
281
288 from rhodecode.lib import auth
282 log = get_logger(reset_user_password)
283 DBS = get_session()
289
284
290 try:
285 try:
291 try:
286 try:
292 sa = get_session()
287 user = User.get_by_email(user_email)
293 user = sa.query(User).filter(User.email == user_email).scalar()
294 new_passwd = auth.PasswordGenerator().gen_password(8,
288 new_passwd = auth.PasswordGenerator().gen_password(8,
295 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
289 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
296 if user:
290 if user:
297 user.password = auth.get_crypt_password(new_passwd)
291 user.password = auth.get_crypt_password(new_passwd)
298 user.api_key = auth.generate_api_key(user.username)
292 user.api_key = auth.generate_api_key(user.username)
299 sa.add(user)
293 DBS.add(user)
300 sa.commit()
294 DBS.commit()
301 log.info('change password for %s', user_email)
295 log.info('change password for %s' % user_email)
302 if new_passwd is None:
296 if new_passwd is None:
303 raise Exception('unable to generate new password')
297 raise Exception('unable to generate new password')
304
305 except:
298 except:
306 log.error(traceback.format_exc())
299 log.error(traceback.format_exc())
307 sa.rollback()
300 DBS.rollback()
308
301
309 run_task(send_email, user_email,
302 run_task(send_email, user_email,
310 "Your new RhodeCode password",
303 'Your new password',
311 'Your new RhodeCode password:%s' % (new_passwd))
304 'Your new RhodeCode password:%s' % (new_passwd))
312 log.info('send new password mail to %s', user_email)
305 log.info('send new password mail to %s' % user_email)
313
306
314 except:
307 except:
315 log.error('Failed to update user password')
308 log.error('Failed to update user password')
@@ -320,7 +313,7 b' def reset_user_password(user_email):'
320
313
321 @task(ignore_result=True)
314 @task(ignore_result=True)
322 @dbsession
315 @dbsession
323 def send_email(recipients, subject, body):
316 def send_email(recipients, subject, body, html_body=''):
324 """
317 """
325 Sends an email with defined parameters from the .ini files.
318 Sends an email with defined parameters from the .ini files.
326
319
@@ -328,23 +321,20 b' def send_email(recipients, subject, body'
328 address from field 'email_to' is used instead
321 address from field 'email_to' is used instead
329 :param subject: subject of the mail
322 :param subject: subject of the mail
330 :param body: body of the mail
323 :param body: body of the mail
324 :param html_body: html version of body
331 """
325 """
332 try:
326 log = get_logger(send_email)
333 log = send_email.get_logger()
327 DBS = get_session()
334 except:
328
335 log = logging.getLogger(__name__)
336
337 sa = get_session()
338 email_config = config
329 email_config = config
339
330 subject = "%s %s" % (email_config.get('email_prefix'), subject)
340 if not recipients:
331 if not recipients:
341 # if recipients are not defined we send to email_config + all admins
332 # if recipients are not defined we send to email_config + all admins
342 admins = [
333 admins = [u.email for u in User.query()
343 u.email for u in sa.query(User).filter(User.admin==True).all()
334 .filter(User.admin == True).all()]
344 ]
345 recipients = [email_config.get('email_to')] + admins
335 recipients = [email_config.get('email_to')] + admins
346
336
347 mail_from = email_config.get('app_email_from')
337 mail_from = email_config.get('app_email_from', 'RhodeCode')
348 user = email_config.get('smtp_username')
338 user = email_config.get('smtp_username')
349 passwd = email_config.get('smtp_password')
339 passwd = email_config.get('smtp_password')
350 mail_server = email_config.get('smtp_server')
340 mail_server = email_config.get('smtp_server')
@@ -355,9 +345,9 b' def send_email(recipients, subject, body'
355 smtp_auth = email_config.get('smtp_auth')
345 smtp_auth = email_config.get('smtp_auth')
356
346
357 try:
347 try:
358 m = SmtpMailer(mail_from, user, passwd, mail_server,smtp_auth,
348 m = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth,
359 mail_port, ssl, tls, debug=debug)
349 mail_port, ssl, tls, debug=debug)
360 m.send(recipients, subject, body)
350 m.send(recipients, subject, body, html_body)
361 except:
351 except:
362 log.error('Mail sending failed')
352 log.error('Mail sending failed')
363 log.error(traceback.format_exc())
353 log.error(traceback.format_exc())
@@ -368,29 +358,45 b' def send_email(recipients, subject, body'
368 @task(ignore_result=True)
358 @task(ignore_result=True)
369 @dbsession
359 @dbsession
370 def create_repo_fork(form_data, cur_user):
360 def create_repo_fork(form_data, cur_user):
361 """
362 Creates a fork of repository using interval VCS methods
363
364 :param form_data:
365 :param cur_user:
366 """
371 from rhodecode.model.repo import RepoModel
367 from rhodecode.model.repo import RepoModel
372
368
373 try:
369 log = get_logger(create_repo_fork)
374 log = create_repo_fork.get_logger()
370 DBS = get_session()
375 except:
371
376 log = logging.getLogger(__name__)
372 base_path = Repository.base_path()
373
374 RepoModel(DBS).create(form_data, cur_user, just_db=True, fork=True)
375
376 alias = form_data['repo_type']
377 org_repo_name = form_data['org_path']
378 fork_name = form_data['repo_name_full']
379 update_after_clone = form_data['update_after_clone']
380 source_repo_path = os.path.join(base_path, org_repo_name)
381 destination_fork_path = os.path.join(base_path, fork_name)
377
382
378 repo_model = RepoModel(get_session())
383 log.info('creating fork of %s as %s', source_repo_path,
379 repo_model.create(form_data, cur_user, just_db=True, fork=True)
384 destination_fork_path)
380 repo_name = form_data['repo_name']
385 backend = get_backend(alias)
381 repos_path = get_repos_path()
386 backend(safe_str(destination_fork_path), create=True,
382 repo_path = os.path.join(repos_path, repo_name)
387 src_url=safe_str(source_repo_path),
383 repo_fork_path = os.path.join(repos_path, form_data['fork_name'])
388 update_after_clone=update_after_clone)
384 alias = form_data['repo_type']
389 action_logger(cur_user, 'user_forked_repo:%s' % fork_name,
390 org_repo_name, '', DBS)
385
391
386 log.info('creating repo fork %s as %s', repo_name, repo_path)
392 action_logger(cur_user, 'user_created_fork:%s' % fork_name,
387 backend = get_backend(alias)
393 fork_name, '', DBS)
388 backend(str(repo_fork_path), create=True, src_url=str(repo_path))
394 # finally commit at latest possible stage
389
395 DBS.commit()
390
396
391 def __get_codes_stats(repo_name):
397 def __get_codes_stats(repo_name):
392 repos_path = get_repos_path()
398 repo = Repository.get_by_repo_name(repo_name).scm_instance
393 repo = get_repo(safe_str(os.path.join(repos_path, repo_name)))
399
394 tip = repo.get_changeset()
400 tip = repo.get_changeset()
395 code_stats = {}
401 code_stats = {}
396
402
@@ -1,7 +1,10 b''
1 import rhodecode
1 from rhodecode.lib.utils import BasePasterCommand, Command
2 from rhodecode.lib.utils import BasePasterCommand, Command
2 from celery.app import app_or_default
3 from celery.app import app_or_default
3 from celery.bin import camqadm, celerybeat, celeryd, celeryev
4 from celery.bin import camqadm, celerybeat, celeryd, celeryev
4
5
6 from rhodecode.lib import str2bool
7
5 __all__ = ['CeleryDaemonCommand', 'CeleryBeatCommand',
8 __all__ = ['CeleryDaemonCommand', 'CeleryBeatCommand',
6 'CAMQPAdminCommand', 'CeleryEventCommand']
9 'CAMQPAdminCommand', 'CeleryEventCommand']
7
10
@@ -26,6 +29,16 b' class CeleryCommand(BasePasterCommand):'
26 self.parser.add_option(x)
29 self.parser.add_option(x)
27
30
28 def command(self):
31 def command(self):
32 from pylons import config
33 try:
34 CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
35 except KeyError:
36 CELERY_ON = False
37
38 if CELERY_ON == False:
39 raise Exception('Please enable celery_on in .ini config '
40 'file before running celeryd')
41 rhodecode.CELERY_ON = CELERY_ON
29 cmd = self.celery_command(app_or_default())
42 cmd = self.celery_command(app_or_default())
30 return cmd.run(**vars(self.options))
43 return cmd.run(**vars(self.options))
31
44
@@ -4,11 +4,11 b''
4 ~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~
5
5
6 Python backward compatibility functions and common libs
6 Python backward compatibility functions and common libs
7
7
8
8
9 :created_on: Oct 7, 2011
9 :created_on: Oct 7, 2011
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2010 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
@@ -87,10 +87,11 b' class _Nil(object):'
87
87
88 _nil = _Nil()
88 _nil = _Nil()
89
89
90
90 class _odict(object):
91 class _odict(object):
91 """Ordered dict data structure, with O(1) complexity for dict operations
92 """Ordered dict data structure, with O(1) complexity for dict operations
92 that modify one element.
93 that modify one element.
93
94
94 Overwriting values doesn't change their original sequential order.
95 Overwriting values doesn't change their original sequential order.
95 """
96 """
96
97
@@ -146,7 +147,7 b' class _odict(object):'
146 dict_impl = self._dict_impl()
147 dict_impl = self._dict_impl()
147 try:
148 try:
148 dict_impl.__getitem__(self, key)[1] = val
149 dict_impl.__getitem__(self, key)[1] = val
149 except KeyError, e:
150 except KeyError:
150 new = [dict_impl.__getattribute__(self, 'lt'), val, _nil]
151 new = [dict_impl.__getattribute__(self, 'lt'), val, _nil]
151 dict_impl.__setitem__(self, key, new)
152 dict_impl.__setitem__(self, key, new)
152 if dict_impl.__getattribute__(self, 'lt') == _nil:
153 if dict_impl.__getattribute__(self, 'lt') == _nil:
@@ -158,7 +159,7 b' class _odict(object):'
158
159
159 def __delitem__(self, key):
160 def __delitem__(self, key):
160 dict_impl = self._dict_impl()
161 dict_impl = self._dict_impl()
161 pred, _ , succ = self._dict_impl().__getitem__(self, key)
162 pred, _, succ = self._dict_impl().__getitem__(self, key)
162 if pred == _nil:
163 if pred == _nil:
163 dict_impl.__setattr__(self, 'lh', succ)
164 dict_impl.__setattr__(self, 'lh', succ)
164 else:
165 else:
@@ -351,6 +352,7 b' class _odict(object):'
351 dict_impl.__getattribute__(self, 'lt'),
352 dict_impl.__getattribute__(self, 'lt'),
352 dict_impl.__repr__(self))
353 dict_impl.__repr__(self))
353
354
355
354 class OrderedDict(_odict, dict):
356 class OrderedDict(_odict, dict):
355
357
356 def _dict_impl(self):
358 def _dict_impl(self):
@@ -8,7 +8,7 b''
8
8
9 :created_on: Apr 10, 2010
9 :created_on: Apr 10, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
@@ -33,13 +33,15 b' from os.path import dirname as dn, join '
33 from rhodecode import __dbversion__
33 from rhodecode import __dbversion__
34 from rhodecode.model import meta
34 from rhodecode.model import meta
35
35
36 from rhodecode.lib.auth import get_crypt_password, generate_api_key
36 from rhodecode.model.user import UserModel
37 from rhodecode.lib.utils import ask_ok
37 from rhodecode.lib.utils import ask_ok
38 from rhodecode.model import init_model
38 from rhodecode.model import init_model
39 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
39 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
40 RhodeCodeSettings, UserToPerm, DbMigrateVersion
40 RhodeCodeSetting, UserToPerm, DbMigrateVersion, RepoGroup,\
41 UserRepoGroupToPerm
41
42
42 from sqlalchemy.engine import create_engine
43 from sqlalchemy.engine import create_engine
44 from rhodecode.model.repos_group import ReposGroupModel
43
45
44 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
45
47
@@ -57,10 +59,11 b' class DbManage(object):'
57 def init_db(self):
59 def init_db(self):
58 engine = create_engine(self.dburi, echo=self.log_sql)
60 engine = create_engine(self.dburi, echo=self.log_sql)
59 init_model(engine)
61 init_model(engine)
60 self.sa = meta.Session()
62 self.sa = meta.Session
61
63
62 def create_tables(self, override=False):
64 def create_tables(self, override=False):
63 """Create a auth database
65 """
66 Create a auth database
64 """
67 """
65
68
66 log.info("Any existing database is going to be destroyed")
69 log.info("Any existing database is going to be destroyed")
@@ -75,23 +78,19 b' class DbManage(object):'
75
78
76 checkfirst = not override
79 checkfirst = not override
77 meta.Base.metadata.create_all(checkfirst=checkfirst)
80 meta.Base.metadata.create_all(checkfirst=checkfirst)
78 log.info('Created tables for %s', self.dbname)
81 log.info('Created tables for %s' % self.dbname)
79
82
80 def set_db_version(self):
83 def set_db_version(self):
81 try:
84 ver = DbMigrateVersion()
82 ver = DbMigrateVersion()
85 ver.version = __dbversion__
83 ver.version = __dbversion__
86 ver.repository_id = 'rhodecode_db_migrations'
84 ver.repository_id = 'rhodecode_db_migrations'
87 ver.repository_path = 'versions'
85 ver.repository_path = 'versions'
88 self.sa.add(ver)
86 self.sa.add(ver)
89 log.info('db version set to: %s' % __dbversion__)
87 self.sa.commit()
88 except:
89 self.sa.rollback()
90 raise
91 log.info('db version set to: %s', __dbversion__)
92
90
93 def upgrade(self):
91 def upgrade(self):
94 """Upgrades given database schema to given revision following
92 """
93 Upgrades given database schema to given revision following
95 all needed steps, to perform the upgrade
94 all needed steps, to perform the upgrade
96
95
97 """
96 """
@@ -146,7 +145,7 b' class DbManage(object):'
146 self.klass = klass
145 self.klass = klass
147
146
148 def step_0(self):
147 def step_0(self):
149 #step 0 is the schema upgrade, and than follow proper upgrades
148 # step 0 is the schema upgrade, and than follow proper upgrades
150 print ('attempting to do database upgrade to version %s' \
149 print ('attempting to do database upgrade to version %s' \
151 % __dbversion__)
150 % __dbversion__)
152 api.upgrade(db_uri, repository_path, __dbversion__)
151 api.upgrade(db_uri, repository_path, __dbversion__)
@@ -170,16 +169,26 b' class DbManage(object):'
170 self.klass.fix_settings()
169 self.klass.fix_settings()
171 print ('Adding ldap defaults')
170 print ('Adding ldap defaults')
172 self.klass.create_ldap_options(skip_existing=True)
171 self.klass.create_ldap_options(skip_existing=True)
173
172
173 def step_4(self):
174 print ('create permissions and fix groups')
175 self.klass.create_permissions()
176 self.klass.fixup_groups()
177
178 def step_5(self):
179 pass
180
174 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
181 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
175
182
176 #CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
183 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
177 for step in upgrade_steps:
184 for step in upgrade_steps:
178 print ('performing upgrade step %s' % step)
185 print ('performing upgrade step %s' % step)
179 getattr(UpgradeSteps(self), 'step_%s' % step)()
186 getattr(UpgradeSteps(self), 'step_%s' % step)()
187 self.sa.commit()
180
188
181 def fix_repo_paths(self):
189 def fix_repo_paths(self):
182 """Fixes a old rhodecode version path into new one without a '*'
190 """
191 Fixes a old rhodecode version path into new one without a '*'
183 """
192 """
184
193
185 paths = self.sa.query(RhodeCodeUi)\
194 paths = self.sa.query(RhodeCodeUi)\
@@ -196,7 +205,8 b' class DbManage(object):'
196 raise
205 raise
197
206
198 def fix_default_user(self):
207 def fix_default_user(self):
199 """Fixes a old default user with some 'nicer' default values,
208 """
209 Fixes a old default user with some 'nicer' default values,
200 used mostly for anonymous access
210 used mostly for anonymous access
201 """
211 """
202 def_user = self.sa.query(User)\
212 def_user = self.sa.query(User)\
@@ -215,10 +225,11 b' class DbManage(object):'
215 raise
225 raise
216
226
217 def fix_settings(self):
227 def fix_settings(self):
218 """Fixes rhodecode settings adds ga_code key for google analytics
228 """
229 Fixes rhodecode settings adds ga_code key for google analytics
219 """
230 """
220
231
221 hgsettings3 = RhodeCodeSettings('ga_code', '')
232 hgsettings3 = RhodeCodeSetting('ga_code', '')
222
233
223 try:
234 try:
224 self.sa.add(hgsettings3)
235 self.sa.add(hgsettings3)
@@ -258,18 +269,27 b' class DbManage(object):'
258 self.create_user(username, password, email, True)
269 self.create_user(username, password, email, True)
259 else:
270 else:
260 log.info('creating admin and regular test users')
271 log.info('creating admin and regular test users')
261 self.create_user('test_admin', 'test12',
272 from rhodecode.tests import TEST_USER_ADMIN_LOGIN,\
262 'test_admin@mail.com', True)
273 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL,\
263 self.create_user('test_regular', 'test12',
274 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,\
264 'test_regular@mail.com', False)
275 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
265 self.create_user('test_regular2', 'test12',
276 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
266 'test_regular2@mail.com', False)
277
278 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
279 TEST_USER_ADMIN_EMAIL, True)
280
281 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
282 TEST_USER_REGULAR_EMAIL, False)
283
284 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
285 TEST_USER_REGULAR2_EMAIL, False)
267
286
268 def create_ui_settings(self):
287 def create_ui_settings(self):
269 """Creates ui settings, fills out hooks
288 """
289 Creates ui settings, fills out hooks
270 and disables dotencode
290 and disables dotencode
291 """
271
292
272 """
273 #HOOKS
293 #HOOKS
274 hooks1_key = RhodeCodeUi.HOOK_UPDATE
294 hooks1_key = RhodeCodeUi.HOOK_UPDATE
275 hooks1_ = self.sa.query(RhodeCodeUi)\
295 hooks1_ = self.sa.query(RhodeCodeUi)\
@@ -300,7 +320,7 b' class DbManage(object):'
300 hooks4.ui_key = RhodeCodeUi.HOOK_PULL
320 hooks4.ui_key = RhodeCodeUi.HOOK_PULL
301 hooks4.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
321 hooks4.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
302
322
303 #For mercurial 1.7 set backward comapatibility with format
323 # For mercurial 1.7 set backward comapatibility with format
304 dotencode_disable = RhodeCodeUi()
324 dotencode_disable = RhodeCodeUi()
305 dotencode_disable.ui_section = 'format'
325 dotencode_disable.ui_section = 'format'
306 dotencode_disable.ui_key = 'dotencode'
326 dotencode_disable.ui_key = 'dotencode'
@@ -312,39 +332,43 b' class DbManage(object):'
312 largefiles.ui_key = 'largefiles'
332 largefiles.ui_key = 'largefiles'
313 largefiles.ui_value = ''
333 largefiles.ui_value = ''
314
334
315 try:
335 self.sa.add(hooks1)
316 self.sa.add(hooks1)
336 self.sa.add(hooks2)
317 self.sa.add(hooks2)
337 self.sa.add(hooks3)
318 self.sa.add(hooks3)
338 self.sa.add(hooks4)
319 self.sa.add(hooks4)
339 self.sa.add(largefiles)
320 self.sa.add(dotencode_disable)
321 self.sa.add(largefiles)
322 self.sa.commit()
323 except:
324 self.sa.rollback()
325 raise
326
340
327 def create_ldap_options(self,skip_existing=False):
341 def create_ldap_options(self, skip_existing=False):
328 """Creates ldap settings"""
342 """Creates ldap settings"""
329
343
330 try:
344 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
331 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
345 ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
332 ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
346 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
333 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
347 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
334 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
348 ('ldap_filter', ''), ('ldap_search_scope', ''),
335 ('ldap_filter', ''), ('ldap_search_scope', ''),
349 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
336 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
350 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
337 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
351
352 if skip_existing and RhodeCodeSetting.get_by_name(k) != None:
353 log.debug('Skipping option %s' % k)
354 continue
355 setting = RhodeCodeSetting(k, v)
356 self.sa.add(setting)
338
357
339 if skip_existing and RhodeCodeSettings.get_by_name(k) != None:
358 def fixup_groups(self):
340 log.debug('Skipping option %s' % k)
359 def_usr = User.get_by_username('default')
341 continue
360 for g in RepoGroup.query().all():
342 setting = RhodeCodeSettings(k, v)
361 g.group_name = g.get_new_name(g.name)
343 self.sa.add(setting)
362 self.sa.add(g)
344 self.sa.commit()
363 # get default perm
345 except:
364 default = UserRepoGroupToPerm.query()\
346 self.sa.rollback()
365 .filter(UserRepoGroupToPerm.group == g)\
347 raise
366 .filter(UserRepoGroupToPerm.user == def_usr)\
367 .scalar()
368
369 if default is None:
370 log.debug('missing default permission for group %s adding' % g)
371 ReposGroupModel()._create_default_perms(g)
348
372
349 def config_prompt(self, test_repo_path='', retries=3):
373 def config_prompt(self, test_repo_path='', retries=3):
350 if retries == 3:
374 if retries == 3:
@@ -359,16 +383,15 b' class DbManage(object):'
359 path = test_repo_path
383 path = test_repo_path
360 path_ok = True
384 path_ok = True
361
385
362 #check proper dir
386 # check proper dir
363 if not os.path.isdir(path):
387 if not os.path.isdir(path):
364 path_ok = False
388 path_ok = False
365 log.error('Given path %s is not a valid directory', path)
389 log.error('Given path %s is not a valid directory' % path)
366
390
367 #check write access
391 # check write access
368 if not os.access(path, os.W_OK) and path_ok:
392 if not os.access(path, os.W_OK) and path_ok:
369 path_ok = False
393 path_ok = False
370 log.error('No write permission to given path %s', path)
394 log.error('No write permission to given path %s' % path)
371
372
395
373 if retries == 0:
396 if retries == 0:
374 sys.exit('max retries reached')
397 sys.exit('max retries reached')
@@ -408,85 +431,68 b' class DbManage(object):'
408 paths.ui_key = '/'
431 paths.ui_key = '/'
409 paths.ui_value = path
432 paths.ui_value = path
410
433
411 hgsettings1 = RhodeCodeSettings('realm', 'RhodeCode authentication')
434 hgsettings1 = RhodeCodeSetting('realm', 'RhodeCode authentication')
412 hgsettings2 = RhodeCodeSettings('title', 'RhodeCode')
435 hgsettings2 = RhodeCodeSetting('title', 'RhodeCode')
413 hgsettings3 = RhodeCodeSettings('ga_code', '')
436 hgsettings3 = RhodeCodeSetting('ga_code', '')
414
437
415 try:
438 self.sa.add(web1)
416 self.sa.add(web1)
439 self.sa.add(web2)
417 self.sa.add(web2)
440 self.sa.add(web3)
418 self.sa.add(web3)
441 self.sa.add(web4)
419 self.sa.add(web4)
442 self.sa.add(paths)
420 self.sa.add(paths)
443 self.sa.add(hgsettings1)
421 self.sa.add(hgsettings1)
444 self.sa.add(hgsettings2)
422 self.sa.add(hgsettings2)
445 self.sa.add(hgsettings3)
423 self.sa.add(hgsettings3)
424
425 self.sa.commit()
426 except:
427 self.sa.rollback()
428 raise
429
446
430 self.create_ldap_options()
447 self.create_ldap_options()
431
448
432 log.info('created ui config')
449 log.info('created ui config')
433
450
434 def create_user(self, username, password, email='', admin=False):
451 def create_user(self, username, password, email='', admin=False):
435 log.info('creating administrator user %s', username)
452 log.info('creating user %s' % username)
436
453 UserModel().create_or_update(username, password, email,
437 form_data = dict(username=username,
454 name='RhodeCode', lastname='Admin',
438 password=password,
455 active=True, admin=admin)
439 active=True,
440 admin=admin,
441 name='RhodeCode',
442 lastname='Admin',
443 email=email)
444 User.create(form_data)
445
446
456
447 def create_default_user(self):
457 def create_default_user(self):
448 log.info('creating default user')
458 log.info('creating default user')
449 #create default user for handling default permissions.
459 # create default user for handling default permissions.
460 UserModel().create_or_update(username='default',
461 password=str(uuid.uuid1())[:8],
462 email='anonymous@rhodecode.org',
463 name='Anonymous', lastname='User')
450
464
451 form_data = dict(username='default',
452 password=str(uuid.uuid1())[:8],
453 active=False,
454 admin=False,
455 name='Anonymous',
456 lastname='User',
457 email='anonymous@rhodecode.org')
458 User.create(form_data)
459
460 def create_permissions(self):
465 def create_permissions(self):
461 #module.(access|create|change|delete)_[name]
466 # module.(access|create|change|delete)_[name]
462 #module.(read|write|owner)
467 # module.(none|read|write|admin)
463 perms = [('repository.none', 'Repository no access'),
468 perms = [
464 ('repository.read', 'Repository read access'),
469 ('repository.none', 'Repository no access'),
465 ('repository.write', 'Repository write access'),
470 ('repository.read', 'Repository read access'),
466 ('repository.admin', 'Repository admin access'),
471 ('repository.write', 'Repository write access'),
467 ('hg.admin', 'Hg Administrator'),
472 ('repository.admin', 'Repository admin access'),
468 ('hg.create.repository', 'Repository create'),
469 ('hg.create.none', 'Repository creation disabled'),
470 ('hg.register.none', 'Register disabled'),
471 ('hg.register.manual_activate', 'Register new user with '
472 'RhodeCode without manual'
473 'activation'),
474
473
475 ('hg.register.auto_activate', 'Register new user with '
474 ('group.none', 'Repositories Group no access'),
476 'RhodeCode without auto '
475 ('group.read', 'Repositories Group read access'),
477 'activation'),
476 ('group.write', 'Repositories Group write access'),
478 ]
477 ('group.admin', 'Repositories Group admin access'),
478
479 ('hg.admin', 'Hg Administrator'),
480 ('hg.create.repository', 'Repository create'),
481 ('hg.create.none', 'Repository creation disabled'),
482 ('hg.register.none', 'Register disabled'),
483 ('hg.register.manual_activate', 'Register new user with RhodeCode '
484 'without manual activation'),
485
486 ('hg.register.auto_activate', 'Register new user with RhodeCode '
487 'without auto activation'),
488 ]
479
489
480 for p in perms:
490 for p in perms:
481 new_perm = Permission()
491 if not Permission.get_by_key(p[0]):
482 new_perm.permission_name = p[0]
492 new_perm = Permission()
483 new_perm.permission_longname = p[1]
493 new_perm.permission_name = p[0]
484 try:
494 new_perm.permission_longname = p[1]
485 self.sa.add(new_perm)
495 self.sa.add(new_perm)
486 self.sa.commit()
487 except:
488 self.sa.rollback()
489 raise
490
496
491 def populate_default_permissions(self):
497 def populate_default_permissions(self):
492 log.info('creating default user permissions')
498 log.info('creating default user permissions')
@@ -512,11 +518,6 b' class DbManage(object):'
512 .filter(Permission.permission_name == 'repository.read')\
518 .filter(Permission.permission_name == 'repository.read')\
513 .scalar()
519 .scalar()
514
520
515 try:
521 self.sa.add(reg_perm)
516 self.sa.add(reg_perm)
522 self.sa.add(create_repo_perm)
517 self.sa.add(create_repo_perm)
523 self.sa.add(default_repo_perm)
518 self.sa.add(default_repo_perm)
519 self.sa.commit()
520 except:
521 self.sa.rollback()
522 raise
@@ -7,7 +7,7 b''
7
7
8 :created_on: Dec 11, 2010
8 :created_on: Dec 11, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
@@ -8,4 +8,4 b''
8 from rhodecode.lib.dbmigrate.migrate.versioning import *
8 from rhodecode.lib.dbmigrate.migrate.versioning import *
9 from rhodecode.lib.dbmigrate.migrate.changeset import *
9 from rhodecode.lib.dbmigrate.migrate.changeset import *
10
10
11 __version__ = '0.7.2.dev' No newline at end of file
11 __version__ = '0.7.3.dev'
@@ -12,7 +12,7 b' from sqlalchemy import __version__ as _s'
12
12
13 warnings.simplefilter('always', DeprecationWarning)
13 warnings.simplefilter('always', DeprecationWarning)
14
14
15 _sa_version = tuple(int(re.match("\d+", x).group(0))
15 _sa_version = tuple(int(re.match("\d+", x).group(0))
16 for x in _sa_version.split("."))
16 for x in _sa_version.split("."))
17 SQLA_06 = _sa_version >= (0, 6)
17 SQLA_06 = _sa_version >= (0, 6)
18 SQLA_07 = _sa_version >= (0, 7)
18 SQLA_07 = _sa_version >= (0, 7)
@@ -17,23 +17,19 b' from sqlalchemy.schema import (ForeignKe'
17 Index)
17 Index)
18
18
19 from rhodecode.lib.dbmigrate.migrate import exceptions
19 from rhodecode.lib.dbmigrate.migrate import exceptions
20 from rhodecode.lib.dbmigrate.migrate.changeset import constraint, SQLA_06
20 from rhodecode.lib.dbmigrate.migrate.changeset import constraint
21
21
22 if not SQLA_06:
22 from sqlalchemy.schema import AddConstraint, DropConstraint
23 from sqlalchemy.sql.compiler import SchemaGenerator, SchemaDropper
23 from sqlalchemy.sql.compiler import DDLCompiler
24 else:
24 SchemaGenerator = SchemaDropper = DDLCompiler
25 from sqlalchemy.schema import AddConstraint, DropConstraint
26 from sqlalchemy.sql.compiler import DDLCompiler
27 SchemaGenerator = SchemaDropper = DDLCompiler
28
25
29
26
30 class AlterTableVisitor(SchemaVisitor):
27 class AlterTableVisitor(SchemaVisitor):
31 """Common operations for ``ALTER TABLE`` statements."""
28 """Common operations for ``ALTER TABLE`` statements."""
32
29
33 if SQLA_06:
30 # engine.Compiler looks for .statement
34 # engine.Compiler looks for .statement
31 # when it spawns off a new compiler
35 # when it spawns off a new compiler
32 statement = ClauseElement()
36 statement = ClauseElement()
37
33
38 def append(self, s):
34 def append(self, s):
39 """Append content to the SchemaIterator's query buffer."""
35 """Append content to the SchemaIterator's query buffer."""
@@ -123,9 +119,8 b' class ANSIColumnGenerator(AlterTableVisi'
123 name=column.primary_key_name)
119 name=column.primary_key_name)
124 cons.create()
120 cons.create()
125
121
126 if SQLA_06:
122 def add_foreignkey(self, fk):
127 def add_foreignkey(self, fk):
123 self.connection.execute(AddConstraint(fk))
128 self.connection.execute(AddConstraint(fk))
129
124
130 class ANSIColumnDropper(AlterTableVisitor, SchemaDropper):
125 class ANSIColumnDropper(AlterTableVisitor, SchemaDropper):
131 """Extends ANSI SQL dropper for column dropping (``ALTER TABLE
126 """Extends ANSI SQL dropper for column dropping (``ALTER TABLE
@@ -232,10 +227,7 b' class ANSISchemaChanger(AlterTableVisito'
232
227
233 def _visit_column_type(self, table, column, delta):
228 def _visit_column_type(self, table, column, delta):
234 type_ = delta['type']
229 type_ = delta['type']
235 if SQLA_06:
230 type_text = str(type_.compile(dialect=self.dialect))
236 type_text = str(type_.compile(dialect=self.dialect))
237 else:
238 type_text = type_.dialect_impl(self.dialect).get_col_spec()
239 self.append("TYPE %s" % type_text)
231 self.append("TYPE %s" % type_text)
240
232
241 def _visit_column_name(self, table, column, delta):
233 def _visit_column_name(self, table, column, delta):
@@ -279,75 +271,17 b' class ANSIConstraintCommon(AlterTableVis'
279 def visit_migrate_unique_constraint(self, *p, **k):
271 def visit_migrate_unique_constraint(self, *p, **k):
280 self._visit_constraint(*p, **k)
272 self._visit_constraint(*p, **k)
281
273
282 if SQLA_06:
274 class ANSIConstraintGenerator(ANSIConstraintCommon, SchemaGenerator):
283 class ANSIConstraintGenerator(ANSIConstraintCommon, SchemaGenerator):
275 def _visit_constraint(self, constraint):
284 def _visit_constraint(self, constraint):
276 constraint.name = self.get_constraint_name(constraint)
285 constraint.name = self.get_constraint_name(constraint)
277 self.append(self.process(AddConstraint(constraint)))
286 self.append(self.process(AddConstraint(constraint)))
278 self.execute()
287 self.execute()
288
289 class ANSIConstraintDropper(ANSIConstraintCommon, SchemaDropper):
290 def _visit_constraint(self, constraint):
291 constraint.name = self.get_constraint_name(constraint)
292 self.append(self.process(DropConstraint(constraint, cascade=constraint.cascade)))
293 self.execute()
294
295 else:
296 class ANSIConstraintGenerator(ANSIConstraintCommon, SchemaGenerator):
297
298 def get_constraint_specification(self, cons, **kwargs):
299 """Constaint SQL generators.
300
301 We cannot use SA visitors because they append comma.
302 """
303
279
304 if isinstance(cons, PrimaryKeyConstraint):
280 class ANSIConstraintDropper(ANSIConstraintCommon, SchemaDropper):
305 if cons.name is not None:
281 def _visit_constraint(self, constraint):
306 self.append("CONSTRAINT %s " % self.preparer.format_constraint(cons))
282 constraint.name = self.get_constraint_name(constraint)
307 self.append("PRIMARY KEY ")
283 self.append(self.process(DropConstraint(constraint, cascade=constraint.cascade)))
308 self.append("(%s)" % ', '.join(self.preparer.quote(c.name, c.quote)
284 self.execute()
309 for c in cons))
310 self.define_constraint_deferrability(cons)
311 elif isinstance(cons, ForeignKeyConstraint):
312 self.define_foreign_key(cons)
313 elif isinstance(cons, CheckConstraint):
314 if cons.name is not None:
315 self.append("CONSTRAINT %s " %
316 self.preparer.format_constraint(cons))
317 self.append("CHECK (%s)" % cons.sqltext)
318 self.define_constraint_deferrability(cons)
319 elif isinstance(cons, UniqueConstraint):
320 if cons.name is not None:
321 self.append("CONSTRAINT %s " %
322 self.preparer.format_constraint(cons))
323 self.append("UNIQUE (%s)" % \
324 (', '.join(self.preparer.quote(c.name, c.quote) for c in cons)))
325 self.define_constraint_deferrability(cons)
326 else:
327 raise exceptions.InvalidConstraintError(cons)
328
329 def _visit_constraint(self, constraint):
330
331 table = self.start_alter_table(constraint)
332 constraint.name = self.get_constraint_name(constraint)
333 self.append("ADD ")
334 self.get_constraint_specification(constraint)
335 self.execute()
336
337
338 class ANSIConstraintDropper(ANSIConstraintCommon, SchemaDropper):
339
340 def _visit_constraint(self, constraint):
341 self.start_alter_table(constraint)
342 self.append("DROP CONSTRAINT ")
343 constraint.name = self.get_constraint_name(constraint)
344 self.append(self.preparer.format_constraint(constraint))
345 if constraint.cascade:
346 self.cascade_constraint(constraint)
347 self.execute()
348
349 def cascade_constraint(self, constraint):
350 self.append(" CASCADE")
351
285
352
286
353 class ANSIDialect(DefaultDialect):
287 class ANSIDialect(DefaultDialect):
@@ -4,7 +4,7 b''
4 from sqlalchemy import schema
4 from sqlalchemy import schema
5
5
6 from rhodecode.lib.dbmigrate.migrate.exceptions import *
6 from rhodecode.lib.dbmigrate.migrate.exceptions import *
7 from rhodecode.lib.dbmigrate.migrate.changeset import SQLA_06
7
8
8
9 class ConstraintChangeset(object):
9 class ConstraintChangeset(object):
10 """Base class for Constraint classes."""
10 """Base class for Constraint classes."""
@@ -85,7 +85,6 b' class PrimaryKeyConstraint(ConstraintCha'
85 if table is not None:
85 if table is not None:
86 self._set_parent(table)
86 self._set_parent(table)
87
87
88
89 def autoname(self):
88 def autoname(self):
90 """Mimic the database's automatic constraint names"""
89 """Mimic the database's automatic constraint names"""
91 return "%s_pkey" % self.table.name
90 return "%s_pkey" % self.table.name
@@ -111,8 +110,9 b' class ForeignKeyConstraint(ConstraintCha'
111 table = kwargs.pop('table', table)
110 table = kwargs.pop('table', table)
112 refcolnames, reftable = self._normalize_columns(refcolumns,
111 refcolnames, reftable = self._normalize_columns(refcolumns,
113 table_name=True)
112 table_name=True)
114 super(ForeignKeyConstraint, self).__init__(colnames, refcolnames, *args,
113 super(ForeignKeyConstraint, self).__init__(
115 **kwargs)
114 colnames, refcolnames, *args,**kwargs
115 )
116 if table is not None:
116 if table is not None:
117 self._set_parent(table)
117 self._set_parent(table)
118
118
@@ -165,8 +165,6 b' class CheckConstraint(ConstraintChangese'
165 table = kwargs.pop('table', table)
165 table = kwargs.pop('table', table)
166 schema.CheckConstraint.__init__(self, sqltext, *args, **kwargs)
166 schema.CheckConstraint.__init__(self, sqltext, *args, **kwargs)
167 if table is not None:
167 if table is not None:
168 if not SQLA_06:
169 self.table = table
170 self._set_parent(table)
168 self._set_parent(table)
171 self.colnames = colnames
169 self.colnames = colnames
172
170
@@ -199,4 +197,4 b' class UniqueConstraint(ConstraintChanges'
199
197
200 def autoname(self):
198 def autoname(self):
201 """Mimic the database's automatic constraint names"""
199 """Mimic the database's automatic constraint names"""
202 return "%s_%s_key" % (self.table.name, self.colnames[0])
200 return "%s_%s_key" % (self.table.name, '_'.join(self.colnames))
@@ -4,13 +4,10 b''
4 from sqlalchemy.databases import firebird as sa_base
4 from sqlalchemy.databases import firebird as sa_base
5 from sqlalchemy.schema import PrimaryKeyConstraint
5 from sqlalchemy.schema import PrimaryKeyConstraint
6 from rhodecode.lib.dbmigrate.migrate import exceptions
6 from rhodecode.lib.dbmigrate.migrate import exceptions
7 from rhodecode.lib.dbmigrate.migrate.changeset import ansisql, SQLA_06
7 from rhodecode.lib.dbmigrate.migrate.changeset import ansisql
8
8
9
9
10 if SQLA_06:
10 FBSchemaGenerator = sa_base.FBDDLCompiler
11 FBSchemaGenerator = sa_base.FBDDLCompiler
12 else:
13 FBSchemaGenerator = sa_base.FBSchemaGenerator
14
11
15 class FBColumnGenerator(FBSchemaGenerator, ansisql.ANSIColumnGenerator):
12 class FBColumnGenerator(FBSchemaGenerator, ansisql.ANSIColumnGenerator):
16 """Firebird column generator implementation."""
13 """Firebird column generator implementation."""
@@ -41,10 +38,7 b' class FBColumnDropper(ansisql.ANSIColumn'
41 # is deleted!
38 # is deleted!
42 continue
39 continue
43
40
44 if SQLA_06:
41 should_drop = column.name in cons.columns
45 should_drop = column.name in cons.columns
46 else:
47 should_drop = cons.contains_column(column) and cons.name
48 if should_drop:
42 if should_drop:
49 self.start_alter_table(column)
43 self.start_alter_table(column)
50 self.append("DROP CONSTRAINT ")
44 self.append("DROP CONSTRAINT ")
@@ -6,13 +6,10 b' from sqlalchemy.databases import mysql a'
6 from sqlalchemy import types as sqltypes
6 from sqlalchemy import types as sqltypes
7
7
8 from rhodecode.lib.dbmigrate.migrate import exceptions
8 from rhodecode.lib.dbmigrate.migrate import exceptions
9 from rhodecode.lib.dbmigrate.migrate.changeset import ansisql, SQLA_06
9 from rhodecode.lib.dbmigrate.migrate.changeset import ansisql
10
10
11
11
12 if not SQLA_06:
12 MySQLSchemaGenerator = sa_base.MySQLDDLCompiler
13 MySQLSchemaGenerator = sa_base.MySQLSchemaGenerator
14 else:
15 MySQLSchemaGenerator = sa_base.MySQLDDLCompiler
16
13
17 class MySQLColumnGenerator(MySQLSchemaGenerator, ansisql.ANSIColumnGenerator):
14 class MySQLColumnGenerator(MySQLSchemaGenerator, ansisql.ANSIColumnGenerator):
18 pass
15 pass
@@ -53,37 +50,11 b' class MySQLSchemaChanger(MySQLSchemaGene'
53 class MySQLConstraintGenerator(ansisql.ANSIConstraintGenerator):
50 class MySQLConstraintGenerator(ansisql.ANSIConstraintGenerator):
54 pass
51 pass
55
52
56 if SQLA_06:
57 class MySQLConstraintDropper(MySQLSchemaGenerator, ansisql.ANSIConstraintDropper):
58 def visit_migrate_check_constraint(self, *p, **k):
59 raise exceptions.NotSupportedError("MySQL does not support CHECK"
60 " constraints, use triggers instead.")
61
53
62 else:
54 class MySQLConstraintDropper(MySQLSchemaGenerator, ansisql.ANSIConstraintDropper):
63 class MySQLConstraintDropper(ansisql.ANSIConstraintDropper):
55 def visit_migrate_check_constraint(self, *p, **k):
64
56 raise exceptions.NotSupportedError("MySQL does not support CHECK"
65 def visit_migrate_primary_key_constraint(self, constraint):
57 " constraints, use triggers instead.")
66 self.start_alter_table(constraint)
67 self.append("DROP PRIMARY KEY")
68 self.execute()
69
70 def visit_migrate_foreign_key_constraint(self, constraint):
71 self.start_alter_table(constraint)
72 self.append("DROP FOREIGN KEY ")
73 constraint.name = self.get_constraint_name(constraint)
74 self.append(self.preparer.format_constraint(constraint))
75 self.execute()
76
77 def visit_migrate_check_constraint(self, *p, **k):
78 raise exceptions.NotSupportedError("MySQL does not support CHECK"
79 " constraints, use triggers instead.")
80
81 def visit_migrate_unique_constraint(self, constraint, *p, **k):
82 self.start_alter_table(constraint)
83 self.append('DROP INDEX ')
84 constraint.name = self.get_constraint_name(constraint)
85 self.append(self.preparer.format_constraint(constraint))
86 self.execute()
87
58
88
59
89 class MySQLDialect(ansisql.ANSIDialect):
60 class MySQLDialect(ansisql.ANSIDialect):
@@ -3,14 +3,11 b''
3
3
4 .. _`PostgreSQL`: http://www.postgresql.org/
4 .. _`PostgreSQL`: http://www.postgresql.org/
5 """
5 """
6 from rhodecode.lib.dbmigrate.migrate.changeset import ansisql, SQLA_06
6 from rhodecode.lib.dbmigrate.migrate.changeset import ansisql
7
7
8
8 if not SQLA_06:
9 from sqlalchemy.databases import postgresql as sa_base
9 from sqlalchemy.databases import postgres as sa_base
10 PGSchemaGenerator = sa_base.PGDDLCompiler
10 PGSchemaGenerator = sa_base.PGSchemaGenerator
11 else:
12 from sqlalchemy.databases import postgresql as sa_base
13 PGSchemaGenerator = sa_base.PGDDLCompiler
14
11
15
12
16 class PGColumnGenerator(PGSchemaGenerator, ansisql.ANSIColumnGenerator):
13 class PGColumnGenerator(PGSchemaGenerator, ansisql.ANSIColumnGenerator):
@@ -11,11 +11,8 b' from sqlalchemy.databases import sqlite '
11 from rhodecode.lib.dbmigrate.migrate import exceptions
11 from rhodecode.lib.dbmigrate.migrate import exceptions
12 from rhodecode.lib.dbmigrate.migrate.changeset import ansisql, SQLA_06
12 from rhodecode.lib.dbmigrate.migrate.changeset import ansisql, SQLA_06
13
13
14 SQLiteSchemaGenerator = sa_base.SQLiteDDLCompiler
14
15
15 if not SQLA_06:
16 SQLiteSchemaGenerator = sa_base.SQLiteSchemaGenerator
17 else:
18 SQLiteSchemaGenerator = sa_base.SQLiteDDLCompiler
19
16
20 class SQLiteCommon(object):
17 class SQLiteCommon(object):
21
18
@@ -39,7 +36,7 b' class SQLiteHelper(SQLiteCommon):'
39
36
40 insertion_string = self._modify_table(table, column, delta)
37 insertion_string = self._modify_table(table, column, delta)
41
38
42 table.create()
39 table.create(bind=self.connection)
43 self.append(insertion_string % {'table_name': table_name})
40 self.append(insertion_string % {'table_name': table_name})
44 self.execute()
41 self.execute()
45 self.append('DROP TABLE migration_tmp')
42 self.append('DROP TABLE migration_tmp')
@@ -349,10 +349,7 b' class ColumnDelta(DictMixin, sqlalchemy.'
349 def process_column(self, column):
349 def process_column(self, column):
350 """Processes default values for column"""
350 """Processes default values for column"""
351 # XXX: this is a snippet from SA processing of positional parameters
351 # XXX: this is a snippet from SA processing of positional parameters
352 if not SQLA_06 and column.args:
352 toinit = list()
353 toinit = list(column.args)
354 else:
355 toinit = list()
356
353
357 if column.server_default is not None:
354 if column.server_default is not None:
358 if isinstance(column.server_default, sqlalchemy.FetchedValue):
355 if isinstance(column.server_default, sqlalchemy.FetchedValue):
@@ -368,9 +365,6 b' class ColumnDelta(DictMixin, sqlalchemy.'
368 if toinit:
365 if toinit:
369 column._init_items(*toinit)
366 column._init_items(*toinit)
370
367
371 if not SQLA_06:
372 column.args = []
373
374 def _get_table(self):
368 def _get_table(self):
375 return getattr(self, '_table', None)
369 return getattr(self, '_table', None)
376
370
@@ -469,14 +463,18 b' class ChangesetTable(object):'
469 self._set_parent(self.metadata)
463 self._set_parent(self.metadata)
470
464
471 def _meta_key(self):
465 def _meta_key(self):
466 """Get the meta key for this table."""
472 return sqlalchemy.schema._get_table_key(self.name, self.schema)
467 return sqlalchemy.schema._get_table_key(self.name, self.schema)
473
468
474 def deregister(self):
469 def deregister(self):
475 """Remove this table from its metadata"""
470 """Remove this table from its metadata"""
476 key = self._meta_key()
471 if SQLA_07:
477 meta = self.metadata
472 self.metadata._remove_table(self.name, self.schema)
478 if key in meta.tables:
473 else:
479 del meta.tables[key]
474 key = self._meta_key()
475 meta = self.metadata
476 if key in meta.tables:
477 del meta.tables[key]
480
478
481
479
482 class ChangesetColumn(object):
480 class ChangesetColumn(object):
@@ -83,6 +83,5 b' class NotSupportedError(Error):'
83 class InvalidConstraintError(Error):
83 class InvalidConstraintError(Error):
84 """Invalid constraint error"""
84 """Invalid constraint error"""
85
85
86
87 class MigrateDeprecationWarning(DeprecationWarning):
86 class MigrateDeprecationWarning(DeprecationWarning):
88 """Warning for deprecated features in Migrate"""
87 """Warning for deprecated features in Migrate"""
@@ -119,7 +119,7 b' def script_sql(database, description, re'
119
119
120 For instance, manage.py script_sql postgresql description creates:
120 For instance, manage.py script_sql postgresql description creates:
121 repository/versions/001_description_postgresql_upgrade.sql and
121 repository/versions/001_description_postgresql_upgrade.sql and
122 repository/versions/001_description_postgresql_postgres.sql
122 repository/versions/001_description_postgresql_downgrade.sql
123 """
123 """
124 repo = Repository(repository)
124 repo = Repository(repository)
125 repo.create_script_sql(database, description, **opts)
125 repo.create_script_sql(database, description, **opts)
@@ -212,14 +212,15 b' def test(url, repository, **opts):'
212 """
212 """
213 engine = opts.pop('engine')
213 engine = opts.pop('engine')
214 repos = Repository(repository)
214 repos = Repository(repository)
215 script = repos.version(None).script()
216
215
217 # Upgrade
216 # Upgrade
218 log.info("Upgrading...")
217 log.info("Upgrading...")
218 script = repos.version(None).script(engine.name, 'upgrade')
219 script.run(engine, 1)
219 script.run(engine, 1)
220 log.info("done")
220 log.info("done")
221
221
222 log.info("Downgrading...")
222 log.info("Downgrading...")
223 script = repos.version(None).script(engine.name, 'downgrade')
223 script.run(engine, -1)
224 script.run(engine, -1)
224 log.info("done")
225 log.info("done")
225 log.info("Success")
226 log.info("Success")
@@ -282,4 +282,3 b' class ModelGenerator(object):'
282 except:
282 except:
283 trans.rollback()
283 trans.rollback()
284 raise
284 raise
285
@@ -115,7 +115,7 b' class Repository(pathed.Pathed):'
115 options.setdefault('version_table', 'migrate_version')
115 options.setdefault('version_table', 'migrate_version')
116 options.setdefault('repository_id', name)
116 options.setdefault('repository_id', name)
117 options.setdefault('required_dbs', [])
117 options.setdefault('required_dbs', [])
118 options.setdefault('use_timestamp_numbering', '0')
118 options.setdefault('use_timestamp_numbering', False)
119
119
120 tmpl = open(os.path.join(tmpl_dir, cls._config)).read()
120 tmpl = open(os.path.join(tmpl_dir, cls._config)).read()
121 ret = TempitaTemplate(tmpl).substitute(options)
121 ret = TempitaTemplate(tmpl).substitute(options)
@@ -153,7 +153,7 b' class Repository(pathed.Pathed):'
153
153
154 def create_script(self, description, **k):
154 def create_script(self, description, **k):
155 """API to :meth:`migrate.versioning.version.Collection.create_new_python_version`"""
155 """API to :meth:`migrate.versioning.version.Collection.create_new_python_version`"""
156
156
157 k['use_timestamp_numbering'] = self.use_timestamp_numbering
157 k['use_timestamp_numbering'] = self.use_timestamp_numbering
158 self.versions.create_new_python_version(description, **k)
158 self.versions.create_new_python_version(description, **k)
159
159
@@ -180,9 +180,9 b' class Repository(pathed.Pathed):'
180 @property
180 @property
181 def use_timestamp_numbering(self):
181 def use_timestamp_numbering(self):
182 """Returns use_timestamp_numbering specified in config"""
182 """Returns use_timestamp_numbering specified in config"""
183 ts_numbering = self.config.get('db_settings', 'use_timestamp_numbering', raw=True)
183 if self.config.has_option('db_settings', 'use_timestamp_numbering'):
184
184 return self.config.getboolean('db_settings', 'use_timestamp_numbering')
185 return ts_numbering
185 return False
186
186
187 def version(self, *p, **k):
187 def version(self, *p, **k):
188 """API to :attr:`migrate.versioning.version.Collection.version`"""
188 """API to :attr:`migrate.versioning.version.Collection.version`"""
@@ -17,8 +17,16 b' def getDiffOfModelAgainstDatabase(metada'
17 :return: object which will evaluate to :keyword:`True` if there \
17 :return: object which will evaluate to :keyword:`True` if there \
18 are differences else :keyword:`False`.
18 are differences else :keyword:`False`.
19 """
19 """
20 return SchemaDiff(metadata,
20 db_metadata = sqlalchemy.MetaData(engine, reflect=True)
21 sqlalchemy.MetaData(engine, reflect=True),
21
22 # sqlite will include a dynamically generated 'sqlite_sequence' table if
23 # there are autoincrement sequences in the database; this should not be
24 # compared.
25 if engine.dialect.name == 'sqlite':
26 if 'sqlite_sequence' in db_metadata.tables:
27 db_metadata.remove(db_metadata.tables['sqlite_sequence'])
28
29 return SchemaDiff(metadata, db_metadata,
22 labelA='model',
30 labelA='model',
23 labelB='database',
31 labelB='database',
24 excludeTables=excludeTables)
32 excludeTables=excludeTables)
@@ -31,7 +39,7 b' def getDiffOfModelAgainstModel(metadataA'
31 :return: object which will evaluate to :keyword:`True` if there \
39 :return: object which will evaluate to :keyword:`True` if there \
32 are differences else :keyword:`False`.
40 are differences else :keyword:`False`.
33 """
41 """
34 return SchemaDiff(metadataA, metadataB, excludeTables)
42 return SchemaDiff(metadataA, metadataB, excludeTables=excludeTables)
35
43
36
44
37 class ColDiff(object):
45 class ColDiff(object):
@@ -7,4 +7,6 b" del _vars['__template_name__']"
7 _vars.pop('repository_name', None)
7 _vars.pop('repository_name', None)
8 defaults = ", ".join(["%s='%s'" % var for var in _vars.iteritems()])
8 defaults = ", ".join(["%s='%s'" % var for var in _vars.iteritems()])
9 }}
9 }}
10 main({{ defaults }})
10
11 if __name__ == '__main__':
12 main({{ defaults }})
@@ -26,4 +26,5 b' conf_dict = ConfigLoader(conf_path).pars'
26
26
27 # migrate supports passing url as an existing Engine instance (since 0.6.0)
27 # migrate supports passing url as an existing Engine instance (since 0.6.0)
28 # usage: migrate -c path/to/config.ini COMMANDS
28 # usage: migrate -c path/to/config.ini COMMANDS
29 main(url=engine_from_config(conf_dict), repository=migrations.__path__[0],{{ defaults }})
29 if __name__ == '__main__':
30 main(url=engine_from_config(conf_dict), repository=migrations.__path__[0],{{ defaults }})
@@ -158,7 +158,7 b' def with_engine(f, *a, **kw):'
158 kw['engine'] = engine
158 kw['engine'] = engine
159 return f(*a, **kw)
159 return f(*a, **kw)
160 finally:
160 finally:
161 if isinstance(engine, Engine):
161 if isinstance(engine, Engine) and engine is not url:
162 log.debug('Disposing SQLAlchemy engine %s', engine)
162 log.debug('Disposing SQLAlchemy engine %s', engine)
163 engine.dispose()
163 engine.dispose()
164
164
@@ -60,7 +60,7 b' class Collection(pathed.Pathed):'
60 and store them in self.versions
60 and store them in self.versions
61 """
61 """
62 super(Collection, self).__init__(path)
62 super(Collection, self).__init__(path)
63
63
64 # Create temporary list of files, allowing skipped version numbers.
64 # Create temporary list of files, allowing skipped version numbers.
65 files = os.listdir(path)
65 files = os.listdir(path)
66 if '1' in files:
66 if '1' in files:
@@ -90,9 +90,7 b' class Collection(pathed.Pathed):'
90 return max([VerNum(0)] + self.versions.keys())
90 return max([VerNum(0)] + self.versions.keys())
91
91
92 def _next_ver_num(self, use_timestamp_numbering):
92 def _next_ver_num(self, use_timestamp_numbering):
93 print use_timestamp_numbering
94 if use_timestamp_numbering == True:
93 if use_timestamp_numbering == True:
95 print "Creating new timestamp version!"
96 return VerNum(int(datetime.utcnow().strftime('%Y%m%d%H%M%S')))
94 return VerNum(int(datetime.utcnow().strftime('%Y%m%d%H%M%S')))
97 else:
95 else:
98 return self.latest + 1
96 return self.latest + 1
@@ -113,7 +111,7 b' class Collection(pathed.Pathed):'
113
111
114 script.PythonScript.create(filepath, **k)
112 script.PythonScript.create(filepath, **k)
115 self.versions[ver] = Version(ver, self.path, [filename])
113 self.versions[ver] = Version(ver, self.path, [filename])
116
114
117 def create_new_sql_version(self, database, description, **k):
115 def create_new_sql_version(self, database, description, **k):
118 """Create SQL files for new version"""
116 """Create SQL files for new version"""
119 ver = self._next_ver_num(k.pop('use_timestamp_numbering', False))
117 ver = self._next_ver_num(k.pop('use_timestamp_numbering', False))
@@ -133,7 +131,7 b' class Collection(pathed.Pathed):'
133 filepath = self._version_path(filename)
131 filepath = self._version_path(filename)
134 script.SqlScript.create(filepath, **k)
132 script.SqlScript.create(filepath, **k)
135 self.versions[ver].add_script(filepath)
133 self.versions[ver].add_script(filepath)
136
134
137 def version(self, vernum=None):
135 def version(self, vernum=None):
138 """Returns latest Version if vernum is not given.
136 """Returns latest Version if vernum is not given.
139 Otherwise, returns wanted version"""
137 Otherwise, returns wanted version"""
@@ -152,7 +150,7 b' class Collection(pathed.Pathed):'
152
150
153 class Version(object):
151 class Version(object):
154 """A single version in a collection
152 """A single version in a collection
155 :param vernum: Version Number
153 :param vernum: Version Number
156 :param path: Path to script files
154 :param path: Path to script files
157 :param filelist: List of scripts
155 :param filelist: List of scripts
158 :type vernum: int, VerNum
156 :type vernum: int, VerNum
@@ -169,7 +167,7 b' class Version(object):'
169
167
170 for script in filelist:
168 for script in filelist:
171 self.add_script(os.path.join(path, script))
169 self.add_script(os.path.join(path, script))
172
170
173 def script(self, database=None, operation=None):
171 def script(self, database=None, operation=None):
174 """Returns SQL or Python Script"""
172 """Returns SQL or Python Script"""
175 for db in (database, 'default'):
173 for db in (database, 'default'):
@@ -198,7 +196,7 b' class Version(object):'
198 def _add_script_sql(self, path):
196 def _add_script_sql(self, path):
199 basename = os.path.basename(path)
197 basename = os.path.basename(path)
200 match = self.SQL_FILENAME.match(basename)
198 match = self.SQL_FILENAME.match(basename)
201
199
202 if match:
200 if match:
203 basename = basename.replace('.sql', '')
201 basename = basename.replace('.sql', '')
204 parts = basename.split('_')
202 parts = basename.split('_')
@@ -14,7 +14,7 b' from rhodecode.lib.dbmigrate.migrate imp'
14
14
15 log = logging.getLogger(__name__)
15 log = logging.getLogger(__name__)
16
16
17 class RhodeCodeSettings(Base):
17 class RhodeCodeSetting(Base):
18 __tablename__ = 'rhodecode_settings'
18 __tablename__ = 'rhodecode_settings'
19 __table_args__ = (UniqueConstraint('app_settings_name'), {'useexisting':True})
19 __table_args__ = (UniqueConstraint('app_settings_name'), {'useexisting':True})
20 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
20 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
@@ -74,7 +74,7 b' class User(Base):'
74 self.last_login = datetime.datetime.now()
74 self.last_login = datetime.datetime.now()
75 session.add(self)
75 session.add(self)
76 session.commit()
76 session.commit()
77 log.debug('updated user %s lastlogin', self.username)
77 log.debug('updated user %s lastlogin' % self.username)
78 except (DatabaseError,):
78 except (DatabaseError,):
79 session.rollback()
79 session.rollback()
80
80
@@ -107,7 +107,7 b' class Repository(Base):'
107
107
108 user = relation('User')
108 user = relation('User')
109 fork = relation('Repository', remote_side=repo_id)
109 fork = relation('Repository', remote_side=repo_id)
110 repo_to_perm = relation('RepoToPerm', cascade='all')
110 repo_to_perm = relation('UserRepoToPerm', cascade='all')
111 stats = relation('Statistics', cascade='all', uselist=False)
111 stats = relation('Statistics', cascade='all', uselist=False)
112
112
113 repo_followers = relation('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
113 repo_followers = relation('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
@@ -126,7 +126,7 b' class Permission(Base):'
126 def __repr__(self):
126 def __repr__(self):
127 return "<Permission('%s:%s')>" % (self.permission_id, self.permission_name)
127 return "<Permission('%s:%s')>" % (self.permission_id, self.permission_name)
128
128
129 class RepoToPerm(Base):
129 class UserRepoToPerm(Base):
130 __tablename__ = 'repo_to_perm'
130 __tablename__ = 'repo_to_perm'
131 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'useexisting':True})
131 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'useexisting':True})
132 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
132 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
@@ -12,6 +12,7 b' from rhodecode.lib.dbmigrate.migrate.cha'
12
12
13 log = logging.getLogger(__name__)
13 log = logging.getLogger(__name__)
14
14
15
15 def upgrade(migrate_engine):
16 def upgrade(migrate_engine):
16 """ Upgrade operations go here.
17 """ Upgrade operations go here.
17 Don't create your own engine; bind migrate_engine to your metadata
18 Don't create your own engine; bind migrate_engine to your metadata
@@ -44,8 +45,6 b' def upgrade(migrate_engine):'
44 nullable=True, unique=None, default=None)
45 nullable=True, unique=None, default=None)
45 revision.create(tbl)
46 revision.create(tbl)
46
47
47
48
49 #==========================================================================
48 #==========================================================================
50 # Upgrade of `repositories` table
49 # Upgrade of `repositories` table
51 #==========================================================================
50 #==========================================================================
@@ -69,47 +68,18 b' def upgrade(migrate_engine):'
69 #==========================================================================
68 #==========================================================================
70 # Add table `user_followings`
69 # Add table `user_followings`
71 #==========================================================================
70 #==========================================================================
72 class UserFollowing(Base, BaseModel):
71 from rhodecode.lib.dbmigrate.schema.db_1_1_0 import UserFollowing
73 __tablename__ = 'user_followings'
74 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
75 UniqueConstraint('user_id', 'follows_user_id')
76 , {'useexisting':True})
77
78 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
79 user_id = Column("user_id", Integer(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
80 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey(u'repositories.repo_id'), nullable=True, unique=None, default=None)
81 follows_user_id = Column("follows_user_id", Integer(), ForeignKey(u'users.user_id'), nullable=True, unique=None, default=None)
82
83 user = relation('User', primaryjoin='User.user_id==UserFollowing.user_id')
84
85 follows_user = relation('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
86 follows_repository = relation('Repository')
87
88 UserFollowing().__table__.create()
72 UserFollowing().__table__.create()
89
73
90 #==========================================================================
74 #==========================================================================
91 # Add table `cache_invalidation`
75 # Add table `cache_invalidation`
92 #==========================================================================
76 #==========================================================================
93 class CacheInvalidation(Base, BaseModel):
77 from rhodecode.lib.dbmigrate.schema.db_1_1_0 import CacheInvalidation
94 __tablename__ = 'cache_invalidation'
95 __table_args__ = (UniqueConstraint('cache_key'), {'useexisting':True})
96 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
97 cache_key = Column("cache_key", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
98 cache_args = Column("cache_args", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
99 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
100
101
102 def __init__(self, cache_key, cache_args=''):
103 self.cache_key = cache_key
104 self.cache_args = cache_args
105 self.cache_active = False
106
107 def __repr__(self):
108 return "<CacheInvalidation('%s:%s')>" % (self.cache_id, self.cache_key)
109 CacheInvalidation().__table__.create()
78 CacheInvalidation().__table__.create()
110
79
111 return
80 return
112
81
82
113 def downgrade(migrate_engine):
83 def downgrade(migrate_engine):
114 meta = MetaData()
84 meta = MetaData()
115 meta.bind = migrate_engine
85 meta.bind = migrate_engine
@@ -13,6 +13,7 b' from rhodecode.model.meta import Base'
13
13
14 log = logging.getLogger(__name__)
14 log = logging.getLogger(__name__)
15
15
16
16 def upgrade(migrate_engine):
17 def upgrade(migrate_engine):
17 """ Upgrade operations go here.
18 """ Upgrade operations go here.
18 Don't create your own engine; bind migrate_engine to your metadata
19 Don't create your own engine; bind migrate_engine to your metadata
@@ -21,46 +22,46 b' def upgrade(migrate_engine):'
21 #==========================================================================
22 #==========================================================================
22 # Add table `groups``
23 # Add table `groups``
23 #==========================================================================
24 #==========================================================================
24 from rhodecode.model.db import Group
25 from rhodecode.lib.dbmigrate.schema.db_1_2_0 import RepoGroup as Group
25 Group().__table__.create()
26 Group().__table__.create()
26
27
27 #==========================================================================
28 #==========================================================================
28 # Add table `group_to_perm`
29 # Add table `group_to_perm`
29 #==========================================================================
30 #==========================================================================
30 from rhodecode.model.db import GroupToPerm
31 from rhodecode.lib.dbmigrate.schema.db_1_2_0 import UserRepoGroupToPerm
31 GroupToPerm().__table__.create()
32 UserRepoGroupToPerm().__table__.create()
32
33
33 #==========================================================================
34 #==========================================================================
34 # Add table `users_groups`
35 # Add table `users_groups`
35 #==========================================================================
36 #==========================================================================
36 from rhodecode.model.db import UsersGroup
37 from rhodecode.lib.dbmigrate.schema.db_1_2_0 import UsersGroup
37 UsersGroup().__table__.create()
38 UsersGroup().__table__.create()
38
39
39 #==========================================================================
40 #==========================================================================
40 # Add table `users_groups_members`
41 # Add table `users_groups_members`
41 #==========================================================================
42 #==========================================================================
42 from rhodecode.model.db import UsersGroupMember
43 from rhodecode.lib.dbmigrate.schema.db_1_2_0 import UsersGroupMember
43 UsersGroupMember().__table__.create()
44 UsersGroupMember().__table__.create()
44
45
45 #==========================================================================
46 #==========================================================================
46 # Add table `users_group_repo_to_perm`
47 # Add table `users_group_repo_to_perm`
47 #==========================================================================
48 #==========================================================================
48 from rhodecode.model.db import UsersGroupRepoToPerm
49 from rhodecode.lib.dbmigrate.schema.db_1_2_0 import UsersGroupRepoToPerm
49 UsersGroupRepoToPerm().__table__.create()
50 UsersGroupRepoToPerm().__table__.create()
50
51
51 #==========================================================================
52 #==========================================================================
52 # Add table `users_group_to_perm`
53 # Add table `users_group_to_perm`
53 #==========================================================================
54 #==========================================================================
54 from rhodecode.model.db import UsersGroupToPerm
55 from rhodecode.lib.dbmigrate.schema.db_1_2_0 import UsersGroupToPerm
55 UsersGroupToPerm().__table__.create()
56 UsersGroupToPerm().__table__.create()
56
57
57 #==========================================================================
58 #==========================================================================
58 # Upgrade of `users` table
59 # Upgrade of `users` table
59 #==========================================================================
60 #==========================================================================
60 from rhodecode.model.db import User
61 from rhodecode.lib.dbmigrate.schema.db_1_2_0 import User
61
62
62 #add column
63 #add column
63 ldap_dn = Column("ldap_dn", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
64 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
64 ldap_dn.create(User().__table__)
65 ldap_dn.create(User().__table__)
65
66
66 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
67 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
@@ -74,7 +75,7 b' def upgrade(migrate_engine):'
74 #==========================================================================
75 #==========================================================================
75 # Upgrade of `repositories` table
76 # Upgrade of `repositories` table
76 #==========================================================================
77 #==========================================================================
77 from rhodecode.model.db import Repository
78 from rhodecode.lib.dbmigrate.schema.db_1_2_0 import Repository
78
79
79 #ADD clone_uri column#
80 #ADD clone_uri column#
80
81
@@ -83,7 +84,7 b' def upgrade(migrate_engine):'
83 nullable=True, unique=False, default=None)
84 nullable=True, unique=False, default=None)
84
85
85 clone_uri.create(Repository().__table__)
86 clone_uri.create(Repository().__table__)
86
87
87 #ADD downloads column#
88 #ADD downloads column#
88 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
89 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
89 enable_downloads.create(Repository().__table__)
90 enable_downloads.create(Repository().__table__)
@@ -104,10 +105,10 b' def upgrade(migrate_engine):'
104 # Upgrade of `user_followings` table
105 # Upgrade of `user_followings` table
105 #==========================================================================
106 #==========================================================================
106
107
107 from rhodecode.model.db import UserFollowing
108 from rhodecode.lib.dbmigrate.schema.db_1_2_0 import UserFollowing
108
109
109 follows_from = Column('follows_from', DateTime(timezone=False),
110 follows_from = Column('follows_from', DateTime(timezone=False),
110 nullable=True, unique=None,
111 nullable=True, unique=None,
111 default=datetime.datetime.now)
112 default=datetime.datetime.now)
112 follows_from.create(UserFollowing().__table__)
113 follows_from.create(UserFollowing().__table__)
113
114
@@ -7,7 +7,7 b''
7
7
8 :created_on: Dec 11, 2010
8 :created_on: Dec 11, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
@@ -6,7 +6,8 b''
6 Set of custom exceptions used in RhodeCode
6 Set of custom exceptions used in RhodeCode
7
7
8 :created_on: Nov 17, 2010
8 :created_on: Nov 17, 2010
9 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
11 """
12 """
12 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
@@ -46,5 +47,6 b' class DefaultUserException(Exception):'
46 class UserOwnsReposException(Exception):
47 class UserOwnsReposException(Exception):
47 pass
48 pass
48
49
50
49 class UsersGroupsAssignedException(Exception):
51 class UsersGroupsAssignedException(Exception):
50 pass
52 pass
@@ -8,22 +8,24 b' import hashlib'
8 import StringIO
8 import StringIO
9 import urllib
9 import urllib
10 import math
10 import math
11 import logging
11
12
12 from datetime import datetime
13 from datetime import datetime
13 from pygments.formatters import HtmlFormatter
14 from pygments.formatters.html import HtmlFormatter
14 from pygments import highlight as code_highlight
15 from pygments import highlight as code_highlight
15 from pylons import url, request, config
16 from pylons import url, request, config
16 from pylons.i18n.translation import _, ungettext
17 from pylons.i18n.translation import _, ungettext
18 from hashlib import md5
17
19
18 from webhelpers.html import literal, HTML, escape
20 from webhelpers.html import literal, HTML, escape
19 from webhelpers.html.tools import *
21 from webhelpers.html.tools import *
20 from webhelpers.html.builder import make_tag
22 from webhelpers.html.builder import make_tag
21 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
23 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
22 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
24 end_form, file, form, hidden, image, javascript_link, link_to, \
23 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
25 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
24 password, textarea, title, ul, xml_declaration, radio
26 submit, text, password, textarea, title, ul, xml_declaration, radio
25 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
27 from webhelpers.html.tools import auto_link, button_to, highlight, \
26 mail_to, strip_links, strip_tags, tag_re
28 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
27 from webhelpers.number import format_byte_size, format_bit_size
29 from webhelpers.number import format_byte_size, format_bit_size
28 from webhelpers.pylonslib import Flash as _Flash
30 from webhelpers.pylonslib import Flash as _Flash
29 from webhelpers.pylonslib.secure_form import secure_form
31 from webhelpers.pylonslib.secure_form import secure_form
@@ -33,11 +35,15 b' from webhelpers.text import chop_at, col'
33 from webhelpers.date import time_ago_in_words
35 from webhelpers.date import time_ago_in_words
34 from webhelpers.paginate import Page
36 from webhelpers.paginate import Page
35 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
37 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
36 convert_boolean_attrs, NotGiven
38 convert_boolean_attrs, NotGiven, _make_safe_id_component
37
39
38 from vcs.utils.annotate import annotate_highlight
40 from rhodecode.lib.annotate import annotate_highlight
39 from rhodecode.lib.utils import repo_name_slug
41 from rhodecode.lib.utils import repo_name_slug
40 from rhodecode.lib import str2bool, safe_unicode, safe_str,get_changeset_safe
42 from rhodecode.lib import str2bool, safe_unicode, safe_str, get_changeset_safe
43 from rhodecode.lib.markup_renderer import MarkupRenderer
44
45 log = logging.getLogger(__name__)
46
41
47
42 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
48 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
43 """
49 """
@@ -49,6 +55,19 b' def _reset(name, value=None, id=NotGiven'
49 return HTML.input(**attrs)
55 return HTML.input(**attrs)
50
56
51 reset = _reset
57 reset = _reset
58 safeid = _make_safe_id_component
59
60
61 def FID(raw_id, path):
62 """
63 Creates a uniqe ID for filenode based on it's hash of path and revision
64 it's safe to use in urls
65
66 :param raw_id:
67 :param path:
68 """
69
70 return 'C-%s-%s' % (short_id(raw_id), md5(path).hexdigest()[:12])
52
71
53
72
54 def get_token():
73 def get_token():
@@ -104,10 +123,14 b' class _FilesBreadCrumbs(object):'
104 paths_l = paths.split('/')
123 paths_l = paths.split('/')
105 for cnt, p in enumerate(paths_l):
124 for cnt, p in enumerate(paths_l):
106 if p != '':
125 if p != '':
107 url_l.append(link_to(p, url('files_home',
126 url_l.append(link_to(p,
108 repo_name=repo_name,
127 url('files_home',
109 revision=rev,
128 repo_name=repo_name,
110 f_path='/'.join(paths_l[:cnt + 1]))))
129 revision=rev,
130 f_path='/'.join(paths_l[:cnt + 1])
131 )
132 )
133 )
111
134
112 return literal('/'.join(url_l))
135 return literal('/'.join(url_l))
113
136
@@ -198,13 +221,16 b' def pygmentize(filenode, **kwargs):'
198 return literal(code_highlight(filenode.content,
221 return literal(code_highlight(filenode.content,
199 filenode.lexer, CodeHtmlFormatter(**kwargs)))
222 filenode.lexer, CodeHtmlFormatter(**kwargs)))
200
223
224
201 def pygmentize_annotation(repo_name, filenode, **kwargs):
225 def pygmentize_annotation(repo_name, filenode, **kwargs):
202 """pygmentize function for annotation
226 """
227 pygmentize function for annotation
203
228
204 :param filenode:
229 :param filenode:
205 """
230 """
206
231
207 color_dict = {}
232 color_dict = {}
233
208 def gen_color(n=10000):
234 def gen_color(n=10000):
209 """generator for getting n of evenly distributed colors using
235 """generator for getting n of evenly distributed colors using
210 hsv color and golden ratio. It always return same order of colors
236 hsv color and golden ratio. It always return same order of colors
@@ -213,19 +239,26 b' def pygmentize_annotation(repo_name, fil'
213 """
239 """
214
240
215 def hsv_to_rgb(h, s, v):
241 def hsv_to_rgb(h, s, v):
216 if s == 0.0: return v, v, v
242 if s == 0.0:
217 i = int(h * 6.0) # XXX assume int() truncates!
243 return v, v, v
244 i = int(h * 6.0) # XXX assume int() truncates!
218 f = (h * 6.0) - i
245 f = (h * 6.0) - i
219 p = v * (1.0 - s)
246 p = v * (1.0 - s)
220 q = v * (1.0 - s * f)
247 q = v * (1.0 - s * f)
221 t = v * (1.0 - s * (1.0 - f))
248 t = v * (1.0 - s * (1.0 - f))
222 i = i % 6
249 i = i % 6
223 if i == 0: return v, t, p
250 if i == 0:
224 if i == 1: return q, v, p
251 return v, t, p
225 if i == 2: return p, v, t
252 if i == 1:
226 if i == 3: return p, q, v
253 return q, v, p
227 if i == 4: return t, p, v
254 if i == 2:
228 if i == 5: return v, p, q
255 return p, v, t
256 if i == 3:
257 return p, q, v
258 if i == 4:
259 return t, p, v
260 if i == 5:
261 return v, p, q
229
262
230 golden_ratio = 0.618033988749895
263 golden_ratio = 0.618033988749895
231 h = 0.22717784590367374
264 h = 0.22717784590367374
@@ -235,12 +268,12 b' def pygmentize_annotation(repo_name, fil'
235 h %= 1
268 h %= 1
236 HSV_tuple = [h, 0.95, 0.95]
269 HSV_tuple = [h, 0.95, 0.95]
237 RGB_tuple = hsv_to_rgb(*HSV_tuple)
270 RGB_tuple = hsv_to_rgb(*HSV_tuple)
238 yield map(lambda x:str(int(x * 256)), RGB_tuple)
271 yield map(lambda x: str(int(x * 256)), RGB_tuple)
239
272
240 cgenerator = gen_color()
273 cgenerator = gen_color()
241
274
242 def get_color_string(cs):
275 def get_color_string(cs):
243 if color_dict.has_key(cs):
276 if cs in color_dict:
244 col = color_dict[cs]
277 col = color_dict[cs]
245 else:
278 else:
246 col = color_dict[cs] = cgenerator.next()
279 col = color_dict[cs] = cgenerator.next()
@@ -275,6 +308,7 b' def pygmentize_annotation(repo_name, fil'
275
308
276 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
309 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
277
310
311
278 def is_following_repo(repo_name, user_id):
312 def is_following_repo(repo_name, user_id):
279 from rhodecode.model.scm import ScmModel
313 from rhodecode.model.scm import ScmModel
280 return ScmModel().is_following_repo(repo_name, user_id)
314 return ScmModel().is_following_repo(repo_name, user_id)
@@ -284,17 +318,75 b' flash = _Flash()'
284 #==============================================================================
318 #==============================================================================
285 # SCM FILTERS available via h.
319 # SCM FILTERS available via h.
286 #==============================================================================
320 #==============================================================================
287 from vcs.utils import author_name, author_email
321 from rhodecode.lib.vcs.utils import author_name, author_email
288 from rhodecode.lib import credentials_filter, age as _age
322 from rhodecode.lib import credentials_filter, age as _age
323 from rhodecode.model.db import User
289
324
290 age = lambda x:_age(x)
325 age = lambda x: _age(x)
291 capitalize = lambda x: x.capitalize()
326 capitalize = lambda x: x.capitalize()
292 email = author_email
327 email = author_email
293 email_or_none = lambda x: email(x) if email(x) != x else None
294 person = lambda x: author_name(x)
295 short_id = lambda x: x[:12]
328 short_id = lambda x: x[:12]
296 hide_credentials = lambda x: ''.join(credentials_filter(x))
329 hide_credentials = lambda x: ''.join(credentials_filter(x))
297
330
331
332 def is_git(repository):
333 if hasattr(repository, 'alias'):
334 _type = repository.alias
335 elif hasattr(repository, 'repo_type'):
336 _type = repository.repo_type
337 else:
338 _type = repository
339 return _type == 'git'
340
341
342 def is_hg(repository):
343 if hasattr(repository, 'alias'):
344 _type = repository.alias
345 elif hasattr(repository, 'repo_type'):
346 _type = repository.repo_type
347 else:
348 _type = repository
349 return _type == 'hg'
350
351
352 def email_or_none(author):
353 _email = email(author)
354 if _email != '':
355 return _email
356
357 # See if it contains a username we can get an email from
358 user = User.get_by_username(author_name(author), case_insensitive=True,
359 cache=True)
360 if user is not None:
361 return user.email
362
363 # No valid email, not a valid user in the system, none!
364 return None
365
366
367 def person(author):
368 # attr to return from fetched user
369 person_getter = lambda usr: usr.username
370
371 # Valid email in the attribute passed, see if they're in the system
372 _email = email(author)
373 if _email != '':
374 user = User.get_by_email(_email, case_insensitive=True, cache=True)
375 if user is not None:
376 return person_getter(user)
377 return _email
378
379 # Maybe it's a username?
380 _author = author_name(author)
381 user = User.get_by_username(_author, case_insensitive=True,
382 cache=True)
383 if user is not None:
384 return person_getter(user)
385
386 # Still nothing? Just pass back the author name then
387 return _author
388
389
298 def bool2icon(value):
390 def bool2icon(value):
299 """Returns True/False values represented as small html image of true/false
391 """Returns True/False values represented as small html image of true/false
300 icons
392 icons
@@ -314,7 +406,8 b' def bool2icon(value):'
314
406
315
407
316 def action_parser(user_log, feed=False):
408 def action_parser(user_log, feed=False):
317 """This helper will action_map the specified string action into translated
409 """
410 This helper will action_map the specified string action into translated
318 fancy names with icons and links
411 fancy names with icons and links
319
412
320 :param user_log: user log instance
413 :param user_log: user log instance
@@ -330,52 +423,84 b' def action_parser(user_log, feed=False):'
330 action, action_params = x
423 action, action_params = x
331
424
332 def get_cs_links():
425 def get_cs_links():
333 revs_limit = 3 #display this amount always
426 revs_limit = 3 # display this amount always
334 revs_top_limit = 50 #show upto this amount of changesets hidden
427 revs_top_limit = 50 # show upto this amount of changesets hidden
335 revs = action_params.split(',')
428 revs_ids = action_params.split(',')
429 deleted = user_log.repository is None
430 if deleted:
431 return ','.join(revs_ids)
432
336 repo_name = user_log.repository.repo_name
433 repo_name = user_log.repository.repo_name
337
434
338 from rhodecode.model.scm import ScmModel
339 repo = user_log.repository.scm_instance
435 repo = user_log.repository.scm_instance
340
436
341 message = lambda rev: get_changeset_safe(repo, rev).message
437 message = lambda rev: rev.message
438 lnk = lambda rev, repo_name: (
439 link_to('r%s:%s' % (rev.revision, rev.short_id),
440 url('changeset_home', repo_name=repo_name,
441 revision=rev.raw_id),
442 title=tooltip(message(rev)), class_='tooltip')
443 )
444 # get only max revs_top_limit of changeset for performance/ui reasons
445 revs = [
446 x for x in repo.get_changesets(revs_ids[0],
447 revs_ids[:revs_top_limit][-1])
448 ]
449
342 cs_links = []
450 cs_links = []
343 cs_links.append(" " + ', '.join ([link_to(rev,
451 cs_links.append(" " + ', '.join(
344 url('changeset_home',
452 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
345 repo_name=repo_name,
453 )
346 revision=rev), title=tooltip(message(rev)),
454 )
347 class_='tooltip') for rev in revs[:revs_limit] ]))
348
455
349 compare_view = (' <div class="compare_view tooltip" title="%s">'
456 compare_view = (
350 '<a href="%s">%s</a> '
457 ' <div class="compare_view tooltip" title="%s">'
351 '</div>' % (_('Show all combined changesets %s->%s' \
458 '<a href="%s">%s</a> </div>' % (
352 % (revs[0], revs[-1])),
459 _('Show all combined changesets %s->%s') % (
353 url('changeset_home', repo_name=repo_name,
460 revs_ids[0], revs_ids[-1]
354 revision='%s...%s' % (revs[0], revs[-1])
461 ),
355 ),
462 url('changeset_home', repo_name=repo_name,
356 _('compare view'))
463 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
357 )
464 ),
465 _('compare view')
466 )
467 )
358
468
359 if len(revs) > revs_limit:
469 # if we have exactly one more than normally displayed
360 uniq_id = revs[0]
470 # just display it, takes less space than displaying
361 html_tmpl = ('<span> %s '
471 # "and 1 more revisions"
362 '<a class="show_more" id="_%s" href="#more">%s</a> '
472 if len(revs_ids) == revs_limit + 1:
363 '%s</span>')
473 rev = revs[revs_limit]
474 cs_links.append(", " + lnk(rev, repo_name))
475
476 # hidden-by-default ones
477 if len(revs_ids) > revs_limit + 1:
478 uniq_id = revs_ids[0]
479 html_tmpl = (
480 '<span> %s <a class="show_more" id="_%s" '
481 'href="#more">%s</a> %s</span>'
482 )
364 if not feed:
483 if not feed:
365 cs_links.append(html_tmpl % (_('and'), uniq_id, _('%s more') \
484 cs_links.append(html_tmpl % (
366 % (len(revs) - revs_limit),
485 _('and'),
367 _('revisions')))
486 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
487 _('revisions')
488 )
489 )
368
490
369 if not feed:
491 if not feed:
370 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
492 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
371 else:
493 else:
372 html_tmpl = '<span id="%s"> %s </span>'
494 html_tmpl = '<span id="%s"> %s </span>'
373
495
374 cs_links.append(html_tmpl % (uniq_id, ', '.join([link_to(rev,
496 morelinks = ', '.join(
375 url('changeset_home',
497 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
376 repo_name=repo_name, revision=rev),
498 )
377 title=message(rev), class_='tooltip')
499
378 for rev in revs[revs_limit:revs_top_limit]])))
500 if len(revs_ids) > revs_top_limit:
501 morelinks += ', ...'
502
503 cs_links.append(html_tmpl % (uniq_id, morelinks))
379 if len(revs) > 1:
504 if len(revs) > 1:
380 cs_links.append(compare_view)
505 cs_links.append(compare_view)
381 return ''.join(cs_links)
506 return ''.join(cs_links)
@@ -385,36 +510,39 b' def action_parser(user_log, feed=False):'
385 return _('fork name ') + str(link_to(action_params, url('summary_home',
510 return _('fork name ') + str(link_to(action_params, url('summary_home',
386 repo_name=repo_name,)))
511 repo_name=repo_name,)))
387
512
388 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
513 action_map = {'user_deleted_repo': (_('[deleted] repository'), None),
389 'user_created_repo':(_('[created] repository'), None),
514 'user_created_repo': (_('[created] repository'), None),
390 'user_forked_repo':(_('[forked] repository'), get_fork_name),
515 'user_created_fork': (_('[created] repository as fork'), None),
391 'user_updated_repo':(_('[updated] repository'), None),
516 'user_forked_repo': (_('[forked] repository'), get_fork_name),
392 'admin_deleted_repo':(_('[delete] repository'), None),
517 'user_updated_repo': (_('[updated] repository'), None),
393 'admin_created_repo':(_('[created] repository'), None),
518 'admin_deleted_repo': (_('[delete] repository'), None),
394 'admin_forked_repo':(_('[forked] repository'), None),
519 'admin_created_repo': (_('[created] repository'), None),
395 'admin_updated_repo':(_('[updated] repository'), None),
520 'admin_forked_repo': (_('[forked] repository'), None),
396 'push':(_('[pushed] into'), get_cs_links),
521 'admin_updated_repo': (_('[updated] repository'), None),
397 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
522 'push': (_('[pushed] into'), get_cs_links),
398 'push_remote':(_('[pulled from remote] into'), get_cs_links),
523 'push_local': (_('[committed via RhodeCode] into'), get_cs_links),
399 'pull':(_('[pulled] from'), None),
524 'push_remote': (_('[pulled from remote] into'), get_cs_links),
400 'started_following_repo':(_('[started following] repository'), None),
525 'pull': (_('[pulled] from'), None),
401 'stopped_following_repo':(_('[stopped following] repository'), None),
526 'started_following_repo': (_('[started following] repository'), None),
527 'stopped_following_repo': (_('[stopped following] repository'), None),
402 }
528 }
403
529
404 action_str = action_map.get(action, action)
530 action_str = action_map.get(action, action)
405 if feed:
531 if feed:
406 action = action_str[0].replace('[', '').replace(']', '')
532 action = action_str[0].replace('[', '').replace(']', '')
407 else:
533 else:
408 action = action_str[0].replace('[', '<span class="journal_highlight">')\
534 action = action_str[0]\
409 .replace(']', '</span>')
535 .replace('[', '<span class="journal_highlight">')\
536 .replace(']', '</span>')
410
537
411 action_params_func = lambda :""
538 action_params_func = lambda: ""
412
539
413 if callable(action_str[1]):
540 if callable(action_str[1]):
414 action_params_func = action_str[1]
541 action_params_func = action_str[1]
415
542
416 return [literal(action), action_params_func]
543 return [literal(action), action_params_func]
417
544
545
418 def action_parser_icon(user_log):
546 def action_parser_icon(user_log):
419 action = user_log.action
547 action = user_log.action
420 action_params = None
548 action_params = None
@@ -426,6 +554,7 b' def action_parser_icon(user_log):'
426 tmpl = """<img src="%s%s" alt="%s"/>"""
554 tmpl = """<img src="%s%s" alt="%s"/>"""
427 map = {'user_deleted_repo':'database_delete.png',
555 map = {'user_deleted_repo':'database_delete.png',
428 'user_created_repo':'database_add.png',
556 'user_created_repo':'database_add.png',
557 'user_created_fork':'arrow_divide.png',
429 'user_forked_repo':'arrow_divide.png',
558 'user_forked_repo':'arrow_divide.png',
430 'user_updated_repo':'database_edit.png',
559 'user_updated_repo':'database_edit.png',
431 'admin_deleted_repo':'database_delete.png',
560 'admin_deleted_repo':'database_delete.png',
@@ -449,6 +578,7 b' def action_parser_icon(user_log):'
449 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
578 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
450 HasRepoPermissionAny, HasRepoPermissionAll
579 HasRepoPermissionAny, HasRepoPermissionAll
451
580
581
452 #==============================================================================
582 #==============================================================================
453 # GRAVATAR URL
583 # GRAVATAR URL
454 #==============================================================================
584 #==============================================================================
@@ -456,7 +586,8 b' HasRepoPermissionAny, HasRepoPermissionA'
456 def gravatar_url(email_address, size=30):
586 def gravatar_url(email_address, size=30):
457 if (not str2bool(config['app_conf'].get('use_gravatar')) or
587 if (not str2bool(config['app_conf'].get('use_gravatar')) or
458 not email_address or email_address == 'anonymous@rhodecode.org'):
588 not email_address or email_address == 'anonymous@rhodecode.org'):
459 return url("/images/user%s.png" % size)
589 f = lambda a, l: min(l, key=lambda x: abs(x - a))
590 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
460
591
461 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
592 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
462 default = 'identicon'
593 default = 'identicon'
@@ -469,7 +600,7 b' def gravatar_url(email_address, size=30)'
469 email_address = safe_str(email_address)
600 email_address = safe_str(email_address)
470 # construct the url
601 # construct the url
471 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
602 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
472 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
603 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
473
604
474 return gravatar_url
605 return gravatar_url
475
606
@@ -480,7 +611,7 b' def gravatar_url(email_address, size=30)'
480 class RepoPage(Page):
611 class RepoPage(Page):
481
612
482 def __init__(self, collection, page=1, items_per_page=20,
613 def __init__(self, collection, page=1, items_per_page=20,
483 item_count=None, url=None, branch_name=None, **kwargs):
614 item_count=None, url=None, **kwargs):
484
615
485 """Create a "RepoPage" instance. special pager for paging
616 """Create a "RepoPage" instance. special pager for paging
486 repository
617 repository
@@ -498,7 +629,7 b' class RepoPage(Page):'
498 # The self.page is the number of the current page.
629 # The self.page is the number of the current page.
499 # The first page has the number 1!
630 # The first page has the number 1!
500 try:
631 try:
501 self.page = int(page) # make it int() if we get it as a string
632 self.page = int(page) # make it int() if we get it as a string
502 except (ValueError, TypeError):
633 except (ValueError, TypeError):
503 self.page = 1
634 self.page = 1
504
635
@@ -518,7 +649,8 b' class RepoPage(Page):'
518 self.items_per_page))
649 self.items_per_page))
519 self.last_page = self.first_page + self.page_count - 1
650 self.last_page = self.first_page + self.page_count - 1
520
651
521 # Make sure that the requested page number is the range of valid pages
652 # Make sure that the requested page number is the range of
653 # valid pages
522 if self.page > self.last_page:
654 if self.page > self.last_page:
523 self.page = self.last_page
655 self.page = self.last_page
524 elif self.page < self.first_page:
656 elif self.page < self.first_page:
@@ -531,11 +663,7 b' class RepoPage(Page):'
531 self.last_item = ((self.item_count - 1) - items_per_page *
663 self.last_item = ((self.item_count - 1) - items_per_page *
532 (self.page - 1))
664 (self.page - 1))
533
665
534 iterator = self.collection.get_changesets(start=self.first_item,
666 self.items = list(self.collection[self.first_item:self.last_item + 1])
535 end=self.last_item,
536 reverse=True,
537 branch_name=branch_name)
538 self.items = list(iterator)
539
667
540 # Links to previous and next page
668 # Links to previous and next page
541 if self.page > self.first_page:
669 if self.page > self.first_page:
@@ -560,14 +688,14 b' class RepoPage(Page):'
560 self.items = []
688 self.items = []
561
689
562 # This is a subclass of the 'list' type. Initialise the list now.
690 # This is a subclass of the 'list' type. Initialise the list now.
563 list.__init__(self, self.items)
691 list.__init__(self, reversed(self.items))
564
692
565
693
566 def changed_tooltip(nodes):
694 def changed_tooltip(nodes):
567 """
695 """
568 Generates a html string for changed nodes in changeset page.
696 Generates a html string for changed nodes in changeset page.
569 It limits the output to 30 entries
697 It limits the output to 30 entries
570
698
571 :param nodes: LazyNodesGenerator
699 :param nodes: LazyNodesGenerator
572 """
700 """
573 if nodes:
701 if nodes:
@@ -581,15 +709,14 b' def changed_tooltip(nodes):'
581 return ': ' + _('No Files')
709 return ': ' + _('No Files')
582
710
583
711
584
585 def repo_link(groups_and_repos):
712 def repo_link(groups_and_repos):
586 """
713 """
587 Makes a breadcrumbs link to repo within a group
714 Makes a breadcrumbs link to repo within a group
588 joins &raquo; on each group to create a fancy link
715 joins &raquo; on each group to create a fancy link
589
716
590 ex::
717 ex::
591 group >> subgroup >> repo
718 group >> subgroup >> repo
592
719
593 :param groups_and_repos:
720 :param groups_and_repos:
594 """
721 """
595 groups, repo_name = groups_and_repos
722 groups, repo_name = groups_and_repos
@@ -603,11 +730,12 b' def repo_link(groups_and_repos):'
603 return literal(' &raquo; '.join(map(make_link, groups)) + \
730 return literal(' &raquo; '.join(map(make_link, groups)) + \
604 " &raquo; " + repo_name)
731 " &raquo; " + repo_name)
605
732
733
606 def fancy_file_stats(stats):
734 def fancy_file_stats(stats):
607 """
735 """
608 Displays a fancy two colored bar for number of added/deleted
736 Displays a fancy two colored bar for number of added/deleted
609 lines of code on file
737 lines of code on file
610
738
611 :param stats: two element list of added/deleted lines of code
739 :param stats: two element list of added/deleted lines of code
612 """
740 """
613
741
@@ -630,13 +758,12 b' def fancy_file_stats(stats):'
630 a_v = a if a > 0 else ''
758 a_v = a if a > 0 else ''
631 d_v = d if d > 0 else ''
759 d_v = d if d > 0 else ''
632
760
633
634 def cgen(l_type):
761 def cgen(l_type):
635 mapping = {'tr':'top-right-rounded-corner',
762 mapping = {'tr': 'top-right-rounded-corner',
636 'tl':'top-left-rounded-corner',
763 'tl': 'top-left-rounded-corner',
637 'br':'bottom-right-rounded-corner',
764 'br': 'bottom-right-rounded-corner',
638 'bl':'bottom-left-rounded-corner'}
765 'bl': 'bottom-left-rounded-corner'}
639 map_getter = lambda x:mapping[x]
766 map_getter = lambda x: mapping[x]
640
767
641 if l_type == 'a' and d_v:
768 if l_type == 'a' and d_v:
642 #case when added and deleted are present
769 #case when added and deleted are present
@@ -651,22 +778,137 b' def fancy_file_stats(stats):'
651 if l_type == 'd' and not a_v:
778 if l_type == 'd' and not a_v:
652 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
779 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
653
780
654
781 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
655
782 cgen('a'), a_p, a_v
656 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
783 )
657 a_p, a_v)
784 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
658 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
785 cgen('d'), d_p, d_v
659 d_p, d_v)
786 )
660 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
787 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
661
788
662
789
663 def urlify_text(text):
790 def urlify_text(text_):
664 import re
791 import re
665
792
666 url_pat = re.compile(r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)')
793 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
794 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
667
795
668 def url_func(match_obj):
796 def url_func(match_obj):
669 url_full = match_obj.groups()[0]
797 url_full = match_obj.groups()[0]
670 return '<a href="%(url)s">%(url)s</a>' % ({'url':url_full})
798 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
799
800 return literal(url_pat.sub(url_func, text_))
801
802
803 def urlify_changesets(text_, repository):
804 import re
805 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
806
807 def url_func(match_obj):
808 rev = match_obj.groups()[0]
809 pref = ''
810 if match_obj.group().startswith(' '):
811 pref = ' '
812 tmpl = (
813 '%(pref)s<a class="%(cls)s" href="%(url)s">'
814 '%(rev)s'
815 '</a>'
816 )
817 return tmpl % {
818 'pref': pref,
819 'cls': 'revision-link',
820 'url': url('changeset_home', repo_name=repository, revision=rev),
821 'rev': rev,
822 }
823
824 newtext = URL_PAT.sub(url_func, text_)
825
826 return newtext
827
828
829 def urlify_commit(text_, repository=None, link_=None):
830 """
831 Parses given text message and makes proper links.
832 issues are linked to given issue-server, and rest is a changeset link
833 if link_ is given, in other case it's a plain text
834
835 :param text_:
836 :param repository:
837 :param link_: changeset link
838 """
839 import re
840 import traceback
841
842 # urlify changesets
843 text_ = urlify_changesets(text_, repository)
844
845 def linkify_others(t, l):
846 urls = re.compile(r'(\<a.*?\<\/a\>)',)
847 links = []
848 for e in urls.split(t):
849 if not urls.match(e):
850 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
851 else:
852 links.append(e)
671
853
672 return literal(url_pat.sub(url_func, text))
854 return ''.join(links)
855 try:
856 conf = config['app_conf']
857
858 URL_PAT = re.compile(r'%s' % conf.get('issue_pat'))
859
860 if URL_PAT:
861 ISSUE_SERVER_LNK = conf.get('issue_server_link')
862 ISSUE_PREFIX = conf.get('issue_prefix')
863
864 def url_func(match_obj):
865 pref = ''
866 if match_obj.group().startswith(' '):
867 pref = ' '
868
869 issue_id = ''.join(match_obj.groups())
870 tmpl = (
871 '%(pref)s<a class="%(cls)s" href="%(url)s">'
872 '%(issue-prefix)s%(id-repr)s'
873 '</a>'
874 )
875 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
876 if repository:
877 url = url.replace('{repo}', repository)
878
879 return tmpl % {
880 'pref': pref,
881 'cls': 'issue-tracker-link',
882 'url': url,
883 'id-repr': issue_id,
884 'issue-prefix': ISSUE_PREFIX,
885 'serv': ISSUE_SERVER_LNK,
886 }
887
888 newtext = URL_PAT.sub(url_func, text_)
889
890 if link_:
891 # wrap not links into final link => link_
892 newtext = linkify_others(newtext, link_)
893
894 return literal(newtext)
895 except:
896 log.error(traceback.format_exc())
897 pass
898
899 return text_
900
901
902 def rst(source):
903 return literal('<div class="rst-block">%s</div>' %
904 MarkupRenderer.rst(source))
905
906
907 def rst_w_mentions(source):
908 """
909 Wrapped rst renderer with @mention highlighting
910
911 :param source:
912 """
913 return literal('<div class="rst-block">%s</div>' %
914 MarkupRenderer.rst_with_mentions(source))
@@ -7,7 +7,7 b''
7
7
8 :created_on: Aug 6, 2010
8 :created_on: Aug 6, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
@@ -33,15 +33,14 b' from rhodecode.lib.utils import action_l'
33
33
34
34
35 def repo_size(ui, repo, hooktype=None, **kwargs):
35 def repo_size(ui, repo, hooktype=None, **kwargs):
36 """Presents size of repository after push
36 """
37 Presents size of repository after push
37
38
38 :param ui:
39 :param ui:
39 :param repo:
40 :param repo:
40 :param hooktype:
41 :param hooktype:
41 """
42 """
42
43
43 if hooktype != 'changegroup':
44 return False
45 size_hg, size_root = 0, 0
44 size_hg, size_root = 0, 0
46 for path, dirs, files in os.walk(repo.root):
45 for path, dirs, files in os.walk(repo.root):
47 if path.find('.hg') != -1:
46 if path.find('.hg') != -1:
@@ -60,12 +59,20 b' def repo_size(ui, repo, hooktype=None, *'
60 size_hg_f = h.format_byte_size(size_hg)
59 size_hg_f = h.format_byte_size(size_hg)
61 size_root_f = h.format_byte_size(size_root)
60 size_root_f = h.format_byte_size(size_root)
62 size_total_f = h.format_byte_size(size_root + size_hg)
61 size_total_f = h.format_byte_size(size_root + size_hg)
63 sys.stdout.write('Repository size .hg:%s repo:%s total:%s\n' \
62
64 % (size_hg_f, size_root_f, size_total_f))
63 last_cs = repo[len(repo) - 1]
64
65 msg = ('Repository size .hg:%s repo:%s total:%s\n'
66 'Last revision is now r%s:%s\n') % (
67 size_hg_f, size_root_f, size_total_f, last_cs.rev(), last_cs.hex()[:12]
68 )
69
70 sys.stdout.write(msg)
65
71
66
72
67 def log_pull_action(ui, repo, **kwargs):
73 def log_pull_action(ui, repo, **kwargs):
68 """Logs user last pull action
74 """
75 Logs user last pull action
69
76
70 :param ui:
77 :param ui:
71 :param repo:
78 :param repo:
@@ -76,13 +83,15 b' def log_pull_action(ui, repo, **kwargs):'
76 repository = extra_params['repository']
83 repository = extra_params['repository']
77 action = 'pull'
84 action = 'pull'
78
85
79 action_logger(username, action, repository, extra_params['ip'])
86 action_logger(username, action, repository, extra_params['ip'],
87 commit=True)
80
88
81 return 0
89 return 0
82
90
83
91
84 def log_push_action(ui, repo, **kwargs):
92 def log_push_action(ui, repo, **kwargs):
85 """Maps user last push action to new changeset id, from mercurial
93 """
94 Maps user last push action to new changeset id, from mercurial
86
95
87 :param ui:
96 :param ui:
88 :param repo:
97 :param repo:
@@ -110,6 +119,37 b' def log_push_action(ui, repo, **kwargs):'
110
119
111 action = action % ','.join(revs)
120 action = action % ','.join(revs)
112
121
113 action_logger(username, action, repository, extra_params['ip'])
122 action_logger(username, action, repository, extra_params['ip'],
123 commit=True)
114
124
115 return 0
125 return 0
126
127
128 def log_create_repository(repository_dict, created_by, **kwargs):
129 """
130 Post create repository Hook. This is a dummy function for admins to re-use
131 if needed
132
133 :param repository: dict dump of repository object
134 :param created_by: username who created repository
135 :param created_date: date of creation
136
137 available keys of repository_dict:
138
139 'repo_type',
140 'description',
141 'private',
142 'created_on',
143 'enable_downloads',
144 'repo_id',
145 'user_id',
146 'enable_statistics',
147 'clone_uri',
148 'fork_id',
149 'group_id',
150 'repo_name'
151
152 """
153
154
155 return 0
@@ -7,7 +7,7 b''
7
7
8 :created_on: Aug 17, 2010
8 :created_on: Aug 17, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
@@ -37,38 +37,39 b' from whoosh.analysis import RegexTokeniz'
37 from whoosh.fields import TEXT, ID, STORED, Schema, FieldType
37 from whoosh.fields import TEXT, ID, STORED, Schema, FieldType
38 from whoosh.index import create_in, open_dir
38 from whoosh.index import create_in, open_dir
39 from whoosh.formats import Characters
39 from whoosh.formats import Characters
40 from whoosh.highlight import highlight, SimpleFragmenter, HtmlFormatter
40 from whoosh.highlight import highlight, HtmlFormatter, ContextFragmenter
41
41
42 from webhelpers.html.builder import escape
42 from webhelpers.html.builder import escape
43 from sqlalchemy import engine_from_config
43 from sqlalchemy import engine_from_config
44 from vcs.utils.lazy import LazyProperty
45
44
46 from rhodecode.model import init_model
45 from rhodecode.model import init_model
47 from rhodecode.model.scm import ScmModel
46 from rhodecode.model.scm import ScmModel
48 from rhodecode.model.repo import RepoModel
47 from rhodecode.model.repo import RepoModel
49 from rhodecode.config.environment import load_environment
48 from rhodecode.config.environment import load_environment
50 from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP
49 from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, LazyProperty
51 from rhodecode.lib.utils import BasePasterCommand, Command, add_cache
50 from rhodecode.lib.utils import BasePasterCommand, Command, add_cache
52
51
53 #EXTENSIONS WE WANT TO INDEX CONTENT OFF
52 # EXTENSIONS WE WANT TO INDEX CONTENT OFF
54 INDEX_EXTENSIONS = LANGUAGES_EXTENSIONS_MAP.keys()
53 INDEX_EXTENSIONS = LANGUAGES_EXTENSIONS_MAP.keys()
55
54
56 #CUSTOM ANALYZER wordsplit + lowercase filter
55 # CUSTOM ANALYZER wordsplit + lowercase filter
57 ANALYZER = RegexTokenizer(expression=r"\w+") | LowercaseFilter()
56 ANALYZER = RegexTokenizer(expression=r"\w+") | LowercaseFilter()
58
57
59
58
60 #INDEX SCHEMA DEFINITION
59 #INDEX SCHEMA DEFINITION
61 SCHEMA = Schema(owner=TEXT(),
60 SCHEMA = Schema(
62 repository=TEXT(stored=True),
61 owner=TEXT(),
63 path=TEXT(stored=True),
62 repository=TEXT(stored=True),
64 content=FieldType(format=Characters(ANALYZER),
63 path=TEXT(stored=True),
65 scorable=True, stored=True),
64 content=FieldType(format=Characters(), analyzer=ANALYZER,
66 modtime=STORED(), extension=TEXT(stored=True))
65 scorable=True, stored=True),
67
66 modtime=STORED(),
67 extension=TEXT(stored=True)
68 )
68
69
69 IDX_NAME = 'HG_INDEX'
70 IDX_NAME = 'HG_INDEX'
70 FORMATTER = HtmlFormatter('span', between='\n<span class="break">...</span>\n')
71 FORMATTER = HtmlFormatter('span', between='\n<span class="break">...</span>\n')
71 FRAGMENTER = SimpleFragmenter(200)
72 FRAGMENTER = ContextFragmenter(200)
72
73
73
74
74 class MakeIndex(BasePasterCommand):
75 class MakeIndex(BasePasterCommand):
@@ -129,13 +130,14 b' class MakeIndex(BasePasterCommand):'
129 " destroy old and build from scratch",
130 " destroy old and build from scratch",
130 default=False)
131 default=False)
131
132
133
132 class ResultWrapper(object):
134 class ResultWrapper(object):
133 def __init__(self, search_type, searcher, matcher, highlight_items):
135 def __init__(self, search_type, searcher, matcher, highlight_items):
134 self.search_type = search_type
136 self.search_type = search_type
135 self.searcher = searcher
137 self.searcher = searcher
136 self.matcher = matcher
138 self.matcher = matcher
137 self.highlight_items = highlight_items
139 self.highlight_items = highlight_items
138 self.fragment_size = 200 / 2
140 self.fragment_size = 200
139
141
140 @LazyProperty
142 @LazyProperty
141 def doc_ids(self):
143 def doc_ids(self):
@@ -171,11 +173,10 b' class ResultWrapper(object):'
171 """
173 """
172 i, j = key.start, key.stop
174 i, j = key.start, key.stop
173
175
174 slice = []
176 slices = []
175 for docid in self.doc_ids[i:j]:
177 for docid in self.doc_ids[i:j]:
176 slice.append(self.get_full_content(docid))
178 slices.append(self.get_full_content(docid))
177 return slice
179 return slices
178
179
180
180 def get_full_content(self, docid):
181 def get_full_content(self, docid):
181 res = self.searcher.stored_fields(docid[0])
182 res = self.searcher.stored_fields(docid[0])
@@ -183,9 +184,9 b' class ResultWrapper(object):'
183 + len(res['repository']):].lstrip('/')
184 + len(res['repository']):].lstrip('/')
184
185
185 content_short = self.get_short_content(res, docid[1])
186 content_short = self.get_short_content(res, docid[1])
186 res.update({'content_short':content_short,
187 res.update({'content_short': content_short,
187 'content_short_hl':self.highlight(content_short),
188 'content_short_hl': self.highlight(content_short),
188 'f_path':f_path})
189 'f_path': f_path})
189
190
190 return res
191 return res
191
192
@@ -198,7 +199,7 b' class ResultWrapper(object):'
198 Smart function that implements chunking the content
199 Smart function that implements chunking the content
199 but not overlap chunks so it doesn't highlight the same
200 but not overlap chunks so it doesn't highlight the same
200 close occurrences twice.
201 close occurrences twice.
201
202
202 :param matcher:
203 :param matcher:
203 :param size:
204 :param size:
204 """
205 """
@@ -217,10 +218,12 b' class ResultWrapper(object):'
217 def highlight(self, content, top=5):
218 def highlight(self, content, top=5):
218 if self.search_type != 'content':
219 if self.search_type != 'content':
219 return ''
220 return ''
220 hl = highlight(escape(content),
221 hl = highlight(
221 self.highlight_items,
222 text=escape(content),
222 analyzer=ANALYZER,
223 terms=self.highlight_items,
223 fragmenter=FRAGMENTER,
224 analyzer=ANALYZER,
224 formatter=FORMATTER,
225 fragmenter=FRAGMENTER,
225 top=top)
226 formatter=FORMATTER,
227 top=top
228 )
226 return hl
229 return hl
@@ -7,7 +7,7 b''
7
7
8 :created_on: Jan 26, 2010
8 :created_on: Jan 26, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
@@ -43,12 +43,12 b' from rhodecode.model.scm import ScmModel'
43 from rhodecode.lib import safe_unicode
43 from rhodecode.lib import safe_unicode
44 from rhodecode.lib.indexers import INDEX_EXTENSIONS, SCHEMA, IDX_NAME
44 from rhodecode.lib.indexers import INDEX_EXTENSIONS, SCHEMA, IDX_NAME
45
45
46 from vcs.exceptions import ChangesetError, RepositoryError
46 from rhodecode.lib.vcs.exceptions import ChangesetError, RepositoryError, \
47 NodeDoesNotExistError
47
48
48 from whoosh.index import create_in, open_dir
49 from whoosh.index import create_in, open_dir
49
50
50
51
51
52 log = logging.getLogger('whooshIndexer')
52 log = logging.getLogger('whooshIndexer')
53 # create logger
53 # create logger
54 log.setLevel(logging.DEBUG)
54 log.setLevel(logging.DEBUG)
@@ -67,12 +67,13 b' ch.setFormatter(formatter)'
67 # add ch to logger
67 # add ch to logger
68 log.addHandler(ch)
68 log.addHandler(ch)
69
69
70
70 class WhooshIndexingDaemon(object):
71 class WhooshIndexingDaemon(object):
71 """
72 """
72 Daemon for atomic jobs
73 Daemon for atomic jobs
73 """
74 """
74
75
75 def __init__(self, indexname='HG_INDEX', index_location=None,
76 def __init__(self, indexname=IDX_NAME, index_location=None,
76 repo_location=None, sa=None, repo_list=None):
77 repo_location=None, sa=None, repo_list=None):
77 self.indexname = indexname
78 self.indexname = indexname
78
79
@@ -94,7 +95,6 b' class WhooshIndexingDaemon(object):'
94
95
95 self.repo_paths = filtered_repo_paths
96 self.repo_paths = filtered_repo_paths
96
97
97
98 self.initial = False
98 self.initial = False
99 if not os.path.isdir(self.index_location):
99 if not os.path.isdir(self.index_location):
100 os.makedirs(self.index_location)
100 os.makedirs(self.index_location)
@@ -154,7 +154,6 b' class WhooshIndexingDaemon(object):'
154 modtime=self.get_node_mtime(node),
154 modtime=self.get_node_mtime(node),
155 extension=node.extension)
155 extension=node.extension)
156
156
157
158 def build_index(self):
157 def build_index(self):
159 if os.path.exists(self.index_location):
158 if os.path.exists(self.index_location):
160 log.debug('removing previous index')
159 log.debug('removing previous index')
@@ -176,7 +175,6 b' class WhooshIndexingDaemon(object):'
176 writer.commit(merge=True)
175 writer.commit(merge=True)
177 log.debug('>>> FINISHED BUILDING INDEX <<<')
176 log.debug('>>> FINISHED BUILDING INDEX <<<')
178
177
179
180 def update_index(self):
178 def update_index(self):
181 log.debug('STARTING INCREMENTAL INDEXING UPDATE')
179 log.debug('STARTING INCREMENTAL INDEXING UPDATE')
182
180
@@ -198,7 +196,7 b' class WhooshIndexingDaemon(object):'
198
196
199 try:
197 try:
200 node = self.get_node(repo, indexed_path)
198 node = self.get_node(repo, indexed_path)
201 except ChangesetError:
199 except (ChangesetError, NodeDoesNotExistError):
202 # This file was deleted since it was indexed
200 # This file was deleted since it was indexed
203 log.debug('removing from index %s' % indexed_path)
201 log.debug('removing from index %s' % indexed_path)
204 writer.delete_by_term('path', indexed_path)
202 writer.delete_by_term('path', indexed_path)
@@ -7,7 +7,7 b''
7
7
8 :created_on: May 23, 2010
8 :created_on: May 23, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
@@ -8,7 +8,7 b''
8
8
9 :created_on: Apr 28, 2010
9 :created_on: Apr 28, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
@@ -27,7 +27,6 b''
27 import os
27 import os
28 import logging
28 import logging
29 import traceback
29 import traceback
30 import time
31
30
32 from dulwich import server as dulserver
31 from dulwich import server as dulserver
33
32
@@ -67,13 +66,12 b' dulserver.DEFAULT_HANDLERS = {'
67 from dulwich.repo import Repo
66 from dulwich.repo import Repo
68 from dulwich.web import HTTPGitApplication
67 from dulwich.web import HTTPGitApplication
69
68
70 from paste.auth.basic import AuthBasicAuthenticator
71 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
69 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
72
70
73 from rhodecode.lib import safe_str
71 from rhodecode.lib import safe_str
74 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
72 from rhodecode.lib.base import BaseVCSController
75 from rhodecode.lib.utils import invalidate_cache, is_valid_repo
73 from rhodecode.lib.auth import get_container_username
76 from rhodecode.model import meta
74 from rhodecode.lib.utils import is_valid_repo
77 from rhodecode.model.db import User
75 from rhodecode.model.db import User
78
76
79 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
77 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
@@ -93,24 +91,7 b' def is_git(environ):'
93 return False
91 return False
94
92
95
93
96 class SimpleGit(object):
94 class SimpleGit(BaseVCSController):
97
98 def __init__(self, application, config):
99 self.application = application
100 self.config = config
101 # base path of repo locations
102 self.basepath = self.config['base_path']
103 #authenticate this mercurial request using authfunc
104 self.authenticate = AuthBasicAuthenticator('', authfunc)
105
106 def __call__(self, environ, start_response):
107 start = time.time()
108 try:
109 return self._handle_request(environ, start_response)
110 finally:
111 log = logging.getLogger(self.__class__.__name__)
112 log.debug('Request time: %.3fs' % (time.time() - start))
113 meta.Session.remove()
114
95
115 def _handle_request(self, environ, start_response):
96 def _handle_request(self, environ, start_response):
116 if not is_git(environ):
97 if not is_git(environ):
@@ -143,9 +124,8 b' class SimpleGit(object):'
143 if action in ['pull', 'push']:
124 if action in ['pull', 'push']:
144 anonymous_user = self.__get_user('default')
125 anonymous_user = self.__get_user('default')
145 username = anonymous_user.username
126 username = anonymous_user.username
146 anonymous_perm = self.__check_permission(action,
127 anonymous_perm = self._check_permission(action, anonymous_user,
147 anonymous_user,
128 repo_name)
148 repo_name)
149
129
150 if anonymous_perm is not True or anonymous_user.active is False:
130 if anonymous_perm is not True or anonymous_user.active is False:
151 if anonymous_perm is not True:
131 if anonymous_perm is not True:
@@ -159,27 +139,29 b' class SimpleGit(object):'
159 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
139 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
160 #==============================================================
140 #==============================================================
161
141
162 if not REMOTE_USER(environ):
142 # Attempting to retrieve username from the container
143 username = get_container_username(environ, self.config)
144
145 # If not authenticated by the container, running basic auth
146 if not username:
163 self.authenticate.realm = \
147 self.authenticate.realm = \
164 safe_str(self.config['rhodecode_realm'])
148 safe_str(self.config['rhodecode_realm'])
165 result = self.authenticate(environ)
149 result = self.authenticate(environ)
166 if isinstance(result, str):
150 if isinstance(result, str):
167 AUTH_TYPE.update(environ, 'basic')
151 AUTH_TYPE.update(environ, 'basic')
168 REMOTE_USER.update(environ, result)
152 REMOTE_USER.update(environ, result)
153 username = result
169 else:
154 else:
170 return result.wsgi_application(environ, start_response)
155 return result.wsgi_application(environ, start_response)
171
156
172 #==============================================================
157 #==============================================================
173 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
158 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
174 # BASIC AUTH
175 #==============================================================
159 #==============================================================
176
177 if action in ['pull', 'push']:
160 if action in ['pull', 'push']:
178 username = REMOTE_USER(environ)
179 try:
161 try:
180 user = self.__get_user(username)
162 user = self.__get_user(username)
181 if user is None:
163 if user is None or not user.active:
182 return HTTPForbidden()(environ, start_response)
164 return HTTPForbidden()(environ, start_response)
183 username = user.username
165 username = user.username
184 except:
166 except:
185 log.error(traceback.format_exc())
167 log.error(traceback.format_exc())
@@ -187,16 +169,11 b' class SimpleGit(object):'
187 start_response)
169 start_response)
188
170
189 #check permissions for this repository
171 #check permissions for this repository
190 perm = self.__check_permission(action, user,
172 perm = self._check_permission(action, user,
191 repo_name)
173 repo_name)
192 if perm is not True:
174 if perm is not True:
193 return HTTPForbidden()(environ, start_response)
175 return HTTPForbidden()(environ, start_response)
194
176
195 extras = {'ip': ipaddr,
196 'username': username,
197 'action': action,
198 'repository': repo_name}
199
200 #===================================================================
177 #===================================================================
201 # GIT REQUEST HANDLING
178 # GIT REQUEST HANDLING
202 #===================================================================
179 #===================================================================
@@ -211,8 +188,8 b' class SimpleGit(object):'
211 try:
188 try:
212 #invalidate cache on push
189 #invalidate cache on push
213 if action == 'push':
190 if action == 'push':
214 self.__invalidate_cache(repo_name)
191 self._invalidate_cache(repo_name)
215
192 log.info('%s action on GIT repo "%s"' % (action, repo_name))
216 app = self.__make_app(repo_name, repo_path)
193 app = self.__make_app(repo_name, repo_path)
217 return app(environ, start_response)
194 return app(environ, start_response)
218 except Exception:
195 except Exception:
@@ -222,7 +199,7 b' class SimpleGit(object):'
222 def __make_app(self, repo_name, repo_path):
199 def __make_app(self, repo_name, repo_path):
223 """
200 """
224 Make an wsgi application using dulserver
201 Make an wsgi application using dulserver
225
202
226 :param repo_name: name of the repository
203 :param repo_name: name of the repository
227 :param repo_path: full path to the repository
204 :param repo_path: full path to the repository
228 """
205 """
@@ -233,31 +210,6 b' class SimpleGit(object):'
233
210
234 return gitserve
211 return gitserve
235
212
236 def __check_permission(self, action, user, repo_name):
237 """
238 Checks permissions using action (push/pull) user and repository
239 name
240
241 :param action: push or pull action
242 :param user: user instance
243 :param repo_name: repository name
244 """
245 if action == 'push':
246 if not HasPermissionAnyMiddleware('repository.write',
247 'repository.admin')(user,
248 repo_name):
249 return False
250
251 else:
252 #any other action need at least read permission
253 if not HasPermissionAnyMiddleware('repository.read',
254 'repository.write',
255 'repository.admin')(user,
256 repo_name):
257 return False
258
259 return True
260
261 def __get_repository(self, environ):
213 def __get_repository(self, environ):
262 """
214 """
263 Get's repository name out of PATH_INFO header
215 Get's repository name out of PATH_INFO header
@@ -265,6 +217,7 b' class SimpleGit(object):'
265 :param environ: environ where PATH_INFO is stored
217 :param environ: environ where PATH_INFO is stored
266 """
218 """
267 try:
219 try:
220 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
268 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
221 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
269 if repo_name.endswith('/'):
222 if repo_name.endswith('/'):
270 repo_name = repo_name.rstrip('/')
223 repo_name = repo_name.rstrip('/')
@@ -293,10 +246,3 b' class SimpleGit(object):'
293 service_cmd if service_cmd else 'other')
246 service_cmd if service_cmd else 'other')
294 else:
247 else:
295 return 'other'
248 return 'other'
296
297 def __invalidate_cache(self, repo_name):
298 """we know that some change was made to repositories and we should
299 invalidate the cache to see the changes right away but only for
300 push requests"""
301 invalidate_cache('get_repo_cached_%s' % repo_name)
302
@@ -8,7 +8,7 b''
8
8
9 :created_on: Apr 28, 2010
9 :created_on: Apr 28, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
@@ -27,19 +27,16 b''
27 import os
27 import os
28 import logging
28 import logging
29 import traceback
29 import traceback
30 import time
31
30
32 from mercurial.error import RepoError
31 from mercurial.error import RepoError
33 from mercurial.hgweb import hgweb_mod
32 from mercurial.hgweb import hgweb_mod
34
33
35 from paste.auth.basic import AuthBasicAuthenticator
36 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
34 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
37
35
38 from rhodecode.lib import safe_str
36 from rhodecode.lib import safe_str
39 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
37 from rhodecode.lib.base import BaseVCSController
40 from rhodecode.lib.utils import make_ui, invalidate_cache, \
38 from rhodecode.lib.auth import get_container_username
41 is_valid_repo, ui_sections
39 from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections
42 from rhodecode.model import meta
43 from rhodecode.model.db import User
40 from rhodecode.model.db import User
44
41
45 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
42 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
@@ -57,25 +54,7 b' def is_mercurial(environ):'
57 return False
54 return False
58
55
59
56
60 class SimpleHg(object):
57 class SimpleHg(BaseVCSController):
61
62 def __init__(self, application, config):
63 self.application = application
64 self.config = config
65 # base path of repo locations
66 self.basepath = self.config['base_path']
67 #authenticate this mercurial request using authfunc
68 self.authenticate = AuthBasicAuthenticator('', authfunc)
69 self.ipaddr = '0.0.0.0'
70
71 def __call__(self, environ, start_response):
72 start = time.time()
73 try:
74 return self._handle_request(environ, start_response)
75 finally:
76 log = logging.getLogger(self.__class__.__name__)
77 log.debug('Request time: %.3fs' % (time.time() - start))
78 meta.Session.remove()
79
58
80 def _handle_request(self, environ, start_response):
59 def _handle_request(self, environ, start_response):
81 if not is_mercurial(environ):
60 if not is_mercurial(environ):
@@ -101,7 +80,6 b' class SimpleHg(object):'
101 # GET ACTION PULL or PUSH
80 # GET ACTION PULL or PUSH
102 #======================================================================
81 #======================================================================
103 action = self.__get_action(environ)
82 action = self.__get_action(environ)
104
105 #======================================================================
83 #======================================================================
106 # CHECK ANONYMOUS PERMISSION
84 # CHECK ANONYMOUS PERMISSION
107 #======================================================================
85 #======================================================================
@@ -109,9 +87,8 b' class SimpleHg(object):'
109 anonymous_user = self.__get_user('default')
87 anonymous_user = self.__get_user('default')
110
88
111 username = anonymous_user.username
89 username = anonymous_user.username
112 anonymous_perm = self.__check_permission(action,
90 anonymous_perm = self._check_permission(action, anonymous_user,
113 anonymous_user,
91 repo_name)
114 repo_name)
115
92
116 if anonymous_perm is not True or anonymous_user.active is False:
93 if anonymous_perm is not True or anonymous_user.active is False:
117 if anonymous_perm is not True:
94 if anonymous_perm is not True:
@@ -125,26 +102,28 b' class SimpleHg(object):'
125 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
102 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
126 #==============================================================
103 #==============================================================
127
104
128 if not REMOTE_USER(environ):
105 # Attempting to retrieve username from the container
106 username = get_container_username(environ, self.config)
107
108 # If not authenticated by the container, running basic auth
109 if not username:
129 self.authenticate.realm = \
110 self.authenticate.realm = \
130 safe_str(self.config['rhodecode_realm'])
111 safe_str(self.config['rhodecode_realm'])
131 result = self.authenticate(environ)
112 result = self.authenticate(environ)
132 if isinstance(result, str):
113 if isinstance(result, str):
133 AUTH_TYPE.update(environ, 'basic')
114 AUTH_TYPE.update(environ, 'basic')
134 REMOTE_USER.update(environ, result)
115 REMOTE_USER.update(environ, result)
116 username = result
135 else:
117 else:
136 return result.wsgi_application(environ, start_response)
118 return result.wsgi_application(environ, start_response)
137
119
138 #==============================================================
120 #==============================================================
139 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
121 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
140 # BASIC AUTH
141 #==============================================================
122 #==============================================================
142
143 if action in ['pull', 'push']:
123 if action in ['pull', 'push']:
144 username = REMOTE_USER(environ)
145 try:
124 try:
146 user = self.__get_user(username)
125 user = self.__get_user(username)
147 if user is None:
126 if user is None or not user.active:
148 return HTTPForbidden()(environ, start_response)
127 return HTTPForbidden()(environ, start_response)
149 username = user.username
128 username = user.username
150 except:
129 except:
@@ -153,7 +132,7 b' class SimpleHg(object):'
153 start_response)
132 start_response)
154
133
155 #check permissions for this repository
134 #check permissions for this repository
156 perm = self.__check_permission(action, user,
135 perm = self._check_permission(action, user,
157 repo_name)
136 repo_name)
158 if perm is not True:
137 if perm is not True:
159 return HTTPForbidden()(environ, start_response)
138 return HTTPForbidden()(environ, start_response)
@@ -173,16 +152,15 b' class SimpleHg(object):'
173 baseui = make_ui('db')
152 baseui = make_ui('db')
174 self.__inject_extras(repo_path, baseui, extras)
153 self.__inject_extras(repo_path, baseui, extras)
175
154
176
177 # quick check if that dir exists...
155 # quick check if that dir exists...
178 if is_valid_repo(repo_name, self.basepath) is False:
156 if is_valid_repo(repo_name, self.basepath) is False:
179 return HTTPNotFound()(environ, start_response)
157 return HTTPNotFound()(environ, start_response)
180
158
181 try:
159 try:
182 #invalidate cache on push
160 # invalidate cache on push
183 if action == 'push':
161 if action == 'push':
184 self.__invalidate_cache(repo_name)
162 self._invalidate_cache(repo_name)
185
163 log.info('%s action on HG repo "%s"' % (action, repo_name))
186 app = self.__make_app(repo_path, baseui, extras)
164 app = self.__make_app(repo_path, baseui, extras)
187 return app(environ, start_response)
165 return app(environ, start_response)
188 except RepoError, e:
166 except RepoError, e:
@@ -199,32 +177,6 b' class SimpleHg(object):'
199 """
177 """
200 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
178 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
201
179
202
203 def __check_permission(self, action, user, repo_name):
204 """
205 Checks permissions using action (push/pull) user and repository
206 name
207
208 :param action: push or pull action
209 :param user: user instance
210 :param repo_name: repository name
211 """
212 if action == 'push':
213 if not HasPermissionAnyMiddleware('repository.write',
214 'repository.admin')(user,
215 repo_name):
216 return False
217
218 else:
219 #any other action need at least read permission
220 if not HasPermissionAnyMiddleware('repository.read',
221 'repository.write',
222 'repository.admin')(user,
223 repo_name):
224 return False
225
226 return True
227
228 def __get_repository(self, environ):
180 def __get_repository(self, environ):
229 """
181 """
230 Get's repository name out of PATH_INFO header
182 Get's repository name out of PATH_INFO header
@@ -232,6 +184,7 b' class SimpleHg(object):'
232 :param environ: environ where PATH_INFO is stored
184 :param environ: environ where PATH_INFO is stored
233 """
185 """
234 try:
186 try:
187 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
235 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
188 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
236 if repo_name.endswith('/'):
189 if repo_name.endswith('/'):
237 repo_name = repo_name.rstrip('/')
190 repo_name = repo_name.rstrip('/')
@@ -265,18 +218,12 b' class SimpleHg(object):'
265 else:
218 else:
266 return 'pull'
219 return 'pull'
267
220
268 def __invalidate_cache(self, repo_name):
269 """we know that some change was made to repositories and we should
270 invalidate the cache to see the changes right away but only for
271 push requests"""
272 invalidate_cache('get_repo_cached_%s' % repo_name)
273
274 def __inject_extras(self, repo_path, baseui, extras={}):
221 def __inject_extras(self, repo_path, baseui, extras={}):
275 """
222 """
276 Injects some extra params into baseui instance
223 Injects some extra params into baseui instance
277
224
278 also overwrites global settings with those takes from local hgrc file
225 also overwrites global settings with those takes from local hgrc file
279
226
280 :param baseui: baseui instance
227 :param baseui: baseui instance
281 :param extras: dict with extra params to put into baseui
228 :param extras: dict with extra params to put into baseui
282 """
229 """
@@ -298,4 +245,3 b' class SimpleHg(object):'
298 for section in ui_sections:
245 for section in ui_sections:
299 for k, v in repoui.configitems(section):
246 for k, v in repoui.configitems(section):
300 baseui.setconfig(section, k, v)
247 baseui.setconfig(section, k, v)
301
1 NO CONTENT: file renamed from rhodecode/lib/smtp_mailer.py to rhodecode/lib/rcmail/smtp_mailer.py
NO CONTENT: file renamed from rhodecode/lib/smtp_mailer.py to rhodecode/lib/rcmail/smtp_mailer.py
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file chmod 100755 => 100644
NO CONTENT: modified file chmod 100755 => 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from rhodecode/templates/settings/repo_fork.html to rhodecode/templates/forks/fork.html
NO CONTENT: file renamed from rhodecode/templates/settings/repo_fork.html to rhodecode/templates/forks/fork.html
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file chmod 100755 => 100644, file renamed from rhodecode/tests/test_concurency.py to rhodecode/tests/_test_concurency.py
NO CONTENT: modified file chmod 100755 => 100644, file renamed from rhodecode/tests/test_concurency.py to rhodecode/tests/_test_concurency.py
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed, binary diff hidden
NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed, binary diff hidden
NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed, binary diff hidden
NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed, binary diff hidden
NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed, binary diff hidden
NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed, binary diff hidden
NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed, binary diff hidden
NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed, binary diff hidden
NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now